Clinic Admin Portal — Single-Clinic Operations Hub
Audiences: clinical-buyer, developer, internal
The most feature-dense of the eleven portals: one clinic administrator manages users, scheduling, HR, inventory, accounting, billing, communications, and clinical configuration for a single clinic — roughly forty route sections under one role-gated shell.
Business Purpose
The clinic administrator is the operational owner of a single clinic. Where the doctor/nurse portals are clinical and the org-admin portal is cross-clinic, the clinic-admin portal is the back-office control room for one clinic: it hires and schedules staff, configures online booking and appointment templates, runs the invoice/EU-billing pipeline, manages inventory and devices, keeps the chart of accounts, and configures the clinic's diagnosis-engine catalogue.
Because a clinic buyer's day-to-day administration lives here, this portal is the single
largest route surface in ddx-web — 191 page.tsx files across ~40 top-level sections.
It is built from the same one-factory/one-config pattern as every other portal (see
Multi-Portal Architecture); its size comes from route
breadth, not a divergent shell.
Audiences
- Clinical buyer (clinic owner/office manager): this is the portal they evaluate — it is where "run my clinic" actually happens (staff, schedules, money, supplies, communications).
- Developer: one
RolePortalConfig(CLINIC_ADMIN_CONFIG) + one sectioned nav config govern the whole surface; new sections are added as route folders + nav entries, not a new shell. - Internal (ops/support): locates which section owns a given admin task (e.g. "where do I
configure online booking?" →
online-booking-config/).
Architecture
Shell composition (identical to every portal)
ddx-web/src/app/[locale]/portal/clinic-admin/layout.tsx is a two-line invocation of the
shared factory:
export default createRoleLayout(CLINIC_ADMIN_CONFIG, ClinicAdminNav);
where ClinicAdminNav renders <PortalNavigation roleKey="clinic-admin" />. The factory
(ddx-web/src/components/portal/createRoleLayout.tsx) runs the standard sequence: await params → requireRoleAccess('CLINIC_ADMIN_ONLY', locale) server-side RBAC gate → org fetch
→ brand/label resolution → <DDXHeader> + <PortalShell> + <AppStatusBar> + nav + SSE
bootstrap. There is no clinic-admin-specific shell code — the depth is entirely in the routes.
Role config
CLINIC_ADMIN_CONFIG (ddx-web/src/lib/portal/role-configs.ts:117):
| Field | Value |
|---|---|
roleKey | clinic-admin |
authRequirement | CLINIC_ADMIN_ONLY |
roleBadge | CLINIC_ADMIN, label key roles.clinic_admin, BuildingsIcon, warning variant |
brandNameKey | clinicAdmin.title |
Registered in the ROLE_CONFIGS registry (role-configs.ts:190).
Navigation sections
The sectioned nav config ddx-web/src/lib/portal/nav-configs/clinic-admin.ts groups the ~40
route sections into major nav groups (label keys shown as authored in the config, mixing the
admin:, clinicAdmin:, diagnosisEngine:, and common: namespaces):
| Nav group | Representative sections (route folders under clinic-admin/) |
|---|---|
| Overview | dashboard |
| User Management | users, approvals, invitations |
| Clinic Operations | appointments, appointment-templates, recurring-appointments, waitlist, waiting-room, online-booking-config, staff, departments, locations, holidays |
| Diagnosis Engine | diagnosis-engine (diseases / symptoms / treatments) |
| Billing & Revenue | payment-settings, invoices (clinicAdmin:invoices), charge items, pricing, patient accounts, insurance claims, EU-billing (coverage / claims / reconciliation) |
| Human Resources | hr (employees, contracts, timesheets, leave, certifications, skills) |
| Inventory | inventory (stock, devices, device-definitions, supply-requests, deliveries) |
| Accounting | accounting (chart of accounts, journal entries, cost centers, budgets, fiscal periods) |
| Communications | activity-log, communications, incoming-mail, messenger, messenger-admin |
| Insights & Config | analytics, reports, branding, settings, cms, document-generation, flowagent |
The label keys are authored inline in the nav config; several use
admin:navigation.*keys (shared with the admin portal) rather than a dedicatedclinicAdmin:key. This is intentional reuse — clinic-admin and admin share much administrative vocabulary — and is the reasonCLINIC_ADMIN_CONFIG.brandNameKeyresolves from theadmin-familyclinicAdmin.title.
Tech Stack & Choices
Same stack as every portal (see Multi-Portal Architecture):
Next.js 16 App Router, server-component RBAC gate via requireRoleAccess, next-intl
localePrefix: 'always', config-driven PortalNavigation. The only clinic-admin-specific
choices are the route breadth and the clinicAdmin i18n namespace.
Rejected: a bespoke clinic-admin shell. Every attempt to special-case the largest portal would fork the design vocabulary; instead the factory pattern scales to 40 sections at the cost of route folders + nav entries only.
Data Flow
Identical three-gate flow to all portals: browser → proxy.ts (locale + session-cookie gate,
trigram → X-Clinic-ID header) → [locale]/portal/clinic-admin/layout.tsx
(requireRoleAccess('CLINIC_ADMIN_ONLY')) → per-section island fetches its entity data
client-side through the /api/v1 BFF seam (per the client-fetch island architecture in
ddx-web/CLAUDE.md), with the backend re-enforcing RBAC + tenant scope authoritatively.
Because clinic-admin touches billing, HR, and accounting endpoints, every one of those routes
must be allowlisted in the clinic-admin role file under
ddx-web/src/lib/api/rbac-permissions/ — an un-allowlisted endpoint 403s silently (empty
section, no api.log entry).
Implicated Code
ddx-web/src/app/[locale]/portal/clinic-admin/layout.tsx— two-linecreateRoleLayoutinvocation; no bespoke shell.ddx-web/src/app/[locale]/portal/clinic-admin/**— ~40 route sections, 191page.tsxfiles.ddx-web/src/lib/portal/role-configs.ts:117—CLINIC_ADMIN_CONFIG(authRequirement: 'CLINIC_ADMIN_ONLY',brandNameKey: 'clinicAdmin.title').ddx-web/src/lib/portal/nav-configs/clinic-admin.ts— the sectioned nav config.ddx-web/src/locales/{en,de,fr}/clinicAdmin.json— theclinicAdminnamespace (one of the 102 namespaces; see i18n — Multi-Lingual Interface).
Operational Notes
- Largest route surface in ddx-web — when auditing or refactoring portal routes, expect clinic-admin to dominate any file count; scope route sweeps per-section, not portal-wide.
- Shared
admin:nav keys — many nav entries reuseadmin:navigation.*keys; a rename in theadminnamespace ripples into the clinic-admin sidebar. Verify both portals when touching those keys. - New section = folder + nav entry + RBAC allowlist — adding a clinic-admin section is
three coordinated edits: the route folder under
clinic-admin/, the nav entry innav-configs/clinic-admin.ts, and the endpoint(s) in the clinic-admin RBAC allowlist. Miss the last and the section renders empty with a silent 403.
Related Topics
- Multi-Portal Architecture — the shared factory/config pattern this portal instantiates.
- Admin Portal — shares administrative vocabulary and several nav keys.
- Accountant Portal — the finance-only cut of what clinic-admin's Billing/Accounting sections expose.
- i18n — Multi-Lingual Interface — the
clinicAdminnamespace and the 102-namespace registry.