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" }, + ]} + /> +