This commit is contained in:
estifanos
2026-08-19 09:04:28 +00:00
parent 1eeb4c5cc3
commit 439a4963ec

View File

@@ -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) => (
<StaffEvidenceCell
staffId={member.id}
required={evidenceByRole.get(member.roleKey) ?? []}
fallback={member.documents}
/>
),
@@ -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 (
<Text size="xs" c="dimmed">
</Text>
);
}
const badge = (
key: string,
label: string,
url: string | undefined,
mandatory: boolean,
) => {
const missing = !url;
return (
<Tooltip
key={key}
label={
missing
? mandatory
? t("review.evidenceMissingRequired", "Required — not uploaded")
: t("review.evidenceMissing", "Not uploaded")
: t("licensing.documents.view", "View")
}
withArrow
>
<Badge
size="xs"
variant={missing ? "outline" : "light"}
color={missing ? (mandatory ? "red" : "gray") : "teal"}
style={missing ? undefined : { cursor: "pointer" }}
onClick={
missing ? undefined : () => setPreview({ url: url, title: label })
}
>
{label}
{missing && mandatory ? " *" : ""}
</Badge>
</Tooltip>
);
};
return (
<Group gap={4}>
{docs.map((doc) => {
const url = doc.files?.[0]?.url;
return (
<Badge
key={doc.id}
size="xs"
variant="light"
component={url ? "a" : undefined}
href={url}
target={url ? "_blank" : undefined}
rel={url ? "noreferrer" : undefined}
style={url ? { cursor: "pointer" } : undefined}
>
{doc.documentKey}
</Badge>
);
})}
{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),
)}
<PdfPreviewModal
opened={Boolean(preview)}
onClose={() => setPreview(null)}
url={preview?.url ?? ""}
title={preview?.title}
/>
</Group>
);
}