From 707923b603b7f3a54daea107a1c2c5d9e423669a Mon Sep 17 00:00:00 2001 From: Hagernesh Date: Fri, 10 Jul 2026 09:51:36 +0000 Subject: [PATCH] feat(warehouses): expandable booking rows list containers / cargo inline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every booking row in the queue tables (export received, ready-to-load, loaded/dispatch, import unloaded) gets a chevron that expands to the booking's items: container number, goods, lifecycle stage badge (PENDING..DELIVERED), carrying truck, and GRN. Bulk bookings (no container units) show a one-line summary instead ("Bulk cargo — Coffee export cargo, 3,200 t"). The expansion reuses the ['container-items', bookingId] query the container modal already uses, so data is shared and instant after either has loaded it. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../warehouses/ReceiveInventoryModal.tsx | 169 +++++++++++++++++- 1 file changed, 165 insertions(+), 4 deletions(-) diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx index 465b59aca..55722f04a 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/ReceiveInventoryModal.tsx @@ -257,6 +257,83 @@ const toTruckEntrancePayload = (form: TruckEntranceFormState): TruckEntrancePayl + +const SUB_STAGE_COLOR: Record = { + PENDING: 'gray', + RECEIVED: 'blue', + GRN: 'teal', + ASSIGNED: 'indigo', + LOADED: 'grape', + LEFT: 'orange', + DELIVERED: 'green', +}; + +/** + * Expanded booking row: the booking's containers / bulk items with their + * lifecycle stage. Shares the ['container-items', bookingId] cache with + * ContainerItemsModal, so expanding after using the modal is instant. + */ +function BookingItemsExpansion({ + bookingId, + colSpan, + bulkFallback, +}: { + bookingId: string | null; + colSpan: number; + bulkFallback?: string; +}) { + const { data: items = [], isLoading } = useQuery({ + queryKey: ['container-items', bookingId], + queryFn: () => warehouseService.getContainerItems(bookingId as string), + enabled: Boolean(bookingId), + }); + + return ( + + + {isLoading ? ( + + + + ) : items.length === 0 ? ( + + {bulkFallback ?? 'No container units recorded on this booking.'} + + ) : ( + + + + Container # + Goods + Stage + Truck + GRN + + + + {items.map((i) => ( + + + {i.containerNumber} + + {i.goods ?? '—'} + + + {i.stage} + + + {i.truckPlate ?? '—'} + {i.grnNumber ?? '—'} + + ))} + +
+ )} +
+
+ ); +} + type ConfirmAction = { title: string; message: string; confirmLabel: string; run: () => void }; /** One-click bulk actions are irreversible — make the click deliberate. */ @@ -1295,6 +1372,7 @@ function ExportReceivedTab({ enabled, onChanged }: { enabled: boolean; onChanged ); const [selected, setSelected] = useState>(new Set()); const [confirmAction, setConfirmAction] = useState(null); + const [expandedRow, setExpandedRow] = useState(null); const [inspectId, setInspectId] = useState(null); const pendingRows = rows.filter((r) => r.inspectionStatus !== 'PASSED'); @@ -1365,6 +1443,7 @@ function ExportReceivedTab({ enabled, onChanged }: { enabled: boolean; onChanged + { const selectable = r.inspectionStatus !== 'PASSED'; return ( - + + + + setExpandedRow(expandedRow === r.id ? null : r.id)} + > + {expandedRow === r.id ? : } + + + {expandedRow === r.id && ( + + )} + ); })} @@ -1450,6 +1548,7 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?: ); const qc = useQueryClient(); const [trainPickerOpen, setTrainPickerOpen] = useState(false); + const [expandedRow, setExpandedRow] = useState(null); const [targetScheduleId, setTargetScheduleId] = useState(null); // Loading is always onto a SPECIFIC pre-dispatch train. No train -> no auto-load. const { data: trains = [], isLoading: trainsLoading } = useQuery({ @@ -1571,6 +1670,7 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?:
+ Booking Ref GRN Customer Name @@ -1584,7 +1684,18 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?: {rows.map((r: ReadyToLoadRow) => ( - + + + + setExpandedRow(expandedRow === r.id ? null : r.id)} + > + {expandedRow === r.id ? : } + + {r.bookingReference ?? '—'} @@ -1607,6 +1718,14 @@ function ReadyToLoadTab({ enabled, onChanged }: { enabled: boolean; onChanged?: + {expandedRow === r.id && ( + + )} + ))}
@@ -1634,6 +1753,7 @@ function LoadedExportTab({ api.warehouses.loadedExport.queryOptions({ enabled }), ); const [confirmAction, setConfirmAction] = useState(null); + const [expandedRow, setExpandedRow] = useState(null); const bulkDispatch = useMutation( api.warehouses.bulkDispatchExport.mutationOptions(), ); @@ -1743,6 +1863,7 @@ function LoadedExportTab({ /> )} + Booking Ref GRN Customer Name @@ -1755,7 +1876,8 @@ function LoadedExportTab({ {rows.map((r: ReadyToLoadRow) => ( - + + {dispatchable && ( )} + + setExpandedRow(expandedRow === r.id ? null : r.id)} + > + {expandedRow === r.id ? : } + + {r.bookingReference ?? '—'} @@ -1782,6 +1914,14 @@ function LoadedExportTab({ + {expandedRow === r.id && ( + + )} + ))} @@ -2221,6 +2361,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) { const readyMutation = useMutation(api.warehouses.markReadyForPickup.mutationOptions()); const [selected, setSelected] = useState>(new Set()); const [confirmAction, setConfirmAction] = useState(null); + const [expandedRow, setExpandedRow] = useState(null); const [inspectId, setInspectId] = useState(null); const [busyId, setBusyId] = useState(null); const [viewItem, setViewItem] = useState(null); @@ -2383,6 +2524,7 @@ function ImportUnloadedQueueTab({ enabled }: { enabled: boolean }) { + {rows.map((r: ImportUnloadedItem) => ( - + + + + setExpandedRow(expandedRow === r.id ? null : r.id)} + > + {expandedRow === r.id ? : } + + + {expandedRow === r.id && ( + + )} + ))}