import { For, Show, createMemo, createSignal, onMount } from 'solid-js'; import AdminShell from '~/components/AdminShell'; import type { CrudRecord } from '~/lib/admin/types'; const API = '/api/gateway'; type RoleRecord = CrudRecord & { key?: string; department?: string; departmentId?: string; description?: string; usersAssigned?: number; permissionsCount?: number; canApproveRequests?: boolean; canManageSystemSettings?: boolean; createdDate?: string; }; type DepartmentOption = { id: string; name: string }; // Permission matrix: each module maps to CRUD permission keys const MODULE_PERMISSIONS = [ { module: 'Employee Management', prefix: 'EMPLOYEES' }, { module: 'Department Management', prefix: 'DEPARTMENTS' }, { module: 'Designation Management', prefix: 'DESIGNATIONS' }, { module: 'Role Management', prefix: 'ROLES' }, { module: 'Verification Management', prefix: 'VERIFICATIONS' }, { module: 'Approval Management', prefix: 'APPROVALS' }, { module: 'Users Management', prefix: 'USERS' }, { module: 'Company Management', prefix: 'COMPANIES' }, ]; const ACTIONS = ['VIEW', 'CREATE', 'UPDATE', 'DELETE'] as const; function permKey(prefix: string, action: string) { return `${prefix}_${action}`; } function normalizeRole(item: any, idx: number): RoleRecord { return { id: String(item.id ?? `role-${idx + 1}`), name: String(item.name ?? ''), key: item.key ? String(item.key) : undefined, department: item.department_name ? String(item.department_name) : undefined, departmentId: item.department_id ? String(item.department_id) : undefined, description: item.description ? String(item.description) : undefined, usersAssigned: Number(item.users_assigned ?? 0), permissionsCount: Number(item.permissions_count ?? 0), canApproveRequests: Boolean(item.can_approve_requests ?? false), canManageSystemSettings: Boolean(item.can_manage_system_settings ?? false), status: item.is_active === false ? 'INACTIVE' : 'ACTIVE', updatedAt: String(item.updated_at ?? item.created_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 }) { return ( ); } export default function RoleManagementPage() { const [view, setView] = createSignal<'list' | 'form' | 'detail'>('list'); const [listTab, setListTab] = createSignal<'all' | 'create' | 'view'>('all'); const [formTab, setFormTab] = createSignal<'general' | 'permissions' | 'settings'>('general'); const [detailTab, setDetailTab] = createSignal<'permissions' | 'users' | 'logs'>('permissions'); const [search, setSearch] = createSignal(''); const [rows, setRows] = createSignal([]); const [viewingRole, setViewingRole] = createSignal(null); const [viewingPermissions, setViewingPermissions] = createSignal([]); const [editingId, setEditingId] = createSignal(null); const [openMenuId, setOpenMenuId] = createSignal(null); const [isLoading, setIsLoading] = createSignal(false); const [error, setError] = createSignal(''); // Form state const [name, setName] = createSignal(''); const [roleKey, setRoleKey] = createSignal(''); const [description, setDescription] = createSignal(''); const [departmentId, setDepartmentId] = createSignal(''); const [status, setStatus] = createSignal<'ACTIVE' | 'INACTIVE'>('ACTIVE'); const [canApproveRequests, setCanApproveRequests] = createSignal(false); const [canManageSystemSettings, setCanManageSystemSettings] = createSignal(false); const [selectedPermissions, setSelectedPermissions] = createSignal>(new Set()); const [isSaving, setIsSaving] = createSignal(false); const [formError, setFormError] = createSignal(''); const [departments, setDepartments] = createSignal([]); const load = async () => { setIsLoading(true); setError(''); try { const params = new URLSearchParams({ audience: 'INTERNAL', per_page: '100', q: search().trim() }); const res = await fetch(`${API}/api/admin/roles?${params}`); if (!res.ok) throw new Error(`Request failed (${res.status})`); const payload = await res.json().catch(() => null); const list: any[] = Array.isArray(payload) ? payload : Array.isArray(payload?.roles) ? payload.roles : []; setRows(list.map(normalizeRole)); } catch (err: any) { setError(err?.message || 'Could not reach roles API.'); setRows([]); } finally { setIsLoading(false); } }; const loadDepartments = async () => { try { const res = await fetch(`${API}/api/admin/departments?per_page=100`); if (!res.ok) return; const payload = await res.json().catch(() => null); const list: any[] = Array.isArray(payload?.departments) ? payload.departments : []; setDepartments(list.map((d: any) => ({ id: String(d.id), name: String(d.name) }))); } catch { /* dropdown just empty */ } }; onMount(() => { void load(); void loadDepartments(); }); const filteredRows = createMemo(() => { const q = search().toLowerCase(); if (!q) return rows(); return rows().filter(r => r.name.toLowerCase().includes(q) || (r.key || '').toLowerCase().includes(q) ); }); const togglePermission = (key: string) => { setSelectedPermissions(prev => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); }; const resetForm = () => { setEditingId(null); setName(''); setRoleKey(''); setDescription(''); setDepartmentId(''); setStatus('ACTIVE'); setCanApproveRequests(false); setCanManageSystemSettings(false); setSelectedPermissions(new Set()); setFormTab('general'); setFormError(''); }; const openCreate = () => { resetForm(); setView('form'); }; const openEdit = (row: RoleRecord) => { setEditingId(row.id); setName(row.name); setRoleKey(row.key || ''); setDescription(row.description || ''); setDepartmentId(row.departmentId || ''); setStatus(row.status === 'INACTIVE' ? 'INACTIVE' : 'ACTIVE'); setCanApproveRequests(Boolean(row.canApproveRequests)); setCanManageSystemSettings(Boolean(row.canManageSystemSettings)); setSelectedPermissions(new Set()); setFormTab('general'); setView('form'); setOpenMenuId(null); // Fetch permission_keys for this role fetch(`${API}/api/admin/roles/${row.id}`).then(r => r.json()).then(detail => { if (Array.isArray(detail?.permission_keys)) { setSelectedPermissions(new Set(detail.permission_keys)); } }).catch(() => {}); }; const openDetail = async (row: RoleRecord) => { setViewingRole(row); setView('detail'); setListTab('view'); setOpenMenuId(null); setViewingPermissions([]); try { const res = await fetch(`${API}/api/admin/roles/${row.id}`); if (res.ok) { const detail = await res.json(); setViewingPermissions(Array.isArray(detail?.permission_keys) ? detail.permission_keys : []); } } catch { /* ignore */ } }; const save = async () => { if (!name().trim() || !roleKey().trim()) { setFormError('Role name and role key are required.'); setFormTab('general'); return; } setIsSaving(true); setFormError(''); try { const isCreate = !editingId(); const endpoint = isCreate ? `${API}/api/admin/roles` : `${API}/api/admin/roles/${editingId()}`; const method = isCreate ? 'POST' : 'PATCH'; const body: Record = { name: name().trim(), description: description().trim() || null, is_active: status() === 'ACTIVE', can_approve_requests: canApproveRequests(), can_manage_system_settings: canManageSystemSettings(), permission_keys: Array.from(selectedPermissions()), }; if (departmentId().trim()) body.department_id = departmentId().trim(); if (isCreate) { body.key = roleKey().trim(); body.audience = 'INTERNAL'; } const res = await fetch(endpoint, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error((data as any).message || `Request failed (${res.status})`); } setView('list'); resetForm(); await load(); } catch (err: any) { setFormError(err?.message || 'Failed to save role.'); } finally { setIsSaving(false); } }; const deleteRole = async (id: string, roleName: string) => { if (!window.confirm(`Delete role "${roleName}"?`)) return; setOpenMenuId(null); try { const res = await fetch(`${API}/api/admin/roles/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(`Request failed (${res.status})`); await load(); } catch (err: any) { setError(err?.message || 'Failed to delete role.'); } }; const toggleStatus = async (row: RoleRecord) => { setOpenMenuId(null); try { const res = await fetch(`${API}/api/admin/roles/${row.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ is_active: row.status !== 'ACTIVE' }), }); if (!res.ok) throw new Error(`Request failed (${res.status})`); await load(); } catch (err: any) { setError(err?.message || 'Failed to update status.'); } }; return (

