1
0
Fork 0
polaris-task-force/src/components/frontend/logistics/ShipmentCard.tsx

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>
);
}