First Bedside commit. Delivers the /app/bedside page with the full 15-pill sub-nav in the same order + labels as the vanilla app, and switches the sidebar Bedside link to available. client/src/pages/Bedside.tsx Single-file shell. PILLS array is the single source of truth for pill ID / label / icon / summary, ordered to match public/components/bedside.html (neonatal, airway, cardiac, respiratory, ventilation, seizure, sepsis, anaphylaxis, sedation, agitation, antiemetics, antimicrobials, burns, toxicology, trauma). Clicking a pill flips useState<active>, and LegacyPanel renders a summary of that module + a button to open the legacy Bedside tab. What is intentionally NOT in this commit Each pill's actual clinical dosing panel stays in vanilla for now. Those panels encode weight-based dosing, syndrome-keyed antimicrobials, and PALS / ALS formulas — exactly the class of content the migration checkpoint memory flags as must-not-be- "simplified" by an LLM. They belong in per-module commits that land alongside the calculators port (APLS + Best Guess weight, Fenton 2013 LMS, AAP 2022 bilirubin, Rosner BP splines) where test vectors can verify byte-for-byte parity with the vanilla output. The top-level age → weight estimator also waits on calculators — it calls window._PED_MATH.estimateWeightFromAgeMonths in the vanilla module, which is defined in public/js/calculators.js. e2e/tests/bedside-react.spec.js — three smoke tests All 15 pills render by data-testid, clicking a pill swaps the panel, and the legacy-viewer link is present. The pill-order list is hard-coded in the spec so re-ordering or dropping a pill trips a loud failure. Client tsc -b + vite build clean. Bundle 456.71 kB / 130.53 kB gz.
109 lines
5.7 KiB
TypeScript
109 lines
5.7 KiB
TypeScript
// ============================================================
|
|
// BEDSIDE — emergency + rapid-reference pediatric tools. This first
|
|
// port ships the sub-nav shell at /app/bedside so the sidebar links
|
|
// light up and users land on a page that mirrors the legacy layout.
|
|
//
|
|
// The 15 clinical sub-modules (neonatal, airway, cardiac, respiratory,
|
|
// ventilation, seizures, sepsis, anaphylaxis, sedation, agitation,
|
|
// antiemetics, antimicrobials, burns, toxicology, trauma) stay in the
|
|
// vanilla viewer for now. Each one carries weight-based dosing +
|
|
// clinical decision content the migration checkpoint explicitly
|
|
// flagged as must-not-be-"simplified" by an LLM — they belong in
|
|
// dedicated per-module commits alongside the calculators port (Rosner
|
|
// BP splines, Fenton LMS, AAP 2022 bilirubin, APLS weights) where
|
|
// test vectors can verify byte-for-byte parity.
|
|
//
|
|
// The top-level age → weight estimator (APLS / Best Guess formulas)
|
|
// also depends on public/js/calculators.js which has not been ported
|
|
// yet. It will appear at the top of this page when calculators port.
|
|
// ============================================================
|
|
|
|
import { useState } from 'react';
|
|
|
|
const card = 'rounded-lg border border-border bg-card p-5 space-y-3';
|
|
const btnPrimary = 'rounded-md bg-primary text-primary-foreground px-4 py-2 text-sm font-medium';
|
|
|
|
interface Pill {
|
|
id: string;
|
|
label: string;
|
|
icon?: string; // emoji stand-in; font-awesome lives in the legacy shell
|
|
summary: string;
|
|
}
|
|
|
|
// Order and labels match public/components/bedside.html exactly.
|
|
const PILLS: Pill[] = [
|
|
{ id: 'neonatal', label: 'Neonatal', icon: '👶', summary: 'GA classification, AGA/SGA/LGA, prematurity category (Fenton 2013 / WHO).' },
|
|
{ id: 'airway', label: 'Airway / RSI', icon: '💨', summary: 'ETT size + depth, RSI induction + paralytic dosing by weight.' },
|
|
{ id: 'cardiac', label: 'Cardiac Arrest', icon: '❤️', summary: 'PALS dosing (epinephrine, amiodarone, lidocaine), defibrillation J/kg.' },
|
|
{ id: 'respiratory', label: 'Respiratory', icon: '🫁', summary: 'Asthma, bronchiolitis, croup severity + dosing.' },
|
|
{ id: 'ventilation', label: 'O₂ & Ventilation', icon: '🌀', summary: 'NC / HFNC / CPAP / BiPAP flow + FiO₂ targets by age.' },
|
|
{ id: 'seizure', label: 'Seizures', icon: '🧠', summary: 'Benzodiazepine + second/third-line weight-based dosing.' },
|
|
{ id: 'sepsis', label: 'Sepsis & Fever', icon: '🦠', summary: 'Empirical antibiotics + fluid bolus dosing by weight.' },
|
|
{ id: 'anaphylaxis', label: 'Anaphylaxis', icon: '💉', summary: 'Epinephrine IM, IV infusion, steroid + antihistamine dosing.' },
|
|
{ id: 'sedation', label: 'Sedation', icon: '🛌', summary: 'Procedural sedation regimens — ketamine, propofol, midazolam.' },
|
|
{ id: 'agitation', label: 'Agitation', icon: '😤', summary: 'Weight-based haloperidol, olanzapine, lorazepam.' },
|
|
{ id: 'antiemetics', label: 'Antiemetics', icon: '💊', summary: 'Ondansetron, metoclopramide, promethazine dosing.' },
|
|
{ id: 'antimicrobials', label: 'Antimicrobials', icon: '🧫', summary: 'Common empirical regimens keyed to syndrome + weight.' },
|
|
{ id: 'burns', label: 'Burns', icon: '🔥', summary: 'TBSA % (Lund-Browder, Rule of Nines-children), Parkland fluids.' },
|
|
{ id: 'toxicology', label: 'Toxicology', icon: '☠️', summary: 'Common toxidromes + antidotes + decontamination windows.' },
|
|
{ id: 'trauma', label: 'Trauma', icon: '🩹', summary: 'PECARN, c-spine, blood-product dosing, TXA.' },
|
|
];
|
|
|
|
function LegacyPanel({ pill }: { pill: Pill }) {
|
|
return (
|
|
<section className={card} data-testid={'bedside-panel-' + pill.id}>
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-2xl" aria-hidden>{pill.icon}</span>
|
|
<h2 className="text-lg font-semibold">{pill.label}</h2>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">{pill.summary}</p>
|
|
<div className="rounded-md border border-amber-300 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm space-y-2">
|
|
<p className="text-amber-900 dark:text-amber-100">
|
|
Weight-based calculators for this module run in the legacy viewer while the clinical data is
|
|
verified for a direct React port. Open the legacy Bedside tab to use the full dosing flow.
|
|
</p>
|
|
</div>
|
|
<a href="/#bedside" className={btnPrimary + ' inline-block'}>
|
|
Open in legacy viewer
|
|
</a>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export default function Bedside() {
|
|
const [active, setActive] = useState<string>(PILLS[0].id);
|
|
const pill = PILLS.find((p) => p.id === active) ?? PILLS[0];
|
|
|
|
return (
|
|
<div className="max-w-5xl mx-auto p-6 space-y-4">
|
|
<header>
|
|
<h1 className="text-2xl font-semibold">Bedside</h1>
|
|
<p className="text-sm text-muted-foreground">
|
|
Emergency and rapid-reference pediatric tools. Weight-based dosing throughout — always verify against institutional protocols.
|
|
</p>
|
|
</header>
|
|
|
|
<div className="flex flex-wrap gap-2" data-testid="bedside-subnav">
|
|
{PILLS.map((p) => (
|
|
<button
|
|
key={p.id}
|
|
type="button"
|
|
onClick={() => setActive(p.id)}
|
|
className={
|
|
'px-3 py-1.5 rounded-full text-xs font-medium border transition-colors ' +
|
|
(active === p.id
|
|
? 'bg-primary text-primary-foreground border-primary'
|
|
: 'bg-muted hover:bg-muted/80 border-border')
|
|
}
|
|
data-testid={'bedside-pill-' + p.id}
|
|
>
|
|
<span className="mr-1" aria-hidden>{p.icon}</span>
|
|
{p.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<LegacyPanel pill={pill} />
|
|
</div>
|
|
);
|
|
}
|