People
Internal directory that rolls up customers + partners + staff, plus the staff-only user management surface at /people/employees. This is the module ops uses to see "who's who" across all user types in one place; Admin → Permissions is where roles are assigned once a user exists.
Scope
Two pages:
/people—src/pages/people/People.tsx. Cross-party directory with KPIs (customers, partners, staff counts + 30-day new-arrivals) and a recent-activity feed drawn from the audit log./people/employees—src/pages/people/Employees.tsx. Thin wrapper around the shared<UserManagement>component withexcludeRoles={['agent', 'customer']}.
Shared component: src/components/admin/UserManagement.tsx — the actual CRUD surface for staff users, reused here and on Admin.
Pages
People.tsx — /people
Route gated by customers.view (src/App.tsx:181).
Route permission is inherited, not semantic
/people reuses customers.view because most viewers of this directory are customer-facing staff. There's no dedicated people.view permission. See docs/PERMISSIONS.md §6.15.
Pulls three parallel fetches on mount (People.tsx:40-75):
fetchCustomers()→ customer count + new-last-30d.fetchAgentsWithStats()→ partner count + new-last-30d.apiFetch('/users')→ staff count + new-last-30d. If the call 403s the staff KPI showsnullinstead of a number.
Then loads recent audit-log activity (People.tsx:77-99) filtered to customer / agent / lead entities, top 20. If the audit call is forbidden, the activity panel shows the "restricted" empty state instead of an error.
Employees.tsx — /people/employees
Full file (Employees.tsx:1-10):
import { MainLayout } from '@/components/layout/MainLayout';
import { UserManagement } from '@/components/admin/UserManagement';
export default function Employees() {
return (
<MainLayout title="Employees & Staff" subtitle="Manage internal users and roles">
<UserManagement excludeRoles={['agent', 'customer']} />
</MainLayout>
);
}
Route gated by admin.view (src/App.tsx:182). All behavior lives in <UserManagement>.
Role assignment — <UserManagement>
Source: src/components/admin/UserManagement.tsx. Reused in both /people/employees and /admin.
Operations
| Operation | Call |
|---|---|
| List users | GET /users (UserManagement.tsx:171) |
| Create user | POST /users |
| Change role | POST /users/:id/roles with { role: 'UPPERCASE_NAME' } (UserManagement.tsx:226-229) |
| Reset password | POST /users/:id/password (or similar — see component) |
| Delete user | DELETE /users/:id |
Role catalog
UserManagement.tsx is the authoritative UI list of canonical roles (UserManagement.tsx:48-73, :110-120). 18 active roles + 3 legacy buckets (operations, finance, sales) preserved so existing assignments still render. See PERMISSIONS.md §3 for the definitive catalog.
Visual tiering (UserManagement.tsx:77-101):
- Destructive (red) —
ceo,gm,director,it_admin,admin_hr(super-admin / admin tier). - Primary — manager-tier roles.
- Info — operational / support roles.
- Muted —
auditor,agent,customer.
Wire format
UI uses lowercase (sales_manager), API uses uppercase (SALES_MANAGER). Conversion helpers at UserManagement.tsx:158-166 (mapApiRole / mapAppRoleToApiRole). Unknown values fall back to null so nothing mis-renders.
Staff email domain gate
isAllowedStaffEmail (UserManagement.tsx:123-126) checks the user's email ends with @alhuda.co.in or @alhudatravels.in.
Relationship to permissions
Role membership does not directly grant permissions in code. The chain is:
<UserManagement>assigns arole_name(e.g.SALES_MANAGER) to a user viaPOST /users/:id/roles.- The DB has a
RolePermissiontable populated by seed migrations (seesupabase/migrations/*permissions*.sql) that maps each role to its explicit list of permission rows. auth_user_has_permission(perm_name)(Postgres function) resolves a user's effective permissions by UNION-ing (a) role grants with (b) per-user overrides fromUserPermission.- Code checks specific permissions via
<ProtectedRoute>,<PermissionGate>, orrequirePermission()— never by role name.
The three super-admin roles (CEO, GM, IT_ADMIN) are super-admins because migration 20260416020000_remove_permission_bypass.sql seeds them every permission row explicitly — not by a hardcoded role short-circuit. Revoking a specific permission in Admin → Permissions will restrict even a CEO.
Per-user overrides (UserPermission.allowed=true/false) beat role grants — see PERMISSIONS.md §2 for the precedence order.
Changing a user's role takes effect on next token refresh
The role+permission set is included in the user's auth context. A user in an active session continues to use their old role until they re-authenticate or their session is refreshed. If you need an immediate revocation (e.g. offboarding), follow up with a session invalidation via the admin tool.
Permissions
Canonical reference: docs/PERMISSIONS.md §6.15.
| Action | Permission |
|---|---|
View /people |
customers.view |
View /people/employees |
admin.view |
| Create staff user | admin.view (should tighten to admin.users.create) |
| Reset staff password | admin.view (should tighten to admin.users.reset_password) |
| Change staff role | admin.view (should tighten to admin.users.edit) |
| Delete staff user | admin.view (should tighten to admin.users.delete) |
| View recent activity feed | customers.view + (audit log read on server) |
Granular admin permissions are seeded but not yet wired
admin.users.create, admin.users.edit, admin.users.delete, admin.users.reset_password, admin.roles.*, admin.permissions.edit exist in the catalog (PERMISSIONS.md §4.4) and are seeded to super-admin-tier roles, but <UserManagement> today gates all its actions behind the coarse admin.view. See docs/PERMISSIONS.md §8 drift item 7.
Related
- Customers — one of the three populations counted on
/people. - Partners — another counted population.
- Admin module —
/adminhosts the Permissions matrix UI where role grants and user overrides are edited. - PERMISSIONS.md §3, §6.15, §6.16 — role catalog, page matrix, admin surface.