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.
80 lines
3.8 KiB
TypeScript
80 lines
3.8 KiB
TypeScript
// ============================================================
|
|
// Visit-status helpers — pure functions used by the WellVisit
|
|
// "By Visit Age" pane to map AAP visit IDs onto growth/reflex
|
|
// reference keys, color-code reflex chips, and read tri-state
|
|
// status values out of the localStorage cache.
|
|
//
|
|
// Mirrors public/js/wellVisit.js (@be14578) getGrowthDataForVisit /
|
|
// getReflexDataForVisit / reflexStatusColor. Lives in shared/ so
|
|
// the root vitest config can pick the tests up.
|
|
// ============================================================
|
|
|
|
export type VisitStatusVal = string | { status: string; note: string };
|
|
|
|
// Map a visit ID (e.g. "7y") to its GROWTH_REFERENCE key (e.g. "6y_to_10y").
|
|
// Returns null when no match — caller should hide the growth section.
|
|
export function mapToGrowthKey<T>(visitId: string, refs: Record<string, T>): string | null {
|
|
if (refs[visitId]) return visitId;
|
|
const m: Record<string, string> = {
|
|
'newborn': 'newborn', '3-5d': 'newborn',
|
|
'1mo': '1mo', '2mo': '2mo', '4mo': '4mo', '6mo': '6mo', '9mo': '9mo',
|
|
'12mo': '12mo', '15mo': '15mo', '18mo': '18mo',
|
|
'24mo': '24mo', '30mo': '30mo',
|
|
'3y': '3y', '4y': '4y', '5y': '5y',
|
|
'6y': '6y_to_10y', '7y': '6y_to_10y', '8y': '6y_to_10y', '9y': '6y_to_10y', '10y': '6y_to_10y',
|
|
'11y': '11y_to_14y', '12y': '11y_to_14y', '13y': '11y_to_14y', '14y': '11y_to_14y',
|
|
'15y': '15y_to_21y', '16y': '15y_to_21y', '17y': '15y_to_21y', '18y': '15y_to_21y',
|
|
'19y': '15y_to_21y', '20y': '15y_to_21y', '21y': '15y_to_21y',
|
|
};
|
|
const k = m[visitId];
|
|
return k && refs[k] ? k : null;
|
|
}
|
|
|
|
// REFLEXES_REFERENCE shares 3y..5y, not split by year — see vanilla.
|
|
export function mapToReflexKey<T>(visitId: string, refs: Record<string, T>): string | null {
|
|
if (refs[visitId]) return visitId;
|
|
const m: Record<string, string> = {
|
|
'newborn': 'newborn', '3-5d': 'newborn',
|
|
'1mo': '1mo', '2mo': '2mo', '4mo': '4mo', '6mo': '6mo', '9mo': '9mo',
|
|
'12mo': '12mo', '15mo': '15mo', '18mo': '18mo',
|
|
'24mo': '24mo', '30mo': '30mo',
|
|
'3y': '3y_to_5y', '4y': '3y_to_5y', '5y': '3y_to_5y',
|
|
'6y': '6y_to_10y', '7y': '6y_to_10y', '8y': '6y_to_10y', '9y': '6y_to_10y', '10y': '6y_to_10y',
|
|
'11y': '11y_to_14y', '12y': '11y_to_14y', '13y': '11y_to_14y', '14y': '11y_to_14y',
|
|
'15y': '15y_to_21y', '16y': '15y_to_21y', '17y': '15y_to_21y', '18y': '15y_to_21y',
|
|
'19y': '15y_to_21y', '20y': '15y_to_21y', '21y': '15y_to_21y',
|
|
};
|
|
const k = m[visitId];
|
|
return k && refs[k] ? k : null;
|
|
}
|
|
|
|
// Color a reflex status chip. Greens = expected resolved/lifelong state,
|
|
// blues = present and expected, purple = emerging, ambers = transitional,
|
|
// slate = unspecified. Mirrors reflexStatusColor() exactly.
|
|
export function reflexStatusColor(status: string): string {
|
|
const s = (status || '').toLowerCase();
|
|
if (s.includes('absent / integrated') || s === 'absent' || s.includes('integrated')) return '#059669';
|
|
if (s.includes('present') && s.includes('lifelong')) return '#059669';
|
|
if (s.includes('present')) return '#2563eb';
|
|
if (s.includes('peak')) return '#2563eb';
|
|
if (s.includes('emerging')) return '#7c3aed';
|
|
if (s.includes('fading') || s.includes('transition')) return '#d97706';
|
|
if (s.includes('down-going')) return '#059669';
|
|
if (s.includes('up-going')) return '#d97706';
|
|
if (s.includes('2+')) return '#2563eb';
|
|
return '#475569';
|
|
}
|
|
|
|
// A visit-status value can be a bare string (legacy) or { status, note }.
|
|
export function getStatusValue(s: VisitStatusVal | undefined): string {
|
|
if (!s) return '';
|
|
return typeof s === 'object' ? s.status : s;
|
|
}
|
|
|
|
// SSHADESS sub-tab visibility — vanilla shows it only for these visits.
|
|
export const SSHADESS_VISITS: ReadonlyArray<string> = [
|
|
'12y','13y','14y','15y','16y','17y','18y','19y','20y','21y',
|
|
];
|
|
export function isSshadessVisit(visitId: string): boolean {
|
|
return SSHADESS_VISITS.includes(visitId);
|
|
}
|