Merge pull request #867 from Tria-plc/testfixes

Onsite truck
Fixed
The page now lists every truck assigned to a booking that hasn't departed, both haulage paths, tagged:

On site — arrived, not departed (green)
Inbound — assigned, not yet arrived (grey)
This commit is contained in:
Hagernesh Tadesse
2026-07-21 11:50:15 +03:00
committed by GitHub
3 changed files with 59 additions and 20 deletions

View File

@@ -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`,
);
}

View File

@@ -47,15 +47,16 @@ function Rows({ rows }: { rows: TruckOnSite[] }) {
if (rows.length === 0) {
return (
<Alert variant="light" color="gray">
No trucks on site.
No trucks assigned or on site.
</Alert>
);
}
return (
<Table.ScrollContainer minWidth={980}>
<Table.ScrollContainer minWidth={1040}>
<Table striped highlightOnHover verticalSpacing="xs">
<Table.Thead>
<Table.Tr>
<Table.Th>Status</Table.Th>
<Table.Th>Plate</Table.Th>
<Table.Th>Haulage</Table.Th>
<Table.Th>Driver</Table.Th>
@@ -69,6 +70,16 @@ function Rows({ rows }: { rows: TruckOnSite[] }) {
<Table.Tbody>
{rows.map((row) => (
<Table.Tr key={`${row.source}-${row.assignmentId}`}>
<Table.Td>
<Badge
size="sm"
radius="sm"
variant={row.status === "ON_SITE" ? "filled" : "light"}
color={row.status === "ON_SITE" ? "edr-green" : "gray"}
>
{row.status === "ON_SITE" ? "On site" : "Inbound"}
</Badge>
</Table.Td>
<Table.Td>
<Text size="sm" fw={600}>
{row.plateNumber ?? "—"}
@@ -101,9 +112,13 @@ function Rows({ rows }: { rows: TruckOnSite[] }) {
<Text size="sm">{row.containers ?? "Bulk"}</Text>
</Table.Td>
<Table.Td>
{isLongDwell(row.arrivedAt) ? (
{row.arrivedAt == null ? (
<Text size="sm" c="dimmed">
</Text>
) : isLongDwell(row.arrivedAt) ? (
<Tooltip
label={`On site over ${LONG_DWELL_HOURS}h — arrived ${new Date(row.arrivedAt as string).toLocaleString()}`}
label={`On site over ${LONG_DWELL_HOURS}h — arrived ${new Date(row.arrivedAt).toLocaleString()}`}
withArrow
>
<Text size="sm" c="red" fw={600}>
@@ -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() {
<PageContainer>
<PageHeader
title="Trucks on site"
subtitle="Arrived at the yard and not yet left — customer self-haul and EDR last-mile."
subtitle="Customer self-haul and EDR last-mile trucks — assigned (inbound) or arrived, until they leave the yard."
/>
<Group justify="space-between" mb="md" wrap="wrap" gap="sm">
<SegmentedControl
size="xs"
value={source}
onChange={(v) => setSource(v as typeof source)}
data={[
{ label: `All (${trucks.length})`, value: "ALL" },
{ label: `Customer (${customerCount})`, value: "CUSTOMER" },
{ label: `EDR (${edrCount})`, value: "EDR" },
]}
/>
<Group gap="sm" wrap="wrap">
<SegmentedControl
size="xs"
value={scope}
onChange={(v) => setScope(v as typeof scope)}
data={[
{ label: `All (${trucks.length})`, value: "ALL" },
{ label: `On site (${onSiteCount})`, value: "ON_SITE" },
{ label: `Inbound (${inboundCount})`, value: "INBOUND" },
]}
/>
<SegmentedControl
size="xs"
value={source}
onChange={(v) => setSource(v as typeof source)}
data={[
{ label: "All", value: "ALL" },
{ label: `Customer (${customerCount})`, value: "CUSTOMER" },
{ label: `EDR (${edrCount})`, value: "EDR" },
]}
/>
</Group>
<TextInput
size="xs"
w={280}

View File

@@ -1128,6 +1128,8 @@ export interface WarehouseOpsStats {
export interface TruckOnSite {
source: "CUSTOMER" | "EDR";
assignmentId: string;
/** INBOUND = assigned, not yet arrived; ON_SITE = arrived, not yet departed. */
status: "INBOUND" | "ON_SITE";
plateNumber: string | null;
driverName: string | null;
truckType: string | null;