// ============================================================ // 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 // 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 } if (!opts.label || !opts.label.trim()) { showToast('Enter a patient label first', 'error'); return; } fetch('/api/encounters/saved', { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify(opts) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { showToast('Encounter saved (' + (opts.label || '') + ')', 'success'); // Store the returned ID for subsequent saves if (data.id && opts.onSaved) opts.onSaved(data.id); loadSavedEncountersList(); } else { showToast(data.error || 'Save failed', 'error'); } }) .catch(function() { 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) { // 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'); // 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; } }); } function esc(str) { if (!str) return ''; return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } console.log('✅ Encounters module loaded'); })();