import { createResource, createSignal, createMemo, Show, For } from 'solid-js'; import { Plus, Pencil, Archive, RotateCcw, Trash2, ChevronLeft, ChevronRight } from 'lucide-solid'; import AdminShell from '~/components/AdminShell'; const API = '/api/gateway'; type Department = { id: string; departmentId?: string; name?: string; departmentName?: string; description?: string; createdBy?: string; updatedBy?: string; is_archived?: boolean; status?: string | number; createdAt?: string; created_at?: string; updatedAt?: string; }; type ViewMode = 'list' | 'create' | 'update'; async function loadDepartments(params: { page: number; limit: number; status: string }): Promise<{ items: Department[]; total: number }> { try { const res = await fetch(`${API}/api/admin/departments?page=${params.page}&limit=${params.limit}&status=${params.status}`); if (!res.ok) throw new Error('Failed to load'); const data = await res.json(); const items = Array.isArray(data) ? data : (data.departments ?? []); const total = data.total ?? items.length; return { items, total }; } catch { return { items: [], total: 0 }; } } function isArchived(item: Department): boolean { if (item.is_archived !== undefined) return item.is_archived; if (item.status !== undefined) { const s = String(item.status).toUpperCase(); return s === 'ARCHIVED' || s === '2'; } return false; } function deptLabel(item: Department): string { return item.departmentName || item.name || '—'; } function fmtDate(val?: string): string { if (!val) return '—'; try { return new Date(val).toLocaleDateString(); } catch { return val; } } export default function DepartmentPage() { const [view, setView] = createSignal('list'); const [statusFilter, setStatusFilter] = createSignal<'active' | 'archived'>('active'); const [page, setPage] = createSignal(1); const limit = 10; const fetchParams = createMemo(() => ({ page: page(), limit, status: statusFilter() === 'archived' ? '2' : '1', })); const [data, { refetch }] = createResource(fetchParams, loadDepartments); // form state const [createName, setCreateName] = createSignal(''); const [createDesc, setCreateDesc] = createSignal(''); const [creating, setCreating] = createSignal(false); const [createError, setCreateError] = createSignal(''); // inline edit const [editingId, setEditingId] = createSignal(''); const [editName, setEditName] = createSignal(''); const [editDesc, setEditDesc] = createSignal(''); const [saving, setSaving] = createSignal(false); const [editError, setEditError] = createSignal(''); const [busy, setBusy] = createSignal(''); const [actionError, setActionError] = createSignal(''); const items = () => data()?.items ?? []; const total = () => data()?.total ?? 0; const totalPages = () => Math.ceil(total() / limit); const filtered = createMemo(() => { const all = items(); return statusFilter() === 'archived' ? all.filter((d) => isArchived(d)) : all.filter((d) => !isArchived(d)); }); const handleCreate = async (e: Event) => { e.preventDefault(); if (!createName().trim()) return; setCreating(true); setCreateError(''); try { const res = await fetch(`${API}/api/admin/departments`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: createName().trim(), description: createDesc().trim() }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as any).message || 'Failed to create'); } setCreateName(''); setCreateDesc(''); setView('list'); setStatusFilter('active'); setPage(1); refetch(); } catch (err: any) { setCreateError(err.message || 'Failed to create department'); } finally { setCreating(false); } }; const startEdit = (item: Department) => { setEditingId(item.id); setEditName(deptLabel(item)); setEditDesc(item.description ?? ''); setEditError(''); }; const cancelEdit = () => { setEditingId(''); setEditError(''); }; const handleUpdate = async (id: string) => { if (!editName().trim()) return; setSaving(true); setEditError(''); try { const res = await fetch(`${API}/api/admin/departments/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: editName().trim(), description: editDesc().trim() }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error((err as any).message || 'Failed to update'); } setEditingId(''); refetch(); } catch (err: any) { setEditError(err.message || 'Failed to update department'); } finally { setSaving(false); } }; const handleArchive = async (id: string) => { setBusy(id); setActionError(''); try { const res = await fetch(`${API}/api/admin/departments/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ is_archived: true }), }); if (!res.ok) throw new Error('Failed to archive'); refetch(); } catch (err: any) { setActionError(err.message || 'Failed to archive department'); } finally { setBusy(''); } }; const handleRestore = async (id: string) => { setBusy(id); setActionError(''); try { const res = await fetch(`${API}/api/admin/departments/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ is_archived: false }), }); if (!res.ok) throw new Error('Failed to restore'); refetch(); } catch (err: any) { setActionError(err.message || 'Failed to restore department'); } finally { setBusy(''); } }; const handleDelete = async (id: string, name: string) => { if (!confirm(`Delete department "${name}"?`)) return; setBusy(id); setActionError(''); try { const res = await fetch(`${API}/api/admin/departments/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error('Failed to delete'); refetch(); } catch (err: any) { setActionError(err.message || 'Failed to delete department'); } finally { setBusy(''); } }; const switchTab = (t: 'active' | 'archived') => { setView('list'); setStatusFilter(t); setPage(1); setEditingId(''); }; return (
{/* ── Page header ── */}

Departments

Manage organization structure and units.

{/* ── Tab + action bar ── */}
{(t) => ( )}
{/* ── Content ── */}
{/* Create form */}
setCreateName(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]" />
setCreateDesc(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]" />

{createError()}

{/* List view */}
{actionError()}
{(item) => ( <> {/* Inline edit row */} )}
ID Name Description Created By Created Last Updated By Last Updated At Actions
Loading…
Failed to load. Is the backend running?
No departments found.
{item.departmentId || item.id.slice(0, 8)} {deptLabel(item)} {item.description || '—'} {item.createdBy || '—'} {fmtDate(item.createdAt || item.created_at)} {item.updatedBy || item.createdBy || '—'} {fmtDate(item.updatedAt)}
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]" />
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]" />

{editError()}

{/* Pagination */} 1}>
Page {page()} of {totalPages()}
); }