"use client"; import { useState, useEffect, useTransition, useCallback } from "react"; import { useRouter } from "next/navigation"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { addResource, removeResource, transferResource, } from "@/app/(frontend)/logistics/structures/actions"; import { GameStructure, Structure, Resource } from "@/payload-types"; import { ArrowDownToLineIcon, ArrowUpFromLineIcon, ArrowRightLeftIcon, Loader2Icon, } from "lucide-react"; import { Progress } from "@/components/ui/progress"; import { cn, pluralize } from "@/lib/utils"; interface ManageStorageDialogProps { structureId: number; open: boolean; onOpenChange: (open: boolean) => void; } export function ManageStorageDialog({ structureId, open, onOpenChange, }: ManageStorageDialogProps) { const [structure, setStructure] = useState< (GameStructure & { type: Structure }) | null >(null); const [allStructures, setAllStructures] = useState< (GameStructure & { type: Structure })[] >([]); const [allResources, setAllResources] = useState([]); const [loading, setLoading] = useState(false); const [isPending, startTransition] = useTransition(); const router = useRouter(); const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null); // Form state const [depositResourceId, setDepositResourceId] = useState(""); const [depositAmount, setDepositAmount] = useState("1"); const [withdrawResourceId, setWithdrawResourceId] = useState(""); const [withdrawAmount, setWithdrawAmount] = useState("1"); const [transferToId, setTransferToId] = useState(""); const [transferResourceId, setTransferResourceId] = useState(""); const [transferAmount, setTransferAmount] = useState("1"); const fetchData = useCallback(async () => { setLoading(true); try { const [structRes, allRes, resourcesRes] = await Promise.all([ fetch(`/api/game-structures/${structureId}?depth=2`), fetch(`/api/game-structures?depth=2&limit=500`), fetch(`/api/resources?depth=0&limit=500`), ]); const structData = await structRes.json(); const allData = await allRes.json(); const resourcesData = await resourcesRes.json(); setStructure(structData); setAllStructures( allData.docs.filter( (s: GameStructure & { type: Structure }) => s.id !== structureId, ), ); setAllResources(resourcesData.docs ?? []); } catch { setMessage({ type: "error", text: "Failed to load structure data." }); } finally { setLoading(false); } }, [structureId]); useEffect(() => { if (open) { fetchData(); setMessage(null); setDepositResourceId(""); setDepositAmount("1"); setWithdrawResourceId(""); setWithdrawAmount("1"); setTransferToId(""); setTransferResourceId(""); setTransferAmount("1"); } }, [open, fetchData]); 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 remainingMass = maxMass ? maxMass - currentMass : null; 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 getDepositMax = (): number | undefined => { const resource = findResource(depositResourceId); if (!resource) return undefined; if (remainingMass === null) return undefined; const massPerUnit = resource.massPerUnit ?? 0; if (massPerUnit <= 0) return undefined; return Math.floor(remainingMass / massPerUnit); }; const getWithdrawMax = (): number | undefined => { const stored = findStoredResource(withdrawResourceId); return stored?.amount; }; const getTransferMax = (): number | undefined => { const stored = findStoredResource(transferResourceId); return stored?.amount; }; const clampAmount = (value: string, max: number | undefined): string => { const num = Number(value); if (isNaN(num) || num < 1) return "1"; if (max !== undefined && num > max) return String(max); return value; }; 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; const prohibited = structureType.prohibitedStorage?.some((rule) => { if (!rule.item) return false; const item = rule.item; if (typeof item === "object" && "id" in item) { return item.relationTo === "resources" && item.value === resourceId; } return false; }); return !prohibited; }; const handleDeposit = () => { if (!depositResourceId) return; const resId = Number(depositResourceId); const amt = Number(depositAmount); if (isNaN(amt) || amt <= 0) { setMessage({ type: "error", text: "Amount must be a positive number." }); return; } startTransition(async () => { const result = await addResource(structureId, resId, amt); if (result.success) { setMessage({ type: "success", text: "Resource deposited successfully." }); setDepositAmount("1"); await fetchData(); router.refresh(); } else { setMessage({ type: "error", text: result.error ?? "Unknown error." }); } }); }; const handleWithdraw = () => { if (!withdrawResourceId) return; const resId = Number(withdrawResourceId); const amt = Number(withdrawAmount); if (isNaN(amt) || amt <= 0) { setMessage({ type: "error", text: "Amount must be a positive number." }); return; } startTransition(async () => { const result = await removeResource(structureId, resId, amt); if (result.success) { setMessage({ type: "success", text: "Resource withdrawn successfully." }); setWithdrawAmount("1"); await fetchData(); router.refresh(); } else { setMessage({ type: "error", text: result.error ?? "Unknown error." }); } }); }; const handleTransfer = () => { if (!transferToId || !transferResourceId) return; const destId = Number(transferToId); const resId = Number(transferResourceId); const amt = Number(transferAmount); if (isNaN(amt) || amt <= 0) { setMessage({ type: "error", text: "Amount must be a positive number." }); return; } startTransition(async () => { const result = await transferResource(structureId, destId, resId, amt); if (result.success) { setMessage({ type: "success", text: "Transfer completed successfully." }); setTransferAmount("1"); await fetchData(); router.refresh(); } else { setMessage({ type: "error", text: result.error ?? "Unknown error." }); } }); }; return ( Manage Storage {structure?.name ?? "Loading..."} {loading ? (
) : ( <> {maxMass && (
Current capacity {Math.round(Math.min((currentMass / maxMass) * 100, 100))}%
{currentMass.toLocaleString()} kg used {maxMass.toLocaleString()} kg max
)} Deposit Withdraw Transfer
setDepositAmount(clampAmount(e.target.value, getDepositMax()))} /> {getDepositMax() !== undefined && ( Max: {getDepositMax()!.toLocaleString()} (limited by remaining capacity) )}
{renderCapacityPreview("deposit")}
setWithdrawAmount(clampAmount(e.target.value, getWithdrawMax()))} /> {getWithdrawMax() !== undefined && ( Max: {getWithdrawMax()!.toLocaleString()} stored )}
{renderCapacityPreview("withdraw")}
setTransferAmount(clampAmount(e.target.value, getTransferMax()))} /> {getTransferMax() !== undefined && ( Max: {getTransferMax()!.toLocaleString()} stored )}
{renderCapacityPreview("transfer")}
)} {message && (
{message.text}
)}
); }