121 lines
4.7 KiB
TypeScript
121 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import type { GameStructure, GameVehicle, Shipment, Vehicle } from "@/payload-types";
|
|
import {
|
|
Item,
|
|
ItemContent,
|
|
ItemDescription,
|
|
ItemHeader,
|
|
ItemMedia,
|
|
ItemTitle,
|
|
} from "@/components/ui/item";
|
|
import { ShipmentProgressBar } from "@/components/frontend/logistics/ShipmentProgressBar";
|
|
import { ArrowRightIcon, ChevronRightIcon, TruckIcon } from "lucide-react";
|
|
|
|
interface ShipmentCardProps {
|
|
shipment: Shipment;
|
|
}
|
|
|
|
const statusColors: Record<string, string> = {
|
|
pending: "bg-muted text-muted-foreground",
|
|
dispatched: "bg-blue-500/20 text-blue-400",
|
|
in_transit: "bg-amber-500/20 text-amber-400",
|
|
arrived: "bg-green-500/20 text-green-400",
|
|
completed: "bg-emerald-500/20 text-emerald-400",
|
|
cancelled: "bg-muted text-muted-foreground",
|
|
failed: "bg-red-500/20 text-red-400",
|
|
stranded: "bg-red-500/20 text-red-400",
|
|
};
|
|
|
|
export function ShipmentCard({ shipment }: ShipmentCardProps) {
|
|
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;
|
|
|
|
const dispatchedAt = shipment.dispatchedAt ? new Date(shipment.dispatchedAt) : null;
|
|
const estimatedArrival = shipment.estimatedArrival ? new Date(shipment.estimatedArrival) : null;
|
|
|
|
const isActive = shipment.status === "dispatched" || shipment.status === "in_transit";
|
|
|
|
const cargoCount = shipment.cargo?.length ?? 0;
|
|
const totalItems = shipment.cargo?.reduce((sum, c) => sum + (c.amount ?? 0), 0) ?? 0;
|
|
|
|
return (
|
|
<Link href={`/logistics/shipments/${shipment.id}`}>
|
|
<Item
|
|
variant="outline"
|
|
className="cursor-pointer group transition-all duration-200 hover:scale-[1.01] hover:[box-shadow:inset_0_2px_8px_rgba(255,255,255,0.33)]"
|
|
>
|
|
<ItemHeader>
|
|
<div className="flex items-center gap-2">
|
|
<ItemMedia variant="icon">
|
|
<TruckIcon />
|
|
</ItemMedia>
|
|
<ItemTitle className="flex flex-col">
|
|
<span>{shipment.name ?? `SHP-${shipment.id}`}</span>
|
|
<span className="text-xs font-normal text-muted-foreground">
|
|
{vehicleType?.name ?? "Unknown vehicle"}
|
|
</span>
|
|
</ItemTitle>
|
|
</div>
|
|
<span
|
|
className={`text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded ${statusColors[shipment.status] ?? "bg-muted text-muted-foreground"}`}
|
|
>
|
|
{shipment.status === "in_transit"
|
|
? "In Transit"
|
|
: shipment.status.charAt(0).toUpperCase() + shipment.status.slice(1)}
|
|
</span>
|
|
</ItemHeader>
|
|
<ItemContent>
|
|
<div className="flex flex-col gap-3 w-full">
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<span className="truncate">{origin?.name ?? `Structure #${shipment.origin}`}</span>
|
|
<ArrowRightIcon className="size-3 text-muted-foreground shrink-0" />
|
|
<span className="truncate">
|
|
{destination?.name ?? `Structure #${shipment.destination}`}
|
|
</span>
|
|
</div>
|
|
|
|
{isActive && dispatchedAt && estimatedArrival && (
|
|
<ShipmentProgressBar
|
|
dispatchedAt={dispatchedAt.toISOString()}
|
|
estimatedArrival={estimatedArrival.toISOString()}
|
|
/>
|
|
)}
|
|
|
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
|
<span>
|
|
{cargoCount} item type{cargoCount !== 1 ? "s" : ""} ({totalItems} total)
|
|
</span>
|
|
<span className="font-mono">
|
|
{shipment.distance?.toFixed(1)} units · {shipment.fuelCost} fuel
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</ItemContent>
|
|
<ItemDescription>
|
|
{shipment.status === "completed"
|
|
? `Completed ${shipment.completedAt ? new Date(shipment.completedAt).toLocaleDateString() : ""}`
|
|
: shipment.status === "cancelled"
|
|
? "Cancelled"
|
|
: (shipment.failureReason ?? (isActive ? "In transit" : shipment.status))}
|
|
</ItemDescription>
|
|
<span className="text-xs text-muted-foreground group-hover:text-primary transition-colors flex items-center gap-1">
|
|
View details <ChevronRightIcon className="size-3" />
|
|
</span>
|
|
</Item>
|
|
</Link>
|
|
);
|
|
}
|