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:
parent
3b98609cb5
commit
33619a1b27
32 changed files with 811 additions and 585 deletions
|
|
@ -4,8 +4,8 @@
|
|||
{
|
||||
"name": "admin-solid",
|
||||
"runtimeExecutable": "sh",
|
||||
"runtimeArgs": ["-c", "cd /Users/ashwin/workspace/nxtgauge-admin-solid && npm run dev -- --port 3002"],
|
||||
"port": 3002
|
||||
"runtimeArgs": ["-c", "cd /Users/ashwin/workspace/nxtgauge-admin-solid && npm run dev -- --port 3020 --host"],
|
||||
"port": 3020
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,86 +75,109 @@ export default function ApplicationsPage() {
|
|||
return requirements().find((item) => item.id === id);
|
||||
}
|
||||
|
||||
function statusClass(status: string): string {
|
||||
if (status === 'ACCEPTED') return 'status-approved';
|
||||
if (status === 'REJECTED' || status === 'WITHDRAWN') return 'status-rejected';
|
||||
return 'status-pending';
|
||||
function statusBadge(status: string) {
|
||||
if (status === 'ACCEPTED') return 'bg-green-100 text-green-800';
|
||||
if (status === 'REJECTED' || status === 'WITHDRAWN') return 'bg-red-100 text-red-700';
|
||||
return 'bg-yellow-100 text-yellow-800';
|
||||
}
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<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>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
|
||||
<Show when={payload.loading}>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading applications...</p></div>
|
||||
</Show>
|
||||
{/* ── Page header ── */}
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<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()}>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
|
||||
</Show>
|
||||
{/* ── 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={!payload.loading && applications().length === 0}>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">No applications found.</p></div>
|
||||
</Show>
|
||||
<Show when={payload.loading}>
|
||||
<div class="table-card">
|
||||
<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">
|
||||
<For each={applications()}>
|
||||
{(app) => {
|
||||
const req = createMemo(() => requirementFor(app.requirementId));
|
||||
return (
|
||||
<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>
|
||||
<h2 style="margin:0;font-size:19px">{req()?.title || 'Unknown Requirement'}</h2>
|
||||
<p class="notice" style="margin:6px 0 0">ID: {app.id}</p>
|
||||
<Show when={!payload.loading && applications().length === 0}>
|
||||
<div class="table-card">
|
||||
<p class="py-10 text-center text-sm text-slate-400">No applications found.</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<For each={applications()}>
|
||||
{(app) => {
|
||||
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>
|
||||
<span class={`status-pill ${statusClass(app.status)}`}>{app.status}</span>
|
||||
</div>
|
||||
|
||||
<div class="sub-card">
|
||||
<p class="kv-label">Message</p>
|
||||
<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>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -975,7 +975,7 @@ export default function ApprovalPage() {
|
|||
|
||||
<div class="mb-6 flex items-start justify-between gap-4" style="margin-bottom:16px">
|
||||
<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'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -1010,7 +1010,7 @@ export default function ApprovalPage() {
|
|||
</div>
|
||||
</div>
|
||||
<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'}
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -247,13 +247,15 @@ export default function ApprovalDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Submission Review</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Review a user's onboarding form submission and take action.</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">← 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 class="p-6 flex-1">
|
||||
|
||||
<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>
|
||||
|
|
@ -389,6 +391,8 @@ export default function ApprovalDetailPage() {
|
|||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,19 +96,21 @@ export default function CompanyDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">{name()}</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">{companyId()}</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">{name()}</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">{companyId()}</p>
|
||||
</div>
|
||||
<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'}
|
||||
</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="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 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="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 class="p-6 flex-1">
|
||||
|
||||
<Show when={bundle.loading}>
|
||||
<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>
|
||||
</>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Create Company</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Add a new organization profile to the admin company catalog.</p>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
|
||||
{/* ── Page header ── */}
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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';
|
||||
|
||||
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 (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Edit Employee</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Update internal employee profile and role assignment.</p>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
|
||||
{/* ── Page header ── */}
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
|
|
@ -454,13 +454,15 @@ export default function ExternalDashboardManagementPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">External Dashboard Management</h1>
|
||||
<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>
|
||||
<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 class="p-6 flex-1">
|
||||
|
||||
<div class="hidden" style="margin-bottom:14px">
|
||||
<a class="hidden" href="#builder">View Dashboards</a>
|
||||
|
|
@ -473,10 +475,10 @@ export default function ExternalDashboardManagementPage() {
|
|||
<Show when={!selected()}>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<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>
|
||||
</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'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -532,7 +534,7 @@ export default function ExternalDashboardManagementPage() {
|
|||
</div>
|
||||
<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 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'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -730,6 +732,8 @@ export default function ExternalDashboardManagementPage() {
|
|||
</Show>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,21 @@ export default function HelpArticlePage() {
|
|||
const params = useParams();
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Help Article</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Legacy help article route preserved for migration compatibility.</p>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,18 @@ import AdminShell from '~/components/AdminShell';
|
|||
export default function HelpSupportBridgePage() {
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="text-2xl font-bold text-gray-900">Support Bridge</h1>
|
||||
<p class="mt-1 text-sm text-gray-500" style="margin-top:8px">
|
||||
This legacy help bridge now routes through the unified Support Management module.
|
||||
</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 class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Support Bridge</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">This legacy help bridge now routes through the unified Support Management module.</p>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -413,13 +413,15 @@ export default function InternalDashboardManagementPage() {
|
|||
// ---------- List view ----------
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Internal Dashboard Management</h1>
|
||||
<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>
|
||||
<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 class="p-6 flex-1">
|
||||
|
||||
<div class="hidden" style="margin-bottom:14px">
|
||||
<a class="hidden" href="#builder">View Dashboards</a>
|
||||
|
|
@ -431,10 +433,10 @@ export default function InternalDashboardManagementPage() {
|
|||
<Show when={!selected()}>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<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>
|
||||
</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'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -490,7 +492,7 @@ export default function InternalDashboardManagementPage() {
|
|||
</div>
|
||||
<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 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'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -738,6 +740,8 @@ export default function InternalDashboardManagementPage() {
|
|||
</Show>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,13 +51,15 @@ export default function JobDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Job Management</h1>
|
||||
<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>
|
||||
<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 class="p-6 flex-1">
|
||||
|
||||
<Show when={job.loading}>
|
||||
<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>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,14 +33,15 @@ export default function KbArticleDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">KB Article Detail</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Metadata and safe content preview for this article.</p>
|
||||
</div>
|
||||
<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="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 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="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>
|
||||
|
||||
|
|
@ -72,6 +73,8 @@ export default function KbArticleDetailPage() {
|
|||
</section>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Edit KB Article</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Update article metadata, status, and content.</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Edit KB Article</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Update article metadata, status, and content.</p>
|
||||
</div>
|
||||
<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="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/${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">Back to Articles</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 flex-1">
|
||||
<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 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 when={article() && loaded()}>
|
||||
<form class="rounded-xl border border-gray-200 bg-white shadow-sm" onSubmit={save}>
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div class="field">
|
||||
<label>Title</label>
|
||||
<input value={title()} onInput={(e) => setTitle(e.currentTarget.value)} required />
|
||||
<form class="rounded-xl border border-gray-200 bg-white shadow-sm p-6" onSubmit={save}>
|
||||
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class={labelCls}>Title</label>
|
||||
<input class={inputCls} value={title()} onInput={(e) => setTitle(e.currentTarget.value)} required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Slug</label>
|
||||
<input value={slug()} onInput={(e) => setSlug(e.currentTarget.value)} />
|
||||
<div>
|
||||
<label class={labelCls}>Slug</label>
|
||||
<input class={inputCls} value={slug()} onInput={(e) => setSlug(e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Category ID</label>
|
||||
<input value={categoryId()} onInput={(e) => setCategoryId(e.currentTarget.value)} />
|
||||
<div>
|
||||
<label class={labelCls}>Category ID</label>
|
||||
<input class={inputCls} value={categoryId()} onInput={(e) => setCategoryId(e.currentTarget.value)} />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Status</label>
|
||||
<select value={status()} onChange={(e) => setStatus(e.currentTarget.value)}>
|
||||
<div>
|
||||
<label class={labelCls}>Status</label>
|
||||
<select class={inputCls} value={status()} onChange={(e) => setStatus(e.currentTarget.value)}>
|
||||
<option value="DRAFT">DRAFT</option>
|
||||
<option value="PUBLISHED">PUBLISHED</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field" style="grid-column:1 / -1">
|
||||
<label>Content</label>
|
||||
<textarea rows="16" value={content()} onInput={(e) => setContent(e.currentTarget.value)} />
|
||||
<div class="sm:col-span-2">
|
||||
<label class={labelCls}>Content</label>
|
||||
<textarea rows="16" class={inputCls} value={content()} onInput={(e) => setContent(e.currentTarget.value)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<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 Article'}
|
||||
<div class="mt-6 flex justify-end border-t border-gray-100 pt-5">
|
||||
<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'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,13 +73,15 @@ export default function LeadDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Lead Detail</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review one lead and its linked requirement identifiers.</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Lead Detail</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Review one lead and its linked requirement identifiers.</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/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 class="p-6 flex-1">
|
||||
|
||||
<Show when={bundle.loading}>
|
||||
<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>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,112 +107,149 @@ export default function ModulesPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Module Registry</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage internal module definitions and activation state.</p>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
|
||||
{/* ── Page header ── */}
|
||||
<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>
|
||||
<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()}>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
|
||||
</Show>
|
||||
{/* ── Content ── */}
|
||||
<div class="p-6">
|
||||
<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="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading modules...</p></div>
|
||||
</Show>
|
||||
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
<th>Status</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={(modules() || []).length > 0} fallback={
|
||||
<tr><td colspan="5" class="notice" style="padding:16px">No modules found.</td></tr>
|
||||
}>
|
||||
<For each={modules() || []}>
|
||||
{(item) => (
|
||||
<tr>
|
||||
<td>{item.name}</td>
|
||||
<td><code>{item.key}</code></td>
|
||||
<td>{item.description || '—'}</td>
|
||||
<td>
|
||||
<span class={`status-pill ${item.isActive ? 'status-approved' : 'status-rejected'}`}>
|
||||
{item.isActive ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Key</th>
|
||||
<th>Description</th>
|
||||
<th>Status</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={modules.loading}>
|
||||
<tr><td colspan="5" class="py-10 text-center text-sm text-slate-400">Loading modules…</td></tr>
|
||||
</Show>
|
||||
<Show when={!modules.loading && (modules() || []).length === 0}>
|
||||
<tr><td colspan="5" class="py-10 text-center text-sm text-slate-400">No modules found.</td></tr>
|
||||
</Show>
|
||||
<For each={modules() || []}>
|
||||
{(item) => (
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="font-medium text-gray-900">{item.name}</td>
|
||||
<td><code class="text-xs bg-gray-100 px-1.5 py-0.5 rounded">{item.key}</code></td>
|
||||
<td class="text-slate-500">{item.description || '—'}</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'}`}>
|
||||
{item.isActive ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<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"
|
||||
onClick={() => openModal(item)}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
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"
|
||||
onClick={() => removeModule(item.id)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Modal ── */}
|
||||
<Show when={isModalOpen()}>
|
||||
<div class="modal-backdrop">
|
||||
<div class="modal">
|
||||
<h2 style="margin-top:0">{modalTitle()}</h2>
|
||||
<form onSubmit={submitForm}>
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
|
||||
<div class="w-full max-w-lg rounded-xl bg-white shadow-xl">
|
||||
<div class="border-b border-gray-200 px-6 py-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900">{modalTitle()}</h2>
|
||||
</div>
|
||||
<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
|
||||
value={form().name}
|
||||
onInput={(event) => setForm((prev) => ({ ...prev, name: event.currentTarget.value }))}
|
||||
placeholder="e.g. Job Board"
|
||||
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 class="field">
|
||||
<label>Key</label>
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700">Key *</label>
|
||||
<input
|
||||
value={form().key}
|
||||
onInput={(event) => setForm((prev) => ({ ...prev, key: event.currentTarget.value }))}
|
||||
placeholder="e.g. manage_jobs"
|
||||
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 class="field">
|
||||
<label>Description</label>
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700">Description</label>
|
||||
<textarea
|
||||
rows="3"
|
||||
value={form().description}
|
||||
onInput={(event) => setForm((prev) => ({ ...prev, description: event.currentTarget.value }))}
|
||||
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>
|
||||
<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
|
||||
type="checkbox"
|
||||
checked={form().isActive}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, isActive: event.currentTarget.checked }))}
|
||||
class="rounded"
|
||||
/>
|
||||
Active
|
||||
</label>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<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 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()}>
|
||||
<div class="flex justify-end gap-3 border-t border-gray-100 pt-4">
|
||||
<button
|
||||
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'}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -133,20 +133,21 @@ export default function OnboardingSchemaDetailPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<OnboardingManagementTabs />
|
||||
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Onboarding Management</h1>
|
||||
<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 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
|
||||
</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>
|
||||
<OnboardingManagementTabs />
|
||||
<div class="p-6 flex-1">
|
||||
|
||||
<Show when={schema.loading}>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,79 +93,80 @@ export default function OnboardingSchemasPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Onboarding Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Manage onboarding flows, role assignments, and previewable step groups for external users.</p>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
|
||||
{/* ── Page header ── */}
|
||||
<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>
|
||||
<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()}>
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{deleteError()}</div>
|
||||
</Show>
|
||||
|
||||
<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>
|
||||
{/* ── Content ── */}
|
||||
<div class="p-6">
|
||||
<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>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<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) => (
|
||||
|
||||
<div class="table-card">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="data-table w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="font-weight:600;color:#0f172a">{schema.title}</td>
|
||||
<td style="color:#475569">{schema.roleKey || '—'}</td>
|
||||
<td style="color:#475569">{schema.stepCount}</td>
|
||||
<td style="color:#475569">v{schema.version}</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 ${schema.status === 'PUBLISHED' ? 'active' : ''}`}>{schema.status}</span>
|
||||
</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>
|
||||
<th>Flow</th>
|
||||
<th>Role</th>
|
||||
<th>Steps</th>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
))}
|
||||
</Show>
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
<Show when={schemas.loading}>
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,15 +105,16 @@ export default function NewOnboardingSchemaPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<OnboardingManagementTabs />
|
||||
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Create Onboarding Flow</h1>
|
||||
<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>
|
||||
<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>
|
||||
<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 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}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,17 +50,18 @@ export default function PhotographerDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Photographer Detail</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">View profile snapshot and account metadata for one photographer.</p>
|
||||
</div>
|
||||
<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="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/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/users/details/${params.id}`}>Open User Detail</A>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 flex-1">
|
||||
<Show when={profile.loading}>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading photographer...</p></div>
|
||||
</Show>
|
||||
|
|
@ -136,6 +137,8 @@ export default function PhotographerDetailPage() {
|
|||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,13 +40,15 @@ export default function RequirementDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Requirement Request</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review full requirement request details before approval action.</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Requirement Request</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Review full requirement request details before approval action.</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">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 class="p-6 flex-1">
|
||||
|
||||
<Show when={requirement.loading}>
|
||||
<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>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ export default function ResponsesPage() {
|
|||
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) {
|
||||
setError('');
|
||||
setBusyId(id);
|
||||
|
|
@ -76,50 +83,86 @@ export default function ResponsesPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<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>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
|
||||
<Show when={payload.loading}>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="notice">Loading responses...</p></div>
|
||||
</Show>
|
||||
<Show when={error()}>
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm"><p class="error-note">{error()}</p></div>
|
||||
</Show>
|
||||
{/* ── Page header ── */}
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Responses</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Track professional responses and move them through shortlist, accept, or reject states.</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-gray-200 bg-white shadow-sm">
|
||||
<Show when={!payload.loading && responses().length === 0} fallback={
|
||||
<div class="list-grid" style="grid-template-columns:1fr;gap:12px">
|
||||
{/* ── 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={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()}>
|
||||
{(row) => (
|
||||
<div class="list-item">
|
||||
<p style="margin:0;font-weight:700;color:#0f172a">{requirementTitle(row.requirementId)}</p>
|
||||
<p class="notice" style="margin:6px 0 0">{row.message || 'No message'}</p>
|
||||
<div style="margin-top:8px;display:flex;gap:10px;flex-wrap:wrap">
|
||||
<span class="meta-chip">Professional: {row.professionalName || row.professionalId}</span>
|
||||
<span class="meta-chip">Quote: {row.quote || 0}</span>
|
||||
<span class={`status-pill ${row.status === 'ACCEPTED' ? 'status-approved' : row.status === 'REJECTED' ? 'status-rejected' : 'status-pending'}`}>{row.status}</span>
|
||||
<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">
|
||||
<p class="text-base font-semibold text-gray-900">{requirementTitle(row.requirementId)}</p>
|
||||
<span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${statusBadge(row.status)}`}>{row.status}</span>
|
||||
</div>
|
||||
<p class="mt-1.5 text-sm text-slate-500">{row.message || 'No message'}</p>
|
||||
<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}>
|
||||
<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>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="mt-4 flex gap-2 flex-wrap">
|
||||
<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 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 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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
}>
|
||||
<p class="notice">No responses yet.</p>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -76,10 +76,12 @@ export default function RoleUiConfigsViewPage() {
|
|||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="page-hero-card">
|
||||
<h1 class="text-2xl font-bold text-gray-900">External Dashboard Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Read-only view of the currently published external dashboard and runtime role configuration.</p>
|
||||
<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">
|
||||
<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 class="p-6">
|
||||
|
||||
<ExternalRoleTabs roleKey={roleKey()} />
|
||||
|
||||
|
|
@ -107,7 +109,7 @@ export default function RoleUiConfigsViewPage() {
|
|||
<button
|
||||
type="button"
|
||||
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>
|
||||
|
|
@ -222,6 +224,8 @@ export default function RoleUiConfigsViewPage() {
|
|||
</Show>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,13 +112,15 @@ export default function EditInternalRolePage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Edit Internal Role</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Update role name, access areas, and permissions.</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Edit Internal Role</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Update role name, access areas, and permissions.</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/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 class="p-6">
|
||||
|
||||
<nav class="hidden" aria-label="Role Management Navigation">
|
||||
<A class="hidden" href="/admin/roles">Internal Roles</A>
|
||||
|
|
@ -226,16 +228,18 @@ export default function EditInternalRolePage() {
|
|||
</Show>
|
||||
|
||||
{/* Save */}
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:8px">
|
||||
<div class="flex justify-end mt-2">
|
||||
<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}
|
||||
disabled={saving() || !roleName().trim()}
|
||||
>
|
||||
{saving() ? 'Saving...' : 'Save Changes'}
|
||||
{saving() ? 'Saving…' : 'Save Changes'}
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,18 +43,20 @@ export default function RoleDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Role Details</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">View role information and assigned permissions.</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Role Details</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">View role information and assigned permissions.</p>
|
||||
</div>
|
||||
<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}>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
|
||||
<nav class="hidden" aria-label="Role Management Navigation">
|
||||
<A class="hidden" href="/admin/roles">Internal Roles</A>
|
||||
|
|
@ -132,6 +134,8 @@ export default function RoleDetailPage() {
|
|||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,26 +53,10 @@ export default function InternalRolesPage() {
|
|||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
|
||||
{/* ── Page header ── */}
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-4">
|
||||
<h1 class="text-xl font-semibold text-gray-900">Internal Role Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Manage internal employee roles and permissions.</p>
|
||||
</div>
|
||||
|
||||
{/* ── 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 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">Internal Role Management</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Manage internal employee roles and permissions.</p>
|
||||
</div>
|
||||
<A
|
||||
href="/admin/roles/create"
|
||||
|
|
|
|||
|
|
@ -152,18 +152,18 @@ export default function EditExternalRolePage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">External Role Management</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Update this external role with simple settings: pages, permissions, onboarding form, approvals, and limits.
|
||||
</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">External Role Management</h1>
|
||||
<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>
|
||||
</div>
|
||||
<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="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/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/runtime-roles">Back to External Roles</A>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 flex-1">
|
||||
|
||||
<ExternalRoleTabs roleKey={roleKey()} />
|
||||
|
||||
|
|
@ -195,6 +195,8 @@ export default function EditExternalRolePage() {
|
|||
onSubmit={handleSubmit}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,16 +114,18 @@ export default function EditUserPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Edit User</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Update user profile, role assignment, and account status.</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Edit User</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Update user profile, role assignment, and account status.</p>
|
||||
</div>
|
||||
<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="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/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">Back to Users</A>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
|
@ -138,19 +140,19 @@ export default function EditUserPage() {
|
|||
</Show>
|
||||
|
||||
<Show when={user()}>
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="max-width:900px">
|
||||
<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)} />
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm p-6 max-w-3xl">
|
||||
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700">Full Name</label>
|
||||
<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 class="field">
|
||||
<label>Email</label>
|
||||
<input type="email" value={email()} onInput={(e) => setEmail(e.currentTarget.value)} />
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700">Email</label>
|
||||
<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 class="field">
|
||||
<label>Role</label>
|
||||
<select value={roleId()} onChange={(e) => setRoleId(e.currentTarget.value)}>
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700">Role</label>
|
||||
<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>
|
||||
<Show when={!roles.loading}>
|
||||
{roles()?.map((r) => (
|
||||
|
|
@ -159,9 +161,9 @@ export default function EditUserPage() {
|
|||
</Show>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Status</label>
|
||||
<select value={status()} onChange={(e) => setStatus(e.currentTarget.value as 'ACTIVE' | 'INACTIVE' | 'PENDING')}>
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700">Status</label>
|
||||
<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="PENDING">Pending</option>
|
||||
<option value="INACTIVE">Inactive</option>
|
||||
|
|
@ -169,14 +171,16 @@ export default function EditUserPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions" style="justify-content:flex-end">
|
||||
<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="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()}>
|
||||
{submitting() ? 'Saving...' : 'Save Changes'}
|
||||
<div class="mt-6 flex justify-end gap-3 border-t border-gray-100 pt-5">
|
||||
<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="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'}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,16 +161,18 @@ export default function UserDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">{roleTitleLabel() ? `${roleTitleLabel()} Profile` : 'User Details'}</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Review account profile and role registration data with approval status.</p>
|
||||
</div>
|
||||
<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="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 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="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 class="p-6 flex-1">
|
||||
|
||||
<Show when={bundle.loading}>
|
||||
<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>
|
||||
</>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,62 +40,95 @@ export default function VerificationStatusPage() {
|
|||
createdAt: r.createdAt || r.created_at || '',
|
||||
})));
|
||||
|
||||
function statusBadge(status: string) {
|
||||
if (status === 'APPROVED') return 'bg-green-100 text-green-800';
|
||||
if (status === 'REJECTED') return 'bg-red-100 text-red-700';
|
||||
if (status === 'PENDING') return 'bg-yellow-100 text-yellow-800';
|
||||
return 'bg-gray-100 text-gray-600';
|
||||
}
|
||||
|
||||
return (
|
||||
<AdminShell>
|
||||
<div class="mb-6 flex items-start justify-between gap-4">
|
||||
<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>
|
||||
<div class="flex flex-col -mx-6 -mt-6 min-h-full">
|
||||
|
||||
<section class="rounded-xl border border-gray-200 bg-white shadow-sm" style="padding:0;overflow:hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="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" 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>
|
||||
{/* ── Page header ── */}
|
||||
<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">Verification Status</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Track request status states and open a specific record for follow-up.</p>
|
||||
</div>
|
||||
<A
|
||||
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"
|
||||
href="/admin/approval"
|
||||
>
|
||||
Open Approval Center
|
||||
</A>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,16 +40,18 @@ export default function VerificationStatusDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold 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>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Verification Status Detail</h1>
|
||||
<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 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="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 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="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 class="p-6 flex-1">
|
||||
|
||||
<Show when={detail.loading}>
|
||||
<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>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,16 +37,18 @@ export default function VerificationDetailPage() {
|
|||
|
||||
return (
|
||||
<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>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Verification Review</h1>
|
||||
<p class="mt-1 text-sm text-gray-500">Review submission context, documents, and verification decision state.</p>
|
||||
<h1 class="text-xl font-semibold text-gray-900">Verification Review</h1>
|
||||
<p class="text-sm text-gray-500 mt-0.5">Review submission context, documents, and verification decision state.</p>
|
||||
</div>
|
||||
<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="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 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="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 class="p-6 flex-1">
|
||||
|
||||
<Show when={approval.loading}>
|
||||
<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.
|
||||
</p>
|
||||
<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>
|
||||
</section>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</AdminShell>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue