Superadmin UI Refactor — Spec
Date: 2026-04-28Scope: Invoice Studio standalone page · Nav consolidation · User status + re-add bugs
1. Invoice Studio — Standalone Full-Page Module
Problem
invoice-studio nav item renders <BillingManagement defaultTab="manual" />, which includes billing stats, a revenue chart, license-request tabs, and the subscription table. The actual invoice creation form is buried as a tab inside that noise.
Solution
Createui/src/components/superadmin/InvoiceStudio.tsx as a fully isolated page.
Layout (Option C — approved):
InvoiceStudio: owns clinic selector state, fetches clinic list + all invoices, passesclinicId/clinicNamedownInvoiceGenerator: receivesclinicId,clinicName,onSuccess— remove its own summary Card and action buttons from its return JSX; expose them via a render-prop or lift them toInvoiceStudio’s sidebar. The simplest approach: add an optionalhideSummaryprop toInvoiceGeneratorand render the summary card insideInvoiceStudio’s sidebar instead, passing totals up viaonTotalsChangecallback.- Invoice history list: extracted from
BillingManagement’s invoices tab — samehandleDownloadPdf/handleInvoiceActionlogic, refactored into a smallInvoiceHistoryListsub-component inside the same file.
SuperAdminDashboard.tsx: changeactiveView === 'invoice-studio'render from<BillingManagement defaultTab="manual" />to<InvoiceStudio />BillingManagement.tsx: remove themanualTabsTrigger and TabsContent entirely
2. Nav Consolidation (Option A — −2 nav entries)
Removals from useNavItems.ts
| Removed entry | Where content moves |
|---|---|
{ id: 'revenue', label: 'Revenue Cycle' } (Overview group) | New “Revenue” tab inside Billing Management |
{ id: 'invitations', label: 'Invitations' } (Management group) | New “Invitations” tab inside User Management |
Revenue Cycle → Billing Management tab
BillingManagement.tsx: add arevenueTabsTrigger and TabsContent that renders<RevenueDashboard />RevenueDashboardis already a standalone component — import and embed, no logic changes neededSuperAdminDashboard.tsx: remove{activeView === 'revenue' && <RevenueDashboard />}
Invitations → User Management tab
- Extract
PlatformInvitationsfunction fromSuperAdminDashboard.tsxinto a new file:ui/src/components/superadmin/PlatformInvitations.tsx(named export) UserManagement.tsx: wrap existing content in a Tabs shell with two tabs:- “Users” — existing user management content (unchanged)
- “Invitations” — renders
<PlatformInvitations />
SuperAdminDashboard.tsx: remove{activeView === 'invitations' && <PlatformInvitations />}render and the local function definition
3. Bug Fixes
Bug A — Active user count hardcoded (ignores isActive flag)
File: ui/src/components/superadmin/UserManagement.tsxLocation: Stats grid “Active” card (~line 671) Before:
getEffectiveUserStatus is already imported in the file. No other changes.
Bug B — “User already accepted an invitation” blocks re-adding a hard-deleted user
Root cause:Users are hard-deleted (
db.delete(users)) but their staffInvitations row (status 'accepted') is never cleaned up. On re-invite the users table check passes (user is gone), but the invitation check throws immediately when it finds the stale 'accepted' row — even though there is no live user.
File: server/src/routes/admin.tsLocation: Invitation existence check (~line 2112–2117) Before:
pending with a new token/expiry when existingInv is truthy — no additional logic needed.
Files Touched
| File | Change |
|---|---|
ui/src/components/superadmin/InvoiceStudio.tsx | New — standalone Invoice Studio page |
ui/src/components/superadmin/PlatformInvitations.tsx | New — extracted from SuperAdminDashboard |
ui/src/components/superadmin/InvoiceGenerator.tsx | Add hideSummary prop + onTotalsChange callback |
ui/src/components/superadmin/BillingManagement.tsx | Remove manual tab · Add revenue tab · Split subscriptions by trial/active |
ui/src/components/superadmin/UserManagement.tsx | Add Invitations tab · Fix active count stat · Amber border on trial clinic headers |
ui/src/components/dashboards/SuperAdminDashboard.tsx | Wire InvoiceStudio · Remove revenue/invitations views |
ui/src/hooks/useNavItems.ts | Remove revenue + invitations nav entries |
server/src/routes/admin.ts | Fix re-add-after-delete invitation check |
4. Trial vs. Live Tenant Separation
Clinics intrial subscription status must be visually and functionally separated from active/paid tenants anywhere clinics are listed or selectable.
Invoice Studio clinic selector
- Group the clinic
<Select>dropdown into two<SelectGroup>sections: “Active Clinics” (subscriptionStatusactiveorpaid) and “Trial / Other” (statustrial,expired,suspended, or null) - Append a
[TRIAL]label after the clinic name for trial entries - When a trial clinic is selected, render a yellow warning banner below the selector:
"This clinic is on a free trial — invoicing is allowed but confirm before issuing."
BillingManagement — Subscriptions tab
- Split the subscriptions table into two visual sections with a divider label: Active Tenants (rows with
subscriptionStatus === 'active') rendered first, then Trial / Inactive below a<Separator>with a muted section label - Active rows keep their current appearance; trial rows get a muted/dimmed style + amber “TRIAL” badge
UserManagement — clinic headers
- The existing
subscriptionStatusbadge on each clinic collapsible header already shows status text; change trial clinic headers to use an amber border accent (border-amber-500/30) so trial clinics stand out at a glance
Out of Scope
- No changes to any other superadmin modules (Analytics, Clinics, System, etc.)
- No DB schema changes
- No API changes except the one-line server bug fix

