import { For, Show, createMemo, createSignal, onMount } from 'solid-js'; import { useSearchParams } from '@solidjs/router'; import type { CrudRecord } from '~/lib/admin/types'; const API = ''; type DesignationRecord = CrudRecord & { code?: string; department?: string; departmentId?: string; level?: string; description?: string; totalEmployees?: number; createdDate?: string; canManageTeam?: boolean; canApprove?: boolean; }; type DepartmentOption = { id: string; name: string }; const permissionGroups = [ { title: 'Employee Management', items: ['View Employees', 'Create Employees', 'Edit Employees', 'Delete Employees'] }, { title: 'Role Management', items: ['View Roles', 'Assign Roles'] }, { title: 'Workflow Actions', items: ['Approve Requests', 'Manage Team Members'] }, ]; const LEVELS = ['Intern', 'Junior', 'Mid-Level', 'Senior', 'Lead', 'Manager', 'Director', 'VP', 'C-Level', 'Executive', 'Specialist', 'Analyst']; type DesignationListResponse = { designations?: any[]; data?: any[]; items?: any[]; }; function normalizeDesignation(item: any, idx: number): DesignationRecord { const status = String(item.status ?? '').toUpperCase(); const isActive = typeof item.is_active === 'boolean' ? item.is_active : status ? status === 'ACTIVE' : true; return { id: String(item.id ?? `des-${idx + 1}`), name: String(item.name ?? ''), code: item.code ? String(item.code) : undefined, department: item.department_name ? String(item.department_name) : undefined, departmentId: item.department_id ? String(item.department_id) : undefined, level: item.level ? String(item.level) : undefined, description: item.description ? String(item.description) : undefined, totalEmployees: Number(item.total_employees ?? 0), canManageTeam: Boolean(item.can_manage_team ?? false), canApprove: Boolean(item.can_approve ?? false), status: isActive ? 'ACTIVE' : 'INACTIVE', updatedAt: String(item.updated_at ?? ''), createdDate: String(item.created_at ?? ''), }; } 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 ( ); } function FormSelect(props: { label: string; required?: boolean; value: string; onChange: (v: string) => void; children: any }) { return ( ); } export default function DesignationManagementPage() { const [searchParams] = useSearchParams(); const isPreview = () => searchParams._preview === '1'; 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 [sortBy, setSortBy] = createSignal<'name_asc' | 'name_desc' | 'employees_desc' | 'employees_asc'>('name_asc'); const [sortMenuOpen, setSortMenuOpen] = createSignal(false); const [filterMenuOpen, setFilterMenuOpen] = createSignal(false); const [rows, setRows] = createSignal([]); const [openMenuId, setOpenMenuId] = createSignal(null); const [openMenuPos, setOpenMenuPos] = createSignal({ x: 0, y: 0 }); const [editingId, setEditingId] = createSignal(null); const [viewingRecord, setViewingRecord] = createSignal(null); const [deleteTarget, setDeleteTarget] = createSignal(null); const [isDeleting, setIsDeleting] = createSignal(false); const [name, setName] = createSignal(''); const [code, setCode] = createSignal(''); const [departmentId, setDepartmentId] = 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 [isLoading, setIsLoading] = createSignal(false); const [isSaving, setIsSaving] = createSignal(false); const [error, setError] = createSignal(''); const [departments, setDepartments] = createSignal([]); const load = async () => { setIsLoading(true); setError(''); try { const accessToken = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('nxtgauge_admin_access_token') || '' : ''; const params = new URLSearchParams({ page: '1', per_page: '100', q: search().trim(), }); if (statusFilter() !== 'all') { params.set('status', statusFilter()); } if (deptFilter() !== 'all') { params.set('department_id', deptFilter()); } const res = await fetch(`${API}/api/admin/designations?${params.toString()}`, { headers: { Accept: 'application/json', ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, credentials: 'include', }); if (!res.ok) throw new Error(`Request failed (${res.status})`); const payload = (await res.json().catch(() => null)) as DesignationListResponse | null; const list = Array.isArray(payload) ? payload : Array.isArray(payload?.designations) ? payload.designations : Array.isArray(payload?.data) ? payload.data : Array.isArray(payload?.items) ? payload.items : []; setRows(list.map(normalizeDesignation)); } catch (err: any) { setError(err?.message || 'Could not reach designations API.'); setRows([]); } finally { setIsLoading(false); } }; const loadDepartments = async () => { try { const accessToken = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('nxtgauge_admin_access_token') || '' : ''; const res = await fetch(`${API}/api/admin/departments?per_page=100`, { headers: { Accept: 'application/json', ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, credentials: 'include', }); if (!res.ok) return; const payload = await res.json().catch(() => null); const list: any[] = Array.isArray(payload) ? payload : Array.isArray(payload?.departments) ? payload.departments : Array.isArray(payload?.data) ? payload.data : Array.isArray(payload?.items) ? payload.items : []; setDepartments(list.map((d: any) => ({ id: String(d.id), name: String(d.name) }))); } catch { // departments dropdown will just be empty } }; onMount(() => { void load(); void loadDepartments(); }); const filteredRows = createMemo(() => { let r = rows(); if (statusFilter() !== 'all') r = r.filter((d) => d.status === statusFilter().toUpperCase()); if (deptFilter() !== 'all') { const selected = String(deptFilter()).trim().toLowerCase(); r = r.filter((d) => { const byId = String(d.departmentId || '').trim().toLowerCase(); const byName = String(d.department || '').trim().toLowerCase(); return byId === selected || byName === selected; }); } const q = search().toLowerCase(); if (q) { r = r.filter((d) => d.name.toLowerCase().includes(q) || String(d.code ?? '').toLowerCase().includes(q) || String(d.description ?? '').toLowerCase().includes(q) ); } const sorted = [...r]; const mode = sortBy(); sorted.sort((a, b) => { if (mode === 'name_desc') return b.name.localeCompare(a.name); if (mode === 'employees_desc') return Number(b.totalEmployees || 0) - Number(a.totalEmployees || 0); if (mode === 'employees_asc') return Number(a.totalEmployees || 0) - Number(b.totalEmployees || 0); return a.name.localeCompare(b.name); }); r = sorted; return r; }); const exportCsv = () => { const headers = ['Designation Name', 'Code', 'Department', 'Level', 'Employees', 'Status']; const rowsData = filteredRows().map((row) => [ row.name || '', row.code || '', row.department || '', row.level || '', String(row.totalEmployees ?? 0), row.status || '', ]); const csv = [ headers, ...rowsData, ] .map((line) => line.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')) .join('\n'); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `designation-management-${new Date().toISOString().slice(0, 10)}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; const resetForm = () => { setEditingId(null); setName(''); setCode(''); setDepartmentId(''); 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(row.code || ''); setDepartmentId(row.departmentId || ''); setLevel(row.level || ''); setDescription(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 (isSaving()) return; if (!name().trim() || !code().trim()) { setError('Designation name and code are required.'); setFormTab('general'); return; } setIsSaving(true); setError(''); const payload: Record = { name: name().trim(), code: code().trim(), level: level().trim() || null, description: description().trim() || null, status: status(), can_manage_team: canManageTeam(), can_approve: canApprove(), }; if (departmentId().trim()) { payload.department_id = departmentId().trim(); } try { const accessToken = typeof sessionStorage !== 'undefined' ? (sessionStorage.getItem('nxtgauge_admin_access_token') || '').trim() : ''; const endpoint = editingId() ? `${API}/api/admin/designations/${editingId()}` : `${API}/api/admin/designations`; const method = editingId() ? 'PATCH' : 'POST'; const res = await fetch(endpoint, { method, headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), }, credentials: 'include', body: JSON.stringify(payload), }); const raw = await res.text(); let message = ''; if (raw) { try { const parsed = JSON.parse(raw) as { message?: string; error?: string }; message = parsed?.message || parsed?.error || ''; } catch { message = raw; } } if (!res.ok) throw new Error(message || `Request failed (${res.status})`); setView('list'); resetForm(); await load(); } catch (err: any) { const msg = String(err?.message || '').trim(); setError(msg || '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) => ( ))}
{/* View Record panel */}

No designation selected

Click the menu on any designation row and choose View Details.

{/* Header */}

{viewingRecord()!.name}

{viewingRecord()!.description || 'No description'}

{/* Details grid */}

Designation Code

{viewingRecord()!.code || '—'}

Department

{viewingRecord()!.department || '—'}

Level

{viewingRecord()!.level || '—'}

Total Employees

{String(viewingRecord()!.totalEmployees ?? 0)}

Can Manage Team

{viewingRecord()!.canManageTeam ? 'Yes' : 'No'}

Created Date

{viewingRecord()!.createdDate?.slice(0, 10) || '—'}

{/* Actions */}
{/* 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" />
{(['name_asc', 'name_desc', 'employees_desc', 'employees_asc'] as const).map((s, i) => ( ))}

Status

{(['all', 'active', 'inactive'] as const).map((s) => ( ))}

Department

{(dept) => ( )}
{/* Table */}
0} fallback={ } > {(row) => ( )}
Designation Name Code Department Level Employees Status Actions

No designations found

Create your first designation to get started.

{row.name}

{String(row.code || '—')} {String(row.department || '—')} {String(row.level || '—')} {Number(row.totalEmployees || 0)}
{/* Pagination */} 0}>

Showing 1–{filteredRows().length} of {filteredRows().length} designations

Delete Designation?

You are about to permanently delete {deleteTarget()?.name}. This action cannot be undone.

{/* ── FORM VIEW (Create / Edit) ── */} {/* Top tabs */}
{/* Sub-tabs */}
{(['general', 'settings', 'permissions'] as const).map((tab, i) => { const labels = ['General Information', 'Designation Settings', 'Permissions']; const active = () => formTab() === tab; return ( ); })}
{error()}
{/* General Information */}
{(d) => } {(l) => }