366 lines
15 KiB
JavaScript
366 lines
15 KiB
JavaScript
// ============================================================
|
||
// WELL VISIT / PREVENTIVE CARE TAB
|
||
// ============================================================
|
||
|
||
(function () {
|
||
'use strict';
|
||
|
||
// ─── Human-readable labels ─────────────────────────────────────────────────
|
||
var SCREEN_LABELS = {
|
||
maternalDepression: 'Maternal/Caregiver Depression Screen (Edinburgh/PHQ)',
|
||
developmentalScreening: 'Developmental Screening (ASQ / PEDS)',
|
||
autismScreening: 'Autism Screening (M-CHAT-R)',
|
||
developmentalSurveillance:'Developmental Surveillance',
|
||
behavioralScreening: 'Social-Emotional/Behavioral Screening (ASQ:SE)',
|
||
tobaccoAlcoholDrugs: 'Tobacco / Alcohol / Drug Use Screening (CRAFFT/AUDIT)',
|
||
depressionSuicideRisk: 'Depression & Suicide Risk Screening (PHQ-A)',
|
||
};
|
||
|
||
var PROC_LABELS = {
|
||
newbornBlood: 'Newborn Blood Spot Screening (NBS)',
|
||
newbornBilirubin: 'Newborn Bilirubin (TcB or TSB)',
|
||
criticalCHD: 'Critical CHD Screening (Pulse Ox)',
|
||
immunization: 'Immunizations Review & Update',
|
||
anemia: 'Anemia Screening (Hgb/Hct)',
|
||
lead: 'Lead Exposure Risk / Blood Lead Level',
|
||
tuberculosis: 'Tuberculosis / Latent TB Risk Assessment',
|
||
dyslipidemia: 'Dyslipidemia Screening (lipid panel)',
|
||
sti: 'STI Screening (gonorrhea / chlamydia / syphilis)',
|
||
hiv: 'HIV Screening',
|
||
hepB: 'Hepatitis B Screening (HBsAg)',
|
||
hepC: 'Hepatitis C Screening (anti-HCV)',
|
||
suddenCardiacArrest:'Sudden Cardiac Arrest Risk Assessment',
|
||
cervicalDysplasia: 'Cervical Dysplasia Screening (Pap smear)',
|
||
};
|
||
|
||
var MEASURE_LABELS = {
|
||
lengthHeight: 'Length / Height',
|
||
weight: 'Weight',
|
||
headCircumference: 'Head Circumference',
|
||
weightForLength: 'Weight-for-Length',
|
||
bmi: 'BMI',
|
||
bloodPressure: 'Blood Pressure',
|
||
};
|
||
|
||
var ORAL_LABELS = {
|
||
assessment: 'Oral Health Risk Assessment',
|
||
fluorideVarnish: 'Fluoride Varnish Application',
|
||
fluorideSupplementation:'Fluoride Supplementation (if water <0.6 ppm)',
|
||
};
|
||
|
||
var SENSORY_LABELS = {
|
||
vision: 'Vision Screening',
|
||
hearing: 'Hearing Screening',
|
||
};
|
||
|
||
var VACCINE_FULL_NAMES = {
|
||
HepB: 'Hepatitis B (HepB)',
|
||
RV: 'Rotavirus (RV)',
|
||
DTaP: 'DTaP (Diphtheria, Tetanus, Pertussis)',
|
||
Hib: 'Hib (Haemophilus influenzae type b)',
|
||
PCV: 'Pneumococcal (PCV)',
|
||
IPV: 'Polio (IPV)',
|
||
Flu_IIV: 'Influenza (IIV)',
|
||
MMR: 'MMR (Measles, Mumps, Rubella)',
|
||
VAR: 'Varicella (VAR)',
|
||
HepA: 'Hepatitis A (HepA)',
|
||
Tdap: 'Tdap (Tetanus, Diphtheria, Pertussis booster)',
|
||
HPV: 'HPV (Human Papillomavirus)',
|
||
MenACWY: 'Meningococcal ACWY (MenACWY)',
|
||
MenB: 'Meningococcal B (MenB)',
|
||
RSV_mAb: 'RSV Monoclonal Antibody (Nirsevimab/Beyfortus)',
|
||
COVID: 'COVID-19',
|
||
Dengue: 'Dengue (DEN4CYD / Dengvaxia)',
|
||
Mpox: 'Mpox (JYNNEOS)',
|
||
};
|
||
|
||
// ─── Init ──────────────────────────────────────────────────────────────────
|
||
function init() {
|
||
populateVisitSelect();
|
||
renderCatchUp();
|
||
renderFullSchedule();
|
||
|
||
document.getElementById('wv-visit-select').addEventListener('change', onVisitChange);
|
||
document.querySelectorAll('.wv-subtab-btn').forEach(function (btn) {
|
||
btn.addEventListener('click', function () { switchSubtab(btn.dataset.subtab); });
|
||
});
|
||
|
||
// Show first real visit by default
|
||
var sel = document.getElementById('wv-visit-select');
|
||
if (sel && sel.value) onVisitChange();
|
||
}
|
||
|
||
function populateVisitSelect() {
|
||
var sel = document.getElementById('wv-visit-select');
|
||
if (!sel || typeof VISIT_AGES === 'undefined') return;
|
||
var grouped = {};
|
||
VISIT_AGES.forEach(function (v) {
|
||
if (!grouped[v.era]) grouped[v.era] = [];
|
||
grouped[v.era].push(v);
|
||
});
|
||
var eraNames = {
|
||
prenatal: 'Prenatal',
|
||
infancy: 'Infancy (0–12 mo)',
|
||
earlyChildhood: 'Early Childhood (1–5 y)',
|
||
middleChildhood: 'Middle Childhood (6–11 y)',
|
||
adolescence: 'Adolescence (11–21 y)',
|
||
};
|
||
Object.keys(grouped).forEach(function (era) {
|
||
var og = document.createElement('optgroup');
|
||
og.label = eraNames[era] || era;
|
||
grouped[era].forEach(function (v) {
|
||
var opt = document.createElement('option');
|
||
opt.value = v.id;
|
||
opt.textContent = v.label;
|
||
og.appendChild(opt);
|
||
});
|
||
sel.appendChild(og);
|
||
});
|
||
// select newborn by default
|
||
sel.value = 'newborn';
|
||
}
|
||
|
||
function onVisitChange() {
|
||
var visitId = document.getElementById('wv-visit-select').value;
|
||
if (!visitId) return;
|
||
renderVisitPanel(visitId);
|
||
}
|
||
|
||
function switchSubtab(name) {
|
||
document.querySelectorAll('.wv-subtab-btn').forEach(function (b) {
|
||
b.classList.toggle('active', b.dataset.subtab === name);
|
||
});
|
||
document.querySelectorAll('.wv-subpanel').forEach(function (p) {
|
||
p.classList.toggle('hidden', p.id !== 'wv-panel-' + name);
|
||
});
|
||
}
|
||
|
||
// ─── BY VISIT panel ───────────────────────────────────────────────────────
|
||
function renderVisitPanel(visitId) {
|
||
var data = (typeof PERIODICITY !== 'undefined') ? PERIODICITY[visitId] : null;
|
||
var codes = (typeof WELL_VISIT_CODES !== 'undefined') ? WELL_VISIT_CODES[visitId] : null;
|
||
var panel = document.getElementById('wv-visit-detail');
|
||
if (!panel) return;
|
||
|
||
if (!data) {
|
||
panel.innerHTML = '<p class="wv-empty">No data for this visit.</p>';
|
||
return;
|
||
}
|
||
|
||
var html = '';
|
||
|
||
// ── Billing codes ──
|
||
if (codes) {
|
||
html += '<div class="wv-section wv-billing">';
|
||
html += '<h4 class="wv-section-title"><i class="fas fa-receipt"></i> Billing Codes</h4>';
|
||
html += '<div class="wv-billing-grid">';
|
||
html += '<div class="wv-billing-cell"><span class="wv-label">ICD-10</span><span class="wv-code-chip icd">' + esc(codes.icd10) + '</span></div>';
|
||
html += '<div class="wv-billing-cell"><span class="wv-label">CPT</span><span class="wv-code-chip cpt">' + esc(codes.cpt) + '</span></div>';
|
||
html += '<div class="wv-billing-desc">' + esc(codes.description) + '</div>';
|
||
html += '</div></div>';
|
||
}
|
||
|
||
// ── Measurements ──
|
||
var measDue = Object.keys(data.measurements || {}).filter(function (k) {
|
||
return data.measurements[k] === 'dot' || data.measurements[k] === 'range';
|
||
});
|
||
if (measDue.length) {
|
||
html += '<div class="wv-section">';
|
||
html += '<h4 class="wv-section-title"><i class="fas fa-ruler"></i> Measurements</h4>';
|
||
html += '<div class="wv-chip-list">';
|
||
measDue.forEach(function (k) {
|
||
var isRange = data.measurements[k] === 'range';
|
||
html += '<span class="wv-chip wv-chip-measure' + (isRange ? ' wv-chip-range' : '') + '">' + esc(MEASURE_LABELS[k] || k) + (isRange ? ' <em>(range)</em>' : '') + '</span>';
|
||
});
|
||
html += '</div></div>';
|
||
}
|
||
|
||
// ── Vaccines ──
|
||
if (data.vaccines && data.vaccines.length) {
|
||
html += '<div class="wv-section">';
|
||
html += '<h4 class="wv-section-title"><i class="fas fa-syringe"></i> Vaccines Due</h4>';
|
||
html += '<div class="wv-vax-list">';
|
||
data.vaccines.forEach(function (v) {
|
||
var fullName = VACCINE_FULL_NAMES[v.vaccine] || v.vaccine;
|
||
html += '<div class="wv-vax-item">';
|
||
html += '<div class="wv-vax-name"><i class="fas fa-circle-dot wv-vax-dot"></i>' + esc(fullName) + '</div>';
|
||
if (v.dose) html += '<span class="wv-vax-dose">Dose ' + esc(String(v.dose)) + '</span>';
|
||
if (v.notes) html += '<div class="wv-vax-note"><i class="fas fa-circle-info"></i> ' + esc(v.notes) + '</div>';
|
||
html += '</div>';
|
||
});
|
||
html += '</div></div>';
|
||
}
|
||
|
||
// ── Screenings: sensory ──
|
||
var sensoryItems = buildStatusItems(data.sensory || {}, SENSORY_LABELS);
|
||
if (sensoryItems.length) {
|
||
html += '<div class="wv-section">';
|
||
html += '<h4 class="wv-section-title"><i class="fas fa-eye"></i> Sensory Screens</h4>';
|
||
html += renderScreenItems(sensoryItems);
|
||
html += '</div>';
|
||
}
|
||
|
||
// ── Screenings: developmental / behavioral ──
|
||
var devItems = buildStatusItems(data.developmental || {}, SCREEN_LABELS);
|
||
if (devItems.length) {
|
||
html += '<div class="wv-section">';
|
||
html += '<h4 class="wv-section-title"><i class="fas fa-brain"></i> Developmental / Behavioral Screens</h4>';
|
||
html += renderScreenItems(devItems);
|
||
html += '</div>';
|
||
}
|
||
|
||
// ── Procedures ──
|
||
var procItems = buildStatusItems(data.procedures || {}, PROC_LABELS, ['immunization']);
|
||
if (procItems.length) {
|
||
html += '<div class="wv-section">';
|
||
html += '<h4 class="wv-section-title"><i class="fas fa-flask"></i> Labs & Procedures</h4>';
|
||
html += renderScreenItems(procItems);
|
||
html += '</div>';
|
||
}
|
||
|
||
// ── Oral Health ──
|
||
var oralItems = buildStatusItems(data.oralHealth || {}, ORAL_LABELS);
|
||
if (oralItems.length) {
|
||
html += '<div class="wv-section">';
|
||
html += '<h4 class="wv-section-title"><i class="fas fa-tooth"></i> Oral Health</h4>';
|
||
html += renderScreenItems(oralItems);
|
||
html += '</div>';
|
||
}
|
||
|
||
// ── Notes ──
|
||
if (data.notes) {
|
||
html += '<div class="wv-section wv-notes-box"><i class="fas fa-circle-info"></i> ' + esc(data.notes) + '</div>';
|
||
}
|
||
|
||
panel.innerHTML = html || '<p class="wv-empty">No specific recommendations found for this visit.</p>';
|
||
}
|
||
|
||
function buildStatusItems(obj, labels, exclude) {
|
||
exclude = exclude || [];
|
||
return Object.keys(obj).filter(function (k) {
|
||
return obj[k] && obj[k] !== '' && exclude.indexOf(k) === -1;
|
||
}).map(function (k) {
|
||
return { key: k, status: obj[k], label: labels[k] || k };
|
||
});
|
||
}
|
||
|
||
function renderScreenItems(items) {
|
||
var html = '<div class="wv-screen-list">';
|
||
items.forEach(function (item) {
|
||
var statusClass = item.status === 'dot' ? 'wv-status-dot' : item.status === 'range' ? 'wv-status-range' : 'wv-status-risk';
|
||
var statusText = item.status === 'dot' ? 'Recommended' : item.status === 'range' ? 'In Range' : 'If at Risk';
|
||
html += '<div class="wv-screen-item">';
|
||
html += '<span class="wv-status-badge ' + statusClass + '">' + statusText + '</span>';
|
||
html += '<span class="wv-screen-name">' + esc(item.label) + '</span>';
|
||
html += '</div>';
|
||
});
|
||
html += '</div>';
|
||
return html;
|
||
}
|
||
|
||
// ─── CATCH-UP panel ───────────────────────────────────────────────────────
|
||
function renderCatchUp() {
|
||
var panel = document.getElementById('wv-panel-catchup');
|
||
if (!panel || typeof CATCH_UP_SCHEDULE === 'undefined') return;
|
||
|
||
var html = '<div class="wv-catchup-intro"><i class="fas fa-circle-info"></i> CDC 2025 Catch-up Schedule — minimum ages and intervals for each vaccine series. Applies when a child is behind on routine immunizations.</div>';
|
||
|
||
Object.keys(CATCH_UP_SCHEDULE).forEach(function (vaxKey) {
|
||
var v = CATCH_UP_SCHEDULE[vaxKey];
|
||
var fullName = VACCINE_FULL_NAMES[vaxKey] || vaxKey;
|
||
html += '<div class="wv-catchup-card">';
|
||
html += '<h4 class="wv-catchup-title"><i class="fas fa-syringe"></i> ' + esc(fullName) + '</h4>';
|
||
if (v.minimumAgeForDose1) {
|
||
html += '<div class="wv-catchup-min-age">Minimum age for dose 1: <strong>' + esc(v.minimumAgeForDose1) + '</strong></div>';
|
||
}
|
||
|
||
if (v.series && v.series.length) {
|
||
html += '<table class="wv-catchup-table"><thead><tr><th>Dose</th><th>Min Age</th><th>Min Interval from Prev</th><th>Notes</th></tr></thead><tbody>';
|
||
v.series.forEach(function (s) {
|
||
html += '<tr>';
|
||
html += '<td><strong>Dose ' + esc(String(s.dose)) + '</strong></td>';
|
||
html += '<td>' + esc(s.minimumAge || '—') + '</td>';
|
||
html += '<td>' + esc(s.minimumIntervalToPrev || '—') + '</td>';
|
||
html += '<td>' + esc(s.notes || '') + '</td>';
|
||
html += '</tr>';
|
||
});
|
||
html += '</tbody></table>';
|
||
}
|
||
|
||
if (v.catchUpNotes) {
|
||
var notes = Array.isArray(v.catchUpNotes) ? v.catchUpNotes : [v.catchUpNotes];
|
||
html += '<ul class="wv-catchup-notes">';
|
||
notes.forEach(function (n) {
|
||
html += '<li>' + esc(n) + '</li>';
|
||
});
|
||
html += '</ul>';
|
||
}
|
||
html += '</div>';
|
||
});
|
||
|
||
panel.innerHTML = html;
|
||
}
|
||
|
||
// ─── FULL SCHEDULE table ──────────────────────────────────────────────────
|
||
function renderFullSchedule() {
|
||
var panel = document.getElementById('wv-panel-schedule');
|
||
if (!panel || typeof PERIODICITY === 'undefined' || typeof VISIT_AGES === 'undefined') return;
|
||
|
||
// Collect all vaccine keys that appear in any visit
|
||
var allVaxKeys = {};
|
||
var visitIds = VISIT_AGES.map(function (v) { return v.id; }).filter(function (id) {
|
||
return PERIODICITY[id] && PERIODICITY[id].vaccines;
|
||
});
|
||
|
||
visitIds.forEach(function (id) {
|
||
PERIODICITY[id].vaccines.forEach(function (v) { allVaxKeys[v.vaccine] = true; });
|
||
});
|
||
|
||
var vaxKeys = Object.keys(allVaxKeys);
|
||
var visitLabels = VISIT_AGES.filter(function (v) { return visitIds.indexOf(v.id) !== -1; });
|
||
|
||
var html = '<div class="wv-schedule-scroll">';
|
||
html += '<table class="wv-schedule-table">';
|
||
html += '<thead><tr><th class="wv-sched-vax-col">Vaccine</th>';
|
||
visitLabels.forEach(function (v) {
|
||
html += '<th class="wv-sched-age-col">' + esc(v.label) + '</th>';
|
||
});
|
||
html += '</tr></thead><tbody>';
|
||
|
||
vaxKeys.forEach(function (vaxKey) {
|
||
var fullName = VACCINE_FULL_NAMES[vaxKey] || vaxKey;
|
||
html += '<tr><td class="wv-sched-vax-name">' + esc(fullName) + '</td>';
|
||
visitLabels.forEach(function (visit) {
|
||
var vaxData = PERIODICITY[visit.id] && PERIODICITY[visit.id].vaccines;
|
||
var match = vaxData && vaxData.find(function (v) { return v.vaccine === vaxKey; });
|
||
if (match) {
|
||
var dose = match.dose ? String(match.dose) : '•';
|
||
var label = (typeof match.dose === 'number') ? '#' + dose : dose;
|
||
html += '<td class="wv-sched-cell wv-sched-has" title="' + esc(match.notes || fullName + ' dose ' + dose) + '">' + esc(label) + '</td>';
|
||
} else {
|
||
html += '<td class="wv-sched-cell"></td>';
|
||
}
|
||
});
|
||
html += '</tr>';
|
||
});
|
||
|
||
html += '</tbody></table></div>';
|
||
html += '<p class="wv-sched-legend"><span class="wv-sched-leg-dot"></span> Dose number shown. Hover cells for notes. "#N" = routine dose N. "annual" / "seasonal" = per CDC guidance. "catch-up" = as needed.</p>';
|
||
panel.innerHTML = html;
|
||
}
|
||
|
||
function esc(str) {
|
||
if (!str) return '';
|
||
return String(str)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
// ─── Wire up ───────────────────────────────────────────────────────────────
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
init();
|
||
});
|
||
|
||
})();
|