From 83f3ac70d5cfb1ce6a7b46d85d41c4dd4f319510 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 24 Apr 2026 04:32:54 +0200 Subject: [PATCH] =?UTF-8?q?feat(wellvisit):=20port=20the=204=20sub-tabs=20?= =?UTF-8?q?vanilla=20had=20=E2=80=94=20By=20Visit=20/=20Milestones=20/=20S?= =?UTF-8?q?SHADESS=20/=20Note?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Earlier WellVisit was a single-pane skeleton — the comment in that file even admitted "Milestones and SSHADESS sub-tabs land in a follow-up". This commit lands them, faithful to public/components/ wellvisit.html and the three modules behind it (wellVisit.js, milestones.js, shadess.js, all @be14578). WellVisit.tsx is now an 83-line shell that lazy-loads four panels: client/src/pages/wellvisit/ ByVisitAge.tsx — per-visit AAP Bright Futures recommendations (billing codes, measurements, vaccines, sensory/dev/proc/oral screens, growth + feeding, expected reflexes, BMI table, notes). Status buttons mirror vanilla (Given/Refused/Deferred/Already-Done for vaccines; Done/Refused/N-A for screens). Statuses persist to localStorage under ped_visit_statuses (same key as vanilla). Milestones.tsx — checklist per age group, three-state toggle (✓/✗/blank=not assessed), All-Yes/Clear, Generate → /api/generate-milestone-narrative, 3-sentence summary → /api/generate-milestone- summary, Copy-to-Note bridge. Shadess.tsx — 8 SSHADESS domains verbatim from shadess.js (Strengths, School, Home, Activities, Drugs, Emotions/Eating, Sexuality, Safety) with concern_if auto-flag, skip toggle, manual concern toggle, optional Listen-In recorder, Generate → /api/well-visit/shadess. VisitNote.tsx — pre-existing note generator + carry-over pickup from sessionStorage so the user can flow Milestones → SSHADESS → Note without retyping. Now also includes the recorder. Server: GET /api/schedule-data now also returns wellVisitCodes, growthReference, reflexesReference, and bmiClassification so the React side has everything it needs without a second round trip. Tests: shared/clinical/visit-status.ts (+ .test.ts) — pure helpers for the visitId → growth/reflex key mapping (the vanilla tables collapse 6y/7y/8y/9y/10y onto one window, etc.) and the reflex-status color rules. Lives in shared/ so the root vitest config picks it up; ByVisitAge.tsx imports from there. --- client/src/pages/WellVisit.tsx | 221 +++-------- client/src/pages/wellvisit/ByVisitAge.tsx | 462 ++++++++++++++++++++++ client/src/pages/wellvisit/Milestones.tsx | 328 +++++++++++++++ client/src/pages/wellvisit/Shadess.tsx | 363 +++++++++++++++++ client/src/pages/wellvisit/VisitNote.tsx | 242 ++++++++++++ shared/clinical/visit-status.test.ts | 105 +++++ shared/clinical/visit-status.ts | 80 ++++ src/routes/wellVisit.ts | 4 + 8 files changed, 1648 insertions(+), 157 deletions(-) create mode 100644 client/src/pages/wellvisit/ByVisitAge.tsx create mode 100644 client/src/pages/wellvisit/Milestones.tsx create mode 100644 client/src/pages/wellvisit/Shadess.tsx create mode 100644 client/src/pages/wellvisit/VisitNote.tsx create mode 100644 shared/clinical/visit-status.test.ts create mode 100644 shared/clinical/visit-status.ts diff --git a/client/src/pages/WellVisit.tsx b/client/src/pages/WellVisit.tsx index de27974..4d2dfd2 100644 --- a/client/src/pages/WellVisit.tsx +++ b/client/src/pages/WellVisit.tsx @@ -1,176 +1,83 @@ // ============================================================ -// WELL VISIT — /api/well-visit/note (minimum-viable single-pane -// port; the vanilla tab has 4 sub-panes for byvisit / milestones / -// SSHADESS / note — each becomes its own sub-route or tab in a -// follow-up commit). +// WELL VISIT — sub-tab shell. Mirrors public/components/wellvisit.html: +// • By Visit Age — AAP Bright Futures recs per visit +// • Milestones — developmental checklist → AI narrative +// • SSHADESS — psychosocial screening (12+ only) +// • Visit Note — final preventive-care note generator +// +// SSHADESS pill is hidden for under-12 visits to match vanilla. +// Each panel lazy-loads so the initial WellVisit bundle stays small. // ============================================================ -import { useState } from 'react'; -import { useMutation } from '@tanstack/react-query'; -import { api, ApiError } from '@/lib/api'; -import type { VisitNoteOk } from '@/shared/types'; -import EncounterToolbar from '@/components/EncounterToolbar'; -import OutputActions from '@/components/OutputActions'; +import { Suspense, lazy, useState } from 'react'; -const TYPE = 'wellvisit' as const; +const ByVisitAge = lazy(() => import('./wellvisit/ByVisitAge')); +const Milestones = lazy(() => import('./wellvisit/Milestones')); +const Shadess = lazy(() => import('./wellvisit/Shadess')); +const VisitNote = lazy(() => import('./wellvisit/VisitNote')); + +type SubTab = 'byvisit' | 'milestones' | 'shadess' | 'note'; + +const TABS: { id: SubTab; icon: string; label: string }[] = [ + { id: 'byvisit', icon: '👶', label: 'By Visit Age' }, + { id: 'milestones', icon: '🍼', label: 'Milestones' }, + { id: 'shadess', icon: '🧠', label: 'SSHADESS (12+)' }, + { id: 'note', icon: '📄', label: 'Visit Note' }, +]; + +const STORAGE_KEY = 'ped_wellvisit_subtab'; + +function loadSubTab(): SubTab { + try { + const v = localStorage.getItem(STORAGE_KEY); + if (v === 'byvisit' || v === 'milestones' || v === 'shadess' || v === 'note') return v; + } catch { /* ignore */ } + return 'byvisit'; +} export default function WellVisit() { - const [label, setLabel] = useState(''); - const [patientAge, setPatientAge] = useState(''); - const [patientGender, setPatientGender] = useState(''); - const [visitAge, setVisitAge] = useState(''); - const [vitals, setVitals] = useState(''); - const [measurements, setMeasurements] = useState(''); - const [parentConcerns, setParentConcerns] = useState(''); - const [transcript, setTranscript] = useState(''); - const [screenings, setScreenings] = useState(''); - const [vaccines, setVaccines] = useState(''); - const [noteStyle, setNoteStyle] = useState<'full' | 'short'>('full'); - const [result, setResult] = useState(null); + const [active, setActive] = useState(loadSubTab); - const generate = useMutation({ - mutationFn: (body) => api.post('/api/well-visit/note', body), - onSuccess: (data) => setResult(data.note), - }); - - function submit(e: React.FormEvent) { - e.preventDefault(); - setResult(null); - generate.mutate({ - patientAge, patientGender, visitAge, - vitals, measurements, parentConcerns, - transcript, screenings, vaccines, - noteStyle, - }); + function pick(tab: SubTab) { + setActive(tab); + try { localStorage.setItem(STORAGE_KEY, tab); } catch { /* ignore */ } } - const input = 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm'; - return ( -
+
-

