import { Alert, Badge, Box, Button, Checkbox, Group, Loader, Paper, Stack, Table, Text, } from '@mantine/core'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ChevronDown, ChevronRight, TrainFront } from 'lucide-react'; import { useMemo, useState } from 'react'; import { useToast } from '@/hooks/use-toast'; import { warehouseService, type LoadableTrain, type TrainLoadableItem, } from '@/services/warehouse.service'; import { extractErrorMessage } from './options'; const STAGE_COLOR: Record = { RECEIVED: 'blue', STORED: 'gray', RESERVED: 'grape', READY_FOR_LOADING: 'teal', LOADED: 'green', }; const weight = (w: number | null) => (w == null ? '—' : `${Number(w).toLocaleString()} t`); interface BookingGroup { bookingId: string | null; bookingReference: string | null; customerName: string | null; items: TrainLoadableItem[]; } function groupByBooking(items: TrainLoadableItem[]): BookingGroup[] { const map = new Map(); for (const i of items) { const key = i.bookingId ?? i.bookingReference ?? 'unknown'; let g = map.get(key); if (!g) { g = { bookingId: i.bookingId, bookingReference: i.bookingReference, customerName: i.customerName, items: [] }; map.set(key, g); } g.items.push(i); } return [...map.values()]; } /** * Load to Train — a datatable of allocated EXPORT trains. Expand a train to see * the bookings allocated to it; expand a booking to see its containers/cargoes * and load the ready ones onto their wagons. Only READY_FOR_LOADING items with an * allocated wagon are selectable. */ export function LoadToTrainPanel() { const { data: trains = [], isLoading } = useQuery({ queryKey: ['loadable-trains'], queryFn: () => warehouseService.getLoadableTrains(), }); const [expanded, setExpanded] = useState>(new Set()); const toggle = (id: string) => setExpanded((s) => { const next = new Set(s); next.has(id) ? next.delete(id) : next.add(id); return next; }); if (isLoading) { return ( ); } if (trains.length === 0) { return ( No allocated EXPORT trains awaiting loading. Trains appear here after train and wagon allocation. ); } return ( Train Route Ready Loaded {trains.map((t) => ( toggle(t.scheduleId)} /> ))}
); } function TrainRow({ train, expanded, onToggle }: { train: LoadableTrain; expanded: boolean; onToggle: () => void }) { const { data: items = [], isLoading } = useQuery({ queryKey: ['train-loadable-items', train.scheduleId], queryFn: () => warehouseService.getTrainLoadableItems(train.scheduleId), enabled: expanded, }); const bookings = useMemo(() => groupByBooking(items), [items]); const route = train.origin || train.destination ? `${train.origin ?? '?'} → ${train.destination ?? '?'}` : '—'; return ( <> {expanded ? : } {train.trainNumber ?? train.scheduleId.slice(0, 8)} {route} {train.readyCount} {train.loadedCount} {expanded && ( {isLoading ? ( ) : bookings.length === 0 ? ( No arrived containers/cargoes allocated to this train yet. ) : ( {bookings.map((b) => ( ))} )} )} ); } function BookingBlock({ scheduleId, booking }: { scheduleId: string; booking: BookingGroup }) { const { toast } = useToast(); const queryClient = useQueryClient(); const [open, setOpen] = useState(false); const [selected, setSelected] = useState([]); const loadedCount = booking.items.filter((i) => i.status === 'LOADED').length; const selectable = booking.items.filter((i) => i.loadable); const allSelected = selectable.length > 0 && selectable.every((i) => selected.includes(i.id)); const toggleItem = (id: string) => setSelected((s) => (s.includes(id) ? s.filter((x) => x !== id) : [...s, id])); const toggleAll = () => setSelected((s) => allSelected ? s.filter((id) => !selectable.some((i) => i.id === id)) : selectable.map((i) => i.id), ); const loadMutation = useMutation({ mutationFn: () => warehouseService.loadItemsOntoTrain(scheduleId, selected), onSuccess: (r) => { queryClient.invalidateQueries({ queryKey: ['train-loadable-items', scheduleId] }); queryClient.invalidateQueries({ queryKey: ['loadable-trains'] }); setSelected([]); toast({ title: 'Loaded onto train', description: `Loaded ${r.loadedCount}; skipped ${r.skippedCount}.` }); }, onError: (e) => toast({ variant: 'destructive', title: 'Load failed', description: extractErrorMessage(e) }), }); return ( setOpen((o) => !o)} wrap="nowrap"> {open ? : } {booking.bookingReference ?? booking.bookingId?.slice(0, 8) ?? '—'} {booking.customerName ?? '—'} {booking.items.length} item(s) {loadedCount > 0 && ( {loadedCount} loaded )} {open && ( <> 0} onChange={toggleAll} disabled={selectable.length === 0} /> Container / Cargo Goods Weight Stage Wagon Inspection {booking.items.map((i) => ( toggleItem(i.id)} disabled={!i.loadable} /> {i.containerNumber ?? i.cargoType ?? '—'} {i.cargoType ?? '—'} {weight(i.weight)} {i.status.replace(/_/g, ' ')} {i.wagonNumber ? ( {i.wagonNumber} ) : ( Not allocated )} {i.inspectionStatus ? ( {i.inspectionStatus} ) : ( '—' )} ))}
{selected.length} selected · only READY_FOR_LOADING items with a wagon can be loaded )}
); }