Merge pull request #1430 from Tria-plc/freight_feature/usermanagement

feat: simplify dispatch logic by removing manual loading checks and u…
This commit is contained in:
marshal
2026-08-27 14:17:27 +03:00
committed by GitHub

View File

@@ -135,14 +135,8 @@ export default function TrainScheduleV2DetailPage() {
// Actual departure — staff often dispatch on paper first and record it later,
// so the time is picked (defaults to now when the dialog opens).
const [dispatchAt, setDispatchAt] = useState<Date | null>(null);
// Loading is manual: dispatch decides the fate of every unloaded origin
// boarder — checked = loaded and departs, unchecked = left behind (wagon
// freed, booking back to the pool). Default unchecked; government bookings
// cannot be removed from a train so they are forced on.
const [dispatchLoadedIds, setDispatchLoadedIds] = useState<Set<string>>(new Set());
const openDispatchConfirm = () => {
setDispatchAt(new Date());
setDispatchLoadedIds(new Set());
setDispatchConfirmOpen(true);
};
const [switchTarget, setSwitchTarget] = useState<EligibleContainerBooking | null>(null);
@@ -473,9 +467,8 @@ export default function TrainScheduleV2DetailPage() {
// per yard from the track page's log-pass flow. Everything below is advisory.
const hasDispatchWarnings =
unassignedCount > 0 || unloadedCount > 0 || intercityNotLoadedCount > 0;
// Unloaded boarders at the TRAIN's origin — the dispatch dialog's manual
// load/leave list. Mirrors the API's unloadedOriginBoarderIds predicate
// (plus government, which is shown but forced-loaded).
// Unloaded boarders at the TRAIN's origin — all sent as loaded on dispatch.
// Mirrors the API's unloadedOriginBoarderIds predicate (plus government).
const originYardId = schedule.originStation?.id;
const pendingOriginBoarders = dispatchBookings.filter(
(b) =>
@@ -489,12 +482,9 @@ export default function TrainScheduleV2DetailPage() {
// Shipping-line bookings ride from accept on the credit ledger.
(Boolean(b.shippingLineCompanyId) && b.status === "FULLY_EXECUTED")),
);
const dispatchLeftCount = pendingOriginBoarders.filter(
(b) => !b.isGovernment && !dispatchLoadedIds.has(b.id),
).length;
// Origin loading time window: dispatch (which marks the ticked boarders
// loaded) is server-rejected until "Start loading" was clicked for the
// origin yard, so the button mirrors that gate.
// Origin loading time window: dispatch (which marks the boarders loaded)
// is server-rejected until "Start loading" was clicked for the origin
// yard, so the button mirrors that gate.
const originLoadingLog = originYardId
? schedule.stationWorkLogs?.[originYardId]?.loading
: undefined;
@@ -557,9 +547,9 @@ export default function TrainScheduleV2DetailPage() {
id: scheduleId,
payload: {
...(dispatchAt ? { actualDepartureAt: dispatchAt.toISOString() } : {}),
loadedBookingIds: pendingOriginBoarders
.filter((b) => b.isGovernment || dispatchLoadedIds.has(b.id))
.map((b) => b.id),
// No per-booking ticking in the dispatch dialog: every pending origin
// boarder rides — none are left behind at dispatch time.
loadedBookingIds: pendingOriginBoarders.map((b) => b.id),
},
});
await openMarshallingDocument({
@@ -1590,56 +1580,6 @@ export default function TrainScheduleV2DetailPage() {
radius="md"
/>
{pendingOriginBoarders.length > 0 ? (
<Stack gap={6}>
<Text size="sm" fw={700}>
Cargo boarding at {schedule.originStation?.label ?? "the origin yard"}
tick what was loaded
</Text>
{originYardId ? (
<StationWorkControls
scheduleId={scheduleId}
yardId={originYardId}
phase="loading"
log={originLoadingLog}
/>
) : null}
<Text size="xs" c="dimmed">
Unticked bookings are left behind: removed from this train, their
wagons freed, and the booking returned to the pool for a later
schedule. The customer is notified.
</Text>
<Stack gap={6} mah={220} style={{ overflowY: "auto" }}>
{pendingOriginBoarders.map((b) => (
<Checkbox
key={b.id}
size="sm"
checked={b.isGovernment || dispatchLoadedIds.has(b.id)}
disabled={b.isGovernment}
onChange={(e) => {
const next = new Set(dispatchLoadedIds);
if (e.currentTarget.checked) next.add(b.id);
else next.delete(b.id);
setDispatchLoadedIds(next);
}}
label={
<Text size="sm" span>
{b.reference ?? b.id.slice(0, 8)} {b.customer ?? "Unknown customer"}
{b.isGovernment ? " (government — always rides)" : ""}
</Text>
}
/>
))}
</Stack>
{dispatchLeftCount > 0 ? (
<Text size="xs" c="orange.7" fw={600}>
{dispatchLeftCount} booking{dispatchLeftCount === 1 ? "" : "s"} will
be left behind and returned to the booking pool.
</Text>
) : null}
</Stack>
) : null}
{hasDispatchWarnings ? (
<Alert
color="orange"