feat: enhance contract clearance process with linked booking details

- Added  and  to  for better visibility of GL-created shipment bookings.
- Implemented  method in  to fetch the latest clearance phase for contracts, improving list responses.
- Introduced  property in the  entity to store the latest clearance cycle's phase.
- Updated  to surface linked booking information in the clearance view.
- Created  component to display detailed container information in booking details.
- Refactored booking actions to remove contract-related actions from the booking request page.
- Enhanced the  component to reflect the current phase of clearance actions.
- Updated UI components to provide clearer messaging regarding the status of clearance and linked bookings.
- Adjusted action handling in  to include duty payment actions.
- Improved the  to show hints for each phase of the clearance process.
This commit is contained in:
Marshal
2026-07-03 21:04:46 +00:00
parent c3f06b6aa9
commit 85c2fd1428
22 changed files with 537 additions and 204 deletions

View File

@@ -1,7 +1,7 @@
import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import { Button, Modal, Text, type ButtonProps } from "@mantine/core";
import { AlertCircle, Upload } from "lucide-react";
import { AlertCircle, Upload, type LucideIcon } from "lucide-react";
import { api } from "@/services/api";
import { ContractClearancePanel } from "@/pages/contracts/ContractClearancePanel";
@@ -13,6 +13,10 @@ interface ContractClearanceActionProps {
label?: string;
size?: ButtonProps["size"];
urgent?: boolean;
/** GL's turn — render as a calm status button, not a call to action. */
waiting?: boolean;
/** Icon override from the phase-aware action derivation. */
icon?: LucideIcon;
}
export function ContractClearanceAction({
@@ -20,6 +24,8 @@ export function ContractClearanceAction({
label: labelProp,
size = "xs",
urgent = false,
waiting = false,
icon: iconProp,
}: ContractClearanceActionProps) {
const [opened, { open, close }] = useDisclosure(false);
@@ -38,7 +44,13 @@ export function ContractClearanceAction({
return urgent ? "Upload clearance" : "Manage clearance";
}, [labelProp, clearance, urgent]);
const Icon = urgent || label.includes("Update") ? AlertCircle : Upload;
const Icon =
iconProp ?? (urgent || label.includes("Update") ? AlertCircle : Upload);
// Urgent (customer's turn) = filled orange so it stands out among the green
// actions; waiting (GL's turn) = calm subtle gray; default = brand green.
const color = urgent ? "orange" : waiting ? "gray" : "edr-green";
const variant = waiting ? "light" : "filled";
return (
<ModalSafeWrapper>
@@ -47,7 +59,8 @@ export function ContractClearanceAction({
radius="md"
fw={700}
fz={13}
color="edr-green"
color={color}
variant={variant}
leftSection={<Icon size={14} />}
onClick={(e) => {
e.stopPropagation();

View File

@@ -46,6 +46,8 @@ export function ContractCustomerAction({
label={action.label}
size={size}
urgent={action.urgent}
waiting={action.waiting}
icon={action.icon}
/>
);
}

View File

@@ -4,8 +4,10 @@ import {
CreditCard,
Eye,
FileSignature,
Hourglass,
PackagePlus,
PencilLine,
Receipt,
RotateCcw,
Upload,
} from "lucide-react";
@@ -61,6 +63,8 @@ export type ContractCustomerAction =
primary: boolean;
icon: LucideIcon;
urgent: boolean;
/** True when it's GL's turn — render calm/informational, not a call to action. */
waiting?: boolean;
}
| {
type: "pay";
@@ -126,14 +130,56 @@ export function deriveContractCustomerAction(
const clr = contractNeedsClearanceAction(contract);
if (clr.show) {
return {
type: "clearance",
contractId: id,
label: clr.urgent ? "Upload clearance" : "Update clearance",
primary: true,
icon: Upload,
urgent: clr.urgent,
};
// Refine the generic clearance action by the persisted clearance phase so
// the button says what the customer actually has to do right now (e.g.
// "Pay duty & upload slip" during CUSTOMER_DUTY, not "Update clearance").
const phase = contract.clearancePhase ?? null;
switch (phase) {
case "CUSTOMER_INTAKE":
return {
type: "clearance",
contractId: id,
label: "Upload clearance documents",
primary: true,
icon: Upload,
urgent: true,
};
case "CUSTOMER_DUTY":
return {
type: "clearance",
contractId: id,
label: "Pay duty & upload slip",
primary: true,
icon: Receipt,
urgent: true,
};
case "GL_ET_REVIEW":
case "GL_DJ_COLLECTION":
case "GL_ET_OUTPUT":
case "GL_ET_POST_CLEARANCE":
case "GL_DJ_LOADING":
case "POST_TRANSIT":
// GL's turn — nothing for the customer to do; show a calm status.
return {
type: "clearance",
contractId: id,
label: "Clearance in progress",
primary: false,
icon: Hourglass,
urgent: false,
waiting: true,
};
default:
// No persisted phase (legacy / early cycles) — keep the status-derived label.
return {
type: "clearance",
contractId: id,
label: clr.urgent ? "Upload clearance" : "Update clearance",
primary: true,
icon: Upload,
urgent: clr.urgent,
};
}
}
if (