enhance contract clearance details with service type and customs information

This commit is contained in:
Marshal
2026-06-28 21:34:06 +00:00
parent 006b4226e8
commit f45d260c53
4 changed files with 83 additions and 13 deletions

View File

@@ -110,11 +110,12 @@ export function ContractClearanceReviewSection({
(clearance?.documents ?? []).filter((d) => d.uploadedBy === "customer"),
[clearance],
);
// GL output documents — anything not uploaded by the customer. The backend
// tags these `uploadedBy: 'gl'`; matching on "not customer" keeps it robust if
// that ever splits into gl_et / gl_dj.
const glDocs = useMemo(
() =>
(clearance?.documents ?? []).filter(
(d) => d.uploadedBy === "gl_et" || d.uploadedBy === "gl_dj",
),
(clearance?.documents ?? []).filter((d) => d.uploadedBy !== "customer"),
[clearance],
);

View File

@@ -222,6 +222,11 @@ function ClearanceHero({
stats: { pct: number; approved: number; total: number };
}) {
const direction = contract?.tradeDirection ?? "—";
const serviceName = contract?.serviceType?.serviceName ?? null;
const customs =
contract?.serviceType?.includesCustoms ??
contract?.customsClearingEnabled ??
false;
const routes = [...(contract?.routes ?? [])].sort(
(a, b) => a.sortOrder - b.sortOrder,
);
@@ -253,16 +258,27 @@ function ClearanceHero({
>
{direction}
</Badge>
<Badge
size="sm"
variant="light"
color="edr-green"
radius="sm"
leftSection={<ShieldCheck size={12} />}
>
Customs
</Badge>
{customs ? (
<Badge
size="sm"
variant="light"
color="edr-green"
radius="sm"
leftSection={<ShieldCheck size={12} />}
>
Customs
</Badge>
) : (
<Badge size="sm" variant="light" color="gray" radius="sm">
No customs
</Badge>
)}
</Group>
{serviceName && (
<Text size="sm" fw={600} c="edr-text" mt={6} truncate maw={280}>
{serviceName}
</Text>
)}
<Group gap={8} mt={6} wrap="nowrap">
<Text size="sm" fw={600} truncate maw={160}>
{origin}

View File

@@ -54,6 +54,8 @@ interface ClearanceRow {
originLabel: string;
destinationLabel: string;
contractKind: string;
serviceTypeName: string;
customs: boolean;
status: string;
/** true once GL has finalized clearance — customer now books in the portal. */
ready: boolean;
@@ -84,11 +86,32 @@ function toClearanceRow(contract: Freight.IContract): ClearanceRow {
originLabel: yardLabel(first?.originYard),
destinationLabel: yardLabel(last?.destinationYard),
contractKind: contract.contractKind,
serviceTypeName: contract.serviceType?.serviceName ?? "—",
customs:
contract.serviceType?.includesCustoms ?? contract.customsClearingEnabled,
status: contract.status,
ready: contract.status === "CLEARANCE_READY_FOR_BOOKING",
};
}
function CustomsBadge({ customs }: { customs: boolean }) {
return customs ? (
<Badge
size="xs"
variant="light"
color="edr-green"
radius="sm"
leftSection={<ShieldCheck size={11} />}
>
Customs
</Badge>
) : (
<Badge size="xs" variant="light" color="gray" radius="sm">
No customs
</Badge>
);
}
function DirectionIcon({ direction }: { direction: string }) {
const isImport = direction === "IMPORT";
const Icon = isImport ? Truck : ShipWheel;
@@ -248,6 +271,21 @@ export default function ContractClearanceListPage() {
</Badge>
),
},
{
id: "service",
header: () => <span className={bookingTable.headerCell}>Service</span>,
cell: ({ row }) => {
const r = row.original;
return (
<Stack gap={4} py={2} style={{ minWidth: 0 }}>
<Text size="sm" fw={500} truncate maw={160}>
{r.serviceTypeName}
</Text>
<CustomsBadge customs={r.customs} />
</Stack>
);
},
},
{
id: "status",
header: () => <span className={bookingTable.headerCell}>Status</span>,
@@ -545,6 +583,13 @@ function ClearanceCard({
{row.contractKind === "GENERAL" ? "General" : "One-time"}
</Badge>
</Group>
<Group justify="space-between" mt={8} wrap="nowrap" gap={8}>
<Text size="xs" c="dimmed" truncate maw={150}>
{row.serviceTypeName}
</Text>
<CustomsBadge customs={row.customs} />
</Group>
</Card>
);
}

View File

@@ -230,7 +230,7 @@ export interface ContractClearanceDocument {
fileKey: string;
label: string;
required: boolean;
uploadedBy: "customer" | "gl_et" | "gl_dj";
uploadedBy: "customer" | "gl" | "gl_et" | "gl_dj";
phase: ContractDocPhase;
settingCode: string;
file: { id: string; name: string; url: string } | null;
@@ -406,6 +406,14 @@ export interface IContract extends BaseEntity {
freightType: ContractFreightType;
serviceTypeId: string;
/** Loaded service-type relation (queue + detail responses include it). */
serviceType?: {
id: string;
code: string;
serviceName: string;
canBeBookedAlone: boolean;
includesCustoms: boolean;
} | null;
paymentCurrency: string;
customsClearingEnabled: boolean;
customsClearingAgent?: string | null;