Mobile Doctor Dashboard¶
Overview¶
The mobile doctor home was redesigned in May 2026 around three ideas: see every patient at a glance, prep for the patients you're seeing today, and drill into a single patient's signals without leaving the dashboard. The new dashboard surfaces compliance grades on every patient row, lets the therapist mark patients as "seeing today," exposes a dedicated patient detail screen with a Quick Analysis view, and adds a one-tap re-onboarding flow for patients who never finished signing up.
Compliance grades and per-patient activity stats come from the same backend service that powers the admin Daily Review tool — see internal/daily-review.md for the grading rubric and payload shape.
Dashboard Layout¶
components/doctor/DoctorDashboard.tsx is one list under two pills, Today's patients and Patients (the first pill and everything about today's list follow the todaysPatients tenant flag, on unless the clinic set it to false; with it off the screen is the directory alone, without pills, and the Scheduling cards move above it; app builds from before 2026-10-29 ignore the flag):
- Today's patients — patients the therapist has added to today's caseload via
useInClinicPatients(). Each row shows a small indicator dot and a dismiss button. While someone is on the list, a search field above it filters today's patients as you type (since 2026-10-06); when nobody matches, the list says so ("Nobody on today's list matches…") and offers Search all patients, which opens the directory search with the same words. Switching pills clears it. - Patients — the directory: the therapist's own patients or, with the
allActivePatientsDashboardflag, a scope chooser at the top of the view that switches between My patients and All clinic patients, with the count beside it ({n} patients).
The header also holds a search icon that opens PatientSearchModal (see Patient search), an "Add patient seeing today" button that opens TodayPickerModal, and an "Onboard new patient" button that opens OnboardPatientModal. (The greeting-and-date line and the scope toggle in the header of the first design are gone: the scope lives inside Patients.)
Skeleton rows (PatientRowSkeleton) appear while usePatientsSummary is loading.
The dashboard opens on Today's patients only when someone is on today's list; once that list finishes its first load and it's empty, it opens on Patients instead. A tap on either pill wins from then on.
Sample patient (internal/sample-patient.md): "My patients" asks for the doctor's sample with includeSample=1. While the sample is the doctor's only patient, a card at the top of the Patients view offers "Explore Alex, your sample patient" and "Onboard your first patient" in place of the empty state. The sample row carries a Sample badge and always sorts below real patients.
Patient Row¶
components/doctor/PatientRow.tsx is the workhorse cell. Each row shows:
- Avatar with initials
- Patient name + case (most recent injury / operation, or "No case")
- A "Not onboarded" pill when the patient hasn't completed onboarding
- A compliance grade chip (
ComplianceGradecomponent, see below) - A last-visit chip with relative time ("Today", "Yesterday", "3 days ago", "2 weeks ago", …) and a stale indicator after 14 days
- Flag chips for elevated pain/exertion, long inactivity, excessive exercises, and never-started states (
FlagChipcomponent) - A small "today" dot on the left rail when the patient is in today's list, plus a dismiss button on the right
Compliance Grade Chip¶
components/doctor/ComplianceGrade.tsx renders a single letter (A+, A, B, C, D, F, or a dash for NONE) with palette-tuned background and text colors:
| Grade | Color family |
|---|---|
| A+, A | Green |
| B | Lime |
| C | Amber |
| D | Orange |
| F | Red |
| NONE | Gray |
Sizes (sm, md, lg) and variants (chip background vs bare letter) let the same component fit in row cells, header summaries, and the full-bleed Quick Analysis card.
Seeing Today¶
Since 2026-10-29 the whole of this follows the todaysPatients tenant flag (default on): with it set to false, the dashboard shows no pills and no today list, the patient screen has no Seeing today chip and no add/remove action, and useInClinicPatients({ enabled: false }) keeps the list empty without reading the server. The server keeps the list; turning the flag back on shows it again.
useInClinicPatients() manages a rolling per-doctor list of "patients I'm seeing today" through the backend at /v2/doctor/in-clinic-patients. The backend stores the active list by tenant, therapist, and patient, and writes an audit event whenever a patient is added or removed. The hook still keeps a per-user AsyncStorage cache for instant display/offline fallback and migrates the legacy local-only list once when the backend list is empty.
TodayPickerModal shows the doctor's full caseload with checkboxes; the modal preloads the full patient list via usePatientsSummary().loadPickerPatients() so it can list patients outside the current scope. Adding a patient surfaces a success toast and the row immediately appears in the Today's patients section at the top of the dashboard.
Dismissing a patient from today's list is a one-tap action on the row (no confirmation) — it only removes them from the today list, not from the caseload.
When the doctor has no patients at all, the picker says so and offers Onboard New Patient (which closes the picker and opens OnboardPatientModal), instead of the "All your patients are already added" message.
Patient search¶
PatientSearchModal (opened from the header's search icon, or from the Today tab's Search all patients) searches with one matcher, lib/patientSearch.ts, used by every patient search box in the doctor's app, the Today picker and the Today tab's field included: every word of the query has to appear in the name, the email or the case, in any order and in any case ("lee anna" finds Anna Lee; ё reads as е, so "Семенов" finds "Семёнов"), and a query of three or more digits, written with spaces, brackets, dashes, dots or a plus, also matches the phone number.
It searches the current scope's patients first. Since 2026-10-06 it also looks further: usePatientsSummary().loadSearchPools() fetches the clinic list and the archived list when the search opens ("Checking the rest of the clinic…" while they load; the archive is read again after a pull-to-refresh or a scope change), and the modal lists:
- matches among other therapists' patients under Not your patients ("Other therapists' patients in your clinic"), only with clinic-wide access (
allActivePatientsDashboard;otherPatientsis empty otherwise); - matches among archived patients (
GET /v2/doctor/patients/archived,doctorPatientsService.getArchivedPatients()) under Archived ("Tap a patient to restore them"). Tapping one asks, over the open search, to restore them ("{name} will come back to your patients with their records." → Restore and open patient; Cancel leaves the search as it was), restores them and opens their screen. A patient the clinic list marks as the doctor's own (isConnectedToMe) never shows under Not your patients, even while the doctor's own list is still loading, and opening the search again while it is still looking joins that look instead of starting another.
Patient Detail Screen¶
app/(doctor)/patient/[id].tsx is the per-patient hub, reachable by tapping any row. It shows:
- Name, age, and case
- Care team — the connected therapists, with
(you)next to the viewer - An "Assign to me" CTA when the viewer is browsing a patient outside their caseload (only with
therapistPatientAssignmentenabled) - A "Patient hasn't onboarded yet" banner with a Re-onboard patient button when applicable
- For a patient who already set up their account, a Send sign-in link quick action (see Sign-in link)
- Stat tiles: last visit, next session, completion %
- Action grid (filtered by feature flags):
- Start test — clinical movement test recording
- Record exercise video — gated by
therapistPersonalizedExerciseVideo - Manage HEP — gated by
therapistMobileHepManagement(see mobile-doctor-hep-management.md) - View videos — opens the personalized video library for this patient (see personalized-exercise-videos.md)
- A Quick Analysis link that opens
patient-analysis/[id].tsx - Two list sections:
- Movement tests — recent clinical assessments (
PatientTestRow), with a "See all tests" link - Personalized videos — recent personalized videos
- Movement tests — recent clinical assessments (
- For a sample patient: a banner ("made-up data, only visible to you") with Reset sample data and Remove sample; the care team, re-onboard, personalized-video and Screening Lab entries are hidden. Opening Quick Analysis, saving the HEP and replying in the sample thread check off the sample checklist.
- A pair of Section Info modals (
SectionInfoModal) that explain what movement tests and personalized videos are, opened from each section header — useful for therapists trying these features for the first time
Clinician RTM time¶
When the clinic uses RTM billing (rtmBillingDashboard), a real patient's working screens add the clinician's active time to that patient's monthly RTM total, silently, as on the web: there is no timer on screen and nothing to start or stop. The patient screen, Quick Analysis, session history and review (including notes), HEP editing, assessment results/report and conversations with exactly one active patient count; one screen counts at a time, so opening a related screen never doubles time. Recording pauses in the background and 30 seconds after the last tap, scroll or typing, and starts again on the next input.
General patient lists, live visits, camera capture and sample patients never accrue time. Unsent time waits on the device (for up to 7 days) and is sent under the same clinician. Totals, review and approval are in the web billing dashboard. See RTM billing and the implementation audit.
Quick Analysis Screen¶
app/(doctor)/patient-analysis/[id].tsx calls a doctor-scoped endpoint that returns the same DailyReviewPatient payload shape as the admin daily review (one patient). It renders:
- Compliance grade (lg variant) with the reason string
- Flags section — chips for any active flags (long inactivity, elevated pain count, elevated exertion count, excessive exercises, has personalized videos, never started). Pain and exertion counts are derived from the last completed session
- Notes section — the
suggestedNotes[]array from the service, rendered as a checklist - Next up — upcoming session exercises with planned sets/reps/duration. Exercises with a personalized video are marked
- Last completed — last completed session exercises with elevated pain/exertion highlighted
- Footer stats: streak, sessions in last 7 days, sessions in last 14 days
- A Manage HEP quick action and a View all completed sessions link to
patient-sessions/[id].tsx
All Sessions Screen¶
app/(doctor)/patient-sessions/[id].tsx paginates through the patient's completed workouts, each session expandable to show its exercise list, with elevated pain/exertion exposed inline. The same SessionPreviewCard is used here and on the Quick Analysis "next up" / "last completed" cards.
Patient Re-onboarding¶
When a patient has been invited but never completed onboarding, the patient detail screen shows a Re-onboard patient CTA. Tapping it opens ReonboardPatientModal (components/doctor/ReonboardPatientModal.tsx):
- Loading — the modal asks
patientInviteService.generateInviteLink(patientId)for a fresh magic-link URL. - DOB required — if the patient has no DOB on file the backend returns
requiresDob: true; the modal collects DOB viaDOBFieldInputand retries. - Ready — shows the magic link with three actions:
- Send invite email — emails the patient (
patientInviteService.sendInviteEmail) when they have an email on file - Copy link — copies the URL to the clipboard
- QR code — a
react-native-qrcode-svgblock under the link for in-clinic scanning - The modal also exposes Generate a new link which calls the same endpoint with a regeneration flag, invalidating any prior token for the patient.
This flow reuses the same /v2/user/generate-invite-link/:patientId and /v2/user/send-invite-email/:patientId endpoints used by web — see internal/patient-onboarding.md for the full token lifecycle.
Sign-in link¶
Once the patient has set up their account the re-onboard button is gone, and a patient who can't get back in needs something else. The patient screen's quick actions then include Send sign-in link (for an active patient with an email; never the sample patient). It opens SendSignInLinkSheet (components/doctor/SendSignInLinkSheet.tsx): the patient's email, what the link does, and Send link. The sheet says when the email is out, and explains a refusal: a patient who never finished the invite ("send the invite again instead"), a missing email, a switched-off account, a patient the therapist can no longer open, or too many links in an hour. Two quick taps send one email, and the sheet stays open while the email is on its way.
The email takes the patient to the web page behind the link, which shows their email, takes their password, or lets them set a new one right there and signs them in. Same endpoint as the web dashboard's action: POST /v2/user/send-sign-in-link/:patientId (patientInviteService.sendSignInLink); the token lifecycle is in internal/patient-onboarding.md.
Translations¶
~140 new keys were added under these namespaces in Okta-Mobile/locales/{en,ru}.json:
| Namespace | Coverage |
|---|---|
doctorHome |
Header, scope chooser, today/all sections, the Today search, the search's section headings, search hints, empty states |
patientDetail |
Patient hub strings, action grid labels, care team, re-onboard modal copy, section info modals |
quickAnalysis |
Flags, notes, next-up / last-completed labels, relative counts |
inClinic |
Today picker buttons, dismiss confirmations |
personalizedVideo |
Library picker additions (chooseFromLibrary, videoTooLongMessage, …) |
All strings are present in both English and Russian.
Backend Tie-in¶
The doctor controller (OktaPT-API/src/routes/doctor/controller.ts) imports gradeFromActivity and getPatientSummaryStats from src/services/dailyReviewService.ts so the getMyPatients and getAllTenantPatients endpoints return:
- Compliance grade and reason
- Last completed date
- Next upcoming session date
- Completed and incomplete workout counts
- Care team (when therapist assignment is enabled)
usePatientsSummary consumes both endpoints with separate caches, and the patient detail screen calls a single-patient summary endpoint to refresh stats when returning to the screen. Since 2026-10-06 every /v2/doctor route requires the DOCTOR role (requireDoctor), as the archived list already did (see therapist-patient-assignment.md).
Key Files¶
| File | Role |
|---|---|
components/doctor/DoctorDashboard.tsx |
Top-level dashboard list, scope toggle, modals |
components/doctor/PatientRow.tsx |
Patient row cell |
components/doctor/PatientRowSkeleton.tsx |
Loading state |
components/doctor/ComplianceGrade.tsx |
Grade chip / letter |
components/doctor/FlagChip.tsx |
Status flag chip |
components/doctor/PatientSearchModal.tsx |
Patient search: the current scope, other therapists' patients (clinic-wide access), archived patients |
lib/patientSearch.ts |
The one matcher for every patient search box (name in any order, email, case, phone digits) |
components/doctor/TodayPickerModal.tsx |
"Seeing today" picker |
components/doctor/ReonboardPatientModal.tsx |
Re-onboarding flow |
components/doctor/SendSignInLinkSheet.tsx |
Sign-in link for a patient who already set up their account |
components/doctor/SectionInfoModal.tsx |
Explanatory popovers |
components/doctor/SessionPreviewCard.tsx |
Shared session/exercise card |
components/doctor/PatientTestRow.tsx |
Clinical assessment row |
app/(doctor)/patient/[id].tsx |
Patient detail hub |
app/(doctor)/patient-analysis/[id].tsx |
Quick Analysis |
app/(doctor)/patient-sessions/[id].tsx |
All completed sessions |
hooks/usePatientsSummary.ts |
Caseload loader with scope + caching, plus the search pools (other therapists' and archived patients) |
hooks/useInClinicPatients.ts |
Backend-backed today's-list hook with AsyncStorage cache |
services/doctorPatientsService.ts |
getMyPatients, getAllTenantPatients, getArchivedPatients, getPatientSummary |
services/patientAnalysisService.ts |
Quick Analysis payload |
services/patientInviteService.ts |
Re-onboarding endpoints |