All JS modules were running immediately (IIFE/DOMContentLoaded) but tab HTML is lazy-loaded from /components/*.html, causing null errors on every addEventListener call. Each module now listens for tabChanged with its tab name and initializes once after the DOM is ready. Also: quiz question/explanation boxes changed to textarea for multi-line support; quiz display uses sanitizeHtml() so formatted text (bold, lists, etc.) renders correctly.
189 lines
8.2 KiB
JavaScript
189 lines
8.2 KiB
JavaScript
(function() {
|
|
var _inited = false;
|
|
document.addEventListener('tabChanged', function(e) {
|
|
if (e.detail.tab !== 'wellvisit' || _inited) return;
|
|
_inited = true;
|
|
var ageSelect = document.getElementById('ms-age-group');
|
|
var checklist = document.getElementById('milestone-checklist');
|
|
var actionsBar = document.getElementById('milestone-actions');
|
|
var allYesBtn = document.getElementById('ms-all-yes');
|
|
var allClearBtn = document.getElementById('ms-all-clear');
|
|
var generateBtn = document.getElementById('ms-generate-btn');
|
|
var outputCard = document.getElementById('ms-output');
|
|
var summaryBar = document.getElementById('ms-summary-bar');
|
|
var narrativeText = document.getElementById('ms-narrative-text');
|
|
var modelTag = document.getElementById('ms-model-tag');
|
|
|
|
var state = {};
|
|
|
|
ageSelect.addEventListener('change', function() {
|
|
var age = ageSelect.value;
|
|
state = {};
|
|
outputCard.classList.add('hidden');
|
|
if (!age || !MILESTONES_DATA[age]) {
|
|
checklist.innerHTML = '<p style="text-align:center;color:#9ca3af;padding:40px;">Select an age group.</p>';
|
|
actionsBar.style.display = 'none';
|
|
return;
|
|
}
|
|
renderChecklist(MILESTONES_DATA[age]);
|
|
actionsBar.style.display = 'flex';
|
|
});
|
|
|
|
function safeId(s) { return s.replace(/[^a-zA-Z0-9]/g, '_'); }
|
|
|
|
function renderChecklist(data) {
|
|
checklist.innerHTML = '';
|
|
Object.keys(data).forEach(function(domain) {
|
|
var items = data[domain];
|
|
var config = DOMAIN_CONFIG[domain] || { icon: '📋', css: '' };
|
|
var section = document.createElement('div');
|
|
section.className = 'domain-section ' + config.css;
|
|
|
|
var html = '<div class="domain-header"><span>' + config.icon + '</span><span>' + domain +
|
|
'</span><span class="badge" id="badge-' + safeId(domain) + '">' + items.length + ' items</span></div><div class="ms-items">';
|
|
|
|
items.forEach(function(m, idx) {
|
|
var id = domain + '-' + idx;
|
|
state[id] = { domain: domain, milestone: m, status: null };
|
|
html += '<div class="ms-item" id="item-' + safeId(id) + '">' +
|
|
'<div class="ms-toggle">' +
|
|
'<button class="toggle-btn" data-id="' + id + '" data-action="yes">✓</button>' +
|
|
'<button class="toggle-btn" data-id="' + id + '" data-action="no">✗</button>' +
|
|
'</div><span class="ms-text">' + m + '</span></div>';
|
|
});
|
|
|
|
section.innerHTML = html + '</div>';
|
|
checklist.appendChild(section);
|
|
});
|
|
|
|
checklist.querySelectorAll('.toggle-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() { handleToggle(btn.getAttribute('data-id'), btn.getAttribute('data-action')); });
|
|
});
|
|
}
|
|
|
|
function handleToggle(id, action) {
|
|
var sid = safeId(id);
|
|
var item = document.getElementById('item-' + sid);
|
|
var yesBtn = item.querySelector('[data-action="yes"]');
|
|
var noBtn = item.querySelector('[data-action="no"]');
|
|
|
|
if (action === 'yes') {
|
|
if (state[id].status === 'yes') { state[id].status = null; yesBtn.classList.remove('yes-on'); item.classList.remove('is-yes','is-no'); }
|
|
else { state[id].status = 'yes'; yesBtn.classList.add('yes-on'); noBtn.classList.remove('no-on'); item.classList.add('is-yes'); item.classList.remove('is-no'); }
|
|
} else {
|
|
if (state[id].status === 'no') { state[id].status = null; noBtn.classList.remove('no-on'); item.classList.remove('is-yes','is-no'); }
|
|
else { state[id].status = 'no'; noBtn.classList.add('no-on'); yesBtn.classList.remove('yes-on'); item.classList.remove('is-yes'); item.classList.add('is-no'); }
|
|
}
|
|
updateBadges();
|
|
}
|
|
|
|
function updateBadges() {
|
|
var counts = {};
|
|
Object.keys(state).forEach(function(id) {
|
|
var d = state[id].domain;
|
|
if (!counts[d]) counts[d] = { total: 0, yes: 0, no: 0 };
|
|
counts[d].total++;
|
|
if (state[id].status === 'yes') counts[d].yes++;
|
|
if (state[id].status === 'no') counts[d].no++;
|
|
});
|
|
Object.keys(counts).forEach(function(d) {
|
|
var b = document.getElementById('badge-' + safeId(d));
|
|
if (b) { var c = counts[d]; b.textContent = (c.yes + c.no) > 0 ? c.yes + '✓ ' + c.no + '✗ / ' + c.total : c.total + ' items'; }
|
|
});
|
|
}
|
|
|
|
allYesBtn.addEventListener('click', function() {
|
|
Object.keys(state).forEach(function(id) {
|
|
state[id].status = 'yes';
|
|
var item = document.getElementById('item-' + safeId(id));
|
|
if (item) { item.querySelector('[data-action="yes"]').classList.add('yes-on'); item.querySelector('[data-action="no"]').classList.remove('no-on'); item.classList.add('is-yes'); item.classList.remove('is-no'); }
|
|
});
|
|
updateBadges();
|
|
});
|
|
|
|
allClearBtn.addEventListener('click', function() {
|
|
Object.keys(state).forEach(function(id) {
|
|
state[id].status = null;
|
|
var item = document.getElementById('item-' + safeId(id));
|
|
if (item) { item.querySelector('[data-action="yes"]').classList.remove('yes-on'); item.querySelector('[data-action="no"]').classList.remove('no-on'); item.classList.remove('is-yes','is-no'); }
|
|
});
|
|
updateBadges();
|
|
outputCard.classList.add('hidden');
|
|
});
|
|
|
|
generateBtn.addEventListener('click', function() {
|
|
if (!ageSelect.value) { showToast('Select age group', 'error'); return; }
|
|
var assessed = Object.values(state).filter(function(m) { return m.status !== null; });
|
|
if (assessed.length === 0) { showToast('Assess at least one milestone', 'error'); return; }
|
|
|
|
showLoading('Generating...');
|
|
|
|
fetch('/api/generate-milestone-narrative', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({
|
|
milestones: Object.values(state),
|
|
ageGroup: ageSelect.value,
|
|
patientAge: document.getElementById('ms-age').value,
|
|
patientGender: document.getElementById('ms-gender').value,
|
|
format: document.getElementById('ms-format').value,
|
|
model: getSelectedModel()
|
|
})
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
hideLoading();
|
|
if (data.success) {
|
|
setOutputText(narrativeText, data.narrative);
|
|
modelTag.textContent = (data.model || '').split('/').pop();
|
|
if (data.summary) {
|
|
summaryBar.innerHTML = '<div class="stat"><span class="dot dot-green"></span> Achieved: ' + data.summary.achieved + '</div>' +
|
|
'<div class="stat"><span class="dot dot-red"></span> Not Yet: ' + data.summary.notAchieved + '</div>' +
|
|
'<div class="stat"><span class="dot dot-gray"></span> Not Assessed: ' + data.summary.notAssessed + ' (omitted)</div>';
|
|
summaryBar.classList.remove('hidden');
|
|
}
|
|
outputCard.classList.remove('hidden');
|
|
document.getElementById('ms-quick-summary').classList.add('hidden');
|
|
outputCard.scrollIntoView({ behavior: 'smooth' });
|
|
showToast('Generated!', 'success');
|
|
} else showToast(data.error || 'Failed', 'error');
|
|
})
|
|
.catch(function(err) { hideLoading(); showToast(err.message, 'error'); });
|
|
});
|
|
|
|
// 3-sentence summary
|
|
document.getElementById('ms-quick-summary-btn').addEventListener('click', function() {
|
|
var narrative = narrativeText.innerText.trim();
|
|
if (!narrative) { showToast('Generate narrative first', 'error'); return; }
|
|
|
|
var btn = document.getElementById('ms-quick-summary-btn');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Summarizing...';
|
|
|
|
fetch('/api/generate-milestone-summary', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({
|
|
narrative: narrative,
|
|
ageGroup: ageSelect.value,
|
|
patientAge: document.getElementById('ms-age').value,
|
|
patientGender: document.getElementById('ms-gender').value,
|
|
model: getSelectedModel()
|
|
})
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="fas fa-compress"></i> Regenerate Summary';
|
|
if (data.success) {
|
|
setOutputText('ms-summary-text', data.summary);
|
|
document.getElementById('ms-quick-summary').classList.remove('hidden');
|
|
showToast('Summary generated!', 'success');
|
|
}
|
|
})
|
|
.catch(function(err) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-compress"></i> 3-Sentence Summary'; showToast(err.message, 'error'); });
|
|
});
|
|
|
|
console.log('✅ Milestones module loaded');
|
|
});
|
|
})();
|