feat: add wagon usage computation and maintenance logging features

- Implemented  utility to calculate wagon usage metrics for train schedules.
- Created  for sending wagons to maintenance with optional notes.
- Added unit tests for train builder maintenance functionalities, including formatting train run labels and building maintenance notes.
- Developed  component for merging train schedules with detailed previews and reasons for merging.
- Introduced  component for selecting wagons with search functionality and selection limits.
- Created  for displaying and filtering audit logs, including detailed views of individual log entries.
- Added  for handling API interactions related to audit logs, including fetching logs and entity types.
This commit is contained in:
marshalyordanos
2026-08-12 09:36:50 +03:00
parent 35e5404b41
commit 5da36eb128
77 changed files with 6275 additions and 296 deletions

View File

@@ -142,6 +142,21 @@ export function BookingSchedulingWindowCard({
schedule?.reference ??
(schedule ? "Assigned train" : null);
// Before the batch engine allocates, the only train on the booking is the one
// the customer picked at day-commit — staff review that during Operation
// Review, so it is labelled as a request, not as a confirmed allocation.
const isRequested = schedule?.isRequested === true;
const trainRowLabel = isRequested ? "Requested train" : "Scheduled on train";
// A train that has already left (or is past its planned departure) can no
// longer carry this booking, so accepting onto it would be wrong. Called out
// here because this card sits above the staff-actions toolbar.
const departureIso =
schedule?.actualDepartureAt ?? schedule?.scheduledDepartureDate ?? null;
const hasDeparted = schedule?.actualDepartureAt
? true
: departureIso !== null && new Date(departureIso).getTime() <= nowMs;
return (
<SectionCard
icon={CalendarClock}
@@ -153,7 +168,7 @@ export function BookingSchedulingWindowCard({
<Stack gap="sm">
{trainLabel ? (
<Row
label="Scheduled on train"
label={trainRowLabel}
value={trainLabel}
hint={
schedule?.reference && schedule.reference !== trainLabel
@@ -163,13 +178,21 @@ export function BookingSchedulingWindowCard({
/>
) : (
<Row
label="Scheduled on train"
label={trainRowLabel}
value="Not yet allocated"
tone="muted"
hint="The booking has not been placed on a train schedule"
/>
)}
{isRequested && trainLabel ? (
<Text size="xs" c="dimmed">
{hasDeparted
? "This train has already departed — accepting the operation will not place the booking on it."
: "Picked by the customer at day-commit. Accepting the operation releases the booking to the batch pool for this train."}
</Text>
) : null}
{schedule?.status ? (
<Group justify="space-between" wrap="nowrap">
<Text size="sm" c="dimmed">
@@ -231,10 +254,15 @@ export function BookingSchedulingWindowCard({
formatStamp(schedule.scheduledDepartureDate) ??
"—"
}
tone={isRequested && hasDeparted ? "danger" : undefined}
hint={
schedule.actualDepartureAt
? `Actual · planned ${formatStamp(schedule.scheduledDepartureDate) ?? "—"}`
: "Planned"
: departureIso && !hasDeparted
? `Planned · departs ${formatRelative(departureIso, nowMs)}`
: hasDeparted
? "Planned — already past"
: "Planned"
}
/>
<Row