mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
fix slip
This commit is contained in:
@@ -186,6 +186,133 @@ const BookingInfo = ({ job }: { job: LastMileJob }) => (
|
||||
</Card>
|
||||
);
|
||||
|
||||
const tripSlipRows = (job: LastMileJob): [string, string][] => [
|
||||
["Customer", job.customer],
|
||||
["Service", job.serviceType],
|
||||
["Origin yard", job.originYard],
|
||||
["Destination", job.destination],
|
||||
["Cargo", job.cargo],
|
||||
["Weight", job.weight],
|
||||
["Vehicle", job.assignedVehicle ?? "Unassigned"],
|
||||
["Contact", `${job.contactName} · ${job.contactPhone}`],
|
||||
["Requested date", job.requestedDate],
|
||||
["Delivery status", DELIVERY_STATUS_META[job.deliveryStatus].label],
|
||||
];
|
||||
|
||||
const SignatureBlock = ({ title }: { title: string }) => (
|
||||
<Stack gap="sm" style={{ flex: 1 }}>
|
||||
<Text fw={600} size="sm">
|
||||
{title}
|
||||
</Text>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<Text size="sm" c="dimmed">
|
||||
Name:
|
||||
</Text>
|
||||
<Box style={{ flex: 1, borderBottom: "1px solid var(--mantine-color-gray-5)", height: 18 }} />
|
||||
</Group>
|
||||
<Group gap="xs" align="flex-end">
|
||||
<Text size="sm" c="dimmed">
|
||||
Signature:
|
||||
</Text>
|
||||
<Box style={{ flex: 1, borderBottom: "1px solid var(--mantine-color-gray-5)", height: 18 }} />
|
||||
</Group>
|
||||
<Box
|
||||
style={{
|
||||
marginTop: 4,
|
||||
height: 90,
|
||||
borderRadius: 8,
|
||||
border: "1px dashed var(--mantine-color-gray-4)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Text size="xs" c="dimmed" tt="uppercase" fw={600}>
|
||||
Stamp
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const TripSlipDocument = ({ job }: { job: LastMileJob }) => (
|
||||
<Stack gap="md">
|
||||
<Stack gap={2} align="center">
|
||||
<Text fw={700}>EDR Freight</Text>
|
||||
<Text size="sm" c="dimmed" tt="uppercase" fw={600}>
|
||||
Last Mile Trip Slip
|
||||
</Text>
|
||||
</Stack>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" fw={600}>
|
||||
{job.bookingRef}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{job.requestedDate}
|
||||
</Text>
|
||||
</Group>
|
||||
<Divider />
|
||||
<SimpleGrid cols={2} spacing="xs">
|
||||
{tripSlipRows(job).map(([label, value]) => (
|
||||
<InfoRow key={label} label={label} value={value} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<Divider label="Acknowledgement" labelPosition="center" />
|
||||
<Group align="flex-start" gap="xl" wrap="nowrap">
|
||||
<SignatureBlock title="Driver" />
|
||||
<SignatureBlock title="Operator" />
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const escapeHtml = (value: string) =>
|
||||
value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
|
||||
const buildTripSlipHtml = (job: LastMileJob) => {
|
||||
const rows = tripSlipRows(job)
|
||||
.map(
|
||||
([label, value]) =>
|
||||
`<tr><td class="lbl">${escapeHtml(label)}</td><td>${escapeHtml(value)}</td></tr>`,
|
||||
)
|
||||
.join("");
|
||||
const signature = (title: string) => `
|
||||
<div class="sign-col">
|
||||
<div class="sign-title">${title}</div>
|
||||
<div class="sign-field"><span>Name:</span><span class="line"></span></div>
|
||||
<div class="sign-field"><span>Signature:</span><span class="line"></span></div>
|
||||
<div class="stamp">STAMP</div>
|
||||
</div>`;
|
||||
return `<!doctype html><html><head><meta charset="utf-8" />
|
||||
<title>Trip Slip ${escapeHtml(job.bookingRef)}</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body { font-family: Arial, Helvetica, sans-serif; color: #111; margin: 32px; }
|
||||
.head { text-align: center; margin-bottom: 16px; }
|
||||
.head h1 { font-size: 18px; margin: 0; }
|
||||
.head p { font-size: 12px; letter-spacing: 1px; text-transform: uppercase; color: #555; margin: 2px 0 0; }
|
||||
.meta { display: flex; justify-content: space-between; font-size: 13px; font-weight: 600; margin: 8px 0; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 8px 0 20px; }
|
||||
td { padding: 5px 6px; border-bottom: 1px solid #eee; vertical-align: top; }
|
||||
td.lbl { color: #666; text-transform: uppercase; font-size: 11px; font-weight: 700; width: 40%; }
|
||||
.ack { text-align: center; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: #777; margin: 16px 0 8px; }
|
||||
.signs { display: flex; gap: 32px; }
|
||||
.sign-col { flex: 1; }
|
||||
.sign-title { font-weight: 700; font-size: 13px; margin-bottom: 12px; }
|
||||
.sign-field { display: flex; gap: 6px; align-items: flex-end; font-size: 12px; color: #666; margin-bottom: 10px; }
|
||||
.sign-field .line { flex: 1; border-bottom: 1px solid #888; height: 16px; }
|
||||
.stamp { margin-top: 6px; height: 96px; border: 1px dashed #aaa; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 11px; letter-spacing: 1px; color: #999; }
|
||||
</style></head>
|
||||
<body onload="window.print()">
|
||||
<div class="head"><h1>EDR Freight</h1><p>Last Mile Trip Slip</p></div>
|
||||
<div class="meta"><span>${escapeHtml(job.bookingRef)}</span><span>${escapeHtml(job.requestedDate)}</span></div>
|
||||
<table>${rows}</table>
|
||||
<div class="ack">Acknowledgement</div>
|
||||
<div class="signs">${signature("Driver")}${signature("Operator")}</div>
|
||||
</body></html>`;
|
||||
};
|
||||
|
||||
const LastMilePage = () => {
|
||||
const { toast } = useToast();
|
||||
const { pagination, setPagination } = usePagination({ pageSize: 10 });
|
||||
@@ -196,6 +323,8 @@ const LastMilePage = () => {
|
||||
|
||||
const [assignOpen, setAssignOpen] = useState(false);
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
const [tripSlipOpen, setTripSlipOpen] = useState(false);
|
||||
const [tripSlipJob, setTripSlipJob] = useState<LastMileJob | null>(null);
|
||||
const [activeJobId, setActiveJobId] = useState<string | null>(null);
|
||||
const [vehicleValue, setVehicleValue] = useState<string | null>(null);
|
||||
|
||||
@@ -315,8 +444,23 @@ const LastMilePage = () => {
|
||||
};
|
||||
|
||||
const handlePrintTripSlip = (job: LastMileJob) => {
|
||||
// Placeholder — wire to the trip-slip document service when available.
|
||||
toast({ title: "Printing trip slip", description: job.bookingRef });
|
||||
setTripSlipJob(job);
|
||||
setTripSlipOpen(true);
|
||||
};
|
||||
|
||||
const printTripSlip = () => {
|
||||
if (!tripSlipJob) return;
|
||||
const win = window.open("", "_blank", "width=820,height=920");
|
||||
if (!win) {
|
||||
toast({
|
||||
title: "Pop-up blocked",
|
||||
description: "Allow pop-ups to print the trip slip.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
win.document.write(buildTripSlipHtml(tripSlipJob));
|
||||
win.document.close();
|
||||
};
|
||||
|
||||
const columns = useMemo((): ColumnDef<LastMileJob>[] => {
|
||||
@@ -582,6 +726,28 @@ const LastMilePage = () => {
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
opened={tripSlipOpen}
|
||||
onClose={() => setTripSlipOpen(false)}
|
||||
title={<Text fw={600}>Trip Slip</Text>}
|
||||
size="lg"
|
||||
radius="lg"
|
||||
centered
|
||||
>
|
||||
<Stack gap="md">
|
||||
{tripSlipJob && <TripSlipDocument job={tripSlipJob} />}
|
||||
<Divider />
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="default" onClick={() => setTripSlipOpen(false)}>
|
||||
Close
|
||||
</Button>
|
||||
<Button leftSection={<Printer size={16} />} onClick={printTripSlip}>
|
||||
Print
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user