mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
Add contract booking windows feature
This commit is contained in:
@@ -58,6 +58,25 @@ import {
|
||||
StepLabel,
|
||||
} from "./gl-booking-form/form-ui";
|
||||
|
||||
/** All booking-window times are communicated in East Africa Time. */
|
||||
const EAT_TZ = "Africa/Addis_Ababa";
|
||||
|
||||
function fmtWindowOpensAt(iso: string): string {
|
||||
const date = new Date(iso).toLocaleDateString("en-GB", {
|
||||
weekday: "short",
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
timeZone: EAT_TZ,
|
||||
});
|
||||
const time = new Date(iso).toLocaleTimeString("en-GB", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
timeZone: EAT_TZ,
|
||||
});
|
||||
return `${date} · ${time}`;
|
||||
}
|
||||
|
||||
interface UnitDraft {
|
||||
containerNumber: string;
|
||||
sealNumber: string;
|
||||
@@ -106,6 +125,33 @@ export default function GlCreateBookingForm() {
|
||||
enabled: Boolean(requestId),
|
||||
});
|
||||
|
||||
// Same window-gating the customer sees: GL may only create a booking while a
|
||||
// booking window is OPEN for one of the contract's routes.
|
||||
const contractId = contract?.id ?? id;
|
||||
const { data: bookingWindows, isLoading: windowsLoading } = useQuery({
|
||||
...api.trainScheduling.contractBookingWindows.queryOptions({
|
||||
input: { contractId: contractId ?? "" },
|
||||
}),
|
||||
enabled: Boolean(contractId),
|
||||
});
|
||||
|
||||
const windowOpen = useMemo(
|
||||
() => (bookingWindows ?? []).some((w) => w.isOpenNow),
|
||||
[bookingWindows],
|
||||
);
|
||||
|
||||
// Soonest future window across all routes, used for the "next window" notice.
|
||||
const nextWindow = useMemo(() => {
|
||||
const now = Date.now();
|
||||
return (bookingWindows ?? [])
|
||||
.filter((w) => w.windowOpensAt && new Date(w.windowOpensAt).getTime() > now)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(a.windowOpensAt!).getTime() -
|
||||
new Date(b.windowOpensAt!).getTime(),
|
||||
)[0];
|
||||
}, [bookingWindows]);
|
||||
|
||||
const [scheduledDate, setScheduledDate] = useState("");
|
||||
const [contractRouteId, setContractRouteId] = useState<string | null>(null);
|
||||
const [notes, setNotes] = useState("");
|
||||
@@ -314,12 +360,13 @@ export default function GlCreateBookingForm() {
|
||||
);
|
||||
|
||||
const canSubmit =
|
||||
windowOpen &&
|
||||
Boolean(scheduledDate) &&
|
||||
(!needsRouteSelect || Boolean(contractRouteId)) &&
|
||||
(isContainer ? containerLines.some((l) => l.units.length > 0) : bulkLines.length > 0);
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!scheduledDate || !contract) return;
|
||||
if (!scheduledDate || !contract || !windowOpen) return;
|
||||
|
||||
const payload: Freight.CreateBookingUnderContractDto = {
|
||||
scheduledDate,
|
||||
@@ -451,6 +498,33 @@ export default function GlCreateBookingForm() {
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{!windowsLoading && !windowOpen ? (
|
||||
<Alert
|
||||
color="orange"
|
||||
variant="light"
|
||||
radius="md"
|
||||
icon={<AlertCircle size={16} />}
|
||||
title="Booking window is closed"
|
||||
mb="lg"
|
||||
>
|
||||
GL can create a booking only while a window is open.{" "}
|
||||
{nextWindow?.windowOpensAt ? (
|
||||
<>
|
||||
Next window: <b>{fmtWindowOpensAt(nextWindow.windowOpensAt)} EAT</b>{" "}
|
||||
for{" "}
|
||||
<b>
|
||||
{nextWindow.origin ?? "Origin"} → {nextWindow.destination ?? "Destination"}
|
||||
</b>
|
||||
.
|
||||
</>
|
||||
) : (
|
||||
<>No upcoming booking window scheduled.</>
|
||||
)}
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
{windowsLoading || windowOpen ? (
|
||||
<>
|
||||
<Stack gap="lg" maw={896} mx="auto">
|
||||
<StepCard>
|
||||
<StepHeader
|
||||
@@ -846,6 +920,8 @@ export default function GlCreateBookingForm() {
|
||||
</Stack>
|
||||
) : null}
|
||||
</Modal>
|
||||
</>
|
||||
) : null}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user