"use client"; import { useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { ArrowRightIcon, ChevronDownIcon, ChevronUpIcon, TruckIcon } from "lucide-react"; import type { GameStructure, GameVehicle, Shipment, Vehicle } from "@/payload-types"; import { ShipmentProgressBar } from "@/components/frontend/logistics/ShipmentProgressBar"; import { useGameTick } from "@/hooks/useGameTick"; type ActiveShipment = Shipment & { origin: GameStructure | number; destination: GameStructure | number; transportVehicle: GameVehicle & { type: Vehicle }; }; const statusBadge: Record = { dispatched: "bg-blue-500/20 text-blue-400", in_transit: "bg-amber-500/20 text-amber-400", }; const statusLabel: Record = { dispatched: "Dispatched", in_transit: "In Transit", }; // Module-scoped cache so the toast persists across re-mounts (page navigation // re-mounts this client component; without this it flashes away and back). const store: { shipments: ActiveShipment[]; open: boolean } = { shipments: [], open: false, }; export function ShipmentToasts() { const [shipments, setShipments] = useState(store.shipments); const [open, setOpenState] = useState(store.open); const setOpen = useCallback((value: boolean | ((v: boolean) => boolean)) => { setOpenState((prev) => { const next = typeof value === "function" ? value(prev) : value; store.open = next; return next; }); }, []); const fetchShipments = useCallback(async () => { const params = new URLSearchParams(); params.append("where[status][in][0]", "dispatched"); params.append("where[status][in][1]", "in_transit"); params.append("sort", "estimatedArrival"); params.append("limit", "20"); params.append("depth", "2"); try { const r = await fetch(`/api/shipments?${params.toString()}`); const body = await r.json(); store.shipments = body.docs ?? []; setShipments(store.shipments); } catch { store.shipments = []; setShipments([]); } }, []); useEffect(() => { fetchShipments(); const interval = setInterval(fetchShipments, 30_000); return () => clearInterval(interval); }, [fetchShipments]); useGameTick(() => fetchShipments()); if (shipments.length === 0) return null; const count = shipments.length; const countLabel = `${count} active shipment${count !== 1 ? "s" : ""}`; return (
{open && (
Active Shipments {count}
View all
{shipments.map((shipment) => { const origin = typeof shipment.origin === "object" && shipment.origin !== null ? (shipment.origin as GameStructure) : null; const destination = typeof shipment.destination === "object" && shipment.destination !== null ? (shipment.destination as GameStructure) : null; const vehicle = typeof shipment.transportVehicle === "object" && shipment.transportVehicle !== null ? (shipment.transportVehicle as GameVehicle & { type: Vehicle }) : null; const vehicleType = vehicle && typeof vehicle.type === "object" ? vehicle.type : null; return (
{shipment.name ?? `SHP-${shipment.id}`} {statusLabel[shipment.status] ?? shipment.status}
{origin?.name ?? `Structure #${shipment.origin}`} {destination?.name ?? `Structure #${shipment.destination}`}
{vehicleType && (

{vehicleType.name}

)} {shipment.dispatchedAt && shipment.estimatedArrival && (
)} ); })}
)}
); }