// Permissions.jsx — Role × Feature access matrix function PermissionsPage() { const features = [ { id: 'dashboard', label: 'Dashboard' }, { id: 'workorders', label: 'Work Orders' }, { id: 'create', label: 'Create Work Order' }, { id: 'mobile', label: 'Mobile Job View' }, { id: 'completion', label: 'Job Completion' }, { id: 'notifications', label: 'Notifications' }, { id: 'repairs', label: 'Repair Tickets' }, { id: 'analytics', label: 'Analytics' }, { id: 'export', label: 'AutoCount Export' }, { id: 'users', label: 'User Management' }, { id: 'settings', label: 'Settings' }, { id: 'permissions', label: 'Role Permissions' }, ]; const cellStyle = (lvl) => { if (lvl === 'edit') return { background: 'rgba(34,197,94,0.12)', color: '#15803d' }; if (lvl === 'view') return { background: 'rgba(37,99,235,0.10)', color: '#1d4ed8' }; return { background: 'rgba(148,163,184,0.12)', color: '#64748b' }; }; const cellLabel = (lvl) => lvl === 'edit' ? 'Edit' : lvl === 'view' ? 'View' : '—'; return (
Role × Feature Access Matrix
Single source of truth for what each role can see and do across the system.
Edit View only No access
{ROLES.map(r => ( ))} {features.map(f => ( {ROLES.map(r => { const lvl = can(r, f.id); return ( ); })} ))}
Feature{r}
{f.label} {cellLabel(lvl)}
How this is enforced: the same matrix lives on the FastAPI backend. Every API endpoint checks the requester's role against this table — the frontend hiding a button is for UX, but the backend's check is what actually keeps data safe. Frontend and backend pull from one shared permissions.json so the two never drift.
); } Object.assign(window, { PermissionsPage });