feat(client): port Airway, Agitation, Antiemetics, Antimicrobials, Trauma panels
Second batch of Bedside sub-module ports. Eight of the fifteen pills
now run real React (anaphylaxis + cardiac + seizure from the previous
commit, plus these five). Remaining seven — neonatal, respiratory,
ventilation, sepsis, sedation, burns, toxicology — still fall through
to LegacyPanel.
client/src/pages/BedsidePanels.tsx
• AirwayPanel — weight + age inputs drive the full RSI sheet.
Pre-medication, induction, paralytics, and maintenance-sedation
tables with per-kg dosing + max caps preserved from airway.js.
Equipment sizing (ETT uncuffed/cuffed, depth, blade, LMA, Fr
suction, NG, chest tube) follows the same age-/weight-keyed
formulas. Ventilator starting settings carry Vt = 6-8 mL/kg
computed from the current weight.
• AgitationPanel — step 1 / 2 / 3 pathway with weight-conditional
doses for olanzapine (<30 kg vs ≥30 kg) and haloperidol
(<40 kg vs ≥40 kg). Droperidol keeps the range-prefix + computed
dose. Drug list matches AGIT_FALLBACK.
• AntiemeticsPanel — full drug list with the ondansetron weight-
band display alongside the per-kg fallback. Promethazine <2 yr
contraindication text preserved.
• AntimicrobialsPanel — neonate / infant / child × sepsis /
meningitis / pna / uti / skin / ent / neutropenic / intra-abd /
bone regimen map ported verbatim from REG. Age + infection
selectors pick the right cell.
• TraumaPanel — ABCDE primary survey cards with the 20 mL/kg
bolus auto-computed from the entered weight. MTP (1:1:1, TXA,
calcium), NEXUS c-spine, pediatric shock signs, and AMPLE
secondary-survey content all preserved.
renderBedsideRealPanel + REAL_BEDSIDE_PANELS extended to cover the
five new panels.
Client tsc -b + vite build clean (warnings on chunk size are expected
until we code-split; not blocking).
This commit is contained in:
parent
7acfefac1c
commit
ec418da0d6
5 changed files with 526 additions and 18 deletions
|
|
@ -601,16 +601,524 @@ export function SeizurePanel() {
|
|||
);
|
||||
}
|
||||
|
||||
// ── Airway / RSI ────────────────────────────────────────────
|
||||
// Drug per-kg + max values ported verbatim from public/js/bedside/airway.js.
|
||||
export function AirwayPanel() {
|
||||
const [weight, setWeight] = useState('');
|
||||
const [age, setAge] = useState('');
|
||||
const wt = Number.parseFloat(weight);
|
||||
const ageY = Number.parseFloat(age);
|
||||
const valid = Number.isFinite(wt) && wt > 0;
|
||||
const f = (perKg: number, max: number | null, unit = 'mg') => (valid ? formatDose(wt, perKg, max, unit) : null);
|
||||
|
||||
const hasAge = Number.isFinite(ageY) && ageY >= 0;
|
||||
const ettUncuff = hasAge ? Math.round(((ageY / 4) + 4) * 2) / 2 : null;
|
||||
const ettCuff = hasAge ? Math.round(((ageY / 4) + 3.5) * 2) / 2 : null;
|
||||
const ettDepth = ettUncuff ? Math.round(ettUncuff * 3 * 10) / 10 : null;
|
||||
const blade = hasAge
|
||||
? ageY < 1 ? 'Miller 0-1'
|
||||
: ageY < 2 ? 'Miller 1 / Mac 1'
|
||||
: ageY < 8 ? 'Miller 2 / Mac 2'
|
||||
: 'Miller/Mac 3'
|
||||
: '—';
|
||||
const lma = valid
|
||||
? wt < 5 ? '1' : wt < 10 ? '1.5' : wt < 20 ? '2' : wt < 30 ? '2.5' : wt < 50 ? '3' : wt < 70 ? '4' : '5'
|
||||
: '—';
|
||||
const succPerKg = valid && wt < 10 ? 2 : 1.5;
|
||||
const tvLow = f(6, null, 'mL');
|
||||
const tvHigh = f(8, null, 'mL');
|
||||
|
||||
function Table({ title, rows }: { title: string; rows: Array<[string, string, string, string]> }) {
|
||||
return (
|
||||
<>
|
||||
<h3 className="text-sm font-semibold mt-3">{title}</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr><th className={th}>Drug</th><th className={th}>Dose</th><th className={th}>Route</th><th className={th}>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((r, i) => (
|
||||
<tr key={i}>
|
||||
<td className={td + ' font-semibold'}>{r[0]}</td>
|
||||
<td className={td}><Dose label={r[1]} /></td>
|
||||
<td className={td + ' text-xs'}>{r[2]}</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'}>{r[3]}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={card} data-testid="bedside-panel-airway">
|
||||
<h2 className="text-lg font-semibold">Airway / RSI</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 max-w-md">
|
||||
<WeightField id="airway-wt" value={weight} onChange={setWeight} testid="airway-weight" />
|
||||
<div>
|
||||
<label htmlFor="airway-age" className={label}>Age (years)</label>
|
||||
<input
|
||||
id="airway-age"
|
||||
type="number"
|
||||
min="0"
|
||||
step="0.5"
|
||||
className={input}
|
||||
value={age}
|
||||
onChange={(e) => setAge(e.target.value)}
|
||||
data-testid="airway-age"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!valid ? (
|
||||
<p className="text-xs text-destructive">Enter weight (kg) to see doses.</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="rounded-md border-2 border-blue-500 bg-blue-50 dark:bg-blue-950/30 p-3 text-sm font-semibold text-blue-900 dark:text-blue-100">
|
||||
RSI doses & equipment — {wt} kg{hasAge ? `, ~${ageY} yr` : ''}
|
||||
</div>
|
||||
|
||||
<Table title="Pre-medication (optional)" rows={[
|
||||
['Atropine', `${f(0.02, 1, 'mg')!.label} (min 0.1 mg)`, 'IV', 'Consider <1 yr or if using succinylcholine, or bradycardia risk'],
|
||||
['Lidocaine 2%', f(1.5, 100, 'mg')!.label, 'IV over 1 min', 'For ↑ICP, asthma; give 2-3 min pre-induction'],
|
||||
['Fentanyl', `${f(2, 150, 'mcg')!.label} (2-3 mcg/kg)`, 'IV over 1 min', 'Blunts sympathetic response; avoid if hypotensive'],
|
||||
]} />
|
||||
<Table title="Induction" rows={[
|
||||
['Ketamine', `${f(1.5, 150, 'mg')!.label} (1-2 mg/kg)`, 'IV', 'Preserves BP. First-line for shock, asthma, sepsis'],
|
||||
['Etomidate', f(0.3, 30, 'mg')!.label, 'IV', 'Hemodynamically neutral. Avoid in septic shock (adrenal suppression)'],
|
||||
['Propofol', `${f(2, 200, 'mg')!.label} (1-2 mg/kg)`, 'IV', 'Causes hypotension. Avoid in shock'],
|
||||
['Midazolam', f(0.2, 10, 'mg')!.label, 'IV', 'Less optimal induction — slower onset'],
|
||||
]} />
|
||||
<Table title="Paralytics" rows={[
|
||||
['Rocuronium', `${f(1.2, 100, 'mg')!.label} (1-1.2 mg/kg)`, 'IV', '30-60 sec onset; 30-45 min duration. First-line non-depolarizing.'],
|
||||
['Succinylcholine', f(succPerKg, 150, 'mg')!.label, 'IV', '<10kg: 2 mg/kg; ≥10kg: 1.5 mg/kg. Avoid in burns/crush/hyperK/MH/NM dz. IM option: 4 mg/kg.'],
|
||||
['Vecuronium', f(0.1, 10, 'mg')!.label, 'IV', '2-3 min onset; longer duration than roc'],
|
||||
]} />
|
||||
<Table title="Maintenance sedation / analgesia" rows={[
|
||||
['Midazolam gtt', `${f(0.05, 2, 'mg')!.label}–${f(0.2, 10, 'mg')!.label}/hr`, 'IV infusion', '0.05-0.2 mg/kg/hr'],
|
||||
['Fentanyl gtt', `${f(1, 100, 'mcg')!.label}–${f(4, 200, 'mcg')!.label}/hr`, 'IV infusion', '1-4 mcg/kg/hr'],
|
||||
['Ketamine gtt', `${f(0.5, 30, 'mg')!.label}–${f(2, 100, 'mg')!.label}/hr`, 'IV infusion', '0.5-2 mg/kg/hr. Good for asthma'],
|
||||
['Dexmedetomidine gtt', '0.2-1.4 mcg/kg/hr', 'IV infusion', 'No resp depression; causes bradycardia + hypotension'],
|
||||
['Rocuronium gtt', '10-12 mcg/kg/min', 'IV infusion', 'If continued paralysis needed — only with adequate sedation'],
|
||||
]} />
|
||||
|
||||
<h3 className="text-sm font-semibold mt-3">Equipment sizing</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
|
||||
<div className="rounded-md bg-muted/40 p-3">
|
||||
{hasAge ? (
|
||||
<>
|
||||
<div><strong>ETT (uncuffed):</strong> {ettUncuff} mm</div>
|
||||
<div><strong>ETT (cuffed):</strong> {ettCuff} mm</div>
|
||||
<div><strong>Depth at lip:</strong> ~{ettDepth} cm</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div><em>Enter age for ETT / blade sizing.</em></div>
|
||||
<div>ETT (uncuffed) = (age/4) + 4</div>
|
||||
<div>ETT (cuffed) = (age/4) + 3.5</div>
|
||||
<div>Depth = ETT size × 3</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="rounded-md bg-muted/40 p-3">
|
||||
<div><strong>Laryngoscope blade:</strong> {blade}</div>
|
||||
<div><strong>LMA size:</strong> {lma}</div>
|
||||
<div>OPA = corner mouth → angle mandible</div>
|
||||
<div>NPA = tip nose → tragus</div>
|
||||
</div>
|
||||
<div className="rounded-md bg-muted/40 p-3 md:col-span-2">
|
||||
<div><strong>Suction catheter (Fr):</strong> ETT size × 2</div>
|
||||
<div><strong>NG tube:</strong> ETT × 2</div>
|
||||
<div><strong>Chest tube:</strong> 4 × ETT</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-semibold mt-3">Ventilator — starting settings</h3>
|
||||
<div className="rounded-md bg-blue-50 dark:bg-blue-950/30 p-3 text-xs space-y-1">
|
||||
<div><strong>Mode:</strong> Volume or pressure control</div>
|
||||
<div><strong>Tidal volume:</strong> 6-8 mL/kg (= {tvLow!.value}-{tvHigh!.value} mL). Use lower (4-6 mL/kg) for ARDS.</div>
|
||||
<div><strong>Rate:</strong> Infant 25-30, Child 15-25, Adolescent 12-16</div>
|
||||
<div><strong>PEEP:</strong> 5 cmH₂O (↑ for oxygenation problems)</div>
|
||||
<div><strong>FiO₂:</strong> Start 100%, wean to SpO₂ 92-97%</div>
|
||||
<div><strong>I:E ratio:</strong> 1:2 (1:3-4 for obstructive disease)</div>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-muted-foreground italic">
|
||||
Harriet Lane Handbook 23rd ed · PALS 2020. Always confirm doses against institutional protocols.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Agitation ───────────────────────────────────────────────
|
||||
// Drug data ported verbatim from public/js/bedside/agitation.js AGIT_FALLBACK.
|
||||
export function AgitationPanel() {
|
||||
const [weight, setWeight] = useState('');
|
||||
const wt = Number.parseFloat(weight);
|
||||
const valid = Number.isFinite(wt) && wt > 0;
|
||||
|
||||
function row(display: string, dose: string, route: string, notes: string) {
|
||||
return (
|
||||
<tr>
|
||||
<td className={td + ' font-semibold'}>{display}</td>
|
||||
<td className={td}><Dose label={dose} /></td>
|
||||
<td className={td + ' text-xs'}>{route}</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'} dangerouslySetInnerHTML={{ __html: notes }} />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
const step2Rows = !valid ? null : [
|
||||
row('Lorazepam', formatDose(wt, 0.05, 2).label, 'PO', '0.05 mg/kg. May repeat in 30 min.'),
|
||||
row('Midazolam', formatDose(wt, 0.5, 10).label, 'PO', '0.5 mg/kg (max 10 mg)'),
|
||||
row('Midazolam', formatDose(wt, 0.3, 10).label, 'IN (5 mg/mL)', '0.3 mg/kg split between nares (max 10 mg)'),
|
||||
row('Olanzapine (ODT)', wt < 30 ? '2.5-5 mg' : '5-10 mg', 'PO / ODT', 'Age ≥6 yr. Avoid IM + benzo combo (risk of resp depression).'),
|
||||
row('Diphenhydramine', formatDose(wt, 1, 50).label, 'PO', '1 mg/kg. Adjunct only; sedating.'),
|
||||
];
|
||||
const step3Rows = !valid ? null : [
|
||||
row('Midazolam', formatDose(wt, 0.1, 5).label, 'IV / IM', '0.05-0.1 mg/kg IV, 0.1-0.15 mg/kg IM (max 10 mg)'),
|
||||
row('Lorazepam', formatDose(wt, 0.1, 4).label, 'IV / IM', '0.05-0.1 mg/kg (max 4 mg). May cause resp depression.'),
|
||||
row('Haloperidol', wt < 40 ? `0.025-0.075 mg/kg = ${Math.round(wt * 0.05 * 100) / 100} mg` : '2.5-5 mg', 'IM / IV', 'Avoid <3 yr. Risk: QT, EPS, NMS. ECG if repeated.'),
|
||||
row('Olanzapine', wt < 40 ? '2.5-5 mg' : '5-10 mg', 'IM', 'Avoid benzo co-administration (resp depression, hypotension)'),
|
||||
row('Ketamine', formatDose(wt, 4, 500).label, 'IM', '4-5 mg/kg IM (rescue for severe excited delirium). Monitor airway.'),
|
||||
row('Droperidol', `0.03-0.07 mg/kg = ${Math.round(wt * 0.05 * 100) / 100} mg`, 'IM / IV', 'Effective but QT concern — get ECG.'),
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={card} data-testid="bedside-panel-agitation">
|
||||
<h2 className="text-lg font-semibold">Agitation</h2>
|
||||
<WeightField id="agit-wt" value={weight} onChange={setWeight} testid="agit-weight" />
|
||||
|
||||
{!valid ? (
|
||||
<p className="text-xs text-destructive">Enter weight (kg) to see doses.</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="rounded-md border-2 border-purple-500 bg-purple-50 dark:bg-purple-950/30 p-3 text-sm font-semibold text-purple-900 dark:text-purple-100">
|
||||
Agitation management — {wt} kg
|
||||
</div>
|
||||
<div className="rounded-md border-l-4 border-green-500 bg-green-50 dark:bg-green-950/30 p-3 text-sm">
|
||||
<div className="font-semibold text-green-800 dark:text-green-200">Step 1 — Non-pharmacologic</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Dim lights, quiet room, familiar caregiver present, reduce stimulation. Rule out hypoglycemia, hypoxia, pain, ↑ICP, toxic ingestion, infection.
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border-l-4 border-blue-500 bg-blue-50 dark:bg-blue-950/30 p-3 text-sm">
|
||||
<div className="font-semibold text-blue-800 dark:text-blue-200">Step 2 — Oral / intranasal (cooperative)</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">First-line for moderate agitation if the patient will accept PO/IN.</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr><th className={th}>Drug</th><th className={th}>Dose</th><th className={th}>Route</th><th className={th}>Notes</th></tr></thead>
|
||||
<tbody>{step2Rows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="rounded-md border-l-4 border-amber-500 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm">
|
||||
<div className="font-semibold text-amber-800 dark:text-amber-200">Step 3 — IM / IV (severe, uncooperative, or safety risk)</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">Require continuous monitoring. Consider restraints only as last resort.</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr><th className={th}>Drug</th><th className={th}>Dose</th><th className={th}>Route</th><th className={th}>Notes</th></tr></thead>
|
||||
<tbody>{step3Rows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="rounded-md bg-amber-50 dark:bg-amber-950/30 p-3 text-xs text-amber-900 dark:text-amber-100">
|
||||
<strong>Monitoring:</strong> Continuous SpO₂ + HR after parenteral sedation. Have airway equipment, flumazenil, naloxone, and IV access ready.
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground italic">
|
||||
AAP Clinical Report on Pediatric Agitation · ACEP Guidelines for Acute Agitation.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Antiemetics ─────────────────────────────────────────────
|
||||
// Drug data ported verbatim from public/js/bedside/antiemetics.js EMET_FALLBACK.
|
||||
export function AntiemeticsPanel() {
|
||||
const [weight, setWeight] = useState('');
|
||||
const wt = Number.parseFloat(weight);
|
||||
const valid = Number.isFinite(wt) && wt > 0;
|
||||
const f = (perKg: number, max: number | null, unit = 'mg') => (valid ? formatDose(wt, perKg, max, unit) : null);
|
||||
|
||||
const ondBand = !valid ? null : (wt < 15 ? '2 mg' : wt < 30 ? '4 mg' : '8 mg');
|
||||
|
||||
return (
|
||||
<section className={card} data-testid="bedside-panel-antiemetics">
|
||||
<h2 className="text-lg font-semibold">Antiemetics</h2>
|
||||
<WeightField id="emet-wt" value={weight} onChange={setWeight} testid="emet-weight" />
|
||||
|
||||
{!valid ? (
|
||||
<p className="text-xs text-destructive">Enter weight (kg) to see doses.</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="rounded-md border-2 border-blue-500 bg-blue-50 dark:bg-blue-950/30 p-3 text-sm font-semibold text-blue-900 dark:text-blue-100">
|
||||
Antiemetic doses — {wt} kg
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr><th className={th}>Drug</th><th className={th}>Dose</th><th className={th}>Route</th><th className={th}>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={td + ' font-semibold'}>Ondansetron</td>
|
||||
<td className={td + ' text-xs'}>{ondBand} (weight band) OR <Dose label={f(0.15, 8)!.label} /></td>
|
||||
<td className={td + ' text-xs'}>PO / ODT / IV</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'}>First-line. Max single 8 mg. Repeat q8h. QT prolongation — avoid with other QT drugs. <6 mo: limited data.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={td + ' font-semibold'}>Metoclopramide</td>
|
||||
<td className={td}><Dose label={f(0.15, 10)!.label} /></td>
|
||||
<td className={td + ' text-xs'}>IV / IM / PO</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'}>0.1-0.15 mg/kg (max 10 mg). Give with diphenhydramine to prevent EPS / dystonia.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={td + ' font-semibold'}>Dimenhydrinate</td>
|
||||
<td className={td}><Dose label={f(1.25, 50)!.label} /></td>
|
||||
<td className={td + ' text-xs'}>PO / IV / IM / PR</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'}>1.25 mg/kg (max 50 mg) q6h. ≥2 yr.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={td + ' font-semibold'}>Diphenhydramine</td>
|
||||
<td className={td}><Dose label={f(1, 50)!.label} /></td>
|
||||
<td className={td + ' text-xs'}>PO / IV / IM</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'}>1 mg/kg (max 50 mg) q6h. Adjunct, sedating.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={td + ' font-semibold'}>Promethazine</td>
|
||||
<td className={td}><Dose label={f(0.25, 25)!.label} /></td>
|
||||
<td className={td + ' text-xs'}>PO / IV / IM</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'} dangerouslySetInnerHTML={{ __html: '0.25-1 mg/kg. <strong>CONTRAINDICATED <2 yr</strong> (resp depression). Tissue injury if IV extrav.' }} />
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={td + ' font-semibold'}>Dexamethasone</td>
|
||||
<td className={td}><Dose label={f(0.15, 10)!.label} /></td>
|
||||
<td className={td + ' text-xs'}>IV / PO</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'}>Adjunct, esp. chemo-induced. 0.15 mg/kg (max 10 mg).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={td + ' font-semibold'}>Scopolamine patch</td>
|
||||
<td className={td + ' text-xs'}>1.5 mg patch</td>
|
||||
<td className={td + ' text-xs'}>Transdermal</td>
|
||||
<td className={td + ' text-xs text-muted-foreground'}>≥12 yr. Motion sickness. Apply 4h before exposure.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="rounded-md bg-amber-50 dark:bg-amber-950/30 p-3 text-xs text-amber-900 dark:text-amber-100">
|
||||
<strong>Pearls:</strong> Ondansetron is first-line in ED for acute gastroenteritis vomiting — single PO/ODT dose increases oral rehydration success. Avoid anticholinergics + opioid combinations. Check QTc if stacking ondansetron + other QT drugs.
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground italic">
|
||||
AAP gastroenteritis guidance · WHO essential medicines · Lexicomp.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Antimicrobials ──────────────────────────────────────────
|
||||
// Regimen map ported verbatim from public/js/bedside/antimicrobials.js REG.
|
||||
const ABX_REG: Record<string, Record<string, { first: string; alt: string; duration: string; notes: string }>> = {
|
||||
neonate: {
|
||||
sepsis: { first: 'Ampicillin + gentamicin', alt: 'Add cefotaxime if meningitis suspected or gram-neg concern', duration: '7-10 days; 21 days if confirmed meningitis', notes: 'Cover GBS, E. coli, Listeria. Add acyclovir if HSV risk (maternal lesions, vesicles, seizures).' },
|
||||
meningitis: { first: 'Ampicillin + cefotaxime (or gentamicin) + acyclovir', alt: 'Add vancomycin if gram-positive cocci', duration: '14-21 days depending on organism', notes: 'HSV coverage essential. LP + HSV PCR.' },
|
||||
pna: { first: 'Ampicillin + gentamicin', alt: 'Add cefotaxime for gram-neg', duration: '7-10 days', notes: 'Same as sepsis coverage.' },
|
||||
uti: { first: 'Ampicillin + gentamicin', alt: 'Cefotaxime if sensitivities known', duration: '10-14 days', notes: 'Evaluate for sepsis. Renal US and VCUG workup.' },
|
||||
skin: { first: 'Ampicillin + gentamicin', alt: 'Add vancomycin if MRSA risk', duration: '7-10 days', notes: 'Omphalitis, mastitis — broad coverage.' },
|
||||
ent: { first: 'Discuss with infectious diseases', alt: '—', duration: '—', notes: 'Rare in neonates. Any ear/throat infection warrants sepsis eval.' },
|
||||
neutropenic: { first: 'Cefepime or piperacillin-tazobactam', alt: 'Add vancomycin if indwelling line, severe mucositis, or MRSA', duration: 'Until ANC recovery + afebrile ≥48h', notes: 'Oncology/ID consult.' },
|
||||
ic: { first: 'Ampicillin + gentamicin + metronidazole', alt: 'Or piperacillin-tazobactam', duration: '7-14 days', notes: 'NEC: add metronidazole/clindamycin for anaerobes.' },
|
||||
bone: { first: 'Ampicillin + gentamicin', alt: 'Vancomycin if MRSA risk', duration: '3-6 weeks (IV then PO)', notes: 'Usually hematogenous. S. aureus, GBS, E. coli.' },
|
||||
},
|
||||
infant: {
|
||||
sepsis: { first: 'Ampicillin + ceftriaxone (or gentamicin)', alt: '+ vancomycin if MRSA / severe', duration: '7-10 days', notes: 'Add acyclovir if HSV suspected (<6 wks). Fever in <90 days requires workup.' },
|
||||
meningitis: { first: 'Ceftriaxone + vancomycin (± ampicillin if <6 wks for Listeria)', alt: '+ acyclovir if HSV', duration: '10-14 days (longer for Listeria/gram-neg)', notes: 'Dexamethasone if H. influenzae suspected.' },
|
||||
pna: { first: 'Ampicillin (or ceftriaxone)', alt: 'Add azithromycin if atypical', duration: '7-10 days', notes: 'S. pneumoniae most common. RSV/viral often primary.' },
|
||||
uti: { first: 'Ceftriaxone or cefotaxime', alt: 'Ampicillin + gentamicin', duration: '10-14 days', notes: 'US kidney/bladder if first febrile UTI.' },
|
||||
skin: { first: 'Cefazolin or clindamycin', alt: 'Vancomycin if MRSA risk', duration: '7-10 days', notes: 'Consider MRSA if purulent or local resistance >10%.' },
|
||||
ent: { first: 'Amoxicillin (90 mg/kg/day) for AOM', alt: 'Amox-clav if recent abx/treatment failure', duration: 'AOM 10 days <2 yr; 7 days ≥2 yr', notes: 'Observe mild AOM if >6 mo and no severe features.' },
|
||||
neutropenic: { first: 'Cefepime or piperacillin-tazobactam', alt: '+ vancomycin for severe/mucositis/line', duration: 'Until ANC recovery + afebrile', notes: 'Oncology/ID consult.' },
|
||||
ic: { first: 'Ceftriaxone + metronidazole', alt: 'Piperacillin-tazobactam', duration: '5-7 days (uncomplicated), longer for complicated', notes: 'Appendicitis — surgical consult.' },
|
||||
bone: { first: 'Cefazolin ± vancomycin', alt: 'Clindamycin', duration: '3-4 weeks (IV then PO transition)', notes: 'S. aureus most common. Kingella in <4 yr.' },
|
||||
},
|
||||
child: {
|
||||
sepsis: { first: 'Ceftriaxone + vancomycin', alt: 'Piperacillin-tazobactam if intra-abdominal', duration: '7-14 days', notes: 'Expand if specific source. Add antifungal if prolonged neutropenia.' },
|
||||
meningitis: { first: 'Ceftriaxone + vancomycin', alt: '+ dexamethasone (Hib coverage)', duration: '10-14 days', notes: 'Lumbar puncture. Dexamethasone 0.15 mg/kg q6h x 4 days.' },
|
||||
pna: { first: 'Ampicillin OR amoxicillin (high dose)', alt: 'Ceftriaxone if hospitalized; add azithromycin for atypical', duration: '5-7 days (uncomplicated)', notes: 'Mycoplasma if >5 yr. Consider viral causes.' },
|
||||
uti: { first: 'Cephalexin or TMP-SMX (PO)', alt: 'Ceftriaxone if ill', duration: '7-10 days (10-14 if pyelo)', notes: 'Culture-guided de-escalation.' },
|
||||
skin: { first: 'Cephalexin or clindamycin', alt: 'TMP-SMX or doxycycline if MRSA', duration: '5-10 days', notes: 'Abscess — incision & drainage primary tx.' },
|
||||
ent: { first: 'Amoxicillin 90 mg/kg/day (AOM); Penicillin V for strep pharyngitis', alt: 'Amox-clav; cephalexin if non-anaphylactic PCN allergy', duration: 'AOM 5-10 days; strep 10 days', notes: 'Strep pharyngitis dose: PCN V 250 mg BID-TID (<27 kg) or 500 mg BID (≥27 kg).' },
|
||||
neutropenic: { first: 'Cefepime OR piperacillin-tazobactam', alt: '+ vancomycin', duration: 'Until ANC recovery + afebrile', notes: 'Add empiric antifungal if fever >4-7 days.' },
|
||||
ic: { first: 'Ceftriaxone + metronidazole', alt: 'Piperacillin-tazobactam', duration: '5-7 days (uncomplicated appendicitis)', notes: 'Surgical consult.' },
|
||||
bone: { first: 'Cefazolin ± vancomycin', alt: 'Clindamycin', duration: '3-4 weeks total', notes: 'S. aureus, group A strep. Consider MRSA if severe.' },
|
||||
},
|
||||
};
|
||||
|
||||
const ABX_AGE_LABEL: Record<string, string> = {
|
||||
neonate: 'Neonate (0-28 d)',
|
||||
infant: 'Infant (1-3 mo)',
|
||||
child: 'Child (>3 mo)',
|
||||
};
|
||||
const ABX_INFECTIONS: Array<[string, string]> = [
|
||||
['sepsis', 'Sepsis / bacteremia'],
|
||||
['meningitis', 'Meningitis'],
|
||||
['pna', 'Pneumonia'],
|
||||
['uti', 'Urinary tract infection'],
|
||||
['skin', 'Skin / soft tissue'],
|
||||
['ent', 'Ear / throat (ENT)'],
|
||||
['neutropenic', 'Febrile neutropenia'],
|
||||
['ic', 'Intra-abdominal'],
|
||||
['bone', 'Bone / joint'],
|
||||
];
|
||||
|
||||
export function AntimicrobialsPanel() {
|
||||
const [age, setAge] = useState<'neonate' | 'infant' | 'child'>('child');
|
||||
const [infection, setInfection] = useState('sepsis');
|
||||
const regimen = ABX_REG[age][infection];
|
||||
|
||||
return (
|
||||
<section className={card} data-testid="bedside-panel-antimicrobials">
|
||||
<h2 className="text-lg font-semibold">Antimicrobials (empirical)</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 max-w-xl">
|
||||
<div>
|
||||
<label htmlFor="abx-age" className={label}>Age band</label>
|
||||
<select id="abx-age" className={input} value={age} onChange={(e) => setAge(e.target.value as typeof age)} data-testid="abx-age">
|
||||
{Object.entries(ABX_AGE_LABEL).map(([v, l]) => <option key={v} value={v}>{l}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="abx-infection" className={label}>Infection</label>
|
||||
<select id="abx-infection" className={input} value={infection} onChange={(e) => setInfection(e.target.value)} data-testid="abx-infection">
|
||||
{ABX_INFECTIONS.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border-2 border-blue-500 bg-blue-50 dark:bg-blue-950/30 p-3 text-sm font-semibold text-blue-900 dark:text-blue-100">
|
||||
{ABX_AGE_LABEL[age]} — {ABX_INFECTIONS.find(([v]) => v === infection)?.[1]}
|
||||
</div>
|
||||
<div className="rounded-md bg-green-50 dark:bg-green-950/30 p-3 text-sm">
|
||||
<strong className="text-green-800 dark:text-green-200">First-line:</strong> {regimen.first}
|
||||
</div>
|
||||
<div className="rounded-md bg-muted/40 p-3 text-sm">
|
||||
<strong>Alternative / add:</strong> {regimen.alt}
|
||||
</div>
|
||||
<div className="rounded-md bg-muted/40 p-3 text-sm">
|
||||
<strong>Duration:</strong> {regimen.duration}
|
||||
</div>
|
||||
<div className="rounded-md bg-amber-50 dark:bg-amber-950/30 p-3 text-xs text-amber-900 dark:text-amber-100">
|
||||
<strong>Notes:</strong> {regimen.notes}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground italic">
|
||||
AAP Red Book · IDSA guidelines · Harriet Lane. Always tailor to local resistance patterns and culture results.
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Trauma ──────────────────────────────────────────────────
|
||||
// Clinical content ported verbatim from public/js/bedside/trauma.js.
|
||||
export function TraumaPanel() {
|
||||
const [weight, setWeight] = useState('');
|
||||
const wt = Number.parseFloat(weight);
|
||||
const valid = Number.isFinite(wt) && wt > 0;
|
||||
const bolus = valid ? Math.round(wt * 20) : null;
|
||||
|
||||
return (
|
||||
<section className={card} data-testid="bedside-panel-trauma">
|
||||
<h2 className="text-lg font-semibold">Trauma — primary survey + MTP</h2>
|
||||
<WeightField id="trauma-wt" value={weight} onChange={setWeight} testid="trauma-weight" />
|
||||
|
||||
<h3 className="text-sm font-semibold mt-2">Primary Survey — ABCDE</h3>
|
||||
<div className="rounded-md border-l-4 border-destructive bg-red-50 dark:bg-red-950/30 p-3 text-sm">
|
||||
<div className="font-semibold text-destructive">A — Airway + c-spine</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Maintain airway (jaw thrust, suction). <strong>Manual inline stabilization</strong>; apply collar. Intubate if GCS ≤8, inadequate airway, or impending compromise. Avoid succinylcholine if burn/crush/prolonged immobilization.
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border-l-4 border-amber-500 bg-amber-50 dark:bg-amber-950/30 p-3 text-sm">
|
||||
<div className="font-semibold text-amber-800 dark:text-amber-200">B — Breathing</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
SpO₂, bilateral breath sounds, chest wall integrity. Identify tension PTX (needle decompression), open PTX (3-sided dressing), flail chest, massive hemothorax.
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border-l-4 border-green-500 bg-green-50 dark:bg-green-950/30 p-3 text-sm">
|
||||
<div className="font-semibold text-green-800 dark:text-green-200">C — Circulation</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Two large-bore IVs / IO. Control hemorrhage (direct pressure, tourniquet, pelvic binder).{' '}
|
||||
{valid ? <>NS or LR 20 mL/kg = <strong>{bolus} mL</strong> bolus.</> : <>NS/LR 20 mL/kg bolus.</>}{' '}
|
||||
Consider blood after 40-60 mL/kg crystalloid or in Class III shock.
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border-l-4 border-blue-500 bg-blue-50 dark:bg-blue-950/30 p-3 text-sm">
|
||||
<div className="font-semibold text-blue-800 dark:text-blue-200">D — Disability</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
GCS, pupils, glucose, gross motor. Consider ↑ICP (head up 30°, mannitol 0.5-1 g/kg or hypertonic saline 3% 3-5 mL/kg).
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border-l-4 border-violet-500 bg-violet-50 dark:bg-violet-950/30 p-3 text-sm">
|
||||
<div className="font-semibold text-violet-800 dark:text-violet-200">E — Exposure + environment</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">Fully expose; prevent hypothermia (warm blankets, fluids).</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-semibold mt-3">Massive Transfusion Protocol (MTP)</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead><tr><th className={th}>Drug</th><th className={th}>Dose</th><th className={th}>Route</th><th className={th}>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td className={td + ' font-semibold'}>Ratio</td><td className={td}>1:1:1 (pRBC : FFP : platelets)</td><td className={td + ' text-xs'}>—</td><td className={td + ' text-xs text-muted-foreground'}>Activate early if ≥40 mL/kg transfused or ongoing hemorrhage. Avoid excessive crystalloid.</td></tr>
|
||||
<tr><td className={td + ' font-semibold'}>Tranexamic acid (TXA)</td><td className={td}>15 mg/kg IV (max 1 g) over 10 min → 2 mg/kg/hr × 8h</td><td className={td + ' text-xs'}>IV</td><td className={td + ' text-xs text-muted-foreground'}>Within 3 hr of injury. CRASH-2 / MATIC.</td></tr>
|
||||
<tr><td className={td + ' font-semibold'}>Calcium</td><td className={td}>20 mg/kg CaCl or 60 mg/kg Ca-gluconate IV per unit citrated blood</td><td className={td + ' text-xs'}>IV</td><td className={td + ' text-xs text-muted-foreground'}>Citrated blood chelates calcium.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-semibold mt-3">C-spine clearance (NEXUS / CCR)</h3>
|
||||
<div className="rounded-md bg-muted/40 p-3 text-xs">
|
||||
Pediatric c-spine decision tools are imperfect. <strong>Imaging if any:</strong> focal neurologic deficit, altered mental status, neck pain / tenderness, torticollis, substantial torso injury, high-risk mechanism (diving, MVC >55 mph, fall >10 ft). Plain films + CT if positive or equivocal.
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-semibold mt-3">Pediatric shock — signs</h3>
|
||||
<div className="rounded-md bg-red-50 dark:bg-red-950/30 p-3 text-xs text-red-900 dark:text-red-200">
|
||||
Children compensate well — <strong>hypotension is a late finding</strong>. Early signs: tachycardia, cool extremities, weak peripheral pulses, prolonged cap refill (>3 sec), narrowed pulse pressure, altered mentation. Minimum SBP = 70 + (2 × age in years) for ages 1-10.
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-semibold mt-3">Secondary survey — AMPLE + head-to-toe</h3>
|
||||
<div className="rounded-md bg-muted/40 p-3 text-xs">
|
||||
<strong>AMPLE:</strong> Allergies, Medications, Past history, Last meal, Events of injury. Head-to-toe exam; log-roll for back; digital rectal; neurovascular checks of all extremities.
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-muted-foreground italic">
|
||||
ATLS 10th ed · PALS 2020 · PECARN c-spine rule · CRASH-2 trial (TXA).
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to keep Bedside.tsx concise — dispatch by pill id.
|
||||
export function renderBedsideRealPanel(pillId: string): React.ReactNode | null {
|
||||
switch (pillId) {
|
||||
case 'anaphylaxis': return <AnaphylaxisPanel />;
|
||||
case 'cardiac': return <CardiacPanel />;
|
||||
case 'seizure': return <SeizurePanel />;
|
||||
default: return null;
|
||||
case 'anaphylaxis': return <AnaphylaxisPanel />;
|
||||
case 'cardiac': return <CardiacPanel />;
|
||||
case 'seizure': return <SeizurePanel />;
|
||||
case 'airway': return <AirwayPanel />;
|
||||
case 'agitation': return <AgitationPanel />;
|
||||
case 'antiemetics': return <AntiemeticsPanel />;
|
||||
case 'antimicrobials': return <AntimicrobialsPanel />;
|
||||
case 'trauma': return <TraumaPanel />;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Expose the pill IDs that now have a real React panel so Bedside.tsx
|
||||
// can pick between the real panel and the legacy-link fallback.
|
||||
export const REAL_BEDSIDE_PANELS: ReadonlySet<string> = new Set(['anaphylaxis', 'cardiac', 'seizure']);
|
||||
export const REAL_BEDSIDE_PANELS: ReadonlySet<string> = new Set([
|
||||
'anaphylaxis', 'cardiac', 'seizure',
|
||||
'airway', 'agitation', 'antiemetics', 'antimicrobials', 'trauma',
|
||||
]);
|
||||
|
|
|
|||
2
public/app/assets/index-BFoZEwhP.css
Normal file
2
public/app/assets/index-BFoZEwhP.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -5,8 +5,8 @@
|
|||
<link rel="icon" type="image/svg+xml" href="/app/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>client</title>
|
||||
<script type="module" crossorigin src="/app/assets/index-CJeLsmYO.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/app/assets/index-DwfNQaVq.css">
|
||||
<script type="module" crossorigin src="/app/assets/index-CU47FuJC.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/app/assets/index-BFoZEwhP.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue