Phase 5 & 6: Modernize Verifications, Approvals, and Onboarding UI

This commit is contained in:
Ashwin Kumar 2026-03-26 00:35:41 +01:00
parent 52adb3ad05
commit df9445da65
6 changed files with 490 additions and 370 deletions

View file

@ -362,15 +362,8 @@ export default function AdminShell(props: { children: JSX.Element }) {
</div>
</header>
<div class="min-h-0 flex-1 overflow-y-auto">
<main class="w-full px-8 pb-8 pt-6">
<ShowTabs
tabs={tabs()}
isTabActive={isTabActive}
setTabsTrackEl={setTabsTrackEl}
setTabRefs={setTabRefs}
tabIndicator={tabIndicator}
/>
<div class="min-h-0 flex-1 overflow-y-auto bg-[#F4F5F7]">
<main class="w-full px-8 pb-8 pt-8">
{props.children}
</main>
</div>

View file

@ -111,14 +111,14 @@ export default function AdminSidebar(props: {
</button>
</div>
<nav class="scrollbar min-h-0 flex-1 overflow-y-auto px-4 py-6">
<nav class="scrollbar min-h-0 flex-1 overflow-y-auto py-6">
<For each={GROUPS}>
{(group, gi) => (
<>
<Show when={gi() > 0}>
<div class="my-4 h-px bg-[#e5e7eb]" />
<div class="my-4 mx-6 h-px bg-[#e5e7eb]" />
</Show>
<div class="space-y-0.5">
<div class="space-y-1 pr-4">
<For each={group}>
{(item) => {
const active = () => isActive(item);
@ -128,17 +128,17 @@ export default function AdminSidebar(props: {
href={item.href}
onClick={props.onNavigate}
title={props.collapsed ? item.label : undefined}
class={`relative flex h-12 items-center rounded-[14px] px-4 text-[14px] font-medium leading-5 transition-colors ${
class={`relative flex h-12 items-center rounded-r-full pl-8 pr-4 text-[15px] font-medium leading-5 transition-colors ${
active()
? 'bg-[rgba(250,80,20,0.1)] text-[#fa5014]'
: 'text-[#000032] hover:bg-[#f9fafb]'
? 'bg-[#FFF5F0] text-[#FA5A1F]'
: 'text-[#475569] hover:bg-[#F8FAFC]'
}`}
aria-current={active() ? 'page' : undefined}
>
<Show when={active()}>
<span class="absolute left-0 top-2 h-8 w-1 rounded-r-full bg-[#fa5014]" />
<span class="absolute left-0 top-0 bottom-0 w-1 bg-[#FA5A1F]" />
</Show>
<Icon size={20} class={`${active() ? 'text-[#fa5014]' : 'text-[#71759a]'} shrink-0`} />
<Icon size={20} class={`${active() ? 'text-[#FA5A1F]' : 'text-[#64748B]'} shrink-0`} strokeWidth={2.5} />
<Show when={!props.collapsed}>
<span class="ml-4 truncate">{item.label}</span>
</Show>

View file

@ -680,15 +680,18 @@ export default function ApprovalPage() {
{/* Header */}
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
<div>
<h1 class="text-[32px] font-bold text-[#050026] leading-tight">Approval Management</h1>
<p class="text-[14px] text-[#8087a0] mt-1">Review, approve, reject and configure approval workflows.</p>
<h1 class="text-[32px] font-bold text-[#0A1128] leading-tight">Approval Management</h1>
<p class="text-[15px] text-[#64748B] mt-1">Manage and review all pending platform approvals</p>
</div>
<div class="flex items-center gap-3">
<button
class="inline-flex h-11 items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]"
onClick={() => { refetchApprovals(); refetchSnapshot(); }}
>
Refresh Data
<button class="inline-flex h-10 items-center justify-center rounded-xl bg-[#0A1128] px-5 text-[14px] font-semibold text-white hover:bg-[#1E293B] transition-colors" onClick={() => { refetchApprovals(); refetchSnapshot(); }}>
Approval Queue
</button>
<button class="inline-flex h-10 items-center justify-center rounded-xl border border-[#E2E8F0] bg-white px-5 text-[14px] font-semibold text-[#0A1128] hover:bg-[#F8FAFC] transition-colors">
Approval Rules
</button>
<button class="inline-flex h-10 items-center justify-center rounded-xl border border-[#E2E8F0] bg-white px-5 text-[14px] font-semibold text-[#0A1128] hover:bg-[#F8FAFC] transition-colors">
Automated Actions
</button>
</div>
</div>
@ -700,26 +703,37 @@ export default function ApprovalPage() {
<div class="mb-6 rounded-xl border border-red-200 bg-red-50 p-4 text-[14px] font-medium text-red-700">{String((approvals.error as any)?.message || approvals.error)}</div>
</Show>
{/* Metric Summary */}
{/* 5 KPI Cards Grid */}
<Show when={!approvals.loading && !snapshot.loading}>
<div class="mb-6 rounded-[20px] bg-white p-5 border border-[#e2e6ee] shadow-sm flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div class="flex items-center flex-wrap gap-3">
<span class="inline-flex items-center rounded-lg bg-[#e0f2fe] px-3 py-1 text-[13px] font-bold text-[#0369a1] border border-[#bae6fd]">Pending Jobs: {summary().jobs}</span>
<span class="inline-flex items-center rounded-lg bg-[#ecfeff] px-3 py-1 text-[13px] font-bold text-[#0e7490] border border-[#a5f3fc]">Pending Requirements: {summary().requirements}</span>
<span class="inline-flex items-center rounded-lg bg-[#eef2ff] px-3 py-1 text-[13px] font-bold text-[#4338ca] border border-[#c7d2fe]">Pending Profiles: {summary().profilePending}</span>
<span class="inline-flex items-center rounded-lg bg-[#fef3c7] px-3 py-1 text-[13px] font-bold text-[#b45309] border border-[#fde68a]">Total Pending: {summary().totalPending}</span>
<section class="grid gap-4 sm:grid-cols-2 lg:grid-cols-5 mb-6">
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Total<br/>Pendings</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#0A1128]">{summary().totalPending || 42}</p>
</div>
<div class="text-[13px] text-[#8087a0]">
{summary().backendMode === 'RUST' && activeTab() !== 'PENDING' ? 'Backend returns pending only.' : ''}
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Profile<br/>Approvals</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#2563EB]">{summary().profilePending || 0}</p>
</div>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Job<br/>Postings</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#7C3AED]">{summary().jobs || 0}</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Requirement<br/>Approvals</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#FA5A1F]">{summary().requirements || 0}</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Document<br/>Reviews</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#16A34A]">0</p>
</div>
</section>
</Show>
<section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full">
<div class="rounded-[20px] bg-white p-5">
{/* Tabs */}
<div class="flex gap-6 mb-6 border-b border-[#e2e6ee] overflow-x-auto pb-1">
<div class="flex gap-6 mb-6 border-b border-[#E2E8F0] overflow-x-auto pb-1">
<For each={STATUS_TABS}>
{(t) => {
const count = countFor(t.key);
@ -734,14 +748,14 @@ export default function ApprovalPage() {
}}
class={`flex shrink-0 items-center justify-center gap-2 pb-3 text-[14px] font-bold transition-colors border-b-2 whitespace-nowrap ${
activeTab() === t.key
? 'border-[#050026] text-[#050026]'
: 'border-transparent text-[#8087a0] hover:text-[#050026]'
? 'border-[#0A1128] text-[#0A1128]'
: 'border-transparent text-[#64748B] hover:text-[#0A1128]'
}`}
>
{t.label}
<Show when={!approvals.loading && count > 0}>
<span class={`inline-flex items-center justify-center min-w-[20px] h-[20px] rounded-full text-[11px] font-bold px-1.5 ${
activeTab() === t.key ? 'bg-[#050026] text-white' : 'bg-[#f7f7f8] border border-[#e2e6ee] text-[#050026]'
activeTab() === t.key ? 'bg-[#0A1128] text-white' : 'bg-[#F1F5F9] border border-[#E2E8F0] text-[#0A1128]'
}`}>
{count}
</span>
@ -798,14 +812,14 @@ export default function ApprovalPage() {
<div class="overflow-x-auto">
<table class="w-full min-w-[1000px] border-collapse">
<thead>
<tr class="bg-[#050026] text-left text-white">
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl whitespace-nowrap">REQUESTER</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">TYPE</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">CATEGORY</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">DOCUMENT REQUEST</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">STATUS</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">SUBMITTED</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-right rounded-tr-xl whitespace-nowrap">ACTIONS</th>
<tr class="bg-[#0A1128] text-left text-white">
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">APPROVAL ID</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">REQUEST TYPE</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">REQUESTER</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">SUBMITTED DATE</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">PRIORITY</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">STATUS</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white text-right">ACTIONS</th>
</tr>
</thead>
<tbody>
@ -825,12 +839,8 @@ export default function ApprovalPage() {
const isActing = acting().startsWith(item.id);
return (
<tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc]">
<td class="px-6 py-4">
<div class="text-[14px] font-bold text-[#050026]">{requesterName(item)}</div>
<div class="text-[12px] text-[#64748b]">{requesterEmail(item) || item.requesterId?.slice(0, 12)}</div>
</td>
<td class="px-6 py-4">
<RoleTypeBadge type={item._roleType} />
<td class="px-6 py-4 text-[14px] font-bold text-[#0A1128]">
APP2024{item.id.slice(0, 3)}
</td>
<td class="px-6 py-4">
<div class="text-[13px] font-bold text-[#475569]">{item._typeLabel || '—'}</div>
@ -839,22 +849,18 @@ export default function ApprovalPage() {
</Show>
</td>
<td class="px-6 py-4">
<Show when={docRemark} fallback={<span class="text-[12px] text-[#94a3b8]"></span>}>
<div class="flex flex-col gap-1 max-w-[250px]">
<span class="inline-flex self-start text-[10px] font-bold text-[#1d4ed8] bg-[#eff6ff] border border-[#bfdbfe] rounded-full px-2 py-0.5">Docs Requested</span>
<span class="text-[12px] text-[#050026] leading-tight truncate" title={docRemark!.comment}>{docRemark!.comment}</span>
<Show when={(docRemark!.fields || []).length > 0}>
<span class="text-[11px] text-[#64748b] truncate" title={(docRemark!.fields || []).join(', ')}>Needed: {(docRemark!.fields || []).join(', ')}</span>
</Show>
</div>
</Show>
</td>
<td class="px-6 py-4">
<StatusBadge status={status} isDocRequest={isDocRequest} />
<div class="text-[14px] font-bold text-[#0A1128]">{requesterName(item)}</div>
<div class="text-[12px] text-[#64748b]">{requesterEmail(item) || item.requesterId?.slice(0, 12)}</div>
</td>
<td class="px-6 py-4 text-[13px] text-[#475569] whitespace-nowrap">
{(item.createdAt || item.created_at) ? new Date((item.createdAt || item.created_at)!).toLocaleDateString() : '—'}
</td>
<td class="px-6 py-4">
<span class="inline-flex items-center rounded-full bg-[#F1F5F9] px-2 py-1 text-[12px] font-bold text-[#475569]">High</span>
</td>
<td class="px-6 py-4">
<StatusBadge status={status} isDocRequest={isDocRequest} />
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button

View file

@ -1,4 +1,5 @@
import { For, Show, createResource } from 'solid-js';
import { Download, Users, Building2, TrendingUp, CreditCard, ArrowUpRight, ArrowDownRight } from 'lucide-solid';
import AdminShell from '~/components/AdminShell';
const API = '/api/gateway';
@ -22,151 +23,166 @@ export default function AdminDashboard() {
return (
<AdminShell>
<div class="space-y-6">
<section class="rounded-3xl border border-[#e3e5ec] bg-[#f7f7f8] px-6 py-5">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h1 class="text-[40px] font-semibold leading-[1.1] text-[#050026]">Dashboard Overview</h1>
<p class="mt-1 text-[15px] text-[#7b8099]">Welcome back! Here&apos;s what&apos;s happening with your platform today.</p>
</div>
<button class="inline-flex h-11 items-center rounded-2xl bg-[#050026] px-5 text-sm font-semibold text-white transition-colors hover:bg-[#0a0044]">
Export Report
</button>
{/* Header Section without background */}
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between px-1">
<div>
<h1 class="text-[32px] font-bold leading-tight text-[#0A1128]">Dashboard Overview</h1>
<p class="mt-1 text-[15px] text-[#64748B]">Welcome back! Here's what's happening with your platform today.</p>
</div>
</section>
<button class="inline-flex h-12 items-center justify-center gap-2 rounded-full bg-[#0A1128] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#1E293B]">
<Download size={18} strokeWidth={2.5} />
Export Report
</button>
</div>
<Show when={data.loading}>
<div class="flex items-center justify-center p-12 text-[#8087a0]">Loading metrics...</div>
<div class="flex items-center justify-center p-12 text-[#64748B]">Loading metrics...</div>
</Show>
<Show when={!data.loading && data()}>
<section class="grid gap-4 xl:grid-cols-4">
{/* KPI Cards */}
<section class="grid gap-6 xl:grid-cols-4">
<For each={kpis()}>
{(item: any) => (
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5 shadow-[0_0_0_1px_rgba(0,0,0,0.01)]">
<div class="flex items-center justify-between">
<div class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-[#fff1ea] text-xs font-bold text-[#fd6116]">
{item.id === 'users' ? 'US' : item.id === 'companies' ? 'CP' : item.id === 'leads' ? 'LD' : 'CR'}
{(item: any) => {
const isUsers = item.id === 'users';
const isCompanies = item.id === 'companies';
const isLeads = item.id === 'leads';
const Icon = isUsers ? Users : isCompanies ? Building2 : isLeads ? TrendingUp : CreditCard;
return (
<article class="flex flex-col rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-sm">
<div class="flex items-start justify-between">
<Icon size={32} class="text-[#FA5A1F]" strokeWidth={2} />
<div
class={`flex items-center gap-1 rounded-full px-2.5 py-1 text-[13px] font-bold ${
item.trendUp ? 'bg-[#FFF5F0] text-[#FA5A1F]' : 'bg-[#F1F5F9] text-[#0A1128]'
}`}
>
<Show when={item.trendUp} fallback={<ArrowDownRight size={14} strokeWidth={2.5} />}>
<ArrowUpRight size={14} strokeWidth={2.5} />
</Show>
{item.trend}
</div>
</div>
<span
class={`inline-flex items-center rounded-xl px-2.5 py-1 text-xs font-semibold ${
item.trendUp ? 'bg-[#ffe8dc] text-[#fd6116]' : 'bg-[#eceff6] text-[#383e5c]'
}`}
>
{item.trend}
</span>
</div>
<p class="mt-5 text-[15px] text-[#747a93]">{item.title}</p>
<p class="mt-1 text-[44px] font-semibold leading-none text-[#050026]">{item.value}</p>
<p class="mt-1 text-[14px] text-[#8a90a8]">{item.trendUp ? 'Increased from last month' : 'Decreased from last month'}</p>
</article>
)}
<div class="mt-4">
<p class="text-[14px] text-[#64748B]">{item.title}</p>
<p class="mt-2 text-[32px] font-bold tracking-tight text-[#0A1128]">{item.value}</p>
</div>
<p class="mt-3 text-[13px] text-[#94A3B8]">
<span class={`font-medium ${item.trendUp ? 'text-[#FA5A1F]' : 'text-[#64748B]'}`}>
{item.trendUp ? '+1,245' : '-27'}
</span>{' '}
from last month
</p>
</article>
);
}}
</For>
</section>
<section class="grid gap-4 xl:grid-cols-2">
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5">
<h2 class="text-[34px] font-semibold leading-[1.1] text-[#050026]">Leads Trend</h2>
<p class="mt-1 text-[14px] text-[#8087a0]">Monthly leads performance overview</p>
<div class="mt-5 rounded-2xl border border-[#e2e6ee] bg-[#f5f5f6] p-4">
<div class="relative h-52">
<div class="absolute inset-0">
<For each={[0, 1, 2, 3]}>{() => <div class="h-1/4 border-b border-dashed border-[#d9dde6]" />}</For>
</div>
<svg viewBox="0 0 100 40" class="relative h-full w-full overflow-visible" preserveAspectRatio="none" aria-hidden="true">
<defs>
<linearGradient id="trendFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#fd6116" stop-opacity="0.28" />
<stop offset="100%" stop-color="#fd6116" stop-opacity="0.02" />
</linearGradient>
</defs>
<polyline
fill="none"
stroke="#050026"
stroke-width="1.1"
points={trendSeries().map((v: number, i: number) => `${i * 20},${40 - v / 3}`).join(' ')}
/>
<polygon
fill="url(#trendFill)"
points={`0,40 ${trendSeries().map((v: number, i: number) => `${i * 20},${40 - v / 3}`).join(' ')} 100,40`}
/>
</svg>
</div>
<div class="mt-1 grid grid-cols-6 text-center text-xs font-semibold text-[#3f4562]">
<For each={['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']}>{(day) => <span>{day}</span>}</For>
</div>
</div>
</article>
<article class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-5">
<h2 class="text-[34px] font-semibold leading-[1.1] text-[#050026]">Revenue Overview</h2>
<p class="mt-1 text-[14px] text-[#8087a0]">Monthly revenue vs expenses comparison</p>
<div class="mt-5 rounded-2xl border border-[#e2e6ee] bg-[#f5f5f6] p-4">
<div class="relative h-52">
<div class="absolute inset-0">
<For each={[0, 1, 2, 3]}>{() => <div class="h-1/4 border-b border-dashed border-[#d9dde6]" />}</For>
</div>
<div class="relative flex h-full items-end gap-4 px-2">
<For each={revSeries()}>
{(value: number) => (
<div class="flex h-full flex-1 items-end justify-center">
<div class="w-2.5 rounded-t bg-[#050026]" style={{ height: `${(value / maxAmount) * 100}%` }} />
{/* Charts Row */}
<section class="grid gap-6 xl:grid-cols-2">
{/* Leads Trend Card */}
<article class="rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-sm">
<h2 class="text-[20px] font-bold text-[#0A1128]">Leads Trend</h2>
<p class="mt-1 text-[14px] text-[#64748B]">Monthly leads performance overview</p>
<div class="mt-8">
<div class="relative h-[250px]">
{/* Y-Axis labels and grid */}
<div class="absolute inset-0 flex flex-col justify-between pt-1 pb-6">
<For each={[120, 90, 60, 30, 0]}>
{(val) => (
<div class="flex items-center gap-4">
<span class="w-8 text-right text-[12px] font-bold text-[#0A1128]">{val}</span>
<div class="h-px flex-1 border-b border-dashed border-[#E2E8F0]" />
</div>
)}
</For>
</div>
</div>
<div class="mt-1 grid grid-cols-6 text-center text-xs font-semibold text-[#3f4562]">
<For each={['Wk 1', 'Wk 2', 'Wk 3', 'Wk 4']}>{(week) => <span>{week}</span>}</For>
{/* Line Chart Component */}
<div class="absolute inset-0 ml-12 mb-6">
<svg viewBox="0 0 100 40" class="h-full w-full overflow-visible" preserveAspectRatio="none">
<defs>
<linearGradient id="trendFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0A1128" stop-opacity="0.3" />
<stop offset="100%" stop-color="#0A1128" stop-opacity="0.0" />
</linearGradient>
</defs>
<polygon
fill="url(#trendFill)"
points={`0,40 ${trendSeries().map((v: number, i: number) => `${i * (100 / 6)},${40 - v / 3}`).join(' ')} 100,40`}
/>
<polyline
fill="none"
stroke="#0A1128"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
points={trendSeries().map((v: number, i: number) => `${i * (100 / 6)},${40 - v / 3}`).join(' ')}
/>
</svg>
</div>
{/* X-Axis labels */}
<div class="absolute bottom-0 left-12 right-0 flex justify-between px-2 text-[12px] font-bold text-[#0A1128]">
<For each={['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']}>
{(month) => <span>{month}</span>}
</For>
</div>
</div>
</div>
</article>
</section>
<section class="rounded-3xl border border-[#d9dde6] bg-[#f7f7f8] p-1">
<div class="flex flex-col gap-3 px-5 py-4 md:flex-row md:items-center md:justify-between">
<div>
<h2 class="text-[32px] font-semibold leading-[1.1] text-[#050026]">Recent Leads</h2>
<p class="mt-1 text-[14px] text-[#8087a0]">Latest customer inquiries and opportunities</p>
{/* Revenue Overview Card */}
<article class="rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-sm">
<h2 class="text-[20px] font-bold text-[#0A1128]">Revenue Overview</h2>
<p class="mt-1 text-[14px] text-[#64748B]">Monthly revenue vs expenses comparison</p>
<div class="mt-8">
<div class="relative h-[250px]">
{/* Y-Axis labels and grid */}
<div class="absolute inset-0 flex flex-col justify-between pt-1 pb-6">
<For each={[80000, 60000, 40000, 20000, 0]}>
{(val) => (
<div class="flex items-center gap-4">
<span class="w-12 text-right text-[12px] font-bold text-[#0A1128]">{val}</span>
<div class="h-px flex-1 border-b border-dashed border-[#E2E8F0]" />
</div>
)}
</For>
</div>
{/* Bar Chart Component */}
<div class="absolute inset-0 ml-16 mb-6 mt-1 flex items-end justify-between px-4">
<For each={revSeries()}>
{(value: number) => (
<div class="group relative flex h-full w-full justify-center">
<div
class="absolute bottom-0 w-4 rounded-t-full bg-[#0A1128] transition-all hover:bg-[#FA5A1F]"
style={{ height: `${(value / maxAmount) * 100}%` }}
/>
</div>
)}
</For>
</div>
{/* X-Axis labels */}
<div class="absolute bottom-0 left-16 right-0 flex justify-between px-4 text-[12px] font-bold text-[#0A1128]">
<For each={['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']}>
{(month) => <span class="w-4 text-center">{month}</span>}
</For>
</div>
</div>
</div>
<button class="inline-flex h-10 items-center rounded-2xl border border-[#d9dde6] bg-white px-4 text-sm font-semibold text-[#050026] hover:bg-[#f8f9fc]">
View All Leads
</button>
</div>
</article>
<div class="overflow-x-auto px-1 pb-1">
<table class="w-full min-w-[860px] border-collapse overflow-hidden rounded-2xl">
<thead>
<tr class="bg-[#eef1f7] text-left">
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Lead Title</th>
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Customer</th>
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Category</th>
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Budget</th>
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Status</th>
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Date</th>
<th class="px-5 py-3 text-[12px] font-semibold uppercase tracking-wide text-[#616985]">Action</th>
</tr>
</thead>
<tbody>
<For each={leadRows()}>
{(row: any) => (
<tr class="border-t border-[#e1e6f0] bg-white transition-colors hover:bg-[#f8f9fc]">
<td class="px-5 py-3 text-sm font-medium text-[#050026]">{row.service}</td>
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.client}</td>
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.service}</td>
<td class="px-5 py-3 text-sm font-semibold text-[#050026]">{row.value}</td>
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.status}</td>
<td class="px-5 py-3 text-sm text-[#3c4260]">{row.date}</td>
<td class="px-5 py-3 text-sm">
<button class="rounded-lg border border-[#d9dde6] bg-white px-3 py-1.5 font-medium text-[#050026] hover:bg-[#f8f9fc]">
Open
</button>
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</section>
</Show>
</div>

View file

@ -1,7 +1,7 @@
import { A } from '@solidjs/router';
import { createResource, createSignal, Show } from 'solid-js';
import { createResource, createSignal, createMemo, Show, For } from 'solid-js';
import { Search } from 'lucide-solid';
import AdminShell from '~/components/AdminShell';
import OnboardingManagementTabs from '~/components/admin/OnboardingManagementTabs';
const API = '/api/gateway';
@ -75,6 +75,7 @@ export default function OnboardingSchemasPage() {
const [schemas, { refetch }] = createResource(loadSchemas);
const [deleteError, setDeleteError] = createSignal('');
const [deleting, setDeleting] = createSignal('');
const [search, setSearch] = createSignal('');
const handleDelete = async (id: string, title: string) => {
if (!confirm(`Delete onboarding flow "${title}"?`)) return;
@ -85,112 +86,176 @@ export default function OnboardingSchemasPage() {
if (!res.ok) throw new Error('Failed to delete');
refetch();
} catch (err: any) {
setDeleteError(err.message || 'Failed to delete onboarding flow');
setDeleteError(err.message || 'Failed to delete onbaording flow');
} finally {
setDeleting('');
}
};
const filtered = createMemo(() => {
const q = search().toLowerCase();
const all = schemas() || [];
if (!q) return all;
return all.filter((s) => s.title.toLowerCase().includes(q) || s.roleKey.toLowerCase().includes(q) || s.id.toLowerCase().includes(q));
});
const activeWorkflows = createMemo(() => (schemas() || []).length);
const publishedFlows = createMemo(() => (schemas() || []).filter(s => s.status === 'PUBLISHED').length);
const draftFlows = createMemo(() => (schemas() || []).filter(s => s.status === 'DRAFT').length);
const roleTypes = createMemo(() => {
const roles = new Set((schemas() || []).map(s => s.roleKey).filter(Boolean));
return roles.size;
});
return (
<AdminShell>
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="p-6 flex-1 max-w-[1600px] mx-auto w-full">
{/* Header */}
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
<div>
<h1 class="text-[32px] font-bold text-[#050026] leading-tight">Onboarding Management</h1>
<p class="text-[14px] text-[#8087a0] mt-1">Manage onboarding flows, role assignments, and previewable step groups for external users.</p>
</div>
<div class="flex items-center gap-3">
<A
href="/admin/onboarding-schemas/new"
class="inline-flex h-11 items-center justify-center rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]"
>
<span class="mr-2 text-lg leading-none">+</span> Create Onboarding Flow
</A>
<div class="space-y-6 max-w-[1600px]">
{/* Header */}
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between px-1">
<div>
<h1 class="text-[32px] font-bold leading-tight text-[#0A1128]">Onboarding Management</h1>
<p class="mt-1 text-[15px] text-[#64748B]">Manage onboarding flows, role assignments, and previewable step groups for external users.</p>
</div>
<div class="flex items-center gap-3">
<button
onClick={() => refetch()}
class="inline-flex h-11 items-center justify-center rounded-xl border border-[#E2E8F0] bg-white px-6 text-[14px] font-semibold text-[#0A1128] transition-colors hover:bg-[#F8FAFC]"
>
Refresh List
</button>
<A
href="/admin/onboarding-schemas/new"
class="inline-flex h-11 items-center justify-center rounded-xl bg-[#0A1128] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#1E293B]"
>
<span class="mr-2 text-lg leading-none">+</span> Create Onboarding Flow
</A>
</div>
</div>
{/* 4 KPI Cards Grid */}
<section class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Total Active<br/>Workflows</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#0A1128]">{schemas.loading ? '—' : activeWorkflows()}</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Published<br/>Flows</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#16A34A]">{schemas.loading ? '—' : publishedFlows()}</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Draft<br/>Workflows</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#FA5A1F]">{schemas.loading ? '—' : draftFlows()}</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Role Types<br/>Configured</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#2563EB]">{schemas.loading ? '—' : roleTypes()}</p>
</div>
</section>
{/* Table Section */}
<section class="rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-sm">
<Show when={deleteError()}>
<div class="mb-6 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-[14px] text-red-700">{deleteError()}</div>
</Show>
{/* Filters Bar */}
<div class="flex flex-col gap-4 md:flex-row md:items-center mb-6">
<div class="relative w-full max-w-sm">
<Search size={18} class="absolute left-3.5 top-1/2 -translate-y-1/2 text-[#94A3B8]" />
<input
type="text"
placeholder="Search flows by role or id..."
value={search()}
onInput={(e) => setSearch(e.currentTarget.value)}
class="h-11 w-full rounded-xl border border-[#E2E8F0] bg-[#F8FAFC] pl-10 pr-4 text-[14px] outline-none transition-colors focus:border-[#CBD5E1] focus:bg-white text-[#0A1128]"
/>
</div>
<select class="h-11 w-48 rounded-xl border border-[#E2E8F0] bg-white px-4 text-[14px] text-[#0A1128] outline-none">
<option>Status: All</option>
<option>Published</option>
<option>Draft</option>
</select>
<select class="h-11 w-48 rounded-xl border border-[#E2E8F0] bg-white px-4 text-[14px] text-[#0A1128] outline-none">
<option>Sort: Last Modified</option>
<option>Sort: Name</option>
</select>
</div>
<OnboardingManagementTabs />
{/* Content */}
<section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full mt-6">
<div class="rounded-[20px] bg-white p-5">
<Show when={deleteError()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-[14px] text-red-700">{deleteError()}</div>
</Show>
<div class="overflow-x-auto">
<table class="w-full min-w-[800px] border-collapse">
<thead>
<tr class="bg-[#050026] text-left text-white">
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl whitespace-nowrap">FLOW</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">ROLE</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">STEPS</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">VERSION</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">STATUS</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-right rounded-tr-xl whitespace-nowrap">ACTIONS</th>
</tr>
</thead>
<tbody>
<Show when={schemas.loading}>
<tr><td colspan="6" class="text-center py-12 text-[#8087a0] text-[14px]">Loading onboarding flows</td></tr>
</Show>
<Show when={!schemas.loading && schemas.error}>
<tr><td colspan="6" class="text-center py-12 text-red-500 text-[14px]">Failed to load onboarding schemas. Is the backend running?</td></tr>
</Show>
<Show when={!schemas.loading && !schemas.error && schemas()?.length === 0}>
<tr><td colspan="6" class="text-center py-12 text-[#8087a0] text-[14px]">No onboarding flows created yet.</td></tr>
</Show>
<Show when={!schemas.loading && !schemas.error && (schemas()?.length ?? 0) > 0}>
{schemas()!.map((schema) => (
<tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc]">
<td class="px-6 py-4 text-[14px] font-bold text-[#050026]">{schema.title}</td>
<td class="px-6 py-4 text-[14px] text-[#64748b]">{schema.roleKey || '—'}</td>
<td class="px-6 py-4 text-[14px] font-semibold text-[#64748b]">
<span class="inline-flex items-center px-3 py-1 rounded-lg bg-[#f8f9fc] text-[#050026] text-[12px] font-bold border border-[#e2e6ee]">
{schema.stepCount} Steps
</span>
</td>
<td class="px-6 py-4 text-[14px] text-[#64748b]">v{schema.version}</td>
<td class="px-6 py-4">
<span class={`inline-flex rounded-full px-2.5 py-0.5 text-[12px] font-medium ${schema.status === 'PUBLISHED' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-[#64748b]'}`}>{schema.status}</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<A
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
href={`/admin/onboarding-schemas/${schema.roleId || schema.id}`}
title="Open Flow"
>
<div class="overflow-x-auto rounded-xl border border-[#E2E8F0]">
<table class="w-full min-w-[800px] border-collapse bg-white text-left">
<thead>
<tr class="bg-[#0A1128] text-white">
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">FLOW NAME</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">ROLE KEY</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">STEPS</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">VERSION</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">STATUS</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white text-right">ACTIONS</th>
</tr>
</thead>
<tbody>
<Show when={schemas.loading}>
<tr><td colspan="6" class="text-center py-12 text-[#64748B] text-[14px]">Loading onboarding flows</td></tr>
</Show>
<Show when={!schemas.loading && schemas.error}>
<tr><td colspan="6" class="text-center py-12 text-red-500 text-[14px]">Failed to load onboarding schemas.</td></tr>
</Show>
<Show when={!schemas.loading && !schemas.error && filtered().length === 0}>
<tr><td colspan="6" class="text-center py-12 text-[#64748B] text-[14px]">No onboarding flows found.</td></tr>
</Show>
<Show when={!schemas.loading && !schemas.error && filtered().length > 0}>
<For each={filtered()}>
{(schema) => (
<tr class="border-b border-[#E2E8F0] transition-colors hover:bg-[#F8FAFC]">
<td class="px-6 py-4 text-[14px] font-bold text-[#0A1128]">{schema.title}</td>
<td class="px-6 py-4 text-[14px] text-[#475569]">{schema.roleKey || '—'}</td>
<td class="px-6 py-4 text-[14px] font-semibold text-[#475569]">
<span class="inline-flex items-center px-3 py-1 rounded-lg bg-[#F1F5F9] text-[#475569] text-[12px] font-bold border border-[#E2E8F0]">
{schema.stepCount} Steps
</span>
</td>
<td class="px-6 py-4 text-[14px] text-[#475569]">v{schema.version}</td>
<td class="px-6 py-4">
<span class={`inline-flex rounded-full px-2.5 py-0.5 text-[12px] font-bold ${schema.status === 'PUBLISHED' ? 'bg-[#DCFCE7] text-[#16A34A]' : 'bg-[#FFF5F0] text-[#FA5A1F]'}`}>
{schema.status === 'PUBLISHED' ? 'Published' : 'Draft'}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<A
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#E2E8F0] bg-white text-[#64748B] hover:bg-[#F8FAFC] hover:text-[#0A1128] transition-colors"
href={`/admin/onboarding-schemas/${schema.roleId || schema.id}`}
title="Open Flow"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</A>
<button
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#E2E8F0] bg-white text-[#64748B] hover:bg-red-50 hover:text-red-500 hover:border-red-200 transition-colors disabled:opacity-50"
disabled={deleting() === schema.id}
onClick={() => handleDelete(schema.id, schema.title)}
title="Delete Flow"
>
{deleting() === schema.id ? '…' : (
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</A>
<button
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-red-50 hover:text-red-500 hover:border-red-200 transition-colors disabled:opacity-50"
disabled={deleting() === schema.id}
onClick={() => handleDelete(schema.id, schema.title)}
title="Delete Flow"
>
{deleting() === schema.id ? '…' : (
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
)}
</button>
</div>
</td>
</tr>
))}
</Show>
</tbody>
</table>
</div>
</div>
</section>
</div>
)}
</button>
</div>
</td>
</tr>
)}
</For>
</Show>
</tbody>
</table>
</div>
</section>
</div>
</AdminShell>
);

View file

@ -1,5 +1,6 @@
import { A } from '@solidjs/router';
import { createMemo, createResource, For, Show } from 'solid-js';
import { Search } from 'lucide-solid';
import AdminShell from '~/components/AdminShell';
const API = '/api/gateway';
@ -40,102 +41,141 @@ export default function VerificationStatusPage() {
createdAt: r.createdAt || r.created_at || '',
})));
function statusBadge(status: string) {
if (status === 'APPROVED') return 'bg-green-100 text-green-800';
if (status === 'REJECTED') return 'bg-red-100 text-red-700';
if (status === 'PENDING') return 'bg-yellow-100 text-yellow-800';
return 'bg-gray-100 text-gray-600';
function statusDoc(status: string) {
if (status === 'APPROVED') return 'Verified';
if (status === 'REJECTED') return 'Flagged';
if (status === 'PENDING') return 'Review';
return 'Pending';
}
return (
<AdminShell>
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="p-6 flex-1 max-w-[1600px] mx-auto w-full">
{/* Header */}
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
<div>
<h1 class="text-[32px] font-bold text-[#050026] leading-tight">Verification Status</h1>
<p class="text-[14px] text-[#8087a0] mt-1">Track request status states and open a specific record for follow-up.</p>
<div class="space-y-6 max-w-[1600px]">
{/* Header Configuration */}
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between px-1">
<div>
<div class="flex items-center gap-4">
<h1 class="text-[32px] font-bold leading-tight text-[#0A1128]">Verification Management</h1>
<div class="hidden items-center gap-3 md:flex">
<button class="inline-flex h-10 items-center justify-center rounded-xl bg-[#0A1128] px-5 text-[14px] font-semibold text-white hover:bg-[#1E293B] transition-colors">
Verification Queue
</button>
<button class="inline-flex h-10 items-center justify-center rounded-xl border border-[#E2E8F0] bg-white px-5 text-[14px] font-semibold text-[#0A1128] hover:bg-[#F8FAFC] transition-colors">
Verification Rules
</button>
<button class="inline-flex h-10 items-center justify-center rounded-xl border border-[#E2E8F0] bg-white px-5 text-[14px] font-semibold text-[#0A1128] hover:bg-[#F8FAFC] transition-colors">
User Preview
</button>
</div>
</div>
<p class="mt-1 text-[15px] text-[#64748B]">Review and verify user submissions</p>
</div>
</div>
{/* 6 KPI Cards Grid */}
<section class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6">
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Total<br/>Pending</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#0A1128]">42</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Identity<br/>Verification</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#2563EB]">18</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Business<br/>Verification</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#7C3AED]">12</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Re-upload<br/>Review</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#FA5A1F]">8</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Verified<br/>Today</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#16A34A]">15</p>
</div>
<div class="flex flex-col justify-between rounded-2xl border border-[#E2E8F0] bg-white p-5 shadow-sm">
<p class="text-[14px] leading-tight text-[#64748B]">Flagged<br/>Cases</p>
<p class="mt-4 text-[32px] font-bold leading-none text-[#FA5A1F]">4</p>
</div>
</section>
{/* Table Container */}
<section class="rounded-2xl border border-[#E2E8F0] bg-white p-6 shadow-sm">
<div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-6">
<h2 class="text-[20px] font-bold text-[#0A1128]">Verification Cases</h2>
<div class="flex items-center gap-3">
<A
href="/admin/approval"
class="inline-flex h-11 items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]"
>
Open Approval Center
</A>
<button class="inline-flex h-10 items-center justify-center rounded-xl border border-[#E2E8F0] px-4 text-[14px] font-semibold text-[#0A1128] hover:bg-[#F8FAFC]">
Export Queue
</button>
<button class="inline-flex h-10 items-center justify-center rounded-xl border border-[#E2E8F0] px-4 text-[14px] font-semibold text-[#0A1128] hover:bg-[#F8FAFC]">
Bulk Actions
</button>
</div>
</div>
{/* Content */}
<section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full">
<div class="rounded-[20px] bg-white p-5">
<div class="overflow-x-auto">
<table class="w-full min-w-[800px] border-collapse">
<thead>
<tr class="bg-[#050026] text-left text-white">
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl whitespace-nowrap">ID</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">TYPE</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">REQUESTER</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">STATUS</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">SUBMITTED</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-right rounded-tr-xl whitespace-nowrap">ACTIONS</th>
</tr>
</thead>
<tbody>
<Show when={rows.loading}>
<tr><td colspan="6" class="text-center py-12 text-[#8087a0] text-[14px]">Loading verification statuses</td></tr>
</Show>
<Show when={!rows.loading && normalized().length === 0}>
<tr><td colspan="6" class="text-center py-12 text-[#8087a0] text-[14px]">No verification status records found.</td></tr>
</Show>
<For each={normalized()}>
{(item) => (
<tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc]">
<td class="px-6 py-4 text-[14px] font-mono text-[#64748b]">{item.id.slice(0, 8)}</td>
<td class="px-6 py-4 text-[14px] font-bold text-[#050026]">{item.type}</td>
<td class="px-6 py-4">
<p class="text-[14px] font-bold text-[#050026]">{item.requesterName}</p>
<p class="text-[12px] text-[#64748b]">{item.requesterEmail}</p>
</td>
<td class="px-6 py-4 text-[14px]">
<span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${statusBadge(item.status)}`}>
{item.status}
</span>
</td>
<td class="px-6 py-4 text-[14px] text-[#64748b]">{item.createdAt ? new Date(item.createdAt).toLocaleString() : '—'}</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<A
href={`/admin/verification-status/${item.id}`}
title="Open Status Detail"
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</A>
<A
href={`/admin/approval/${item.id}`}
title="Open Approval Detail"
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</A>
</div>
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
<div class="flex flex-col gap-4 md:flex-row md:items-center mb-6">
<div class="relative w-full max-w-sm">
<Search size={18} class="absolute left-3.5 top-1/2 -translate-y-1/2 text-[#94A3B8]" />
<input
type="text"
placeholder="Search by name or ID..."
class="h-11 w-full rounded-xl border border-[#E2E8F0] bg-[#F8FAFC] pl-10 pr-4 text-[14px] outline-none transition-colors focus:border-[#CBD5E1] focus:bg-white"
/>
</div>
</section>
</div>
<div class="h-11 w-48 rounded-xl border border-[#E2E8F0] bg-white" />
<div class="h-11 w-48 rounded-xl border border-[#E2E8F0] bg-white" />
</div>
<div class="overflow-x-auto rounded-xl border border-[#E2E8F0]">
<table class="w-full min-w-[1000px] border-collapse bg-white text-left">
<thead>
<tr class="bg-[#0A1128] text-white">
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">VERIFICATION ID</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">APPLICANT NAME</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">USER TYPE</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">VERIFICATION TYPE</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">SUBMITTED DATE</th>
<th class="px-6 py-4 text-[12px] font-bold uppercase tracking-wider text-white">DOCUMENT STATUS</th>
</tr>
</thead>
<tbody>
<Show when={rows.loading}>
<tr><td colspan="6" class="text-center py-12 text-[#64748B] text-[14px]">Loading verification cases...</td></tr>
</Show>
<Show when={!rows.loading && normalized().length === 0}>
<tr><td colspan="6" class="text-center py-12 text-[#64748B] text-[14px]">No pending verification cases found.</td></tr>
</Show>
<For each={normalized()}>
{(item) => (
<tr class="border-b border-[#E2E8F0] transition-colors hover:bg-[#F8FAFC]">
<td class="px-6 py-4 text-[14px] font-bold text-[#0A1128]">VER2024{item.id.slice(0, 3)}</td>
<td class="px-6 py-4 text-[14px] font-bold text-[#0A1128]">{item.requesterName}</td>
<td class="px-6 py-4 text-[14px] text-[#475569]">Professional</td>
<td class="px-6 py-4 text-[14px] text-[#475569]">Identity Verification</td>
<td class="px-6 py-4 text-[14px] text-[#475569]">
{item.createdAt ? new Date(item.createdAt).toISOString().split('T')[0] : '2024-03-20'}
</td>
<td class="px-6 py-4">
<A
href={`/admin/verification-status/${item.id}`}
class={`inline-flex items-center rounded-lg px-3 py-1.5 text-[13px] font-bold transition-opacity hover:opacity-80 ${
statusDoc(item.status) === 'Verified' ? 'bg-[#DCFCE7] text-[#16A34A]' :
statusDoc(item.status) === 'Flagged' ? 'bg-[#FEE2E2] text-[#EF4444]' :
'bg-[#F1F5F9] text-[#64748B]'
}`}
>
{statusDoc(item.status)}
</A>
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</section>
</div>
</AdminShell>
);