mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 12:58:13 +00:00
booking operations and trains scheduling also allocations
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Paper,
|
||||
Progress,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import { CheckCircle2, Container } from "lucide-react";
|
||||
|
||||
import type { ContainerPlacement, ContainerUnitRow } from "@/types/trainScheduling";
|
||||
|
||||
import { autoFillPlacements, unitKey, validateLocalPlacements } from "./containerPlacement.util";
|
||||
|
||||
export function ContainerPlacementGrid({
|
||||
units,
|
||||
containerSlots,
|
||||
placements,
|
||||
onChange,
|
||||
}: {
|
||||
units: ContainerUnitRow[];
|
||||
containerSlots: number[];
|
||||
placements: ContainerPlacement[];
|
||||
onChange: (placements: ContainerPlacement[]) => void;
|
||||
}) {
|
||||
const placementMap = useMemo(() => {
|
||||
const map = new Map<string, ContainerPlacement>();
|
||||
for (const placement of placements) {
|
||||
map.set(unitKey(placement.bookingContainerId, placement.unitIndex), placement);
|
||||
}
|
||||
return map;
|
||||
}, [placements]);
|
||||
|
||||
const issues = useMemo(() => validateLocalPlacements(units, placements), [units, placements]);
|
||||
|
||||
const completedCount = useMemo(
|
||||
() =>
|
||||
units.filter((unit) => {
|
||||
const placement = placementMap.get(unitKey(unit.bookingContainerId, unit.unitIndex));
|
||||
return placement?.sequenceNo && placement.containerNumber?.trim();
|
||||
}).length,
|
||||
[units, placementMap],
|
||||
);
|
||||
|
||||
const slotOptions = containerSlots.map((seq) => ({
|
||||
value: String(seq),
|
||||
label: `Wagon #${seq}`,
|
||||
}));
|
||||
|
||||
const updatePlacement = (unit: ContainerUnitRow, patch: Partial<ContainerPlacement>) => {
|
||||
const key = unitKey(unit.bookingContainerId, unit.unitIndex);
|
||||
const existing = placementMap.get(key);
|
||||
const next: ContainerPlacement = {
|
||||
bookingContainerId: unit.bookingContainerId,
|
||||
unitIndex: unit.unitIndex,
|
||||
sequenceNo: existing?.sequenceNo ?? containerSlots[0] ?? 1,
|
||||
containerNumber: existing?.containerNumber,
|
||||
sealNumber: existing?.sealNumber,
|
||||
...patch,
|
||||
};
|
||||
onChange([
|
||||
...placements.filter(
|
||||
(p) => !(p.bookingContainerId === unit.bookingContainerId && p.unitIndex === unit.unitIndex),
|
||||
),
|
||||
next,
|
||||
]);
|
||||
};
|
||||
|
||||
if (!units.length) {
|
||||
return (
|
||||
<Text size="sm" c="dimmed">
|
||||
No container units in this selection.
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
const progress = units.length ? Math.round((completedCount / units.length) * 100) : 0;
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Paper p="md" radius="xl" withBorder>
|
||||
<Group justify="space-between" align="flex-start" wrap="wrap" gap="md">
|
||||
<Stack gap={4}>
|
||||
<Group gap="xs">
|
||||
<Container size={18} />
|
||||
<Text fw={600} size="sm">
|
||||
Container assignment
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed">
|
||||
Map each booking unit to a wagon slot and enter the container number. One wagon fits
|
||||
either 1×40ft or 2×20ft containers.
|
||||
</Text>
|
||||
</Stack>
|
||||
<Button
|
||||
variant="light"
|
||||
size="compact-sm"
|
||||
onClick={() => onChange(autoFillPlacements(units, containerSlots))}
|
||||
>
|
||||
Auto-fill slots
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<Stack gap={6} mt="md">
|
||||
<Group justify="space-between">
|
||||
<Text size="xs" c="dimmed">
|
||||
{completedCount} of {units.length} units complete
|
||||
</Text>
|
||||
<Text size="xs" fw={500}>
|
||||
{progress}%
|
||||
</Text>
|
||||
</Group>
|
||||
<Progress
|
||||
value={progress}
|
||||
size="sm"
|
||||
radius="xl"
|
||||
color={issues.length ? "yellow" : "teal"}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{issues.length ? (
|
||||
<Stack gap={6}>
|
||||
{issues.map((issue) => (
|
||||
<Badge key={issue} color="red" variant="light" size="sm" w="fit-content">
|
||||
{issue}
|
||||
</Badge>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Badge
|
||||
color="teal"
|
||||
variant="light"
|
||||
size="sm"
|
||||
w="fit-content"
|
||||
leftSection={<CheckCircle2 size={12} />}
|
||||
>
|
||||
All units mapped
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<SimpleGrid cols={{ base: 1, lg: 2 }} spacing="md">
|
||||
{units.map((unit) => {
|
||||
const key = unitKey(unit.bookingContainerId, unit.unitIndex);
|
||||
const placement = placementMap.get(key);
|
||||
const isComplete = placement?.sequenceNo && placement.containerNumber?.trim();
|
||||
|
||||
return (
|
||||
<Card key={key} radius="xl" padding="md" withBorder>
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between" align="flex-start">
|
||||
<Stack gap={2}>
|
||||
<Text size="sm" fw={600}>
|
||||
{unit.bookingReference}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{unit.label} · {unit.containerTypeCode} · {unit.grossWeightTons}T
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge size="sm" variant="light" color={isComplete ? "teal" : "gray"}>
|
||||
{isComplete ? "Ready" : "Pending"}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
|
||||
<Select
|
||||
label="Wagon slot"
|
||||
size="sm"
|
||||
data={slotOptions}
|
||||
value={placement?.sequenceNo ? String(placement.sequenceNo) : null}
|
||||
onChange={(value) =>
|
||||
updatePlacement(unit, { sequenceNo: Number(value ?? containerSlots[0]) })
|
||||
}
|
||||
placeholder="Select wagon"
|
||||
searchable
|
||||
/>
|
||||
<TextInput
|
||||
label="Container number"
|
||||
size="sm"
|
||||
placeholder="e.g. MSCU1234567"
|
||||
value={placement?.containerNumber ?? ""}
|
||||
onChange={(e) =>
|
||||
updatePlacement(unit, {
|
||||
containerNumber: e.currentTarget.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user