From 2397c6bca4c26dbb562273aa92eb928ee0e6d426 Mon Sep 17 00:00:00 2001 From: Hagernesh Date: Tue, 21 Jul 2026 08:19:45 +0000 Subject: [PATCH] fix(warehouse): show assigned trucks on the Trucks on Site page, not only arrived ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page filtered on arrived_at IS NOT NULL, so a truck appeared only once the warehouse receive flow stamped its arrival. Assigned trucks that had not yet reached the yard were invisible, which left the page empty whenever nothing had been received — every assigned truck was missing. It now lists every truck assigned to a booking that has not departed, from both haulage paths, tagged INBOUND (assigned, not yet arrived) or ON_SITE (arrived). A scope toggle filters between them, dwell time shows only once a truck has actually arrived, and the KPI count on the dashboard stays strict (arrived only). Co-Authored-By: Claude Opus 4.8 --- .../warehouses/warehouse-inventory.service.ts | 14 +++-- .../src/pages/warehouses/TrucksOnSitePage.tsx | 63 ++++++++++++++----- .../backoffice/src/types/warehouse.ts | 2 + 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts b/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts index d842727ee..96122d9bd 100644 --- a/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts +++ b/apps/edr-freight-api/src/modules/warehouses/warehouse-inventory.service.ts @@ -417,12 +417,16 @@ export class WarehouseInventoryService { * * Covers both haulage paths because the gate does: a customer's own truck and * an EDR last-mile truck arrive at the same barrier and need the same paper. - * "On site" means arrived and not yet departed. + * Includes trucks assigned but not yet arrived, flagged INBOUND, so staff see + * what is coming as well as what is here — an assigned truck only stamps + * `arrived_at` when it reaches the warehouse. A truck drops off the list once + * it departs. */ async trucksOnSite(): Promise< Array<{ source: 'CUSTOMER' | 'EDR'; assignmentId: string; + status: 'INBOUND' | 'ON_SITE'; plateNumber: string | null; driverName: string | null; truckType: string | null; @@ -436,6 +440,7 @@ export class WarehouseInventoryService { return this.dataSource.query( `SELECT 'CUSTOMER' AS "source", a.id AS "assignmentId", + CASE WHEN a.arrived_at IS NULL THEN 'INBOUND' ELSE 'ON_SITE' END AS "status", a.plate_number AS "plateNumber", a.driver_name AS "driverName", a.truck_type AS "truckType", @@ -450,13 +455,13 @@ export class WarehouseInventoryService { JOIN freight.bookings b ON b.id = a.booking_id AND b.deleted_at IS NULL LEFT JOIN freight.companies company ON company.id = b.company_id WHERE a.deleted_at IS NULL - AND a.arrived_at IS NOT NULL AND a.departed_at IS NULL UNION ALL SELECT 'EDR' AS "source", va.id AS "assignmentId", + CASE WHEN va.arrived_at IS NULL THEN 'INBOUND' ELSE 'ON_SITE' END AS "status", COALESCE(v.plate_number, v.power_plate_no) AS "plateNumber", NULLIF(TRIM(CONCAT_WS(' ', d.first_name, d.last_name)), '') AS "driverName", v.vehicle_type AS "truckType", @@ -474,10 +479,11 @@ export class WarehouseInventoryService { LEFT JOIN freight.drivers d ON d.id = v.assigned_driver_id LEFT JOIN freight.companies company ON company.id = b.company_id WHERE va.deleted_at IS NULL - AND va.arrived_at IS NOT NULL AND va.departed_at IS NULL - ORDER BY "arrivedAt" ASC`, + -- On-site trucks first, each group oldest-arrival first; inbound trucks + -- (null arrival) sort to the end. + ORDER BY "arrivedAt" ASC NULLS LAST`, ); } diff --git a/apps/edr-freight-web/backoffice/src/pages/warehouses/TrucksOnSitePage.tsx b/apps/edr-freight-web/backoffice/src/pages/warehouses/TrucksOnSitePage.tsx index 9d2ae0e63..2c1bc714b 100644 --- a/apps/edr-freight-web/backoffice/src/pages/warehouses/TrucksOnSitePage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/warehouses/TrucksOnSitePage.tsx @@ -47,15 +47,16 @@ function Rows({ rows }: { rows: TruckOnSite[] }) { if (rows.length === 0) { return ( - No trucks on site. + No trucks assigned or on site. ); } return ( - + + Status Plate Haulage Driver @@ -69,6 +70,16 @@ function Rows({ rows }: { rows: TruckOnSite[] }) { {rows.map((row) => ( + + + {row.status === "ON_SITE" ? "On site" : "Inbound"} + + {row.plateNumber ?? "—"} @@ -101,9 +112,13 @@ function Rows({ rows }: { rows: TruckOnSite[] }) { {row.containers ?? "Bulk"} - {isLongDwell(row.arrivedAt) ? ( + {row.arrivedAt == null ? ( + + — + + ) : isLongDwell(row.arrivedAt) ? ( @@ -124,12 +139,14 @@ function Rows({ rows }: { rows: TruckOnSite[] }) { export default function TrucksOnSitePage() { const { data: trucks = [], isLoading } = useTrucksOnSite(); + const [scope, setScope] = useState<"ALL" | "ON_SITE" | "INBOUND">("ALL"); const [source, setSource] = useState<"ALL" | "CUSTOMER" | "EDR">("ALL"); const [search, setSearch] = useState(""); const rows = useMemo(() => { const term = search.trim().toLowerCase(); return trucks + .filter((t) => scope === "ALL" || t.status === scope) .filter((t) => source === "ALL" || t.source === source) .filter((t) => !term @@ -137,8 +154,10 @@ export default function TrucksOnSitePage() { : [t.plateNumber, t.driverName, t.bookingReference, t.customerName, t.containers] .some((field) => field?.toLowerCase().includes(term)), ); - }, [trucks, source, search]); + }, [trucks, scope, source, search]); + const onSiteCount = trucks.filter((t) => t.status === "ON_SITE").length; + const inboundCount = trucks.length - onSiteCount; const customerCount = trucks.filter((t) => t.source === "CUSTOMER").length; const edrCount = trucks.length - customerCount; @@ -146,20 +165,32 @@ export default function TrucksOnSitePage() { - setSource(v as typeof source)} - data={[ - { label: `All (${trucks.length})`, value: "ALL" }, - { label: `Customer (${customerCount})`, value: "CUSTOMER" }, - { label: `EDR (${edrCount})`, value: "EDR" }, - ]} - /> + + setScope(v as typeof scope)} + data={[ + { label: `All (${trucks.length})`, value: "ALL" }, + { label: `On site (${onSiteCount})`, value: "ON_SITE" }, + { label: `Inbound (${inboundCount})`, value: "INBOUND" }, + ]} + /> + setSource(v as typeof source)} + data={[ + { label: "All", value: "ALL" }, + { label: `Customer (${customerCount})`, value: "CUSTOMER" }, + { label: `EDR (${edrCount})`, value: "EDR" }, + ]} + /> +