// ============================================================ // ENCOUNTERS.JS — Save/resume/pause encounter progress // ============================================================ (function() { // ── Pause/Resume for recording buttons ───────────────────────────────── // Each recording module (encounter, dictation) gets a pause button wired here. function wirePause(recordBtnId, pauseBtnId, getRecorder) { var pauseBtn = document.getElementById(pauseBtnId); if (!pauseBtn) return; var isPaused = false; pauseBtn.addEventListener('click', function() { var rec = getRecorder(); if (!rec || !rec.mediaRecorder) return; if (!isPaused) { if (rec.mediaRecorder.state === 'recording') { rec.mediaRecorder.pause(); isPaused = true; pauseBtn.innerHTML = ' Resume'; pauseBtn.classList.remove('btn-ghost'); pauseBtn.classList.add('btn-primary'); } } else { if (rec.mediaRecorder.state === 'paused') { rec.mediaRecorder.resume(); isPaused = false; pauseBtn.innerHTML = ' Pause'; pauseBtn.classList.remove('btn-primary'); pauseBtn.classList.add('btn-ghost'); } } }); // Reset when recording stops document.addEventListener('recording-stopped', function(e) { if (e.detail && e.detail.module === recordBtnId.replace('-record-btn', '')) { isPaused = false; pauseBtn.innerHTML = ' Pause'; pauseBtn.classList.remove('btn-primary'); pauseBtn.classList.add('btn-ghost'); pauseBtn.classList.add('hidden'); } }); document.addEventListener('recording-started', function(e) { if (e.detail && e.detail.module === recordBtnId.replace('-record-btn', '')) { isPaused = false; pauseBtn.innerHTML = ' Pause'; pauseBtn.classList.remove('btn-primary'); pauseBtn.classList.add('btn-ghost'); pauseBtn.classList.remove('hidden'); } }); } // Expose pause wiring globally (called by each recording module) window.wirePauseButton = wirePause; // ── Saved Encounters Manager ─────────────────────────────────────────── var _savedEncounters = []; var _loadCallback = null; // set by each module to handle loading a saved encounter var _savingInProgress = {}; // prevent duplicate saves on double-click // Restore saved encounter IDs from sessionStorage (survive page refresh, cleared on tab close) (function() { ['encounter','dictation','hospital','chart','wellvisit','sickvisit'].forEach(function(t) { try { var id = sessionStorage.getItem('_savedEncId_' + t); if (id) window['_savedEncId_' + t] = id; } catch(e) {} }); })(); // Register a load handler for a specific tab type window.registerEncounterLoadHandler = function(type, fn) { if (!window._encLoadHandlers) window._encLoadHandlers = {}; window._encLoadHandlers[type] = fn; }; // Save current encounter state window.saveEncounter = function(opts) { // opts: { id, label, enc_type, transcript, generated_note, partial_data, onSaved } if (!opts.label || !opts.label.trim()) { showToast('Enter a patient label first', 'error'); return; } var type = opts.enc_type || 'encounter'; // Prevent duplicate saves from double-click if (_savingInProgress[type]) { showToast('Save in progress…', 'info'); return; } _savingInProgress[type] = true; fetch('/api/encounters/saved', { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify(opts) }) .then(function(r) { return r.json(); }) .then(function(data) { _savingInProgress[type] = false; if (data.success) { showToast('Encounter saved (' + (opts.label || '') + ')', 'success'); // Persist ID so refreshing the page won't create a duplicate if (data.id) { if (opts.onSaved) opts.onSaved(data.id); try { sessionStorage.setItem('_savedEncId_' + type, data.id); } catch(e) {} } loadSavedEncountersList(); } else { showToast(data.error || 'Save failed', 'error'); } }) .catch(function() { _savingInProgress[type] = false; showToast('Save failed', 'error'); }); }; // Load list and refresh all displays (settings page + open popovers) function loadSavedEncountersList(targetContainer) { fetch('/api/encounters/saved', { headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { _savedEncounters = data.encounters || []; renderSavedList(); // Refresh any open popovers ['enc', 'dict', 'wv', 'sick', 'hosp', 'chart'].forEach(function(pfx) { var pop = document.getElementById(pfx + '-load-popover'); if (pop && !pop.classList.contains('hidden')) { var search = pop.querySelector('.enc-load-search'); renderPopoverList(pfx, search ? search.value : ''); } }); if (targetContainer) { renderPopoverList(targetContainer, ''); } }) .catch(function() {}); } function renderPopoverList(pfx, query) { var listEl = document.getElementById(pfx + '-pop-list'); if (!listEl) return; var filtered = _savedEncounters.filter(function(enc) { if (!query) return true; return (enc.label || '').toLowerCase().indexOf(query.toLowerCase()) !== -1 || (enc.enc_type || '').toLowerCase().indexOf(query.toLowerCase()) !== -1; }); if (filtered.length === 0) { listEl.innerHTML = '
' + (query ? 'No matches.' : 'No saved encounters.') + '
'; return; } listEl.innerHTML = filtered.map(function(enc) { var date = enc.updated_at ? new Date(enc.updated_at).toLocaleDateString() : ''; return '
' + '
' + '
' + esc(enc.label) + '
' + '
' + esc(enc.enc_type) + ' · ' + date + '
' + '
' + '' + '
'; }).join(''); listEl.querySelectorAll('.enc-pop-item').forEach(function(item) { item.addEventListener('click', function(e) { if (e.target.closest('.enc-pop-delete')) return; resumeEncounter(item.dataset.id, item.dataset.type); // close popover var pop = item.closest('.enc-load-popover'); if (pop) pop.classList.add('hidden'); }); }); listEl.querySelectorAll('.enc-pop-delete').forEach(function(btn) { btn.addEventListener('click', function(e) { e.stopPropagation(); deleteEncounter(btn.dataset.id); }); }); } function renderSavedList() { var container = document.getElementById('saved-enc-list'); if (!container) return; if (_savedEncounters.length === 0) { container.innerHTML = '

