feat(train-scheduling): add wagon type, tare, equated length, station, seal no and note columns to import marshalling doc

Import Load List / Marshalling Document only rendered Seq, Wagon,
Booking, Company, Load, Container numbers, Weight T — missing fields
present on the physical marshaling sheet (wagon type, tare, equated
length, departure/arrival station, seal no) and a blank note column
for yard staff. Export marshalling doc already had most of these;
import doc now matches. Existing columns kept in place, unchanged.
This commit is contained in:
Hagernesh
2026-08-13 08:22:51 +00:00
parent f5e98ae67c
commit 833e62990e
7 changed files with 215 additions and 12 deletions

View File

@@ -1,6 +1,9 @@
import { Truck } from "lucide-react";
import { SimpleGrid } from "@mantine/core";
import { Download, Truck } from "lucide-react";
import { Button, Group, SimpleGrid, Stack, Text } from "@mantine/core";
import { useQuery } from "@tanstack/react-query";
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
import { lastMileRequestsService } from "@/services/last-mile-requests.service";
import type { BookingDetail } from "@/types/booking";
import { SectionCard } from "./SectionCard";
@@ -10,12 +13,37 @@ export interface BookingMileServicesCardProps {
booking: BookingDetail;
}
/** First / last mile addresses. Renders nothing when neither is present. */
/**
* First / last mile addresses, plus the stored last-mile contract reference
* (signed status + PDF download) for Truck & Machinery once a request on this
* booking is approved. Renders nothing when neither address is present.
*/
export function BookingMileServicesCard({ booking }: BookingMileServicesCardProps) {
const { data: requestsResponse } = useQuery({
queryKey: QUERY_KEYS.LAST_MILE_REQUESTS.list({ bookingId: booking.id }),
queryFn: async () =>
(await lastMileRequestsService.list({ bookingId: booking.id })).data,
enabled: Boolean(booking.lastMileDeliveryAddress),
});
const approvedRequest = (requestsResponse?.data ?? []).find(
(r) => r.status === "APPROVED",
);
if (!booking.firstMilePickupAddress && !booking.lastMileDeliveryAddress) {
return null;
}
const downloadContract = async () => {
if (!approvedRequest) return;
const blob = (await lastMileRequestsService.contractDocument(approvedRequest.id)).data;
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `last-mile-contract-${booking.reference ?? booking.id}.pdf`;
a.click();
URL.revokeObjectURL(url);
};
return (
<SectionCard icon={Truck} title="Mile services" accent="grape">
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="sm">
@@ -26,6 +54,32 @@ export function BookingMileServicesCard({ booking }: BookingMileServicesCardProp
<MetricTile label="Last mile delivery" value={booking.lastMileDeliveryAddress} />
)}
</SimpleGrid>
{approvedRequest && (
<Group justify="space-between" align="center" wrap="wrap" mt="sm">
<Stack gap={0}>
<Text size="sm" fw={600}>
Last-mile contract
</Text>
<Text size="xs" c={approvedRequest.customerSignedAt ? "green.8" : "orange.8"}>
{approvedRequest.customerSignedAt
? `Signed ${new Date(approvedRequest.customerSignedAt).toLocaleDateString()}${
approvedRequest.signerDisplayName
? ` by ${approvedRequest.signerDisplayName}`
: ""
}`
: "Awaiting customer signature"}
</Text>
</Stack>
<Button
size="xs"
variant="light"
leftSection={<Download size={14} />}
onClick={() => void downloadContract()}
>
Download PDF
</Button>
</Group>
)}
</SectionCard>
);
}