import { Alert, Badge, Button, Divider, Group, Loader, Paper, Stack, Table, Text, Tooltip, } from "@mantine/core"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AlertCircle, MapPin, PackageCheck, PackageOpen, TrainFront } from "lucide-react"; import { Freight } from "@edr/types"; import { api } from "@/services/api"; import { useToast } from "@/hooks/use-toast"; import { BookingStatusBadge } from "@/components/bookings/BookingStatusBadge"; import type { YardWorkBookingRow, YardWorkYard } from "@/types/trainScheduling"; const parseError = (error: unknown, fallback: string) => { const message = (error as { response?: { data?: { message?: string | string[] } } }) ?.response?.data?.message; if (Array.isArray(message)) return message.join("; "); return message || (error as Error)?.message || fallback; }; const fmtDate = (iso: string) => { const d = new Date(iso); return Number.isNaN(d.getTime()) ? iso : d.toLocaleString(); }; const DIRECTION_COLORS: Record = { IMPORT: "blue", EXPORT: "teal", DOMESTIC: "violet", }; /** DOMESTIC displays as "Intercity" — shared with the rest of the platform. */ const DIRECTION_LABELS: Record = Freight.TRADE_DIRECTION_LABELS; function DirectionChip({ direction }: { direction: string }) { return ( {DIRECTION_LABELS[direction] ?? direction} ); } function BookingCell({ row }: { row: YardWorkBookingRow }) { return ( {row.reference ?? row.id.slice(0, 8)} {row.isGovernment && ( GOV )} ); } function WorkTable({ rows, side, trainHere, onLoad, onUnload, pendingBookingId, }: { rows: YardWorkBookingRow[]; side: "load" | "unload"; trainHere: boolean; onLoad: (bookingId: string) => void; onUnload: (bookingId: string) => void; pendingBookingId: string | null; }) { if (rows.length === 0) { return ( {side === "load" ? "No bookings board here." : "No bookings alight here."} ); } return ( Booking Customer Direction Status {side === "load" ? "Loaded" : "Arrived"} {rows.map((row) => { const timestamp = side === "load" ? row.loadedAt : row.arrivedAt; const canAct = side === "load" ? row.canLoad : row.canUnload; return ( {row.customer} {timestamp ? ( {fmtDate(timestamp)} ) : ( )} {side === "load" ? ( ) : ( )} ); })}
); } /** * Per-yard load/unload worklist for one schedule — every trade direction. Each * booking boards at its origin yard and alights at its destination yard; the * operator confirms both while the train's last recorded checkpoint is at that * yard (the server validates the position). Unloading stamps the booking's own * arrival — ARRIVED for import/export, COMPLETED for intercity. */ export function YardWorkPanel({ scheduleId }: { scheduleId: string }) { const { toast } = useToast(); const queryClient = useQueryClient(); const yardWorkQuery = useQuery( api.trainScheduling.yardWork.queryOptions({ input: { scheduleId }, refetchInterval: 60_000, }), ); const invalidate = () => queryClient.invalidateQueries({ queryKey: api.trainScheduling.yardWork.queryKey({ scheduleId }), }); const load = useMutation( api.trainScheduling.loadScheduleBooking.mutationOptions({ onSuccess: () => { void invalidate(); toast({ title: "Cargo loaded" }); }, onError: (err) => toast({ title: "Load failed", description: parseError(err, "Could not confirm loading"), variant: "destructive", }), }), ); const unload = useMutation( api.trainScheduling.unloadScheduleBooking.mutationOptions({ onSuccess: (result) => { void invalidate(); toast({ title: result.status === "COMPLETED" ? "Cargo unloaded — booking completed" : "Cargo unloaded — booking arrived", }); }, onError: (err) => toast({ title: "Unload failed", description: parseError(err, "Could not confirm unloading"), variant: "destructive", }), }), ); const data = yardWorkQuery.data; const yards: YardWorkYard[] = data?.yards ?? []; const trainAtYardId = data?.trainAtYardId ?? null; const pendingLoadId = load.isPending ? (load.variables?.bookingId ?? null) : null; const pendingUnloadId = unload.isPending ? (unload.variables?.bookingId ?? null) : null; return ( Yard load / unload {yardWorkQuery.isLoading ? ( Loading yard worklists… ) : yardWorkQuery.isError ? ( }> {parseError(yardWorkQuery.error, "Could not load the yard worklist")} ) : yards.length === 0 ? ( No bookings are assigned to this schedule yet. ) : ( <> What boards and alights at each stop. Confirm loading at a booking's origin and unloading at its destination while the train is at that yard — unloading stamps the booking's own arrival, even before the train's final stop. {yards.map((yard, index) => { const trainHere = trainAtYardId === yard.yardId; return ( {index > 0 && } {yard.yard} {trainHere && ( } > Train here )} Board here load.mutate({ scheduleId, bookingId })} onUnload={(bookingId) => unload.mutate({ scheduleId, bookingId })} pendingBookingId={pendingLoadId} /> Alight here load.mutate({ scheduleId, bookingId })} onUnload={(bookingId) => unload.mutate({ scheduleId, bookingId })} pendingBookingId={pendingUnloadId} /> ); })} )} ); }