mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 19:30:57 +00:00
remove reopen delay minutes from global rules and update related types
- Removed the field from and related components. - Updated to reflect the removal of the reopen delay input field. - Modified to include new train number fields: and . - Added interface to manage active schedules with trade direction. - Introduced interface to track wagon shortages in bookings. - Updated logic to ensure consistent UI state representation. - Created migrations to drop the column and add and columns to the table. - Added tests for the new booking window display logic and wagon planning functionality.
This commit is contained in:
@@ -152,6 +152,9 @@ export function ScheduleWorkspacePanel({
|
||||
|
||||
// ── Mutations (reuse the existing endpoints) ───────────────────────────────
|
||||
const assign = useMutation(api.trainScheduling.assignBookings.mutationOptions());
|
||||
const assignUnassigned = useMutation(
|
||||
api.trainScheduling.assignUnassignedBooking.mutationOptions(),
|
||||
);
|
||||
const unassign = useMutation(api.trainScheduling.unassignBooking.mutationOptions());
|
||||
const setLoading = useMutation(
|
||||
api.trainScheduling.setLoadingStatus.mutationOptions(),
|
||||
@@ -166,6 +169,12 @@ export function ScheduleWorkspacePanel({
|
||||
const [moveBookingId, setMoveBookingId] = useState<string | null>(null);
|
||||
const [moveTarget, setMoveTarget] = useState<string | null>(null);
|
||||
|
||||
// Pool → pick a same-day schedule with free wagons and place the booking there.
|
||||
const [poolAssign, setPoolAssign] = useState<{ id: string; reference: string } | null>(
|
||||
null,
|
||||
);
|
||||
const [poolTarget, setPoolTarget] = useState<string | null>(null);
|
||||
|
||||
const { data: targets } = useQuery(
|
||||
api.trainScheduling.bookableSchedules.queryOptions({
|
||||
input: {
|
||||
@@ -190,6 +199,22 @@ export function ScheduleWorkspacePanel({
|
||||
[targets, schedule.id],
|
||||
);
|
||||
|
||||
// Every schedule departing on THIS train's day (EAT) — a paid booking waiting
|
||||
// for a wagon may board any of them, so staff pick whichever has wagons free.
|
||||
const eatDayOf = (iso: string) =>
|
||||
new Date(iso).toLocaleDateString("en-CA", { timeZone: "Africa/Addis_Ababa" });
|
||||
const sameDayOptions = useMemo(() => {
|
||||
const day = eatDayOf(schedule.scheduledDepartureDate);
|
||||
return (targets ?? [])
|
||||
.filter((s) => eatDayOf(s.scheduleDate) === day)
|
||||
.map((s) => ({
|
||||
value: s.id,
|
||||
label: `${s.id === schedule.id ? "This train · " : ""}${
|
||||
s.routeName ?? `${s.origin} → ${s.destination}`
|
||||
} · ${s.remainingWagons}/${s.maxWagons} wagons free`,
|
||||
}));
|
||||
}, [targets, schedule.id, schedule.scheduledDepartureDate]);
|
||||
|
||||
// ── Capacity meter (by cargo weight vs locomotive pull) ────────────────────
|
||||
const used = usedWeight(schedule);
|
||||
const capacity = pullCapacity(schedule);
|
||||
@@ -288,6 +313,36 @@ export function ScheduleWorkspacePanel({
|
||||
);
|
||||
};
|
||||
|
||||
// Point the pool booking at the chosen same-day train, then put it on wagons.
|
||||
// If the wagon step fails (that train is short too) the booking stays paid &
|
||||
// unassigned in the pool — nothing is lost, staff just pick another train.
|
||||
const doPoolAssign = () => {
|
||||
if (!poolAssign || !poolTarget) return;
|
||||
const { id: bookingId, reference } = poolAssign;
|
||||
moveSchedule
|
||||
.mutateAsync({ bookingId, trainScheduleId: poolTarget })
|
||||
.then(() => assignUnassigned.mutateAsync({ id: poolTarget, bookingId }))
|
||||
.then(() => {
|
||||
toast({
|
||||
title: `${reference} assigned`,
|
||||
description: "Booking placed on the selected train with wagons pinned.",
|
||||
});
|
||||
setPoolAssign(null);
|
||||
onChanged();
|
||||
void poolQuery.refetch();
|
||||
})
|
||||
.catch((error) =>
|
||||
toast({
|
||||
title: `Could not assign ${reference}`,
|
||||
description: apiErrorMessage(
|
||||
error,
|
||||
"The selected train has no free wagon of the required type.",
|
||||
),
|
||||
variant: "destructive",
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const doMove = () => {
|
||||
if (!moveBookingId || !moveTarget) return;
|
||||
moveSchedule
|
||||
@@ -465,20 +520,41 @@ export function ScheduleWorkspacePanel({
|
||||
customer={b.customer}
|
||||
weightTons={b.weightTons}
|
||||
status={b.status}
|
||||
waitingForWagon={b.schedulingStatus === "WAITING_FOR_WAGON"}
|
||||
right={
|
||||
canManage ? (
|
||||
<Tooltip label="Force-add to this train" withArrow>
|
||||
<Button
|
||||
size="compact-sm"
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
rightSection={<ArrowRight size={14} />}
|
||||
loading={assign.isPending}
|
||||
onClick={() => forceAdd(b.id, b.reference, b.weightTons)}
|
||||
<Group gap={6} wrap="nowrap" justify="flex-end">
|
||||
<Tooltip label="Force-add to this train" withArrow>
|
||||
<Button
|
||||
size="compact-sm"
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
rightSection={<ArrowRight size={14} />}
|
||||
loading={assign.isPending}
|
||||
onClick={() => forceAdd(b.id, b.reference, b.weightTons)}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label="Pick any train departing this day that has wagons free"
|
||||
withArrow
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
size="compact-sm"
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
leftSection={<ArrowLeftRight size={13} />}
|
||||
onClick={() => {
|
||||
setPoolAssign({ id: b.id, reference: b.reference });
|
||||
setPoolTarget(null);
|
||||
}}
|
||||
>
|
||||
Add to…
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
@@ -588,6 +664,52 @@ export function ScheduleWorkspacePanel({
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
{/* Pool → same-day train assignment modal */}
|
||||
<Modal
|
||||
opened={Boolean(poolAssign)}
|
||||
onClose={() => setPoolAssign(null)}
|
||||
title={
|
||||
<Group gap={8}>
|
||||
<Train size={18} />
|
||||
<Text fw={700}>
|
||||
Assign {poolAssign?.reference ?? "booking"} to a train on this day
|
||||
</Text>
|
||||
</Group>
|
||||
}
|
||||
centered
|
||||
radius="lg"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text size="xs" c="dimmed">
|
||||
All open trains departing on this schedule's day. Pick one with
|
||||
free wagons — the booking is placed and its wagons pinned in one step.
|
||||
</Text>
|
||||
<Select
|
||||
label="Target train (same day)"
|
||||
placeholder="Select a departure"
|
||||
data={sameDayOptions}
|
||||
value={poolTarget}
|
||||
onChange={setPoolTarget}
|
||||
searchable
|
||||
nothingFoundMessage="No open schedules depart on this day"
|
||||
/>
|
||||
<Group justify="flex-end">
|
||||
<Button variant="default" onClick={() => setPoolAssign(null)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
color="edr-green"
|
||||
disabled={!poolTarget}
|
||||
loading={moveSchedule.isPending || assignUnassigned.isPending}
|
||||
leftSection={<CheckCircle2 size={16} />}
|
||||
onClick={doPoolAssign}
|
||||
>
|
||||
Assign to train
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{/* Reassign modal */}
|
||||
<Modal
|
||||
opened={Boolean(moveBookingId)}
|
||||
@@ -710,6 +832,7 @@ function BookingCard({
|
||||
weightTons,
|
||||
status,
|
||||
loadingStatus,
|
||||
waitingForWagon,
|
||||
right,
|
||||
}: {
|
||||
reference: string;
|
||||
@@ -717,6 +840,8 @@ function BookingCard({
|
||||
weightTons?: number | null;
|
||||
status?: string | null;
|
||||
loadingStatus?: "LOADED" | "UNLOADED";
|
||||
/** Paid, but no wagon of the required type was free — waiting for one. */
|
||||
waitingForWagon?: boolean;
|
||||
right?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
@@ -742,6 +867,16 @@ function BookingCard({
|
||||
{reference}
|
||||
</Text>
|
||||
{status ? <BookingStatusBadge status={status} /> : null}
|
||||
{waitingForWagon ? (
|
||||
<Tooltip
|
||||
label="Paid, but no wagon of the required type was free. Free a wagon or assign it to a same-day train that has one."
|
||||
withArrow
|
||||
>
|
||||
<Badge size="sm" radius="sm" variant="light" color="orange">
|
||||
Waiting for wagon
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{loadingStatus ? (
|
||||
<Badge
|
||||
size="sm"
|
||||
|
||||
Reference in New Issue
Block a user