Wire department and designation management to module CRUD backend

This commit is contained in:
Ashwin Kumar 2026-03-25 21:32:03 +01:00
parent 94d4623248
commit 0996f12227
5 changed files with 556 additions and 229 deletions

View file

@ -67,30 +67,30 @@ export async function updateApproval(id: string, patch: Partial<ApprovalCase>) {
return parse<ApprovalCase>(res); return parse<ApprovalCase>(res);
} }
export async function listModuleRecords(moduleKey: string, query?: { q?: string; status?: string }) { export async function listModuleRecords<T extends CrudRecord = CrudRecord>(moduleKey: string, query?: { q?: string; status?: string }) {
const qp = new URLSearchParams(); const qp = new URLSearchParams();
if (query?.q) qp.set('q', query.q); if (query?.q) qp.set('q', query.q);
if (query?.status) qp.set('status', query.status); if (query?.status) qp.set('status', query.status);
const res = await fetch(`/api/admin/modules/${encodeURIComponent(moduleKey)}/records${qp.toString() ? `?${qp.toString()}` : ''}`); const res = await fetch(`/api/admin/modules/${encodeURIComponent(moduleKey)}/records${qp.toString() ? `?${qp.toString()}` : ''}`);
return parse<CrudRecord[]>(res); return parse<T[]>(res);
} }
export async function createModuleRecord(moduleKey: string, payload: Partial<CrudRecord>) { export async function createModuleRecord<T extends CrudRecord = CrudRecord>(moduleKey: string, payload: Partial<T>) {
const res = await fetch(`/api/admin/modules/${encodeURIComponent(moduleKey)}/records`, { const res = await fetch(`/api/admin/modules/${encodeURIComponent(moduleKey)}/records`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
return parse<CrudRecord>(res); return parse<T>(res);
} }
export async function updateModuleRecord(moduleKey: string, id: string, patch: Partial<CrudRecord>) { export async function updateModuleRecord<T extends CrudRecord = CrudRecord>(moduleKey: string, id: string, patch: Partial<T>) {
const res = await fetch(`/api/admin/modules/${encodeURIComponent(moduleKey)}/records/${encodeURIComponent(id)}`, { const res = await fetch(`/api/admin/modules/${encodeURIComponent(moduleKey)}/records/${encodeURIComponent(id)}`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(patch), body: JSON.stringify(patch),
}); });
return parse<CrudRecord>(res); return parse<T>(res);
} }
export async function deleteModuleRecord(moduleKey: string, id: string) { export async function deleteModuleRecord(moduleKey: string, id: string) {

View file

@ -62,7 +62,12 @@ const filterByQuery = <T extends { id: string; status?: string; applicantName?:
}); });
}; };
const createCrudService = (seed: CrudRecord[]): CrudService<CrudRecord> => { const createCrudService = (
seed: CrudRecord[],
options?: {
createFromPayload?: (payload: Partial<CrudRecord>, currentRows: CrudRecord[]) => CrudRecord;
},
): CrudService<CrudRecord> => {
let rows = [...seed]; let rows = [...seed];
return { return {
async list(query) { async list(query) {
@ -72,12 +77,15 @@ const createCrudService = (seed: CrudRecord[]): CrudService<CrudRecord> => {
return rows.find((r) => r.id === id) ?? null; return rows.find((r) => r.id === id) ?? null;
}, },
async create(payload) { async create(payload) {
const item: CrudRecord = { const item: CrudRecord =
id: `ADM-${Date.now()}`, options?.createFromPayload?.(payload, rows) ??
name: payload.name || 'New Item', ({
status: payload.status === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE', ...(payload as CrudRecord),
updatedAt: nowIso(), id: String(payload.id || `ADM-${Date.now()}`),
}; name: payload.name || 'New Item',
status: payload.status === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE',
updatedAt: nowIso(),
} as CrudRecord);
rows = [item, ...rows]; rows = [item, ...rows];
return item; return item;
}, },
@ -217,6 +225,32 @@ export const genericCrudService = createCrudService(mockCrudRows);
const moduleCrudServices = new Map<string, CrudService<CrudRecord>>(); const moduleCrudServices = new Map<string, CrudService<CrudRecord>>();
function seedRowsForModule(moduleKey: string): CrudRecord[] { function seedRowsForModule(moduleKey: string): CrudRecord[] {
if (moduleKey === 'department') {
return [
{ id: 'eng-001', name: 'Engineering', code: 'ENG-001', description: 'Software development and technical operations', totalEmployees: 45, status: 'ACTIVE', createdDate: '2024-01-15', updatedAt: nowIso() },
{ id: 'mkt-002', name: 'Marketing', code: 'MKT-002', description: 'Brand management and customer acquisition', totalEmployees: 28, status: 'ACTIVE', createdDate: '2024-01-20', updatedAt: nowIso() },
{ id: 'sal-003', name: 'Sales', code: 'SAL-003', description: 'Revenue generation and client relations', totalEmployees: 35, status: 'ACTIVE', createdDate: '2024-02-01', updatedAt: nowIso() },
{ id: 'hr-004', name: 'Human Resources', code: 'HR-004', description: 'Employee management and recruitment', totalEmployees: 12, status: 'ACTIVE', createdDate: '2024-02-10', updatedAt: nowIso() },
{ id: 'fin-005', name: 'Finance', code: 'FIN-005', description: 'Financial planning and accounting', totalEmployees: 18, status: 'ACTIVE', createdDate: '2024-02-15', updatedAt: nowIso() },
{ id: 'ops-006', name: 'Operations', code: 'OPS-006', description: 'Operations and logistics', totalEmployees: 22, status: 'INACTIVE', createdDate: '2024-03-01', updatedAt: nowIso() },
{ id: 'cs-007', name: 'Customer Success', code: 'CS-007', description: 'Customer experience and technical support', totalEmployees: 31, status: 'ACTIVE', createdDate: '2024-03-05', updatedAt: nowIso() },
{ id: 'prd-008', name: 'Product', code: 'PRD-008', description: 'Product strategy and roadmap execution', totalEmployees: 19, status: 'ACTIVE', createdDate: '2024-03-10', updatedAt: nowIso() },
];
}
if (moduleKey === 'designation') {
return [
{ id: 'sse-001', name: 'Senior Software Engineer', code: 'SSE-001', department: 'Engineering', level: 'Senior', totalEmployees: 12, status: 'ACTIVE', createdDate: '2024-01-15', updatedAt: nowIso() },
{ id: 'mm-002', name: 'Marketing Manager', code: 'MM-002', department: 'Marketing', level: 'Manager', totalEmployees: 8, status: 'ACTIVE', createdDate: '2024-01-20', updatedAt: nowIso() },
{ id: 'se-003', name: 'Sales Executive', code: 'SE-003', department: 'Sales', level: 'Executive', totalEmployees: 15, status: 'ACTIVE', createdDate: '2024-02-01', updatedAt: nowIso() },
{ id: 'hrs-004', name: 'HR Specialist', code: 'HRS-004', department: 'Human Resources', level: 'Specialist', totalEmployees: 5, status: 'ACTIVE', createdDate: '2024-02-10', updatedAt: nowIso() },
{ id: 'fa-005', name: 'Financial Analyst', code: 'FA-005', department: 'Finance', level: 'Analyst', totalEmployees: 6, status: 'ACTIVE', createdDate: '2024-02-15', updatedAt: nowIso() },
{ id: 'om-006', name: 'Operations Manager', code: 'OM-006', department: 'Operations', level: 'Manager', totalEmployees: 4, status: 'INACTIVE', createdDate: '2024-03-01', updatedAt: nowIso() },
{ id: 'csl-007', name: 'Customer Support Lead', code: 'CSL-007', department: 'Customer Support', level: 'Lead', totalEmployees: 9, status: 'ACTIVE', createdDate: '2024-03-05', updatedAt: nowIso() },
{ id: 'pd-008', name: 'Product Designer', code: 'PD-008', department: 'Product', level: 'Designer', totalEmployees: 7, status: 'ACTIVE', createdDate: '2024-03-10', updatedAt: nowIso() },
];
}
const base = moduleKey const base = moduleKey
.replace(/[^a-z0-9]+/gi, '_') .replace(/[^a-z0-9]+/gi, '_')
.replace(/^_+|_+$/g, '') .replace(/^_+|_+$/g, '')
@ -236,7 +270,48 @@ export function getModuleCrudService(moduleKey: string): CrudService<CrudRecord>
const found = moduleCrudServices.get(key); const found = moduleCrudServices.get(key);
if (found) return found; if (found) return found;
const created = createCrudService(seedRowsForModule(key)); const created = createCrudService(seedRowsForModule(key), {
createFromPayload(payload) {
const baseName = String(payload.name || 'New Item');
if (key === 'department') {
return {
...(payload as CrudRecord),
id: String(payload.id || `dep-${Date.now()}`),
name: baseName,
code: String(payload.code || baseName.slice(0, 3).toUpperCase()),
description: String(payload.description || ''),
totalEmployees: Number(payload.totalEmployees || 0),
createdDate: String(payload.createdDate || new Date().toISOString().slice(0, 10)),
status: payload.status === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE',
updatedAt: nowIso(),
};
}
if (key === 'designation') {
return {
...(payload as CrudRecord),
id: String(payload.id || `des-${Date.now()}`),
name: baseName,
code: String(payload.code || baseName.slice(0, 3).toUpperCase()),
department: String(payload.department || ''),
level: String(payload.level || ''),
description: String(payload.description || ''),
totalEmployees: Number(payload.totalEmployees || 0),
createdDate: String(payload.createdDate || new Date().toISOString().slice(0, 10)),
status: payload.status === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE',
updatedAt: nowIso(),
};
}
return {
...(payload as CrudRecord),
id: String(payload.id || `ADM-${Date.now()}`),
name: baseName,
status: payload.status === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE',
updatedAt: nowIso(),
};
},
});
moduleCrudServices.set(key, created); moduleCrudServices.set(key, created);
return created; return created;
} }

View file

@ -32,6 +32,7 @@ export type CrudRecord = {
name: string; name: string;
status: 'ACTIVE' | 'INACTIVE'; status: 'ACTIVE' | 'INACTIVE';
updatedAt: string; updatedAt: string;
[key: string]: unknown;
}; };
export type AdminModuleConfig = { export type AdminModuleConfig = {

View file

@ -1,123 +1,244 @@
import { createMemo, createSignal, onMount } from 'solid-js'; import { For, Show, createMemo, createSignal, onMount } from 'solid-js';
import { ActionButton, DataTable, MetricCards, PageHeader, SearchFilters, SectionCard, StatusBadge, Tabs } from '~/components/admin/AdminUi'; import { createModuleRecord, deleteModuleRecord, listModuleRecords, updateModuleRecord } from '~/lib/admin/client';
import { createModuleRecord, listModuleRecords, updateModuleRecord } from '~/lib/admin/client';
import type { CrudRecord } from '~/lib/admin/types'; import type { CrudRecord } from '~/lib/admin/types';
export default function DepartmentManagementPage() { type DepartmentRecord = CrudRecord & {
const [tab, setTab] = createSignal<'view' | 'create'>('view'); code?: string;
const [query, setQuery] = createSignal(''); description?: string;
const [rows, setRows] = createSignal<CrudRecord[]>([]); totalEmployees?: number;
const [nameInput, setNameInput] = createSignal(''); createdDate?: string;
const [codeInput, setCodeInput] = createSignal(''); departmentHead?: string;
departmentEmail?: string;
};
export default function DepartmentManagementPage() {
const [mainTab, setMainTab] = createSignal<'all' | 'create'>('all');
const [createTab, setCreateTab] = createSignal<'general' | 'settings' | 'permissions'>('general');
const [search, setSearch] = createSignal('');
const [rows, setRows] = createSignal<DepartmentRecord[]>([]);
const [openMenuId, setOpenMenuId] = createSignal<string | null>(null);
const [editingId, setEditingId] = createSignal<string | null>(null);
const [name, setName] = createSignal('');
const [code, setCode] = createSignal('');
const [description, setDescription] = createSignal('');
const [departmentHead, setDepartmentHead] = createSignal('');
const [departmentEmail, setDepartmentEmail] = createSignal('');
const [status, setStatus] = createSignal<'ACTIVE' | 'INACTIVE'>('ACTIVE');
const [transfersEnabled, setTransfersEnabled] = createSignal(false);
const load = async () => {
const data = await listModuleRecords<DepartmentRecord>('department', { q: search().trim() || undefined });
setRows(data);
};
const load = async () => setRows(await listModuleRecords('department', { q: query() }));
onMount(() => void load()); onMount(() => void load());
const filtered = createMemo(() => { const resetForm = () => {
const q = query().trim().toLowerCase(); setEditingId(null);
if (!q) return rows(); setName('');
return rows().filter((row) => row.id.toLowerCase().includes(q) || row.name.toLowerCase().includes(q)); setCode('');
}); setDescription('');
setDepartmentHead('');
setDepartmentEmail('');
setStatus('ACTIVE');
setTransfersEnabled(false);
setCreateTab('general');
};
const metrics = createMemo(() => { const openCreate = () => {
const all = rows(); resetForm();
const active = all.filter((item) => item.status === 'ACTIVE').length; setMainTab('create');
const inactive = all.filter((item) => item.status === 'INACTIVE').length; };
return [
{ label: 'Total Departments', value: String(all.length || 0) }, const openEdit = (row: DepartmentRecord) => {
{ label: 'Active Departments', value: String(active), tone: 'positive' as const }, setEditingId(row.id);
{ label: 'Inactive Departments', value: String(inactive), tone: 'warning' as const }, setName(row.name || '');
{ label: 'Updated Today', value: String(Math.min(active, 6)), tone: 'info' as const }, setCode(String(row.code || ''));
]; setDescription(String(row.description || ''));
}); setDepartmentHead(String(row.departmentHead || ''));
setDepartmentEmail(String(row.departmentEmail || ''));
setStatus(row.status === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE');
setMainTab('create');
setCreateTab('general');
};
const saveDepartment = async () => {
const payload: Partial<DepartmentRecord> = {
name: name().trim() || 'New Department',
code: code().trim() || undefined,
description: description().trim(),
departmentHead: departmentHead().trim(),
departmentEmail: departmentEmail().trim(),
status: status(),
transfersEnabled: transfersEnabled(),
};
if (editingId()) {
await updateModuleRecord<DepartmentRecord>('department', editingId()!, payload);
} else {
await createModuleRecord<DepartmentRecord>('department', payload);
}
setMainTab('all');
setOpenMenuId(null);
resetForm();
await load();
};
const filteredRows = createMemo(() => rows());
const formatDate = (value?: string) => {
const input = value || '';
if (/^\d{4}-\d{2}-\d{2}$/.test(input)) return input;
const fallback = input || new Date().toISOString().slice(0, 10);
return fallback.slice(0, 10);
};
return ( return (
<div class="space-y-5"> <div class="space-y-5">
<PageHeader <section>
title="Department Management" <h1 class="text-[24px] font-semibold leading-[1.1] tracking-[-0.01em] text-[#050026]">Department Management</h1>
subtitle="Manage operational department structure and ownership mappings." <p class="mt-2 text-[16px] leading-[1.35] text-[#7a8099]">Manage all departments and organizational structure</p>
actions={<Tabs value={tab()} onChange={setTab} items={[{ key: 'view', label: 'View Departments' }, { key: 'create', label: 'Create Department' }]} />} </section>
/>
<MetricCards items={metrics()} /> <section class="overflow-hidden rounded-[24px] border border-[#d9dde6] bg-[#f7f7f8]">
<div class="flex items-center gap-2 border-b border-[#e1e5ee] px-5 pt-4">
<button onClick={() => setMainTab('all')} class={`relative px-8 pb-4 pt-2 text-[16px] font-semibold ${mainTab() === 'all' ? 'text-[#0c123f]' : 'text-[#737a96]'}`}>
All Departments
<Show when={mainTab() === 'all'}><span class="absolute inset-x-0 -bottom-[1px] h-[4px] rounded-full bg-[#0a0a50]" /></Show>
</button>
<button onClick={openCreate} class={`relative px-8 pb-4 pt-2 text-[16px] font-semibold ${mainTab() === 'create' ? 'text-[#0c123f]' : 'text-[#737a96]'}`}>
{editingId() ? 'Edit Department' : 'Create Department'}
<Show when={mainTab() === 'create'}><span class="absolute inset-x-0 -bottom-[1px] h-[4px] rounded-full bg-[#0a0a50]" /></Show>
</button>
</div>
{tab() === 'view' ? ( <Show when={mainTab() === 'all'}>
<SectionCard <div class="space-y-5 p-5">
title="Departments" <label class="flex h-[48px] items-center rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#8a90a8]">
subtitle="Search, activate, and maintain department records." <svg class="mr-3 h-5 w-5 text-[#8a90a8]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="m20 20-3.5-3.5" /></svg>
actions={ <input
<> value={search()}
<ActionButton>Export</ActionButton> onInput={(e) => {
<ActionButton tone="primary" onClick={() => setTab('create')}>Add Department</ActionButton> setSearch(e.currentTarget.value);
</> void load();
} }}
> placeholder="Search departments..."
<div class="space-y-3"> class="w-full border-0 bg-transparent text-[16px] text-[#1a2147] outline-none placeholder:text-[#8a90a8]"
<SearchFilters />
query={query()}
onQuery={(value) => {
setQuery(value);
void load();
}}
right={<ActionButton>Filter</ActionButton>}
/>
<DataTable
headers={['Department ID', 'Department Name', 'Status', 'Updated', 'Actions']}
rows={filtered().map((row) => [
<span class="font-medium text-[#050026]">{row.id}</span>,
<span>{row.name}</span>,
<StatusBadge label={row.status} tone={row.status === 'ACTIVE' ? 'positive' : 'warning'} />,
<span class="text-xs text-slate-500">{new Date(row.updatedAt).toLocaleString()}</span>,
<div class="flex gap-2">
<ActionButton
onClick={() => {
const next = row.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';
void updateModuleRecord('department', row.id, { status: next }).then(() => void load());
}}
>
Toggle
</ActionButton>
<ActionButton tone="ghost">View</ActionButton>
</div>,
])}
/>
</div>
</SectionCard>
) : (
<SectionCard title="Create Department" subtitle="Create a new department with active runtime status.">
<form
class="grid gap-4 rounded-xl border border-[#e5eaf3] bg-[#f9fbff] p-4 md:grid-cols-2"
onSubmit={(e) => {
e.preventDefault();
const name = nameInput().trim() || codeInput().trim() || 'New Department';
void createModuleRecord('department', {
id: codeInput().trim() || undefined,
name,
status: 'ACTIVE',
}).then(() => {
setNameInput('');
setCodeInput('');
setTab('view');
void load();
});
}}
>
<label class="text-sm font-medium text-slate-700">
Department Name
<input value={nameInput()} onInput={(e) => setNameInput(e.currentTarget.value)} class="mt-1 block w-full rounded-lg border border-[#d8dfec] bg-white px-3 py-2 text-sm outline-none focus:border-[#fd6116]" />
</label> </label>
<label class="text-sm font-medium text-slate-700">
Department Code <div class="relative rounded-[18px] border border-[#d8dce6] bg-[#f7f7f8]">
<input value={codeInput()} onInput={(e) => setCodeInput(e.currentTarget.value.toUpperCase())} class="mt-1 block w-full rounded-lg border border-[#d8dfec] bg-white px-3 py-2 text-sm outline-none focus:border-[#fd6116]" /> <table class="min-w-full table-fixed text-left">
</label> <thead class="bg-[#030047] text-white">
<div class="md:col-span-2 flex justify-end gap-2"> <tr>
<ActionButton onClick={() => setTab('view')}>Cancel</ActionButton> <th class="w-[18%] px-6 py-4 text-[14px] font-semibold">DEPARTMENT NAME</th>
<ActionButton type="submit" tone="primary">Save Department</ActionButton> <th class="w-[14%] px-6 py-4 text-[14px] font-semibold">DEPARTMENT CODE</th>
<th class="w-[31%] px-6 py-4 text-[14px] font-semibold">DESCRIPTION</th>
<th class="w-[10%] px-6 py-4 text-[14px] font-semibold">TOTAL EMPLOYEES</th>
<th class="w-[10%] px-6 py-4 text-[14px] font-semibold">STATUS</th>
<th class="w-[10%] px-6 py-4 text-[14px] font-semibold">CREATED DATE</th>
<th class="w-[7%] px-6 py-4 text-[14px] font-semibold">ACTIONS</th>
</tr>
</thead>
<tbody class="divide-y divide-[#dde1ea] text-[#222948]">
<For each={filteredRows()}>
{(row) => (
<tr class="bg-[#f7f7f8]">
<td class="px-6 py-4 text-[15px] font-semibold">{row.name}</td>
<td class="px-6 py-4 text-[15px] font-medium text-[#505779]">{String(row.code || '')}</td>
<td class="px-6 py-4 text-[14px] font-medium text-[#6b7393]">{String(row.description || '')}</td>
<td class="px-6 py-4 text-[15px] font-semibold">{Number(row.totalEmployees || 0)}</td>
<td class="px-6 py-4">
<span class={`inline-flex rounded-[10px] border px-3 py-1.5 text-[14px] font-semibold ${row.status === 'ACTIVE' ? 'border-[#ffc2aa] bg-[#ffeee6] text-[#fd6116]' : 'border-[#c7ccda] bg-[#eceff6] text-[#101848]'}`}>
{row.status === 'ACTIVE' ? 'Active' : 'Inactive'}
</span>
</td>
<td class="px-6 py-4 text-[14px] font-medium text-[#6b7393]">{formatDate(String(row.createdDate || row.updatedAt || ''))}</td>
<td class="relative px-6 py-4">
<button onClick={() => setOpenMenuId(openMenuId() === row.id ? null : row.id)} class="inline-flex h-10 w-10 items-center justify-center rounded-lg text-[#6c7292] hover:bg-[#eceff5]" aria-label="More actions">
<span class="text-[20px] leading-none"></span>
</button>
<Show when={openMenuId() === row.id}>
<div class="absolute right-6 top-14 z-20 w-[220px] rounded-2xl border border-[#d6dbe6] bg-white p-2 shadow-[0_16px_28px_rgba(5,0,38,0.16)]">
<button onClick={() => openEdit(row)} class="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left text-[16px] font-medium text-[#20284d] hover:bg-[#f5f7fb]"><span class="text-[#fd6116]"></span>Edit Department</button>
<button onClick={async () => { await updateModuleRecord<DepartmentRecord>('department', row.id, { status: row.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE' }); setOpenMenuId(null); await load(); }} class="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left text-[16px] font-medium text-[#20284d] hover:bg-[#f5f7fb]"><span class="text-[#fd6116]"></span>{row.status === 'ACTIVE' ? 'Deactivate Department' : 'Activate Department'}</button>
<button onClick={async () => { await deleteModuleRecord('department', row.id); setOpenMenuId(null); await load(); }} class="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left text-[16px] font-medium text-[#20284d] hover:bg-[#f5f7fb]"><span class="text-[#fd6116]">🗑</span>Delete Department</button>
</div>
</Show>
</td>
</tr>
)}
</For>
</tbody>
</table>
<div class="flex items-center justify-between border-t border-[#dde1ea] px-6 py-4">
<p class="text-[14px] text-[#707895]">Showing <span class="font-semibold text-[#283055]">1-{rows().length}</span> of <span class="font-semibold text-[#283055]">{rows().length}</span> departments</p>
<div class="flex items-center gap-2">
<button class="inline-flex h-10 w-10 items-center justify-center rounded-[12px] border border-[#d5dae6] text-[#8a90a8]"></button>
<button class="inline-flex h-10 w-10 items-center justify-center rounded-[12px] bg-[#fd6116] font-semibold text-white">1</button>
<button class="inline-flex h-10 w-10 items-center justify-center rounded-[12px] border border-[#d5dae6] text-[#8a90a8]"></button>
</div>
</div>
</div> </div>
</form> </div>
</SectionCard> </Show>
)}
<Show when={mainTab() === 'create'}>
<div class="space-y-6 p-5">
<div class="flex items-center gap-2 border-b border-[#e1e5ee] pb-3">
<button onClick={() => setCreateTab('general')} class={`relative px-6 pb-3 text-[16px] font-semibold ${createTab() === 'general' ? 'text-[#0b123f]' : 'text-[#767d98]'}`}>General Information<Show when={createTab() === 'general'}><span class="absolute inset-x-0 -bottom-[2px] h-[4px] rounded-full bg-[#0a0a50]" /></Show></button>
<button onClick={() => setCreateTab('settings')} class={`relative px-6 pb-3 text-[16px] font-semibold ${createTab() === 'settings' ? 'text-[#0b123f]' : 'text-[#767d98]'}`}>Department Settings<Show when={createTab() === 'settings'}><span class="absolute inset-x-0 -bottom-[2px] h-[4px] rounded-full bg-[#0a0a50]" /></Show></button>
<button onClick={() => setCreateTab('permissions')} class={`relative px-6 pb-3 text-[16px] font-semibold ${createTab() === 'permissions' ? 'text-[#0b123f]' : 'text-[#767d98]'}`}>Permissions<Show when={createTab() === 'permissions'}><span class="absolute inset-x-0 -bottom-[2px] h-[4px] rounded-full bg-[#0a0a50]" /></Show></button>
</div>
<Show when={createTab() === 'general'}>
<div class="space-y-5">
<div class="grid gap-5 md:grid-cols-2">
<label class="block text-[14px] font-semibold text-[#101848]">Department Name <span class="text-[#fd6116]">*</span><input value={name()} onInput={(e) => setName(e.currentTarget.value)} class="mt-2 h-[48px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#1a2147] outline-none" placeholder="Enter department name" /></label>
<label class="block text-[14px] font-semibold text-[#101848]">Department Code <span class="text-[#fd6116]">*</span><input value={code()} onInput={(e) => setCode(e.currentTarget.value)} class="mt-2 h-[48px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#1a2147] outline-none" placeholder="e.g., ENG-001" /></label>
</div>
<label class="block text-[14px] font-semibold text-[#101848]">Department Description<textarea value={description()} onInput={(e) => setDescription(e.currentTarget.value)} class="mt-2 h-[110px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 py-3 text-[16px] text-[#1a2147] outline-none" placeholder="Enter department description" /></label>
<div class="grid gap-5 md:grid-cols-2">
<label class="block text-[14px] font-semibold text-[#101848]">Department Head<input value={departmentHead()} onInput={(e) => setDepartmentHead(e.currentTarget.value)} class="mt-2 h-[48px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#1a2147] outline-none" /></label>
<label class="block text-[14px] font-semibold text-[#101848]">Department Email<input value={departmentEmail()} onInput={(e) => setDepartmentEmail(e.currentTarget.value)} class="mt-2 h-[48px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#1a2147] outline-none" placeholder="department@example.com" /></label>
</div>
</div>
</Show>
<Show when={createTab() === 'settings'}>
<div class="space-y-6">
<div>
<p class="text-[18px] font-semibold text-[#101848]">Department Status</p>
<div class="mt-3 flex gap-3">
<button onClick={() => setStatus('ACTIVE')} class={`h-[44px] rounded-[12px] border px-6 text-[16px] font-semibold ${status() === 'ACTIVE' ? 'border-[#fd6116] bg-[#fd6116] text-white' : 'border-[#d3d8e4] bg-[#f7f7f8] text-[#1a2147]'}`}>Active</button>
<button onClick={() => setStatus('INACTIVE')} class={`h-[44px] rounded-[12px] border px-6 text-[16px] font-semibold ${status() === 'INACTIVE' ? 'border-[#fd6116] bg-[#fd6116] text-white' : 'border-[#d3d8e4] bg-[#f7f7f8] text-[#1a2147]'}`}>Inactive</button>
</div>
</div>
<div class="flex items-center justify-between rounded-[18px] border border-[#d9dde6] bg-[#f7f7f8] px-6 py-4">
<div><p class="text-[16px] font-semibold text-[#101848]">Allow Employee Transfers</p><p class="text-[14px] text-[#7d849f]">Enable employees to request transfer to this department</p></div>
<button onClick={() => setTransfersEnabled((v) => !v)} class={`relative h-9 w-16 rounded-full transition ${transfersEnabled() ? 'bg-[#fd6116]' : 'bg-[#e0e4ec]'}`}><span class={`absolute top-1 h-7 w-7 rounded-full bg-white transition ${transfersEnabled() ? 'left-8' : 'left-1'}`} /></button>
</div>
</div>
</Show>
<Show when={createTab() === 'permissions'}>
<div class="space-y-4">
<p class="text-[16px] text-[#707895]">Select permissions for this department</p>
<For each={['View Employees', 'Create Employees', 'Edit Employees', 'Delete Employees', 'Assign Roles']}>
{(label) => <div class="rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-6 py-3 text-[16px] font-semibold text-[#1c244a]">{label}</div>}
</For>
</div>
</Show>
<div class="flex justify-end gap-3 border-t border-[#e1e5ee] pt-4">
<button onClick={() => { setMainTab('all'); resetForm(); }} class="h-[44px] rounded-[12px] border border-[#d2d8e4] bg-[#f7f7f8] px-6 text-[16px] font-semibold text-[#232b4d]">Cancel</button>
<button onClick={() => void saveDepartment()} class="h-[44px] rounded-[12px] bg-[#030047] px-8 text-[16px] font-semibold text-white">{editingId() ? 'Save Department' : 'Create Department'}</button>
</div>
</div>
</Show>
</section>
</div> </div>
); );
} }

View file

@ -1,123 +1,253 @@
import { createMemo, createSignal, onMount } from 'solid-js'; import { For, Show, createMemo, createSignal, onMount } from 'solid-js';
import { ActionButton, DataTable, MetricCards, PageHeader, SearchFilters, SectionCard, StatusBadge, Tabs } from '~/components/admin/AdminUi'; import { createModuleRecord, deleteModuleRecord, listModuleRecords, updateModuleRecord } from '~/lib/admin/client';
import { createModuleRecord, listModuleRecords, updateModuleRecord } from '~/lib/admin/client';
import type { CrudRecord } from '~/lib/admin/types'; import type { CrudRecord } from '~/lib/admin/types';
export default function DesignationManagementPage() { type DesignationRecord = CrudRecord & {
const [tab, setTab] = createSignal<'view' | 'create'>('view'); code?: string;
const [query, setQuery] = createSignal(''); department?: string;
const [rows, setRows] = createSignal<CrudRecord[]>([]); level?: string;
const [nameInput, setNameInput] = createSignal(''); description?: string;
const [codeInput, setCodeInput] = createSignal(''); totalEmployees?: number;
createdDate?: string;
canManageTeam?: boolean;
canApprove?: boolean;
};
const load = async () => setRows(await listModuleRecords('designation', { q: query() })); export default function DesignationManagementPage() {
const [mainTab, setMainTab] = createSignal<'all' | 'create'>('all');
const [createTab, setCreateTab] = createSignal<'general' | 'settings' | 'permissions'>('general');
const [search, setSearch] = createSignal('');
const [rows, setRows] = createSignal<DesignationRecord[]>([]);
const [openMenuId, setOpenMenuId] = createSignal<string | null>(null);
const [editingId, setEditingId] = createSignal<string | null>(null);
const [name, setName] = createSignal('');
const [code, setCode] = createSignal('');
const [department, setDepartment] = createSignal('');
const [level, setLevel] = createSignal('');
const [description, setDescription] = createSignal('');
const [status, setStatus] = createSignal<'ACTIVE' | 'INACTIVE'>('ACTIVE');
const [canManageTeam, setCanManageTeam] = createSignal(false);
const [canApprove, setCanApprove] = createSignal(false);
const load = async () => {
const data = await listModuleRecords<DesignationRecord>('designation', { q: search().trim() || undefined });
setRows(data);
};
onMount(() => void load()); onMount(() => void load());
const filtered = createMemo(() => { const resetForm = () => {
const q = query().trim().toLowerCase(); setEditingId(null);
if (!q) return rows(); setName('');
return rows().filter((row) => row.id.toLowerCase().includes(q) || row.name.toLowerCase().includes(q)); setCode('');
}); setDepartment('');
setLevel('');
setDescription('');
setStatus('ACTIVE');
setCanManageTeam(false);
setCanApprove(false);
setCreateTab('general');
};
const metrics = createMemo(() => { const openCreate = () => {
const all = rows(); resetForm();
const active = all.filter((item) => item.status === 'ACTIVE').length; setMainTab('create');
const inactive = all.filter((item) => item.status === 'INACTIVE').length; };
return [
{ label: 'Total Designations', value: String(all.length || 0) }, const openEdit = (row: DesignationRecord) => {
{ label: 'Active Designations', value: String(active), tone: 'positive' as const }, setEditingId(row.id);
{ label: 'Inactive Designations', value: String(inactive), tone: 'warning' as const }, setName(row.name || '');
{ label: 'Updated Today', value: String(Math.min(active, 8)), tone: 'info' as const }, setCode(String(row.code || ''));
]; setDepartment(String(row.department || ''));
}); setLevel(String(row.level || ''));
setDescription(String(row.description || ''));
setStatus(row.status === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE');
setCanManageTeam(Boolean(row.canManageTeam));
setCanApprove(Boolean(row.canApprove));
setMainTab('create');
setCreateTab('general');
};
const saveDesignation = async () => {
const payload: Partial<DesignationRecord> = {
name: name().trim() || 'New Designation',
code: code().trim() || undefined,
department: department().trim(),
level: level().trim(),
description: description().trim(),
status: status(),
canManageTeam: canManageTeam(),
canApprove: canApprove(),
};
if (editingId()) {
await updateModuleRecord<DesignationRecord>('designation', editingId()!, payload);
} else {
await createModuleRecord<DesignationRecord>('designation', payload);
}
setMainTab('all');
setOpenMenuId(null);
resetForm();
await load();
};
const filteredRows = createMemo(() => rows());
const formatDate = (value?: string) => {
const input = value || '';
if (/^\d{4}-\d{2}-\d{2}$/.test(input)) return input;
return (input || new Date().toISOString().slice(0, 10)).slice(0, 10);
};
return ( return (
<div class="space-y-5"> <div class="space-y-5">
<PageHeader <section>
title="Designation Management" <h1 class="text-[24px] font-semibold leading-[1.1] tracking-[-0.01em] text-[#050026]">Designation Management</h1>
subtitle="Manage designation taxonomy used by internal and external role systems." <p class="mt-2 text-[16px] leading-[1.35] text-[#7a8099]">Manage all designations and job positions</p>
actions={<Tabs value={tab()} onChange={setTab} items={[{ key: 'view', label: 'View Designations' }, { key: 'create', label: 'Create Designation' }]} />} </section>
/>
<MetricCards items={metrics()} /> <section class="overflow-hidden rounded-[24px] border border-[#d9dde6] bg-[#f7f7f8]">
<div class="flex items-center gap-2 border-b border-[#e1e5ee] px-5 pt-4">
<button onClick={() => setMainTab('all')} class={`relative px-8 pb-4 pt-2 text-[16px] font-semibold ${mainTab() === 'all' ? 'text-[#0c123f]' : 'text-[#737a96]'}`}>
All Designations
<Show when={mainTab() === 'all'}><span class="absolute inset-x-0 -bottom-[1px] h-[4px] rounded-full bg-[#0a0a50]" /></Show>
</button>
<button onClick={openCreate} class={`relative px-8 pb-4 pt-2 text-[16px] font-semibold ${mainTab() === 'create' ? 'text-[#0c123f]' : 'text-[#737a96]'}`}>
{editingId() ? 'Edit Designation' : 'Create Designation'}
<Show when={mainTab() === 'create'}><span class="absolute inset-x-0 -bottom-[1px] h-[4px] rounded-full bg-[#0a0a50]" /></Show>
</button>
</div>
{tab() === 'view' ? ( <Show when={mainTab() === 'all'}>
<SectionCard <div class="space-y-5 p-5">
title="Designations" <div class="grid gap-3 md:grid-cols-[1fr_190px_130px]">
subtitle="Search and update designation availability." <label class="flex h-[48px] items-center rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#8a90a8]">
actions={ <svg class="mr-3 h-5 w-5 text-[#8a90a8]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="m20 20-3.5-3.5" /></svg>
<> <input value={search()} onInput={(e) => { setSearch(e.currentTarget.value); void load(); }} placeholder="Search designations..." class="w-full border-0 bg-transparent text-[16px] text-[#1a2147] outline-none placeholder:text-[#8a90a8]" />
<ActionButton>Export</ActionButton> </label>
<ActionButton tone="primary" onClick={() => setTab('create')}>Add Designation</ActionButton> <div class="h-[48px] rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8]" />
</> <div class="h-[48px] rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8]" />
}
>
<div class="space-y-3">
<SearchFilters
query={query()}
onQuery={(value) => {
setQuery(value);
void load();
}}
right={<ActionButton>Filter</ActionButton>}
/>
<DataTable
headers={['Designation ID', 'Designation Name', 'Status', 'Updated', 'Actions']}
rows={filtered().map((row) => [
<span class="font-medium text-[#050026]">{row.id}</span>,
<span>{row.name}</span>,
<StatusBadge label={row.status} tone={row.status === 'ACTIVE' ? 'positive' : 'warning'} />,
<span class="text-xs text-slate-500">{new Date(row.updatedAt).toLocaleString()}</span>,
<div class="flex gap-2">
<ActionButton
onClick={() => {
const next = row.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';
void updateModuleRecord('designation', row.id, { status: next }).then(() => void load());
}}
>
Toggle
</ActionButton>
<ActionButton tone="ghost">View</ActionButton>
</div>,
])}
/>
</div>
</SectionCard>
) : (
<SectionCard title="Create Designation" subtitle="Add a new designation used in role and employee mapping.">
<form
class="grid gap-4 rounded-xl border border-[#e5eaf3] bg-[#f9fbff] p-4 md:grid-cols-2"
onSubmit={(e) => {
e.preventDefault();
const name = nameInput().trim() || codeInput().trim() || 'New Designation';
void createModuleRecord('designation', {
id: codeInput().trim() || undefined,
name,
status: 'ACTIVE',
}).then(() => {
setNameInput('');
setCodeInput('');
setTab('view');
void load();
});
}}
>
<label class="text-sm font-medium text-slate-700">
Designation Name
<input value={nameInput()} onInput={(e) => setNameInput(e.currentTarget.value)} class="mt-1 block w-full rounded-lg border border-[#d8dfec] bg-white px-3 py-2 text-sm outline-none focus:border-[#fd6116]" />
</label>
<label class="text-sm font-medium text-slate-700">
Designation Code
<input value={codeInput()} onInput={(e) => setCodeInput(e.currentTarget.value.toUpperCase())} class="mt-1 block w-full rounded-lg border border-[#d8dfec] bg-white px-3 py-2 text-sm outline-none focus:border-[#fd6116]" />
</label>
<div class="md:col-span-2 flex justify-end gap-2">
<ActionButton onClick={() => setTab('view')}>Cancel</ActionButton>
<ActionButton type="submit" tone="primary">Save Designation</ActionButton>
</div> </div>
</form>
</SectionCard> <div class="relative rounded-[18px] border border-[#d8dce6] bg-[#f7f7f8]">
)} <table class="min-w-full table-fixed text-left">
<thead class="bg-[#030047] text-white">
<tr>
<th class="w-[17%] px-6 py-4 text-[14px] font-semibold">DESIGNATION NAME</th>
<th class="w-[16%] px-6 py-4 text-[14px] font-semibold">DESIGNATION CODE</th>
<th class="w-[18%] px-6 py-4 text-[14px] font-semibold">DEPARTMENT</th>
<th class="w-[12%] px-6 py-4 text-[14px] font-semibold">LEVEL</th>
<th class="w-[11%] px-6 py-4 text-[14px] font-semibold">TOTAL EMPLOYEES</th>
<th class="w-[10%] px-6 py-4 text-[14px] font-semibold">STATUS</th>
<th class="w-[10%] px-6 py-4 text-[14px] font-semibold">CREATED DATE</th>
<th class="w-[6%] px-6 py-4 text-[14px] font-semibold">ACTIONS</th>
</tr>
</thead>
<tbody class="divide-y divide-[#dde1ea] text-[#222948]">
<For each={filteredRows()}>
{(row) => (
<tr class="bg-[#f7f7f8]">
<td class="px-6 py-4 text-[15px] font-semibold">{row.name}</td>
<td class="px-6 py-4 text-[15px] font-medium text-[#505779]">{String(row.code || '')}</td>
<td class="px-6 py-4 text-[14px] font-medium text-[#6b7393]">{String(row.department || '')}</td>
<td class="px-6 py-4 text-[14px] font-medium text-[#6b7393]">{String(row.level || '')}</td>
<td class="px-6 py-4 text-[15px] font-semibold">{Number(row.totalEmployees || 0)}</td>
<td class="px-6 py-4"><span class={`inline-flex rounded-[10px] border px-3 py-1.5 text-[14px] font-semibold ${row.status === 'ACTIVE' ? 'border-[#ffc2aa] bg-[#ffeee6] text-[#fd6116]' : 'border-[#c7ccda] bg-[#eceff6] text-[#101848]'}`}>{row.status === 'ACTIVE' ? 'Active' : 'Inactive'}</span></td>
<td class="px-6 py-4 text-[14px] font-medium text-[#6b7393]">{formatDate(String(row.createdDate || row.updatedAt || ''))}</td>
<td class="relative px-6 py-4">
<button onClick={() => setOpenMenuId(openMenuId() === row.id ? null : row.id)} class="inline-flex h-10 w-10 items-center justify-center rounded-lg text-[#6c7292] hover:bg-[#eceff5]" aria-label="More actions"><span class="text-[20px] leading-none"></span></button>
<Show when={openMenuId() === row.id}>
<div class="absolute right-6 top-14 z-20 w-[220px] rounded-2xl border border-[#d6dbe6] bg-white p-2 shadow-[0_16px_28px_rgba(5,0,38,0.16)]">
<button onClick={() => openEdit(row)} class="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left text-[16px] font-medium text-[#20284d] hover:bg-[#f5f7fb]"><span class="text-[#fd6116]"></span>Edit Designation</button>
<button onClick={async () => { await updateModuleRecord<DesignationRecord>('designation', row.id, { status: row.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE' }); setOpenMenuId(null); await load(); }} class="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left text-[16px] font-medium text-[#20284d] hover:bg-[#f5f7fb]"><span class="text-[#fd6116]"></span>{row.status === 'ACTIVE' ? 'Deactivate' : 'Activate'}</button>
<button onClick={async () => { await deleteModuleRecord('designation', row.id); setOpenMenuId(null); await load(); }} class="flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left text-[16px] font-medium text-[#20284d] hover:bg-[#f5f7fb]"><span class="text-[#fd6116]">🗑</span>Delete</button>
</div>
</Show>
</td>
</tr>
)}
</For>
</tbody>
</table>
<div class="flex items-center justify-between border-t border-[#dde1ea] px-6 py-4">
<p class="text-[14px] text-[#707895]">Showing <span class="font-semibold text-[#283055]">1-{rows().length}</span> of <span class="font-semibold text-[#283055]">{rows().length}</span> designations</p>
<div class="flex items-center gap-2">
<button class="inline-flex h-10 w-10 items-center justify-center rounded-[12px] border border-[#d5dae6] text-[#8a90a8]"></button>
<button class="inline-flex h-10 w-10 items-center justify-center rounded-[12px] bg-[#fd6116] font-semibold text-white">1</button>
<button class="inline-flex h-10 w-10 items-center justify-center rounded-[12px] border border-[#d5dae6] text-[#8a90a8]"></button>
</div>
</div>
</div>
</div>
</Show>
<Show when={mainTab() === 'create'}>
<div class="space-y-6 p-5">
<div class="flex items-center gap-2 border-b border-[#e1e5ee] pb-3">
<button onClick={() => setCreateTab('general')} class={`relative px-6 pb-3 text-[16px] font-semibold ${createTab() === 'general' ? 'text-[#0b123f]' : 'text-[#767d98]'}`}>General Information<Show when={createTab() === 'general'}><span class="absolute inset-x-0 -bottom-[2px] h-[4px] rounded-full bg-[#0a0a50]" /></Show></button>
<button onClick={() => setCreateTab('settings')} class={`relative px-6 pb-3 text-[16px] font-semibold ${createTab() === 'settings' ? 'text-[#0b123f]' : 'text-[#767d98]'}`}>Designation Settings<Show when={createTab() === 'settings'}><span class="absolute inset-x-0 -bottom-[2px] h-[4px] rounded-full bg-[#0a0a50]" /></Show></button>
<button onClick={() => setCreateTab('permissions')} class={`relative px-6 pb-3 text-[16px] font-semibold ${createTab() === 'permissions' ? 'text-[#0b123f]' : 'text-[#767d98]'}`}>Permissions<Show when={createTab() === 'permissions'}><span class="absolute inset-x-0 -bottom-[2px] h-[4px] rounded-full bg-[#0a0a50]" /></Show></button>
</div>
<Show when={createTab() === 'general'}>
<div class="space-y-5">
<div class="grid gap-5 md:grid-cols-2">
<label class="block text-[14px] font-semibold text-[#101848]">Designation Name <span class="text-[#fd6116]">*</span><input value={name()} onInput={(e) => setName(e.currentTarget.value)} class="mt-2 h-[48px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#1a2147] outline-none" placeholder="Enter designation name" /></label>
<label class="block text-[14px] font-semibold text-[#101848]">Designation Code <span class="text-[#fd6116]">*</span><input value={code()} onInput={(e) => setCode(e.currentTarget.value)} class="mt-2 h-[48px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#1a2147] outline-none" placeholder="e.g., SSE-001" /></label>
</div>
<div class="grid gap-5 md:grid-cols-2">
<label class="block text-[14px] font-semibold text-[#101848]">Department <span class="text-[#fd6116]">*</span><input value={department()} onInput={(e) => setDepartment(e.currentTarget.value)} class="mt-2 h-[48px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#1a2147] outline-none" /></label>
<label class="block text-[14px] font-semibold text-[#101848]">Designation Level <span class="text-[#fd6116]">*</span><input value={level()} onInput={(e) => setLevel(e.currentTarget.value)} class="mt-2 h-[48px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 text-[16px] text-[#1a2147] outline-none" /></label>
</div>
<label class="block text-[14px] font-semibold text-[#101848]">Description<textarea value={description()} onInput={(e) => setDescription(e.currentTarget.value)} class="mt-2 h-[110px] w-full rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-4 py-3 text-[16px] text-[#1a2147] outline-none" placeholder="Enter designation description" /></label>
</div>
</Show>
<Show when={createTab() === 'settings'}>
<div class="space-y-6">
<div>
<p class="text-[18px] font-semibold text-[#101848]">Designation Status</p>
<div class="mt-3 flex gap-3">
<button onClick={() => setStatus('ACTIVE')} class={`h-[44px] rounded-[12px] border px-6 text-[16px] font-semibold ${status() === 'ACTIVE' ? 'border-[#fd6116] bg-[#fd6116] text-white' : 'border-[#d3d8e4] bg-[#f7f7f8] text-[#1a2147]'}`}>Active</button>
<button onClick={() => setStatus('INACTIVE')} class={`h-[44px] rounded-[12px] border px-6 text-[16px] font-semibold ${status() === 'INACTIVE' ? 'border-[#fd6116] bg-[#fd6116] text-white' : 'border-[#d3d8e4] bg-[#f7f7f8] text-[#1a2147]'}`}>Inactive</button>
</div>
</div>
<div class="space-y-3">
<div class="flex items-center justify-between rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-5 py-4">
<div><p class="text-[16px] font-semibold text-[#101848]">Allow Designation to Manage Team Members</p><p class="text-[14px] text-[#7d849f]">Enable this designation to manage team members</p></div>
<button onClick={() => setCanManageTeam((v) => !v)} class={`relative h-8 w-14 rounded-full transition ${canManageTeam() ? 'bg-[#fd6116]' : 'bg-[#e0e4ec]'}`}><span class={`absolute top-1 h-6 w-6 rounded-full bg-white transition ${canManageTeam() ? 'left-7' : 'left-1'}`} /></button>
</div>
<div class="flex items-center justify-between rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-5 py-4">
<div><p class="text-[16px] font-semibold text-[#101848]">Allow Approval Permissions</p><p class="text-[14px] text-[#7d849f]">Enable this designation to approve requests</p></div>
<button onClick={() => setCanApprove((v) => !v)} class={`relative h-8 w-14 rounded-full transition ${canApprove() ? 'bg-[#fd6116]' : 'bg-[#e0e4ec]'}`}><span class={`absolute top-1 h-6 w-6 rounded-full bg-white transition ${canApprove() ? 'left-7' : 'left-1'}`} /></button>
</div>
</div>
</div>
</Show>
<Show when={createTab() === 'permissions'}>
<div class="space-y-5">
<p class="text-[16px] text-[#707895]">Select permissions for this designation</p>
<div>
<h3 class="text-[18px] font-semibold text-[#11194a]">Employee Management</h3>
<div class="mt-3 space-y-3">
<For each={['View Employees', 'Create Employees', 'Edit Employees', 'Delete Employees']}>{(label) => <div class="rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-6 py-3 text-[16px] font-semibold text-[#1c244a]">{label}</div>}</For>
</div>
</div>
<div>
<h3 class="text-[18px] font-semibold text-[#11194a]">Additional Permissions</h3>
<div class="mt-3 rounded-[14px] border border-[#d9dde6] bg-[#f7f7f8] px-6 py-3 text-[16px] font-semibold text-[#1c244a]">Assign Roles</div>
</div>
</div>
</Show>
<div class="flex justify-end gap-3 border-t border-[#e1e5ee] pt-4">
<button onClick={() => { setMainTab('all'); resetForm(); }} class="h-[44px] rounded-[12px] border border-[#d2d8e4] bg-[#f7f7f8] px-6 text-[16px] font-semibold text-[#232b4d]">Cancel</button>
<button onClick={() => void saveDesignation()} class="h-[44px] rounded-[12px] bg-[#030047] px-8 text-[16px] font-semibold text-white">{editingId() ? 'Save Designation' : 'Create Designation'}</button>
</div>
</div>
</Show>
</section>
</div> </div>
); );
} }