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.
210 lines
8.6 KiB
JavaScript
210 lines
8.6 KiB
JavaScript
(function() {
|
|
var _inited = false;
|
|
document.addEventListener('tabChanged', function(e) {
|
|
if (e.detail.tab !== 'hospital' || _inited) return;
|
|
_inited = true;
|
|
var notesContainer = document.getElementById('hc-notes-container');
|
|
var addNoteBtn = document.getElementById('hc-add-note');
|
|
var labsContainer = document.getElementById('hc-labs-container');
|
|
var addLabBtn = document.getElementById('hc-add-lab');
|
|
var generateBtn = document.getElementById('hc-generate-btn');
|
|
var outputCard = document.getElementById('hc-output');
|
|
var courseText = document.getElementById('hc-course-text');
|
|
var formatTag = document.getElementById('hc-format-tag');
|
|
var modelTag = document.getElementById('hc-model-tag');
|
|
var clarifyOutput = document.getElementById('hc-clarify-output');
|
|
|
|
var noteIndex = 1;
|
|
|
|
// Add progress note
|
|
addNoteBtn.addEventListener('click', function() {
|
|
noteIndex++;
|
|
var card = document.createElement('div');
|
|
card.className = 'card note-card';
|
|
card.innerHTML = '<div class="card-header"><h3><i class="fas fa-notes-medical"></i> Progress Note #' + noteIndex + '</h3>' +
|
|
'<button class="btn-sm btn-ghost remove-note-btn"><i class="fas fa-trash"></i></button></div>' +
|
|
'<div class="note-entry"><div class="note-meta">' +
|
|
'<input type="date" class="note-date">' +
|
|
'<select class="note-type"><option value="progress-attending">Progress - Attending</option><option value="progress-resident">Progress - Resident</option><option value="progress-np">Progress - NP/PA</option><option value="consult">Consult Note</option><option value="procedure">Procedure Note</option><option value="discharge-summary">Discharge Summary</option></select></div>' +
|
|
'<div class="editable-box note-content" contenteditable="true" data-placeholder="Paste or type note..."></div>' +
|
|
'<div class="note-dictate"><button class="btn-sm btn-ghost hc-dictate-btn"><i class="fas fa-microphone"></i> Dictate</button></div></div>';
|
|
notesContainer.appendChild(card);
|
|
|
|
card.querySelector('.remove-note-btn').addEventListener('click', function() { card.remove(); });
|
|
setupDictateButton(card.querySelector('.hc-dictate-btn'), card.querySelector('.note-content'));
|
|
});
|
|
|
|
// Remove note buttons for initial note
|
|
document.querySelectorAll('.remove-note-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() { btn.closest('.note-card').remove(); });
|
|
});
|
|
|
|
// Add lab
|
|
addLabBtn.addEventListener('click', function() {
|
|
var entry = document.createElement('div');
|
|
entry.className = 'lab-entry';
|
|
entry.innerHTML = '<input type="date" class="lab-date"><textarea class="lab-values" placeholder="Lab results..." rows="3"></textarea>' +
|
|
'<button class="btn-sm btn-ghost" onclick="this.parentElement.remove()"><i class="fas fa-trash"></i></button>';
|
|
labsContainer.appendChild(entry);
|
|
});
|
|
|
|
// Dictate button helper
|
|
function setupDictateButton(btn, targetEl) {
|
|
var rec = new AudioRecorder();
|
|
var isRec = false;
|
|
|
|
btn.addEventListener('click', function() {
|
|
if (!isRec) {
|
|
rec.start().then(function() {
|
|
isRec = true;
|
|
btn.innerHTML = '<i class="fas fa-stop"></i> Stop';
|
|
btn.classList.add('btn-recording');
|
|
});
|
|
} else {
|
|
isRec = false;
|
|
btn.innerHTML = '<i class="fas fa-microphone"></i> Dictate';
|
|
btn.classList.remove('btn-recording');
|
|
|
|
showLoading('Transcribing...');
|
|
rec.stop().then(function(blob) {
|
|
if (!blob) { hideLoading(); return; }
|
|
return transcribeAudio(blob).then(function(data) {
|
|
hideLoading();
|
|
if (data.success) {
|
|
var existing = targetEl.innerText.trim();
|
|
targetEl.textContent = existing ? existing + '\n' + data.text : data.text;
|
|
showToast('Transcribed!', 'success');
|
|
}
|
|
});
|
|
}).catch(function() { hideLoading(); });
|
|
}
|
|
});
|
|
}
|
|
|
|
// Setup dictate buttons for initial elements
|
|
document.querySelectorAll('.hc-dictate-btn').forEach(function(btn) {
|
|
var targetId = btn.getAttribute('data-target');
|
|
if (targetId) {
|
|
setupDictateButton(btn, document.getElementById(targetId));
|
|
} else {
|
|
var card = btn.closest('.note-card');
|
|
if (card) setupDictateButton(btn, card.querySelector('.note-content'));
|
|
}
|
|
});
|
|
|
|
// Collect all data and generate
|
|
generateBtn.addEventListener('click', function() {
|
|
var notes = [];
|
|
notesContainer.querySelectorAll('.note-card').forEach(function(card) {
|
|
var content = card.querySelector('.note-content').innerText.trim();
|
|
if (content) {
|
|
notes.push({
|
|
date: card.querySelector('.note-date').value || '',
|
|
type: card.querySelector('.note-type').value || 'progress-attending',
|
|
content: content
|
|
});
|
|
}
|
|
});
|
|
|
|
var edContent = document.getElementById('hc-ed-content').innerText.trim();
|
|
var edNote = edContent ? {
|
|
date: document.getElementById('hc-ed-date').value || '',
|
|
content: edContent,
|
|
labs: document.getElementById('hc-ed-labs').value || ''
|
|
} : null;
|
|
|
|
var hpContent = document.getElementById('hc-hp-content').innerText.trim();
|
|
var hAndP = hpContent ? {
|
|
date: document.getElementById('hc-hp-date').value || '',
|
|
content: hpContent
|
|
} : null;
|
|
|
|
var labs = [];
|
|
labsContainer.querySelectorAll('.lab-entry').forEach(function(entry) {
|
|
var vals = entry.querySelector('.lab-values').value.trim();
|
|
if (vals) {
|
|
labs.push({ date: entry.querySelector('.lab-date').value || '', values: vals });
|
|
}
|
|
});
|
|
|
|
if (notes.length === 0 && !edNote && !hAndP) {
|
|
showToast('Add at least one note', 'error');
|
|
return;
|
|
}
|
|
|
|
showLoading('Generating hospital course...');
|
|
|
|
fetch('/api/generate-hospital-course', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({
|
|
notes: notes,
|
|
edNote: edNote,
|
|
hAndP: hAndP,
|
|
labs: labs,
|
|
patientAge: document.getElementById('hc-age').value,
|
|
patientGender: document.getElementById('hc-gender').value,
|
|
pmh: document.getElementById('hc-pmh').value,
|
|
setting: document.getElementById('hc-setting').value,
|
|
los: document.getElementById('hc-los').value,
|
|
formatPreference: document.getElementById('hc-format').value,
|
|
additionalInstructions: document.getElementById('hc-instructions').value,
|
|
model: getSelectedModel()
|
|
})
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
hideLoading();
|
|
if (data.success) {
|
|
setOutputText(courseText, data.hospitalCourse);
|
|
formatTag.textContent = data.format || '';
|
|
modelTag.textContent = (data.model || '').split('/').pop();
|
|
outputCard.classList.remove('hidden');
|
|
clarifyOutput.classList.add('hidden');
|
|
outputCard.scrollIntoView({ behavior: 'smooth' });
|
|
showToast('Hospital course generated!', 'success');
|
|
} else showToast(data.error || 'Failed', 'error');
|
|
})
|
|
.catch(function(err) { hideLoading(); showToast(err.message, 'error'); });
|
|
});
|
|
|
|
// Refine, Shorten, Clarify
|
|
document.getElementById('hc-refine-btn').addEventListener('click', function() { refineDocument('hc-course-text', 'hc-refine-input'); });
|
|
document.getElementById('hc-shorten-btn').addEventListener('click', function() { shortenDocument('hc-course-text'); });
|
|
|
|
document.getElementById('hc-clarify-btn').addEventListener('click', function() {
|
|
var doc = courseText.innerText.trim();
|
|
if (!doc) { showToast('Generate first', 'error'); return; }
|
|
|
|
showLoading('Checking for missing info...');
|
|
|
|
fetch('/api/clarify', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify({ document: doc, context: 'hospital course', model: getSelectedModel() })
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
hideLoading();
|
|
if (data.success) {
|
|
setOutputText(clarifyOutput, data.questions);
|
|
clarifyOutput.classList.remove('hidden');
|
|
showToast('Review missing information below', 'info');
|
|
}
|
|
})
|
|
.catch(function(err) { hideLoading(); showToast(err.message, 'error'); });
|
|
});
|
|
|
|
// Register load handler for saved encounters
|
|
window.registerEncounterLoadHandler('hospital', function(enc) {
|
|
if (enc.generated_note) {
|
|
var courseText = document.getElementById('hc-course-text');
|
|
var outputCard = document.getElementById('hc-output');
|
|
if (courseText) courseText.textContent = enc.generated_note;
|
|
if (outputCard) outputCard.classList.remove('hidden');
|
|
}
|
|
});
|
|
|
|
console.log('✅ Hospital Course module loaded');
|
|
});
|
|
})();
|