mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 19:28:17 +00:00
remove reopen delay minutes from global rules and update related types
- Removed the field from and related components. - Updated to reflect the removal of the reopen delay input field. - Modified to include new train number fields: and . - Added interface to manage active schedules with trade direction. - Introduced interface to track wagon shortages in bookings. - Updated logic to ensure consistent UI state representation. - Created migrations to drop the column and add and columns to the table. - Added tests for the new booking window display logic and wagon planning functionality.
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
import { CountdownTimer } from "@edr/ui-common";
|
||||
import { CountdownTimer, bookingWindowUiState } from "@edr/ui-common";
|
||||
import type { MyBookingWindow } from "@/services/bookings.service";
|
||||
import { windowRouteStops } from "@/pages/contracts/booking-window";
|
||||
import { Card } from "./Card";
|
||||
@@ -50,54 +50,34 @@ function windowLabel(w: MyBookingWindow): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* The countdown for whichever phase the window is currently in. Phases run:
|
||||
* pre-window (opens at windowOpensAt) → open (closes at windowClosesAt) →
|
||||
* document review (docReviewEndsAt) → payment (paymentPhaseEndsAt).
|
||||
* The countdown for the window's UI state (shared with the status badge via
|
||||
* `bookingWindowUiState`, so the two can never contradict — a FULL train shows
|
||||
* no ticking "closes in" under a non-open badge).
|
||||
*
|
||||
* `label` describes the deadline being counted down to; `expiredText` names the
|
||||
* NEXT step so that when a deadline lapses between the 60s refetches the row
|
||||
* announces what comes next ("Booking opening now…", "Review starting…") rather
|
||||
* than the bare word "Expired". Returns null when no phase is timing down.
|
||||
*/
|
||||
const COUNTDOWN_TEXT: Partial<
|
||||
Record<
|
||||
ReturnType<typeof bookingWindowUiState>["kind"],
|
||||
{ label: string; expiredText: string }
|
||||
>
|
||||
> = {
|
||||
PRE_WINDOW: { label: "Booking opens in", expiredText: "Booking opening now…" },
|
||||
OPEN: { label: "Window closes in", expiredText: "Document review starting…" },
|
||||
DOC_REVIEW: { label: "Document review ends in", expiredText: "Payment starting…" },
|
||||
PAYMENT: { label: "Payment due in", expiredText: "Payment window closing…" },
|
||||
};
|
||||
|
||||
function phaseCountdown(
|
||||
w: MyBookingWindow,
|
||||
): { label: string; deadline: string; expiredText: string } | null {
|
||||
switch (w.windowPhase) {
|
||||
case "PRE_WINDOW":
|
||||
if (w.windowOpensAt)
|
||||
return {
|
||||
label: "Booking opens in",
|
||||
deadline: w.windowOpensAt,
|
||||
expiredText: "Booking opening now…",
|
||||
};
|
||||
return null;
|
||||
case "OPEN":
|
||||
if (w.windowClosesAt)
|
||||
return {
|
||||
label: "Window closes in",
|
||||
deadline: w.windowClosesAt,
|
||||
expiredText: "Document review starting…",
|
||||
};
|
||||
return null;
|
||||
case "DOC_REVIEW":
|
||||
if (w.docReviewEndsAt)
|
||||
return {
|
||||
label: "Document review ends in",
|
||||
deadline: w.docReviewEndsAt,
|
||||
expiredText: "Payment starting…",
|
||||
};
|
||||
return null;
|
||||
case "PAYMENT":
|
||||
if (w.paymentPhaseEndsAt)
|
||||
return {
|
||||
label: "Payment due in",
|
||||
deadline: w.paymentPhaseEndsAt,
|
||||
expiredText: "Payment window closing…",
|
||||
};
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
const state = bookingWindowUiState(w);
|
||||
const text = COUNTDOWN_TEXT[state.kind];
|
||||
if (!state.countdownTo || !text) return null;
|
||||
return { ...text, deadline: state.countdownTo };
|
||||
}
|
||||
|
||||
function Pill({
|
||||
@@ -147,19 +127,47 @@ function DirectionBadge({ direction }: { direction: MyBookingWindow["direction"]
|
||||
}
|
||||
|
||||
function StatusBadge({ window: w }: { window: MyBookingWindow }) {
|
||||
if (w.isOpenNow) {
|
||||
return (
|
||||
<Pill bg="#ECF6F1" color="#0A6F4D" border="#CDEBDD">
|
||||
Open now
|
||||
</Pill>
|
||||
);
|
||||
}
|
||||
if (w.windowPhase === "PRE_WINDOW" && w.windowOpensAt) {
|
||||
return (
|
||||
<Pill bg="#FEF6E6" color="#B07D14">
|
||||
Opens at {fmtTime(w.windowOpensAt)} EAT
|
||||
</Pill>
|
||||
);
|
||||
const state = bookingWindowUiState(w);
|
||||
switch (state.kind) {
|
||||
case "OPEN":
|
||||
return (
|
||||
<Pill bg="#ECF6F1" color="#0A6F4D" border="#CDEBDD">
|
||||
Open now
|
||||
</Pill>
|
||||
);
|
||||
case "FULL":
|
||||
return (
|
||||
<Pill bg="#FDECEA" color="#B3261E" border="#F6C9C4">
|
||||
Train full
|
||||
</Pill>
|
||||
);
|
||||
case "PRE_WINDOW":
|
||||
if (w.windowOpensAt) {
|
||||
return (
|
||||
<Pill bg="#FEF6E6" color="#B07D14">
|
||||
Opens at {fmtTime(w.windowOpensAt)} EAT
|
||||
</Pill>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "DOC_REVIEW":
|
||||
return (
|
||||
<Pill bg="#EAF1FB" color="#2E5B96">
|
||||
Document review
|
||||
</Pill>
|
||||
);
|
||||
case "PAYMENT":
|
||||
return (
|
||||
<Pill bg="#EAF1FB" color="#2E5B96">
|
||||
Payment window
|
||||
</Pill>
|
||||
);
|
||||
case "CLOSED":
|
||||
return (
|
||||
<Pill bg="#F1F5F9" color={MUTED}>
|
||||
Closed
|
||||
</Pill>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Pill bg="#F1F5F9" color={MUTED}>
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
ChevronRight,
|
||||
Clock,
|
||||
} from "lucide-react";
|
||||
import { CountdownTimer } from "@edr/ui-common";
|
||||
import { CountdownTimer, bookingWindowUiState } from "@edr/ui-common";
|
||||
import type { BookingWindowUiKind } from "@edr/ui-common";
|
||||
|
||||
import type { MyBookingWindow } from "@/services/bookings.service";
|
||||
import {
|
||||
@@ -90,50 +91,29 @@ function windowLabel(w: MyBookingWindow): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* The countdown for whichever phase the window is currently in, mirroring the
|
||||
* home dashboard's Booking Windows card. `expiredText` names the NEXT step so a
|
||||
* deadline that lapses between refetches announces what comes next rather than
|
||||
* the bare "Expired".
|
||||
* The countdown for the window's UI state, mirroring the home dashboard's
|
||||
* Booking Windows card. Derived from the SAME state as the badge
|
||||
* (`bookingWindowUiState`) so they can never contradict — a full train shows
|
||||
* no ticking countdown. `expiredText` names the NEXT step so a deadline that
|
||||
* lapses between refetches announces what comes next rather than the bare
|
||||
* "Expired".
|
||||
*/
|
||||
const COUNTDOWN_TEXT: Partial<
|
||||
Record<BookingWindowUiKind, { label: string; expiredText: string }>
|
||||
> = {
|
||||
PRE_WINDOW: { label: "Booking opens in", expiredText: "Booking opening now…" },
|
||||
OPEN: { label: "Window closes in", expiredText: "Document review starting…" },
|
||||
DOC_REVIEW: { label: "Document review ends in", expiredText: "Payment starting…" },
|
||||
PAYMENT: { label: "Payment due in", expiredText: "Payment window closing…" },
|
||||
};
|
||||
|
||||
function phaseCountdown(
|
||||
w: MyBookingWindow,
|
||||
): { label: string; deadline: string; expiredText: string } | null {
|
||||
switch (w.windowPhase) {
|
||||
case "PRE_WINDOW":
|
||||
return w.windowOpensAt
|
||||
? {
|
||||
label: "Booking opens in",
|
||||
deadline: w.windowOpensAt,
|
||||
expiredText: "Booking opening now…",
|
||||
}
|
||||
: null;
|
||||
case "OPEN":
|
||||
return w.windowClosesAt
|
||||
? {
|
||||
label: "Window closes in",
|
||||
deadline: w.windowClosesAt,
|
||||
expiredText: "Document review starting…",
|
||||
}
|
||||
: null;
|
||||
case "DOC_REVIEW":
|
||||
return w.docReviewEndsAt
|
||||
? {
|
||||
label: "Document review ends in",
|
||||
deadline: w.docReviewEndsAt,
|
||||
expiredText: "Payment starting…",
|
||||
}
|
||||
: null;
|
||||
case "PAYMENT":
|
||||
return w.paymentPhaseEndsAt
|
||||
? {
|
||||
label: "Payment due in",
|
||||
deadline: w.paymentPhaseEndsAt,
|
||||
expiredText: "Payment window closing…",
|
||||
}
|
||||
: null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
const state = bookingWindowUiState(w);
|
||||
const text = COUNTDOWN_TEXT[state.kind];
|
||||
if (!state.countdownTo || !text) return null;
|
||||
return { ...text, deadline: state.countdownTo };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,9 +128,21 @@ function isPast(w: MyBookingWindow): boolean {
|
||||
return w.windowPhase === "DONE" || w.windowPhase === "CLOSED_FOR_DAY";
|
||||
}
|
||||
|
||||
/** Badge label + Mantine color per UI state — same state the countdown uses. */
|
||||
const KIND_BADGE: Record<BookingWindowUiKind, { label: string; color: string }> = {
|
||||
OPEN: { label: "Open now", color: "edr-green" },
|
||||
FULL: { label: "Train full", color: "red" },
|
||||
PRE_WINDOW: { label: "Opens soon", color: "yellow" },
|
||||
DOC_REVIEW: { label: "Document review", color: "gray" },
|
||||
PAYMENT: { label: "Payment due", color: "gray" },
|
||||
CLOSED: { label: "Closed", color: "gray" },
|
||||
};
|
||||
|
||||
function WindowCard({ w }: { w: MyBookingWindow }) {
|
||||
const cd = phaseCountdown(w);
|
||||
const open = w.isOpenNow;
|
||||
const state = bookingWindowUiState(w);
|
||||
const badge = KIND_BADGE[state.kind];
|
||||
const open = state.isBookable;
|
||||
const isImport = w.direction === "IMPORT";
|
||||
|
||||
return (
|
||||
@@ -183,13 +175,11 @@ function WindowCard({ w }: { w: MyBookingWindow }) {
|
||||
)}
|
||||
<Badge
|
||||
variant={open ? "filled" : "light"}
|
||||
color={open ? "edr-green" : "gray"}
|
||||
color={badge.color}
|
||||
radius="sm"
|
||||
size="sm"
|
||||
>
|
||||
{open
|
||||
? "Open now"
|
||||
: windowPhaseLabel(w.windowPhase ?? w.bookingWindowStatus)}
|
||||
{badge.label}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user