No saved encounters.

'; return; } container.innerHTML = _savedEncounters.map(function(enc) { var date = enc.updated_at ? new Date(enc.updated_at).toLocaleDateString() : ''; var expires = enc.expires_at ? new Date(enc.expires_at).toLocaleDateString() : ''; var preview = (enc.transcript_preview || '').substring(0, 80); return '
' + '
' + '
' + '' + esc(enc.label) + '' + '' + esc(enc.enc_type) + '' + '
' + '
' + date + ' · expires ' + expires + (preview ? ' · ' + esc(preview) + '...' : '') + '
' + '
' + '' + '' + '
'; }).join(''); container.querySelectorAll('.enc-resume-btn').forEach(function(btn) { btn.addEventListener('click', function() { resumeEncounter(btn.dataset.id, btn.dataset.type); }); }); container.querySelectorAll('.enc-delete-btn').forEach(function(btn) { btn.addEventListener('click', function() { deleteEncounter(btn.dataset.id); }); }); } function openLoadPopover(pfx) { var popover = document.getElementById(pfx + '-load-popover'); if (!popover) return; var isHidden = popover.classList.contains('hidden'); // Close all popovers first ['enc', 'dict', 'wv', 'sick', 'hosp', 'chart'].forEach(function(p) { var pop = document.getElementById(p + '-load-popover'); if (pop) pop.classList.add('hidden'); }); if (!isHidden) return; // was open, now closed popover.classList.remove('hidden'); var searchEl = popover.querySelector('.enc-load-search'); if (searchEl) { searchEl.value = ''; searchEl.focus(); } if (_savedEncounters.length === 0) { loadSavedEncountersList(); } else { renderPopoverList(pfx, ''); } // Wire search if (searchEl && !searchEl._wired) { searchEl._wired = true; searchEl.addEventListener('input', function() { renderPopoverList(pfx, this.value); }); } // Wire close button — stop propagation so doc handler doesn't re-process var closeBtn = popover.querySelector('.enc-pop-close'); if (closeBtn && !closeBtn._wired) { closeBtn._wired = true; closeBtn.addEventListener('click', function(e) { e.stopPropagation(); popover.classList.add('hidden'); }); } // Stop click propagation inside popover so outside-click handler doesn't fire for inner clicks if (!popover._stopPropWired) { popover._stopPropWired = true; popover.addEventListener('click', function(e) { e.stopPropagation(); }); } } function resumeEncounter(id, type) { fetch('/api/encounters/saved/' + id, { headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { if (!data.success) { showToast(data.error || 'Failed', 'error'); return; } var enc = data.encounter; // Close all popovers ['enc', 'dict', 'wv', 'sick', 'hosp', 'chart'].forEach(function(p) { var pop = document.getElementById(p + '-load-popover'); if (pop) pop.classList.add('hidden'); }); // Navigate to correct tab var tabMap = { encounter: 'encounter', dictation: 'dictation', hospital: 'hospital', chart: 'chart', soap: 'soap', milestones: 'milestones', wellvisit: 'wellvisit', sickvisit: 'sickvisit' }; var tabName = tabMap[enc.enc_type] || 'encounter'; var tabBtn = document.querySelector('.tab-btn[data-tab="' + tabName + '"]'); if (tabBtn) tabBtn.click(); // Map enc_type → DOM element prefix var domPfxMap = { encounter: 'enc', dictation: 'dict', hospital: 'hosp', chart: 'chart', wellvisit: 'wv', sickvisit: 'sick', soap: 'soap', milestones: 'ms' }; var domPfx = domPfxMap[enc.enc_type] || enc.enc_type; var noteIdMap = { encounter: 'enc-hpi-text', dictation: 'dict-hpi-text', hospital: 'hc-course-text', chart: 'cr-review-text', wellvisit: 'wv-note-text', sickvisit: 'sick-note-text' }; // Fill in data setTimeout(function() { // Fill label var labelEl = document.getElementById(domPfx + '-label'); if (labelEl) labelEl.value = enc.label; // Set saved ID so next save updates same record window['_savedEncId_' + enc.enc_type] = enc.id; // Call tab-specific handler if (window._encLoadHandlers && window._encLoadHandlers[enc.enc_type]) { window._encLoadHandlers[enc.enc_type](enc); } else { // Default: fill transcript and generated note var transcriptEl = document.getElementById(domPfx + '-transcript'); if (transcriptEl && enc.transcript) transcriptEl.textContent = enc.transcript; if (enc.generated_note) { var noteEl = document.getElementById(noteIdMap[enc.enc_type] || (domPfx + '-hpi-text')); if (noteEl) { noteEl.textContent = enc.generated_note; var outputCard = noteEl.closest('.output-card'); if (outputCard) outputCard.classList.remove('hidden'); } } } showToast('Encounter "' + enc.label + '" loaded', 'success'); }, 200); }) .catch(function() { showToast('Load failed', 'error'); }); } function deleteEncounter(id) { fetch('/api/encounters/saved/' + id, { method: 'DELETE', headers: getAuthHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { showToast('Deleted', 'info'); loadSavedEncountersList(); } else showToast(data.error || 'Delete failed', 'error'); }) .catch(function() { showToast('Delete failed', 'error'); }); } // Expose loadSavedEncountersList globally so auth.js / settings page can call it window.loadSavedEncountersList = loadSavedEncountersList; document.addEventListener('click', function(e) { // Copy milestones narrative to visit note if (e.target.closest('#btn-ms-copy-to-note')) { var msText = document.getElementById('ms-narrative-text'); var wvMsEl = document.getElementById('wv-milestones-text'); if (msText && wvMsEl) { var content = (msText.innerText || msText.textContent || '').trim(); if (content) { wvMsEl.value = content; showToast('Copied! Switching to Visit Note…', 'success'); // Navigate to well visit note subtab so user can see the result if (typeof window.activateTab === 'function') window.activateTab('wellvisit'); if (typeof window.wvSwitchSubtab === 'function') { setTimeout(function() { window.wvSwitchSubtab('note'); }, 50); } } else { showToast('Generate a milestone assessment first', 'info'); } } } // Settings button — refresh saved encounters list if (e.target.closest('#btn-settings')) { loadSavedEncountersList(); } // Save buttons per tab if (e.target.closest('#btn-enc-save')) saveFromTab('encounter', 'enc'); if (e.target.closest('#btn-dict-save')) saveFromTab('dictation', 'dict'); if (e.target.closest('#btn-hosp-save')) saveFromTab('hospital', 'hosp'); if (e.target.closest('#btn-chart-save')) saveFromTab('chart', 'chart'); if (e.target.closest('#btn-wv-save')) saveFromTab('wellvisit', 'wv'); // Load buttons — open inline popovers if (e.target.closest('#btn-enc-load')) openLoadPopover('enc'); if (e.target.closest('#btn-dict-load')) openLoadPopover('dict'); if (e.target.closest('#btn-wv-load')) openLoadPopover('wv'); if (e.target.closest('#btn-sick-load')) openLoadPopover('sick'); if (e.target.closest('#btn-hosp-load')) openLoadPopover('hosp'); if (e.target.closest('#btn-chart-load')) openLoadPopover('chart'); // New Patient / clear tab buttons if (e.target.closest('#btn-enc-new')) clearTab('encounter'); if (e.target.closest('#btn-dict-new')) clearTab('dictation'); if (e.target.closest('#btn-hosp-new')) clearTab('hospital'); if (e.target.closest('#btn-chart-new')) clearTab('chart'); if (e.target.closest('#btn-wv-new')) clearTab('wellvisit'); if (e.target.closest('#btn-sick-new')) clearTab('sickvisit'); // Close popovers when clicking outside var loadBtnIds = ['#btn-enc-load','#btn-dict-load','#btn-wv-load','#btn-sick-load','#btn-hosp-load','#btn-chart-load']; var clickedLoadBtn = loadBtnIds.some(function(id) { return e.target.closest(id); }); if (!clickedLoadBtn) { ['enc', 'dict', 'wv', 'sick', 'hosp', 'chart'].forEach(function(p) { var pop = document.getElementById(p + '-load-popover'); if (pop) pop.classList.add('hidden'); }); } }); function saveFromTab(type, prefix) { var labelEl = document.getElementById(prefix + '-label'); var label = labelEl ? labelEl.value.trim() : ''; if (!label) { showToast('Enter a patient label before saving', 'error'); return; } var savedId = window['_savedEncId_' + type] || null; var transcriptEl = document.getElementById(prefix + '-transcript'); // Each tab stores its generated note in a different element var noteIdMap = { encounter: 'enc-hpi-text', dictation: 'dict-hpi-text', hospital: 'hc-course-text', chart: 'cr-review-text', wellvisit: 'wv-note-text' }; var noteEl = document.getElementById(noteIdMap[type] || (prefix + '-hpi-text')); window.saveEncounter({ id: savedId, label: label, enc_type: type, transcript: transcriptEl ? (transcriptEl.innerText || transcriptEl.textContent || '') : '', generated_note: noteEl ? (noteEl.innerText || noteEl.textContent || '') : '', onSaved: function(newId) { window['_savedEncId_' + type] = newId; } }); } // ── New Patient / Clear tab ──────────────────────────────────────────────── function clearTab(type) { var pfxMap = { encounter:'enc', dictation:'dict', hospital:'hosp', chart:'chart', wellvisit:'wv', sickvisit:'sick' }; var pfx = pfxMap[type] || type; var noteElMap = { encounter:'enc-hpi-text', dictation:'dict-hpi-text', hospital:'hc-course-text', chart:'cr-review-text', wellvisit:'wv-note-text', sickvisit:'sick-note-text' }; var outputElMap = { encounter:'enc-output', dictation:'dict-output', hospital:'hc-output', chart:'cr-output', wellvisit:'wv-note-output', sickvisit:'sick-note-output' }; // Clear label var lbl = document.getElementById(pfx + '-label'); if (lbl) lbl.value = ''; // Clear transcript (contenteditable or textarea) var tr = document.getElementById(pfx + '-transcript'); if (tr) { tr.textContent = ''; tr.innerHTML = ''; } // Clear generated note var note = document.getElementById(noteElMap[type]); if (note) { note.textContent = ''; note.innerHTML = ''; } // Hide output card var out = document.getElementById(outputElMap[type]); if (out) out.classList.add('hidden'); // Reset saved ID (memory + sessionStorage) window['_savedEncId_' + type] = null; try { sessionStorage.removeItem('_savedEncId_' + type); } catch(e) {} // Chart review has additional fields/cards to clear if (type === 'chart' && typeof window.resetChartReview === 'function') { window.resetChartReview(); } showToast('Ready for new patient', 'info'); } function esc(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } console.log('✅ Encounters module loaded'); })();