Files
edr-platform/apps/edr-freight-web/backoffice/src/features/contracts/contract-approval-progress.ts
2026-06-26 23:24:48 +00:00

87 lines
2.1 KiB
TypeScript

import type { Freight } from "@edr/types";
export interface ApprovalProgressSummary {
label: string;
detail: string;
complete: boolean;
}
function nextPending(
steps: Freight.IContractApprovalStep[],
): Freight.IContractApprovalStep | undefined {
return steps.find((s) => s.status === "PENDING");
}
/** Compact approval-chain summary for contract list rows (mirrors bookings). */
export function formatContractApprovalProgress(
status: string,
steps?: Freight.IContractApprovalStep[] | null,
): ApprovalProgressSummary {
const sorted = [...(steps ?? [])].sort((a, b) => a.stepOrder - b.stepOrder);
if (sorted.length === 0) {
if (status === "SUBMITTED") {
return {
label: "Awaiting accept",
detail: "Staff must accept intake",
complete: false,
};
}
if (
status === "PENDING_APPROVAL" ||
status === "APPROVED_PENDING_SIGNATURE"
) {
return {
label: "No steps",
detail: "Approval chain not started",
complete: false,
};
}
if (
[
"APPROVED",
"CONTRACT_READY",
"SIGNED_CUSTOMER",
"FULLY_EXECUTED",
"CONTRACT_ACTIVE",
"CLEARANCE_READY_FOR_BOOKING",
"ACTIVE_SHIPMENT_IN_PROGRESS",
"CONTRACT_CLOSED",
].includes(status)
) {
return {
label: "Approved",
detail: "Internal approval complete",
complete: true,
};
}
return { label: "—", detail: "", complete: false };
}
const approved = sorted.filter((s) => s.status === "APPROVED").length;
const total = sorted.length;
const next = nextPending(sorted);
if (!next && approved === total) {
return {
label: `${approved}/${total} done`,
detail: sorted.map((s) => `${s.requiredRole}`).join(" · "),
complete: true,
};
}
if (next) {
return {
label: `${approved}/${total}`,
detail: `Next: ${next.requiredRole} (step ${next.stepOrder})`,
complete: false,
};
}
return {
label: `${approved}/${total}`,
detail: sorted.map((s) => `${s.requiredRole}: ${s.status}`).join(" · "),
complete: approved === total,
};
}