From 95f02d9cd45e5c4b008d40d63ca3da98e8bf65f4 Mon Sep 17 00:00:00 2001 From: Khalid Al-Mutawa Date: Thu, 21 May 2026 17:50:14 +0700 Subject: [PATCH 1/3] feat: add Civil ID fact panel to candidate profile Show civil ID number, expiry date with warning/expired state, photo front/back thumbnails, and needs-verification badge below the profile facts grid. Adds a dedicated CivilIdPanel component with expiry date threshold alerts (90 days) and responsive photo layout. Co-Authored-By: Paperclip --- src/app/styles.css | 103 ++++++++++++++++++++ src/modules/candidates/CandidateProfile.tsx | 61 +++++++++++- 2 files changed, 163 insertions(+), 1 deletion(-) diff --git a/src/app/styles.css b/src/app/styles.css index 4f765e0..b4023c1 100644 --- a/src/app/styles.css +++ b/src/app/styles.css @@ -8672,6 +8672,109 @@ kbd { line-height: 1.5; } +.civilIdPanel { + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); + overflow: hidden; +} + +.civilIdPanelBody { + padding: 12px; + display: grid; + gap: 12px; +} + +.civilIdPanelFields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.civilIdPanelFields div { + min-width: 0; + display: grid; + gap: 4px; +} + +.civilIdPanelFields span { + font-size: 12px; + color: var(--ink-muted, #6b7280); +} + +.civilIdPanelFields strong { + overflow-wrap: anywhere; + font-size: 14px; + font-weight: 600; +} + +.civilIdExpired { + color: var(--rose, #e11d48); +} + +.civilIdWarning { + color: var(--amber, #d97706); +} + +.civilIdPhotos { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.civilIdPhotos div { + min-width: 0; + display: grid; + gap: 4px; +} + +.civilIdPhotos span { + font-size: 12px; + color: var(--ink-muted, #6b7280); +} + +.civilIdPhotos img { + max-width: 100%; + height: auto; + border: 1px solid var(--line); + border-radius: 4px; + aspect-ratio: 16 / 10; + object-fit: cover; +} + +.civilIdClearVerification { + padding-top: 8px; + border-top: 1px solid var(--line); +} + +.civilIdClearVerification button { + font-size: 13px; + padding: 6px 12px; + border: 1px solid var(--line); + border-radius: 4px; + background: var(--surface); + color: var(--ink); + cursor: pointer; +} + +.civilIdClearVerification button:hover { + background: var(--rose); + color: #fff; + border-color: var(--rose); +} + +[data-theme="dark"] .civilIdPanel { + border-color: var(--line); + background: var(--surface); + color: var(--ink); +} + +@media (max-width: 760px) { + .civilIdPhotos { + grid-template-columns: 1fr; + } +} + .candidateProfileColumns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/src/modules/candidates/CandidateProfile.tsx b/src/modules/candidates/CandidateProfile.tsx index 4eb80aa..5231e96 100644 --- a/src/modules/candidates/CandidateProfile.tsx +++ b/src/modules/candidates/CandidateProfile.tsx @@ -109,10 +109,12 @@ export function CandidateProfile({ - + + + {!compact && candidate.candidate_intro ? (
Profile intro @@ -166,6 +168,63 @@ function Fact({ label, value }: { label: string; value: string | number }) { ); } + +function CivilIdPanel({ candidate, viewerRole }: { candidate: NonNullable; viewerRole?: string }) { + if (!candidate.candidate_civil_id && !candidate.candidate_civil_expiry_date && !candidate.candidate_civil_photo_front && !candidate.candidate_civil_photo_back && !candidate.candidate_civil_need_verification) { + return null; + } + + const now = new Date(); + const expiryDate = candidate.candidate_civil_expiry_date ? new Date(candidate.candidate_civil_expiry_date) : null; + const daysUntilExpiry = expiryDate ? Math.ceil((expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)) : null; + const isExpired = daysUntilExpiry !== null && daysUntilExpiry < 0; + const isNearExpiry = daysUntilExpiry !== null && daysUntilExpiry >= 0 && daysUntilExpiry <= 90; + + const badges: string[] = []; + if (candidate.candidate_civil_need_verification) badges.push("Needs verification"); + if (isExpired) badges.push("Expired"); + else if (isNearExpiry) badges.push("Expires soon"); + + return ( +
+
+ Civil ID + {badges.length ? badges.join(" · ") : "On file"} +
+
+
+
+ ID Number + {candidate.candidate_civil_id || "—"} +
+
+ Expiry date + + {expiryDate ? formatDate(expiryDate) : "—"} + +
+
+ {candidate.candidate_civil_photo_front || candidate.candidate_civil_photo_back ? ( +
+ {candidate.candidate_civil_photo_front ? ( +
+ Photo (front) + Civil ID front +
+ ) : null} + {candidate.candidate_civil_photo_back ? ( +
+ Photo (back) + Civil ID back +
+ ) : null} +
+ ) : null} +
+
+ ); +} + function PanelHeader({ title, count }: { title: string; count: number }) { return (
From 0a9a24c0c8b0ac254d66a2f23492da9b9984593a Mon Sep 17 00:00:00 2001 From: Khalid Al-Mutawa Date: Fri, 22 May 2026 04:01:01 +0700 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20resolve=20TS=20errors=20on=20PR=20#3?= =?UTF-8?q?7=20=E2=80=94=20rejection=5Freason=20query=20and=20WorkLogActio?= =?UTF-8?q?n=20casts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/modules/candidates/WorkLogStaffActions.tsx | 4 ++-- src/modules/workspace/data.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/candidates/WorkLogStaffActions.tsx b/src/modules/candidates/WorkLogStaffActions.tsx index ee8347a..ccec249 100644 --- a/src/modules/candidates/WorkLogStaffActions.tsx +++ b/src/modules/candidates/WorkLogStaffActions.tsx @@ -54,7 +54,7 @@ export function WorkLogStaffActions({ if (mode === "reject") { return ( -
+