import { For, Show, createMemo, createSignal, onMount } from 'solid-js'; import AdminShell from '~/components/AdminShell'; import { updateModuleRecord, deleteModuleRecord } from '~/lib/admin/client'; import type { CrudRecord } from '~/lib/admin/types'; type DesignationRecord = CrudRecord & { code?: string; department?: string; level?: string; description?: string; totalEmployees?: number; createdDate?: string; canManageTeam?: boolean; canApprove?: boolean; }; const FALLBACK_DESIGNATIONS: DesignationRecord[] = [ { id: 'z1', name: 'Senior Software Engineer', code: 'SSE-001', department: 'Engineering', level: 'Senior', totalEmployees: 12, status: 'ACTIVE', updatedAt: '2026-03-01', createdDate: '2026-01-15' }, { id: 'z2', name: 'Marketing Manager', code: 'MM-002', department: 'Marketing', level: 'Manager', totalEmployees: 8, status: 'ACTIVE', updatedAt: '2026-03-01', createdDate: '2026-01-20' }, { id: 'z3', name: 'Sales Executive', code: 'SE-003', department: 'Sales', level: 'Executive', totalEmployees: 15, status: 'ACTIVE', updatedAt: '2026-03-01', createdDate: '2026-02-01' }, { id: 'z4', name: 'HR Specialist', code: 'HRS-004', department: 'Human Resources', level: 'Specialist', totalEmployees: 5, status: 'ACTIVE', updatedAt: '2026-03-01', createdDate: '2026-02-05' }, { id: 'z5', name: 'Product Manager', code: 'PM-005', department: 'Product', level: 'Manager', totalEmployees: 6, status: 'ACTIVE', updatedAt: '2026-03-01', createdDate: '2026-02-10' }, { id: 'z6', name: 'Finance Analyst', code: 'FA-006', department: 'Finance', level: 'Analyst', totalEmployees: 9, status: 'INACTIVE', updatedAt: '2026-03-01', createdDate: '2026-02-15' }, ]; const LEVELS = ['Intern', 'Junior', 'Mid-Level', 'Senior', 'Lead', 'Manager', 'Director', 'VP', 'C-Level', 'Executive', 'Specialist', 'Analyst']; const DEPARTMENTS = ['Engineering', 'Marketing', 'Sales', 'Human Resources', 'Finance', 'Operations', 'Product', 'Customer Success']; function StatusBadge(props: { status: string }) { const active = () => props.status === 'ACTIVE'; return ( {active() ? 'Active' : 'Inactive'} ); } function FormInput(props: { label: string; required?: boolean; value: string; onInput: (v: string) => void; placeholder?: string; type?: string }) { return ( {props.label}{props.required && *} props.onInput(e.currentTarget.value)} placeholder={props.placeholder} style="display:block;margin-top:6px;height:40px;width:100%;border-radius:10px;border:1px solid #E5E7EB;background:white;padding:0 14px;font-size:13px;color:#111827;outline:none;box-sizing:border-box" /> ); } function FormSelect(props: { label: string; required?: boolean; value: string; onChange: (v: string) => void; children: any }) { return ( {props.label}{props.required && *} props.onChange(e.currentTarget.value)} style="display:block;margin-top:6px;height:40px;width:100%;border-radius:10px;border:1px solid #E5E7EB;background:white;padding:0 14px;font-size:13px;color:#111827;outline:none;box-sizing:border-box;appearance:none" > {props.children} ); } export default function DesignationManagementPage() { const [view, setView] = createSignal<'list' | 'form'>('list'); const [formTab, setFormTab] = createSignal<'general' | 'settings' | 'permissions'>('general'); const [listTab, setListTab] = createSignal<'all' | 'create' | 'view'>('all'); const [search, setSearch] = createSignal(''); const [deptFilter, setDeptFilter] = createSignal('all'); const [statusFilter, setStatusFilter] = createSignal('all'); const [sortMenuOpen, setSortMenuOpen] = createSignal(false); const [filterMenuOpen, setFilterMenuOpen] = createSignal(false); const [rows, setRows] = createSignal([]); const [openMenuId, setOpenMenuId] = createSignal(null); const [editingId, setEditingId] = createSignal(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 [isSaving, setIsSaving] = createSignal(false); const [error, setError] = createSignal(''); const load = async () => { try { const res = await fetch(`/api/gateway/api/admin/designations?page=1&limit=100&q=${encodeURIComponent(search().trim())}`); if (res.ok) { const payload = await res.json().catch(() => null); const list = Array.isArray(payload) ? payload : Array.isArray(payload?.data) ? payload.data : Array.isArray(payload?.items) ? payload.items : []; if (list.length > 0) { setRows(list.map((item: any, i: number) => ({ id: String(item.id ?? `des-${i + 1}`), name: String(item.name ?? ''), code: String(item.code ?? ''), department: String(item.department ?? item.department_name ?? ''), level: String(item.level ?? ''), description: String(item.description ?? ''), totalEmployees: Number(item.totalEmployees ?? item.total_employees ?? 0), status: String(item.status ?? 'ACTIVE').toUpperCase() === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE', updatedAt: String(item.updatedAt ?? item.updated_at ?? ''), createdDate: String(item.createdDate ?? item.created_at ?? ''), }))); return; } } } catch {} setRows(FALLBACK_DESIGNATIONS); }; onMount(() => void load()); const filteredRows = createMemo(() => { let r = rows(); if (statusFilter() !== 'all') r = r.filter((d) => d.status === statusFilter().toUpperCase()); if (deptFilter() !== 'all') r = r.filter((d) => d.department === deptFilter()); const q = search().toLowerCase(); if (q) r = r.filter((d) => d.name.toLowerCase().includes(q) || String(d.code ?? '').toLowerCase().includes(q)); return r; }); const resetForm = () => { setEditingId(null); setName(''); setCode(''); setDepartment(''); setLevel(''); setDescription(''); setStatus('ACTIVE'); setCanManageTeam(false); setCanApprove(false); setFormTab('general'); setError(''); }; const openCreate = () => { resetForm(); setView('form'); }; const openEdit = (row: DesignationRecord) => { setEditingId(row.id); setName(row.name || ''); 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)); setFormTab('general'); setView('form'); setOpenMenuId(null); }; const save = async () => { if (!name().trim()) { setError('Designation name is required.'); setFormTab('general'); return; } setIsSaving(true); setError(''); try { const payload: Partial = { name: name().trim(), code: code().trim() || undefined, department: department().trim(), level: level().trim(), description: description().trim(), status: status(), canManageTeam: canManageTeam(), canApprove: canApprove(), }; if (editingId()) { await updateModuleRecord('designation', editingId()!, payload); } else { const res = await fetch('/api/gateway/api/admin/designations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!res.ok) throw new Error(`Request failed (${res.status})`); } setView('list'); resetForm(); await load(); } catch (err: any) { setError(err?.message || 'Failed to save designation.'); } finally { setIsSaving(false); } }; const formatDate = (v?: string) => { const s = v || ''; if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s; return s.slice(0, 10) || '—'; }; return ( {/* Page header */} Designation Management Manage all job designations and position levels {/* ── LIST VIEW ── */} {/* Tabs */} {([ { key: 'all', label: 'All Designations', action: () => { setListTab('all'); setStatusFilter('all'); void load(); } }, { key: 'create', label: 'Create Designation', action: () => { setListTab('create'); openCreate(); } }, { key: 'view', label: 'View Designation', action: () => setListTab('view') }, ] as const).map((tab) => ( {tab.label} ))} {/* Table card */} {/* Filter bar */} { setSearch(e.currentTarget.value); void load(); }} placeholder="Search designations..." style="height:34px;flex:1;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:13px;color:#111827;outline:none" /> setDeptFilter(e.currentTarget.value)} style="height:34px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 10px;font-size:12px;color:#374151;outline:none" > All Departments {(d) => {d}} { setFilterMenuOpen((v) => !v); setSortMenuOpen(false); }} style="display:inline-flex;height:34px;align-items:center;gap:6px;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:12px;font-weight:500;color:#374151;cursor:pointer" > Filters {(['all', 'ACTIVE', 'INACTIVE'] as const).map((s) => ( { setStatusFilter(s); setFilterMenuOpen(false); }} style={`display:block;width:100%;border-radius:8px;padding:8px 12px;text-align:left;font-size:13px;border:none;cursor:pointer;color:${statusFilter() === s ? '#FF5E13' : '#374151'};background:${statusFilter() === s ? '#FFF1EB' : 'transparent'}`}> {s === 'all' ? 'All Status' : s === 'ACTIVE' ? 'Active' : 'Inactive'} ))} Export {/* Table */} Designation Name Code Department Level Employees Status Created Date Actions 0} fallback={ No designations found Create your first designation to get started. Create Designation } > {(row) => ( {row.name} {String(row.code || '—')} {String(row.department || '—')} {String(row.level || '—')} {Number(row.totalEmployees || 0)} {formatDate(String(row.createdDate || row.updatedAt || ''))} setOpenMenuId(openMenuId() === row.id ? null : row.id)} style="display:inline-flex;width:32px;height:32px;align-items:center;justify-content:center;border-radius:8px;border:none;background:none;color:#9CA3AF;cursor:pointer" aria-label="More actions" > openEdit(row)} style="display:flex;width:100%;align-items:center;gap:10px;border-radius:8px;padding:8px 12px;font-size:13px;font-weight:500;color:#374151;background:none;border:none;cursor:pointer"> Edit Designation { await updateModuleRecord('designation', row.id, { status: row.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE' }); setOpenMenuId(null); await load(); }} style="display:flex;width:100%;align-items:center;gap:10px;border-radius:8px;padding:8px 12px;font-size:13px;font-weight:500;color:#374151;background:none;border:none;cursor:pointer"> {row.status === 'ACTIVE' ? 'Deactivate' : 'Activate'} { if (!window.confirm(`Delete designation "${row.name}"?`)) return; await deleteModuleRecord('designation', row.id); setOpenMenuId(null); await load(); }} style="display:flex;width:100%;align-items:center;gap:10px;border-radius:8px;padding:8px 12px;font-size:13px;font-weight:500;color:#DC2626;background:none;border:none;cursor:pointer"> Delete )} {/* Pagination */} 0}> Showing 1–{filteredRows().length} of {filteredRows().length} designations ‹ 1 2 › {/* ── FORM VIEW ── */} {/* Top tabs */} { setView('list'); resetForm(); }} style="padding-bottom:12px;font-size:14px;font-weight:500;color:#6B7280;background:none;border:none;cursor:pointer"> All Designations {editingId() ? 'Edit Designation' : 'Create Designation'} {/* Sub-tabs */} {(['general', 'settings', 'permissions'] as const).map((tab, i) => { const labels = ['General Information', 'Designation Settings', 'Permissions']; const active = () => formTab() === tab; return ( setFormTab(tab)} style={`position:relative;padding:14px 8px;font-size:13px;font-weight:500;background:none;border:none;cursor:pointer;color:${active() ? '#FF5E13' : '#6B7280'}`} > {labels[i]} ); })} {error()} {/* General Information */} Select department {(d) => {d}} Select level {(l) => {l}} Description setDescription(e.currentTarget.value)} placeholder="Brief description of this designation's responsibilities..." rows="3" style="display:block;margin-top:6px;width:100%;border-radius:10px;border:1px solid #E5E7EB;background:white;padding:10px 14px;font-size:13px;color:#111827;outline:none;resize:none;box-sizing:border-box;font-family:inherit" /> {/* Designation Settings */} Designation Status Set whether this designation is currently active {(['ACTIVE', 'INACTIVE'] as const).map((s) => ( setStatus(s)} style={`height:38px;border-radius:10px;padding:0 20px;font-size:13px;font-weight:600;cursor:pointer;border:1px solid ${status() === s ? '#FF5E13' : '#E5E7EB'};background:${status() === s ? '#FFF3EE' : 'white'};color:${status() === s ? '#FF5E13' : '#6B7280'}`} > {s === 'ACTIVE' ? 'Active' : 'Inactive'} ))} Can Manage Team This designation can manage team members setCanManageTeam((v) => !v)} style={`position:relative;width:44px;height:24px;border-radius:12px;border:none;cursor:pointer;background:${canManageTeam() ? '#FF5E13' : '#E5E7EB'};transition:background 0.2s;flex-shrink:0`} > Can Approve Requests This designation can approve employee requests setCanApprove((v) => !v)} style={`position:relative;width:44px;height:24px;border-radius:12px;border:none;cursor:pointer;background:${canApprove() ? '#FF5E13' : '#E5E7EB'};transition:background 0.2s;flex-shrink:0`} > {/* Permissions */} Select the permissions available to employees with this designation. {['View Employees', 'Create Employees', 'Edit Employees', 'Delete Employees', 'Assign Roles', 'Approve Requests', 'Manage Team Members'].map((item) => ( {item} ))} {/* Form actions */} { setView('list'); resetForm(); }} style="height:38px;border-radius:10px;border:1px solid #E5E7EB;background:white;padding:0 20px;font-size:13px;font-weight:600;color:#374151;cursor:pointer" > Cancel void save()} disabled={isSaving()} style="height:38px;border-radius:10px;background:#0D0D2A;padding:0 24px;font-size:13px;font-weight:600;color:white;border:none;cursor:pointer" > {isSaving() ? 'Saving...' : editingId() ? 'Update Designation' : 'Create Designation'} ); }
Manage all job designations and position levels
No designations found
Create your first designation to get started.
{row.name}
Showing 1–{filteredRows().length} of {filteredRows().length} designations
Designation Status
Set whether this designation is currently active
Can Manage Team
This designation can manage team members
Can Approve Requests
This designation can approve employee requests
Select the permissions available to employees with this designation.