import { A } from '@solidjs/router'; import { createMemo, createResource, createSignal, For, Show } from 'solid-js'; import AdminShell from '~/components/AdminShell'; const API = '/api/gateway'; interface Approval { id: string; requestType?: string; type?: string; requestStatus?: string; status?: string; priority?: number; createdAt?: string; created_at?: string; requester?: { name?: string; email?: string }; requesterName?: string; requesterEmail?: string; requester_name?: string; requester_email?: string; } interface ApprovalRule { id: string; entityType: string; entity_type?: string; approverType: string; approver_type?: string; priority?: number; } const ENTITY_TYPE_OPTIONS = ['JOB_POST', 'COMPANY', 'LEAD', 'INVOICE']; const APPROVER_TYPE_OPTIONS = ['USER', 'ROLE']; const REQUEST_FILTERS = ['ALL', 'PROFILE', 'JOB', 'REQUIREMENT']; type StatusTab = 'PENDING' | 'APPROVED' | 'REJECTED' | 'CHANGES_REQUESTED' | 'CANCELLED' | 'rules'; type PanelTab = 'list' | 'view'; const STATUS_TABS: { key: StatusTab; label: string }[] = [ { key: 'PENDING', label: 'Pending' }, { key: 'APPROVED', label: 'Approved' }, { key: 'REJECTED', label: 'Rejected' }, { key: 'CHANGES_REQUESTED', label: 'Changes Requested' }, { key: 'CANCELLED', label: 'Cancelled' }, { key: 'rules', label: 'Rules' }, ]; async function fetchApprovals(): Promise { try { const res = await fetch(`${API}/api/admin/approvals`); if (!res.ok) throw new Error('Failed to load approvals'); const data = await res.json(); return Array.isArray(data) ? data : (data.approvals || []); } catch { return []; } } async function fetchRules(): Promise { try { const res = await fetch(`${API}/api/admin/approval-rules`); if (!res.ok) throw new Error('Failed to load rules'); const data = await res.json(); return Array.isArray(data) ? data : (data.rules || []); } catch { return []; } } function statusValue(item: Approval) { return (item.requestStatus || item.status || 'PENDING').toUpperCase(); } function requestTypeValue(item: Approval) { return (item.requestType || item.type || 'OTHER').toUpperCase(); } function requestClass(item: Approval): 'PROFILE' | 'JOB' | 'REQUIREMENT' | 'OTHER' { const t = requestTypeValue(item); if (t.includes('JOB')) return 'JOB'; if (t.includes('LEAD') || t.includes('REQUIREMENT')) return 'REQUIREMENT'; if (t.includes('PROFILE') || t.includes('USER') || t.includes('COMPANY') || t.includes('PROFESSIONAL') || t.includes('CUSTOMER')) return 'PROFILE'; return 'OTHER'; } function StatusBadge(props: { status: string }) { const s = props.status.toUpperCase(); if (s === 'APPROVED') return APPROVED; if (s === 'REJECTED') return REJECTED; if (s === 'CHANGES_REQUESTED') return CHANGES REQUESTED; if (s === 'CANCELLED') return CANCELLED; return {s}; } export default function ApprovalPage() { const [activeTab, setActiveTab] = createSignal('PENDING'); const [panelTab, setPanelTab] = createSignal('list'); const [selectedApproval, setSelectedApproval] = createSignal(null); const [search, setSearch] = createSignal(''); const [requestFilter, setRequestFilter] = createSignal('ALL'); const [currentPage, setCurrentPage] = createSignal(1); const perPage = 10; const [approvals, { refetch: refetchApprovals }] = createResource(fetchApprovals); const [acting, setActing] = createSignal(''); const [approvalError, setApprovalError] = createSignal(''); const [rules, { refetch: refetchRules }] = createResource(fetchRules); const [showAddRule, setShowAddRule] = createSignal(false); const [newEntityType, setNewEntityType] = createSignal('JOB_POST'); const [newApproverType, setNewApproverType] = createSignal('USER'); const [newPriority, setNewPriority] = createSignal(1); const [ruleError, setRuleError] = createSignal(''); const [deletingRule, setDeletingRule] = createSignal(''); const [submittingRule, setSubmittingRule] = createSignal(false); const filteredApprovals = createMemo(() => { const tab = activeTab(); const q = search().trim().toLowerCase(); const rf = requestFilter(); const list = approvals() ?? []; if (tab === 'rules') return [] as Approval[]; return list.filter((a) => { const matchesStatus = statusValue(a) === tab; if (!matchesStatus) return false; const cls = requestClass(a); const matchesType = rf === 'ALL' || cls === rf; if (!matchesType) return false; if (!q) return true; const requesterName = (a.requester?.name || a.requesterName || a.requester_name || '').toLowerCase(); const requesterEmail = (a.requester?.email || a.requesterEmail || a.requester_email || '').toLowerCase(); const requestType = requestTypeValue(a).toLowerCase(); return requesterName.includes(q) || requesterEmail.includes(q) || requestType.includes(q) || a.id.toLowerCase().includes(q); }); }); const totalPages = createMemo(() => Math.max(1, Math.ceil(filteredApprovals().length / perPage))); const paginatedApprovals = createMemo(() => { const start = (currentPage() - 1) * perPage; return filteredApprovals().slice(start, start + perPage); }); const countFor = (status: string) => { const list = approvals() ?? []; if (status === 'rules') return (rules() ?? []).length; return list.filter((a) => statusValue(a) === status).length; }; const selectApproval = (approval: Approval) => { setSelectedApproval(approval); setPanelTab('view'); }; const handleAction = async (id: string, newStatus: 'APPROVED' | 'REJECTED' | 'CHANGES_REQUESTED' | 'CANCELLED') => { try { setActing(`${id}-${newStatus}`); setApprovalError(''); const res = await fetch(`${API}/api/admin/approvals/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus }), }); if (!res.ok) throw new Error(`Failed to ${newStatus.toLowerCase()} approval`); refetchApprovals(); if (selectedApproval()?.id === id) { setSelectedApproval((prev) => (prev ? { ...prev, status: newStatus, requestStatus: newStatus } : prev)); } } catch (err: any) { setApprovalError(err.message || 'Action failed'); } finally { setActing(''); } }; const handleAddRule = async () => { try { setSubmittingRule(true); setRuleError(''); const res = await fetch(`${API}/api/admin/approval-rules`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entityType: newEntityType(), approverType: newApproverType(), priority: newPriority(), }), }); if (!res.ok) throw new Error('Failed to create rule'); setShowAddRule(false); setNewEntityType('JOB_POST'); setNewApproverType('USER'); setNewPriority(1); refetchRules(); } catch (err: any) { setRuleError(err.message || 'Failed to create rule'); } finally { setSubmittingRule(false); } }; const handleDeleteRule = async (id: string) => { if (!confirm('Delete this approval rule?')) return; try { setDeletingRule(id); setRuleError(''); const res = await fetch(`${API}/api/admin/approval-rules/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error('Failed to delete rule'); refetchRules(); } catch (err: any) { setRuleError(err.message || 'Failed to delete rule'); } finally { setDeletingRule(''); } }; return (

Approval Management

Review and manage approvals and approval rules.

{STATUS_TABS.map((t) => { const count = countFor(t.key); return ( ); })}
{approvalError()}
{ setSearch(e.currentTarget.value); setCurrentPage(1); }} style="border:1px solid #cbd5e1;border-radius:6px;padding:7px 12px;font-size:14px;width:300px;outline:none;" /> {filteredApprovals().length} records
0}> {(item) => { const requesterName = item.requester?.name || item.requesterName || item.requester_name || '—'; const requesterEmail = item.requester?.email || item.requesterEmail || item.requester_email || ''; const status = statusValue(item); const requestType = requestTypeValue(item); const submittedAt = item.createdAt || item.created_at; return ( ); }}
Approval ID Requester Request Type Status Priority Submitted At Actions
Loading...
Failed to load approvals.
No matching approvals.
{item.id.slice(0, 8)}...
{requesterName}
{requesterEmail}
{requestType} {item.priority ?? '—'} {submittedAt ? new Date(submittedAt).toLocaleString() : '—'}
Page {currentPage()} of {totalPages()}
Select an approval from list to view details.

}>

Approval Details

Request Summary

Approval ID: {selectedApproval()!.id}

Type: {requestTypeValue(selectedApproval()!)}

Status: {statusValue(selectedApproval()!)}

Priority: {selectedApproval()!.priority ?? '—'}

Submitted: {(selectedApproval()!.createdAt || selectedApproval()!.created_at) ? new Date((selectedApproval()!.createdAt || selectedApproval()!.created_at)!).toLocaleString() : '—'}

Requester

Name: {selectedApproval()!.requester?.name || selectedApproval()!.requesterName || selectedApproval()!.requester_name || '—'}

Email: {selectedApproval()!.requester?.email || selectedApproval()!.requesterEmail || selectedApproval()!.requester_email || '—'}

Class: {requestClass(selectedApproval()!)}

{ruleError()}

New Approval Rule

setNewPriority(parseInt(e.currentTarget.value, 10) || 1)} />
0}> {(rule) => ( )}
Entity Type Approver Type Priority Actions
Loading...
Failed to load rules.
No approval rules found.
{rule.entityType || rule.entity_type || '—'} {rule.approverType || rule.approver_type || '—'} {rule.priority ?? '—'}
); }