add permissions and fix issues

This commit is contained in:
Marshal
2026-07-23 20:24:20 +00:00
parent 40f16f3cec
commit 668b5e1c9d
40 changed files with 18634 additions and 342 deletions

View File

@@ -1,5 +1,6 @@
import { Link, useParams } from "react-router-dom";
import { isAxiosError } from "axios";
import { useState } from "react";
import {
ArrowLeft,
CalendarClock,
@@ -7,9 +8,11 @@ import {
Flag,
MapPin,
Navigation,
PackageCheck,
Train,
} from "lucide-react";
import {
Alert,
Badge,
Box,
Button,
@@ -25,7 +28,9 @@ import {
} from "@mantine/core";
import { PageContainer } from "@/components/page";
import { LogPassYardWorkModal } from "@/components/trainScheduling/LogPassYardWorkModal";
import { RouteCorridorTrack } from "@/components/trainScheduling/RouteCorridorTrack";
import type { TrackStation } from "@/types/trainScheduling";
import {
RouteCorridor,
StatusPill,
@@ -148,6 +153,18 @@ export default function TrainScheduleTrackPage() {
const recordCheckpoint = useMutation(
api.trainScheduling.recordCheckpoint.mutationOptions(),
);
// Yard work drives the log-pass modal: which bookings board/alight per stop.
const yardWorkQuery = useQuery(
api.trainScheduling.yardWork.queryOptions({
input: { scheduleId: scheduleId ?? "" },
enabled: Boolean(scheduleId) && trackQuery.data?.status === "DISPATCHED",
}),
);
const [yardModal, setYardModal] = useState<{
station: TrackStation;
isFinal: boolean;
alreadyLogged: boolean;
} | null>(null);
if (trackQuery.isLoading) {
return (
@@ -181,8 +198,26 @@ export default function TrainScheduleTrackPage() {
const inTransit = track.status === "DISPATCHED";
const arrived = track.status === "ARRIVED";
// Yard work at a station: boarders not yet loaded, and loaded bookings that
// alight there. When either exists, logging the pass goes through the modal
// so the operator sees (and can act on) both lists; empty yards log directly.
const yardWorkFor = (station: TrackStation | undefined) =>
yardWorkQuery.data?.yards.find((y) => y.yardId === station?.yardId);
const stationHasWork = (station: TrackStation | undefined) => {
const yard = yardWorkFor(station);
return Boolean(
yard &&
(yard.toLoad.some((r) => !r.loadedAt) || yard.toUnload.some((r) => r.canUnload)),
);
};
const handleLog = (sequenceNo: number) => {
const station = track.stations.find((s) => s.sequenceNo === sequenceNo);
const isFinal = sequenceNo === track.stations[totalStations - 1]?.sequenceNo;
if (station && stationHasWork(station)) {
setYardModal({ station, isFinal, alreadyLogged: false });
return;
}
recordCheckpoint.mutate(
{ id: scheduleId, payload: { sequenceNo } },
{
@@ -203,6 +238,15 @@ export default function TrainScheduleTrackPage() {
);
};
// "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(
(s) => s.sequenceNo === track.currentSequenceNo,
);
const currentYard = canLog ? yardWorkFor(currentStationObj) : undefined;
const forgottenBoarders =
currentYard?.toLoad.filter((r) => !r.loadedAt) ?? [];
return (
<PageContainer>
<Button
@@ -416,6 +460,43 @@ export default function TrainScheduleTrackPage() {
}
onLogCheckpoint={handleLog}
/>
{/* Cargo the operator forgot: boarders at the CURRENT station stay
loadable until the next pass is logged. */}
{currentStationObj && forgottenBoarders.length > 0 ? (
<Alert
color="yellow"
variant="light"
radius="md"
icon={<PackageCheck size={16} />}
title={`${forgottenBoarders.length} booking${
forgottenBoarders.length === 1 ? "" : "s"
} at ${currentStationObj.label} not loaded yet`}
>
<Group justify="space-between" align="center" wrap="wrap" gap="sm">
<Text size="sm">
The train is at {currentStationObj.label} cargo boarding here can
still be loaded before the next station is logged.
</Text>
<Button
size="compact-sm"
variant="light"
color="yellow"
onClick={() =>
setYardModal({
station: currentStationObj,
isFinal:
currentStationObj.sequenceNo ===
track.stations[totalStations - 1]?.sequenceNo,
alreadyLogged: true,
})
}
>
Open yard work
</Button>
</Group>
</Alert>
) : null}
</Stack>
</Paper>
@@ -505,6 +586,15 @@ export default function TrainScheduleTrackPage() {
</Timeline>
)}
</Paper>
<LogPassYardWorkModal
opened={yardModal !== null}
onClose={() => setYardModal(null)}
scheduleId={scheduleId}
station={yardModal?.station ?? null}
isFinal={yardModal?.isFinal ?? false}
alreadyLogged={yardModal?.alreadyLogged ?? false}
/>
</PageContainer>
);
}