From 439a4963ec1fff63b73b9028d5507ed6891d5bba Mon Sep 17 00:00:00 2001 From: estifanos Date: Wed, 19 Aug 2026 09:04:28 +0000 Subject: [PATCH] fixes --- .../pages/LicenseReviewPage/index.tsx | 110 +++++++++++++++--- 1 file changed, 93 insertions(+), 17 deletions(-) diff --git a/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx index 4b0351b90..3cd1b1e9c 100644 --- a/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx +++ b/apps/backoffice/src/app/features/license-review/pages/LicenseReviewPage/index.tsx @@ -57,12 +57,14 @@ import { useResumeApplicationMutation, useScheduleInspectionMutation, type RemarkTargetType, + type StaffEvidenceRequirement, } from "@ema-platform/api"; import { AdvancedTable, AmharicDatePicker, ErrorState, ModalFooter, + PdfPreviewModal, useServerTable, } from "@ema-platform/ui"; import { useDateDisplayer } from "@ema-platform/shared"; @@ -178,6 +180,19 @@ export function LicenseReviewPage() { ), [requirements], ); + // What each role is *required* to produce (CV, work agreement, ERB + // certificate). Without this the tab can only list what was uploaded, so a + // missing CV looks identical to a role that never needed one. + const evidenceByRole = useMemo( + () => + new Map( + requirements?.staffRoleRequirements.map((r) => [ + r.roleKey, + r.requiredEvidence ?? [], + ]) ?? [], + ), + [requirements], + ); const [completeReview] = useCompleteReviewMutation(); const [requestAdjustment] = useRequestAdjustmentMutation(); @@ -971,6 +986,7 @@ export function LicenseReviewPage() { renderEvidence: (member) => ( ), @@ -1275,36 +1291,96 @@ export function LicenseReviewPage() { */ function StaffEvidenceCell({ staffId, + required, fallback, }: { staffId: string; + /** What this person's role must produce, from the licence-type config. */ + required: StaffEvidenceRequirement[]; fallback?: { id: string; documentKey: string; files: { url?: string }[] }[]; }) { + const localized = useLocalized(); + const { t } = useTranslation(); + const [preview, setPreview] = useState<{ url: string; title: string } | null>( + null, + ); const { data: attachments } = useGetAttachmentsQuery({ ownerType: "APPLICATION_STAFF", ownerId: staffId, }); const docs = attachments?.length ? attachments : (fallback ?? []); + const uploadedBy = new Map(docs.map((d) => [d.documentKey, d])); + + // Drive the list off the requirements, not off what happens to have been + // uploaded: a mandatory CV that is absent has to be visible as absent, which + // is the whole point of the officer looking at this column. Anything + // uploaded outside the list still gets shown rather than silently dropped. + const extras = docs.filter( + (d) => !required.some((r) => r.docKey === d.documentKey), + ); + + if (!required.length && !extras.length) { + return ( + + — + + ); + } + + const badge = ( + key: string, + label: string, + url: string | undefined, + mandatory: boolean, + ) => { + const missing = !url; + return ( + + setPreview({ url: url, title: label }) + } + > + {label} + {missing && mandatory ? " *" : ""} + + + ); + }; return ( - {docs.map((doc) => { - const url = doc.files?.[0]?.url; - return ( - - {doc.documentKey} - - ); - })} + {required.map((item) => + badge( + item.docKey, + localized(item.label) || item.docKey, + uploadedBy.get(item.docKey)?.files?.[0]?.url, + item.mandatory, + ), + )} + {extras.map((doc) => + badge(doc.id, doc.documentKey, doc.files?.[0]?.url, false), + )} + setPreview(null)} + url={preview?.url ?? ""} + title={preview?.title} + /> ); }