mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 08:58:21 +00:00
206 lines
5.6 KiB
TypeScript
206 lines
5.6 KiB
TypeScript
import { useMemo } from "react";
|
|
import { Badge, Box, Stack, Tabs, Text, ThemeIcon } from "@mantine/core";
|
|
import { FileText, Receipt, Ship, Truck } from "lucide-react";
|
|
import type { Freight } from "@edr/types";
|
|
|
|
import { SectionCard } from "@/components/bookings/detail/SectionCard";
|
|
import { PhasedUploadedFileRow } from "@/components/contracts/PhasedUploadedFileRow";
|
|
|
|
type TabValue = Freight.ClearanceWorkflowFileCategory;
|
|
|
|
type TabConfig = {
|
|
value: TabValue;
|
|
label: string;
|
|
icon: typeof FileText;
|
|
emptyHint: string;
|
|
};
|
|
|
|
function tabConfigForTradeDirection(tradeDirection: string): TabConfig[] {
|
|
if (tradeDirection === "EXPORT") {
|
|
return [
|
|
{
|
|
value: "declaration",
|
|
label: "Declaration",
|
|
icon: FileText,
|
|
emptyHint: "No declaration uploaded yet.",
|
|
},
|
|
{
|
|
value: "djibouti",
|
|
label: "Release order",
|
|
icon: Ship,
|
|
emptyHint: "No release order uploaded yet.",
|
|
},
|
|
{
|
|
value: "transit",
|
|
label: "Transit Permit",
|
|
icon: Truck,
|
|
emptyHint: "No transit permit uploaded yet.",
|
|
},
|
|
];
|
|
}
|
|
|
|
return [
|
|
{
|
|
value: "declaration",
|
|
label: "Declaration",
|
|
icon: FileText,
|
|
emptyHint: "No declaration uploaded yet.",
|
|
},
|
|
{
|
|
value: "duty",
|
|
label: "Duty notice",
|
|
icon: Receipt,
|
|
emptyHint: "No duty notice or payment slip uploaded yet.",
|
|
},
|
|
{
|
|
value: "transit",
|
|
label: "Transit permit",
|
|
icon: Truck,
|
|
emptyHint: "No transit permit uploaded yet.",
|
|
},
|
|
];
|
|
}
|
|
|
|
function subtitleForTradeDirection(tradeDirection: string): string {
|
|
return tradeDirection === "EXPORT"
|
|
? "Declaration, release order, and transit permit files for this clearance."
|
|
: "Declaration, duty notice, and transit permit files for this clearance.";
|
|
}
|
|
|
|
function footerHintForTradeDirection(tradeDirection: string): string {
|
|
return tradeDirection === "EXPORT"
|
|
? "Files appear here once GL uploads the declaration and release order, and after booking when the transit permit is uploaded."
|
|
: "Files appear here once GL Ethiopia uploads declaration, duty notice, or transit permit documents.";
|
|
}
|
|
|
|
export interface ClearanceUploadedDocumentsPanelProps {
|
|
files: Freight.ClearanceWorkflowFile[];
|
|
tradeDirection?: string;
|
|
onView: (file: { name: string; url: string }) => void;
|
|
onDownload?: (file: { id: string; name: string }) => void;
|
|
}
|
|
|
|
export function ClearanceUploadedDocumentsPanel({
|
|
files,
|
|
tradeDirection = "IMPORT",
|
|
onView,
|
|
onDownload,
|
|
}: ClearanceUploadedDocumentsPanelProps) {
|
|
const tabConfig = useMemo(
|
|
() => tabConfigForTradeDirection(tradeDirection),
|
|
[tradeDirection],
|
|
);
|
|
const isExport = tradeDirection === "EXPORT";
|
|
|
|
const visibleFiles = useMemo(
|
|
() =>
|
|
isExport ? files.filter((f) => f.category !== "duty") : files,
|
|
[files, isExport],
|
|
);
|
|
|
|
const uploadedCount = visibleFiles.filter((f) => f.file).length;
|
|
|
|
const defaultTab =
|
|
tabConfig.find((tab) =>
|
|
visibleFiles.some((f) => f.category === tab.value && f.file),
|
|
)?.value ?? tabConfig[0]?.value ?? "declaration";
|
|
|
|
return (
|
|
<SectionCard
|
|
icon={FileText}
|
|
title="Uploaded customs documents"
|
|
subtitle={subtitleForTradeDirection(tradeDirection)}
|
|
accent="edr-green"
|
|
>
|
|
<Tabs defaultValue={defaultTab} keepMounted={false}>
|
|
<Tabs.List mb="md">
|
|
{tabConfig.map((tab) => {
|
|
const count = visibleFiles.filter(
|
|
(f) => f.category === tab.value && f.file,
|
|
).length;
|
|
const Icon = tab.icon;
|
|
return (
|
|
<Tabs.Tab
|
|
key={tab.value}
|
|
value={tab.value}
|
|
leftSection={<Icon size={14} />}
|
|
rightSection={
|
|
count > 0 ? (
|
|
<Badge size="xs" variant="light" color="edr-green" circle>
|
|
{count}
|
|
</Badge>
|
|
) : undefined
|
|
}
|
|
>
|
|
{tab.label}
|
|
</Tabs.Tab>
|
|
);
|
|
})}
|
|
</Tabs.List>
|
|
|
|
{tabConfig.map((tab) => {
|
|
const items = visibleFiles.filter(
|
|
(f) => f.category === tab.value && f.file,
|
|
);
|
|
const Icon = tab.icon;
|
|
|
|
return (
|
|
<Tabs.Panel key={tab.value} value={tab.value}>
|
|
{items.length > 0 ? (
|
|
<Stack gap="sm">
|
|
{items.map((item) => (
|
|
<PhasedUploadedFileRow
|
|
key={item.code}
|
|
label={item.label}
|
|
file={item.file!}
|
|
onView={onView}
|
|
onDownload={onDownload}
|
|
/>
|
|
))}
|
|
</Stack>
|
|
) : (
|
|
<EmptyTabState icon={Icon} hint={tab.emptyHint} />
|
|
)}
|
|
</Tabs.Panel>
|
|
);
|
|
})}
|
|
</Tabs>
|
|
|
|
{uploadedCount === 0 ? (
|
|
<Text size="xs" c="dimmed" mt="md">
|
|
{footerHintForTradeDirection(tradeDirection)}
|
|
</Text>
|
|
) : null}
|
|
</SectionCard>
|
|
);
|
|
}
|
|
|
|
function EmptyTabState({
|
|
icon: Icon,
|
|
hint,
|
|
}: {
|
|
icon: typeof FileText;
|
|
hint: string;
|
|
}) {
|
|
return (
|
|
<Box
|
|
py={40}
|
|
style={{
|
|
borderRadius: 12,
|
|
border: "1px dashed var(--mantine-color-gray-4)",
|
|
background: "var(--mantine-color-gray-0)",
|
|
textAlign: "center",
|
|
}}
|
|
>
|
|
<Stack gap={8} align="center">
|
|
<ThemeIcon variant="light" color="gray" radius="xl" size={44}>
|
|
<Icon size={20} />
|
|
</ThemeIcon>
|
|
<Text size="sm" c="dimmed" maw={320}>
|
|
{hint}
|
|
</Text>
|
|
</Stack>
|
|
</Box>
|
|
);
|
|
}
|