// ============================================================ // 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)', }; // ─── Module-level state for visit statuses ──────────────────────────────── // keyed by visitId + '.' + itemKey — persisted to localStorage var _visitStatuses = {}; // SSHADESS is only relevant for 12+ year visits var SSHADESS_VISITS = ['12y','13y','14y','15y','16y','17y','18y','19y','20y','21y']; function saveStatusesToStorage() { try { localStorage.setItem('ped_visit_statuses', JSON.stringify(_visitStatuses)); } catch(e) {} } function clearCurrentVisit() { var visitId = document.getElementById('wv-visit-select').value; if (!visitId) return; Object.keys(_visitStatuses).forEach(function(k) { if (k.indexOf(visitId + '.') === 0) delete _visitStatuses[k]; }); saveStatusesToStorage(); renderVisitPanel(visitId); if (typeof showToast === 'function') showToast('Visit statuses cleared', 'info'); } // ─── Init ────────────────────────────────────────────────────────────────── function init() { // Restore statuses from localStorage try { var saved = localStorage.getItem('ped_visit_statuses'); if (saved) _visitStatuses = JSON.parse(saved) || {}; } catch(e) { _visitStatuses = {}; } 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); }); }); // Wire the visit-detail panel click/input ONCE here (not inside renderVisitPanel) // which would accumulate a new listener on every visit change — causing "can't click Done" var panel = document.getElementById('wv-visit-detail'); if (panel) { panel.addEventListener('click', function (e) { var visitId = document.getElementById('wv-visit-select').value; var btn = e.target.closest('.visit-status-btn'); if (btn) { handleVisitStatusClick(btn, visitId); saveStatusesToStorage(); } if (e.target.closest('#btn-wv-copy-to-note')) copyVisitStatusesToNote(visitId); if (e.target.closest('#btn-wv-clear-visit')) clearCurrentVisit(); }); panel.addEventListener('input', function (e) { var noteInput = e.target.closest('.visit-note-input'); if (noteInput) { var sk = noteInput.dataset.statusKey; if (!_visitStatuses[sk] || typeof _visitStatuses[sk] === 'string') { _visitStatuses[sk] = { status: _visitStatuses[sk] || '', note: '' }; } _visitStatuses[sk].note = noteInput.value; saveStatusesToStorage(); } }); } // 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; // Share selected visit age globally so other tabs can use it var sel = document.getElementById('wv-visit-select'); var selectedOption = sel ? sel.options[sel.selectedIndex] : null; window._wellVisitAge = selectedOption ? selectedOption.textContent : visitId; try { sessionStorage.setItem('ped_visit_age', window._wellVisitAge); } catch(e) {} renderVisitPanel(visitId); // Show SSHADESS subtab only for age 12+ visits var shadessBtn = document.querySelector('.wv-subtab-btn[data-subtab="shadess"]'); if (shadessBtn) { var showShadess = SSHADESS_VISITS.indexOf(visitId) !== -1; shadessBtn.style.display = showShadess ? '' : 'none'; // If SSHADESS was active but should now be hidden, fall back to byvisit if (!showShadess && shadessBtn.classList.contains('active')) { switchSubtab('byvisit'); } } // Milestones subtab is always shown (not age-gated) var milestonesBtn = document.querySelector('.wv-subtab-btn[data-subtab="milestones"]'); if (milestonesBtn) { milestonesBtn.style.display = ''; } } function switchSubtab(name) { document.querySelectorAll('.wv-subtab-btn').forEach(function (b) { b.classList.toggle('active', b.dataset.subtab === name); }); (document.getElementById('wellvisit-tab') || document).querySelectorAll('.wv-subpanel').forEach(function (p) { p.classList.toggle('hidden', p.id !== 'wv-panel-' + name); }); } window.wvSwitchSubtab = switchSubtab; // ─── Map visit ID to GROWTH_REFERENCE key ───────────────────────────────── function getGrowthDataForVisit(visitId) { if (typeof GROWTH_REFERENCE === 'undefined') return null; // Direct match if (GROWTH_REFERENCE[visitId]) return GROWTH_REFERENCE[visitId]; // Map visit IDs to growth reference keys var mapping = { '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' }; var key = mapping[visitId]; return key ? GROWTH_REFERENCE[key] : null; } // ─── 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 = '

