mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
Implement client-side validation for container and bul
This commit is contained in:
@@ -47,6 +47,12 @@ export interface ClearanceReviewSectionProps {
|
||||
queriesLocked?: boolean;
|
||||
/** Read-only audit view — no approve/query actions. */
|
||||
readOnly?: boolean;
|
||||
/**
|
||||
* GENERAL customs bookings use the phased milestone workflow (same as
|
||||
* ONE_TIME contracts): hide the legacy output-documents upload block and the
|
||||
* finalize button — declaration/duty/transit run in the phased action panel.
|
||||
*/
|
||||
phasedCustoms?: boolean;
|
||||
}
|
||||
|
||||
const STATUS_META: Record<
|
||||
@@ -73,6 +79,7 @@ export function ClearanceReviewSection({
|
||||
approvalsLocked = false,
|
||||
queriesLocked = false,
|
||||
readOnly = false,
|
||||
phasedCustoms = false,
|
||||
}: ClearanceReviewSectionProps) {
|
||||
const qc = useQueryClient();
|
||||
const [queryNotes, setQueryNotes] = useState<Record<string, string>>({});
|
||||
@@ -240,7 +247,7 @@ export function ClearanceReviewSection({
|
||||
</Stack>
|
||||
</SectionCard>
|
||||
|
||||
{clearance.outputCode && (
|
||||
{clearance.outputCode && !phasedCustoms && (
|
||||
<SectionCard
|
||||
icon={Upload}
|
||||
title="Customs output documents"
|
||||
@@ -341,7 +348,7 @@ export function ClearanceReviewSection({
|
||||
</SectionCard>
|
||||
)}
|
||||
|
||||
{finalizeMutation.isError && (
|
||||
{!phasedCustoms && finalizeMutation.isError && (
|
||||
<Alert color="red" radius="md" icon={<AlertCircle size={16} />}>
|
||||
{finalizeMutation.error instanceof Error
|
||||
? finalizeMutation.error.message
|
||||
@@ -349,8 +356,10 @@ export function ClearanceReviewSection({
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Paper withBorder radius="md" p="md">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
{phasedCustoms ? (
|
||||
// Phased (GENERAL customs) — no legacy finalize; the milestone steps in
|
||||
// the action panel drive the workflow, same as ONE_TIME contracts.
|
||||
<Paper withBorder radius="md" p="md">
|
||||
<Group gap={8} wrap="nowrap" style={{ minWidth: 0 }}>
|
||||
<ThemeIcon
|
||||
variant="light"
|
||||
@@ -358,26 +367,50 @@ export function ClearanceReviewSection({
|
||||
radius="md"
|
||||
size={28}
|
||||
>
|
||||
<FileCheck2 size={15} />
|
||||
{clearance.allApproved ? (
|
||||
<CheckCircle2 size={15} />
|
||||
) : (
|
||||
<FileCheck2 size={15} />
|
||||
)}
|
||||
</ThemeIcon>
|
||||
<Text fz="12.5px" c="dimmed">
|
||||
{clearance.allApproved
|
||||
? "All required documents are approved — you can finalize."
|
||||
: "Approve every required document to unlock finalization."}
|
||||
? "All required documents are approved. Continue declaration, duty, and transit in the action panel."
|
||||
: "Approve every required document to unlock the customs milestone steps."}
|
||||
</Text>
|
||||
</Group>
|
||||
<Button
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
leftSection={<CheckCircle2 size={16} />}
|
||||
disabled={!clearance.allApproved}
|
||||
loading={finalizeMutation.isPending}
|
||||
onClick={() => finalizeMutation.mutate()}
|
||||
>
|
||||
Finalize clearance
|
||||
</Button>
|
||||
</Group>
|
||||
</Paper>
|
||||
</Paper>
|
||||
) : (
|
||||
<Paper withBorder radius="md" p="md">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap={8} wrap="nowrap" style={{ minWidth: 0 }}>
|
||||
<ThemeIcon
|
||||
variant="light"
|
||||
color={clearance.allApproved ? "edr-green" : "gray"}
|
||||
radius="md"
|
||||
size={28}
|
||||
>
|
||||
<FileCheck2 size={15} />
|
||||
</ThemeIcon>
|
||||
<Text fz="12.5px" c="dimmed">
|
||||
{clearance.allApproved
|
||||
? "All required documents are approved — you can finalize."
|
||||
: "Approve every required document to unlock finalization."}
|
||||
</Text>
|
||||
</Group>
|
||||
<Button
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
leftSection={<CheckCircle2 size={16} />}
|
||||
disabled={!clearance.allApproved}
|
||||
loading={finalizeMutation.isPending}
|
||||
onClick={() => finalizeMutation.mutate()}
|
||||
>
|
||||
Finalize clearance
|
||||
</Button>
|
||||
</Group>
|
||||
</Paper>
|
||||
)}
|
||||
{viewer}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -176,6 +176,7 @@ export default function DocumentClearanceDetailPage() {
|
||||
hideSummary
|
||||
approvalsLocked={isPhasedGeneral && docsPhaseComplete}
|
||||
queriesLocked={queriesLocked}
|
||||
phasedCustoms={isPhasedGeneral}
|
||||
onChanged={() => void refetch()}
|
||||
/>
|
||||
</Grid.Col>
|
||||
|
||||
@@ -188,7 +188,12 @@ export default function GlClearanceDetailPage() {
|
||||
<Grid>
|
||||
<Grid.Col span={{ base: 12, lg: 7 }}>
|
||||
{data.kind === "booking" ? (
|
||||
<ClearanceReviewSection bookingId={id!} hideSummary readOnly />
|
||||
<ClearanceReviewSection
|
||||
bookingId={id!}
|
||||
hideSummary
|
||||
readOnly
|
||||
phasedCustoms
|
||||
/>
|
||||
) : (
|
||||
<ContractClearanceReviewSection
|
||||
contractId={id!}
|
||||
|
||||
@@ -39,11 +39,16 @@ export interface SaveRoutePayload {
|
||||
status?: RouteStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-readable route label: yard names, not yard codes. Staff read
|
||||
* "Addis Ababa → Dire Dawa", not "ADDIS_ABABA → DIRE_DAWA". Falls back to the
|
||||
* code only when a yard has no label.
|
||||
*/
|
||||
export function formatRouteLabel(route: RouteRecord): string {
|
||||
const origin =
|
||||
route.originYard?.code ?? route.originYard?.label ?? 'Origin';
|
||||
route.originYard?.label ?? route.originYard?.code ?? 'Origin';
|
||||
const dest =
|
||||
route.destinationYard?.code ?? route.destinationYard?.label ?? 'Destination';
|
||||
route.destinationYard?.label ?? route.destinationYard?.code ?? 'Destination';
|
||||
return `${origin} → ${dest}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user