mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
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:
@@ -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`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user