Split the Bedside clinical reference section out of calculators.js (~1220 lines) into 20 focused ES modules under public/js/bedside/. Each module owns one clinical topic (cardiac, seizure, sepsis, burns, etc.) and exports an init() wired up by bedside/index.js. Shared helpers live in shared.js and also set window._EM for back-compat with the 4 remaining call sites in calculators.js. Load order: classic defer calculators.js first, then module script bedside/index.js. Handlers bind at DOM-ready; runtime _EM lookups resolve after both are evaluated. Makes bedside content editable per-section, shrinks calculators.js from 4111 to 2891 lines, and keeps the existing Playwright smoke suite (e2e/tests/bedside-smoke.spec.js) as the behavioral contract.
112 lines
8.9 KiB
JavaScript
112 lines
8.9 KiB
JavaScript
// ============================================================
|
|
// bedside/sedation.js
|
|
// PROCEDURAL SEDATION — agents + reversal, drugs.json-backed.
|
|
// ============================================================
|
|
|
|
// Pulls in shared side-effect (window._EM) in case anything else needs it.
|
|
import './shared.js';
|
|
|
|
function d(wt, mgPerKg, max) { var v = Math.round(wt * mgPerKg * 100) / 100; return max ? Math.min(v, max) : v; }
|
|
|
|
// Fallback inline data — mirrors drugs.json sedation section. The first
|
|
// table's rows group by drug (Ketamine has both IV + IM lines), hence the
|
|
// display vs name split. `reverses` marks the final 2 rows as reversal
|
|
// agents shown in a separate table.
|
|
var SED_FALLBACK = [
|
|
{ name: 'Ketamine IV', display: 'Ketamine', dose_mg_per_kg_low: 1.5, dose_mg_per_kg_high: 2, max_mg_low: 100, max_mg_high: 150, unit: 'mg', route: 'IV', onset: '1 min', duration: '15-30 min', notes: 'Dissociative. Preserves airway reflexes.' },
|
|
{ name: 'Ketamine IM', display: '', dose_mg_per_kg_low: 4, dose_mg_per_kg_high: 5, max_mg_low: 300, max_mg_high: 400, unit: 'mg', route: 'IM', onset: '3-5 min', duration: '30-60 min', notes: 'Give atropine 0.01 mg/kg to reduce secretions.' },
|
|
{ name: 'Midazolam IV', display: 'Midazolam', dose_mg_per_kg_low: 0.05, dose_mg_per_kg_high: 0.1, max_mg_low: 2, max_mg_high: 5, unit: 'mg', route: 'IV', onset: '2-3 min', duration: '30-60 min', notes: 'Anxiolysis. Titrate q3-5 min.' },
|
|
{ name: 'Midazolam IN/PO', display: '', dose_mg_per_kg: 0.5, max_mg: 20, unit: 'mg', route: 'IN/PO', onset: '10-15 min', duration: '30-60 min', notes: 'IN (max 10 mg / naris) or PO (max 20 mg).' },
|
|
{ name: 'Propofol', display: 'Propofol', dose_mg_per_kg: 1, max_mg: 40, unit: 'mg', route: 'IV', onset: '30 sec', duration: '5-10 min', notes: 'Short procedures. Causes apnea — manage airway.' },
|
|
{ name: 'Fentanyl IV', display: 'Fentanyl', dose_mg_per_kg: 1, max_mg: 100, unit: 'mcg', route: 'IV', onset: '2-3 min', duration: '30-60 min', notes: 'Analgesic. Often combined with midazolam.' },
|
|
{ name: 'Fentanyl IN', display: '', dose_mg_per_kg: 2, max_mg: 100, unit: 'mcg', route: 'IN', onset: '5-10 min', duration: '30-60 min', notes: 'Intranasal.' },
|
|
{ name: 'Nitrous oxide', display: 'Nitrous oxide', dose_display: '50:50 or 70:30 mix', unit: 'mix', route: 'Inhaled',onset: '2-5 min', duration: '5 min off', notes: 'Self-administered via demand valve. Anxiolysis + mild analgesia.' },
|
|
{ name: 'Dexmedetomidine IN', display: 'Dexmedetomidine', dose_mg_per_kg_low: 2, dose_mg_per_kg_high: 3, max_mg_low: 100, max_mg_high: null, unit: 'mcg', route: 'IN', onset: '15-30 min', duration: '60-90 min', notes: 'Intranasal. No respiratory depression. Good for imaging.' },
|
|
{ name: 'Naloxone', display: 'Naloxone', dose_mg_per_kg: 0.1, max_mg: 2, unit: 'mg', route: 'IV/IM/IN', reverses: 'Opioids (fentanyl, morphine)', notes: 'Max 2 mg. Repeat q2-3 min. Duration shorter than opioids — monitor for re-sedation.' },
|
|
{ name: 'Flumazenil', display: 'Flumazenil', dose_mg_per_kg: 0.01, max_mg: 0.2, unit: 'mg', route: 'IV', reverses: 'Benzodiazepines (midazolam)', notes: 'Max 0.2 mg single dose. Repeat q1 min to max 1 mg total. Risk of seizures — use cautiously.' }
|
|
];
|
|
|
|
function sedDrugs() {
|
|
var s = window._DRUGS && window._DRUGS.sections && window._DRUGS.sections.sedation;
|
|
if (s && s.drugs && s.drugs.length) {
|
|
return s.drugs.map(function(dg) {
|
|
var fb = SED_FALLBACK.filter(function(x) { return x.name === dg.name; })[0] || {};
|
|
return Object.assign({}, fb, dg);
|
|
});
|
|
}
|
|
return SED_FALLBACK;
|
|
}
|
|
|
|
// perKg footer that matches the original inline format exactly.
|
|
function sedPerKg(lo, hi, unit) {
|
|
unit = unit || 'mg';
|
|
return ' <span style="color:var(--g500);font-size:11px;">(' + lo + (hi != null ? '-' + hi : '') + ' ' + unit + '/kg)</span>';
|
|
}
|
|
|
|
// Build the Dose cell text for a sedation drug (supports low/high range
|
|
// and single-dose entries; honors custom dose_display for fixed doses).
|
|
function sedDoseCell(wt, dg) {
|
|
if (dg.dose_display) return dg.dose_display;
|
|
var unitLabel = dg.unit === 'mcg' ? ' mcg' : (dg.unit === 'mix' ? '' : ' mg');
|
|
var perKgUnit = dg.unit === 'mcg' ? 'mcg' : 'mg';
|
|
if (dg.dose_mg_per_kg_low != null) {
|
|
var lo = d(wt, dg.dose_mg_per_kg_low, dg.max_mg_low);
|
|
if (dg.dose_mg_per_kg_high != null) {
|
|
var hi = d(wt, dg.dose_mg_per_kg_high, dg.max_mg_high);
|
|
return lo + '-' + hi + unitLabel + sedPerKg(dg.dose_mg_per_kg_low, dg.dose_mg_per_kg_high, perKgUnit);
|
|
}
|
|
return lo + unitLabel + sedPerKg(dg.dose_mg_per_kg_low, null, perKgUnit);
|
|
}
|
|
if (dg.dose_mg_per_kg != null) {
|
|
var v = d(wt, dg.dose_mg_per_kg, dg.max_mg);
|
|
return v + unitLabel + sedPerKg(dg.dose_mg_per_kg, null, perKgUnit);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function sedRow(wt, dg, showReverses) {
|
|
var displayName = dg.display == null ? dg.name : dg.display;
|
|
var nameCell = displayName
|
|
? '<td style="font-weight:600;">' + displayName + '</td>'
|
|
: '<td></td>';
|
|
var dose = sedDoseCell(wt, dg);
|
|
if (showReverses) {
|
|
return '<tr>' + nameCell + '<td>' + dose + '</td><td>' + dg.route + '</td><td>' + (dg.reverses || '') + '</td><td style="font-size:12px;color:var(--g500);">' + dg.notes + '</td></tr>';
|
|
}
|
|
return '<tr>' + nameCell + '<td>' + dose + '</td><td>' + dg.route + '</td><td>' + (dg.onset || '') + '</td><td>' + (dg.duration || '') + '</td><td style="font-size:12px;color:var(--g500);">' + dg.notes + '</td></tr>';
|
|
}
|
|
|
|
function calcSedation() {
|
|
var wt = parseFloat(document.getElementById('sed-weight').value);
|
|
var resultDiv = document.getElementById('sed-result');
|
|
if (!wt) { resultDiv.innerHTML = '<p style="color:var(--red);font-size:13px;">Enter weight (kg).</p>'; return; }
|
|
|
|
var all = sedDrugs();
|
|
var agents = all.filter(function(dg) { return !dg.reverses; });
|
|
var reversals = all.filter(function(dg) { return !!dg.reverses; });
|
|
|
|
var html = '<div style="padding:10px 14px;border-radius:8px;background:var(--purple-light);border:1.5px solid var(--purple);margin-bottom:12px;"><span style="font-size:14px;font-weight:700;color:var(--purple);">Procedural Sedation — ' + wt + ' kg patient</span></div>';
|
|
|
|
// Sedation agents
|
|
html += '<h4 style="font-size:14px;font-weight:700;color:var(--g800);margin:0 0 8px;">Sedation Agents</h4>';
|
|
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;margin-bottom:16px;"><table style="width:100%;min-width:640px;border-collapse:collapse;font-size:13px;"><thead><tr style="background:var(--g100);"><th style="text-align:left;padding:6px 8px;">Drug</th><th style="padding:6px 8px;">Dose</th><th style="padding:6px 8px;">Route</th><th style="padding:6px 8px;">Onset</th><th style="padding:6px 8px;">Duration</th><th style="padding:6px 8px;">Notes</th></tr></thead><tbody>';
|
|
html += agents.map(function(dg) { return sedRow(wt, dg, false); }).join('');
|
|
html += '</tbody></table></div>';
|
|
|
|
// Reversal agents
|
|
html += '<h4 style="font-size:14px;font-weight:700;color:var(--red);margin:0 0 8px;"><i class="fas fa-rotate-left"></i> Reversal Agents</h4>';
|
|
html += '<div style="overflow-x:auto;-webkit-overflow-scrolling:touch;"><table style="width:100%;min-width:560px;border-collapse:collapse;font-size:13px;"><thead><tr style="background:#fee2e2;"><th style="text-align:left;padding:6px 8px;">Drug</th><th style="padding:6px 8px;">Dose</th><th style="padding:6px 8px;">Route</th><th style="padding:6px 8px;">Reverses</th><th style="padding:6px 8px;">Notes</th></tr></thead><tbody>';
|
|
html += reversals.map(function(dg) { return sedRow(wt, dg, true); }).join('');
|
|
html += '</tbody></table></div>';
|
|
|
|
html += '<div style="margin-top:12px;padding:10px 12px;background:var(--amber-light);border-radius:6px;font-size:12px;color:#92400e;"><strong>Pre-sedation checklist:</strong> NPO status (2h clear liquids, 6h solids), consent, monitoring equipment (pulse ox, capnography, BP), resuscitation equipment at bedside, suction ready, IV access. Minimum monitoring: continuous SpO2, HR, capnography. Provider capable of managing airway must be present.</div>';
|
|
html += '<p style="font-size:11px;color:var(--g400);margin:8px 0 0;">AAP Guidelines for Monitoring and Management of Pediatric Patients Before, During, and After Sedation. ASA Practice Guidelines for Sedation and Analgesia by Non-Anesthesiologists.</p>';
|
|
resultDiv.innerHTML = html;
|
|
}
|
|
|
|
export function init() {
|
|
document.addEventListener('click', function(e) {
|
|
if (e.target.id === 'btn-sed-calc' || e.target.closest('#btn-sed-calc')) calcSedation();
|
|
});
|
|
}
|
|
|