feat: phase 3 identity & configuration ui matching figma designs

This commit is contained in:
Ashwin Kumar 2026-03-26 00:01:15 +01:00
parent 83a1f45e4f
commit 648b6be849
4 changed files with 648 additions and 481 deletions

View file

@ -182,87 +182,60 @@ export default function DepartmentPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="flex flex-col -mx-6 -mt-6 min-h-full"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="p-6 flex-1 max-w-[1600px] mx-auto w-full">
{/* ── Page header ── */} {/* Header & Title */}
<div class="bg-white border-b border-gray-200 px-6 py-4"> <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
<h1 class="text-xl font-semibold text-gray-900">Departments</h1> <div>
<p class="text-sm text-gray-500 mt-0.5">Manage organization structure and units.</p> <h1 class="text-[32px] font-bold text-[#050026] leading-tight">Department Management</h1>
</div> </div>
<div class="flex items-center gap-3">
{/* ── Tab + action bar ── */} <button class="inline-flex h-11 items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
<div class="bg-white border-b border-gray-200 px-6 flex items-center justify-between sticky top-0 z-10"> Export Data
<div class="flex gap-8">
<For each={(['active', 'archived'] as const)}>
{(t) => (
<button
onClick={() => switchTab(t)}
class={`py-3 border-b-2 text-sm font-medium capitalize transition-colors ${
view() === 'list' && statusFilter() === t
? 'border-orange-500 text-orange-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
{t} Departments
</button>
)}
</For>
<Show when={view() === 'create'}>
<button class="py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium">
Create Department
</button> </button>
</Show> <button
<Show when={view() === 'update'}> class="inline-flex h-11 items-center justify-center rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]"
<button class="py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium"> onClick={() => { setView('create'); setCreateError(''); setCreateName(''); setCreateDesc(''); }}
Update Department >
<span class="mr-2 text-lg leading-none">+</span> Create Department
</button> </button>
</Show> </div>
</div> </div>
<Show when={view() === 'list'}>
<button
onClick={() => { setView('create'); setCreateError(''); setCreateName(''); setCreateDesc(''); }}
class="btn-primary"
>
<Plus size={16} />
Add Department
</button>
</Show>
</div>
{/* ── Content ── */}
<div class="flex-1">
{/* Create form */}
<Show when={view() === 'create'}> <Show when={view() === 'create'}>
<div class="bg-white border-b border-gray-200 px-6 py-6"> {/* Create form */}
<form onSubmit={handleCreate} class="grid grid-cols-1 gap-6 md:grid-cols-2 max-w-4xl"> <div class="bg-white border focus-within:border-[#0a1d37] border-[#e2e6ee] rounded-3xl p-6 mb-8 max-w-4xl shadow-sm">
<div> <h2 class="text-[22px] font-bold text-[#050026] mb-6">Create New Department</h2>
<label class="mb-1.5 block text-sm font-medium text-gray-700">Department Name *</label> <form onSubmit={handleCreate} class="space-y-6">
<input <div class="grid grid-cols-1 gap-6 md:grid-cols-2">
type="text" required <div>
placeholder="e.g. Engineering" <label class="mb-1.5 block text-sm font-semibold text-[#383e5c]">Department Name *</label>
value={createName()} <input
onInput={(e) => setCreateName(e.currentTarget.value)} type="text" 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]" placeholder="e.g. Engineering"
/> value={createName()}
</div> onInput={(e) => setCreateName(e.currentTarget.value)}
<div> class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] px-4 text-[14px] outline-none transition-colors focus:border-[#050026] focus:bg-white"
<label class="mb-1.5 block text-sm font-medium text-gray-700">Description</label> />
<input </div>
type="text" <div>
placeholder="Optional description" <label class="mb-1.5 block text-sm font-semibold text-[#383e5c]">Description</label>
value={createDesc()} <input
onInput={(e) => setCreateDesc(e.currentTarget.value)} type="text"
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]" placeholder="Optional description"
/> value={createDesc()}
onInput={(e) => setCreateDesc(e.currentTarget.value)}
class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] px-4 text-[14px] outline-none transition-colors focus:border-[#050026] focus:bg-white"
/>
</div>
</div> </div>
<Show when={createError()}> <Show when={createError()}>
<p class="md:col-span-2 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{createError()}</p> <p class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{createError()}</p>
</Show> </Show>
<div class="md:col-span-2 flex justify-end gap-3 pt-2 border-t border-gray-100"> <div class="flex justify-end gap-3 pt-4 border-t border-[#e2e6ee]">
<button type="button" onClick={() => setView('list')} class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> <button type="button" onClick={() => setView('list')} class="h-11 rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] hover:bg-[#f8f9fc] transition-colors">
Cancel Cancel
</button> </button>
<button type="submit" disabled={creating()} class="btn-primary"> <button type="submit" disabled={creating()} class="h-11 rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white hover:bg-[#0a0044] transition-colors disabled:opacity-70">
{creating() ? 'Saving…' : 'Save'} {creating() ? 'Saving…' : 'Save'}
</button> </button>
</div> </div>
@ -270,66 +243,105 @@ export default function DepartmentPage() {
</div> </div>
</Show> </Show>
{/* List view */}
<Show when={view() === 'list'}> <Show when={view() === 'list'}>
<div class="p-6"> {/* Main Table Section */}
<Show when={actionError()}> <section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full">
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{actionError()}</div> <div class="rounded-[20px] bg-white p-5">
</Show>
{/* Error Message */}
<Show when={actionError()}>
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{actionError()}</div>
</Show>
<div class="table-card"> {/* Tabs */}
<div class="flex gap-6 mb-6 border-b border-[#e2e6ee]">
<For each={(['active', 'archived'] as const)}>
{(t) => (
<button
onClick={() => switchTab(t)}
class={`pb-3 text-[14px] font-bold capitalize transition-colors border-b-2 ${
statusFilter() === t
? 'border-[#050026] text-[#050026]'
: 'border-transparent text-[#8087a0] hover:text-[#050026]'
}`}
>
{t} Department
</button>
)}
</For>
</div>
{/* Filters Row */}
<div class="flex flex-col gap-4 md:flex-row items-center mb-6">
<div class="relative w-full md:w-[320px]">
<div class="absolute inset-y-0 left-4 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-[#a0aabf]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
type="text"
placeholder="Search departments..."
class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] pl-11 pr-4 text-[14px] text-[#050026] outline-none transition-colors focus:border-[#050026] focus:bg-white"
/>
</div>
<div class="h-11 w-full md:w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb]"></div>
<div class="flex-1"></div>
</div>
{/* Table */}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="data-table w-full text-sm"> <table class="w-full min-w-[1000px] border-collapse">
<thead> <thead>
<tr> <tr class="bg-[#050026] text-left text-white">
<th>ID</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl whitespace-nowrap">DEPARTMENT ID</th>
<th>Name</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">DEPARTMENT NAME</th>
<th>Description</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">DESCRIPTION</th>
<th>Created By</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">CREATED BY</th>
<th>Created</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">CREATED</th>
<th>Last Updated By</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">LAST UPDATED BY</th>
<th>Last Updated At</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">LAST UPDATED AT</th>
<th class="text-right">Actions</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-right rounded-tr-xl whitespace-nowrap">ACTION</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<Show when={data.loading}> <Show when={data.loading}>
<tr><td colspan="8" class="py-10 text-center text-sm text-slate-400">Loading</td></tr> <tr><td colspan="8" class="text-center py-12 text-[#8087a0] text-[14px]">Loading departments...</td></tr>
</Show> </Show>
<Show when={!data.loading && data.error}> <Show when={!data.loading && data.error}>
<tr><td colspan="8" class="py-10 text-center text-sm text-red-500">Failed to load. Is the backend running?</td></tr> <tr><td colspan="8" class="text-center py-12 text-red-500 text-[14px]">Failed to load. Is the backend running?</td></tr>
</Show> </Show>
<Show when={!data.loading && !data.error && filtered().length === 0}> <Show when={!data.loading && !data.error && filtered().length === 0}>
<tr><td colspan="8" class="py-10 text-center text-sm text-slate-400">No departments found.</td></tr> <tr><td colspan="8" class="text-center py-12 text-[#8087a0] text-[14px]">No departments found.</td></tr>
</Show> </Show>
<For each={filtered()}> <For each={filtered()}>
{(item) => ( {(item) => (
<> <>
<tr class="group hover:bg-slate-50"> <tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc]">
<td class="font-mono text-xs text-slate-500">{item.departmentId || item.id.slice(0, 8)}</td> <td class="px-6 py-4 text-[14px] font-semibold text-[#64748b]">{item.departmentId || item.id.slice(0, 8).toUpperCase()}</td>
<td class="font-semibold text-slate-900">{deptLabel(item)}</td> <td class="px-6 py-4 text-[14px] font-bold text-[#050026]">{deptLabel(item)}</td>
<td class="text-slate-500">{item.description || '—'}</td> <td class="px-6 py-4 text-[14px] text-[#475569]">{item.description || '—'}</td>
<td class="text-blue-600 hover:underline"><a href={`mailto:${item.createdBy}`}>{item.createdBy || '—'}</a></td> <td class="px-6 py-4 text-[14px] text-[#0ea5e9] hover:underline cursor-pointer">{item.createdBy || 'System'}</td>
<td class="text-slate-500">{fmtDate(item.createdAt || item.created_at)}</td> <td class="px-6 py-4 text-[14px] text-[#475569]">{fmtDate(item.createdAt || item.created_at)}</td>
<td class="text-blue-600 hover:underline"><a href={`mailto:${item.updatedBy || item.createdBy}`}>{item.updatedBy || item.createdBy || '—'}</a></td> <td class="px-6 py-4 text-[14px] text-[#0ea5e9] hover:underline cursor-pointer">{item.updatedBy || item.createdBy || 'System'}</td>
<td class="text-slate-500">{fmtDate(item.updatedAt)}</td> <td class="px-6 py-4 text-[14px] text-[#475569]">{fmtDate(item.updatedAt)}</td>
<td> <td class="px-6 py-4">
<div class="flex items-center justify-end gap-1.5"> <div class="flex items-center justify-end gap-2">
<Show when={!isArchived(item)}> <Show when={!isArchived(item)}>
<button <button
title="Edit" title="Edit"
onClick={() => startEdit(item)} onClick={() => startEdit(item)}
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors" class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
> >
<Pencil size={14} class="text-gray-600" /> <Pencil size={14} />
</button> </button>
<button <button
title="Archive" title="Archive"
disabled={busy() === item.id} disabled={busy() === item.id}
onClick={() => handleArchive(item.id)} onClick={() => handleArchive(item.id)}
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors" class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
> >
<Archive size={14} class="text-gray-600" /> <Archive size={14} />
</button> </button>
</Show> </Show>
<Show when={isArchived(item)}> <Show when={isArchived(item)}>
@ -337,18 +349,18 @@ export default function DepartmentPage() {
title="Restore" title="Restore"
disabled={busy() === item.id} disabled={busy() === item.id}
onClick={() => handleRestore(item.id)} onClick={() => handleRestore(item.id)}
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors" class="flex h-8 w-8 items-center justify-center rounded-lg border border-green-200 bg-green-50 text-[#00c853] hover:bg-green-100 transition-colors"
> >
<RotateCcw size={14} class="text-green-600" /> <RotateCcw size={14} />
</button> </button>
</Show> </Show>
<button <button
title="Delete" title="Delete"
disabled={busy() === item.id} disabled={busy() === item.id}
onClick={() => handleDelete(item.id, deptLabel(item))} onClick={() => handleDelete(item.id, deptLabel(item))}
class="action-btn flex items-center justify-center border-red-100 bg-red-50 hover:bg-red-100 transition-colors" class="flex h-8 w-8 items-center justify-center rounded-lg border border-red-200 bg-red-50 text-red-600 hover:bg-red-100 transition-colors"
> >
<Trash2 size={14} class="text-red-600" /> <Trash2 size={14} />
</button> </button>
</div> </div>
</td> </td>
@ -356,28 +368,28 @@ export default function DepartmentPage() {
{/* Inline edit row */} {/* Inline edit row */}
<Show when={editingId() === item.id}> <Show when={editingId() === item.id}>
<tr> <tr>
<td colspan="8" class="bg-slate-50 px-6 py-4"> <td colspan="8" class="bg-[#f8f9fc] px-6 py-4 border-b border-[#e2e6ee]">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 max-w-2xl"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 max-w-2xl bg-white p-4 rounded-xl shadow-sm border border-[#e2e6ee]">
<div> <div>
<label class="mb-1 block text-xs font-medium text-gray-700">Name *</label> <label class="mb-1 block text-xs font-semibold text-[#383e5c]">Name *</label>
<input type="text" required value={editName()} onInput={(e) => setEditName(e.currentTarget.value)} <input type="text" required value={editName()} onInput={(e) => setEditName(e.currentTarget.value)}
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]" /> class="h-10 w-full rounded-lg border border-[#d9dde6] bg-[#f9fafb] px-3 text-sm outline-none focus:border-[#050026] focus:bg-white transition-colors" />
</div> </div>
<div> <div>
<label class="mb-1 block text-xs font-medium text-gray-700">Description</label> <label class="mb-1 block text-xs font-semibold text-[#383e5c]">Description</label>
<input type="text" value={editDesc()} onInput={(e) => setEditDesc(e.currentTarget.value)} <input type="text" value={editDesc()} onInput={(e) => setEditDesc(e.currentTarget.value)}
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]" /> class="h-10 w-full rounded-lg border border-[#d9dde6] bg-[#f9fafb] px-3 text-sm outline-none focus:border-[#050026] focus:bg-white transition-colors" />
</div>
<Show when={editError()}>
<div class="col-span-2 rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700">{editError()}</div>
</Show>
<div class="col-span-2 mt-2 flex justify-end gap-2 pt-3 border-t border-[#e2e6ee]">
<button type="button" onClick={cancelEdit} class="h-9 rounded-lg border border-[#d9dde6] bg-white px-4 text-[13px] font-semibold text-[#050026] hover:bg-[#f8f9fc] transition-colors">Cancel</button>
<button type="button" disabled={saving()} onClick={() => handleUpdate(item.id)}
class="h-9 rounded-lg bg-[#050026] px-4 text-[13px] font-semibold text-white hover:bg-[#0a0044] transition-colors disabled:opacity-70">
{saving() ? 'Saving…' : 'Save'}
</button>
</div> </div>
</div>
<Show when={editError()}>
<p class="mt-3 rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700">{editError()}</p>
</Show>
<div class="mt-3 flex gap-2">
<button type="button" onClick={cancelEdit} class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors">Cancel</button>
<button type="button" disabled={saving()} onClick={() => handleUpdate(item.id)}
class="btn-primary disabled:opacity-60">
{saving() ? 'Saving…' : 'Save'}
</button>
</div> </div>
</td> </td>
</tr> </tr>
@ -388,31 +400,32 @@ export default function DepartmentPage() {
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
{/* Pagination */} {/* Pagination */}
<Show when={totalPages() > 1}> <Show when={totalPages() > 1}>
<div class="mt-4 flex items-center justify-between border-t border-gray-200 pt-4"> <div class="mt-6 flex items-center justify-between border-t border-[#e2e6ee] pt-4">
<span class="text-sm text-gray-500">Page {page()} of {totalPages()}</span> <span class="text-[13px] font-medium text-[#8087a0]">Page {page()} of {totalPages()}</span>
<div class="flex gap-2"> <div class="flex items-center gap-2">
<button <button
disabled={page() === 1} disabled={page() === 1}
onClick={() => setPage((p) => p - 1)} onClick={() => setPage((p) => p - 1)}
class="rounded-lg border border-gray-200 p-2 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-40 transition-colors" class="flex h-9 w-9 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
> >
<ChevronLeft size={16} /> <ChevronLeft size={16} />
</button> </button>
<button <button
disabled={page() >= totalPages()} disabled={page() >= totalPages()}
onClick={() => setPage((p) => p + 1)} onClick={() => setPage((p) => p + 1)}
class="rounded-lg border border-gray-200 p-2 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-40 transition-colors" class="flex h-9 w-9 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
> >
<ChevronRight size={16} /> <ChevronRight size={16} />
</button> </button>
</div>
</div> </div>
</div> </Show>
</Show>
</div> </div>
</section>
</Show> </Show>
</div> </div>
</div> </div>

View file

@ -248,134 +248,145 @@ export default function DesignationPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="flex flex-col -mx-6 -mt-6 min-h-full"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="p-6 flex-1 max-w-[1600px] mx-auto w-full">
{/* ── Page header ── */} {/* Header & Title */}
<div class="bg-white border-b border-gray-200 px-6 py-4"> <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
<h1 class="text-xl font-semibold text-gray-900">Designations</h1> <div>
<p class="text-sm text-gray-500 mt-0.5">Manage job titles and roles within departments.</p> <h1 class="text-[32px] font-bold text-[#050026] leading-tight">Designation Management</h1>
</div> </div>
<div class="flex items-center gap-3">
{/* ── Tab + action bar ── */} <button class="inline-flex h-11 items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
<div class="bg-white border-b border-gray-200 px-6 flex items-center justify-between sticky top-0 z-10"> Export Data
<div class="flex gap-8">
<button
onClick={() => { setView('list'); setEditingDesignation(null); }}
class={`py-3 border-b-2 text-sm font-medium transition-colors ${
view() === 'list' ? 'border-orange-500 text-orange-600' : 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
Designations List
</button>
<button
onClick={openCreate}
class={`py-3 border-b-2 text-sm font-medium transition-colors ${
view() === 'create' ? 'border-orange-500 text-orange-600' : 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
Add Designation
</button>
<Show when={view() === 'edit' && editingDesignation()}>
<button class="py-3 border-b-2 border-orange-500 text-orange-600 text-sm font-medium">
Edit: {editingDesignation()?.name}
</button> </button>
</Show> <button
class="inline-flex h-11 items-center justify-center rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]"
onClick={openCreate}
>
<span class="mr-2 text-lg leading-none">+</span> Create Designation
</button>
</div>
</div> </div>
{/* Status filter — only in list view */}
<Show when={view() === 'list'}>
<select
value={statusFilter()}
onChange={(e) => { setStatusFilter(e.currentTarget.value as 'active' | 'archived'); setPage(1); }}
class="rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37]"
>
<option value="active">Active</option>
<option value="archived">Archived</option>
</select>
</Show>
</div>
{/* ── Content ── */}
<div class="flex-1">
{/* Create / Edit form */} {/* Create / Edit form */}
<Show when={view() === 'create' || view() === 'edit'}> <Show when={view() === 'create' || view() === 'edit'}>
<div class="bg-white px-6 py-6 border-b border-gray-100"> <div class="bg-white border focus-within:border-[#0a1d37] border-[#e2e6ee] rounded-3xl p-6 mb-8 max-w-4xl shadow-sm">
<h2 class="text-[22px] font-bold text-[#050026] mb-6">
{view() === 'create' ? 'Create New Designation' : `Edit: ${editingDesignation()?.name}`}
</h2>
<FormContent /> <FormContent />
</div> </div>
</Show> </Show>
{/* List */} {/* List */}
<Show when={view() === 'list'}> <Show when={view() === 'list'}>
<div class="p-6"> <section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full">
<Show when={actionError()}> <div class="rounded-[20px] bg-white p-5">
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{actionError()}</div>
</Show> {/* Error Message */}
<Show when={actionError()}>
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{actionError()}</div>
</Show>
<div class="table-card"> {/* Tabs */}
<div class="flex gap-6 mb-6 border-b border-[#e2e6ee]">
<For each={(['active', 'archived'] as const)}>
{(t) => (
<button
onClick={() => { setStatusFilter(t); setPage(1); }}
class={`pb-3 text-[14px] font-bold capitalize transition-colors border-b-2 ${
statusFilter() === t
? 'border-[#050026] text-[#050026]'
: 'border-transparent text-[#8087a0] hover:text-[#050026]'
}`}
>
{t} Designation
</button>
)}
</For>
</div>
{/* Filters Row */}
<div class="flex flex-col gap-4 md:flex-row items-center mb-6">
<div class="relative w-full md:w-[320px]">
<div class="absolute inset-y-0 left-4 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-[#a0aabf]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
type="text"
placeholder="Search designations..."
class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] pl-11 pr-4 text-[14px] text-[#050026] outline-none transition-colors focus:border-[#050026] focus:bg-white"
/>
</div>
<div class="h-11 w-full md:w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb]"></div>
<div class="flex-1"></div>
</div>
{/* Table */}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="data-table w-full text-sm"> <table class="w-full min-w-[1000px] border-collapse">
<thead> <thead>
<tr> <tr class="bg-[#050026] text-left text-white">
<th>ID</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl whitespace-nowrap">DESIGNATION ID</th>
<th>Name</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">DESIGNATION TITLE</th>
<th>Department</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">DEPARTMENT</th>
<th>Description</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">CREATED BY</th>
<th>Active Users</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">CREATED</th>
<th>Active Jobs</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">LAST UPDATED BY</th>
<th>Status</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">LAST UPDATED AT</th>
<th>Created By</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-right rounded-tr-xl whitespace-nowrap">ACTION</th>
<th>Created At</th>
<th>Last Updated By</th>
<th>Last Updated At</th>
<th class="text-right">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<Show when={data.loading}> <Show when={data.loading}>
<tr><td colspan="12" class="py-10 text-center text-sm text-slate-400">Loading</td></tr> <tr><td colspan="8" class="text-center py-12 text-[#8087a0] text-[14px]">Loading designations...</td></tr>
</Show> </Show>
<Show when={!data.loading && data.error}> <Show when={!data.loading && data.error}>
<tr><td colspan="12" class="py-10 text-center text-sm text-red-500">Failed to load. Is the backend running?</td></tr> <tr><td colspan="8" class="text-center py-12 text-red-500 text-[14px]">Failed to load. Is the backend running?</td></tr>
</Show> </Show>
<Show when={!data.loading && !data.error && filtered().length === 0}> <Show when={!data.loading && !data.error && filtered().length === 0}>
<tr><td colspan="12" class="py-10 text-center text-sm text-slate-400">No designations found.</td></tr> <tr><td colspan="8" class="text-center py-12 text-[#8087a0] text-[14px]">No designations found.</td></tr>
</Show> </Show>
<For each={filtered()}> <For each={filtered()}>
{(item) => { {(item) => {
const archived = () => isArchived(item); const archived = () => isArchived(item);
return ( return (
<tr class="hover:bg-slate-50"> <tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc]">
<td class="font-mono text-xs text-slate-500">{(item.designationId || item.id).slice(0, 16)}</td> <td class="px-6 py-4 text-[14px] font-semibold text-[#64748b]">{(item.designationId || item.id).slice(0, 16).toUpperCase()}</td>
<td class="font-semibold text-slate-900">{item.name}</td> <td class="px-6 py-4 text-[14px] font-bold text-[#050026]">{item.name}</td>
<td class="text-slate-500">{deptDisplay(item)}</td> <td class="px-6 py-4 text-[14px] text-[#475569]">{deptDisplay(item)}</td>
<td class="text-slate-500 max-w-xs truncate" title={item.description}>{item.description || '—'}</td> <td class="px-6 py-4 text-[14px] text-[#0ea5e9] hover:underline cursor-pointer">{item.createdBy || 'System'}</td>
<td class="text-slate-500">{item.activeUsersCount ?? 0}</td> <td class="px-6 py-4 text-[14px] text-[#475569]">{fmtDate(item.createdAt)}</td>
<td class="text-slate-500">{item.activeJobsCount ?? 0}</td> <td class="px-6 py-4 text-[14px] text-[#0ea5e9] hover:underline cursor-pointer">{item.updatedBy || item.createdBy || 'System'}</td>
<td> <td class="px-6 py-4 text-[14px] text-[#475569]">{fmtDate(item.updatedAt)}</td>
<span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${archived() ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800'}`}> <td class="px-6 py-4">
{archived() ? 'ARCHIVED' : 'ACTIVE'} <div class="flex items-center justify-end gap-2">
</span> <button
</td> title="Edit"
<td class="text-blue-600 hover:underline"><a href={`mailto:${item.createdBy}`}>{item.createdBy || '—'}</a></td> onClick={() => openEdit(item)}
<td class="text-slate-500">{fmtDate(item.createdAt)}</td> class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
<td class="text-blue-600 hover:underline"><a href={`mailto:${item.updatedBy}`}>{item.updatedBy || '—'}</a></td> >
<td class="text-slate-500">{fmtDate(item.updatedAt)}</td> <Pencil size={14} />
<td>
<div class="flex items-center justify-end gap-1.5">
<button title="Edit" onClick={() => openEdit(item)}
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors">
<Pencil size={14} class="text-gray-600" />
</button> </button>
<Show when={!archived()}> <Show when={!archived()}>
<button title="Archive" disabled={busy() === item.id} onClick={() => handleArchive(item.id)} <button
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors"> title="Archive"
<Archive size={14} class="text-gray-600" /> disabled={busy() === item.id}
onClick={() => handleArchive(item.id)}
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
>
<Archive size={14} />
</button> </button>
</Show> </Show>
<Show when={archived()}> <Show when={archived()}>
<button title="Restore" disabled={busy() === item.id} onClick={() => handleRestore(item.id)} <button
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors"> title="Restore"
<RotateCcw size={14} class="text-green-600" /> disabled={busy() === item.id}
onClick={() => handleRestore(item.id)}
class="flex h-8 w-8 items-center justify-center rounded-lg border border-green-200 bg-green-50 text-[#00c853] hover:bg-green-100 transition-colors"
>
<RotateCcw size={14} />
</button> </button>
</Show> </Show>
</div> </div>
@ -387,24 +398,32 @@ export default function DesignationPage() {
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<Show when={totalPages() > 1}> {/* Pagination */}
<div class="mt-4 flex items-center justify-between border-t border-gray-200 pt-4"> <Show when={totalPages() > 1}>
<span class="text-sm text-gray-500">Page {page()} of {totalPages()}</span> <div class="mt-6 flex items-center justify-between border-t border-[#e2e6ee] pt-4">
<div class="flex gap-2"> <span class="text-[13px] font-medium text-[#8087a0]">Page {page()} of {totalPages()}</span>
<button disabled={page() === 1} onClick={() => setPage((p) => p - 1)} <div class="flex items-center gap-2">
class="rounded-lg border border-gray-200 p-2 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-40 transition-colors"> <button
<ChevronLeft size={16} /> disabled={page() === 1}
</button> onClick={() => setPage((p) => p - 1)}
<button disabled={page() >= totalPages()} onClick={() => setPage((p) => p + 1)} class="flex h-9 w-9 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
class="rounded-lg border border-gray-200 p-2 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-40 transition-colors"> >
<ChevronRight size={16} /> <ChevronLeft size={16} />
</button> </button>
<button
disabled={page() >= totalPages()}
onClick={() => setPage((p) => p + 1)}
class="flex h-9 w-9 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
<ChevronRight size={16} />
</button>
</div>
</div> </div>
</div> </Show>
</Show>
</div> </div>
</section>
</Show> </Show>
</div> </div>
</div> </div>

View file

@ -124,58 +124,50 @@ export default function EmployeesPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="flex flex-col -mx-6 -mt-6 min-h-full"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
<div class="p-6 flex-1 max-w-[1600px] mx-auto w-full">
{/* ── Page header ── */} {/* Header & Title */}
<div class="bg-white border-b border-gray-200 px-6 py-4"> <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
<h1 class="text-xl font-semibold text-gray-900">Internal User Management</h1> <div>
<p class="text-sm text-gray-500 mt-0.5">Manage internal team members and their access.</p> <h1 class="text-[32px] font-bold text-[#050026] leading-tight">Employee Management</h1>
</div> </div>
<div class="flex items-center gap-3">
{/* ── Tab bar ── */} <button class="inline-flex h-11 items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
<div class="bg-white border-b border-gray-200 px-6 flex items-center justify-between sticky top-0 z-10"> Export Data
<div class="flex gap-8"> </button>
<button <button
onClick={() => setView('list')} class="inline-flex h-11 items-center justify-center rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]"
class={`py-3 border-b-2 text-sm font-medium transition-colors ${view() === 'list' ? 'border-orange-500 text-orange-600' : 'border-transparent text-gray-500 hover:text-gray-700'}`} onClick={() => { resetForm(); setView('create'); }}
> >
View Internal Users <span class="mr-2 text-lg leading-none">+</span> Add Employee
</button> </button>
<button </div>
onClick={() => { resetForm(); setView('create'); }}
class={`py-3 border-b-2 text-sm font-medium transition-colors ${view() === 'create' ? 'border-orange-500 text-orange-600' : 'border-transparent text-gray-500 hover:text-gray-700'}`}
>
Add Internal User
</button>
</div> </div>
</div>
{/* ── Content ── */}
<div class="flex-1">
{/* Create form */}
<Show when={view() === 'create'}> <Show when={view() === 'create'}>
<div class="bg-white border-b border-gray-100 px-6 py-6"> {/* Create form */}
<form onSubmit={handleCreate} class="max-w-4xl space-y-6"> <div class="bg-white border focus-within:border-[#0a1d37] border-[#e2e6ee] rounded-3xl p-6 mb-8 max-w-4xl shadow-sm">
<h2 class="text-[22px] font-bold text-[#050026] mb-6">Create New Employee</h2>
<form onSubmit={handleCreate} class="space-y-6">
<div class="grid grid-cols-1 gap-6 md:grid-cols-2"> <div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700">Full Name *</label> <label class="mb-1.5 block text-sm font-semibold text-[#383e5c]">Full Name *</label>
<input type="text" required placeholder="e.g. John Doe" value={formName()} onInput={(e) => setFormName(e.currentTarget.value)} <input type="text" required placeholder="e.g. John Doe" value={formName()} onInput={(e) => setFormName(e.currentTarget.value)}
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]" /> class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] px-4 text-[14px] outline-none transition-colors focus:border-[#050026] focus:bg-white" />
</div> </div>
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700">Email *</label> <label class="mb-1.5 block text-sm font-semibold text-[#383e5c]">Email *</label>
<input type="email" required placeholder="e.g. john@company.com" value={formEmail()} onInput={(e) => setFormEmail(e.currentTarget.value)} <input type="email" required placeholder="e.g. john@company.com" value={formEmail()} onInput={(e) => setFormEmail(e.currentTarget.value)}
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]" /> class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] px-4 text-[14px] outline-none transition-colors focus:border-[#050026] focus:bg-white" />
</div> </div>
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700">Password *</label> <label class="mb-1.5 block text-sm font-semibold text-[#383e5c]">Password *</label>
<input type="password" required placeholder="Set a password" value={formPassword()} onInput={(e) => setFormPassword(e.currentTarget.value)} <input type="password" required placeholder="Set a password" value={formPassword()} onInput={(e) => setFormPassword(e.currentTarget.value)}
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]" /> class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] px-4 text-[14px] outline-none transition-colors focus:border-[#050026] focus:bg-white" />
</div> </div>
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700">Role</label> <label class="mb-1.5 block text-sm font-semibold text-[#383e5c]">Internal Role</label>
<select value={formRoleId()} onChange={(e) => setFormRoleId(e.currentTarget.value)} <select value={formRoleId()} onChange={(e) => setFormRoleId(e.currentTarget.value)}
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]"> class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] px-4 text-[14px] outline-none transition-colors focus:border-[#050026] focus:bg-white appearance-none">
<option value="">Select a role</option> <option value="">Select a role</option>
<Show when={roles.loading}><option disabled>Loading roles</option></Show> <Show when={roles.loading}><option disabled>Loading roles</option></Show>
<For each={roles() ?? []}>{(r) => <option value={r.id}>{r.name}</option>}</For> <For each={roles() ?? []}>{(r) => <option value={r.id}>{r.name}</option>}</For>
@ -183,87 +175,144 @@ export default function EmployeesPage() {
</div> </div>
</div> </div>
<Show when={createError()}> <Show when={createError()}>
<p class="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{createError()}</p> <p class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{createError()}</p>
</Show> </Show>
<div class="flex justify-end gap-3 border-t border-gray-100 pt-4"> <div class="flex justify-end gap-3 pt-4 border-t border-[#e2e6ee]">
<button type="button" onClick={() => { resetForm(); setView('list'); }} <button type="button" onClick={() => { resetForm(); setView('list'); }}
class="rounded-lg border border-gray-200 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"> class="h-11 rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] hover:bg-[#f8f9fc] transition-colors">
Cancel Cancel
</button> </button>
<button type="submit" disabled={creating()} <button type="submit" disabled={creating()}
class="btn-primary"> class="h-11 rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white hover:bg-[#0a0044] transition-colors disabled:opacity-70">
{creating() ? 'Creating…' : 'Create Internal User'} {creating() ? 'Creating…' : 'Create Employee'}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</Show> </Show>
{/* List */}
<Show when={view() === 'list'}> <Show when={view() === 'list'}>
<div class="p-6"> {/* 4 KPI Cards Row */}
<Show when={actionError()}> <div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{actionError()}</div> <div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
</Show> <p class="text-[13px] text-[#8087a0] font-medium leading-snug">Total Employees</p>
<p class="mt-3 text-[32px] font-bold text-[#050026] leading-none">{employees()?.length || 0}</p>
</div>
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
<p class="text-[13px] text-[#8087a0] font-medium leading-snug">Active Employees</p>
<p class="mt-3 text-[32px] font-bold text-[#00c853] leading-none">{employees()?.filter((e) => isActive(e)).length || 0}</p>
</div>
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
<p class="text-[13px] text-[#8087a0] font-medium leading-snug">Inactive Employees</p>
<p class="mt-3 text-[32px] font-bold text-[#ff6e30] leading-none">{employees()?.filter((e) => !isActive(e)).length || 0}</p>
</div>
<div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
<p class="text-[13px] text-[#8087a0] font-medium leading-snug">Leave Today</p>
<p class="mt-3 text-[32px] font-bold text-[#64748b] leading-none">0</p>
</div>
</div>
<div class="table-card"> {/* Main Table Section */}
<section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full">
<div class="rounded-[20px] bg-white p-5">
{/* Error Message */}
<Show when={actionError()}>
<div class="mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{actionError()}</div>
</Show>
{/* Filters Row */}
<div class="flex items-center gap-4 mb-6">
<div class="relative w-[320px]">
<div class="absolute inset-y-0 left-4 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-[#a0aabf]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
type="text"
placeholder="Search employees..."
class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] pl-11 pr-4 text-[14px] text-[#050026] outline-none transition-colors focus:border-[#050026] focus:bg-white"
/>
</div>
<div class="h-11 w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb]"></div>
<div class="h-11 w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb]"></div>
</div>
{/* Table */}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="data-table w-full text-sm"> <table class="w-full min-w-[1000px] border-collapse">
<thead> <thead>
<tr> <tr class="bg-[#050026] text-left text-white">
<th>Name</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl whitespace-nowrap">EMPLOYEE ID</th>
<th>Email</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">NAME & TITLE</th>
<th>Role</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">DEPARTMENT</th>
<th>Status</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">DESIGNATION</th>
<th class="text-right">Actions</th> <th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">INTERNAL ROLE</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">JOINING DATE</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-center whitespace-nowrap">STATUS</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-right rounded-tr-xl whitespace-nowrap">ACTION</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<Show when={employees.loading}> <Show when={employees.loading}>
<tr><td colspan="5" class="py-10 text-center text-sm text-slate-400">Loading</td></tr> <tr><td colspan="8" class="text-center py-12 text-[#8087a0] text-sm">Loading employees...</td></tr>
</Show> </Show>
<Show when={!employees.loading && employees.error}> <Show when={!employees.loading && employees.error}>
<tr><td colspan="5" class="py-10 text-center text-sm text-red-500">Failed to load. Is the backend running?</td></tr> <tr><td colspan="8" class="text-center py-12 text-red-500 text-sm">Failed to load. Is the backend running?</td></tr>
</Show> </Show>
<Show when={!employees.loading && !employees.error && (employees()?.length ?? 0) === 0}> <Show when={!employees.loading && !employees.error && (employees()?.length ?? 0) === 0}>
<tr><td colspan="5" class="py-10 text-center text-sm text-slate-400">No internal users found.</td></tr> <tr><td colspan="8" class="text-center py-12 text-[#8087a0] text-sm">No employees found. Add the first one.</td></tr>
</Show> </Show>
<For each={employees()}> <For each={employees() ?? []}>
{(item) => { {(item) => {
const active = () => isActive(item); const active = () => isActive(item);
return ( return (
<tr class="hover:bg-slate-50"> <tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc]">
<td class="font-semibold text-slate-900">{employeeName(item)}</td> <td class="px-6 py-4 text-[14px] font-semibold text-[#64748b]">EMP-{item.id.slice(0, 6).toUpperCase()}</td>
<td class="text-slate-500">{item.email}</td> <td class="px-6 py-4">
<td class="text-slate-500">{roleName(item)}</td> <div class="flex items-center gap-3">
<td> <div class="h-10 w-10 flex items-center justify-center rounded-full bg-[#f1f5f9] text-[#050026] font-bold uppercase shrink-0">
<span class={`inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium ${active() ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-700'}`}> {employeeName(item).charAt(0)}
{active() ? 'ACTIVE' : 'INACTIVE'} </div>
<div>
<div class="text-[14px] font-bold text-[#050026]">{employeeName(item)}</div>
<div class="text-[13px] text-[#64748b]">{item.email}</div>
</div>
</div>
</td>
<td class="px-6 py-4 text-[14px] text-[#475569]">Operations</td>
<td class="px-6 py-4 text-[14px] text-[#475569]"></td>
<td class="px-6 py-4 text-[14px] text-[#475569]">{roleName(item)}</td>
<td class="px-6 py-4 text-[14px] text-[#475569]">Oct 24, 2023</td>
<td class="px-6 py-4 text-center">
<span class={`inline-flex items-center justify-center rounded-lg px-3 py-1 text-[12px] font-bold ${
active() ? 'bg-[#e6f9ed] text-[#00c853]' : 'bg-[#fff0eb] text-[#ff6e30]'
}`}>
{active() ? 'Active' : 'Inactive'}
</span> </span>
</td> </td>
<td> <td class="px-6 py-4">
<div class="flex items-center justify-end gap-1.5"> <div class="flex items-center justify-end gap-2">
<A href={`/admin/employees/${item.id}/edit`}
class="action-btn flex items-center justify-center hover:bg-gray-50 transition-colors" title="Edit">
<Pencil size={14} class="text-gray-600" />
</A>
<button <button
title={active() ? 'Deactivate' : 'Activate'} title={active() ? 'Deactivate' : 'Activate'}
disabled={toggling() === item.id} disabled={toggling() === item.id}
onClick={() => handleToggleStatus(item.id, active())} onClick={() => handleToggleStatus(item.id, active())}
class={`action-btn flex items-center justify-center transition-colors ${active() ? 'border-red-100 bg-red-50 hover:bg-red-100' : 'hover:bg-green-50 border-green-100 bg-green-50'}`} class={`flex h-8 w-8 items-center justify-center rounded-lg border transition-colors ${
active() ? 'border-red-100 bg-red-50 text-red-600 hover:bg-red-100' : 'border-green-100 bg-green-50 text-green-600 hover:bg-green-100'
}`}
> >
<Show when={active()} fallback={<UserCheck size={14} class="text-green-600" />}> <Show when={active()} fallback={<UserCheck size={14} />}>
<UserX size={14} class="text-red-600" /> <UserX size={14} />
</Show> </Show>
</button> </button>
<button <button
title="Delete" title="Delete"
disabled={deleting() === item.id} disabled={deleting() === item.id}
onClick={() => handleDelete(item.id, employeeName(item))} onClick={() => handleDelete(item.id, employeeName(item))}
class="action-btn flex items-center justify-center border-red-100 bg-red-50 hover:bg-red-100 transition-colors" class="flex h-8 w-8 items-center justify-center rounded-lg border border-red-100 bg-red-50 text-red-600 hover:bg-red-100 transition-colors"
> >
<Trash2 size={14} class="text-red-600" /> <Trash2 size={14} />
</button> </button>
</div> </div>
</td> </td>
@ -275,7 +324,7 @@ export default function EmployeesPage() {
</table> </table>
</div> </div>
</div> </div>
</div> </section>
</Show> </Show>
</div> </div>
</div> </div>

View file

@ -107,169 +107,255 @@ export default function UsersPage() {
return ( return (
<AdminShell> <AdminShell>
<div class="flex flex-col -mx-6 -mt-6 min-h-full"> <div class="flex flex-col -mx-6 -mt-6 min-h-full">
{/* White page header */} <div class="p-6 flex-1 max-w-[1600px] mx-auto w-full">
<div class="bg-white border-b border-gray-200 px-6 py-4"> {/* Header & Title */}
<h1 class="text-xl font-semibold text-gray-900">External User Management</h1> <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between mb-8">
<p class="text-sm text-gray-500 mt-0.5">Manage all external platform users.</p> <div>
</div> <h1 class="text-[32px] font-bold text-[#050026] leading-tight">User Management</h1>
</div>
<div class="flex items-center gap-3">
<button class="inline-flex h-11 items-center justify-center rounded-xl border border-[#d9dde6] bg-white px-6 text-[14px] font-semibold text-[#050026] transition-colors hover:bg-[#f8f9fc]">
Export Data
</button>
<button class="inline-flex h-11 items-center justify-center rounded-xl bg-[#050026] px-6 text-[14px] font-semibold text-white transition-colors hover:bg-[#0a0044]">
<span class="mr-2 text-lg leading-none">+</span> Add User
</button>
</div>
</div>
{/* Tabs */}
<div class="bg-white border-b border-gray-200 px-6 flex items-center gap-8 sticky top-0 z-10">
<button
class={`py-3 border-b-2 text-sm font-medium transition-colors ${activeTab() === 'list' ? 'border-orange-500 text-orange-600' : 'border-transparent text-gray-500 hover:text-gray-700'}`}
type="button"
onClick={() => setActiveTab('list')}
>
User List
</button>
<button
class={`py-3 border-b-2 text-sm font-medium transition-colors ${activeTab() === 'view' ? 'border-orange-500 text-orange-600' : 'border-transparent text-gray-500 hover:text-gray-700'}`}
type="button"
disabled={!selectedUser()}
onClick={() => setActiveTab('view')}
>
View Details
</button>
</div>
{/* Content */}
<div class="flex-1 p-6">
<Show when={activeTab() === 'list'}> <Show when={activeTab() === 'list'}>
{/* Filters */} {/* KPI Cards Row */}
<div class="mb-4 flex flex-wrap items-center gap-3"> <div class="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<input <div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
type="text" <p class="text-[13px] text-[#8087a0] font-medium leading-snug">Total Users</p>
placeholder="Search by name or email..." <p class="mt-3 text-[32px] font-bold text-[#050026] leading-none">{users()?.length || 0}</p>
value={search()} </div>
onInput={(e) => { <div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
setSearch(e.currentTarget.value); <p class="text-[13px] text-[#8087a0] font-medium leading-snug">Active Users</p>
setCurrentPage(1); <p class="mt-3 text-[32px] font-bold text-[#00c853] leading-none">{users()?.filter((u) => u.status === 'ACTIVE').length || 0}</p>
}} </div>
class="rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37] w-64" <div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
/> <p class="text-[13px] text-[#8087a0] font-medium leading-snug">Inactive Users</p>
<select <p class="mt-3 text-[32px] font-bold text-[#ff6e30] leading-none">{users()?.filter((u) => u.status === 'INACTIVE').length || 0}</p>
value={filterRole()} </div>
onChange={(e) => { <div class="rounded-2xl border border-[#e2e6ee] bg-white p-5 shadow-sm">
setFilterRole(e.currentTarget.value); <p class="text-[13px] text-[#8087a0] font-medium leading-snug">New Users Today</p>
setCurrentPage(1); <p class="mt-3 text-[32px] font-bold text-[#64748b] leading-none">0</p>
}} </div>
class="rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37]"
>
<option value="">All Roles</option>
<For each={ROLE_OPTIONS}>
{(r) => <option value={r}>{r}</option>}
</For>
</select>
<select
value={filterStatus()}
onChange={(e) => {
setFilterStatus(e.currentTarget.value);
setCurrentPage(1);
}}
class="rounded-lg border border-gray-200 px-3 py-2 text-sm outline-none focus:border-[#0a1d37]"
>
<option value="">All Status</option>
<option value="ACTIVE">ACTIVE</option>
<option value="INACTIVE">INACTIVE</option>
<option value="PENDING">PENDING</option>
</select>
<Show when={!users.loading}>
<span class="text-xs text-slate-500 ml-auto">
Showing {paginated().length} of {filtered().length} users
</span>
</Show>
</div> </div>
<div class="table-card"> {/* Main Table Section */}
<div class="overflow-x-auto"> <section class="rounded-[24px] border border-[#e2e6ee] bg-[#f7f7f8] p-1.5 h-full">
<table class="data-table w-full text-sm"> <div class="rounded-[20px] bg-white p-5">
<thead>
<tr> {/* Tabs */}
<th>User ID</th> <div class="flex gap-6 mb-6 border-b border-[#e2e6ee]">
<th>Name</th> <For each={['All Users', 'Active', 'Inactive']}>
<th>Email</th> {(t) => {
<th>Registration Role</th> const isActiveTab = (t === 'All Users' && !filterStatus()) ||
<th>Status</th> (t === 'Active' && filterStatus() === 'ACTIVE') ||
<th>Created</th> (t === 'Inactive' && filterStatus() === 'INACTIVE');
<th class="text-right">Actions</th> return (
</tr> <button
</thead> onClick={() => {
<tbody> if (t === 'All Users') setFilterStatus('');
<Show when={users.loading}> else if (t === 'Active') setFilterStatus('ACTIVE');
<tr><td colspan="7" class="text-center py-8 text-slate-500">Loading...</td></tr> else if (t === 'Inactive') setFilterStatus('INACTIVE');
</Show> setCurrentPage(1);
<Show when={!users.loading && users.error}> }}
<tr><td colspan="7" class="text-center py-8 text-red-700">Failed to load. Is the backend running?</td></tr> class={`pb-3 text-[14px] font-bold transition-colors border-b-2 ${
</Show> isActiveTab
<Show when={!users.loading && !users.error && paginated().length === 0}> ? 'border-[#050026] text-[#050026]'
<tr><td colspan="7" class="text-center py-8 text-slate-400">No users found.</td></tr> : 'border-transparent text-[#8087a0] hover:text-[#050026]'
</Show> }`}
<Show when={!users.loading && !users.error && paginated().length > 0}> >
{t}
</button>
);
}}
</For>
</div>
{/* Filters Row */}
<div class="flex flex-col gap-4 md:flex-row items-center mb-6">
<div class="relative w-full md:w-[320px]">
<div class="absolute inset-y-0 left-4 flex items-center pointer-events-none">
<svg class="h-5 w-5 text-[#a0aabf]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<input
type="text"
placeholder="Search users..."
value={search()}
onInput={(e) => { setSearch(e.currentTarget.value); setCurrentPage(1); }}
class="h-11 w-full rounded-xl border border-[#d9dde6] bg-[#f9fafb] pl-11 pr-4 text-[14px] text-[#050026] outline-none transition-colors focus:border-[#050026] focus:bg-white"
/>
</div>
<select
value={filterRole()}
onChange={(e) => { setFilterRole(e.currentTarget.value); setCurrentPage(1); }}
class="h-11 w-full md:w-[200px] rounded-xl border border-[#d9dde6] bg-[#f9fafb] px-4 text-[14px] outline-none transition-colors focus:border-[#050026] focus:bg-white appearance-none"
>
<option value="">All Roles</option>
<For each={ROLE_OPTIONS}>{(r) => <option value={r}>{r}</option>}</For>
</select>
<div class="flex-1"></div>
<Show when={!users.loading}>
<span class="text-[13px] text-[#8087a0] font-medium">
Showing {paginated().length} of {filtered().length} users
</span>
</Show>
</div>
{/* Table */}
<div class="overflow-x-auto">
<table class="w-full min-w-[1000px] border-collapse">
<thead>
<tr class="bg-[#050026] text-left text-white">
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider rounded-tl-xl whitespace-nowrap">USER ID</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">USER DETAILS</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">ROLE</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">JOINING DATE</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider whitespace-nowrap">LAST LOGIN</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-center whitespace-nowrap">STATUS</th>
<th class="px-6 py-4 text-[11px] font-bold uppercase tracking-wider text-right rounded-tr-xl whitespace-nowrap">ACTION</th>
</tr>
</thead>
<tbody>
<Show when={users.loading}>
<tr><td colspan="7" class="text-center py-12 text-[#8087a0] text-[14px]">Loading users...</td></tr>
</Show>
<Show when={!users.loading && users.error}>
<tr><td colspan="7" class="text-center py-12 text-red-500 text-[14px]">Failed to load. Is the backend running?</td></tr>
</Show>
<Show when={!users.loading && !users.error && paginated().length === 0}>
<tr><td colspan="7" class="text-center py-12 text-[#8087a0] text-[14px]">No users found.</td></tr>
</Show>
<For each={paginated()}> <For each={paginated()}>
{(item) => ( {(item) => (
<tr class="hover:bg-slate-50"> <tr class="border-b border-[#e2e6ee] bg-white transition-colors hover:bg-[#f8f9fc]">
<td class="text-slate-500" style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace">{shortId(item.id)}</td> <td class="px-6 py-4 text-[14px] font-semibold text-[#64748b]">USR-{item.id.slice(0, 6).toUpperCase()}</td>
<td class="font-semibold text-slate-900">{item.name || item.full_name || '—'}</td> <td class="px-6 py-4">
<td class="text-slate-500">{item.email}</td> <div class="flex items-center gap-3">
<td class="text-slate-500">{registrationRole(item)}</td> <div class="h-10 w-10 flex items-center justify-center rounded-full bg-[#f1f5f9] text-[#050026] font-bold uppercase shrink-0">
<td> {(item.name || item.full_name || 'U').charAt(0)}
<StatusBadge status={item.status} /> </div>
<div>
<div class="text-[14px] font-bold text-[#050026]">{item.name || item.full_name || '—'}</div>
<div class="text-[13px] text-[#64748b]">{item.email}</div>
</div>
</div>
</td> </td>
<td class="text-slate-500"> <td class="px-6 py-4 text-[14px] text-[#475569]">{registrationRole(item)}</td>
{(item.created_at || item.createdAt) <td class="px-6 py-4 text-[14px] text-[#475569]">
? new Date((item.created_at || item.createdAt)!).toLocaleDateString() {(item.created_at || item.createdAt) ? new Date((item.created_at || item.createdAt)!).toLocaleDateString() : '—'}
: '—'}
</td> </td>
<td> <td class="px-6 py-4 text-[14px] text-[#475569]"></td>
<div class="flex items-center justify-end gap-1"> <td class="px-6 py-4 text-center">
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/users/details/${item.id}`} title="Open Detail Page"></A> <span class={`inline-flex items-center justify-center rounded-lg px-3 py-1 text-[12px] font-bold ${
<button class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" type="button" onClick={() => onView(item)} title="Quick View">👁</button> item.status === 'ACTIVE' ? 'bg-[#e6f9ed] text-[#00c853]' : item.status === 'PENDING' ? 'bg-amber-100 text-amber-700' : 'bg-[#fff0eb] text-[#ff6e30]'
<A class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 text-sm" href={`/admin/users/${item.id}/edit`} title="Edit User"></A> }`}>
<button class="rounded p-1.5 text-red-500 hover:bg-red-50 hover:text-red-700 text-sm" type="button" title="Delete User">🗑</button> {item.status.charAt(0) + item.status.slice(1).toLowerCase()}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center justify-end gap-2">
<button
title="View Details"
onClick={() => onView(item)}
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</button>
<button
title="Edit"
class="flex h-8 w-8 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] transition-colors"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
</button>
</div> </div>
</td> </td>
</tr> </tr>
)} )}
</For> </For>
</Show> </tbody>
</tbody> </table>
</table> </div>
{/* Pagination */}
<Show when={totalPages() > 1}>
<div class="mt-6 flex items-center justify-between border-t border-[#e2e6ee] pt-4">
<button
disabled={currentPage() === 1}
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
class="flex h-9 px-4 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[13px] font-medium text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
Previous
</button>
<span class="text-[13px] font-medium text-[#8087a0]">Page {currentPage()} of {totalPages()}</span>
<button
disabled={currentPage() >= totalPages()}
onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}
class="flex h-9 px-4 items-center justify-center rounded-lg border border-[#e2e6ee] bg-white text-[13px] font-medium text-[#64748b] hover:bg-[#f8f9fc] hover:text-[#050026] disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
>
Next
</button>
</div>
</Show>
</div> </div>
<div class="admin-pagination"> </section>
<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={currentPage() <= 1} onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}>
Previous
</button>
<span>Page {currentPage()} of {totalPages()}</span>
<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={currentPage() >= totalPages()} onClick={() => setCurrentPage((p) => Math.min(totalPages(), p + 1))}>
Next
</button>
</div>
</div>
</Show> </Show>
{/* Details View */}
<Show when={activeTab() === 'view'}> <Show when={activeTab() === 'view'}>
<div class="table-card"> <div class="bg-white rounded-3xl border border-[#e2e6ee] p-6 shadow-sm">
<Show when={selectedUser()} fallback={<p class="notice">Select a user from list to view details.</p>}> <Show when={selectedUser()}>
<div class="list-header"> <div class="flex items-center justify-between mb-8 border-b border-[#e2e6ee] pb-6">
<h2>User Details</h2> <h2 class="text-[22px] font-bold text-[#050026]">User Details</h2>
<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={() => setActiveTab('list')}>Back To List</button> <button
class="h-10 rounded-xl border border-[#d9dde6] bg-white px-5 text-[14px] font-semibold text-[#050026] hover:bg-[#f8f9fc] transition-colors"
type="button"
onClick={() => setActiveTab('list')}
>
Back To List
</button>
</div> </div>
<div class="grid" style="margin-top:0"> <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div class="list-item"> <div class="bg-[#f9fafb] p-6 rounded-2xl border border-[#e2e6ee]">
<h3>Profile</h3> <h3 class="text-[16px] font-bold text-[#050026] mb-4">Profile Information</h3>
<p><strong>User ID:</strong> {selectedUser()!.id}</p> <div class="space-y-4">
<p><strong>Name:</strong> {selectedUser()!.name || selectedUser()!.full_name || '—'}</p> <div><p class="text-[13px] text-[#64748b] font-medium">User ID</p><p class="text-[15px] font-semibold text-[#050026] mt-1">{selectedUser()!.id}</p></div>
<p><strong>Email:</strong> {selectedUser()!.email}</p> <div><p class="text-[13px] text-[#64748b] font-medium">Name</p><p class="text-[15px] font-semibold text-[#050026] mt-1">{selectedUser()!.name || selectedUser()!.full_name || '—'}</p></div>
<p><strong>Role:</strong> {registrationRole(selectedUser()!)}</p> <div><p class="text-[13px] text-[#64748b] font-medium">Email</p><p class="text-[15px] font-semibold text-[#050026] mt-1">{selectedUser()!.email}</p></div>
<p><strong>Status:</strong> {selectedUser()!.status}</p> <div><p class="text-[13px] text-[#64748b] font-medium">Role</p><p class="text-[15px] font-semibold text-[#050026] mt-1">{registrationRole(selectedUser()!)}</p></div>
<div>
<p class="text-[13px] text-[#64748b] font-medium mb-1">Status</p>
<span class={`inline-flex items-center justify-center rounded-lg px-3 py-1 text-[12px] font-bold ${
selectedUser()!.status === 'ACTIVE' ? 'bg-[#e6f9ed] text-[#00c853]' : selectedUser()!.status === 'PENDING' ? 'bg-amber-100 text-amber-700' : 'bg-[#fff0eb] text-[#ff6e30]'
}`}>
{selectedUser()!.status.charAt(0) + selectedUser()!.status.slice(1).toLowerCase()}
</span>
</div>
</div>
</div> </div>
<div class="list-item"> <div class="bg-[#f9fafb] p-6 rounded-2xl border border-[#e2e6ee]">
<h3>Account</h3> <h3 class="text-[16px] font-bold text-[#050026] mb-4">Account Metadata</h3>
<p><strong>Created:</strong> {(selectedUser()!.created_at || selectedUser()!.createdAt) ? new Date((selectedUser()!.created_at || selectedUser()!.createdAt)!).toLocaleString() : '—'}</p> <div class="space-y-4">
<p><strong>Role ID:</strong> {selectedUser()!.roleId || '—'}</p> <div>
<div class="actions"> <p class="text-[13px] text-[#64748b] font-medium">Created</p>
<A class="btn-primary" href={`/admin/users/${selectedUser()!.id}/edit`}>Edit User</A> <p class="text-[15px] font-semibold text-[#050026] mt-1">{(selectedUser()!.created_at || selectedUser()!.createdAt) ? new Date((selectedUser()!.created_at || selectedUser()!.createdAt)!).toLocaleString() : '—'}</p>
<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" type="button">Deactivate</button> </div>
<div><p class="text-[13px] text-[#64748b] font-medium">Role ID</p><p class="text-[15px] font-semibold text-[#050026] mt-1">{selectedUser()!.roleId || '—'}</p></div>
<div class="pt-6 border-t border-[#e2e6ee] mt-6 gap-3 flex">
<A class="h-10 rounded-xl bg-[#050026] px-5 flex items-center justify-center text-[14px] font-semibold text-white hover:bg-[#0a0044] transition-colors" href={`/admin/users/${selectedUser()!.id}/edit`}>Edit User</A>
<button class="h-10 rounded-xl border border-red-200 bg-red-50 flex items-center justify-center px-5 text-[14px] font-semibold text-red-600 hover:bg-red-100 transition-colors" type="button">Deactivate</button>
</div>
</div> </div>
</div> </div>
</div> </div>