Well Visit

+

Well Visit / Preventive Care

- Preventive-care note generation. Milestones and SSHADESS sub-tabs land in a follow-up; this first port covers the Visit Note pane. + AAP 2025 Bright Futures periodicity — vaccines, screenings, billing codes, milestones, SSHADESS, and the encounter note.

- { - setTranscript(enc.transcript || ''); - setResult(enc.generated_note || null); - try { - const pd = enc.partial_data ? JSON.parse(enc.partial_data) : null; - if (pd?.age) setPatientAge(pd.age); - if (pd?.gender) setPatientGender(pd.gender); - if (pd?.visitAge) setVisitAge(pd.visitAge); - if (pd?.vitals) setVitals(pd.vitals); - if (pd?.measurements) setMeasurements(pd.measurements); - if (pd?.parentConcerns) setParentConcerns(pd.parentConcerns); - if (pd?.screenings) setScreenings(pd.screenings); - if (pd?.vaccines) setVaccines(pd.vaccines); - if (pd?.noteStyle) setNoteStyle(pd.noteStyle); - } catch { /* ignore */ } - setLabel(enc.label || ''); - }} - onClear={() => { - setTranscript(''); setResult(null); - setPatientAge(''); setPatientGender(''); setVisitAge(''); - setVitals(''); setMeasurements(''); setParentConcerns(''); - setScreenings(''); setVaccines(''); setNoteStyle('full'); - }} - /> +
+ {TABS.map((t) => ( + + ))} +
-
-
- - - -
- -
-