No data for this visit.

'; return; } var html = ''; // ── Billing codes ── if (codes) { html += '
'; html += '

Billing Codes

'; html += '
'; html += '
ICD-10' + esc(codes.icd10) + '
'; html += '
CPT' + esc(codes.cpt) + '
'; html += '
' + esc(codes.description) + '
'; html += '
'; } // ── Measurements ── var measDue = Object.keys(data.measurements || {}).filter(function (k) { return data.measurements[k] === 'dot' || data.measurements[k] === 'range'; }); if (measDue.length) { html += '
'; html += '

Measurements

'; html += '
'; measDue.forEach(function (k) { var isRange = data.measurements[k] === 'range'; html += '' + esc(MEASURE_LABELS[k] || k) + (isRange ? ' (range)' : '') + ''; }); html += '
'; } // ── Vaccines ── if (data.vaccines && data.vaccines.length) { html += '
'; html += '

Vaccines Due

'; html += '
'; data.vaccines.forEach(function (v) { var fullName = VACCINE_FULL_NAMES[v.vaccine] || v.vaccine; var itemKey = 'vax_' + v.vaccine + '_d' + (v.dose || ''); var statusKey = visitId + '.' + itemKey; var curStatus = _visitStatuses[statusKey] || ''; html += '
'; html += '
' + esc(fullName) + '
'; if (v.dose) html += 'Dose ' + esc(String(v.dose)) + ''; if (v.notes) html += '
' + esc(v.notes) + '
'; html += '
'; html += renderVaxStatusBtns(statusKey, curStatus); html += '
'; html += '
'; }); html += '
'; } // ── Screenings: sensory ── var sensoryItems = buildStatusItems(data.sensory || {}, SENSORY_LABELS); if (sensoryItems.length) { html += '
'; html += '

Sensory Screens

'; html += renderScreenItems(sensoryItems, visitId); html += '
'; } // ── Screenings: developmental / behavioral ── var devItems = buildStatusItems(data.developmental || {}, SCREEN_LABELS); if (devItems.length) { html += '
'; html += '

Developmental / Behavioral Screens

'; html += renderScreenItems(devItems, visitId); html += '
'; } // ── Procedures ── var procItems = buildStatusItems(data.procedures || {}, PROC_LABELS, ['immunization']); if (procItems.length) { html += '
'; html += '

Labs & Procedures

'; html += renderScreenItems(procItems, visitId); html += '
'; } // ── Oral Health ── var oralItems = buildStatusItems(data.oralHealth || {}, ORAL_LABELS); if (oralItems.length) { html += '
'; html += '

Oral Health

'; html += renderScreenItems(oralItems, visitId); html += '
'; } // ── Growth Reference & Feeding Guidance ── var growthData = getGrowthDataForVisit(visitId); if (growthData) { html += '
'; html += '

Expected Growth

'; html += '
'; if (growthData.weight) html += '
⚖️ Weight' + esc(growthData.weight) + '
'; if (growthData.length) html += '
📏 Length/Height' + esc(growthData.length) + '
'; if (growthData.headCirc) html += '
🧠 Head Circumference' + esc(growthData.headCirc) + '
'; html += '
'; if (growthData.feeding && growthData.feeding.length) { html += '

Feeding & Nutrition Guidance

'; html += ''; } html += '
'; } // ── BMI / Weight Classification (ages 2+) ── if (growthData && growthData.bmiClassification && typeof BMI_CLASSIFICATION !== 'undefined') { html += '
'; html += '

BMI / Weight Classification (AAP 2023)

'; html += '
'; BMI_CLASSIFICATION.categories.forEach(function (cat) { html += '
'; html += '
' + esc(cat.label) + '
'; html += '
' + esc(cat.range) + '
'; html += '
' + esc(cat.action) + '
'; html += '
'; }); html += '
'; html += '
' + esc(BMI_CLASSIFICATION.notes) + '
'; html += '
'; } // ── Notes ── if (data.notes) { html += '
' + esc(data.notes) + '
'; } // ── Action buttons ── html += '
'; html += ''; html += ''; html += 'Copies vaccine + screening statuses to the Visit Note tab'; html += '
'; panel.innerHTML = html || '

