diff --git a/apps/edr-freight-api/src/modules/contracts/contracts.service.ts b/apps/edr-freight-api/src/modules/contracts/contracts.service.ts
index 4ef525ff7..24aa2b597 100644
--- a/apps/edr-freight-api/src/modules/contracts/contracts.service.ts
+++ b/apps/edr-freight-api/src/modules/contracts/contracts.service.ts
@@ -10,7 +10,7 @@ import { DataSource } from 'typeorm';
import { insertWithGeneratedReference } from '@edr/api-common';
import { YardCountry } from '@edr/types';
-
+//
import { deriveTradeDirection } from '../../common/derive-trade-direction.util';
import { CompaniesService } from '../companies/companies.service';
import { ServiceType } from '../rule-engine/entities/service-type.entity';
diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/ContractActionsToolbar.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/ContractActionsToolbar.tsx
index 8c4833550..d2a245f24 100644
--- a/apps/edr-freight-web/backoffice/src/components/contracts/ContractActionsToolbar.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/contracts/ContractActionsToolbar.tsx
@@ -5,7 +5,7 @@ import { Button, Group, Modal, Stack, Text, Textarea } from "@mantine/core";
import {
Check,
Eye,
- FilePen,
+ // FilePen, // ponytail: back with the "Edit contract articles" button
FileSignature,
MessageSquareWarning,
PauseCircle,
@@ -286,9 +286,9 @@ export function ContractActionsToolbar({
<>
{canEditDocument
- ? "It is your turn to approve. You can edit the articles before approving — the PDF is generated automatically once the last approver approves."
+ ? "It is your turn to approve — the PDF is generated automatically once the last approver approves."
: draft?.nextApproverRole
- ? `Awaiting ${draft.nextApproverRole}. Only the current approver can edit the document.`
+ ? `Awaiting ${draft.nextApproverRole}.`
: "Awaiting approval."}
Preview document
+ {/* Article editing is hidden for now (frontend only) — the approval
+ chain approves the document as accepted. Uncomment to restore.
+
{canEditDocument && (
)}
+
+ */}
>
)}
diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/ContractDocumentEditorModal.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/ContractDocumentEditorModal.tsx
index a53ed85ba..b944b5d12 100644
--- a/apps/edr-freight-web/backoffice/src/components/contracts/ContractDocumentEditorModal.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/contracts/ContractDocumentEditorModal.tsx
@@ -14,18 +14,18 @@ import {
Text,
Textarea,
TextInput,
- Tooltip,
+ // Tooltip, // ponytail: back with the article editor block
} from "@mantine/core";
import {
- ArrowDown,
- ArrowUp,
+ // ArrowDown, // ponytail: back with the article editor block
+ // ArrowUp,
FileText,
Info,
Lock,
Plus,
Trash2,
} from "lucide-react";
-import { DateInput } from "@mantine/dates";
+import { DateTimePicker } from "@mantine/dates";
import type { Freight } from "@edr/types";
import { contractsService } from "@/services/contracts.service";
@@ -44,6 +44,23 @@ function startOfToday(): Date {
return d;
}
+/** Local `YYYY-MM-DD` — the shape Mantine hands day cells. */
+function localDay(date: Date): string {
+ const pad = (n: number) => String(n).padStart(2, "0");
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
+}
+
+/**
+ * Print today in bold inside the calendar. `highlightToday` only rings the cell,
+ * which staff read as "disabled" on a picker whose minimum IS today — the weight
+ * makes it obvious the day is pickable.
+ */
+function boldToday(date: string) {
+ return date === localDay(new Date())
+ ? { style: { fontWeight: 800 } }
+ : {};
+}
+
interface EditableArticle {
id: string;
title: string;
@@ -125,11 +142,14 @@ export function ContractDocumentEditorModal({
);
}, [opened, draft]);
- // Accept mode opens on today — a contract never starts in the past, and the
- // pickers below refuse earlier days.
+ // Accept mode opens on NOW — a contract never starts in the past, and the
+ // pickers below refuse earlier days. Seconds are dropped so the value matches
+ // what the HH:mm picker shows.
useEffect(() => {
if (!opened || mode !== "accept") return;
- setValidityStart(startOfToday());
+ const now = new Date();
+ now.setSeconds(0, 0);
+ setValidityStart(now);
setValidityEnd(null);
}, [opened, mode]);
@@ -144,29 +164,30 @@ export function ContractDocumentEditorModal({
// decides per-caller — the client cannot derive this from the contract alone.
const locked = mode === "edit" && !draft?.editableByMe;
- const moveArticle = (index: number, delta: number) => {
- setArticles((prev) => {
- const next = [...prev];
- const target = index + delta;
- if (target < 0 || target >= next.length) return prev;
- [next[index], next[target]] = [next[target], next[index]];
- return next;
- });
- };
+ // Article edit handlers — parked with the editor block below.
+ // const moveArticle = (index: number, delta: number) => {
+ // setArticles((prev) => {
+ // const next = [...prev];
+ // const target = index + delta;
+ // if (target < 0 || target >= next.length) return prev;
+ // [next[index], next[target]] = [next[target], next[index]];
+ // return next;
+ // });
+ // };
- const updateArticle = (id: string, patch: Partial) =>
- setArticles((prev) =>
- prev.map((a) => (a.id === id ? { ...a, ...patch } : a)),
- );
+ // const updateArticle = (id: string, patch: Partial) =>
+ // setArticles((prev) =>
+ // prev.map((a) => (a.id === id ? { ...a, ...patch } : a)),
+ // );
- const removeArticle = (id: string) =>
- setArticles((prev) => prev.filter((a) => a.id !== id));
+ // const removeArticle = (id: string) =>
+ // setArticles((prev) => prev.filter((a) => a.id !== id));
- const addArticle = () =>
- setArticles((prev) => [
- ...prev,
- { id: newArticleId(), title: "", body: "" },
- ]);
+ // const addArticle = () =>
+ // setArticles((prev) => [
+ // ...prev,
+ // { id: newArticleId(), title: "", body: "" },
+ // ]);
const buildSnapshot = (): Freight.IContractDocumentSnapshot => ({
code: draft?.code ?? null,
@@ -253,7 +274,7 @@ export function ContractDocumentEditorModal({
? draft?.nextApproverRole
? `Only the current approver (${draft.nextApproverRole}) can edit this document right now.`
: "This document can no longer be edited — the contract has advanced beyond approval."
- : "Edits apply to THIS contract only. The six shared templates are never changed."}
+ : "Articles come from this contract's template. Set the validity dates, then accept."}
+ {/* Article editing is hidden for now (frontend only) — staff accept the
+ contract on the template's articles as-is. The articles themselves
+ still ride along in buildSnapshot(), so the generated document is
+ unchanged. Uncomment this block to bring the editor back.
+
@@ -379,7 +405,7 @@ export function ContractDocumentEditorModal({
}
/>
+ */}
+
{mode === "accept" && (
@@ -429,21 +457,29 @@ export function ContractDocumentEditorModal({
)} */}
- setValidityStart(v ? new Date(v) : null)}
+ // Today is the earliest start — and it is ringed in the
+ // calendar so it reads as selectable rather than blocked.
minDate={startOfToday()}
maxDate={validityEnd ?? undefined}
+ highlightToday
+ getDayProps={boldToday}
+ valueFormat="DD MMM YYYY HH:mm"
clearable
/>
- setValidityEnd(v ? new Date(v) : null)}
minDate={validityStart ?? startOfToday()}
+ highlightToday
+ getDayProps={boldToday}
+ valueFormat="DD MMM YYYY HH:mm"
clearable
/>
diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/detail/RequestDetailCards.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/detail/RequestDetailCards.tsx
index 595252cbf..d8c5f819a 100644
--- a/apps/edr-freight-web/backoffice/src/components/contracts/detail/RequestDetailCards.tsx
+++ b/apps/edr-freight-web/backoffice/src/components/contracts/detail/RequestDetailCards.tsx
@@ -95,12 +95,16 @@ export function RequestCustomerCard({ contract }: { contract?: ReqContract | nul
);
}
+// Validity is accepted to the minute, so the expiry reads with its time — a
+// contract that lapses at 09:00 looks identical to one lapsing at 23:59 without it.
const fmtDate = (iso?: string | null) =>
iso
? new Intl.DateTimeFormat("en-GB", {
day: "2-digit",
month: "short",
year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
}).format(new Date(iso))
: "—";
diff --git a/apps/edr-freight-web/backoffice/src/pages/contracts/ContractRequestDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/contracts/ContractRequestDetailPage.tsx
index 4f1d2cb42..7eedd36be 100644
--- a/apps/edr-freight-web/backoffice/src/pages/contracts/ContractRequestDetailPage.tsx
+++ b/apps/edr-freight-web/backoffice/src/pages/contracts/ContractRequestDetailPage.tsx
@@ -110,6 +110,21 @@ function formatDate(value: string | null | undefined): string {
});
}
+/** Same, plus the clock — for values the staff pick to the minute. */
+function formatDateTime(value: string | null | undefined): string {
+ if (!value) return "—";
+ const d = new Date(value);
+ return Number.isNaN(d.getTime())
+ ? "—"
+ : d.toLocaleString(undefined, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ });
+}
+
export default function ContractRequestDetailPage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
@@ -370,7 +385,8 @@ export default function ContractRequestDetailPage() {
{contract.contractValidUntil ? (
) : null}
diff --git a/apps/edr-freight-web/portal/src/components/customer-actions/ContractCustomerAction.tsx b/apps/edr-freight-web/portal/src/components/customer-actions/ContractCustomerAction.tsx
index 81119dc35..4555a5db8 100644
--- a/apps/edr-freight-web/portal/src/components/customer-actions/ContractCustomerAction.tsx
+++ b/apps/edr-freight-web/portal/src/components/customer-actions/ContractCustomerAction.tsx
@@ -16,6 +16,7 @@ import type { Freight } from "@edr/types";
import { PayNowButton } from "@/pages/bookings/payments/PayNowButton";
import { api } from "@/services/api";
+import { bookingDocNoun } from "@/pages/bookings/clearance/bookingNextAction";
import { deriveContractCustomerAction } from "./deriveContractCustomerAction";
interface ContractCustomerActionProps {
@@ -131,7 +132,7 @@ export function InitiateBookingButton({
queryKey: api.contracts.get.queryKey({ id: contract.id }),
});
toast.success(
- "Booking initiated — upload your clearance documents to start the review.",
+ `Booking initiated — upload your ${bookingDocNoun(contract)} to start the review.`,
);
setConfirmOpen(false);
navigate(`/bookings/${booking.id}`);
@@ -167,7 +168,7 @@ export function InitiateBookingButton({
{contract.reference}
- . You'll upload the clearance documents next, and the shipment
+ . You'll upload the {bookingDocNoun(contract)} next, and the shipment
quantity is drawn down from your contract's reserved capacity.
diff --git a/apps/edr-freight-web/portal/src/pages/MyPortalPage/constants.ts b/apps/edr-freight-web/portal/src/pages/MyPortalPage/constants.ts
index 5a5a80ed9..7515befb9 100644
--- a/apps/edr-freight-web/portal/src/pages/MyPortalPage/constants.ts
+++ b/apps/edr-freight-web/portal/src/pages/MyPortalPage/constants.ts
@@ -170,7 +170,9 @@ export const STATUS_CONFIG: Record = {
icon: FileUp,
iconColor: "edr-amber-text",
tile: "edr-amber-soft",
- hint: "Clearance documents needed",
+ // Direction-neutral: a self-clearance shipment collects the customer's own
+ // import/export papers, a customs one collects the clearance set.
+ hint: "Documents needed",
step: "edr-accent",
badgeLabel: "Upload documents",
badgeBg: "edr-amber-soft",
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/ClearanceCard.tsx b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/ClearanceCard.tsx
index a8cc43e14..e5ea8d3a9 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/ClearanceCard.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/ClearanceCard.tsx
@@ -12,7 +12,11 @@ import { useNavigate } from "react-router-dom";
import type { Freight } from "@edr/types";
import { BookingActionModal } from "@/pages/bookings/clearance/BookingActionModal";
-import { getBookingNextAction } from "@/pages/bookings/clearance/bookingNextAction";
+import {
+ bookingDocNoun,
+ bookingDocNounCapitalized,
+ getBookingNextAction,
+} from "@/pages/bookings/clearance/bookingNextAction";
import { CardTitle, SectionCard } from "./layout";
@@ -28,6 +32,9 @@ export function ClearanceCard({ booking }: { booking: Freight.IBooking }) {
const navigate = useNavigate();
const status = booking.status as string;
const action = getBookingNextAction(booking);
+ // Self-clearance services collect the customer's own import/export paperwork,
+ // so the copy is named after the direction rather than "clearance".
+ const docNoun = bookingDocNoun(booking);
// BOOK: clearance finished on a bare instance — go straight to the booking
// form (cargo + shipment day + window check) instead of opening the modal.
const isBookAction = action?.kind === "BOOK" && Boolean(action.to);
@@ -46,9 +53,15 @@ export function ClearanceCard({ booking }: { booking: Freight.IBooking }) {
const summary =
status === "CLEARANCE_READY" ? (
}>
- {isBookAction
- ? "Clearance is complete. Book your shipment — enter the cargo details and pick a shipment day inside an open booking window."
- : "Clearance is complete. Pick a shipment day and proceed to operation."}
+ {`${
+ booking.customsClearingEnabled
+ ? "Clearance is complete."
+ : "Your documents are approved."
+ } ${
+ isBookAction
+ ? "Book your shipment — enter the cargo details and pick a shipment day inside an open booking window."
+ : "Pick a shipment day and proceed to operation."
+ }`}
) : status === "DOCUMENTS_UNDER_REVIEW" ? (
}>
@@ -57,8 +70,7 @@ export function ClearanceCard({ booking }: { booking: Freight.IBooking }) {
) : (
}>
- Upload the required clearance documents so your shipment can be
- reviewed.
+ Upload the required {docNoun} so your shipment can be reviewed.
);
@@ -67,7 +79,7 @@ export function ClearanceCard({ booking }: { booking: Freight.IBooking }) {
{/* The "Clearance progress" stepper moved into the unified journey
wizard at the top of the page — this card keeps only the actions. */}
- Clearance documents
+ {bookingDocNounCapitalized(booking)}
{action && (
{isBookAction
? "Use “Book” to enter the cargo details and schedule your shipment."
- : `Use “${action?.label ?? "the action button"}” to manage your clearance documents.`}
+ : `Use “${action?.label ?? "the action button"}” to manage your ${docNoun}.`}
{!isBookAction && (
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/DocumentsTab.tsx b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/DocumentsTab.tsx
index e43bb43b9..fb328f4b7 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/DocumentsTab.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/DocumentsTab.tsx
@@ -18,7 +18,11 @@ import { fetchViewableFile, downloadStoredFile } from "@/services/files.service"
import { warehouseService } from "@/services/warehouse.service";
import { useFileViewer } from "@/hooks/useFileViewer";
import { BookingActionModal } from "@/pages/bookings/clearance/BookingActionModal";
-import { getBookingNextAction } from "@/pages/bookings/clearance/bookingNextAction";
+import {
+ bookingDocNoun,
+ bookingDocNounCapitalized,
+ getBookingNextAction,
+} from "@/pages/bookings/clearance/bookingNextAction";
import { ClearanceUploadedDocumentsPanel } from "@/components/contracts/ClearanceUploadedDocumentsPanel";
import toast from "react-hot-toast";
@@ -322,7 +326,7 @@ export function DocumentsTab({ booking }: { booking: Freight.IBooking }) {
{hasContract && clearance && (
- Clearance documents
+ {bookingDocNounCapitalized(booking)}
{showManage && (
) : (
}>
- No clearance documents are required for this booking.
+ No {bookingDocNoun(booking)} are required for this booking.
)}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/StatusHero.tsx b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/StatusHero.tsx
index c702329d1..de6d2c755 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/StatusHero.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/BookingDetailPage/components/StatusHero.tsx
@@ -5,6 +5,10 @@ import { MoveRight } from "lucide-react";
import type { Freight } from "@edr/types";
import { contractsService } from "@/services/contracts.service";
+import {
+ bookingDocNoun,
+ bookingDocNounCapitalized,
+} from "@/pages/bookings/clearance/bookingNextAction";
import {
ARRIVAL_STAGE,
@@ -110,12 +114,29 @@ export function StatusHero({
}
: isInitiatedInstance
? {
- title: "Upload clearance documents",
- description:
- "Upload the required clearance documents to start the review. Once the review is finalized you can book your shipment.",
+ title: `Upload ${bookingDocNoun(booking)}`,
+ description: `Upload the required ${bookingDocNoun(
+ booking,
+ )} to start the review. Once the review is finalized you can book your shipment.`,
stage,
}
- : isContractDrawdown && status === "FULLY_EXECUTED"
+ : status === "AWAITING_DOCUMENTS"
+ ? {
+ title: `Upload ${bookingDocNoun(booking)}`,
+ description: `Upload the required ${bookingDocNoun(
+ booking,
+ )} so your shipment can be reviewed.`,
+ stage,
+ }
+ : status === "DOCUMENTS_UNDER_REVIEW"
+ ? {
+ title: `${bookingDocNounCapitalized(booking)} under review`,
+ description: `Your ${bookingDocNoun(
+ booking,
+ )} are being reviewed. Re-upload any queried documents to proceed.`,
+ stage,
+ }
+ : isContractDrawdown && status === "FULLY_EXECUTED"
? {
title: "Accepted by operations",
description:
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/clearance/BookingActionModal.tsx b/apps/edr-freight-web/portal/src/pages/bookings/clearance/BookingActionModal.tsx
index 8f93e040c..c8b324d47 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/clearance/BookingActionModal.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/clearance/BookingActionModal.tsx
@@ -5,7 +5,10 @@ import { useNavigate } from "react-router-dom";
import type { Freight } from "@edr/types";
import { ClearanceFlow } from "./ClearanceFlow";
-import { getBookingNextAction } from "./bookingNextAction";
+import {
+ bookingDocNounCapitalized,
+ getBookingNextAction,
+} from "./bookingNextAction";
import { useClearanceFlow } from "./useClearanceFlow";
interface BookingActionModalProps {
@@ -59,7 +62,7 @@ function BookingActionModalBody({
title={
- {action?.title ?? "Clearance documents"}
+ {action?.title ?? bookingDocNounCapitalized(booking)}
{reference}
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/clearance/ClearanceFlow.tsx b/apps/edr-freight-web/portal/src/pages/bookings/clearance/ClearanceFlow.tsx
index 1997c99d4..196646185 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/clearance/ClearanceFlow.tsx
+++ b/apps/edr-freight-web/portal/src/pages/bookings/clearance/ClearanceFlow.tsx
@@ -24,6 +24,7 @@ import {
import { ClearanceDocumentUploadCard } from "@/components/contracts/ClearanceDocumentUploadCard";
import { fetchViewableFile, downloadStoredFile } from "@/services/files.service";
import { useFileViewer } from "@/hooks/useFileViewer";
+import { bookingDocNoun } from "./bookingNextAction";
import { OperationDatePicker } from "./OperationDatePicker";
import { DayAvailabilityHint } from "./DayAvailabilityHint";
import type { ClearanceFlowController } from "./useClearanceFlow";
@@ -87,7 +88,7 @@ export function ClearanceFlow({ booking, flow, footer }: ClearanceFlowProps) {
? "Customs clearance is complete. Global Logistics is completing your booking (cargo details and shipment day) — you will be notified when payment is due."
: clearance.includesCustoms
? "Customs clearance is complete and your cleared documents are available below. You can now proceed to operation."
- : "Clearance is ready. You can now proceed to operation."}
+ : "Your documents are approved. You can now proceed to operation."}
) : status === "DOCUMENTS_UNDER_REVIEW" ? (
} mb="md">
@@ -99,7 +100,7 @@ export function ClearanceFlow({ booking, flow, footer }: ClearanceFlowProps) {
} mb="md">
{clearance.includesCustoms
? "Upload every required document customs needs (marked *) to start the review. Global Logistics will clear your shipment and return the cleared documents here."
- : "Upload every required clearance document (marked *) below to start the review."}
+ : `Upload every required ${bookingDocNoun(booking, { singular: true })} (marked *) below to start the review.`}
)}
@@ -115,7 +116,7 @@ export function ClearanceFlow({ booking, flow, footer }: ClearanceFlowProps) {
- Your clearance documents
+ Your {bookingDocNoun(booking)}
Upload each required document below. Items marked * are mandatory.
diff --git a/apps/edr-freight-web/portal/src/pages/bookings/clearance/bookingNextAction.ts b/apps/edr-freight-web/portal/src/pages/bookings/clearance/bookingNextAction.ts
index 1ebb047a6..d0256510a 100644
--- a/apps/edr-freight-web/portal/src/pages/bookings/clearance/bookingNextAction.ts
+++ b/apps/edr-freight-web/portal/src/pages/bookings/clearance/bookingNextAction.ts
@@ -22,29 +22,78 @@ export interface BookingNextAction {
to?: string;
}
-const ACTION_BY_STATUS: Record = {
- AWAITING_DOCUMENTS: {
- kind: "UPLOAD_DOCUMENTS",
- label: "Upload documents",
- title: "Upload clearance documents",
- },
- DOCUMENTS_UNDER_REVIEW: {
- kind: "FIX_DOCUMENTS",
- label: "Review documents",
- title: "Clearance documents",
- },
- CLEARANCE_READY: {
- kind: "SCHEDULE_OPERATION",
- label: "Schedule & proceed",
- title: "Schedule your shipment",
- },
-};
-
type ActionBooking = Pick<
Freight.IBooking,
- "id" | "status" | "contractId" | "totalAmount" | "customsClearingEnabled"
+ | "id"
+ | "status"
+ | "contractId"
+ | "totalAmount"
+ | "customsClearingEnabled"
+ | "tradeDirection"
>;
+/** Anything carrying a service + a lane: a booking or the contract behind it. */
+type DocNounSource = {
+ customsClearingEnabled?: boolean | null;
+ tradeDirection?: string | null;
+};
+
+/**
+ * What this booking's document set is CALLED to the customer.
+ *
+ * Only a customs (Path B) service collects EDR's clearance set. On a
+ * self-clearance service the customer clears the cargo himself and simply hands
+ * over his own import or export paperwork — calling that "clearance documents"
+ * reads as if EDR were clearing for him, so it is named after the direction.
+ */
+export function bookingDocNoun(
+ booking: DocNounSource,
+ { singular = false }: { singular?: boolean } = {},
+): string {
+ const kind = booking.customsClearingEnabled
+ ? "clearance"
+ : booking.tradeDirection === "IMPORT"
+ ? "import"
+ : booking.tradeDirection === "EXPORT"
+ ? "export"
+ : "intercity";
+ return `${kind} ${singular ? "document" : "documents"}`;
+}
+
+/** {@link bookingDocNoun} for the start of a sentence or a card title. */
+export function bookingDocNounCapitalized(
+ booking: Pick,
+): string {
+ const noun = bookingDocNoun(booking);
+ return noun.charAt(0).toUpperCase() + noun.slice(1);
+}
+
+function actionByStatus(booking: ActionBooking): BookingNextAction | null {
+ const noun = bookingDocNoun(booking);
+ switch (booking.status) {
+ case "AWAITING_DOCUMENTS":
+ return {
+ kind: "UPLOAD_DOCUMENTS",
+ label: "Upload documents",
+ title: `Upload ${noun}`,
+ };
+ case "DOCUMENTS_UNDER_REVIEW":
+ return {
+ kind: "FIX_DOCUMENTS",
+ label: "Review documents",
+ title: bookingDocNounCapitalized(booking),
+ };
+ case "CLEARANCE_READY":
+ return {
+ kind: "SCHEDULE_OPERATION",
+ label: "Schedule & proceed",
+ title: "Schedule your shipment",
+ };
+ default:
+ return null;
+ }
+}
+
/** Initiated instance still carrying no cargo/price (clearance-first flow). */
function isBareInstance(booking: ActionBooking): boolean {
return (
@@ -83,7 +132,7 @@ export function getBookingNextAction(
to: `/contracts/${booking.contractId}/bookings/${booking.id}/complete`,
};
}
- return ACTION_BY_STATUS[booking.status as string] ?? null;
+ return actionByStatus(booking);
}
/**
diff --git a/apps/edr-freight-web/portal/src/pages/contracts/ContractDetailPage.tsx b/apps/edr-freight-web/portal/src/pages/contracts/ContractDetailPage.tsx
index e38fb4169..7527d37ca 100644
--- a/apps/edr-freight-web/portal/src/pages/contracts/ContractDetailPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/contracts/ContractDetailPage.tsx
@@ -64,6 +64,7 @@ import {
} from "@/pages/bookings/booking-display";
import { InitiateBookingButton } from "@/components/customer-actions/ContractCustomerAction";
import { formatRateUnit } from "./new-contract-form/unit-rates";
+import { bookingDocNoun } from "@/pages/bookings/clearance/bookingNextAction";
import { getContractBookingAction } from "./contract-booking-action";
import { closedWindowMessage, hasOpenWindow } from "./booking-window";
import { ContractBookingWindowsSection } from "./ContractBookingWindowsSection";
@@ -1231,7 +1232,7 @@ export default function ContractDetailPage() {
: customsPath
? "No bookings yet. Global Logistics opens the shipment on your behalf — you upload the clearance documents on it."
: canInitiateBooking
- ? "No shipments yet. Start one with “Initiate booking” — you upload the clearance documents on that shipment."
+ ? `No shipments yet. Start one with “Initiate booking” — you upload the ${bookingDocNoun(contract)} on that shipment.`
: "Bookings appear here once the contract is fully executed."}