Files
edr-platform/apps/edr-freight-web/backoffice/src/components/bookings/detail/BookingRouteServiceCard.tsx
Marshal b926a3116e feat: enhance train scheduling and contract management features
- Added StationWorkControls to manage loading/unloading phases in TrainScheduleV2DetailPage.
- Implemented API endpoints for recording station work and managing wagon detach requests.
- Updated contract templates to include Ethiopian customs handling options.
- Enhanced shipment forms to collect customs clearing agent details for without-customs bookings.
- Introduced NUMBER_OF_WAGONS as a unit of measure for bulk cargo, allowing customers to specify wagon counts.
- Improved validation for customs clearing agent information in shipment forms.
- Updated various components and services to accommodate new features and ensure data integrity.
2026-08-25 21:44:21 +00:00

158 lines
4.7 KiB
TypeScript

import { Train, MapPin, ArrowRight, FileText } from "lucide-react";
import { Group, Stack, Text, Badge, Box, SimpleGrid } from "@mantine/core";
import type { BookingDetail } from "@/types/booking";
import { SectionCard } from "./SectionCard";
import { MetricTile } from "./MetricTile";
export interface BookingRouteServiceCardProps {
booking: BookingDetail;
originLabel: string;
destinationLabel: string;
}
function Endpoint({
label,
station,
align = "left",
}: {
label: string;
station: string;
align?: "left" | "right";
}) {
return (
<Stack gap={2} style={{ flex: 1, minWidth: 0 }} align={align === "right" ? "flex-end" : "flex-start"}>
<Text size="xs" c="dimmed" fw={500} tt="uppercase" lts="0.04em">
{label}
</Text>
<Group gap={6} wrap="nowrap">
<MapPin size={15} color="var(--freight-brand)" />
<Text fw={600} truncate>
{station}
</Text>
</Group>
</Stack>
);
}
export function BookingRouteServiceCard({
booking,
originLabel,
destinationLabel,
}: BookingRouteServiceCardProps) {
const serviceLabel =
booking.serviceType?.label ?? booking.serviceType?.code ?? "Rail service";
const includesCustoms = booking.serviceType?.includesCustoms;
const metrics = [
{ label: "Trade direction", value: booking.tradeDirection },
{ label: "Freight type", value: booking.freightType },
{ label: "Equipment return", value: booking.equipmentReturn ?? "—" },
...(booking.shippingLine
? [
{
label: "Shipping line",
value:
booking.shippingLine.label ??
booking.shippingLine.name ??
booking.shippingLine.code ??
"—",
},
]
: []),
];
return (
<SectionCard icon={Train} title="Route & service" accent="blue">
<Group justify="space-between" align="center" wrap="nowrap" gap="md">
<Endpoint label="Origin" station={originLabel} />
<Stack gap={6} align="center" style={{ flexShrink: 0 }}>
<Box
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 36,
height: 36,
borderRadius: "50%",
background: "var(--mantine-color-gray-1)",
border: "1px solid var(--mantine-color-gray-3)",
}}
>
<Train size={18} color="var(--mantine-color-gray-7)" />
</Box>
<Badge variant="light" color="gray" size="sm" radius="sm" tt="uppercase">
{serviceLabel}
</Badge>
</Stack>
<Group gap={6} wrap="nowrap" style={{ flex: 1, justifyContent: "flex-end" }}>
<ArrowRight size={16} color="var(--mantine-color-gray-4)" style={{ flexShrink: 0 }} />
<Endpoint label="Destination" station={destinationLabel} align="right" />
</Group>
</Group>
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="sm" mt="lg">
{metrics.map((m) => (
<MetricTile key={m.label} label={m.label} value={m.value} />
))}
</SimpleGrid>
{includesCustoms ? (
<Box
mt="md"
px={14}
py={10}
style={{
borderRadius: 10,
border: "1.5px solid #CDEBDD",
background: "#F6FBF8",
}}
>
<Group gap={10} align="center">
<FileText size={15} color="#0A6F4D" />
<Text fz={13} fw={600} c="#0A6F4D">
Customs clearing included automatically
</Text>
</Group>
</Box>
) : booking.customsClearingAgent ? (
<Box
mt="md"
px={14}
py={10}
style={{
borderRadius: 10,
border: "1.5px solid #E6ECF2",
background: "#F8FAFC",
}}
>
<Group gap={10} align="flex-start" wrap="nowrap">
<FileText size={15} color="#64748B" style={{ marginTop: 2 }} />
<Stack gap={2}>
<Text fz={13} fw={500} c="#374151">
Customs clearing agent:{" "}
<Text component="span" fw={700} c="#10202F">
{booking.customsClearingAgent}
</Text>
</Text>
{(booking.customsClearingAgentEmail ||
booking.customsClearingAgentPhone) && (
<Text fz={12.5} c="#64748B">
{[
booking.customsClearingAgentEmail,
booking.customsClearingAgentPhone,
]
.filter(Boolean)
.join(" · ")}
</Text>
)}
</Stack>
</Group>
</Box>
) : null}
</SectionCard>
);
}