mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 00:38:11 +00:00
feat: enhance station work logging with user display names and improve loading/unloading logic
This commit is contained in:
@@ -217,6 +217,20 @@ export function StationWorkControls({
|
||||
{fmtTime(log!.startedAt!)} → {ended ? fmtTime(log!.endedAt!) : "…"} (
|
||||
{fmtElapsed(log!.startedAt!, log?.endedAt)})
|
||||
</Text>
|
||||
{log?.startedByName || log?.endedByName ? (
|
||||
<Tooltip
|
||||
label={[
|
||||
log?.startedByName ? `Started by ${log.startedByName}` : null,
|
||||
log?.endedByName ? `Ended by ${log.endedByName}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ")}
|
||||
>
|
||||
<Badge size="xs" variant="light" color="gray" radius="sm">
|
||||
{log?.endedByName ?? log?.startedByName}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<EditTimeButton
|
||||
label={`${phase} start`}
|
||||
value={log!.startedAt!}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ArrowLeft,
|
||||
CalendarClock,
|
||||
CheckCircle2,
|
||||
Clock,
|
||||
FileText,
|
||||
Flag,
|
||||
MapPin,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
Pencil,
|
||||
Train,
|
||||
} from "lucide-react";
|
||||
import { StationWorkControls } from "@/components/trainScheduling/StationWorkControls";
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
@@ -653,6 +655,78 @@ export default function TrainScheduleTrackPage() {
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* ── Loading / unloading windows per station ── */}
|
||||
<Paper radius="lg" p="lg" withBorder style={CARD_STYLE}>
|
||||
<SectionHead
|
||||
icon={<Clock size={17} />}
|
||||
title="Loading & unloading windows"
|
||||
hint="Start and end each station's work window — times, duration and who recorded them"
|
||||
/>
|
||||
<Stack gap="sm" mt="md">
|
||||
{track.stations.map((s, i) => {
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === track.stations.length - 1;
|
||||
const workLog = track.stationWorkLogs?.[s.yardId];
|
||||
return (
|
||||
<Paper
|
||||
key={s.yardId}
|
||||
withBorder
|
||||
radius="md"
|
||||
p="sm"
|
||||
style={{
|
||||
background:
|
||||
track.currentSequenceNo === s.sequenceNo
|
||||
? "var(--mantine-color-green-0)"
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
<Group gap={10} mb={6} wrap="nowrap">
|
||||
<ThemeIcon size={30} radius="xl" variant="light" color="edr-green">
|
||||
{isLast ? <Flag size={15} /> : <MapPin size={15} />}
|
||||
</ThemeIcon>
|
||||
<Text fw={700} size="sm">
|
||||
{s.label}
|
||||
</Text>
|
||||
{isFirst ? (
|
||||
<Badge size="xs" variant="light" color="edr-green">
|
||||
origin
|
||||
</Badge>
|
||||
) : null}
|
||||
{isLast ? (
|
||||
<Badge size="xs" variant="light" color="gray">
|
||||
destination
|
||||
</Badge>
|
||||
) : null}
|
||||
{track.currentSequenceNo === s.sequenceNo ? (
|
||||
<Badge size="xs" variant="filled" color="edr-green">
|
||||
train here
|
||||
</Badge>
|
||||
) : null}
|
||||
</Group>
|
||||
<Stack gap={6} pl={40}>
|
||||
{!isLast ? (
|
||||
<StationWorkControls
|
||||
scheduleId={scheduleId ?? ""}
|
||||
yardId={s.yardId}
|
||||
phase="loading"
|
||||
log={workLog?.loading}
|
||||
/>
|
||||
) : null}
|
||||
{!isFirst ? (
|
||||
<StationWorkControls
|
||||
scheduleId={scheduleId ?? ""}
|
||||
yardId={s.yardId}
|
||||
phase="unloading"
|
||||
log={workLog?.unloading}
|
||||
/>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* ── Checkpoint log ── */}
|
||||
<Paper radius="lg" p="lg" withBorder style={CARD_STYLE}>
|
||||
<Group justify="space-between" wrap="nowrap" mb="md">
|
||||
|
||||
@@ -499,10 +499,16 @@ export default function TrainScheduleV2DetailPage() {
|
||||
? schedule.stationWorkLogs?.[originYardId]?.loading
|
||||
: undefined;
|
||||
const originLoadingStarted = Boolean(originLoadingLog?.startedAt);
|
||||
const originLoadingEnded = Boolean(originLoadingLog?.endedAt);
|
||||
const dispatchBoardersKept = pendingOriginBoarders.some(
|
||||
(b) => b.isGovernment || dispatchLoadedIds.has(b.id),
|
||||
);
|
||||
const dispatchNeedsLoadingStart = dispatchBoardersKept && !originLoadingStarted;
|
||||
// A train never departs mid-loading: once the window opened (or cargo is to
|
||||
// board), it must be ENDED before dispatch — same gate the server enforces.
|
||||
const dispatchNeedsLoadingEnd =
|
||||
(dispatchBoardersKept || originLoadingStarted) && !originLoadingEnded;
|
||||
const dispatchBlockedByLoading = dispatchNeedsLoadingStart || dispatchNeedsLoadingEnd;
|
||||
|
||||
const finalizeStep = hasContainerStep ? 3 : 2;
|
||||
const canModifyBookings = canEditBookings && !["DISPATCHED", "ARRIVED"].includes(schedule.status);
|
||||
@@ -966,6 +972,11 @@ export default function TrainScheduleV2DetailPage() {
|
||||
Start loading before dispatching — the ticked bookings are marked
|
||||
loaded at dispatch, which needs an open loading window.
|
||||
</Text>
|
||||
) : dispatchNeedsLoadingEnd ? (
|
||||
<Text size="xs" c="dimmed">
|
||||
End the loading window before dispatching — a train never departs
|
||||
mid-loading.
|
||||
</Text>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
@@ -1699,14 +1710,18 @@ export default function TrainScheduleV2DetailPage() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Tooltip
|
||||
label="Start loading at the origin station first — dispatch marks the ticked bookings loaded"
|
||||
disabled={!dispatchNeedsLoadingStart}
|
||||
label={
|
||||
dispatchNeedsLoadingStart
|
||||
? "Start loading at the origin station first — dispatch marks the ticked bookings loaded"
|
||||
: "End the loading window at the origin station first — a train never departs mid-loading"
|
||||
}
|
||||
disabled={!dispatchBlockedByLoading}
|
||||
>
|
||||
<Button
|
||||
color="edr-green"
|
||||
leftSection={<Send size={16} />}
|
||||
loading={dispatch.isPending}
|
||||
disabled={dispatchNeedsLoadingStart}
|
||||
disabled={dispatchBlockedByLoading}
|
||||
onClick={() => void runDispatch()}
|
||||
>
|
||||
{hasDispatchWarnings ? "Dispatch anyway" : "Dispatch train"}
|
||||
|
||||
@@ -902,6 +902,9 @@ export interface StationWorkPhaseLog {
|
||||
endedAt?: string | null;
|
||||
startedByUserId?: string | null;
|
||||
endedByUserId?: string | null;
|
||||
/** Resolved display names of the recorders (track endpoint only). */
|
||||
startedByName?: string | null;
|
||||
endedByName?: string | null;
|
||||
}
|
||||
|
||||
export interface StationWorkLog {
|
||||
|
||||
Reference in New Issue
Block a user