style: apply consistent page header pattern across all admin routes

- Replace text-2xl font-bold with text-xl font-semibold in all page headers
- Replace bg-[#fd6216] orange buttons with bg-[#0a1d37] navy buttons
- Wrap all pages in -mx-6 -mt-6 flex flex-col layout for edge-to-edge headers
- Replace .field/.actions CSS classes with explicit Tailwind utility classes
- Apply data-table/table-card shared CSS classes to remaining list pages
- Remove duplicate tab bar from roles/index.tsx (AdminShell TAB_SETS handles it)
- Move Create Internal Role button to page header in roles/index.tsx

Pages updated: applications, modules, responses, verification-status,
company/create, company/[id], employees/[id]/edit, users/[id]/edit,
users/details/[id], roles/index, roles/[id]/index, roles/[id]/edit,
role-ui-configs, runtime-roles/[roleKey], onboarding-schemas/*,
external/internal-dashboard-management, approval/[id], approval,
jobs/[id], leads/[id], photographer/[id], requirements/[id],
kb/articles/[id], kb/articles/[id]/edit, verification/[id],
verification-status/[id], help/[id], help/support-bridge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-03-24 07:37:02 +01:00
parent 3b98609cb5
commit 33619a1b27
32 changed files with 811 additions and 585 deletions

View file

@ -4,8 +4,8 @@
{ {
"name": "admin-solid", "name": "admin-solid",
"runtimeExecutable": "sh", "runtimeExecutable": "sh",
"runtimeArgs": ["-c", "cd /Users/ashwin/workspace/nxtgauge-admin-solid && npm run dev -- --port 3002"], "runtimeArgs": ["-c", "cd /Users/ashwin/workspace/nxtgauge-admin-solid && npm run dev -- --port 3020 --host"],
"port": 3002 "port": 3020
} }
] ]
} }

View file

@ -75,86 +75,109 @@ export default function ApplicationsPage() {
return requirements().find((item) => item.id === id); return requirements().find((item) => item.id === id);
} }
function statusClass(status: string): string { function statusBadge(status: string) {
if (status === 'ACCEPTED') return 'status-approved'; if (status === 'ACCEPTED') return 'bg-green-100 text-green-800';
if (status === 'REJECTED' || status === 'WITHDRAWN') return 'status-rejected'; if (status === 'REJECTED' || status === 'WITHDRAWN') return 'bg-red-100 text-red-700';
return 'status-pending'; return 'bg-yellow-100 text-yellow-800';
} }
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<h1 class="text-2xl font-bold text-gray-900">Applications</h1>
<p class="mt-1 text-sm text-gray-500">Review submitted applications and update acceptance status.</p>
</div>
<Show when={payload.loading}> {/* ── Page header ── */}
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading applications...</p></div> <div class="bg-white border-b border-gray-200 px-6 py-4">
</Show> <h1 class="text-xl font-semibold text-gray-900">Applications</h1>
<p class="text-sm text-gray-500 mt-0.5">Review submitted applications and update acceptance status.</p>
</div>
<Show when={error()}> {/* ── Content ── */}
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div> <div class="p-6">
</Show> <Show when={error()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show>
<Show when={!payload.loading && applications().length === 0}> <Show when={payload.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">No applications found.</p></div> <div class="table-card">
</Show> <p class="py-10 text-center text-sm text-slate-400">Loading applications</p>
</div>
</Show>
<div class="list-grid" style="grid-template-columns:1fr;gap:14px"> <Show when={!payload.loading && applications().length === 0}>
<For each={applications()}> <div class="table-card">
{(app) => { <p class="py-10 text-center text-sm text-slate-400">No applications found.</p>
const req = createMemo(() => requirementFor(app.requirementId)); </div>
return ( </Show>
<article class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap"> <div class="flex flex-col gap-4">
<div> <For each={applications()}>
<h2 style="margin:0;font-size:19px">{req()?.title || 'Unknown Requirement'}</h2> {(app) => {
<p class="notice" style="margin:6px 0 0">ID: {app.id}</p> const req = createMemo(() => requirementFor(app.requirementId));
return (
<div class="rounded-xl border border-gray-200 bg-white shadow-sm p-5">
<div class="flex items-start justify-between gap-3 flex-wrap">
<div>
<h2 class="text-base font-semibold text-gray-900">{req()?.title || 'Unknown Requirement'}</h2>
<p class="text-xs text-slate-400 mt-1">ID: {app.id}</p>
</div>
<span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${statusBadge(app.status)}`}>{app.status}</span>
</div>
<div class="mt-3 rounded-lg bg-gray-50 px-4 py-3">
<p class="text-xs font-medium text-gray-500 mb-1">Message</p>
<p class="text-sm text-gray-700">{app.message || 'No message provided.'}</p>
</div>
<div class="mt-3 grid grid-cols-2 gap-3 sm:grid-cols-4">
<div>
<p class="text-xs text-gray-500">Quote</p>
<p class="text-sm font-medium text-gray-900"> {app.quote || 0}</p>
</div>
<div>
<p class="text-xs text-gray-500">Category</p>
<p class="text-sm font-medium text-gray-900">{req()?.profession || '—'}</p>
</div>
<div>
<p class="text-xs text-gray-500">Location</p>
<p class="text-sm font-medium text-gray-900">{req()?.location || '—'}</p>
</div>
<div>
<p class="text-xs text-gray-500">Applied On</p>
<p class="text-sm font-medium text-gray-900">{app.createdAt ? new Date(app.createdAt).toLocaleDateString() : '—'}</p>
</div>
</div>
<div class="mt-4 flex gap-2 flex-wrap">
<Show when={app.status === 'SUBMITTED' || app.status === 'SHORTLISTED'}>
<button
class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors"
disabled={busyId() === app.id}
onClick={() => updateStatus(app.id, 'ACCEPTED')}
>
Accept Bid
</button>
<button
class="rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={busyId() === app.id}
onClick={() => updateStatus(app.id, 'REJECTED')}
>
Decline
</button>
<button
class="rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
disabled={busyId() === app.id}
onClick={() => updateStatus(app.id, 'WITHDRAWN')}
>
Withdraw
</button>
</Show>
</div>
</div> </div>
<span class={`status-pill ${statusClass(app.status)}`}>{app.status}</span> );
</div> }}
</For>
<div class="sub-card"> </div>
<p class="kv-label">Message</p> </div>
<p class="kv-value" style="font-weight:500">{app.message || 'No message provided.'}</p>
</div>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2" style="margin-top:12px">
<div class="kv-item">
<p class="kv-label">Quote</p>
<p class="kv-value"> {app.quote || 0}</p>
</div>
<div class="kv-item">
<p class="kv-label">Category</p>
<p class="kv-value">{req()?.profession || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Location</p>
<p class="kv-value">{req()?.location || '—'}</p>
</div>
<div class="kv-item">
<p class="kv-label">Applied On</p>
<p class="kv-value">{app.createdAt ? new Date(app.createdAt).toLocaleDateString() : '—'}</p>
</div>
</div>
<div class="actions">
<Show when={app.status === 'SUBMITTED'}>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button>
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button>
</Show>
<Show when={app.status === 'SHORTLISTED'}>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'ACCEPTED')}>Accept Bid</button>
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'REJECTED')}>Decline</button>
</Show>
<Show when={app.status === 'SUBMITTED' || app.status === 'SHORTLISTED'}>
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" disabled={busyId() === app.id} onClick={() => updateStatus(app.id, 'WITHDRAWN')}>Withdraw</button>
</Show>
</div>
</article>
);
}}
</For>
</div> </div>
</AdminShell> </AdminShell>
); );

View file

@ -975,7 +975,7 @@ export default function ApprovalPage() {
<div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:16px"> <div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:16px">
<div /> <div />
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => setShowAddRule((v) => !v)}> <button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" onClick={() => setShowAddRule((v) => !v)}>
{showAddRule() ? 'Cancel' : '+ Add Rule'} {showAddRule() ? 'Cancel' : '+ Add Rule'}
</button> </button>
</div> </div>
@ -1010,7 +1010,7 @@ export default function ApprovalPage() {
</div> </div>
</div> </div>
<div class="actions" style="margin-top:14px"> <div class="actions" style="margin-top:14px">
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={submittingRule()} onClick={handleAddRule}> <button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" disabled={submittingRule()} onClick={handleAddRule}>
{submittingRule() ? 'Saving...' : 'Save Rule'} {submittingRule() ? 'Saving...' : 'Save Rule'}
</button> </button>
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setShowAddRule(false)}>Cancel</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setShowAddRule(false)}>Cancel</button>
@ -1096,7 +1096,7 @@ function ApprovalDetailPanel(props: {
}> }>
<div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:12px"> <div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:12px">
<div> <div>
<h2 class="text-2xl font-bold text-gray-900" style="font-size:18px">Approval Detail</h2> <h2 class="text-lg font-semibold text-gray-900">Approval Detail</h2>
<p class="mt-1 text-sm text-gray-500">{a()!._typeLabel || 'Request'}</p> <p class="mt-1 text-sm text-gray-500">{a()!._typeLabel || 'Request'}</p>
</div> </div>
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={props.onBack}> Back to List</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={props.onBack}> Back to List</button>

View file

@ -247,13 +247,15 @@ export default function ApprovalDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Submission Review</h1> <h1 class="text-xl font-semibold text-gray-900">Submission Review</h1>
<p class="mt-1 text-sm text-gray-500">Review a user's onboarding form submission and take action.</p> <p class="text-sm text-gray-500 mt-0.5">Review a user's onboarding form submission and take action.</p>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval"> Back to Approvals</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval"> Back to Approvals</A>
</div> </div>
<div class="p-6 flex-1">
<Show when={actionError()}> <Show when={actionError()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700" style="margin-bottom:12px">{actionError()}</div>
@ -389,6 +391,8 @@ export default function ApprovalDetailPage() {
</div> </div>
</Show> </Show>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -96,19 +96,21 @@ export default function CompanyDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">{name()}</h1> <h1 class="text-xl font-semibold text-gray-900">{name()}</h1>
<p class="mt-1 text-sm text-gray-500">{companyId()}</p> <p class="text-sm text-gray-500 mt-0.5">{companyId()}</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class={`status-pill ${isVerified() ? 'status-approved' : 'status-pending'}`}> <span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${isVerified() ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'}`}>
{isVerified() ? 'Verified - Can Post Jobs' : 'Not Verified'} {isVerified() ? 'Verified - Can Post Jobs' : 'Not Verified'}
</span> </span>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Back to Companies</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Back to Companies</A>
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/approval">Open Approval Management</A> <A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" href="/admin/approval">Open Approval Management</A>
</div> </div>
</div> </div>
<div class="p-6 flex-1">
<Show when={bundle.loading}> <Show when={bundle.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading company...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading company...</p></div>
@ -294,6 +296,8 @@ export default function CompanyDetailPage() {
</div> </div>
</> </>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -50,62 +50,74 @@ export default function CreateCompanyPage() {
} }
}; };
const inputCls = 'w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]';
const labelCls = 'mb-1.5 block text-sm font-medium text-gray-700';
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div>
<h1 class="text-2xl font-bold text-gray-900">Create Company</h1> {/* ── Page header ── */}
<p class="mt-1 text-sm text-gray-500">Add a new organization profile to the admin company catalog.</p> <div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold text-gray-900">Create Company</h1>
<p class="text-sm text-gray-500 mt-0.5">Add a new organization profile to the admin company catalog.</p>
</div>
<A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">
Back to Companies
</A>
</div>
{/* ── Content ── */}
<div class="p-6">
<Show when={error()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show>
<form class="rounded-xl border border-gray-200 bg-white shadow-sm p-6" onSubmit={submit}>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label class={labelCls}>Company Name *</label>
<input class={inputCls} value={form().companyName} onInput={(e) => setField('companyName', e.currentTarget.value)} />
</div>
<div>
<label class={labelCls}>Company ID *</label>
<input class={inputCls} value={form().companyId} onInput={(e) => setField('companyId', e.currentTarget.value)} />
</div>
<div>
<label class={labelCls}>Industry</label>
<input class={inputCls} value={form().industry} onInput={(e) => setField('industry', e.currentTarget.value)} />
</div>
<div>
<label class={labelCls}>Website</label>
<input class={inputCls} value={form().websiteUrl} onInput={(e) => setField('websiteUrl', e.currentTarget.value)} />
</div>
<div>
<label class={labelCls}>Email *</label>
<input type="email" class={inputCls} value={form().email} onInput={(e) => setField('email', e.currentTarget.value)} />
</div>
<div>
<label class={labelCls}>Phone *</label>
<input class={inputCls} value={form().phone} onInput={(e) => setField('phone', e.currentTarget.value)} />
</div>
<div class="sm:col-span-2">
<label class={labelCls}>Address *</label>
<input class={inputCls} value={form().address} onInput={(e) => setField('address', e.currentTarget.value)} />
</div>
<div class="sm:col-span-2">
<label class={labelCls}>Description</label>
<textarea rows={3} class={inputCls} value={form().description} onInput={(e) => setField('description', e.currentTarget.value)} />
</div>
</div>
<div class="mt-6 flex justify-end gap-3 border-t border-gray-100 pt-5">
<A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Cancel</A>
<button class="rounded-lg bg-[#0a1d37] px-6 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors disabled:opacity-60" type="submit" disabled={saving()}>
{saving() ? 'Creating…' : 'Create Company'}
</button>
</div>
</form>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Back to Companies</A>
</div> </div>
<Show when={error()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show>
<form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={submit}>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field">
<label>Company Name *</label>
<input value={form().companyName} onInput={(e) => setField('companyName', e.currentTarget.value)} />
</div>
<div class="field">
<label>Company ID *</label>
<input value={form().companyId} onInput={(e) => setField('companyId', e.currentTarget.value)} />
</div>
<div class="field">
<label>Industry</label>
<input value={form().industry} onInput={(e) => setField('industry', e.currentTarget.value)} />
</div>
<div class="field">
<label>Website</label>
<input value={form().websiteUrl} onInput={(e) => setField('websiteUrl', e.currentTarget.value)} />
</div>
<div class="field">
<label>Email *</label>
<input type="email" value={form().email} onInput={(e) => setField('email', e.currentTarget.value)} />
</div>
<div class="field">
<label>Phone *</label>
<input value={form().phone} onInput={(e) => setField('phone', e.currentTarget.value)} />
</div>
<div class="field" style="grid-column:1/-1">
<label>Address *</label>
<input value={form().address} onInput={(e) => setField('address', e.currentTarget.value)} />
</div>
<div class="field" style="grid-column:1/-1">
<label>Description</label>
<textarea rows={3} value={form().description} onInput={(e) => setField('description', e.currentTarget.value)} />
</div>
</div>
<div class="actions" style="justify-content:flex-end">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/company">Cancel</A>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Creating...' : 'Create Company'}
</button>
</div>
</form>
</AdminShell> </AdminShell>
); );
} }

View file

@ -1,5 +1,5 @@
import { A, useNavigate, useParams } from '@solidjs/router'; import { A, useNavigate, useParams } from '@solidjs/router';
import { createMemo, createResource, createSignal, Show } from 'solid-js'; import { createMemo, createResource, createSignal, For, Show } from 'solid-js';
import AdminShell from '~/components/AdminShell'; import AdminShell from '~/components/AdminShell';
const API = '/api/gateway'; const API = '/api/gateway';
@ -75,58 +75,74 @@ export default function EditEmployeePage() {
} }
}; };
const inputCls = 'w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]';
const labelCls = 'mb-1.5 block text-sm font-medium text-gray-700';
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div>
<h1 class="text-2xl font-bold text-gray-900">Edit Employee</h1> {/* ── Page header ── */}
<p class="mt-1 text-sm text-gray-500">Update internal employee profile and role assignment.</p> <div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold text-gray-900">Edit Employee</h1>
<p class="text-sm text-gray-500 mt-0.5">Update internal employee profile and role assignment.</p>
</div>
<A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/employees">
Back to Employees
</A>
</div>
{/* ── Content ── */}
<div class="p-6">
<Show when={error()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show>
<Show when={employee.loading}>
<div class="table-card">
<p class="py-10 text-center text-sm text-slate-400">Loading employee</p>
</div>
</Show>
<Show when={!employee.loading && !employee()}>
<div class="table-card">
<p class="py-10 text-center text-sm text-slate-400">Employee not found.</p>
</div>
</Show>
<Show when={employee()}>
<form class="rounded-xl border border-gray-200 bg-white shadow-sm p-6" onSubmit={submit}>
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label class={labelCls}>Full Name</label>
<input class={inputCls} value={name()} onInput={(e) => setName(e.currentTarget.value)} />
</div>
<div>
<label class={labelCls}>Email</label>
<input type="email" class={inputCls} value={email()} onInput={(e) => setEmail(e.currentTarget.value)} />
</div>
<div>
<label class={labelCls}>Role</label>
<select class={inputCls} value={roleId()} onChange={(e) => setRoleId(e.currentTarget.value)}>
<option value="">Select role</option>
<Show when={!roles.loading}>
<For each={roles() ?? []}>
{(r) => <option value={r.id}>{r.name}</option>}
</For>
</Show>
</select>
</div>
</div>
<div class="mt-6 flex justify-end gap-3 border-t border-gray-100 pt-5">
<A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/employees">Cancel</A>
<button class="rounded-lg bg-[#0a1d37] px-6 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors disabled:opacity-60" type="submit" disabled={saving()}>
{saving() ? 'Saving…' : 'Save Changes'}
</button>
</div>
</form>
</Show>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/employees">Back to Employees</A>
</div> </div>
<Show when={error()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show>
<Show when={employee.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading employee...</p></div>
</Show>
<Show when={!employee.loading && !employee()}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Employee not found.</p></div>
</Show>
<Show when={employee()}>
<form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={submit}>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
<div class="field">
<label>Full Name</label>
<input value={name()} onInput={(e) => setName(e.currentTarget.value)} />
</div>
<div class="field">
<label>Email</label>
<input type="email" value={email()} onInput={(e) => setEmail(e.currentTarget.value)} />
</div>
<div class="field">
<label>Role</label>
<select value={roleId()} onChange={(e) => setRoleId(e.currentTarget.value)}>
<option value="">Select role...</option>
<Show when={!roles.loading}>
{roles()?.map((r) => (
<option value={r.id}>{r.name}</option>
))}
</Show>
</select>
</div>
</div>
<div class="actions" style="justify-content:flex-end">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/employees">Cancel</A>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}>
{saving() ? 'Saving...' : 'Save Changes'}
</button>
</div>
</form>
</Show>
</AdminShell> </AdminShell>
); );
} }

