mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
Frontend for the per-truck EDR backend: - Assign modal takes a container LOAD per truck (MultiSelect, max 2) instead of a single container, so a truck can carry one 40ft or two 20ft. Sends containerNumbers[]; the API enforces the size rule. - requiredVehicles is now size-aware: a 40ft fills a truck (1 each), two 20ft share one. It previously assumed ceil(containers / 2) regardless of size, so a 2x40ft booking under-counted the trucks needed. - Bulk bookings show the drawdown in the assign modal — remaining vs total tonnage, what's been hauled, and a "fully hauled" state when it reaches 0. - New "Truck exit papers" row action opens a per-truck list (plate, containers, arrival, exit, net weight) with a per-truck exit-paper download. - last-mile findAll/findById now load each assignment's containers so the UI can hydrate a truck's load. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
132 lines
4.6 KiB
TypeScript
132 lines
4.6 KiB
TypeScript
import { useState } from "react";
|
|
import { Alert, Badge, Button, Modal, Stack, Table, Text } from "@mantine/core";
|
|
import { FileText } from "lucide-react";
|
|
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { warehouseService } from "@/services/warehouse.service";
|
|
import type { LastMileRecord } from "@/services/last-mile.service";
|
|
import { extractDownloadErrorMessage } from "@/components/warehouses/options";
|
|
import { openPdfBlob } from "@/components/warehouses/pdf";
|
|
|
|
interface EdrTruckExitPapersModalProps {
|
|
opened: boolean;
|
|
onClose: () => void;
|
|
record: LastMileRecord | null;
|
|
}
|
|
|
|
const fmt = (value?: string | null) =>
|
|
value ? new Date(value).toLocaleString() : "—";
|
|
|
|
/**
|
|
* Per-truck exit papers for an EDR last-mile delivery. Each assigned truck has
|
|
* its own arrival, exit and weighed load, so each gets its own paper.
|
|
*/
|
|
export function EdrTruckExitPapersModal({ opened, onClose, record }: EdrTruckExitPapersModalProps) {
|
|
const { toast } = useToast();
|
|
const [busyId, setBusyId] = useState<string | null>(null);
|
|
const trucks = record?.vehicleAssignments ?? [];
|
|
|
|
const download = async (assignmentId: string, plate: string) => {
|
|
setBusyId(assignmentId);
|
|
try {
|
|
const res = await warehouseService.downloadEdrTruckExitPaper(assignmentId);
|
|
openPdfBlob(res.data, `exit-${plate || assignmentId}.pdf`);
|
|
} catch (e) {
|
|
toast({
|
|
variant: "destructive",
|
|
title: "Exit paper not ready",
|
|
description: await extractDownloadErrorMessage(e),
|
|
});
|
|
} finally {
|
|
setBusyId(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
opened={opened}
|
|
onClose={onClose}
|
|
centered
|
|
radius="lg"
|
|
size="lg"
|
|
title={
|
|
<Text fw={600}>
|
|
Truck exit papers {record?.booking?.reference ? `· ${record.booking.reference}` : ""}
|
|
</Text>
|
|
}
|
|
>
|
|
{trucks.length === 0 ? (
|
|
<Alert variant="light" color="gray">
|
|
No trucks assigned to this delivery yet.
|
|
</Alert>
|
|
) : (
|
|
<Stack gap="sm">
|
|
<Table verticalSpacing="xs" highlightOnHover>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th>Truck</Table.Th>
|
|
<Table.Th>Containers</Table.Th>
|
|
<Table.Th>Arrived</Table.Th>
|
|
<Table.Th>Left</Table.Th>
|
|
<Table.Th ta="right">Net</Table.Th>
|
|
<Table.Th ta="right">Exit paper</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>
|
|
{trucks.map((t) => {
|
|
const plate = t.vehicle?.powerPlateNo || t.vehicle?.plateNumber || "—";
|
|
const load = t.containers?.length
|
|
? t.containers.map((c) => c.containerNumber).join(", ")
|
|
: (t.containerNumber ?? "bulk");
|
|
return (
|
|
<Table.Tr key={t.id}>
|
|
<Table.Td>
|
|
<Text fw={600} size="sm">{plate}</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Text size="sm">{load}</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<Text size="sm">{fmt(t.arrivedAt)}</Text>
|
|
</Table.Td>
|
|
<Table.Td>
|
|
{t.departedAt ? (
|
|
<Text size="sm">{fmt(t.departedAt)}</Text>
|
|
) : (
|
|
<Badge size="sm" variant="light" color="gray">
|
|
Still on site
|
|
</Badge>
|
|
)}
|
|
</Table.Td>
|
|
<Table.Td ta="right">
|
|
<Text size="sm">
|
|
{t.netWeightTons != null ? `${t.netWeightTons} t` : "—"}
|
|
</Text>
|
|
</Table.Td>
|
|
<Table.Td ta="right">
|
|
<Button
|
|
size="compact-xs"
|
|
variant="light"
|
|
color="orange"
|
|
leftSection={<FileText size={13} />}
|
|
loading={busyId === t.id}
|
|
onClick={() => download(t.id, plate)}
|
|
>
|
|
Exit Paper
|
|
</Button>
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
);
|
|
})}
|
|
</Table.Tbody>
|
|
</Table>
|
|
<Text size="xs" c="dimmed">
|
|
EDR handovers are generated at delivery, so an exit paper is not gated on a
|
|
signature — warehouse-fee clearance still applies.
|
|
</Text>
|
|
</Stack>
|
|
)}
|
|
</Modal>
|
|
);
|
|
}
|