No specific recommendations found for this visit.

'; // Note: click/input listeners are wired once in init() — NOT here } function renderVaxStatusBtns(statusKey, curStatus) { var statuses = [ { value: 'Given', label: 'Given', cls: 'given' }, { value: 'Refused', label: 'Refused', cls: 'refused' }, { value: 'Deferred', label: 'Deferred', cls: 'deferred' }, { value: 'Already Done', label: 'Already Done', cls: 'already-done' }, ]; return statuses.map(function (s) { var active = (typeof curStatus === 'object' ? curStatus.status : curStatus) === s.value; return ''; }).join(''); } function renderScreenStatusBtns(statusKey, curStatus) { var statuses = [ { value: 'Done', label: 'Done', cls: 'done' }, { value: 'Refused', label: 'Refused', cls: 'refused' }, { value: 'Not Due / N/A', label: 'Not Due / N/A', cls: 'not-due' }, ]; return statuses.map(function (s) { var active = (typeof curStatus === 'object' ? curStatus.status : curStatus) === s.value; return ''; }).join(''); } function handleVisitStatusClick(btn, visitId) { var statusKey = btn.dataset.statusKey; var statusVal = btn.dataset.statusVal; var btnCls = btn.dataset.btnCls; if (!statusKey) return; // Toggle: clicking same status deselects var cur = _visitStatuses[statusKey]; var curVal = typeof cur === 'object' ? (cur ? cur.status : '') : (cur || ''); var note = typeof cur === 'object' && cur ? (cur.note || '') : ''; if (curVal === statusVal) { _visitStatuses[statusKey] = { status: '', note: note }; } else { _visitStatuses[statusKey] = { status: statusVal, note: note }; } // Update button highlight in this row var container = btn.closest('[data-status-key]') || btn.parentElement; // Find all visit-status-btn siblings with same statusKey var parent = btn.parentElement; parent.querySelectorAll('.visit-status-btn').forEach(function (b) { b.className = 'visit-status-btn'; }); var newVal = _visitStatuses[statusKey].status; if (newVal) { parent.querySelectorAll('.visit-status-btn').forEach(function (b) { if (b.dataset.statusVal === newVal) { b.classList.add(b.dataset.btnCls); } }); } } function copyVisitStatusesToNote(visitId) { var lines = []; // Vaccines var vaxLines = []; Object.keys(_visitStatuses).forEach(function (k) { if (k.indexOf(visitId + '.vax_') === 0) { var itemEl = document.querySelector('[data-status-key="' + k + '"]'); var label = itemEl ? itemEl.dataset.itemLabel : k.replace(visitId + '.vax_', ''); var s = _visitStatuses[k]; var status = typeof s === 'object' ? s.status : s; var note = typeof s === 'object' ? (s.note || '') : ''; if (status) { vaxLines.push(label + ': ' + status + (note ? ' (' + note + ')' : '')); } } }); if (vaxLines.length) { lines.push('Vaccines:'); vaxLines.forEach(function (l) { lines.push(' - ' + l); }); } // Screenings var screenLines = []; Object.keys(_visitStatuses).forEach(function (k) { if (k.indexOf(visitId + '.') === 0 && k.indexOf(visitId + '.vax_') !== 0) { var itemEl = document.querySelector('[data-status-key="' + k + '"]'); var label = itemEl ? itemEl.dataset.itemLabel : k.replace(visitId + '.', ''); var s = _visitStatuses[k]; var status = typeof s === 'object' ? s.status : s; var note = typeof s === 'object' ? (s.note || '') : ''; if (status) { screenLines.push(label + ': ' + status + (note ? ' (' + note + ')' : '')); } } }); if (screenLines.length) { lines.push('Screenings:'); screenLines.forEach(function (l) { lines.push(' - ' + l); }); } if (!lines.length) { if (typeof showToast === 'function') showToast('No statuses selected yet. Click Done/Given/etc on each item first.', 'info'); return; } var text = lines.join('\n'); // Put into wv-vaccines and wv-screenings textareas var vaxEl = document.getElementById('wv-vaccines'); var screenEl = document.getElementById('wv-screenings'); if (vaxLines.length && vaxEl) { var existing = vaxEl.value.trim(); vaxEl.value = existing ? existing + '\n' + vaxLines.join('\n') : vaxLines.join('\n'); } if (screenLines.length && screenEl) { var existingS = screenEl.value.trim(); screenEl.value = existingS ? existingS + '\n' + screenLines.join('\n') : screenLines.join('\n'); } // Also copy SSHADESS assessment if present var shadessText = document.getElementById('shadess-result-text'); var wvShadessEl = document.getElementById('wv-shadess-text'); if (shadessText && wvShadessEl) { var shadessContent = (shadessText.innerText || shadessText.textContent || '').trim(); if (shadessContent) { var existingSh = wvShadessEl.value.trim(); wvShadessEl.value = existingSh ? existingSh + '\n\n' + shadessContent : shadessContent; } } if (typeof showToast === 'function') showToast('Visit statuses copied to Visit Note tab', 'success'); } 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, visitId) { var html = '
'; 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'; var statusKey = (visitId || 'visit') + '.' + item.key; var curStatus = _visitStatuses[statusKey] || {}; var curVal = typeof curStatus === 'object' ? (curStatus.status || '') : (curStatus || ''); var curNote = typeof curStatus === 'object' ? (curStatus.note || '') : ''; html += '
'; html += '' + statusText + ''; html += '' + esc(item.label) + ''; html += '
'; html += renderScreenStatusBtns(statusKey, curVal); html += ''; html += '
'; html += '
'; }); html += '
'; return html; } // ─── CATCH-UP panel ─────────────────────────────────────────────────────── function renderCatchUp() { var panel = document.getElementById('wv-panel-catchup'); if (!panel || typeof CATCH_UP_SCHEDULE === 'undefined') return; var html = '
CDC 2025 Catch-up Schedule — minimum ages and intervals for each vaccine series. Applies when a child is behind on routine immunizations.
'; Object.keys(CATCH_UP_SCHEDULE).forEach(function (vaxKey) { var v = CATCH_UP_SCHEDULE[vaxKey]; var fullName = VACCINE_FULL_NAMES[vaxKey] || vaxKey; html += '
'; html += '