View file

@ -127,7 +127,7 @@ function renderModuleContent(module: Module | null) {
<textarea rows={4} placeholder="Describe the request" style="width:100%;border:1px solid #cbd5e1;border-radius:12px;padding:10px 12px;font-size:13px;outline:none" /> <textarea rows={4} placeholder="Describe the request" style="width:100%;border:1px solid #cbd5e1;border-radius:12px;padding:10px 12px;font-size:13px;outline:none" />
</div> </div>
<div style="display:flex;justify-content:flex-end;margin-top:12px"> <div style="display:flex;justify-content:flex-end;margin-top:12px">
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="button">Submit</button> <button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" type="button">Submit</button>
</div> </div>
</div> </div>
); );
@ -454,13 +454,15 @@ export default function ExternalDashboardManagementPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">External Dashboard Management</h1> <h1 class="text-xl font-semibold text-gray-900">External Dashboard Management</h1>
<p class="mt-1 text-sm text-gray-500">Open one external dashboard at a time from the list below and edit it using simple tabs.</p> <p class="text-sm text-gray-500 mt-0.5">Open one external dashboard at a time from the list below and edit it using simple tabs.</p>
</div> </div>
<a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a> <a class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a>
</div> </div>
<div class="p-6 flex-1">
<div class="hidden" style="margin-bottom:14px"> <div class="hidden" style="margin-bottom:14px">
<a class="hidden" href="#builder">View Dashboards</a> <a class="hidden" href="#builder">View Dashboards</a>
@ -473,10 +475,10 @@ export default function ExternalDashboardManagementPage() {
<Show when={!selected()}> <Show when={!selected()}>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h2 class="text-2xl font-bold text-gray-900" style="font-size:20px">External Dashboard List</h2> <h2 class="text-lg font-semibold text-gray-900">External Dashboard List</h2>
<p class="mt-1 text-sm text-gray-500">Choose one external role dashboard to open in the builder, or create a new one.</p> <p class="mt-1 text-sm text-gray-500">Choose one external role dashboard to open in the builder, or create a new one.</p>
</div> </div>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={createDashboard} disabled={creating()}> <button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" onClick={createDashboard} disabled={creating()}>
{creating() ? 'Creating...' : 'Create External Dashboard'} {creating() ? 'Creating...' : 'Create External Dashboard'}
</button> </button>
</div> </div>
@ -532,7 +534,7 @@ export default function ExternalDashboardManagementPage() {
</div> </div>
<div class="builder-header-actions"> <div class="builder-header-actions">
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setSelectedId('')}>Back to List</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setSelectedId('')}>Back to List</button>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={saveSelected} disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" onClick={saveSelected} disabled={saving()}>
{saving() ? 'Saving...' : 'Save Dashboard'} {saving() ? 'Saving...' : 'Save Dashboard'}
</button> </button>
</div> </div>
@ -730,6 +732,8 @@ export default function ExternalDashboardManagementPage() {
</Show> </Show>
</Show> </Show>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -5,21 +5,21 @@ export default function HelpArticlePage() {
const params = useParams(); const params = useParams();
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div> <div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<h1 class="text-2xl font-bold text-gray-900">Help Article</h1> <div>
<p class="mt-1 text-sm text-gray-500">Legacy help article route preserved for migration compatibility.</p> <h1 class="text-xl font-semibold text-gray-900">Help Article</h1>
<p class="text-sm text-gray-500 mt-0.5">Legacy help article route preserved for migration compatibility.</p>
</div>
<A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/support">Back to Support</A>
</div>
<div class="p-6">
<div class="table-card p-5">
<p class="text-sm text-slate-600">Article ID: <strong>{params.id}</strong></p>
<p class="mt-2 text-sm text-slate-500">Detailed knowledge base article rendering is handled in the support/KB modules during this migration phase.</p>
</div>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/support">Back to Support</A>
</div> </div>
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<p class="notice" style="margin:0">
Article ID: <strong>{params.id}</strong>
</p>
<p class="notice" style="margin:8px 0 0">
Detailed knowledge base article rendering is handled in the support/KB modules during this migration phase.
</p>
</section>
</AdminShell> </AdminShell>
); );
} }