Internal Role Management

Define and manage organizational access levels with granular permission control

{/* ── LIST VIEW ── */}
{([ { key: 'all', label: 'All Roles', action: () => { setListTab('all'); void load(); } }, { key: 'create', label: 'Create Role', action: () => { setListTab('create'); openCreate(); } }, { key: 'view', label: 'View Role', action: () => setListTab('view') }, ] as const).map((tab) => ( ))}
{error()}
{ setSearch(e.currentTarget.value); void load(); }} placeholder="Search roles..." style="height:34px;flex:1;border-radius:8px;border:1px solid #E5E7EB;background:white;padding:0 12px;font-size:13px;color:#111827;outline:none" />
{['Role Name', 'Role Key', 'Department', 'Users', 'Permissions', 'Status', 'Actions'].map(h => ( ))} 0} fallback={ } > {(row) => ( )}
{h}

No internal roles found

Create your first role to get started.

}>

Loading...

{row.name}

{row.description}

{row.key || '—'} {row.department || '—'} {Number(row.usersAssigned || 0)} users {Number(row.permissionsCount || 0)}
{/* ── FORM VIEW ── */}
{(['general', 'permissions', 'settings'] as const).map((tab, i) => { const labels = ['General Information', 'Module Access', 'Role Settings']; const active = () => formTab() === tab; return ( ); })}
{formError()}
{/* General */}