mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 18:55:42 +00:00
feat(train-scheduling): implement container movement between wagons
- Added functionality to move containers between wagons in the train scheduling system. - Introduced API endpoint and service method to handle container movement. - Updated component to support drag-and-drop for rearranging containers. - Enhanced to allow moving containers to other wagons via a context menu. - Implemented UI feedback for container movement actions, including loading states and success/error notifications. - Updated relevant types and constants to accommodate new container movement logic. - Added tests for the rule engine to ensure proper handling of hazardous bookings.
This commit is contained in:
@@ -280,3 +280,135 @@ export function RouteCorridor({
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
/** Minimal booking shape the occupancy strip needs from TrainScheduleDetail. */
|
||||
export type SegmentStripBooking = {
|
||||
originYardId?: string | null;
|
||||
destinationYardId?: string | null;
|
||||
tradeDirection?: string | null;
|
||||
wagonsRequired?: number | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Per-segment wagon occupancy along the corridor: which legs are full and
|
||||
* which still run empty. Through cargo (unknown/off-route yards) occupies the
|
||||
* whole corridor; a ride-along counts only on its own leg — this is what makes
|
||||
* "export full Adama→Doraleh, intercity riding Gelan→Adama" legible at a
|
||||
* glance instead of two disconnected booking lists.
|
||||
*/
|
||||
export function SegmentOccupancyStrip({
|
||||
stops,
|
||||
bookings,
|
||||
maxWagons,
|
||||
}: {
|
||||
stops: Array<{ yardId: string; label: string }>;
|
||||
bookings: SegmentStripBooking[];
|
||||
maxWagons?: number | null;
|
||||
}) {
|
||||
if (stops.length < 2) return null;
|
||||
const lastIdx = stops.length - 1;
|
||||
const indexOf = new Map(stops.map((s, i) => [s.yardId, i]));
|
||||
|
||||
const segments = stops.slice(0, -1).map((stop, edge) => {
|
||||
let cargo = 0;
|
||||
let intercity = 0;
|
||||
for (const b of bookings) {
|
||||
const from = (b.originYardId ? indexOf.get(b.originYardId) : undefined) ?? 0;
|
||||
const to =
|
||||
(b.destinationYardId ? indexOf.get(b.destinationYardId) : undefined) ??
|
||||
lastIdx;
|
||||
const rides = from <= edge && edge < (to > from ? to : lastIdx);
|
||||
if (!rides) continue;
|
||||
const wagons = Number(b.wagonsRequired) || 1;
|
||||
if (b.tradeDirection === "DOMESTIC") intercity += wagons;
|
||||
else cargo += wagons;
|
||||
}
|
||||
return { from: stop, to: stops[edge + 1], cargo, intercity };
|
||||
});
|
||||
|
||||
const cap = Number(maxWagons) || null;
|
||||
|
||||
return (
|
||||
<Group gap={0} wrap="nowrap" align="stretch" style={{ overflowX: "auto", paddingBottom: 4 }}>
|
||||
{segments.map((seg, i) => {
|
||||
const used = seg.cargo + seg.intercity;
|
||||
const pct = cap ? Math.min(100, Math.round((used / cap) * 100)) : null;
|
||||
const full = cap != null && used >= cap;
|
||||
return (
|
||||
<Group key={seg.from.yardId} gap={0} wrap="nowrap" align="stretch">
|
||||
<Stack gap={2} align="center" justify="flex-end" style={{ minWidth: 0 }}>
|
||||
<Box
|
||||
w={9}
|
||||
h={9}
|
||||
style={{
|
||||
borderRadius: 999,
|
||||
border: `2px solid ${freightBrand.primary}`,
|
||||
background: i === 0 ? "white" : freightBrand.primary,
|
||||
}}
|
||||
/>
|
||||
<Text size="xs" fw={600} style={{ whiteSpace: "nowrap" }}>
|
||||
{seg.from.label}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack gap={3} px={10} pb={16} justify="flex-end" style={{ minWidth: 130 }}>
|
||||
<Text size="xs" ta="center" fw={600} c={full ? "orange.8" : "dimmed"}>
|
||||
{used}
|
||||
{cap ? `/${cap}` : ""} wagons
|
||||
{full ? " · full" : ""}
|
||||
</Text>
|
||||
<Box
|
||||
style={{
|
||||
height: 6,
|
||||
borderRadius: 999,
|
||||
background: "var(--mantine-color-gray-2)",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
{cap ? (
|
||||
<>
|
||||
<Box
|
||||
style={{
|
||||
width: `${Math.min(100, (seg.cargo / cap) * 100)}%`,
|
||||
background: freightBrand.primary,
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
width: `${Math.min(100, (seg.intercity / cap) * 100)}%`,
|
||||
background: "var(--mantine-color-indigo-6)",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Box style={{ width: pct ? `${pct}%` : 0 }} />
|
||||
)}
|
||||
</Box>
|
||||
<Text size="xs" ta="center" c="dimmed" style={{ whiteSpace: "nowrap" }}>
|
||||
{seg.cargo} cargo
|
||||
{seg.intercity > 0 ? (
|
||||
<Text span size="xs" fw={700} c="indigo.7">
|
||||
{" "}
|
||||
· {seg.intercity} intercity
|
||||
</Text>
|
||||
) : null}
|
||||
</Text>
|
||||
</Stack>
|
||||
{i === segments.length - 1 ? (
|
||||
<Stack gap={2} align="center" justify="flex-end" style={{ minWidth: 0 }}>
|
||||
<Box
|
||||
w={9}
|
||||
h={9}
|
||||
style={{ borderRadius: 999, background: freightBrand.primary }}
|
||||
/>
|
||||
<Text size="xs" fw={600} style={{ whiteSpace: "nowrap" }}>
|
||||
{seg.to.label}
|
||||
</Text>
|
||||
</Stack>
|
||||
) : null}
|
||||
</Group>
|
||||
);
|
||||
})}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user