mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 11:08:12 +00:00
fea: add poa section to backoffice customer detail page
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
Center,
|
Center,
|
||||||
Container,
|
Container,
|
||||||
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
@@ -90,6 +91,9 @@ function tableStatus(query: { isLoading: boolean; isError: boolean }) {
|
|||||||
return query.isLoading ? "loading" : query.isError ? "error" : "success";
|
return query.isLoading ? "loading" : query.isError ? "error" : "success";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Matches the fileKey seeded in the API's file-upload-settings seeder. */
|
||||||
|
const POA_DELEGATION_CODE = "poa_delegation_letter";
|
||||||
|
|
||||||
export default function CustomerDetailPage() {
|
export default function CustomerDetailPage() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -531,6 +535,26 @@ export default function CustomerDetailPage() {
|
|||||||
(p) => p.licenseFiles && p.licenseFiles.length > 0,
|
(p) => p.licenseFiles && p.licenseFiles.length > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const poaDocuments = useMemo(
|
||||||
|
() => documents.filter((d) => d.code === POA_DELEGATION_CODE),
|
||||||
|
[documents],
|
||||||
|
);
|
||||||
|
const poaFields = [
|
||||||
|
{ label: "PoA name", value: company?.poaName },
|
||||||
|
{ label: "PoA email", value: company?.poaEmail },
|
||||||
|
{ label: "PoA phone", value: company?.poaPhone },
|
||||||
|
{ label: "PoA location", value: company?.poaLocation },
|
||||||
|
{ label: "PoA address", value: company?.poaAddress },
|
||||||
|
];
|
||||||
|
const hasPoaDetails = poaFields.some((f) => f.value?.trim());
|
||||||
|
// A freight forwarder acts on other companies' behalf, so its PoA — details
|
||||||
|
// and delegation letter both — is mandatory rather than optional.
|
||||||
|
const poaMandatory = (company?.companyProfiles ?? []).some(
|
||||||
|
(p) => p.type === "freight_forwarder",
|
||||||
|
);
|
||||||
|
const delegationMissing =
|
||||||
|
(hasPoaDetails || poaMandatory) && poaDocuments.length === 0;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Center mih="60vh">
|
<Center mih="60vh">
|
||||||
@@ -672,6 +696,149 @@ export default function CustomerDetailPage() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Group justify="space-between" wrap="nowrap">
|
||||||
|
<Group gap="xs" wrap="nowrap">
|
||||||
|
<Text fw={600} c="edr-text">
|
||||||
|
Power of Attorney
|
||||||
|
</Text>
|
||||||
|
{poaMandatory && (
|
||||||
|
<Badge size="xs" color="blue" variant="light">
|
||||||
|
Required for freight forwarder
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
{delegationMissing ? (
|
||||||
|
<Badge size="sm" color="red" variant="light">
|
||||||
|
Delegation letter missing
|
||||||
|
</Badge>
|
||||||
|
) : poaDocuments.length > 0 ? (
|
||||||
|
<Badge size="sm" color="edr-green" variant="light">
|
||||||
|
Delegation letter on file
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge size="sm" color="gray" variant="light">
|
||||||
|
Not provided
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{hasPoaDetails ? (
|
||||||
|
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="lg">
|
||||||
|
{poaFields.map((f) => (
|
||||||
|
<InfoField
|
||||||
|
key={f.label}
|
||||||
|
label={f.label}
|
||||||
|
value={f.value}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
) : (
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
No Power of Attorney representative recorded for this
|
||||||
|
customer.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Text
|
||||||
|
size="xs"
|
||||||
|
fw={600}
|
||||||
|
c="edr-muted"
|
||||||
|
tt="uppercase"
|
||||||
|
style={{ letterSpacing: "0.04em" }}
|
||||||
|
>
|
||||||
|
Delegation letter
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{documentsQuery.isLoading ? (
|
||||||
|
<Group gap="xs">
|
||||||
|
<Loader size="xs" />
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Loading documents…
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
) : documentsQuery.isError ? (
|
||||||
|
<Group gap="sm">
|
||||||
|
<Text size="sm" c="red">
|
||||||
|
Failed to load documents.
|
||||||
|
</Text>
|
||||||
|
<Anchor
|
||||||
|
component="button"
|
||||||
|
type="button"
|
||||||
|
size="xs"
|
||||||
|
onClick={() => void documentsQuery.refetch()}
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</Anchor>
|
||||||
|
</Group>
|
||||||
|
) : poaDocuments.length === 0 ? (
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
No delegation letter uploaded.
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
poaDocuments.map((doc) => (
|
||||||
|
<Group key={doc.id} justify="space-between" wrap="nowrap">
|
||||||
|
<Group gap="sm" wrap="nowrap" style={{ minWidth: 0 }}>
|
||||||
|
<Paperclip
|
||||||
|
size={14}
|
||||||
|
className="shrink-0 text-edr-muted"
|
||||||
|
/>
|
||||||
|
<Anchor
|
||||||
|
component="button"
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
lineClamp={1}
|
||||||
|
onClick={() =>
|
||||||
|
view({
|
||||||
|
name: doc.name,
|
||||||
|
url: fileViewUrl(doc.id),
|
||||||
|
mimeType: doc.mimeType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{doc.name}
|
||||||
|
</Anchor>
|
||||||
|
<Text size="xs" c="dimmed" className="shrink-0">
|
||||||
|
{formatBytes(doc.size)} ·{" "}
|
||||||
|
{formatDate(doc.uploadedAt)}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group gap={4} wrap="nowrap">
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
color="gray"
|
||||||
|
aria-label={`Preview ${doc.name}`}
|
||||||
|
onClick={() =>
|
||||||
|
view({
|
||||||
|
name: doc.name,
|
||||||
|
url: fileViewUrl(doc.id),
|
||||||
|
mimeType: doc.mimeType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Eye size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
<ActionIcon
|
||||||
|
component="a"
|
||||||
|
href={fileViewUrl(doc.id, true)}
|
||||||
|
variant="subtle"
|
||||||
|
color="gray"
|
||||||
|
aria-label={`Download ${doc.name}`}
|
||||||
|
>
|
||||||
|
<Download size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ function mapCompany(dto: Record<string, unknown>): Company {
|
|||||||
generalManagerName: (attrs.generalManagerName as string | null) ?? null,
|
generalManagerName: (attrs.generalManagerName as string | null) ?? null,
|
||||||
generalManagerEmail: (attrs.generalManagerEmail as string | null) ?? null,
|
generalManagerEmail: (attrs.generalManagerEmail as string | null) ?? null,
|
||||||
generalManagerPhone: (attrs.generalManagerPhone as string | null) ?? null,
|
generalManagerPhone: (attrs.generalManagerPhone as string | null) ?? null,
|
||||||
|
poaName: (attrs.poaName as string | null) ?? null,
|
||||||
|
poaEmail: (attrs.poaEmail as string | null) ?? null,
|
||||||
|
poaPhone: (attrs.poaPhone as string | null) ?? null,
|
||||||
|
poaLocation: (attrs.poaLocation as string | null) ?? null,
|
||||||
|
poaAddress: (attrs.poaAddress as string | null) ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,11 @@ export interface Company {
|
|||||||
generalManagerName?: string | null;
|
generalManagerName?: string | null;
|
||||||
generalManagerEmail?: string | null;
|
generalManagerEmail?: string | null;
|
||||||
generalManagerPhone?: string | null;
|
generalManagerPhone?: string | null;
|
||||||
|
poaName?: string | null;
|
||||||
|
poaEmail?: string | null;
|
||||||
|
poaPhone?: string | null;
|
||||||
|
poaLocation?: string | null;
|
||||||
|
poaAddress?: string | null;
|
||||||
website?: string | null;
|
website?: string | null;
|
||||||
attributes?: Record<string, unknown> | null;
|
attributes?: Record<string, unknown> | null;
|
||||||
companyProfiles: CompanyProfile[];
|
companyProfiles: CompanyProfile[];
|
||||||
|
|||||||
Reference in New Issue
Block a user