import { applyWellVisitScheduleGlobals, loadWellVisitScheduleData } from './wellVisit/scheduleData.js';
// ============================================================
// 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 = {};
var _scheduleReady = null;
function ensureScheduleData() {
if (typeof VISIT_AGES !== 'undefined' && typeof PERIODICITY !== 'undefined') return Promise.resolve();
if (!_scheduleReady) {
_scheduleReady = loadWellVisitScheduleData().then(function(data) {
applyWellVisitScheduleGlobals(data, window);
});
}
return _scheduleReady;
}
function showScheduleLoadError(panelId, err) {
var panel = document.getElementById(panelId);
if (panel) {
panel.innerHTML = '
Unable to load well-visit schedule data. Please refresh and try again.
';
}
console.error('Failed to load well-visit schedule data', err);
}
// 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);
if (window.UIState) window.UIState.set('wv.subtab', btn.dataset.subtab);
});
});
// Restore persisted subtab so the user lands on the last-open pane.
if (window.UIState) {
var savedSubtab = window.UIState.get('wv.subtab');
if (savedSubtab) switchSubtab(savedSubtab);
}
// 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;
}
// ─── Map visit ID to REFLEXES_REFERENCE key ───────────────────────────────
function getReflexDataForVisit(visitId) {
if (typeof REFLEXES_REFERENCE === 'undefined') return null;
if (REFLEXES_REFERENCE[visitId]) return REFLEXES_REFERENCE[visitId];
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_to_5y', '4y': '3y_to_5y', '5y': '3y_to_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 ? REFLEXES_REFERENCE[key] : null;
}
// ─── Color a reflex status chip ───────────────────────────────────────────
function reflexStatusColor(status) {
var s = (status || '').toLowerCase();
if (s.indexOf('absent / integrated') !== -1 || s === 'absent' || s.indexOf('integrated') !== -1) return '#059669'; // green — expected integration
if (s.indexOf('present') !== -1 && s.indexOf('lifelong') !== -1) return '#059669';
if (s.indexOf('present') !== -1) return '#2563eb'; // blue — present and expected
if (s.indexOf('peak') !== -1) return '#2563eb';
if (s.indexOf('emerging') !== -1) return '#7c3aed'; // purple
if (s.indexOf('fading') !== -1) return '#d97706'; // amber — in transition
if (s.indexOf('transition') !== -1) return '#d97706';
if (s.indexOf('down-going') !== -1) return '#059669';
if (s.indexOf('up-going') !== -1) return '#d97706';
if (s.indexOf('2+') !== -1) return '#2563eb';
return '#475569';
}
// ─── 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 += '
';
growthData.feeding.forEach(function (f) {
html += '' + esc(f) + ' ';
});
html += ' ';
}
html += '
';
}
// ── Expected Reflexes ──
var reflexData = getReflexDataForVisit(visitId);
if (reflexData && reflexData.reflexes && reflexData.reflexes.length) {
html += '';
html += '
Expected Reflexes';
if (reflexData.intro) {
html += '
' + esc(reflexData.intro) + '
';
}
html += '
';
reflexData.reflexes.forEach(function (r) {
var color = reflexStatusColor(r.status);
html += '
';
html += '
';
html += '' + esc(r.name) + ' ';
html += '' + esc(r.status) + ' ';
html += '
';
html += '
' + esc(r.note) + '
';
html += '
';
});
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 += ' Copy to Visit Note ';
html += ' Clear This Visit ';
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 '' + s.label + ' ';
}).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 '' + s.label + ' ';
}).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 += '
Dose Min Age Min Interval from Prev Notes ';
v.series.forEach(function (s) {
html += '';
html += 'Dose ' + esc(String(s.dose)) + ' ';
html += '' + esc(s.minimumAge || '—') + ' ';
html += '' + esc(s.minimumIntervalToPrev || '—') + ' ';
html += '' + esc(s.notes || '') + ' ';
html += ' ';
});
html += '
';
}
if (v.catchUpNotes) {
var notes = Array.isArray(v.catchUpNotes) ? v.catchUpNotes : [v.catchUpNotes];
html += '
';
notes.forEach(function (n) {
html += '' + esc(n) + ' ';
});
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 += ' 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;
ensureScheduleData().then(init).catch(function(err) { showScheduleLoadError('wv-visit-detail', err); });
}
if (tab === 'vaxschedule' && !_vaxInited) {
_vaxInited = true;
ensureScheduleData().then(renderFullSchedule).catch(function(err) { showScheduleLoadError('wv-full-schedule', err); });
}
if (tab === 'catchup' && !_catchupInited) {
_catchupInited = true;
ensureScheduleData().then(renderCatchUp).catch(function(err) { showScheduleLoadError('wv-catchup', err); });
}
});
})();