fix schule issue and contianer type issue

This commit is contained in:
Marshal
2026-07-17 09:16:35 +00:00
parent 5a1e0dba4d
commit 7b7e7c3f62
39 changed files with 508 additions and 187 deletions

View File

@@ -65,7 +65,10 @@ export default function AdjustConsistModal({
}
}, [opened]);
// Live projection: gross = cargo + tare of (consist trims + adds).
// Live projection: gross = cargo + tare of (consist trims + adds), plus
// the schedule's wagon-slot picture — the consist IS the booking capacity
// (weight/length only bind while assembling the consist), so trims/adds
// move the FULL line in real time.
const projection = useMemo(() => {
if (!data) return null;
const removed = new Set(removeIds);
@@ -79,8 +82,11 @@ export default function AdjustConsistModal({
const tare = keptTare + addedWagons.reduce((s, w) => s + tareOf(w), 0);
const length = keptLength + addedWagons.reduce((s, w) => s + lengthOf(w), 0);
const gross = round2(data.totals.cargoTons + tare);
const wagonCount = data.totals.wagonCount - removeIds.length + addIds.length;
const cap = data.scheduleCapacity;
const freeSlots = cap ? wagonCount - cap.allocatedWagons : null;
return {
wagonCount: data.totals.wagonCount - removeIds.length + addIds.length,
wagonCount,
tare: round2(tare),
gross,
length: round2(length),
@@ -93,16 +99,33 @@ export default function AdjustConsistModal({
overWeight: data.limits.pullCapTons > 0 && gross > data.limits.pullCapTons,
overLength:
data.limits.lengthCapMeters > 0 && length > data.limits.lengthCapMeters,
slots:
cap && freeSlots != null
? {
allocated: cap.allocatedWagons,
free: freeSlots,
pct:
wagonCount > 0
? Math.round((cap.allocatedWagons / wagonCount) * 100)
: null,
isFullNow: cap.bookingWindowStatus === "FULL",
willBeFull: freeSlots <= 0,
overAllocated: freeSlots < 0,
willReopen: cap.bookingWindowStatus === "FULL" && freeSlots > 0,
}
: null,
};
}, [data, removeIds, addIds]);
const hasChanges = removeIds.length > 0 || addIds.length > 0;
const toggle = (setter: typeof setRemoveIds) => (id: string, checked: boolean) =>
setter((prev) => (checked ? [...prev, id] : prev.filter((x) => x !== id)));
const handleSubmit = async () => {
if (!removeIds.length && !addIds.length) return;
try {
await adjust.mutateAsync({
const result = await adjust.mutateAsync({
scheduleId,
payload: {
...(addIds.length ? { addWagonIds: addIds } : {}),
@@ -114,6 +137,18 @@ export default function AdjustConsistModal({
removeIds.length && addIds.length ? ", " : ""
}${addIds.length ? `${addIds.length} added` : ""}`,
});
// Schedule-impact warnings from the API: window reopened / now FULL /
// consist trimmed below what bookings already hold.
for (const warning of result.warnings ?? []) {
toast({
title: "Schedule capacity",
description: warning,
duration: 8000,
...(warning.includes("over capacity")
? { variant: "destructive" as const }
: {}),
});
}
setRemoveIds([]);
setAddIds([]);
} catch (err) {
@@ -169,8 +204,57 @@ export default function AdjustConsistModal({
over={projection?.overLength ?? false}
/>
</Grid.Col>
{projection?.slots ? (
<Grid.Col span={12}>
<LimitGauge
label="Booking slots — the consist is the schedule's capacity"
detail={`${projection.slots.allocated} of ${projection.wagonCount} projected wagon slot(s) held by bookings${
projection.slots.free > 0
? `${projection.slots.free} free`
: projection.slots.free === 0
? " — none free (FULL)"
: ""
}`}
pct={projection.slots.pct}
over={projection.slots.overAllocated}
/>
</Grid.Col>
) : null}
</Grid>
{projection?.slots?.isFullNow && !hasChanges ? (
<Alert color="yellow" icon={<AlertTriangle size={16} />}>
This schedule is FULL all {projection.wagonCount} wagon slots are
taken. You can still edit the train: coupling wagons adds capacity
and reopens booking; trimming free wagons keeps it FULL.
</Alert>
) : null}
{hasChanges && projection?.slots?.overAllocated ? (
<Alert color="red" icon={<AlertTriangle size={16} />}>
This change leaves {-projection.slots.free} booked wagon(s) without
a slot bookings already hold {projection.slots.allocated} of the{" "}
{projection.wagonCount} remaining. You can apply it, but couple
wagons back or free bookings before departure.
</Alert>
) : null}
{hasChanges &&
projection?.slots &&
!projection.slots.overAllocated &&
projection.slots.willBeFull &&
!projection.slots.isFullNow ? (
<Alert color="yellow" icon={<AlertTriangle size={16} />}>
This change takes the last free wagon slot the schedule becomes
FULL and stops accepting bookings.
</Alert>
) : null}
{hasChanges && projection?.slots?.willReopen ? (
<Alert color="blue" icon={<AlertTriangle size={16} />}>
This schedule is currently FULL applying frees{" "}
{projection.slots.free} wagon slot(s) and reopens its booking
window.
</Alert>
) : null}
<Grid gap="md">
<Grid.Col span={{ base: 12, md: 6 }}>
<Stack gap="xs">

View File

@@ -5,7 +5,6 @@ import type { ContainerUnitRow } from '@/types/trainScheduling';
function makeUnits(containerType: string, sizeFt: number, quantity: number): ContainerUnitRow[] {
const units: ContainerUnitRow[] = [];
const containersPerWagon = sizeFt >= 40 ? 1 : 2;
const wagonsPerUnit = sizeFt >= 40 ? 1 : 0.5;
for (let i = 0; i < quantity; i++) {
units.push({
@@ -18,7 +17,6 @@ function makeUnits(containerType: string, sizeFt: number, quantity: number): Con
label: `${containerType} ${i + 1}/${quantity}`,
grossWeightTons: 25,
sizeFt,
wagonsPerUnit,
containersPerWagon,
teuSlots: sizeFt >= 40 ? 2 : 1,
});