Module: Dashboard
The landing page after login. Gives a clinic staffer a one-glance picture of the day.
Purpose
- Answer "what's going on right now?" — upcoming appointments, outstanding invoices, pending reminders.
- Provide quick stats that indicate clinic health.
What the user sees
- Top row — four stat cards:
- Total Patients — count of active pets.
- Appointments This Week — count of
appointments.scheduled_start BETWEEN this_monday AND next_monday. - Pending Revenue —
SUM(balance_due)across non-void invoices. - Completed Visits — count of
appointments.status = 'completed'.
- Upcoming appointments — next 5 rows with patient, client, vet, time, status badge.
- Recent invoices — last 5 invoices with status badges.
- Reminders widget — pending reminders for the next 7 days (sidebar column).
Data sources
| Stat | Source call |
|---|---|
| Total Patients | services.patients.list({ tenantId }) — response meta count |
| Appointments | services.appointments.list({ tenantId }) — filtered client-side |
| Pending Revenue | services.invoices.list({ tenantId }) — summed client-side |
| Completed Visits | services.appointments.list({ tenantId }) — filtered by status == completed |
A backend-side
GET /tenant/{tenant}/dashboardendpoint already exists (Clinic/DashboardController::show) and returns the counts more efficiently. The SPA should migrate to calling that endpoint to avoid loading three lists when only aggregates are shown.
Implementation pointer
ui/src/components/dashboard/Dashboard.js. The top of the component fires three useEffect fetches on mount, normalizes them via lib/normalizers.js, and derives everything else in memoized selectors.
Empty state
When the tenant has no data yet (freshly onboarded clinic), the dashboard renders zeros. A future enhancement: replace zeros with onboarding call-to-action cards ("Add your first pet", "Create your first appointment") that deep-link into the relevant module.