From 7b777792e38b7ab87f6587fe709cbfef5a18b8f1 Mon Sep 17 00:00:00 2001 From: Hagernesh Date: Mon, 20 Jul 2026 12:52:31 +0000 Subject: [PATCH] feat(warehouse): expand an inventory row to see which containers ride which truck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inventory table has one row per inventory item, so on a self-haul booking with several trucks there was no way to tell which container sat on which truck without opening a document. The plate column now lists every plate, but not the mapping. Each row with a customer truck gets a chevron that opens a per-truck breakdown: plate, driver, type, the containers it carries, and whether it has arrived. Bulk trucks show "Bulk" — they haul loose tonnage, not containers. The breakdown fetches only when opened, so a table nobody expands costs no extra requests, and it reuses the existing per-booking trucks endpoint rather than widening the inventory query. Co-Authored-By: Claude Opus 4.8 --- .../warehouses/TruckBreakdownRow.tsx | 113 ++++++++++++++++++ .../warehouses/WarehouseInventoryTable.tsx | 53 +++++++- 2 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 apps/edr-freight-web/backoffice/src/components/warehouses/TruckBreakdownRow.tsx diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/TruckBreakdownRow.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/TruckBreakdownRow.tsx new file mode 100644 index 000000000..d20f30a6b --- /dev/null +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/TruckBreakdownRow.tsx @@ -0,0 +1,113 @@ +import { useQuery } from "@tanstack/react-query"; +import { Badge, Group, Loader, Table, Text } from "@mantine/core"; + +import { warehouseService } from "@/services/warehouse.service"; + +/** + * The trucks carrying one booking's cargo, and which containers ride each. + * + * A self-haul booking can have several trucks, each with 1–2 containers, but + * the inventory table has one row per inventory item — so which container sits + * on which truck was never visible without opening a document. Fetched lazily: + * only an expanded row costs a request. + */ +export function TruckBreakdownRow({ + bookingId, + colSpan, +}: { + bookingId: string; + colSpan: number; +}) { + const { data: trucks = [], isLoading } = useQuery({ + queryKey: ["booking-customer-trucks", bookingId], + queryFn: () => warehouseService.getCustomerTrucks(bookingId), + }); + + return ( + + + {isLoading ? ( + + + + Loading trucks… + + + ) : trucks.length === 0 ? ( + + No customer trucks assigned to this booking. + + ) : ( + + + + + + Truck + + + + + Driver + + + + + Type + + + + + Containers + + + + + Status + + + + + + {trucks.map((truck) => ( + + + + {truck.plateNumber} + + + + {truck.driverName} + + + {truck.truckType} + + + {/* Bulk trucks carry loose tonnage, not containers. */} + + {truck.containers?.length + ? truck.containers.map((c) => c.containerNumber).join(", ") + : "Bulk"} + + + + + {truck.arrivedAt + ? `Arrived ${new Date(truck.arrivedAt).toLocaleString()}` + : "Not arrived"} + + + + ))} + +
+ )} +
+
+ ); +} diff --git a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx index 060dc600e..48ce59c78 100644 --- a/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx +++ b/apps/edr-freight-web/backoffice/src/components/warehouses/WarehouseInventoryTable.tsx @@ -1,6 +1,6 @@ -import { useState, type MouseEvent } from 'react'; +import { Fragment, useState, type MouseEvent } from 'react'; import { ActionIcon, Badge, Button, Checkbox, Group, Table, Text, Tooltip } from '@mantine/core'; -import { ArrowRightLeft, ClipboardList, Coins, Download, Eye, FileText, History, MapPin } from 'lucide-react'; +import { ArrowRightLeft, ChevronDown, ChevronRight, ClipboardList, Coins, Download, Eye, FileText, History, MapPin } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; import { warehouseService } from '@/services/warehouse.service'; @@ -10,6 +10,7 @@ import { type WarehouseInventoryItem, } from '@/types/warehouse'; import { InventoryStatusBadge } from './badges'; +import { TruckBreakdownRow } from './TruckBreakdownRow'; import { extractDownloadErrorMessage, formatDate, formatNumber, humanizeEnum } from './options'; import { openPdfBlob } from './pdf'; @@ -120,6 +121,16 @@ export function WarehouseInventoryTable({ someSelected, }: WarehouseInventoryTableProps) { const selectable = Boolean(onToggleSelect); + // Bookings whose truck breakdown is open. Expanded rows fetch on demand, so a + // closed table costs nothing extra. + const [expanded, setExpanded] = useState>(new Set()); + const toggleExpanded = (bookingId: string) => + setExpanded((prev) => { + const next = new Set(prev); + if (next.has(bookingId)) next.delete(bookingId); + else next.add(bookingId); + return next; + }); if (items.length === 0) { return ( @@ -144,6 +155,8 @@ export function WarehouseInventoryTable({ /> )} + {/* Expander for the per-truck breakdown. */} + Booking GRN Facility @@ -174,8 +187,16 @@ export function WarehouseInventoryTable({ (!item.booking?.tradeDirection || item.booking.tradeDirection === 'IMPORT'); const handoverReference = handoverDocumentReference(item); + // Only offer the breakdown where there is one: a plate means at + // least one customer truck is on the booking. + const hasCustomerTrucks = Boolean( + item.bookingId && item.booking?.customerTruckPlateNumber?.trim(), + ); + const isExpanded = Boolean(item.bookingId && expanded.has(item.bookingId)); + return ( - + + {selectable && ( )} + + {hasCustomerTrucks ? ( + + toggleExpanded(item.bookingId as string)} + > + {isExpanded ? : } + + + ) : null} + {item.bookingReference || item.booking?.reference || item.bookingId ? ( @@ -309,6 +347,15 @@ export function WarehouseInventoryTable({ + {isExpanded && item.bookingId ? ( + + ) : null} + ); })}