fix: show real uploaded documents/profile data on verification review, use reference numbers
All checks were successful
build-and-release / build (push) Successful in 52s

The verification review page was rendering hardcoded mock documents and
mock profile data instead of the real API response, so admins never saw
what customers actually submitted. Wires it to the real documents/payload
fields. Also replaces raw UUID displays with the new human-readable
reference_number across verification, support, orders, and users.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar Sivakumar 2026-07-19 16:35:49 +05:30
parent 0e08cf9d6b
commit b208b37845
5 changed files with 86 additions and 24 deletions

View file

@ -19,6 +19,7 @@ function authHeaders(): Record<string, string> {
type Order = {
id: string;
reference_number: string;
payu_txnid?: string;
amount_inr: number;
tracecoins_credited: number;
@ -76,7 +77,7 @@ export default function OrderPage() {
let data = orders() ?? [];
if (q) {
data = data.filter((o) => {
const txn = (o.payu_txnid || o.id || '').toLowerCase();
const txn = (o.payu_txnid || o.reference_number || '').toLowerCase();
const email = (o.user_email || '').toLowerCase();
const pkg = (o.package_name || '').toLowerCase();
const status = (o.status || '').toLowerCase();
@ -109,7 +110,7 @@ export default function OrderPage() {
const exportCsv = () => {
const headers = ['Order', 'User Email', 'Package', 'TraceCoins', 'Total', 'Status', 'Created'];
const rows = filtered().map((item) => [
item.payu_txnid || item.id,
item.payu_txnid || item.reference_number,
item.user_email || '—',
item.package_name || '—',
item.tracecoins_credited ?? '—',
@ -224,7 +225,7 @@ export default function OrderPage() {
{(item) => (
<tr class="hover:bg-slate-50">
<td class="font-semibold text-slate-900" style="font-family:monospace">
{item.payu_txnid || item.id}
{item.payu_txnid || item.reference_number}
</td>
<td class="text-slate-500">{item.user_email || '—'}</td>
<td class="text-slate-500">{item.package_name || '—'}</td>

View file

@ -20,6 +20,7 @@ function authHeaders(contentType = false): Record<string, string> {
type SupportCase = {
id: string;
reference_number: string;
title: string;
description: string;
type:
@ -515,6 +516,9 @@ export default function SupportPage() {
<tr class="hover:bg-slate-50">
<td>
<div class="font-semibold text-slate-900">{item.title}</div>
<div style="font-size:11px;font-family:monospace;color:#94a3b8">
{item.reference_number}
</div>
<div style="font-size:12px;color:#64748b;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
{item.description}
</div>

View file

@ -119,7 +119,7 @@ export default function UsersManagementPage() {
const mapped: ExternalUserRecord[] = (Array.isArray(data) ? data : []).map((u: any) => ({
id: u.id,
userCode: u.id.slice(0, 8).toUpperCase(),
userCode: u.reference_number || String(u.id).slice(0, 8).toUpperCase(),
name:
u.first_name || u.last_name
? `${u.first_name || ""} ${u.last_name || ""}`.trim()

View file

@ -36,6 +36,17 @@ type DocRequestRow = {
note: string;
};
type SubmittedDocument = {
type: string;
value: string;
status: string;
};
const formatFieldLabel = (key: string) =>
String(key || '')
.replace(/_/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase());
type FieldRevision = {
id: string;
field: string;
@ -67,7 +78,10 @@ export default function VerificationReviewDetailPage() {
}
});
const [docs, setDocs] = createSignal<DocRequestRow[]>([
// Catalog of document types that can be requested from the applicant (used by the
// "Request Documents" action form). This is not the applicant's actual uploaded
// documents — those come from `verif().documents` via the `uploadedDocs` memo below.
const [requestCatalog, setRequestCatalog] = createSignal<DocRequestRow[]>([
{ key: 'identity', title: 'Identity Proof', hint: 'Passport, DL or National ID', enabled: true, reason: 'Expired', note: 'Please upload a current valid ID with clear expiry date.' },
{ key: 'address', title: 'Address Proof', hint: 'Utility bill or bank statement (last 3 months)', enabled: false, reason: 'Not Readable', note: '' },
{ key: 'portfolio', title: 'Professional Portfolio', hint: 'Link or PDF showcasing previous work', enabled: false, reason: 'Insufficient', note: '' },
@ -76,6 +90,30 @@ export default function VerificationReviewDetailPage() {
{ key: 'experience', title: 'Experience Certificate', hint: 'Employment letters from previous employers', enabled: false, reason: 'Missing', note: '' },
]);
// Real customer-uploaded documents, from the fetched verification's `documents` field.
const uploadedDocs = createMemo<SubmittedDocument[]>(() => {
const list = verif()?.documents;
return Array.isArray(list) ? list : [];
});
// Real submitted profile data, from the fetched verification's `payload` field.
const payloadFullName = createMemo(() => {
const payload = verif()?.payload ?? {};
const name = [payload.first_name, payload.last_name].filter(Boolean).join(' ').trim();
return name || payload.company_name || payload.full_name || '—';
});
const payloadEntries = createMemo<Array<[string, unknown]>>(() => {
const payload = verif()?.payload ?? {};
const skipKeys = new Set(['first_name', 'last_name', 'company_name', 'full_name', 'documents']);
return Object.entries(payload).filter(([key, value]) => {
if (skipKeys.has(key)) return false;
if (value == null || value === '') return false;
if (typeof value === 'object') return false;
return true;
});
});
const [deadline, setDeadline] = createSignal('2026-04-08');
const [notifyUser, setNotifyUser] = createSignal(true);
const [markActionRequired, setMarkActionRequired] = createSignal(true);
@ -111,10 +149,10 @@ export default function VerificationReviewDetailPage() {
return 'none';
});
const requestCount = createMemo(() => docs().filter((d) => d.enabled).length + revisionRows().length);
const requestCount = createMemo(() => requestCatalog().filter((d) => d.enabled).length + revisionRows().length);
const updateDocRow = (key: string, patch: Partial<DocRequestRow>) => {
setDocs((prev) => prev.map((row) => (row.key === key ? { ...row, ...patch } : row)));
setRequestCatalog((prev) => prev.map((row) => (row.key === key ? { ...row, ...patch } : row)));
};
const openRequestDocuments = () => {
@ -136,7 +174,7 @@ export default function VerificationReviewDetailPage() {
};
const sendDocumentRequest = async () => {
const selected = docs().filter((d) => d.enabled);
const selected = requestCatalog().filter((d) => d.enabled);
if (selected.length === 0) { setActionMsg('Select at least one document to request.'); return; }
const message = selected
.map((d) => `${d.title}${d.reason}${d.note ? ': ' + d.note : ''}`)
@ -283,7 +321,7 @@ export default function VerificationReviewDetailPage() {
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap">
<div>
<h1 style="margin:0;font-size:42px;line-height:1.08;font-weight:800;color:#111827">Review Submission: #{params.id}</h1>
<h1 style="margin:0;font-size:42px;line-height:1.08;font-weight:800;color:#111827">Review Submission: #{verif()?.reference_number ?? params.id}</h1>
<p style="margin:8px 0 0;font-size:14px;color:#6B7280">Applicant verification detail and action flow.</p>
</div>
@ -335,7 +373,7 @@ export default function VerificationReviewDetailPage() {
<div style="border:1px solid #E5E7EB;background:white;border-radius:14px;padding:14px">
<p style="margin:0;font-size:12px;font-weight:700;color:#64748B;letter-spacing:0.06em;text-transform:uppercase">Submission Summary</p>
<div style="margin-top:10px;display:grid;grid-template-columns:1fr auto;gap:6px 12px;font-size:13px;color:#374151">
<span>Sub ID</span><strong style="color:#111827">#{params.id}</strong>
<span>Sub ID</span><strong style="color:#111827">#{verif()?.reference_number ?? params.id}</strong>
<span>Date Submitted</span><strong style="color:#111827">2026-04-01</strong>
<span>Reviewer</span><strong style="color:#111827">Admin User</strong>
<span>Pending Requests</span><strong style="color:#111827">{requestCount()}</strong>
@ -369,7 +407,7 @@ export default function VerificationReviewDetailPage() {
<h3 style="margin:0;font-size:28px;font-weight:800;color:#111827">Request Documents</h3>
<p style="margin:8px 0 12px;font-size:13px;color:#64748B">Select missing or invalid documents and send one clear request.</p>
<div style="display:grid;gap:10px">
<For each={docs()}>
<For each={requestCatalog()}>
{(row) => (
<div style="border:1px solid #E5E7EB;border-radius:12px;background:#FAFAFA;padding:12px;display:grid;grid-template-columns:auto 1fr;gap:10px;align-items:flex-start">
<input type="checkbox" checked={row.enabled} onChange={(e) => updateDocRow(row.key, { enabled: e.currentTarget.checked })} style="margin-top:4px;width:16px;height:16px;accent-color:#FF5E13" />
@ -467,25 +505,42 @@ export default function VerificationReviewDetailPage() {
<div class="table-card" style="border-radius:14px;padding:14px">
<h3 style="margin:0 0 10px;font-size:24px;font-weight:800;color:#111827">Profile Data</h3>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
<div><p style="margin:0;font-size:11px;color:#9CA3AF;letter-spacing:0.04em;text-transform:uppercase">Full Name</p><p style="margin:6px 0 0;font-size:18px;font-weight:700;color:#111827">Sarah Jenkins</p></div>
<div><p style="margin:0;font-size:11px;color:#9CA3AF;letter-spacing:0.04em;text-transform:uppercase">Role</p><p style="margin:6px 0 0;font-size:18px;font-weight:700;color:#111827">Professional Photographer</p></div>
<div><p style="margin:0;font-size:11px;color:#9CA3AF;letter-spacing:0.04em;text-transform:uppercase">Full Name</p><p style="margin:6px 0 0;font-size:18px;font-weight:700;color:#111827">{payloadFullName()}</p></div>
<div><p style="margin:0;font-size:11px;color:#9CA3AF;letter-spacing:0.04em;text-transform:uppercase">Role</p><p style="margin:6px 0 0;font-size:18px;font-weight:700;color:#111827">{verif()?.role_key ?? roleLabel()}</p></div>
</div>
<p style="margin:14px 0 0;font-size:13px;line-height:1.6;color:#374151">Visual storyteller with a passion for capturing authentic moments. Specializing in high-end wedding and lifestyle photography with focus on natural lighting and candid emotions.</p>
<Show when={payloadEntries().length > 0} fallback={<p style="margin:14px 0 0;font-size:13px;color:#9CA3AF">No submitted profile data available.</p>}>
<div style="margin-top:14px;display:grid;grid-template-columns:1fr 1fr;gap:12px">
<For each={payloadEntries()}>
{([key, value]) => (
<div>
<p style="margin:0;font-size:11px;color:#9CA3AF;letter-spacing:0.04em;text-transform:uppercase">{formatFieldLabel(key)}</p>
<p style="margin:6px 0 0;font-size:13px;line-height:1.6;color:#374151">{String(value)}</p>
</div>
)}
</For>
</div>
</Show>
</div>
</Show>
<Show when={activeAction() === 'none' && tab() === 'documents'}>
<div class="table-card" style="border-radius:14px;overflow:hidden">
<table class="data-table w-full text-sm">
<thead><tr>{['Document', 'Current State', 'Action'].map((h) => <th>{h}</th>)}</tr></thead>
<thead><tr>{['Document', 'File', 'Status'].map((h) => <th>{h}</th>)}</tr></thead>
<tbody>
<For each={docs()}>{(row) => (
<tr>
<td><div><strong>{row.title}</strong><p style="margin:2px 0 0;font-size:12px;color:#64748B">{row.hint}</p></div></td>
<td>{row.enabled ? 'Requested' : 'Received'}</td>
<td><button type="button" onClick={openRequestDocuments} style="height:30px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;font-weight:700;color:#374151">Request</button></td>
</tr>
)}</For>
<Show when={uploadedDocs().length > 0} fallback={<tr><td colSpan={3} style="text-align:center;padding:24px;color:#94A3B8">No documents uploaded yet.</td></tr>}>
<For each={uploadedDocs()}>{(doc) => (
<tr>
<td><strong>{formatFieldLabel(doc.type)}</strong></td>
<td>
<Show when={doc.value} fallback={<span style="color:#9CA3AF"></span>}>
<a href={doc.value} target="_blank" rel="noopener noreferrer" style="color:#2563EB;text-decoration:underline;font-size:13px">View Document</a>
</Show>
</td>
<td>{formatFieldLabel(doc.status)}</td>
</tr>
)}</For>
</Show>
</tbody>
</table>
</div>

View file

@ -6,6 +6,7 @@ type VerificationPriority = 'HIGH' | 'MEDIUM' | 'LOW';
type VerificationRow = {
id: string;
reference_number: string;
applicantName: string;
requestType:
| 'Profile Approval'
@ -185,6 +186,7 @@ export default function VerificationManagementPage() {
return {
id: v.id,
reference_number: v.reference_number || v.id,
applicantName: v.user_name || 'Applicant',
requestType: (isJob ? 'Job Approval' : (isRequirement ? 'Service Seeker Requirement' : 'Profile Approval')) as VerificationRow['requestType'],
roleLabel: toTitle(v.role_key || 'User'),
@ -712,7 +714,7 @@ export default function VerificationManagementPage() {
const p = priorityUi(row.priority);
return (
<tr style="border-bottom:1px solid #F3F4F6" class="hover:bg-[#FAFAFA] transition-colors">
<td style="padding:12px 20px;font-size:12px;font-family:monospace;color:#6B7280">#{row.id}</td>
<td style="padding:12px 20px;font-size:12px;font-family:monospace;color:#6B7280">#{row.reference_number}</td>
<td style="padding:12px 20px;font-size:14px;color:#111827">{row.requestType}</td>
<td style="padding:12px 20px">
<div style="display:flex;flex-direction:column;gap:2px">
@ -788,7 +790,7 @@ export default function VerificationManagementPage() {
<div style="border-radius:16px;border:1px solid #E5E7EB;background:white;box-shadow:0 1px 4px rgba(0,0,0,0.06);overflow:hidden">
<div style="padding:20px 24px;border-bottom:1px solid #E5E7EB;display:flex;align-items:center;justify-content:space-between">
<div>
<h2 style="font-size:18px;font-weight:700;color:#111827">#{selectedRow()!.id}</h2>
<h2 style="font-size:18px;font-weight:700;color:#111827">#{selectedRow()!.reference_number}</h2>
<p style="margin-top:2px;font-size:13px;color:#6B7280">{selectedRow()!.requestType}</p>
</div>
<span style={`display:inline-flex;align-items:center;border-radius:9999px;border:1px solid ${statusUi(selectedRow()!.status).border};background:${statusUi(selectedRow()!.status).bg};color:${statusUi(selectedRow()!.status).text};padding:2px 10px;font-size:12px;font-weight:500`}>