diff --git a/.gitignore b/.gitignore index a608dd1..95a42d0 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,5 @@ e2e/.env logs/ .vite/ e2e/env-defaults.local.json + +viewer-frontend/.data/ diff --git a/DEPLOYMENT_CHECKLIST.md b/DEPLOYMENT_CHECKLIST.md index 9244f06..f743a40 100644 --- a/DEPLOYMENT_CHECKLIST.md +++ b/DEPLOYMENT_CHECKLIST.md @@ -46,6 +46,11 @@ SMTP_PASS=your-mailbox-password SMTP_FROM_EMAIL=noreply@your-domain.com SMTP_FROM_NAME=PunimTag Viewer +# Optional: digest when pending face-IDs queue grows (same SMTP/Resend transport) +# ADMIN_NOTIFY_EMAIL=you@example.com +# PENDING_QUEUE_DIGEST_THRESHOLD=1 +# PENDING_QUEUE_DIGEST_COOLDOWN_MINUTES=60 + # Option B: Resend only (set EMAIL_PROVIDER=resend, or leave SMTP_* unset above) RESEND_API_KEY=re_xxx RESEND_FROM_EMAIL=onboarding@resend.dev diff --git a/ROADMAP.md b/ROADMAP.md index 13938ef..d78decd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -30,6 +30,9 @@ Living plan for product quality, auth/email reliability, and automation. - [x] **CI: `actions/upload-artifact@v4` pinned to `v3`** — v4 doesn't work against this Gitea/act runner's artifact backend; report upload on e2e failure was silently broken - [x] **CI: npm cache corruption retry** — `viewer-unit`/`admin-unit`/`e2e` all retry `npm ci` once after `npm cache clean --force` on first failure (shared act_runner cache has corrupted `@next/swc-linux-x64-musl` before, redding CI with no product bug) - [x] ROADMAP (this file) +- [x] **Person contact fields** — optional email/phone on identify (viewer + admin), pending approve UI, Modify person +- [x] **Approve Identified bulk select** — Select All Approve/Deny + Approve Next 10 +- [x] **Admin pending-queue digest email** — `ADMIN_NOTIFY_EMAIL` via `noreply@levkine.ca` SMTP (throttled) ## Next (near-term) diff --git a/admin-frontend/src/api/faces.ts b/admin-frontend/src/api/faces.ts index af9bcf0..09d4a4d 100644 --- a/admin-frontend/src/api/faces.ts +++ b/admin-frontend/src/api/faces.ts @@ -85,6 +85,8 @@ export interface IdentifyFaceRequest { middle_name?: string maiden_name?: string date_of_birth?: string + email?: string + phone?: string additional_face_ids?: number[] } diff --git a/admin-frontend/src/api/pendingIdentifications.ts b/admin-frontend/src/api/pendingIdentifications.ts index 2109dfa..3cf68f7 100644 --- a/admin-frontend/src/api/pendingIdentifications.ts +++ b/admin-frontend/src/api/pendingIdentifications.ts @@ -12,6 +12,8 @@ export interface PendingIdentification { middle_name?: string | null maiden_name?: string | null date_of_birth?: string | null + email?: string | null + phone?: string | null status: string created_at: string updated_at: string diff --git a/admin-frontend/src/api/people.ts b/admin-frontend/src/api/people.ts index 4a0712a..aae5d0e 100644 --- a/admin-frontend/src/api/people.ts +++ b/admin-frontend/src/api/people.ts @@ -7,6 +7,8 @@ export interface Person { middle_name?: string | null maiden_name?: string | null date_of_birth?: string | null + email?: string | null + phone?: string | null } export interface PeopleListResponse { @@ -30,6 +32,8 @@ export interface PersonCreateRequest { middle_name?: string maiden_name?: string date_of_birth?: string | null + email?: string | null + phone?: string | null } export interface PersonUpdateRequest { @@ -38,6 +42,8 @@ export interface PersonUpdateRequest { middle_name?: string maiden_name?: string date_of_birth?: string | null + email?: string | null + phone?: string | null } export const peopleApi = { diff --git a/admin-frontend/src/api/videos.ts b/admin-frontend/src/api/videos.ts index 3ad7bdc..e501093 100644 --- a/admin-frontend/src/api/videos.ts +++ b/admin-frontend/src/api/videos.ts @@ -50,6 +50,8 @@ export interface IdentifyVideoRequest { middle_name?: string maiden_name?: string date_of_birth?: string | null + email?: string | null + phone?: string | null } export interface IdentifyVideoResponse { diff --git a/admin-frontend/src/pages/ApproveIdentified.tsx b/admin-frontend/src/pages/ApproveIdentified.tsx index 139c15c..4aef725 100644 --- a/admin-frontend/src/pages/ApproveIdentified.tsx +++ b/admin-frontend/src/pages/ApproveIdentified.tsx @@ -90,6 +90,42 @@ export default function ApproveIdentified() { }) } + + const actionableIds = pendingIdentifications + .filter((p) => p.status !== 'approved') + .map((p) => p.id) + + const setDecisionForIds = (ids: number[], decision: 'approve' | 'deny') => { + setDecisions((prev) => { + const updated = { ...prev } + ids.forEach((id) => { + updated[id] = decision + }) + return updated + }) + } + + const handleSelectAllApprove = () => { + setDecisionForIds(actionableIds, 'approve') + } + + const handleSelectAllDeny = () => { + setDecisionForIds(actionableIds, 'deny') + } + + const handleApproveNextOnScreen = (limit = 10) => { + const ids = actionableIds.slice(0, limit) + if (ids.length === 0) { + alert('No pending identifications to approve.') + return + } + setDecisionForIds(ids, 'approve') + } + + const handleClearDecisions = () => { + setDecisions({}) + } + const handleSubmit = async () => { // Get all decisions that have been made, for pending or denied items (not approved) const decisionsList = Object.entries(decisions) @@ -267,6 +303,39 @@ export default function ApproveIdentified() { /> Include denied + {pendingIdentifications.filter((p) => p.status !== 'approved').length > 0 && ( + <> + + + + + + )}