feat: add poa to the changes approval

This commit is contained in:
Nathnael
2026-07-10 08:45:39 +00:00
parent ea0f264d72
commit e5fce529fe
13 changed files with 1046 additions and 121 deletions

View File

@@ -153,6 +153,7 @@ export function ChangeRequestReview({ company }: { company: Company }) {
: ([] as string[]);
const docCount = pending?.documentFileIds?.length ?? 0;
const licenseChanges = pending?.licenseChanges ?? [];
const documentChanges = pending?.documentChanges ?? [];
const confirmReject = () => {
if (!rejectId) return;
@@ -210,11 +211,75 @@ export function ChangeRequestReview({ company }: { company: Company }) {
</Text>
)}
{documentChanges.length > 0 && (
<Stack gap={8}>
<Text size="sm" fw={600} c="edr-text">
Document changes
</Text>
{documentChanges.map((c, i) => (
<Group key={`${c.fileId}-${i}`} gap={8} wrap="nowrap">
{c.op === "add" ? (
<FilePlus2 size={15} className="text-edr-muted" />
) : (
<FileX2 size={15} className="text-edr-muted" />
)}
<Badge
size="sm"
radius="sm"
variant="light"
color={c.op === "add" ? "green" : "red"}
>
{c.op === "add" ? "Add" : "Remove"}
</Badge>
<Anchor
component="button"
type="button"
size="sm"
onClick={() =>
view({
name: c.fileName ?? humanize(c.code),
url: fileViewUrl(c.fileId),
})
}
style={{
textDecoration:
c.op === "remove" ? "line-through" : undefined,
}}
>
{c.fileName ?? humanize(c.code)}
</Anchor>
<Text size="xs" c="dimmed">
{humanize(c.code)}
</Text>
</Group>
))}
</Stack>
)}
{docCount > 0 && (
<Text size="sm" c="dimmed">
{docCount} document{docCount === 1 ? "" : "s"} uploaded with this
request review them in the Documents tab.
</Text>
<Stack gap={8}>
<Text size="sm" fw={600} c="edr-text">
Documents uploaded with this request
</Text>
{pending!.documentFileIds.map((fileId, i) => (
<Group key={fileId} gap={8} wrap="nowrap">
<FilePlus2 size={15} className="text-edr-muted" />
<Anchor
component="button"
type="button"
size="sm"
onClick={() =>
view({
name: `Document ${i + 1}`,
url: fileViewUrl(fileId),
})
}
>
Document {i + 1}
</Anchor>
</Group>
))}
</Stack>
)}
{licenseChanges.length > 0 && (

View File

@@ -93,6 +93,8 @@ function tableStatus(query: { isLoading: boolean; isError: boolean }) {
/** Matches the fileKey seeded in the API's file-upload-settings seeder. */
const POA_DELEGATION_CODE = "poa_delegation_letter";
/** A letter uploaded by an approved customer, awaiting this reviewer's approval. */
const POA_DELEGATION_PENDING_CODE = "poa_delegation_letter_pending";
export default function CustomerDetailPage() {
const { id } = useParams<{ id: string }>();
@@ -536,9 +538,15 @@ export default function CustomerDetailPage() {
);
const poaDocuments = useMemo(
() => documents.filter((d) => d.code === POA_DELEGATION_CODE),
() =>
documents.filter(
(d) =>
d.code === POA_DELEGATION_CODE ||
d.code === POA_DELEGATION_PENDING_CODE,
),
[documents],
);
const poaLive = poaDocuments.filter((d) => d.code === POA_DELEGATION_CODE);
const poaFields = [
{ label: "PoA name", value: company?.poaName },
{ label: "PoA email", value: company?.poaEmail },
@@ -553,7 +561,7 @@ export default function CustomerDetailPage() {
(p) => p.type === "freight_forwarder",
);
const delegationMissing =
(hasPoaDetails || poaMandatory) && poaDocuments.length === 0;
(hasPoaDetails || poaMandatory) && poaLive.length === 0;
if (isLoading) {
return (
@@ -713,7 +721,7 @@ export default function CustomerDetailPage() {
<Badge size="sm" color="red" variant="light">
Delegation letter missing
</Badge>
) : poaDocuments.length > 0 ? (
) : poaLive.length > 0 ? (
<Badge size="sm" color="edr-green" variant="light">
Delegation letter on file
</Badge>
@@ -806,6 +814,16 @@ export default function CustomerDetailPage() {
{formatBytes(doc.size)} ·{" "}
{formatDate(doc.uploadedAt)}
</Text>
{doc.code === POA_DELEGATION_PENDING_CODE && (
<Badge
size="xs"
color="yellow"
variant="light"
className="shrink-0"
>
Pending approval
</Badge>
)}
</Group>
<Group gap={4} wrap="nowrap">
<ActionIcon

View File

@@ -79,6 +79,15 @@ export interface LicenseChangeIntent {
fileName?: string;
}
/** A staged company-level document add/remove (e.g. the PoA delegation letter). */
export interface DocumentChangeIntent {
op: "add" | "remove";
fileId: string;
/** The live FileRecord code this op targets (the upload setting's fileKey). */
code: string;
fileName?: string;
}
/**
* A staged profile-edit change request. The customer's settings edits land here
* (pending) until a reviewer approves (applies them) or rejects (with a note).
@@ -92,6 +101,8 @@ export interface CompanyChangeRequest {
documentFileIds: string[];
/** Staged business-license add/remove intents attached to this request. */
licenseChanges: LicenseChangeIntent[];
/** Staged company-document add/remove intents (e.g. the PoA letter). */
documentChanges: DocumentChangeIntent[];
note: string | null;
submittedAt: string | null;
reviewedAt: string | null;