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}
+
);
})}