From b719f25ff23ca8c3bf6bfdbff074776161ef28a0 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Mon, 27 Jul 2026 19:27:53 -0400 Subject: [PATCH] feat: use resource unitOfMeasure instead of hardcoded kg, add capacity preview to manage dialog - Replace hardcoded 'kg' labels with resource.unitOfMeasure in ResourceList - Rename 'Max Mass' to 'Max Capacity' on structure detail page - Add dynamic capacity preview bar to ManageStorageDialog deposit/withdraw/transfer tabs - Add baseline capacity display above tabs in manage dialog - Clean up unused imports across storage components --- .../logistics/structures/[id]/page.tsx | 4 +- .../frontend/storage/ManageStorageDialog.tsx | 122 ++++++++++++++++++ .../frontend/storage/ResourceList.tsx | 10 +- .../frontend/storage/StorageGrid.tsx | 2 +- .../frontend/storage/StorageRules.tsx | 1 - .../frontend/storage/StructureCard.tsx | 2 +- 6 files changed, 131 insertions(+), 10 deletions(-) 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")}