diff --git a/src/app/(frontend)/logistics/structures/[id]/page.tsx b/src/app/(frontend)/logistics/structures/[id]/page.tsx
index a16c7c4..3917394 100644
--- a/src/app/(frontend)/logistics/structures/[id]/page.tsx
+++ b/src/app/(frontend)/logistics/structures/[id]/page.tsx
@@ -182,8 +182,8 @@ export default async function StructureDetailPage({ params }: StructureDetailPag
- Max Mass
- {structureType.maxCapacityMass.toLocaleString()} kg
+ Max Capacity
+ {structureType.maxCapacityMass.toLocaleString()} kg (mass)
)}
diff --git a/src/components/frontend/storage/ManageStorageDialog.tsx b/src/components/frontend/storage/ManageStorageDialog.tsx
index 8b49ef1..49e97d1 100644
--- a/src/components/frontend/storage/ManageStorageDialog.tsx
+++ b/src/components/frontend/storage/ManageStorageDialog.tsx
@@ -31,6 +31,8 @@ import {
ArrowRightLeftIcon,
Loader2Icon,
} from "lucide-react";
+import { Progress } from "@/components/ui/progress";
+import { cn } from "@/lib/utils";
interface ManageStorageDialogProps {
structureId: number;
@@ -104,6 +106,103 @@ export function ManageStorageDialog({
const storedResources = structure?.storedResources ?? [];
const structureType = structure?.type;
+ const maxMass = structureType?.maxCapacityMass ?? null;
+
+ const currentMass = storedResources.reduce((total, entry) => {
+ const resource = entry.resource as Resource;
+ if (!resource || typeof resource === "number") return total;
+ return total + (resource.massPerUnit ?? 0) * entry.amount;
+ }, 0);
+
+ const findResource = (id: string): Resource | undefined =>
+ allResources.find((r) => r.id === Number(id));
+
+ const findStoredResource = (id: string): { resource: Resource; amount: number } | undefined => {
+ const entry = storedResources.find((e) => {
+ const res = e.resource;
+ return typeof res === "object" && res !== null && "id" in res
+ ? res.id === Number(id)
+ : res === Number(id);
+ });
+ if (!entry) return undefined;
+ const resource = entry.resource as Resource;
+ if (!resource || typeof resource === "number") return undefined;
+ return { resource, amount: entry.amount };
+ };
+
+ const getPreviewMass = (tab: "deposit" | "withdraw" | "transfer"): number | null => {
+ if (!maxMass) return null;
+
+ if (tab === "deposit") {
+ const resource = findResource(depositResourceId);
+ if (!resource) return null;
+ const amt = Number(depositAmount) || 0;
+ return currentMass + (resource.massPerUnit ?? 0) * amt;
+ }
+
+ if (tab === "withdraw") {
+ const stored = findStoredResource(withdrawResourceId);
+ if (!stored) return null;
+ const amt = Number(withdrawAmount) || 0;
+ return currentMass - (stored.resource.massPerUnit ?? 0) * amt;
+ }
+
+ if (tab === "transfer") {
+ const stored = findStoredResource(transferResourceId);
+ if (!stored) return null;
+ const amt = Number(transferAmount) || 0;
+ return currentMass - (stored.resource.massPerUnit ?? 0) * amt;
+ }
+
+ return null;
+ };
+
+ const renderCapacityPreview = (tab: "deposit" | "withdraw" | "transfer") => {
+ if (!maxMass) return null;
+
+ const previewMass = getPreviewMass(tab);
+ if (previewMass === null) return null;
+
+ const previewPercent = Math.min((previewMass / maxMass) * 100, 100);
+ const delta = previewMass - currentMass;
+ const isOverCapacity = previewMass > maxMass;
+
+ const getColor = (pct: number) => {
+ if (pct > 85) return "text-red-400";
+ if (pct > 60) return "text-yellow-400";
+ return "text-green-400";
+ };
+
+ const getBarColor = (pct: number) => {
+ if (pct > 85) return "[&>div]:bg-red-500";
+ if (pct > 60) return "[&>div]:bg-yellow-500";
+ return "[&>div]:bg-green-500";
+ };
+
+ return (
+
+
+
+ {tab === "deposit" ? "After deposit" : tab === "withdraw" ? "After withdrawal" : "After transfer"}
+
+
+ {Math.round(previewPercent)}%
+
+
+
+
+ {currentMass.toLocaleString()} kg → {previewMass.toLocaleString()} kg
+
+ {delta >= 0 ? "+" : ""}{delta.toLocaleString()} kg
+ {isOverCapacity && " (over capacity!)"}
+
+
+
+ );
+ };
const isResourceAllowed = (resourceId: number): boolean => {
if (!structureType) return true;
@@ -194,6 +293,25 @@ export function ManageStorageDialog({
) : (
+ <>
+ {maxMass && (
+
+
+ Current capacity
+
+ {Math.round(Math.min((currentMass / maxMass) * 100, 100))}%
+
+
+
+
+ {currentMass.toLocaleString()} kg used
+ {maxMass.toLocaleString()} kg max
+
+
+ )}
@@ -237,6 +355,7 @@ export function ManageStorageDialog({
onChange={(e) => setDepositAmount(e.target.value)}
/>
+ {renderCapacityPreview("deposit")}
+ >
)}
{message && (
diff --git a/src/components/frontend/storage/ResourceList.tsx b/src/components/frontend/storage/ResourceList.tsx
index 38cf711..6eaeba7 100644
--- a/src/components/frontend/storage/ResourceList.tsx
+++ b/src/components/frontend/storage/ResourceList.tsx
@@ -1,6 +1,5 @@
import { Resource, GameStructure } from "@/payload-types";
import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext";
-import { Progress } from "@/components/ui/progress";
import {
Item,
ItemContent,
@@ -67,9 +66,6 @@ export function ResourceList({ storedResources, maxCapacityMass }: ResourceListP
if (!resource || typeof resource === "number") return null;
const mass = (resource.massPerUnit ?? 0) * entry.amount;
- const massPercent = maxCapacityMass
- ? Math.min((mass / maxCapacityMass) * 100, 100)
- : null;
const description = resource.description
? convertLexicalToPlaintext({ data: resource.description })
: null;
@@ -106,8 +102,12 @@ export function ResourceList({ storedResources, maxCapacityMass }: ResourceListP
)}
-
{mass.toLocaleString()} kg
+
+ {entry.amount.toLocaleString()} {resource.unitOfMeasure}
+
+ {mass > 0 && `${mass.toLocaleString()} kg mass`}
+ {mass > 0 && " · "}
{(resource.baseValue * entry.amount).toLocaleString()} value
diff --git a/src/components/frontend/storage/StorageGrid.tsx b/src/components/frontend/storage/StorageGrid.tsx
index b8f0181..71b9493 100644
--- a/src/components/frontend/storage/StorageGrid.tsx
+++ b/src/components/frontend/storage/StorageGrid.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
-import { GameStructure, Faction, Structure, Resource } from "@/payload-types";
+import { GameStructure, Faction, Structure } from "@/payload-types";
import { StructureCard } from "@/components/frontend/storage/StructureCard";
import { StorageFilters } from "@/components/frontend/storage/StorageFilters";
diff --git a/src/components/frontend/storage/StorageRules.tsx b/src/components/frontend/storage/StorageRules.tsx
index 1c91f7d..ac187f5 100644
--- a/src/components/frontend/storage/StorageRules.tsx
+++ b/src/components/frontend/storage/StorageRules.tsx
@@ -2,7 +2,6 @@ import { Structure } from "@/payload-types";
import {
Item,
ItemContent,
- ItemDescription,
ItemHeader,
ItemTitle,
} from "@/components/ui/item";
diff --git a/src/components/frontend/storage/StructureCard.tsx b/src/components/frontend/storage/StructureCard.tsx
index ea05563..6519d15 100644
--- a/src/components/frontend/storage/StructureCard.tsx
+++ b/src/components/frontend/storage/StructureCard.tsx
@@ -18,7 +18,7 @@ import {
calculateStoredMass,
calculateUtilization,
} from "@/lib/logistics";
-import { GameStructure, Faction, Structure, Resource } from "@/payload-types";
+import { GameStructure, Faction, Structure } from "@/payload-types";
import {
BoxIcon,
Building2Icon,