mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-06 22:55:02 +00:00
feat: full wagon cancel, leg board, wagon dates
feat(freight): editable train leg times, SL invoice payer
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
MapPin,
|
||||
Navigation,
|
||||
PackageCheck,
|
||||
Pencil,
|
||||
Train,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
@@ -29,9 +30,10 @@ import {
|
||||
} from "@mantine/core";
|
||||
|
||||
import { PageContainer } from "@/components/page";
|
||||
import { CheckpointTimeModal } from "@/components/trainScheduling/CheckpointTimeModal";
|
||||
import { LogPassYardWorkModal } from "@/components/trainScheduling/LogPassYardWorkModal";
|
||||
import { RouteCorridorTrack } from "@/components/trainScheduling/RouteCorridorTrack";
|
||||
import type { TrackStation } from "@/types/trainScheduling";
|
||||
import type { TrackStation, TrainCheckpoint } from "@/types/trainScheduling";
|
||||
import {
|
||||
RouteCorridor,
|
||||
StatusPill,
|
||||
@@ -156,6 +158,16 @@ export default function TrainScheduleTrackPage() {
|
||||
const recordCheckpoint = useMutation(
|
||||
api.trainScheduling.recordCheckpoint.mutationOptions(),
|
||||
);
|
||||
const updateCheckpoint = useMutation(
|
||||
api.trainScheduling.updateCheckpoint.mutationOptions(),
|
||||
);
|
||||
// Time-entry dialogs: logging a pass at a yard with no work (the yard-work
|
||||
// modal carries its own picker), and correcting an already-logged leg.
|
||||
const [logModal, setLogModal] = useState<{
|
||||
station: TrackStation;
|
||||
isFinal: boolean;
|
||||
} | null>(null);
|
||||
const [editModal, setEditModal] = useState<TrainCheckpoint | null>(null);
|
||||
// Yard work drives the log-pass modal: which bookings board/alight per stop.
|
||||
const yardWorkQuery = useQuery(
|
||||
api.trainScheduling.yardWork.queryOptions({
|
||||
@@ -241,15 +253,30 @@ export default function TrainScheduleTrackPage() {
|
||||
|
||||
const handleLog = (sequenceNo: number) => {
|
||||
const station = track.stations.find((s) => s.sequenceNo === sequenceNo);
|
||||
if (!station) return;
|
||||
const isFinal = sequenceNo === track.stations[totalStations - 1]?.sequenceNo;
|
||||
if (station && stationHasWork(station)) {
|
||||
if (stationHasWork(station)) {
|
||||
setYardModal({ station, isFinal, alreadyLogged: false });
|
||||
return;
|
||||
}
|
||||
setLogModal({ station, isFinal });
|
||||
};
|
||||
|
||||
const submitLog = (values: { occurredAt: string; note: string }) => {
|
||||
if (!logModal) return;
|
||||
const { station, isFinal } = logModal;
|
||||
recordCheckpoint.mutate(
|
||||
{ id: scheduleId, payload: { sequenceNo } },
|
||||
{
|
||||
id: scheduleId,
|
||||
payload: {
|
||||
sequenceNo: station.sequenceNo,
|
||||
occurredAt: values.occurredAt,
|
||||
...(values.note ? { note: values.note } : {}),
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setLogModal(null);
|
||||
toast({
|
||||
title: isFinal
|
||||
? "Train arrived — assets freed, moved to destination yard"
|
||||
@@ -266,6 +293,32 @@ export default function TrainScheduleTrackPage() {
|
||||
);
|
||||
};
|
||||
|
||||
const submitEdit = (values: { occurredAt: string; note: string }) => {
|
||||
if (!editModal) return;
|
||||
updateCheckpoint.mutate(
|
||||
{
|
||||
id: scheduleId,
|
||||
sequenceNo: editModal.sequenceNo,
|
||||
payload: { occurredAt: values.occurredAt, note: values.note || null },
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setEditModal(null);
|
||||
toast({ title: "Checkpoint updated" });
|
||||
},
|
||||
onError: (err) =>
|
||||
toast({
|
||||
title: "Could not update checkpoint",
|
||||
description: parseError(err, "Please try again"),
|
||||
variant: "destructive",
|
||||
}),
|
||||
},
|
||||
);
|
||||
};
|
||||
// Legs stay correctable for as long as the journey exists — while rolling
|
||||
// and after arrival.
|
||||
const canEdit = track.status === "DISPATCHED" || track.status === "ARRIVED";
|
||||
|
||||
// "Forgot to load" catch: while the train sits at the current station, any
|
||||
// boarder there that is still unloaded can be loaded until the next pass.
|
||||
const currentStationObj = track.stations.find(
|
||||
@@ -502,6 +555,7 @@ export default function TrainScheduleTrackPage() {
|
||||
: null
|
||||
}
|
||||
onLogCheckpoint={handleLog}
|
||||
onEditCheckpoint={canEdit ? setEditModal : undefined}
|
||||
/>
|
||||
|
||||
{/* Cargo the operator forgot: boarders at the CURRENT station stay
|
||||
@@ -595,24 +649,38 @@ export default function TrainScheduleTrackPage() {
|
||||
)
|
||||
}
|
||||
title={
|
||||
<Group gap="sm">
|
||||
<Text fw={700} size="sm">
|
||||
{cp.label ?? `Station ${cp.sequenceNo}`}
|
||||
</Text>
|
||||
<Badge
|
||||
size="xs"
|
||||
radius="sm"
|
||||
variant="light"
|
||||
color={
|
||||
cp.kind === "ARRIVED"
|
||||
? "teal"
|
||||
: cp.kind === "DEPARTED"
|
||||
? "blue"
|
||||
: "edr-green"
|
||||
}
|
||||
>
|
||||
{cp.kind}
|
||||
</Badge>
|
||||
<Group gap="sm" justify="space-between" wrap="nowrap">
|
||||
<Group gap="sm">
|
||||
<Text fw={700} size="sm">
|
||||
{cp.label ?? `Station ${cp.sequenceNo}`}
|
||||
</Text>
|
||||
<Badge
|
||||
size="xs"
|
||||
radius="sm"
|
||||
variant="light"
|
||||
color={
|
||||
cp.kind === "ARRIVED"
|
||||
? "teal"
|
||||
: cp.kind === "DEPARTED"
|
||||
? "blue"
|
||||
: "edr-green"
|
||||
}
|
||||
>
|
||||
{cp.kind}
|
||||
</Badge>
|
||||
</Group>
|
||||
{canEdit ? (
|
||||
<Button
|
||||
size="compact-xs"
|
||||
radius="md"
|
||||
variant="light"
|
||||
color="gray"
|
||||
leftSection={<Pencil size={12} />}
|
||||
onClick={() => setEditModal(cp)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
) : null}
|
||||
</Group>
|
||||
}
|
||||
>
|
||||
@@ -630,6 +698,39 @@ export default function TrainScheduleTrackPage() {
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
<CheckpointTimeModal
|
||||
opened={logModal !== null}
|
||||
onClose={() => setLogModal(null)}
|
||||
title={
|
||||
logModal?.isFinal
|
||||
? `Mark arrived at ${logModal.station.label}`
|
||||
: `Log pass at ${logModal?.station.label ?? "station"}`
|
||||
}
|
||||
icon={logModal?.isFinal ? <Flag size={18} /> : <MapPin size={18} />}
|
||||
description={
|
||||
logModal?.isFinal
|
||||
? "Marks the train arrived: remaining bookings arrive, assets are freed."
|
||||
: undefined
|
||||
}
|
||||
submitLabel={logModal?.isFinal ? "Mark arrived" : "Log pass"}
|
||||
submitColor={logModal?.isFinal ? "teal" : "edr-green"}
|
||||
loading={recordCheckpoint.isPending}
|
||||
onSubmit={submitLog}
|
||||
/>
|
||||
|
||||
<CheckpointTimeModal
|
||||
opened={editModal !== null}
|
||||
onClose={() => setEditModal(null)}
|
||||
title={`Edit ${editModal?.label ?? "checkpoint"}`}
|
||||
icon={<Pencil size={18} />}
|
||||
description="Corrects this leg's time and note only — nothing else changes."
|
||||
initialOccurredAt={editModal?.occurredAt}
|
||||
initialNote={editModal?.note}
|
||||
submitLabel="Save"
|
||||
loading={updateCheckpoint.isPending}
|
||||
onSubmit={submitEdit}
|
||||
/>
|
||||
|
||||
<LogPassYardWorkModal
|
||||
opened={yardModal !== null}
|
||||
onClose={() => setYardModal(null)}
|
||||
|
||||
Reference in New Issue
Block a user