mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 03:40:56 +00:00
Merge branch 'dev' into freight/nati-2
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { Hash, Package, Ship, Weight, Clock } from "lucide-react";
|
||||
import { Hash, Package, Ship, Weight, Clock, TrainFront } from "lucide-react";
|
||||
import { Group, Stack, Text, Divider } from "@mantine/core";
|
||||
|
||||
import { cargoTonsAndItems } from "@/utils/cargoWeight";
|
||||
@@ -50,6 +50,21 @@ export function BookingFactsCard({ booking }: BookingFactsCardProps) {
|
||||
},
|
||||
{ icon: Clock, label: "Last Updated", value: formatDate(booking.updatedAt) },
|
||||
];
|
||||
// Allocated train facts — only once the booking rides a schedule.
|
||||
if (booking.trainSchedule?.trainNumber || booking.trainSchedule?.reference) {
|
||||
facts.splice(1, 0, {
|
||||
icon: TrainFront,
|
||||
label: "Allocated Train",
|
||||
value: [
|
||||
booking.trainSchedule.trainNumber
|
||||
? `Train ${booking.trainSchedule.trainNumber}`
|
||||
: null,
|
||||
booking.trainSchedule.reference ?? null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · "),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionCard icon={Hash} title="Booking Details" accent="cyan">
|
||||
|
||||
@@ -144,4 +144,10 @@ export interface BookingDetailView {
|
||||
bookingContainers?: BookingContainerView[];
|
||||
reviewNotes?: BookingReviewNoteView[];
|
||||
files?: BookingFileView[];
|
||||
/** The allocated train, present once the booking is placed on a schedule. */
|
||||
trainSchedule?: {
|
||||
trainNumber: string | null;
|
||||
reference: string | null;
|
||||
scheduledDepartureDate: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Alert, Button, Group, Paper, Stack, Text } from "@mantine/core";
|
||||
import { DateInput } from "@mantine/dates";
|
||||
import { AlertTriangle, Send } from "lucide-react";
|
||||
import { AlertTriangle, Pencil, Send } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import toast from "react-hot-toast";
|
||||
@@ -16,6 +16,9 @@ export interface BookingChangesRequestedAlertProps {
|
||||
scheduledDate?: string | null;
|
||||
/** GL Ethiopia owns customs bookings, so only they get the resubmit control. */
|
||||
canResubmit: boolean;
|
||||
/** Completion-form route for editing the cargo before resubmitting —
|
||||
* rendered only for resubmit-capable users when provided. */
|
||||
editHref?: string;
|
||||
onResubmitted?: () => void;
|
||||
}
|
||||
|
||||
@@ -33,6 +36,7 @@ export function BookingChangesRequestedAlert({
|
||||
note,
|
||||
scheduledDate,
|
||||
canResubmit,
|
||||
editHref,
|
||||
onResubmitted,
|
||||
}: BookingChangesRequestedAlertProps) {
|
||||
const [day, setDay] = useState<Date | null>(
|
||||
@@ -122,6 +126,18 @@ export function BookingChangesRequestedAlert({
|
||||
>
|
||||
Resubmit to Operations
|
||||
</Button>
|
||||
{editHref ? (
|
||||
<Button
|
||||
component={Link}
|
||||
to={editHref}
|
||||
variant="default"
|
||||
radius="md"
|
||||
size="sm"
|
||||
leftSection={<Pencil size={15} />}
|
||||
>
|
||||
Edit cargo & resubmit
|
||||
</Button>
|
||||
) : null}
|
||||
</Group>
|
||||
) : null}
|
||||
</Stack>
|
||||
|
||||
@@ -225,7 +225,13 @@ export function ExportClearanceStepper({
|
||||
|
||||
<Stepper.Step
|
||||
label="Request transit assignee"
|
||||
description="Ask GL Djibouti to name the officer handling this shipment"
|
||||
// Description is the only part of a passed step that stays visible,
|
||||
// so it carries the assigned officer's name for GL Ethiopia.
|
||||
description={
|
||||
clearance.transitAssignee?.name
|
||||
? `Transit assignee: ${clearance.transitAssignee.name}`
|
||||
: "Ask GL Djibouti to name the officer handling this shipment"
|
||||
}
|
||||
icon={
|
||||
clearance.transitAssignee?.name ? (
|
||||
<CheckCircle2 size={14} />
|
||||
|
||||
@@ -961,7 +961,7 @@ export default function GlCreateBookingForm() {
|
||||
// modal falls back to the contract unit-rate estimate while it loads.
|
||||
const validateShipmentMutation = useMutation({
|
||||
mutationFn: (dto: Freight.CreateBookingUnderContractDto) =>
|
||||
contractsService.validateShipment(id ?? "", dto),
|
||||
contractsService.validateShipment(id ?? "", dto, completeBookingId),
|
||||
});
|
||||
const validation = validateShipmentMutation.data ?? null;
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ interface WindowRow {
|
||||
windowClosesAt: string | null;
|
||||
docReviewEndsAt: string | null;
|
||||
paymentPhaseEndsAt: string | null;
|
||||
/** End of the payment drain tail — pending payments may settle until then. */
|
||||
paymentDrainEndsAt?: string | null;
|
||||
bookingWindowStatus: string;
|
||||
bookingCycleNo: number;
|
||||
departureDate: string;
|
||||
@@ -94,16 +96,29 @@ const COUNTDOWN_TEXT: Partial<
|
||||
PRE_WINDOW: { label: "Opens in", expiredText: "Opening now…" },
|
||||
OPEN: { label: "Closes in", expiredText: "Review starting…" },
|
||||
DOC_REVIEW: { label: "Doc review ends in", expiredText: "Payment starting…" },
|
||||
PAYMENT: { label: "Payment ends in", expiredText: "Closing…" },
|
||||
PAYMENT: { label: "Payment ends in", expiredText: "Finalizing…" },
|
||||
};
|
||||
|
||||
function phaseCountdown(
|
||||
w: WindowRow,
|
||||
): { label: string; deadline: string; expiredText: string } | null {
|
||||
function phaseCountdown(w: WindowRow): {
|
||||
label: string;
|
||||
deadline: string;
|
||||
expiredText: string;
|
||||
graceDeadline?: string | null;
|
||||
graceLabel?: string;
|
||||
} | null {
|
||||
const state = bookingWindowUiState(w);
|
||||
const text = COUNTDOWN_TEXT[state.kind];
|
||||
if (!state.countdownTo || !text) return null;
|
||||
return { ...text, deadline: state.countdownTo };
|
||||
// Once the pay deadline lapses, pending payments still settle during the
|
||||
// drain tail — count it down as "processing" instead of a stale "closing".
|
||||
const grace =
|
||||
state.kind === "PAYMENT" && w.paymentDrainEndsAt
|
||||
? {
|
||||
graceDeadline: w.paymentDrainEndsAt,
|
||||
graceLabel: "Processing payments — closes in",
|
||||
}
|
||||
: undefined;
|
||||
return { ...text, deadline: state.countdownTo, ...grace };
|
||||
}
|
||||
|
||||
/** Badge label + Mantine color per UI state — same state the countdown uses. */
|
||||
@@ -225,6 +240,8 @@ function WindowCard({ w }: { w: WindowRow }) {
|
||||
deadline={cd.deadline}
|
||||
label={cd.label}
|
||||
expiredText={cd.expiredText}
|
||||
graceDeadline={cd.graceDeadline}
|
||||
graceLabel={cd.graceLabel}
|
||||
size="xs"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -357,7 +357,14 @@ export function PhasedClearanceActionPanel({
|
||||
|
||||
<Stepper.Step
|
||||
label="Request transit assignee"
|
||||
description="Ask GL Djibouti to name the officer handling this shipment"
|
||||
// Once the flow moves past this step its content collapses — the
|
||||
// description is the only slot that stays visible, so it carries
|
||||
// the assigned officer's name for GL Ethiopia.
|
||||
description={
|
||||
clearance.transitAssignee?.name
|
||||
? `Transit assignee: ${clearance.transitAssignee.name}`
|
||||
: "Ask GL Djibouti to name the officer handling this shipment"
|
||||
}
|
||||
icon={
|
||||
clearance.transitAssignee?.name ? (
|
||||
<CheckCircle2 size={14} />
|
||||
|
||||
Reference in New Issue
Block a user