Files
edr-platform/apps/edr-freight-web/backoffice/src/components/contracts/ClearanceOpsTabs.tsx

179 lines
6.3 KiB
TypeScript

import type { ReactNode } from "react";
import { Badge, Stack, Tabs, Text } from "@mantine/core";
import { AlertTriangle, FileText, Receipt, Share2, ShieldAlert } from "lucide-react";
import type { Freight } from "@edr/types";
import { useAuth } from "@/auth/useAuth";
import { FREIGHT_PERMS, hasPermission } from "@/lib/permissions";
import { SectionCard } from "@/components/bookings/detail/SectionCard";
import { AssignRiskCard } from "@/components/contracts/gl-actions/AssignRiskCard";
import { IncidentReportCard } from "@/components/contracts/gl-actions/IncidentReportCard";
import { ClearanceUploadedDocumentsPanel } from "@/components/contracts/ClearanceUploadedDocumentsPanel";
import { ClearanceChargesTab } from "@/components/contracts/ClearanceChargesTab";
import { GlExchangePanel } from "@/components/contracts/GlExchangePanel";
export interface ClearanceOpsTabsProps {
bookingId: string | undefined;
milestones?: Freight.IClearanceMilestone[];
/** When false, only the clearance tab content is rendered (no tab bar). */
showOpsTabs?: boolean;
clearanceTab: ReactNode;
/** Phased customs workflow files — enables the Uploaded documents tab. */
workflowFiles?: Freight.ClearanceWorkflowFile[];
showWorkflowFilesTab?: boolean;
/**
* Booking or contract id whose GL Ethiopia ↔ GL Djibouti document exchange
* belongs on this page. Undefined hides the tab; it is also hidden from staff
* who hold neither desk's clearance-actions permission.
*/
exchangeEntityId?: string;
tradeDirection?: string;
onViewFile?: (file: { name: string; url: string }) => void;
onDownloadFile?: (file: { id: string; name: string }) => void;
}
function findMilestone(
milestones: Freight.IClearanceMilestone[] | undefined,
code: string,
): Freight.IClearanceMilestone | undefined {
return milestones?.find((m) => m.milestoneCode === code);
}
/**
* Document Clearance detail layout: primary clearance workflow plus optional
* uploaded documents, post-booking risk assignment, and incident reporting tabs.
*/
export function ClearanceOpsTabs({
bookingId,
milestones,
showOpsTabs = true,
clearanceTab,
workflowFiles = [],
showWorkflowFilesTab = false,
exchangeEntityId,
tradeDirection = "IMPORT",
onViewFile,
onDownloadFile,
}: ClearanceOpsTabsProps) {
const riskMs = findMilestone(milestones, "RISK_ASSIGNED");
const hasOps = Boolean(bookingId);
const isExport = tradeDirection === "EXPORT";
const uploadedDocCount = workflowFiles.filter((f) => {
if (!f.file) return false;
if (isExport) return f.category !== "duty";
return true;
}).length;
const showDocuments = showWorkflowFilesTab && Boolean(onViewFile);
const { user } = useAuth();
const showExchange =
Boolean(exchangeEntityId) &&
(hasPermission(user, FREIGHT_PERMS.contracts.clearanceEtActions) ||
hasPermission(user, FREIGHT_PERMS.contracts.clearanceDjActions));
// Post-finalization customer billing. This layout is only rendered on the ET
// clearance pages — the DJ page (GlClearanceDetailPage) mounts its own tab.
const showCharges =
Boolean(bookingId) &&
Boolean(onViewFile) &&
hasPermission(user, FREIGHT_PERMS.contracts.clearanceEtActions);
// Risk assignment + incident reporting hit bookings:operations endpoints.
const canOps = hasPermission(user, FREIGHT_PERMS.bookings.operations);
const hasTabs = (showOpsTabs && hasOps) || showDocuments || showExchange;
if (!hasTabs) {
return <>{clearanceTab}</>;
}
return (
<Tabs defaultValue="clearance" keepMounted={false}>
<Tabs.List mb="md">
<Tabs.Tab value="clearance">Clearance</Tabs.Tab>
{showDocuments ? (
<Tabs.Tab
value="documents"
leftSection={<FileText size={14} />}
rightSection={
uploadedDocCount > 0 ? (
<Badge size="xs" variant="light" color="edr-green" circle>
{uploadedDocCount}
</Badge>
) : undefined
}
>
Uploaded documents
</Tabs.Tab>
) : null}
{showExchange ? (
<Tabs.Tab value="exchange" leftSection={<Share2 size={14} />}>
Document exchange
</Tabs.Tab>
) : null}
{showCharges ? (
<Tabs.Tab value="charges" leftSection={<Receipt size={14} />}>
Customer charges
</Tabs.Tab>
) : null}
{showOpsTabs && canOps && riskMs ? (
<Tabs.Tab value="risk" leftSection={<ShieldAlert size={14} />}>
Risk assignment
</Tabs.Tab>
) : null}
{showOpsTabs && canOps && bookingId ? (
<Tabs.Tab value="incidents" leftSection={<AlertTriangle size={14} />}>
Incidents
</Tabs.Tab>
) : null}
</Tabs.List>
<Tabs.Panel value="clearance">{clearanceTab}</Tabs.Panel>
{showDocuments ? (
<Tabs.Panel value="documents">
<ClearanceUploadedDocumentsPanel
files={workflowFiles}
tradeDirection={tradeDirection}
onView={onViewFile!}
onDownload={onDownloadFile}
/>
</Tabs.Panel>
) : null}
{showExchange ? (
<Tabs.Panel value="exchange">
<GlExchangePanel entityId={exchangeEntityId!} />
</Tabs.Panel>
) : null}
{showCharges ? (
<Tabs.Panel value="charges">
<ClearanceChargesTab
bookingId={bookingId!}
roleMode="ET"
onViewFile={onViewFile!}
/>
</Tabs.Panel>
) : null}
{showOpsTabs && canOps && riskMs && bookingId ? (
<Tabs.Panel value="risk">
<SectionCard icon={ShieldAlert} title="Customs risk" accent="edr-green">
<AssignRiskCard bookingId={bookingId} milestone={riskMs} />
</SectionCard>
</Tabs.Panel>
) : null}
{showOpsTabs && canOps && bookingId ? (
<Tabs.Panel value="incidents">
<SectionCard icon={AlertTriangle} title="Incident reports" accent="edr-green">
<Stack gap="sm">
<Text size="sm" c="dimmed">
Log container or seal issues discovered during clearance handling.
</Text>
<IncidentReportCard bookingId={bookingId} />
</Stack>
</SectionCard>
</Tabs.Panel>
) : null}
</Tabs>
);
}