Owner Dashboard Redesign Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.
Goal: Redesign the clinic owner/admin dashboard (AdminOverview) into a dental-specific, owner-focused, fast-loading view backed by one extended, KV-cached summary endpoint.
Architecture: Extend the existing GET /api/v1/protected/stats/admin additively (new top-level keys, same 300s KV cache, same clinic scoping, same PKT dates). Isolate all non-trivial math into a pure, unit-tested server/src/lib/dashboard-metrics.ts module. Rewrite the UI to consume the extended payload via TanStack Query, render KPI cards + CSS bars instantly, and lazy-load the single Recharts trend below the fold.
Tech Stack: Hono + Drizzle + Neon (server), React + Vite + Tailwind v4 + TanStack Query v5 + Recharts (ui), Vitest (tests). Design tokens per docs/design.md (indigo --primary accent; Banknote not DollarSign; per-icon lucide imports).
Spec: docs/superpowers/specs/2026-05-31-owner-dashboard-redesign-design.md
Conventions reminder:
- Do NOT run anything against live tenants/prod DB without per-action confirmation (read-only included).
- Commit only when the user asks (use the odontox-commit-deploy skill). The
git commitsteps below are the logical checkpoints; batch or defer per the user’s commit policy. - Money:
Banknoteicon,formatCurrency/formatCompactCurrencyfrom@/lib/currency.
File Structure
Server- Create
server/src/lib/dashboard-metrics.ts— pure functions (no DB/IO): time math, flow counts, chair utilization, acceptance rate, revenue bucketing, provider assembly. - Create
server/src/lib/dashboard-metrics.test.ts— vitest unit tests. - Modify
server/src/routes/stats.ts— inside the existingstats.get('/admin', …)handler, add the new queries + assemble new response keys via the helpers.
- Modify
ui/src/lib/serverComm.ts— extend theAdminStatsinterface with the new optional fields. - Modify
ui/src/lib/queryKeys.ts— addadminDashboardkey. - Create
ui/src/components/dashboard-widgets/dental/CssBar.tsx— shared CSS bar primitives + a smallStatChip. - Create
ui/src/components/dashboard-widgets/dental/TodayFlowCard.tsx - Create
ui/src/components/dashboard-widgets/dental/CollectionsCard.tsx - Create
ui/src/components/dashboard-widgets/dental/ChairUtilizationCard.tsx - Create
ui/src/components/dashboard-widgets/dental/NoShowCard.tsx - Create
ui/src/components/dashboard-widgets/dental/AcceptanceCard.tsx - Create
ui/src/components/dashboard-widgets/dental/TodayScheduleCard.tsx - Create
ui/src/components/dashboard-widgets/dental/RevenueByTreatmentCard.tsx - Create
ui/src/components/dashboard-widgets/dental/WeeklyLoadCard.tsx - Create
ui/src/components/dashboard-widgets/dental/ActionRequiredCard.tsx - Create
ui/src/components/dashboard-widgets/dental/ProviderPerformanceCard.tsx - Create
ui/src/components/dashboard-widgets/dental/OwnerQuickActions.tsx - Create
ui/src/components/dashboard-widgets/dental/RevenueTrendChart.tsx— lazy Recharts area (extracted so it can be code-split). - Modify
ui/src/components/admin/AdminOverview.tsx— compose the new layout; switch touseQuery.
Task 1: Pure metrics helpers (TDD)
Files:-
Create:
server/src/lib/dashboard-metrics.ts -
Test:
server/src/lib/dashboard-metrics.test.ts - Step 1: Write the failing tests
- Step 2: Run tests to verify they fail
cd server && npx vitest run src/lib/dashboard-metrics.test.ts
Expected: FAIL — cannot resolve ./dashboard-metrics.
- Step 3: Implement the helpers
- Step 4: Run tests to verify they pass
cd server && npx vitest run src/lib/dashboard-metrics.test.ts
Expected: PASS (all suites green).
- Step 5: Commit (only if user’s commit policy allows now)
Task 2: Extend the /stats/admin endpoint
Files:
- Modify:
server/src/routes/stats.ts— thestats.get('/admin', …)handler (~lines 588–1103). Append new queries after the existing ones and add new keys toresponseData(before thekv.set+c.json).
db = getReadDb(), targetClinicId, todayStr (PKT today), monthStart, monthStartStr, today, weekAgoStr. Imports already include eq, and, gte, lte, sql, count, sum, desc, inArray, isNull and schema.
- Step 1: Add imports for new tables + helpers at the top of
stats.ts
schema.* (they are, schema is the barrel). Add the helper import near the other lib imports:
- Step 2: Inside the
/adminhandler, aftertodayAppointmentsis built (~line 1016) and before “Calculate trends”, add the dental queries
NOTE: replace theacceptancePlansquery’s date bound with the real 90-day window (it is written with the 7-day var above only to keep the array shape obvious during editing). Use:DefineninetyDaysAgoabove thePromise.alland use it in that one query.
- Step 3: Build the derived structures (after the
Promise.all)
- Step 4: Build today’s schedule with payment status + provider performance
- Step 5: Add the new keys to
responseData
const responseData = { … } object (before kv.set) with:
- Step 6: Add the
unacceptedPlansquery (proposed plans older than 7 days)
Promise.all array and wire into actionItems.unacceptedPlans:
unacceptedPlansRow and set unacceptedPlans: parseInt(unacceptedPlansRow[0]?.count?.toString() || '0').
- Step 7: Typecheck the server
cd server && npx tsc --noEmit
Expected: PASS (0 errors). Fix any column-name mismatches against the schema files (e.g., schema.inventoryItems.reorderPoint, schema.inventoryItems.minStock, schema.users.clinicId). If schema.users.clinicId is not the right column for clinic membership, use schema.users.primaryClinicId — verify in server/src/schema/users.ts.
- Step 8: Commit
Task 3: Extend the AdminStats TypeScript interface (UI)
Files:
-
Modify:
ui/src/lib/serverComm.ts:2944-2980(theAdminStatsinterface). -
Step 1: Add the new optional fields to
AdminStats(optional so partial/old cached payloads don’t break types):
- Step 2: Typecheck
cd ui && npx tsc --noEmit
Expected: PASS.
- Step 3: Commit
Task 4: Query key + shared CSS bar primitives
Files:-
Modify:
ui/src/lib/queryKeys.ts— add an admin-dashboard key under the existingqkfactory, clinic-scoped (follow the file’sclinicScope()pattern; read 3–4 neighbouring keys first to match the exact shape). -
Create:
ui/src/components/dashboard-widgets/dental/CssBar.tsx - Step 1: Add the query key
ui/src/lib/queryKeys.ts, clinicScope() takes no arguments (it reads the active clinic from queryClient) and returns a string. Keys are ['resource', clinicScope(), ...sub]. Add this nested key to the qk object, mirroring inventory/receipts:
- Step 2: Create the CSS bar primitives
- Step 3: Typecheck
cd ui && npx tsc --noEmit
Expected: PASS.
- Step 4: Commit
Tasks 5–14: Dental presentational components
These are independent new files consuming typed slices ofCommon import header for each component:AdminStats. They can be built in parallel. Each imports primitives from../../ui/card,../../ui/badge,@/lib/utils,@/lib/currency, and per-iconlucide-react. None fetch data. Each handles its empty/null state. After all are written, runcd ui && npx tsc --noEmit.
Task 5: TodayFlowCard (KPI #1)
File: Create ui/src/components/dashboard-widgets/dental/TodayFlowCard.tsx
Task 6: CollectionsCard (KPI #2)
File: Create ui/src/components/dashboard-widgets/dental/CollectionsCard.tsx
Task 7: ChairUtilizationCard (KPI #3, with progress bar + setup empty state)
File: Create ui/src/components/dashboard-widgets/dental/ChairUtilizationCard.tsx
Task 8: NoShowCard (KPI #4)
File: Create ui/src/components/dashboard-widgets/dental/NoShowCard.tsx
Task 9: AcceptanceCard (KPI #5)
File: Create ui/src/components/dashboard-widgets/dental/AcceptanceCard.tsx
Task 10: TodayScheduleCard
File: Create ui/src/components/dashboard-widgets/dental/TodayScheduleCard.tsx
Task 11: RevenueByTreatmentCard
File: Create ui/src/components/dashboard-widgets/dental/RevenueByTreatmentCard.tsx
Task 12: WeeklyLoadCard
File: Create ui/src/components/dashboard-widgets/dental/WeeklyLoadCard.tsx
Task 13: ActionRequiredCard
File: Create ui/src/components/dashboard-widgets/dental/ActionRequiredCard.tsx
Task 14: ProviderPerformanceCard
File: Create ui/src/components/dashboard-widgets/dental/ProviderPerformanceCard.tsx
cd ui && npx tsc --noEmit → PASS. Commit:
Task 15: Owner quick actions + lazy revenue trend chart
Files:-
Create
ui/src/components/dashboard-widgets/dental/OwnerQuickActions.tsx -
Create
ui/src/components/dashboard-widgets/dental/RevenueTrendChart.tsx - Step 1: Quick actions
onBook; included in props for completeness — wire it in AdminOverview’s headerAction.)
- Step 2: Lazy revenue trend chart (extracts the existing AreaChart so Recharts is code-split)
- Step 3:
cd ui && npx tsc --noEmit→ PASS. Commit:
Task 16: Rewrite AdminOverview to compose the new dashboard
Files:
-
Modify:
ui/src/components/admin/AdminOverview.tsx(full rewrite of the render; keepInvitePatientModal,PendingBookingsCard,PendingAppointments, AIRequireModuleblock, andRevenueRecoveryCard). -
Step 1: Replace the component body with a
useQuery-driven version composing the new cards.
Note: the AI section header is labelled “Ruby Insights” (memory rule: AI surfaces are branded “Ruby”, never “AI”/“Action Items”). Keep OdontoXAIIcon (the Ruby logo) — confirm it is the Ruby mark; if a dedicated Ruby icon exists, use it.
-
Step 2: Verify
formatCurrencyimport path — AdminOverview previously imported from@/lib/currency. ConfirmformatCurrency/formatCompactCurrencyare exported there (they are, per the original imports). If adecimalsoption isn’t supported, fall back toformatCompactCurrencyorformatCurrency(n). - Step 3: Typecheck + build
cd ui && npx tsc --noEmit && npm run build
Expected: tsc 0 errors; vite build succeeds. Confirm Recharts is in a separate chunk (RevenueTrendChart is lazy) — check the build output chunk list.
- Step 4: Commit
Task 17: Full verification
- Step 1: Server unit tests
cd server && npx vitest run src/lib/dashboard-metrics.test.ts
Expected: PASS.
- Step 2: Server typecheck
cd server && npx tsc --noEmit
Expected: 0 errors.
- Step 3: UI typecheck + build
cd ui && npx tsc --noEmit && npm run build
Expected: 0 errors; build succeeds.
- Step 4: Endpoint smoke (NON-LIVE / local or with explicit user OK)
cd server && npm run dev), call GET /api/v1/protected/stats/admin with a dev token and assert the new keys (today, collections, operations, schedule, revenueByTreatment, weeklyAppointments, actionItems, providers) are present and well-typed. Otherwise, present the diff and ask before any live verification against ssh & Associates (clinic b6d3a3f3-…).
- Step 5: Visual sanity
cd ui && npm run dev), open /dashboard as an admin. Confirm: shell + skeletons paint instantly; KPI row shows 5 cards; empty states appear for zero-data sections; no empty charts; Recharts trend loads below the fold; no console 403 spam.
Self-Review (spec coverage)
- Today’s Appointments / flow → Task 5 + endpoint
today. ✔ - Today’s Collections + pending → Task 6 +
collections. ✔ - Chair Utilization (chairs×hours) → Task 7 +
computeChairUtilization. ✔ - No-show Rate → Task 8. ✔
- Treatment Acceptance (90d) → Task 9 +
computeAcceptanceRate. ✔ - Today’s Schedule (time/patient/treatment/dentist/status/payment) → Task 10 +
schedule. ✔ - Revenue by Treatment (clinic categories, billed, empty state) → Task 11 +
bucketRevenueByTreatment. ✔ - Weekly Load (booked/capacity/cancelled/no-show) → Task 12 +
weeklyAppointments. ✔ - Action Required (recalls/plans/payments/lab/missed/inventory) → Task 13 +
actionItems. ✔ - Provider Performance → Task 14 +
providers. ✔ - Quick actions (Book primary; Add Patient/Record Payment/Treatment Plan/Send Recall) → Task 15/16. ✔
- New Patients + Recall Due chips → Task 16 secondary band. ✔
- Single cached endpoint, lazy chart, CSS bars, per-icon imports, Banknote, indigo accent → Tasks 2/4/11/12/15/16. ✔
- Owner-only scope; no schema change; Ruby branding for AI → Task 16 + endpoint. ✔

