Dashboard drill-through and per-widget RBAC
Outcome
Every dashboard widget click lands on the right pre-filtered list. Restricted widgets show a clear "locked" placeholder instead of disappearing for users without permission.
Prerequisites
reports.read.{financial|operational|clinical-auth}scopes assigned to roles per intent.- Familiarity with the tenant RBAC matrix.
What this fixed
Two long-standing inconsistencies surfaced as RM-15 widgets accumulated session-by-session:
| Problem | Fix |
|---|---|
Drill targets were each widget's responsibility, with duplicated monthToDateRange helpers and one widget (ArAgingChart) sending every click to a generic /receivables URL that ignored the bucket. | Centralized helpers in apps/rcm-app/src/components/dashboard/drillthrough.ts. |
RBAC gating hid whole rows via {canSeeFinancial && <Row/>}, so an operational viewer saw a collapsed dashboard with no signal that financial widgets exist behind a permission wall. KPI cards were gated as a single block on reports.read.financial, even the three operational KPIs. | <WidgetGate> wrapper + split KPI gates. |
<WidgetGate> wrapper
Replaces the inline {canSee* && …} pattern. When a viewer lacks the
required scope, the widget slot renders a dashed-border placeholder with
a lock icon, the widget title, and a hint listing the required
permission (e.g. reports.read.financial), instead of disappearing
entirely.
KPI gates
| Scope | KPIs |
|---|---|
reports.read.financial | Total Submitted, Total AR Balance, Avg Days in AR |
reports.read.operational | Claims Pending, Denial Rate, Clean Claim Rate |
Drill mappings
| Widget click | Drill target |
|---|---|
| AR Aging bucket | /claims?agingBucket=…&serviceFrom=…&serviceTo=…&openOnly=1 |
| Denial Category bar | /denials?tab=denials&denialCategory=… |
| Denial Trend dot | /denials?tab=denials&denialCategory=…&dateFrom=…&dateTo=… |
| Revenue month bar | /claims?tab=all&serviceFrom=…&serviceTo=… |
| Claim Funnel stage | /claims?tab=all&status=… |
| Claims Action Queue row | /claims?tab=all&status=<row.status> |
| Auth Alerts → "View all" | /authorizations?status=APPROVED |
| Auto-Correction → "View analytics" | /denials/analytics |
| Timely Filing → "View All Alerts" | /claims?sort=filingDeadlineDate&order=asc |
Operator playbook — assigning roles
Out-of-the-box scope bundles (tenant migration 101):
| Role | financial | operational | clinical-auth |
|---|---|---|---|
OPERATIONS_ANALYST | ✅ | ✅ | ✅ |
EMR_CLIENT_VIEWER | — | ✅ | ✅ |
CONFIG_MANAGER | — | — | — |
If a tenant needs a custom mix (e.g. CFO who only sees financial),
provision a new role in /admin/rbac (see
RBAC matrix editor)
and grant the desired subset of reports.read.{financial,operational,clinical-auth}.
Verifying RBAC visibility
Sign in as
EMR_CLIENT_VIEWER(or any user with noreports.read.financial)./dashboardshould render every operational + clinical-auth widget interactively, while financial slots show the locked placeholder ("You don't have permission to view this widget. Required: reports.read.financial.") in the same layout position — no row collapse.Sign in as
OPERATIONS_ANALYSTand confirm every drill target above lands on the expected pre-filtered list.
Break-glass
This is purely client-side. If a widget's drill target is wrong, edit
the helper in drillthrough.ts and redeploy the rcm-app bundle — no DB
or backend route touch is required.
Validation
| Check | Expected |
|---|---|
| Widget click | Lands on the correct filtered list |
| Locked placeholder | Shows for missing scopes (no row collapse) |
| Operational KPIs visible to operational-only role | Yes |
Cross-references
- RBAC matrix editor for role assignment.
- Refresh reporting views — feeds the numbers.