' + esc(fullName) + '

'; if (v.minimumAgeForDose1) { html += '
Minimum age for dose 1: ' + esc(v.minimumAgeForDose1) + '
'; } if (v.series && v.series.length) { html += ''; v.series.forEach(function (s) { html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; }); html += '
DoseMin AgeMin Interval from PrevNotes
Dose ' + esc(String(s.dose)) + '' + esc(s.minimumAge || '—') + '' + esc(s.minimumIntervalToPrev || '—') + '' + esc(s.notes || '') + '
'; } if (v.catchUpNotes) { var notes = Array.isArray(v.catchUpNotes) ? v.catchUpNotes : [v.catchUpNotes]; html += ''; } html += '
'; }); 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 = '
'; html += ''; html += ''; visitLabels.forEach(function (v) { html += ''; }); html += ''; vaxKeys.forEach(function (vaxKey) { var fullName = VACCINE_FULL_NAMES[vaxKey] || vaxKey; html += ''; 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 += ''; } else { html += ''; } }); html += ''; }); html += '
Vaccine' + esc(v.label) + '
' + esc(fullName) + '' + esc(label) + '
'; html += '

Dose number shown. Hover cells for notes. "#N" = routine dose N. "annual" / "seasonal" = per CDC guidance. "catch-up" = as needed.

'; panel.innerHTML = html; } function esc(str) { if (!str) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } // ─── Wire up ─────────────────────────────────────────────────────────────── var _inited = false; var _vaxInited = false; var _catchupInited = false; document.addEventListener('tabChanged', function(e) { var tab = e.detail.tab; if (tab === 'wellvisit' && !_inited) { _inited = true; init(); } if (tab === 'vaxschedule' && !_vaxInited) { _vaxInited = true; renderFullSchedule(); } if (tab === 'catchup' && !_catchupInited) { _catchupInited = true; renderCatchUp(); } }); })();