mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 14:50:57 +00:00
Enhance contract management by adding booking windows section and updating invoice logic for offloaded cargo
This commit is contained in:
@@ -646,7 +646,9 @@ function FinalInvoiceStep({
|
||||
const invoice = clearance.finalInvoice ?? null;
|
||||
const paid = invoice?.status === "PAID";
|
||||
|
||||
if (!clearance.offloaded && !invoice) {
|
||||
// Export: OFFLOADED is a DJ doc milestone that may never be recorded, so the
|
||||
// secured gate pass is enough to open invoicing. Sending an invoice is optional.
|
||||
if (!clearance.offloaded && !clearance.gatepassGranted && !invoice) {
|
||||
return (
|
||||
<StepStatus
|
||||
done={false}
|
||||
@@ -740,7 +742,7 @@ function FinalInvoiceStep({
|
||||
) : canDjAct && bookingId ? (
|
||||
<>
|
||||
<Text size="sm" c="dimmed">
|
||||
Cargo offloaded — send the final invoice to the customer.
|
||||
Send the final invoice to the customer if post-arrival charges apply (optional).
|
||||
</Text>
|
||||
<Button
|
||||
color="edr-green"
|
||||
|
||||
@@ -21,7 +21,28 @@ import { CountdownTimer } from "@edr/ui-common";
|
||||
|
||||
import { useBookingWindowSocket } from "@/features/bookingWindows/useBookingWindowSocket";
|
||||
import { api } from "@/services/api";
|
||||
import type { StaffBookingWindow } from "@/types/trainScheduling";
|
||||
|
||||
/**
|
||||
* The fields a window card needs. Structural so both `StaffBookingWindow`
|
||||
* (all-lanes staff feed) and `BookingWindow` (contract-scoped feed, which
|
||||
* carries no train number) satisfy it.
|
||||
*/
|
||||
interface WindowRow {
|
||||
scheduleId: string;
|
||||
trainNumber?: string | null;
|
||||
direction: string | null;
|
||||
windowPhase: string | null;
|
||||
isOpenNow: boolean;
|
||||
windowOpensAt: string | null;
|
||||
windowClosesAt: string | null;
|
||||
docReviewEndsAt: string | null;
|
||||
paymentPhaseEndsAt: string | null;
|
||||
bookingWindowStatus: string;
|
||||
bookingCycleNo: number;
|
||||
departureDate: string;
|
||||
origin: string | null;
|
||||
destination: string | null;
|
||||
}
|
||||
|
||||
/** All window times are communicated in East Africa Time. */
|
||||
const TZ = "Africa/Addis_Ababa";
|
||||
@@ -46,7 +67,7 @@ function fmtTime(iso: string): string {
|
||||
});
|
||||
}
|
||||
|
||||
function windowLabel(w: StaffBookingWindow): string {
|
||||
function windowLabel(w: WindowRow): string {
|
||||
if (w.windowOpensAt && w.windowClosesAt) {
|
||||
return `${fmtDay(w.windowOpensAt)} · ${fmtTime(w.windowOpensAt)} – ${fmtTime(
|
||||
w.windowClosesAt,
|
||||
@@ -64,7 +85,7 @@ function windowLabel(w: StaffBookingWindow): string {
|
||||
* between refetches announces what comes next rather than the bare "Expired".
|
||||
*/
|
||||
function phaseCountdown(
|
||||
w: StaffBookingWindow,
|
||||
w: WindowRow,
|
||||
): { label: string; deadline: string; expiredText: string } | null {
|
||||
switch (w.windowPhase) {
|
||||
case "PRE_WINDOW":
|
||||
@@ -105,7 +126,7 @@ function phaseCountdown(
|
||||
}
|
||||
|
||||
/** Drop windows whose booking window (or the train itself) has already passed. */
|
||||
function isPast(w: StaffBookingWindow): boolean {
|
||||
function isPast(w: WindowRow): boolean {
|
||||
const now = Date.now();
|
||||
const closes = w.windowClosesAt ? new Date(w.windowClosesAt).getTime() : null;
|
||||
const departs = w.departureDate ? new Date(w.departureDate).getTime() : null;
|
||||
@@ -116,7 +137,7 @@ function isPast(w: StaffBookingWindow): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function WindowCard({ w }: { w: StaffBookingWindow }) {
|
||||
function WindowCard({ w }: { w: WindowRow }) {
|
||||
const cd = phaseCountdown(w);
|
||||
const open = w.isOpenNow;
|
||||
const isImport = w.direction === "IMPORT";
|
||||
@@ -218,21 +239,45 @@ function WindowCard({ w }: { w: StaffBookingWindow }) {
|
||||
);
|
||||
}
|
||||
|
||||
interface GlUpcomingWindowsSectionProps {
|
||||
/**
|
||||
* Scope the card to one contract: only windows on that contract's routes
|
||||
* (and therefore its import/export direction) are shown. Omit for the
|
||||
* all-lanes staff feed on the clearance queue.
|
||||
*/
|
||||
contractId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* All announced booking windows (import cycles + export FCFS) across every lane,
|
||||
* shown to GL ET on the clearance queue as a paged carousel — three lanes per
|
||||
* page, arrows to flip. Mirrors the customer's portal "Booking Windows" card.
|
||||
* Hidden when nothing is pending.
|
||||
* Announced booking windows (import cycles + export FCFS) as a paged carousel —
|
||||
* three lanes per page, arrows to flip. Without `contractId` it shows every
|
||||
* lane (GL ET clearance queue); with `contractId` it shows only the windows
|
||||
* matching that contract's routes/direction (clearance detail page). Mirrors
|
||||
* the customer's portal "Booking Windows" card. Hidden when nothing is pending.
|
||||
*/
|
||||
export function GlUpcomingWindowsSection() {
|
||||
export function GlUpcomingWindowsSection({
|
||||
contractId,
|
||||
}: GlUpcomingWindowsSectionProps = {}) {
|
||||
// Live pushes flip cards the moment the window engine transitions a phase;
|
||||
// the 60s poll below stays only as a fallback.
|
||||
useBookingWindowSocket();
|
||||
const { data, isLoading } = useQuery(
|
||||
api.trainScheduling.allBookingWindows.queryOptions({
|
||||
const allLanes = useQuery({
|
||||
...api.trainScheduling.allBookingWindows.queryOptions({
|
||||
refetchInterval: 60_000,
|
||||
}),
|
||||
);
|
||||
enabled: !contractId,
|
||||
});
|
||||
const contractLanes = useQuery({
|
||||
...api.trainScheduling.contractBookingWindows.queryOptions({
|
||||
input: { contractId: contractId ?? "" },
|
||||
refetchInterval: 60_000,
|
||||
}),
|
||||
enabled: Boolean(contractId),
|
||||
});
|
||||
const data: WindowRow[] | undefined = contractId
|
||||
? contractLanes.data
|
||||
: allLanes.data;
|
||||
const isLoading = contractId ? contractLanes.isLoading : allLanes.isLoading;
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
const windows = useMemo(() => {
|
||||
@@ -270,7 +315,9 @@ export function GlUpcomingWindowsSection() {
|
||||
Booking windows
|
||||
</Text>
|
||||
<Text fz={13} c="dimmed">
|
||||
Import and export booking windows across all lanes (EAT)
|
||||
{contractId
|
||||
? "Booking windows on this contract's routes (EAT)"
|
||||
: "Import and export booking windows across all lanes (EAT)"}
|
||||
</Text>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
Reference in New Issue
Block a user