View file

@ -4,17 +4,18 @@ import AdminShell from '~/components/AdminShell';
export default function HelpSupportBridgePage() { export default function HelpSupportBridgePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<h1 class="text-2xl font-bold text-gray-900">Support Bridge</h1> <div class="bg-white border-b border-gray-200 px-6 py-4">
<p class="mt-1 text-sm text-gray-500" style="margin-top:8px"> <h1 class="text-xl font-semibold text-gray-900">Support Bridge</h1>
This legacy help bridge now routes through the unified Support Management module. <p class="text-sm text-gray-500 mt-0.5">This legacy help bridge now routes through the unified Support Management module.</p>
</p>
</div>
<section class="rounded-xl border border-gray-200 bg-white shadow-sm">
<div class="actions">
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/support">Open Support Management</A>
</div> </div>
</section> <div class="p-6">
<div class="table-card p-5 flex items-center gap-4">
<p class="text-sm text-slate-600 flex-1">Use the Support Management module to handle all support tickets and help requests.</p>
<A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" href="/admin/support">Open Support Management</A>
</div>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -413,13 +413,15 @@ export default function InternalDashboardManagementPage() {
// ---------- List view ---------- // ---------- List view ----------
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Internal Dashboard Management</h1> <h1 class="text-xl font-semibold text-gray-900">Internal Dashboard Management</h1>
<p class="mt-1 text-sm text-gray-500">Open one internal dashboard at a time from the list below and edit it using simple tabs.</p> <p class="text-sm text-gray-500 mt-0.5">Open one internal dashboard at a time from the list below and edit it using simple tabs.</p>
</div> </div>
<a class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a> <a class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin">Back to Dashboard</a>
</div> </div>
<div class="p-6 flex-1">
<div class="hidden" style="margin-bottom:14px"> <div class="hidden" style="margin-bottom:14px">
<a class="hidden" href="#builder">View Dashboards</a> <a class="hidden" href="#builder">View Dashboards</a>
@ -431,10 +433,10 @@ export default function InternalDashboardManagementPage() {
<Show when={!selected()}> <Show when={!selected()}>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h2 class="text-2xl font-bold text-gray-900" style="font-size:20px">Internal Dashboard List</h2> <h2 class="text-lg font-semibold text-gray-900">Internal Dashboard List</h2>
<p class="mt-1 text-sm text-gray-500">Choose one internal dashboard to open in the builder, or create a new dashboard for an internal role.</p> <p class="mt-1 text-sm text-gray-500">Choose one internal dashboard to open in the builder, or create a new dashboard for an internal role.</p>
</div> </div>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={createDashboard} disabled={creating()}> <button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" onClick={createDashboard} disabled={creating()}>
{creating() ? 'Creating...' : 'Create Internal Dashboard'} {creating() ? 'Creating...' : 'Create Internal Dashboard'}
</button> </button>
</div> </div>
@ -490,7 +492,7 @@ export default function InternalDashboardManagementPage() {
</div> </div>
<div class="builder-header-actions"> <div class="builder-header-actions">
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setSelectedId('')}>Back to List</button> <button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => setSelectedId('')}>Back to List</button>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={saveSelected} disabled={saving()}> <button class="inline-flex items-center rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" onClick={saveSelected} disabled={saving()}>
{saving() ? 'Saving...' : 'Save Dashboard'} {saving() ? 'Saving...' : 'Save Dashboard'}
</button> </button>
</div> </div>
@ -738,6 +740,8 @@ export default function InternalDashboardManagementPage() {
</Show> </Show>
</Show> </Show>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -51,13 +51,15 @@ export default function JobDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Job Management</h1> <h1 class="text-xl font-semibold text-gray-900">Job Management</h1>
<p class="mt-1 text-sm text-gray-500">Review one live backend job in the same detail-first style as other admin modules.</p> <p class="text-sm text-gray-500 mt-0.5">Review one live backend job in the same detail-first style as other admin modules.</p>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/jobs">Back to Jobs</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/jobs">Back to Jobs</A>
</div> </div>
<div class="p-6 flex-1">
<Show when={job.loading}> <Show when={job.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading job...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading job...</p></div>
@ -113,6 +115,8 @@ export default function JobDetailPage() {
</div> </div>
</section> </section>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -33,14 +33,15 @@ export default function KbArticleDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">KB Article Detail</h1> <h1 class="text-xl font-semibold text-gray-900">KB Article Detail</h1>
<p class="mt-1 text-sm text-gray-500">Metadata and safe content preview for this article.</p> <p class="text-sm text-gray-500 mt-0.5">Metadata and safe content preview for this article.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/kb/articles">Back to Articles</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/kb/articles">Back to Articles</A>
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/kb/articles/${params.id}/edit`}>Edit Article</A> <A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" href={`/admin/kb/articles/${params.id}/edit`}>Edit Article</A>
</div> </div>
</div> </div>
@ -72,6 +73,8 @@ export default function KbArticleDetailPage() {
</section> </section>
</div> </div>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -73,66 +73,72 @@ export default function KbArticleEditPage() {
} }
}; };
const inputCls = 'w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]';
const labelCls = 'mb-1.5 block text-sm font-medium text-gray-700';
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Edit KB Article</h1> <h1 class="text-xl font-semibold text-gray-900">Edit KB Article</h1>
<p class="mt-1 text-sm text-gray-500">Update article metadata, status, and content.</p> <p class="text-sm text-gray-500 mt-0.5">Update article metadata, status, and content.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/kb/articles/${params.id}`}>Back to Detail</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/kb/articles/${params.id}`}>Back to Detail</A>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/kb/articles">Back to Articles</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/kb/articles">Back to Articles</A>
</div> </div>
</div> </div>
<div class="p-6 flex-1">
<Show when={article.loading}> <Show when={article.loading}>
<section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading article...</p></section> <div class="table-card"><p class="py-10 text-center text-sm text-slate-400">Loading article</p></div>
</Show> </Show>
<Show when={!article.loading && !article()}> <Show when={!article.loading && !article()}>
<section class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Article not found.</p></section> <div class="table-card"><p class="py-10 text-center text-sm text-slate-400">Article not found.</p></div>
</Show> </Show>
<Show when={article() && loaded()}> <Show when={article() && loaded()}>
<form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={save}> <form class="rounded-xl border border-gray-200 bg-white shadow-sm p-6" onSubmit={save}>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div class="field"> <div>
<label>Title</label> <label class={labelCls}>Title</label>
<input value={title()} onInput={(e) => setTitle(e.currentTarget.value)} required /> <input class={inputCls} value={title()} onInput={(e) => setTitle(e.currentTarget.value)} required />
</div> </div>
<div class="field"> <div>
<label>Slug</label> <label class={labelCls}>Slug</label>
<input value={slug()} onInput={(e) => setSlug(e.currentTarget.value)} /> <input class={inputCls} value={slug()} onInput={(e) => setSlug(e.currentTarget.value)} />
</div> </div>
<div class="field"> <div>
<label>Category ID</label> <label class={labelCls}>Category ID</label>
<input value={categoryId()} onInput={(e) => setCategoryId(e.currentTarget.value)} /> <input class={inputCls} value={categoryId()} onInput={(e) => setCategoryId(e.currentTarget.value)} />
</div> </div>
<div class="field"> <div>
<label>Status</label> <label class={labelCls}>Status</label>
<select value={status()} onChange={(e) => setStatus(e.currentTarget.value)}> <select class={inputCls} value={status()} onChange={(e) => setStatus(e.currentTarget.value)}>
<option value="DRAFT">DRAFT</option> <option value="DRAFT">DRAFT</option>
<option value="PUBLISHED">PUBLISHED</option> <option value="PUBLISHED">PUBLISHED</option>
</select> </select>
</div> </div>
<div class="field" style="grid-column:1 / -1"> <div class="sm:col-span-2">
<label>Content</label> <label class={labelCls}>Content</label>
<textarea rows="16" value={content()} onInput={(e) => setContent(e.currentTarget.value)} /> <textarea rows="16" class={inputCls} value={content()} onInput={(e) => setContent(e.currentTarget.value)} />
</div> </div>
</div> </div>
<Show when={error()}> <Show when={error()}>
<p class="error-note">{error()}</p> <p class="mt-3 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</p>
</Show> </Show>
<div class="actions" style="justify-content:flex-end"> <div class="mt-6 flex justify-end border-t border-gray-100 pt-5">
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="submit" disabled={saving()}> <button class="rounded-lg bg-[#0a1d37] px-6 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors disabled:opacity-60" type="submit" disabled={saving()}>
{saving() ? 'Saving...' : 'Save Article'} {saving() ? 'Saving' : 'Save Article'}
</button> </button>
</div> </div>
</form> </form>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -73,13 +73,15 @@ export default function LeadDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Lead Detail</h1> <h1 class="text-xl font-semibold text-gray-900">Lead Detail</h1>
<p class="mt-1 text-sm text-gray-500">Review one lead and its linked requirement identifiers.</p> <p class="text-sm text-gray-500 mt-0.5">Review one lead and its linked requirement identifiers.</p>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/leads">Back to Leads</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/leads">Back to Leads</A>
</div> </div>
<div class="p-6 flex-1">
<Show when={bundle.loading}> <Show when={bundle.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading lead...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading lead...</p></div>
@ -161,6 +163,8 @@ export default function LeadDetailPage() {
</Show> </Show>
</section> </section>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -107,112 +107,149 @@ export default function ModulesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div>
<h1 class="text-2xl font-bold text-gray-900">Module Registry</h1> {/* ── Page header ── */}
<p class="mt-1 text-sm text-gray-500">Manage internal module definitions and activation state.</p> <div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold text-gray-900">Module Registry</h1>
<p class="text-sm text-gray-500 mt-0.5">Manage internal module definitions and activation state.</p>
</div>
<button
class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors shadow-sm"
onClick={() => openModal()}
>
Add Module
</button>
</div> </div>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" onClick={() => openModal()}>Add Module</button>
</div>
<Show when={error() && !isModalOpen()}> {/* ── Content ── */}
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div> <div class="p-6">
</Show> <Show when={error() && !isModalOpen()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show>
<Show when={modules.loading}> <div class="table-card">
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading modules...</p></div> <div class="overflow-x-auto">
</Show> <table class="data-table w-full text-sm">
<thead>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"> <tr>
<div class="overflow-x-auto"> <th>Name</th>
<table class="w-full text-sm"> <th>Key</th>
<thead> <th>Description</th>
<tr> <th>Status</th>
<th>Name</th> <th class="text-right">Actions</th>
<th>Key</th> </tr>
<th>Description</th> </thead>
<th>Status</th> <tbody>
<th class="text-right">Actions</th> <Show when={modules.loading}>
</tr> <tr><td colspan="5" class="py-10 text-center text-sm text-slate-400">Loading modules</td></tr>
</thead> </Show>
<tbody> <Show when={!modules.loading && (modules() || []).length === 0}>
<Show when={(modules() || []).length > 0} fallback={ <tr><td colspan="5" class="py-10 text-center text-sm text-slate-400">No modules found.</td></tr>
<tr><td colspan="5" class="notice" style="padding:16px">No modules found.</td></tr> </Show>
}> <For each={modules() || []}>
<For each={modules() || []}> {(item) => (
{(item) => ( <tr class="hover:bg-slate-50">
<tr> <td class="font-medium text-gray-900">{item.name}</td>
<td>{item.name}</td> <td><code class="text-xs bg-gray-100 px-1.5 py-0.5 rounded">{item.key}</code></td>
<td><code>{item.key}</code></td> <td class="text-slate-500">{item.description || '—'}</td>
<td>{item.description || '—'}</td> <td>
<td> <span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${item.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-700'}`}>
<span class={`status-pill ${item.isActive ? 'status-approved' : 'status-rejected'}`}> {item.isActive ? 'Active' : 'Inactive'}
{item.isActive ? 'Active' : 'Inactive'} </span>
</span> </td>
</td> <td>
<td class="text-right"> <div class="flex items-center justify-end gap-2">
<div class="flex items-center justify-end gap-1"> <button
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={() => openModal(item)}>Edit</button> class="rounded-lg border border-gray-200 px-3 py-1.5 text-xs font-medium text-gray-700 hover:bg-gray-50 transition-colors"
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" onClick={() => removeModule(item.id)}>Delete</button> onClick={() => openModal(item)}
</div> >
</td> Edit
</tr> </button>
)} <button
</For> class="rounded-lg border border-red-200 bg-red-50 px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-100 transition-colors"
</Show> onClick={() => removeModule(item.id)}
</tbody> >
</table> Delete
</button>
</div>
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</div>
</div> </div>
</div> </div>
{/* ── Modal ── */}
<Show when={isModalOpen()}> <Show when={isModalOpen()}>
<div class="modal-backdrop"> <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
<div class="modal"> <div class="w-full max-w-lg rounded-xl bg-white shadow-xl">
<h2 style="margin-top:0">{modalTitle()}</h2> <div class="border-b border-gray-200 px-6 py-4">
<form onSubmit={submitForm}> <h2 class="text-lg font-semibold text-gray-900">{modalTitle()}</h2>
<div class="field"> </div>
<label>Name</label> <form onSubmit={submitForm} class="px-6 py-5 space-y-4">
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700">Name *</label>
<input <input
value={form().name} value={form().name}
onInput={(event) => setForm((prev) => ({ ...prev, name: event.currentTarget.value }))} onInput={(event) => setForm((prev) => ({ ...prev, name: event.currentTarget.value }))}
placeholder="e.g. Job Board" placeholder="e.g. Job Board"
required required
class="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]"
/> />
</div> </div>
<div class="field"> <div>
<label>Key</label> <label class="mb-1.5 block text-sm font-medium text-gray-700">Key *</label>
<input <input
value={form().key} value={form().key}
onInput={(event) => setForm((prev) => ({ ...prev, key: event.currentTarget.value }))} onInput={(event) => setForm((prev) => ({ ...prev, key: event.currentTarget.value }))}
placeholder="e.g. manage_jobs" placeholder="e.g. manage_jobs"
required required
class="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]"
/> />
</div> </div>
<div class="field"> <div>
<label>Description</label> <label class="mb-1.5 block text-sm font-medium text-gray-700">Description</label>
<textarea <textarea
rows="3" rows="3"
value={form().description} value={form().description}
onInput={(event) => setForm((prev) => ({ ...prev, description: event.currentTarget.value }))} onInput={(event) => setForm((prev) => ({ ...prev, description: event.currentTarget.value }))}
placeholder="Short description..." placeholder="Short description..."
class="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]"
/> />
</div> </div>
<label style="display:flex;gap:8px;align-items:center;font-size:13px"> <label class="flex items-center gap-2 text-sm text-gray-700">
<input <input
type="checkbox" type="checkbox"
checked={form().isActive} checked={form().isActive}
onChange={(event) => setForm((prev) => ({ ...prev, isActive: event.currentTarget.checked }))} onChange={(event) => setForm((prev) => ({ ...prev, isActive: event.currentTarget.checked }))}
class="rounded"
/> />
Active Active
</label> </label>
<Show when={error()}> <Show when={error()}>
<p class="error-note">{error()}</p> <p class="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</p>
</Show> </Show>
<div class="actions" style="justify-content:flex-end"> <div class="flex justify-end gap-3 border-t border-gray-100 pt-4">
<button type="button" class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" onClick={closeModal}>Cancel</button> <button
<button type="submit" class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={submitting()}> type="button"
class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
onClick={closeModal}
>
Cancel
</button>
<button
type="submit"
class="rounded-lg bg-[#0a1d37] px-6 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors disabled:opacity-60"
disabled={submitting()}
>
{editing() ? 'Save Changes' : 'Create'} {editing() ? 'Save Changes' : 'Create'}
</button> </button>
</div> </div>

View file

@ -133,20 +133,21 @@ export default function OnboardingSchemaDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<OnboardingManagementTabs /> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Onboarding Management</h1> <h1 class="text-xl font-semibold text-gray-900">Onboarding Management</h1>
<p class="mt-1 text-sm text-gray-500">Open one onboarding form at a time, check if it is published, then update the role, questions, steps, and final success message.</p> <p class="text-sm text-gray-500 mt-0.5">Open one onboarding form at a time, check if it is published, then update the role, questions, steps, and final success message.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={saving()} onClick={() => void persist(true)}> <button class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" disabled={saving()} onClick={() => void persist(true)}>
Save Active Version Save Active Version
</button> </button>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/onboarding-schemas">Back to Onboarding Management</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/onboarding-schemas">Back to Onboarding Management</A>
</div> </div>
</div> </div>
<OnboardingManagementTabs />
<div class="p-6 flex-1">
<Show when={schema.loading}> <Show when={schema.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading onboarding flow...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading onboarding flow...</p></div>
@ -182,6 +183,8 @@ export default function OnboardingSchemaDetailPage() {
/> />
</> </>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -93,79 +93,80 @@ export default function OnboardingSchemasPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div>
<h1 class="text-2xl font-bold text-gray-900">Onboarding Management</h1> {/* ── Page header ── */}
<p class="mt-1 text-sm text-gray-500">Manage onboarding flows, role assignments, and previewable step groups for external users.</p> <div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div>
<h1 class="text-xl font-semibold text-gray-900">Onboarding Management</h1>
<p class="text-sm text-gray-500 mt-0.5">Manage onboarding flows, role assignments, and previewable step groups for external users.</p>
</div>
<A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors shadow-sm" href="/admin/onboarding-schemas/new">Create Onboarding Flow</A>
</div> </div>
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href="/admin/onboarding-schemas/new">Create Onboarding Flow</A>
</div>
<OnboardingManagementTabs /> <OnboardingManagementTabs />
<Show when={deleteError()}> {/* ── Content ── */}
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{deleteError()}</div> <div class="p-6">
</Show> <Show when={deleteError()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{deleteError()}</div>
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding: 0; overflow: hidden;">
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #e2e8f0">
<h2 style="margin:0;font-size:17px;font-weight:700">Onboarding Flows</h2>
<Show when={!schemas.loading}>
<span style="font-size:13px;color:#64748b">{schemas()?.length || 0} flows</span>
</Show> </Show>
</div>
<div class="overflow-x-auto"> <div class="table-card">
<table class="w-full text-sm"> <div class="overflow-x-auto">
<thead> <table class="data-table w-full text-sm">
<tr> <thead>
<th>Flow</th>
<th>Role</th>
<th>Steps</th>
<th>Version</th>
<th>Status</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<Show when={schemas.loading}>
<tr><td colspan="6" style="text-align:center;padding:32px;color:#64748b">Loading onboarding flows...</td></tr>
</Show>
<Show when={!schemas.loading && schemas.error}>
<tr><td colspan="6" style="text-align:center;padding:32px;color:#b91c1c">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" style="text-align:center;padding:32px;color:#94a3b8">No onboarding flows created yet.</td></tr>
</Show>
<Show when={!schemas.loading && !schemas.error && (schemas()?.length ?? 0) > 0}>
{schemas()!.map((schema) => (
<tr> <tr>
<td style="font-weight:600;color:#0f172a">{schema.title}</td> <th>Flow</th>
<td style="color:#475569">{schema.roleKey || '—'}</td> <th>Role</th>
<td style="color:#475569">{schema.stepCount}</td> <th>Steps</th>
<td style="color:#475569">v{schema.version}</td> <th>Version</th>
<td> <th>Status</th>
<span class={`inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600 ${schema.status === 'PUBLISHED' ? 'active' : ''}`}>{schema.status}</span> <th class="text-right">Actions</th>
</td>
<td>
<div class="flex items-center justify-end gap-1">
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/onboarding-schemas/${schema.roleId || schema.id}`} title="Open Flow">👁</A>
<button
class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm"
disabled={deleting() === schema.id}
onClick={() => handleDelete(schema.id, schema.title)}
title="Delete Flow"
>
{deleting() === schema.id ? '...' : '🗑'}
</button>
</div>
</td>
</tr> </tr>
))} </thead>
</Show> <tbody>
</tbody> <Show when={schemas.loading}>
</table> <tr><td colspan="6" class="py-10 text-center text-sm text-slate-400">Loading onboarding flows</td></tr>
</Show>
<Show when={!schemas.loading && schemas.error}>
<tr><td colspan="6" class="py-10 text-center text-sm text-red-500">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="py-10 text-center text-sm text-slate-400">No onboarding flows created yet.</td></tr>
</Show>
<Show when={!schemas.loading && !schemas.error && (schemas()?.length ?? 0) > 0}>
{schemas()!.map((schema) => (
<tr class="hover:bg-slate-50">
<td class="font-medium text-gray-900">{schema.title}</td>
<td class="text-slate-500">{schema.roleKey || '—'}</td>
<td class="text-slate-500">{schema.stepCount}</td>
<td class="text-slate-500">v{schema.version}</td>
<td>
<span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${schema.status === 'PUBLISHED' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600'}`}>{schema.status}</span>
</td>
<td>
<div class="flex items-center justify-end gap-1">
<A class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors text-sm" href={`/admin/onboarding-schemas/${schema.roleId || schema.id}`} title="Open Flow">👁</A>
<button
class="action-btn flex items-center justify-center border-red-100 bg-red-50 hover:bg-red-100 transition-colors text-sm"
disabled={deleting() === schema.id}
onClick={() => handleDelete(schema.id, schema.title)}
title="Delete Flow"
>
{deleting() === schema.id ? '…' : '🗑'}
</button>
</div>
</td>
</tr>
))}
</Show>
</tbody>
</table>
</div>
</div>
</div> </div>
</section> </div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -105,15 +105,16 @@ export default function NewOnboardingSchemaPage() {
return ( return (
<AdminShell> <AdminShell>
<OnboardingManagementTabs /> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div class="mb-6 flex items-start justify-between gap-4">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Create Onboarding Flow</h1> <h1 class="text-xl font-semibold text-gray-900">Create Onboarding Flow</h1>
<p class="mt-1 text-sm text-gray-500">Create one onboarding form at a time. Pick the role, choose the questions, set the steps, and write the final success message.</p> <p class="text-sm text-gray-500 mt-0.5">Create one onboarding form at a time. Pick the role, choose the questions, set the steps, and write the final success message.</p>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/onboarding-schemas">Back to Onboarding Management</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/onboarding-schemas">Back to Onboarding Management</A>
</div> </div>
<OnboardingManagementTabs />
<div class="p-6 flex-1">
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px"> <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px;margin-bottom:16px">
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Role</p><p class="kv-value">{roleKey().replace(/_/g, ' ').toUpperCase()}</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="kv-label">Role</p><p class="kv-value">{roleKey().replace(/_/g, ' ').toUpperCase()}</p></div>
@ -136,6 +137,8 @@ export default function NewOnboardingSchemaPage() {
onChange={handleChange} onChange={handleChange}
onSubmit={handleSubmit} onSubmit={handleSubmit}
/> />
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -50,17 +50,18 @@ export default function PhotographerDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Photographer Detail</h1> <h1 class="text-xl font-semibold text-gray-900">Photographer Detail</h1>
<p class="mt-1 text-sm text-gray-500">View profile snapshot and account metadata for one photographer.</p> <p class="text-sm text-gray-500 mt-0.5">View profile snapshot and account metadata for one photographer.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/photographer">Back to Photographer List</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/photographer">Back to Photographer List</A>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>Open User Detail</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>Open User Detail</A>
</div> </div>
</div> </div>
<div class="p-6 flex-1">
<Show when={profile.loading}> <Show when={profile.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading photographer...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading photographer...</p></div>
</Show> </Show>
@ -136,6 +137,8 @@ export default function PhotographerDetailPage() {
</div> </div>
</section> </section>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -40,13 +40,15 @@ export default function RequirementDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Requirement Request</h1> <h1 class="text-xl font-semibold text-gray-900">Requirement Request</h1>
<p class="mt-1 text-sm text-gray-500">Review full requirement request details before approval action.</p> <p class="text-sm text-gray-500 mt-0.5">Review full requirement request details before approval action.</p>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Back to Approval Management</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Back to Approval Management</A>
</div> </div>
<div class="p-6 flex-1">
<Show when={requirement.loading}> <Show when={requirement.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading requirement...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading requirement...</p></div>
@ -115,6 +117,8 @@ export default function RequirementDetailPage() {
</div> </div>
</section> </section>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -53,6 +53,13 @@ export default function ResponsesPage() {
return requirements().find((item) => item.id === id)?.title || id; return requirements().find((item) => item.id === id)?.title || id;
} }
function statusBadge(status: string) {
if (status === 'ACCEPTED') return 'bg-green-100 text-green-800';
if (status === 'REJECTED') return 'bg-red-100 text-red-700';
if (status === 'SHORTLISTED') return 'bg-blue-100 text-blue-700';
return 'bg-yellow-100 text-yellow-800';
}
async function transition(id: string, status: string) { async function transition(id: string, status: string) {
setError(''); setError('');
setBusyId(id); setBusyId(id);
@ -76,50 +83,86 @@ export default function ResponsesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<h1 class="text-2xl font-bold text-gray-900">Responses</h1>
<p class="mt-1 text-sm text-gray-500">Track professional responses and move them through shortlist, accept, or reject states.</p>
</div>
<Show when={payload.loading}> {/* ── Page header ── */}
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading responses...</p></div> <div class="bg-white border-b border-gray-200 px-6 py-4">
</Show> <h1 class="text-xl font-semibold text-gray-900">Responses</h1>
<Show when={error()}> <p class="text-sm text-gray-500 mt-0.5">Track professional responses and move them through shortlist, accept, or reject states.</p>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div> </div>
</Show>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"> {/* ── Content ── */}
<Show when={!payload.loading && responses().length === 0} fallback={ <div class="p-6">
<div class="list-grid" style="grid-template-columns:1fr;gap:12px"> <Show when={error()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
</Show>
<Show when={payload.loading}>
<div class="table-card">
<p class="py-10 text-center text-sm text-slate-400">Loading responses</p>
</div>
</Show>
<Show when={!payload.loading && responses().length === 0}>
<div class="table-card">
<p class="py-10 text-center text-sm text-slate-400">No responses yet.</p>
</div>
</Show>
<div class="flex flex-col gap-4">
<For each={responses()}> <For each={responses()}>
{(row) => ( {(row) => (
<div class="list-item"> <div class="rounded-xl border border-gray-200 bg-white shadow-sm p-5">
<p style="margin:0;font-weight:700;color:#0f172a">{requirementTitle(row.requirementId)}</p> <div class="flex items-start justify-between gap-3 flex-wrap">
<p class="notice" style="margin:6px 0 0">{row.message || 'No message'}</p> <p class="text-base font-semibold text-gray-900">{requirementTitle(row.requirementId)}</p>
<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap"> <span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${statusBadge(row.status)}`}>{row.status}</span>
<span class="meta-chip">Professional: {row.professionalName || row.professionalId}</span> </div>
<span class="meta-chip">Quote: {row.quote || 0}</span> <p class="mt-1.5 text-sm text-slate-500">{row.message || 'No message'}</p>
<span class={`status-pill ${row.status === 'ACCEPTED' ? 'status-approved' : row.status === 'REJECTED' ? 'status-rejected' : 'status-pending'}`}>{row.status}</span> <div class="mt-3 flex flex-wrap gap-2">
<span class="inline-flex items-center rounded-md bg-gray-100 px-2.5 py-0.5 text-xs text-gray-700">
Professional: {row.professionalName || row.professionalId}
</span>
<span class="inline-flex items-center rounded-md bg-gray-100 px-2.5 py-0.5 text-xs text-gray-700">
Quote: {row.quote || 0}
</span>
<Show when={row.createdAt}> <Show when={row.createdAt}>
<span class="meta-chip">{new Date(row.createdAt!).toLocaleDateString()}</span> <span class="inline-flex items-center rounded-md bg-gray-100 px-2.5 py-0.5 text-xs text-gray-700">
{new Date(row.createdAt!).toLocaleDateString()}
</span>
</Show> </Show>
</div> </div>
<div class="actions"> <div class="mt-4 flex gap-2 flex-wrap">
<Show when={row.status === 'SUBMITTED'}> <Show when={row.status === 'SUBMITTED'}>
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'SHORTLISTED')}>Shortlist</button> <button
class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
disabled={busyId() === row.id}
onClick={() => transition(row.id, 'SHORTLISTED')}
>
Shortlist
</button>
</Show> </Show>
<Show when={row.status === 'SUBMITTED' || row.status === 'SHORTLISTED'}> <Show when={row.status === 'SUBMITTED' || row.status === 'SHORTLISTED'}>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'ACCEPTED')}>Accept</button> <button
<button class="inline-flex items-center rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors" disabled={busyId() === row.id} onClick={() => transition(row.id, 'REJECTED')}>Reject</button> class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors"
disabled={busyId() === row.id}
onClick={() => transition(row.id, 'ACCEPTED')}
>
Accept
</button>
<button
class="rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-100 transition-colors"
disabled={busyId() === row.id}
onClick={() => transition(row.id, 'REJECTED')}
>
Reject
</button>
</Show> </Show>
</div> </div>
</div> </div>
)} )}
</For> </For>
</div> </div>
}> </div>
<p class="notice">No responses yet.</p>
</Show>
</div> </div>
</AdminShell> </AdminShell>
); );

View file

@ -76,10 +76,12 @@ export default function RoleUiConfigsViewPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="page-hero-card"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<h1 class="text-2xl font-bold text-gray-900">External Dashboard Management</h1> <div class="bg-white border-b border-gray-200 px-6 py-4">
<p class="mt-1 text-sm text-gray-500">Read-only view of the currently published external dashboard and runtime role configuration.</p> <h1 class="text-xl font-semibold text-gray-900">External Dashboard Management</h1>
<p class="text-sm text-gray-500 mt-0.5">Read-only view of the currently published external dashboard and runtime role configuration.</p>
</div> </div>
<div class="p-6">
<ExternalRoleTabs roleKey={roleKey()} /> <ExternalRoleTabs roleKey={roleKey()} />
@ -107,7 +109,7 @@ export default function RoleUiConfigsViewPage() {
<button <button
type="button" type="button"
onClick={() => navigate(`/admin/role-ui-configs?roleKey=${encodeURIComponent(item.roleKey)}`)} onClick={() => navigate(`/admin/role-ui-configs?roleKey=${encodeURIComponent(item.roleKey)}`)}
style={`text-align:left;border:1px solid ${isActive ? '#fd6216' : '#e2e8f0'};border-radius:12px;padding:12px;background:${isActive ? '#fff7ed' : '#fff'}`} style={`text-align:left;border:1px solid ${isActive ? '#0a1d37' : '#e2e8f0'};border-radius:12px;padding:12px;background:${isActive ? '#f0f4f9' : '#fff'}`}
> >
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px"> <div style="display:flex;align-items:center;justify-content:space-between;gap:8px">
<div> <div>
@ -222,6 +224,8 @@ export default function RoleUiConfigsViewPage() {
</Show> </Show>
</section> </section>
</div> </div>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -112,13 +112,15 @@ export default function EditInternalRolePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Edit Internal Role</h1> <h1 class="text-xl font-semibold text-gray-900">Edit Internal Role</h1>
<p class="mt-1 text-sm text-gray-500">Update role name, access areas, and permissions.</p> <p class="text-sm text-gray-500 mt-0.5">Update role name, access areas, and permissions.</p>
</div> </div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/roles/${params.id}`}>Back to Role</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/roles/${params.id}`}>Back to Role</A>
</div> </div>
<div class="p-6">
<nav class="hidden" aria-label="Role Management Navigation"> <nav class="hidden" aria-label="Role Management Navigation">
<A class="hidden" href="/admin/roles">Internal Roles</A> <A class="hidden" href="/admin/roles">Internal Roles</A>
@ -226,16 +228,18 @@ export default function EditInternalRolePage() {
</Show> </Show>
{/* Save */} {/* Save */}
<div style="display:flex;justify-content:flex-end;margin-top:8px"> <div class="flex justify-end mt-2">
<button <button
class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" class="rounded-lg bg-[#0a1d37] px-6 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors disabled:opacity-60"
onClick={handleSave} onClick={handleSave}
disabled={saving() || !roleName().trim()} disabled={saving() || !roleName().trim()}
> >
{saving() ? 'Saving...' : 'Save Changes'} {saving() ? 'Saving' : 'Save Changes'}
</button> </button>
</div> </div>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -43,18 +43,20 @@ export default function RoleDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Role Details</h1> <h1 class="text-xl font-semibold text-gray-900">Role Details</h1>
<p class="mt-1 text-sm text-gray-500">View role information and assigned permissions.</p> <p class="text-sm text-gray-500 mt-0.5">View role information and assigned permissions.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles">Back to List</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/roles">Back to List</A>
<Show when={data()?.role}> <Show when={data()?.role}>
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/roles/${params.id}/edit`}>Edit Role</A> <A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" href={`/admin/roles/${params.id}/edit`}>Edit Role</A>
</Show> </Show>
</div> </div>
</div> </div>
<div class="p-6">
<nav class="hidden" aria-label="Role Management Navigation"> <nav class="hidden" aria-label="Role Management Navigation">
<A class="hidden" href="/admin/roles">Internal Roles</A> <A class="hidden" href="/admin/roles">Internal Roles</A>
@ -132,6 +134,8 @@ export default function RoleDetailPage() {
</div> </div>
</div> </div>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -53,26 +53,10 @@ export default function InternalRolesPage() {
<div class="flex flex-col -mx-6 -mt-6 min-h-full"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
{/* ── Page header ── */} {/* ── Page header ── */}
<div class="bg-white border-b border-gray-200 px-6 py-4"> <div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<h1 class="text-xl font-semibold text-gray-900">Internal Role Management</h1> <div>
<p class="text-sm text-gray-500 mt-0.5">Manage internal employee roles and permissions.</p> <h1 class="text-xl font-semibold text-gray-900">Internal Role Management</h1>
</div> <p class="text-sm text-gray-500 mt-0.5">Manage internal employee roles and permissions.</p>
{/* ── Tab bar ── */}
<div class="bg-white border-b border-gray-200 px-6 flex items-center justify-between sticky top-0 z-10">
<div class="flex gap-8">
<A href="/admin/roles"
class="py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium">
Roles
</A>
<A href="/admin/roles/create"
class="py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors">
Create Role
</A>
<A href="/admin/roles/templates"
class="py-3 border-b-2 border-transparent text-gray-500 hover:text-gray-700 text-sm font-medium transition-colors">
View Roles
</A>
</div> </div>
<A <A
href="/admin/roles/create" href="/admin/roles/create"

View file

@ -152,18 +152,18 @@ export default function EditExternalRolePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">External Role Management</h1> <h1 class="text-xl font-semibold text-gray-900">External Role Management</h1>
<p class="mt-1 text-sm text-gray-500"> <p class="text-sm text-gray-500 mt-0.5">Update this external role with simple settings: pages, permissions, onboarding form, approvals, and limits.</p>
Update this external role with simple settings: pages, permissions, onboarding form, approvals, and limits.
</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/role-ui-configs?roleKey=${encodeURIComponent(roleKey())}`}>Open Inspector</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/role-ui-configs?roleKey=${encodeURIComponent(roleKey())}`}>Open Inspector</A>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/runtime-roles">Back to External Roles</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/runtime-roles">Back to External Roles</A>
</div> </div>
</div> </div>
<div class="p-6 flex-1">
<ExternalRoleTabs roleKey={roleKey()} /> <ExternalRoleTabs roleKey={roleKey()} />
@ -195,6 +195,8 @@ export default function EditExternalRolePage() {
onSubmit={handleSubmit} onSubmit={handleSubmit}
/> />
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -114,16 +114,18 @@ export default function EditUserPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Edit User</h1> <h1 class="text-xl font-semibold text-gray-900">Edit User</h1>
<p class="mt-1 text-sm text-gray-500">Update user profile, role assignment, and account status.</p> <p class="text-sm text-gray-500 mt-0.5">Update user profile, role assignment, and account status.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>View Details</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href={`/admin/users/details/${params.id}`}>View Details</A>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/users">Back to Users</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/users">Back to Users</A>
</div> </div>
</div> </div>
<div class="p-6">
<Show when={error()}> <Show when={error()}>
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div> <div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error()}</div>
@ -138,19 +140,19 @@ export default function EditUserPage() {
</Show> </Show>
<Show when={user()}> <Show when={user()}>
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:900px"> <section class="rounded-xl border border-gray-200 bg-white shadow-sm p-6 max-w-3xl">
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div class="field"> <div>
<label>Full Name</label> <label class="mb-1.5 block text-sm font-medium text-gray-700">Full Name</label>
<input value={name()} onInput={(e) => setName(e.currentTarget.value)} /> <input class="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]" value={name()} onInput={(e) => setName(e.currentTarget.value)} />
</div> </div>
<div class="field"> <div>
<label>Email</label> <label class="mb-1.5 block text-sm font-medium text-gray-700">Email</label>
<input type="email" value={email()} onInput={(e) => setEmail(e.currentTarget.value)} /> <input type="email" class="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]" value={email()} onInput={(e) => setEmail(e.currentTarget.value)} />
</div> </div>
<div class="field"> <div>
<label>Role</label> <label class="mb-1.5 block text-sm font-medium text-gray-700">Role</label>
<select value={roleId()} onChange={(e) => setRoleId(e.currentTarget.value)}> <select class="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]" value={roleId()} onChange={(e) => setRoleId(e.currentTarget.value)}>
<option value="">Select role</option> <option value="">Select role</option>
<Show when={!roles.loading}> <Show when={!roles.loading}>
{roles()?.map((r) => ( {roles()?.map((r) => (
@ -159,9 +161,9 @@ export default function EditUserPage() {
</Show> </Show>
</select> </select>
</div> </div>
<div class="field"> <div>
<label>Status</label> <label class="mb-1.5 block text-sm font-medium text-gray-700">Status</label>
<select value={status()} onChange={(e) => setStatus(e.currentTarget.value as 'ACTIVE' | 'INACTIVE' | 'PENDING')}> <select class="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] focus:ring-1 focus:ring-[#0a1d37]" value={status()} onChange={(e) => setStatus(e.currentTarget.value as 'ACTIVE' | 'INACTIVE' | 'PENDING')}>
<option value="ACTIVE">Active</option> <option value="ACTIVE">Active</option>
<option value="PENDING">Pending</option> <option value="PENDING">Pending</option>
<option value="INACTIVE">Inactive</option> <option value="INACTIVE">Inactive</option>
@ -169,14 +171,16 @@ export default function EditUserPage() {
</div> </div>
</div> </div>
<div class="actions" style="justify-content:flex-end"> <div class="mt-6 flex justify-end gap-3 border-t border-gray-100 pt-5">
<button class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={() => navigate('/admin/users')}>Cancel</button> <button class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" type="button" onClick={() => navigate('/admin/users')}>Cancel</button>
<button class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" type="button" onClick={save} disabled={submitting()}> <button class="rounded-lg bg-[#0a1d37] px-6 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors disabled:opacity-60" type="button" onClick={save} disabled={submitting()}>
{submitting() ? 'Saving...' : 'Save Changes'} {submitting() ? 'Saving' : 'Save Changes'}
</button> </button>
</div> </div>
</section> </section>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -161,16 +161,18 @@ export default function UserDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">{roleTitleLabel() ? `${roleTitleLabel()} Profile` : 'User Details'}</h1> <h1 class="text-xl font-semibold text-gray-900">{roleTitleLabel() ? `${roleTitleLabel()} Profile` : 'User Details'}</h1>
<p class="mt-1 text-sm text-gray-500">Review account profile and role registration data with approval status.</p> <p class="text-sm text-gray-500 mt-0.5">Review account profile and role registration data with approval status.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/users">Back to Users</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/users">Back to Users</A>
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/users/${params.id}/edit`}>Edit User</A> <A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" href={`/admin/users/${params.id}/edit`}>Edit User</A>
</div> </div>
</div> </div>
<div class="p-6 flex-1">
<Show when={bundle.loading}> <Show when={bundle.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading user...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading user...</p></div>
@ -266,6 +268,8 @@ export default function UserDetailPage() {
</section> </section>
</> </>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -40,62 +40,95 @@ export default function VerificationStatusPage() {
createdAt: r.createdAt || r.created_at || '', 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';
}
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div>
<h1 class="text-2xl font-bold text-gray-900">Verification Status</h1>
<p class="mt-1 text-sm text-gray-500">Track request status states and open a specific record for follow-up.</p>
</div>
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/approval">Open Approval Center</A>
</div>
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden"> {/* ── Page header ── */}
<div class="overflow-x-auto"> <div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<table class="w-full text-sm"> <div>
<thead> <h1 class="text-xl font-semibold text-gray-900">Verification Status</h1>
<tr> <p class="text-sm text-gray-500 mt-0.5">Track request status states and open a specific record for follow-up.</p>
<th>ID</th> </div>
<th>Type</th> <A
<th>Requester</th> class="rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
<th>Status</th> href="/admin/approval"
<th>Submitted</th> >
<th class="text-right">Actions</th> Open Approval Center
</tr> </A>
</thead>
<tbody>
<Show when={rows.loading}>
<tr><td colspan="6" style="text-align:center;padding:32px;color:#64748b">Loading verification statuses...</td></tr>
</Show>
<Show when={!rows.loading && normalized().length === 0}>
<tr><td colspan="6" style="text-align:center;padding:32px;color:#94a3b8">No verification status records found.</td></tr>
</Show>
<Show when={!rows.loading && normalized().length > 0}>
<For each={normalized()}>
{(item) => (
<tr>
<td style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:#64748b">{item.id.slice(0, 8)}...</td>
<td style="color:#475569">{item.type}</td>
<td>
<div style="font-weight:600;color:#0f172a">{item.requesterName}</div>
<div style="font-size:12px;color:#64748b">{item.requesterEmail}</div>
</td>
<td><span class="inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">{item.status}</span></td>
<td style="color:#475569">{item.createdAt ? new Date(item.createdAt).toLocaleString() : '—'}</td>
<td>
<div class="flex items-center justify-end gap-1">
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/verification-status/${item.id}`} title="Open Status Detail"></A>
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/approval/${item.id}`} title="Open Approval Detail">👁</A>
</div>
</td>
</tr>
)}
</For>
</Show>
</tbody>
</table>
</div> </div>
</section>
{/* ── Content ── */}
<div class="p-6">
<div class="table-card">
<div class="overflow-x-auto">
<table class="data-table w-full text-sm">
<thead>
<tr>
<th>ID</th>
<th>Type</th>
<th>Requester</th>
<th>Status</th>
<th>Submitted</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<Show when={rows.loading}>
<tr><td colspan="6" class="py-10 text-center text-sm text-slate-400">Loading verification statuses</td></tr>
</Show>
<Show when={!rows.loading && normalized().length === 0}>
<tr><td colspan="6" class="py-10 text-center text-sm text-slate-400">No verification status records found.</td></tr>
</Show>
<For each={normalized()}>
{(item) => (
<tr class="hover:bg-slate-50">
<td class="font-mono text-xs text-slate-500">{item.id.slice(0, 8)}</td>
<td class="text-slate-600">{item.type}</td>
<td>
<p class="font-medium text-gray-900">{item.requesterName}</p>
<p class="text-xs text-slate-500">{item.requesterEmail}</p>
</td>
<td>
<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="text-slate-500">{item.createdAt ? new Date(item.createdAt).toLocaleString() : '—'}</td>
<td>
<div class="flex items-center justify-end gap-2">
<A
class="action-btn flex items-center justify-center text-gray-500 hover:bg-gray-50 transition-colors text-sm"
href={`/admin/verification-status/${item.id}`}
title="Open Status Detail"
>
</A>
<A
class="action-btn flex items-center justify-center text-gray-500 hover:bg-gray-50 transition-colors text-sm"
href={`/admin/approval/${item.id}`}
title="Open Approval Detail"
>
👁
</A>
</div>
</td>
</tr>
)}
</For>
</tbody>
</table>
</div>
</div>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -40,16 +40,18 @@ export default function VerificationStatusDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Verification Status Detail</h1> <h1 class="text-xl font-semibold text-gray-900">Verification Status Detail</h1>
<p class="mt-1 text-sm text-gray-500">Open one verification status request and jump into approval review when needed.</p> <p class="text-sm text-gray-500 mt-0.5">Open one verification status request and jump into approval review when needed.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/verification-status">Back to Status List</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/verification-status">Back to Status List</A>
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Open Approval Detail</A> <A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
</div> </div>
</div> </div>
<div class="p-6 flex-1">
<Show when={detail.loading}> <Show when={detail.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading verification status...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading verification status...</p></div>
@ -79,6 +81,8 @@ export default function VerificationStatusDetailPage() {
<pre class="json">{detail()!.requestReason || 'No requestReason payload found.'}</pre> <pre class="json">{detail()!.requestReason || 'No requestReason payload found.'}</pre>
</section> </section>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }

View file

@ -37,16 +37,18 @@ export default function VerificationDetailPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="mb-6 flex items-start justify-between gap-4"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between">
<div> <div>
<h1 class="text-2xl font-bold text-gray-900">Verification Review</h1> <h1 class="text-xl font-semibold text-gray-900">Verification Review</h1>
<p class="mt-1 text-sm text-gray-500">Review submission context, documents, and verification decision state.</p> <p class="text-sm text-gray-500 mt-0.5">Review submission context, documents, and verification decision state.</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<A class="inline-flex items-center rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/verification">Back to Verification</A> <A class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors" href="/admin/verification">Back to Verification</A>
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Open Approval Detail</A> <A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" href={`/admin/approval/${params.id}`}>Open Approval Detail</A>
</div> </div>
</div> </div>
<div class="p-6 flex-1">
<Show when={approval.loading}> <Show when={approval.loading}>
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading verification detail...</p></div> <div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading verification detail...</p></div>
@ -70,11 +72,13 @@ export default function VerificationDetailPage() {
This route mirrors the Next.js verification detail entry point and delegates action workflow to Approval Management. This route mirrors the Next.js verification detail entry point and delegates action workflow to Approval Management.
</p> </p>
<div class="actions"> <div class="actions">
<A class="inline-flex items-center rounded-lg bg-[#fd6216] px-4 py-2 text-sm font-semibold text-white hover:bg-orange-600 transition-colors" href={`/admin/approval/${params.id}`}>Review & Take Action</A> <A class="rounded-lg bg-[#0a1d37] px-4 py-2 text-sm font-medium text-white hover:bg-[#0f2a4e] transition-colors" href={`/admin/approval/${params.id}`}>Review & Take Action</A>
</div> </div>
</section> </section>
</div> </div>
</Show> </Show>
</div>
</div>
</AdminShell> </AdminShell>
); );
} }