mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 16:28:12 +00:00
implement intercity booking management and booking window websocket integration
This commit is contained in:
@@ -32,6 +32,7 @@ const DEFAULTS = {
|
||||
docReviewMinutes: 30,
|
||||
paymentWindowMinutes: 60,
|
||||
importWindowLeadDays: 3,
|
||||
exportBookingLeadHours: 24,
|
||||
};
|
||||
|
||||
/** 12-hour label for an EAT hour 0–23, e.g. 8 → "8:00 AM", 17 → "5:00 PM". */
|
||||
@@ -53,6 +54,7 @@ interface FormState {
|
||||
docReviewMinutes: number | "";
|
||||
paymentWindowMinutes: number | "";
|
||||
importWindowLeadDays: number | "";
|
||||
exportBookingLeadHours: number | "";
|
||||
}
|
||||
|
||||
function parseError(error: unknown, fallback: string): string {
|
||||
@@ -112,6 +114,8 @@ export default function BookingWindowSettingsModal({
|
||||
r?.paymentWindowMinutes ?? DEFAULTS.paymentWindowMinutes,
|
||||
importWindowLeadDays:
|
||||
r?.importWindowLeadDays ?? DEFAULTS.importWindowLeadDays,
|
||||
exportBookingLeadHours:
|
||||
r?.exportBookingLeadHours ?? DEFAULTS.exportBookingLeadHours,
|
||||
});
|
||||
}, [opened, schedule]);
|
||||
|
||||
@@ -142,15 +146,20 @@ export default function BookingWindowSettingsModal({
|
||||
const doc = Number(form.docReviewMinutes);
|
||||
const pay = Number(form.paymentWindowMinutes);
|
||||
const lead = Number(form.importWindowLeadDays);
|
||||
const exportLead = Number(form.exportBookingLeadHours);
|
||||
const leadInvalid = isExport
|
||||
? form.exportBookingLeadHours === "" ||
|
||||
!Number.isFinite(exportLead) ||
|
||||
exportLead < 1
|
||||
: form.importWindowLeadDays === "" || !Number.isFinite(lead);
|
||||
if (
|
||||
form.windowDurationHours === "" ||
|
||||
form.docReviewMinutes === "" ||
|
||||
form.paymentWindowMinutes === "" ||
|
||||
form.importWindowLeadDays === "" ||
|
||||
!Number.isFinite(duration) ||
|
||||
!Number.isFinite(doc) ||
|
||||
!Number.isFinite(pay) ||
|
||||
!Number.isFinite(lead)
|
||||
leadInvalid
|
||||
) {
|
||||
toast({
|
||||
title: "Fill every field before saving",
|
||||
@@ -164,7 +173,9 @@ export default function BookingWindowSettingsModal({
|
||||
windowDurationHours: duration,
|
||||
docReviewMinutes: doc,
|
||||
paymentWindowMinutes: pay,
|
||||
importWindowLeadDays: lead,
|
||||
...(isExport
|
||||
? { exportBookingLeadHours: exportLead }
|
||||
: { importWindowLeadDays: lead }),
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -223,8 +234,10 @@ export default function BookingWindowSettingsModal({
|
||||
<Stack gap="lg">
|
||||
{isExport ? (
|
||||
<Alert variant="light" color="blue" icon={<Info size={16} />}>
|
||||
Export schedules use a single FCFS lead window — the daily desk
|
||||
hours below don't apply, only the lead time does.
|
||||
Export schedules use a single first-come-first-served window: it
|
||||
opens the export lead time before departure — shifted to the next
|
||||
desk opening if that lands outside desk hours — and stays open
|
||||
until departure. Cycle timing below doesn't apply.
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
@@ -263,7 +276,6 @@ export default function BookingWindowSettingsModal({
|
||||
}
|
||||
allowDeselect={false}
|
||||
comboboxProps={{ withinPortal: true }}
|
||||
disabled={isExport}
|
||||
/>
|
||||
<Select
|
||||
label="Closes"
|
||||
@@ -275,7 +287,6 @@ export default function BookingWindowSettingsModal({
|
||||
}
|
||||
allowDeselect={false}
|
||||
comboboxProps={{ withinPortal: true }}
|
||||
disabled={isExport}
|
||||
/>
|
||||
</Group>
|
||||
{isOvernight && !is24h ? (
|
||||
@@ -290,7 +301,6 @@ export default function BookingWindowSettingsModal({
|
||||
color="grape"
|
||||
label="Run 24 hours a day (never pause overnight)"
|
||||
checked={is24h}
|
||||
disabled={isExport}
|
||||
onChange={(e) => {
|
||||
const checked = e.currentTarget.checked;
|
||||
setForm((f) => {
|
||||
@@ -304,12 +314,11 @@ export default function BookingWindowSettingsModal({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{!isExport ? (
|
||||
<Text size="xs" c="dimmed" mt={6}>
|
||||
A not-yet-full train pauses at the close hour and resumes the next
|
||||
morning at the open hour, every day until it fills or departs.
|
||||
</Text>
|
||||
) : null}
|
||||
<Text size="xs" c="dimmed" mt={6}>
|
||||
{isExport
|
||||
? "If the export lead time lands while the desk is shut, booking opens at the next desk opening instead."
|
||||
: "A not-yet-full train pauses at the close hour and resumes the next morning at the open hour, every day until it fills or departs."}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
@@ -367,27 +376,43 @@ export default function BookingWindowSettingsModal({
|
||||
<Divider />
|
||||
|
||||
{/* ── Lead time ────────────────────────────────────────────────── */}
|
||||
<NumberInput
|
||||
label={isExport ? "Booking lead (days)" : "Window lead (days)"}
|
||||
description={
|
||||
isExport
|
||||
? "How many days before departure export booking opens"
|
||||
: "How many days before departure the booking window starts"
|
||||
}
|
||||
value={form.importWindowLeadDays}
|
||||
onChange={(v) =>
|
||||
setForm(
|
||||
(f) =>
|
||||
f && {
|
||||
...f,
|
||||
importWindowLeadDays: v === "" ? "" : Number(v),
|
||||
},
|
||||
)
|
||||
}
|
||||
min={0}
|
||||
clampBehavior="none"
|
||||
allowDecimal={false}
|
||||
/>
|
||||
{isExport ? (
|
||||
<NumberInput
|
||||
label="Export booking lead (hours)"
|
||||
description="How many hours before departure the export booking window opens"
|
||||
value={form.exportBookingLeadHours}
|
||||
onChange={(v) =>
|
||||
setForm(
|
||||
(f) =>
|
||||
f && {
|
||||
...f,
|
||||
exportBookingLeadHours: v === "" ? "" : Number(v),
|
||||
},
|
||||
)
|
||||
}
|
||||
min={1}
|
||||
clampBehavior="none"
|
||||
allowDecimal={false}
|
||||
/>
|
||||
) : (
|
||||
<NumberInput
|
||||
label="Window lead (days)"
|
||||
description="How many days before departure the booking window starts"
|
||||
value={form.importWindowLeadDays}
|
||||
onChange={(v) =>
|
||||
setForm(
|
||||
(f) =>
|
||||
f && {
|
||||
...f,
|
||||
importWindowLeadDays: v === "" ? "" : Number(v),
|
||||
},
|
||||
)
|
||||
}
|
||||
min={0}
|
||||
clampBehavior="none"
|
||||
allowDecimal={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Group justify="flex-end" mt="xs">
|
||||
<Button variant="default" onClick={onClose} disabled={save.isPending}>
|
||||
|
||||
Reference in New Issue
Block a user