import { Alert, Badge, Button, Group, Loader, Modal, MultiSelect, Stack, Text } from '@mantine/core'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { FileText, Truck } from 'lucide-react'; import { useState } from 'react'; import { useToast } from '@/hooks/use-toast'; import { warehouseService } from '@/services/warehouse.service'; import { extractErrorMessage } from './options'; import { openPdfBlob } from './pdf'; interface TruckDispatchModalProps { opened: boolean; onClose: () => void; bookingId: string | null; bookingReference?: string | null; } /** * Truck_dispatch: after a self-haul truck arrives, staff select which of the * booking's containers ride each truck. The loaded set drives the truck's gross * weight; the truck is weighed for real on departure. */ export function TruckDispatchModal({ opened, onClose, bookingId, bookingReference }: TruckDispatchModalProps) { const { toast } = useToast(); const queryClient = useQueryClient(); const [selectedByTruck, setSelectedByTruck] = useState>({}); const trucksKey = ['td-customer-trucks', bookingId]; const loadableKey = ['td-loadable', bookingId]; const { data: trucks = [], isLoading: trucksLoading } = useQuery({ queryKey: trucksKey, queryFn: () => warehouseService.getCustomerTrucks(bookingId as string), enabled: opened && Boolean(bookingId), }); const { data: loadable = [], isLoading: loadableLoading } = useQuery({ queryKey: loadableKey, queryFn: () => warehouseService.getLoadableContainers(bookingId as string), enabled: opened && Boolean(bookingId), }); const loadMutation = useMutation({ mutationFn: ({ assignmentId, containerNumbers }: { assignmentId: string; containerNumbers: string[] }) => warehouseService.loadTruck(bookingId as string, assignmentId, containerNumbers), onSuccess: (_res, vars) => { queryClient.invalidateQueries({ queryKey: trucksKey }); queryClient.invalidateQueries({ queryKey: loadableKey }); setSelectedByTruck((s) => ({ ...s, [vars.assignmentId]: [] })); toast({ title: 'Truck loaded' }); }, onError: (e) => toast({ variant: 'destructive', title: 'Load failed', description: extractErrorMessage(e) }), }); const openTruckExitPaper = async (assignmentId: string, plate: string) => { try { const res = await warehouseService.downloadTruckExitPaper(assignmentId); openPdfBlob(res.data, `exit-${plate}.pdf`); } catch (e) { toast({ variant: 'destructive', title: 'Exit paper not ready', description: extractErrorMessage(e) }); } }; return ( Truck_dispatch — load containers {bookingReference ? `· ${bookingReference}` : ''} } > {trucksLoading || loadableLoading ? ( ) : trucks.length === 0 ? ( No customer truck is assigned to this booking yet. ) : ( {trucks.map((t) => { const alreadyLoaded = (t.containers ?? []).map((c) => c.containerNumber); // Options = still-loadable + this truck's own already-loaded (so they stay visible). const options = Array.from(new Set([...loadable, ...alreadyLoaded])); const selected = selectedByTruck[t.id] ?? alreadyLoaded; const departed = Boolean(t.arrivedAt) && Boolean((t as { departedAt?: string }).departedAt); return ( {t.plateNumber} {t.driverName} · {t.truckType} {t.arrivedAt ? Arrived : Not arrived} setSelectedByTruck((s) => ({ ...s, [t.id]: v }))} searchable disabled={departed || !t.arrivedAt} nothingFoundMessage="No loadable containers" /> ); })} )} ); }