pediatric-ai-scribe-v3/client/src/pages/WellVisit.tsx
Daniel 83f3ac70d5 feat(wellvisit): port the 4 sub-tabs vanilla had — By Visit / Milestones / SSHADESS / Note
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.
2026-04-24 05:24:09 +02:00

83 lines
3.1 KiB
TypeScript

// ============================================================
// 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 { Suspense, lazy, useState } from 'react';
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 [active, setActive] = useState<SubTab>(loadSubTab);
function pick(tab: SubTab) {
setActive(tab);
try { localStorage.setItem(STORAGE_KEY, tab); } catch { /* ignore */ }
}
return (
<div className="max-w-5xl mx-auto p-6 space-y-4">
<header>
<h1 className="text-2xl font-semibold">Well Visit / Preventive Care</h1>
<p className="text-sm text-muted-foreground">
AAP 2025 Bright Futures periodicity vaccines, screenings, billing codes, milestones, SSHADESS, and the encounter note.
</p>
</header>
<div className="flex flex-wrap gap-2" data-testid="wellvisit-subnav">
{TABS.map((t) => (
<button
key={t.id}
type="button"
onClick={() => pick(t.id)}
className={
'px-3 py-1.5 rounded-full text-xs font-medium border transition-colors ' +
(active === t.id
? 'bg-primary text-primary-foreground border-primary'
: 'bg-muted hover:bg-muted/80 border-border')
}
data-testid={'wellvisit-pill-' + t.id}
>
<span className="mr-1" aria-hidden>{t.icon}</span>
{t.label}
</button>
))}
</div>
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading</div>}>
{active === 'byvisit' && <ByVisitAge />}
{active === 'milestones' && <Milestones />}
{active === 'shadess' && <Shadess />}
{active === 'note' && <VisitNote />}
</Suspense>
</div>
);
}