Four changes batched: 1. ED Encounters tab (new) — multi-stage emergency note with don't-miss tooltips and 2023 E/M MDM finalize. New route /api/ed-encounters (generate per-stage + finalize MDM), new ed-encounters.js owning all client logic, new ed-encounter.html component, new template_ed memory category. Persists draft to localStorage every keystroke and to saved_encounters on stage advance. encounters.js touched only to register the new tab in sessionStorage restore + tabMap (save and idempotency code untouched). 2. Notes model selector — /notes/from-voice now accepts a client-supplied model (validated by the existing callAI allow-list); falls back to the admin default. Added <select class="tab-model-select"> to notes.html so the existing app.js populator handles options + default. 3. Remove AI-learning-from-corrections — deleted correctionTracker.js, POST /memories/correction, the corrections branch in /memories/context, the settings UI section, the FAQ entry, and all dead trackAIOutput/saveCorrection guards in callers. Legacy correction_* DB rows are filtered (NOT LIKE) rather than dropped, so no destructive migration. 4. Fix notes AI framing — /notes/from-voice prompt no longer assumes "physician dictation". Plain notes (shopping lists, reminders, ideas) now match the dictation tone instead of being forced into clinical structure. All 46 tests pass.
516 lines
23 KiB
JavaScript
516 lines
23 KiB
JavaScript
// ============================================================
|
|
// 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 = '<i class="fas fa-play"></i> Resume';
|
|
pauseBtn.classList.remove('btn-ghost');
|
|
pauseBtn.classList.add('btn-primary');
|
|
}
|
|
} else {
|
|
if (rec.mediaRecorder.state === 'paused') {
|
|
rec.mediaRecorder.resume();
|
|
isPaused = false;
|
|
pauseBtn.innerHTML = '<i class="fas fa-pause"></i> 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 = '<i class="fas fa-pause"></i> 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 = '<i class="fas fa-pause"></i> 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
|
|
|
|
// Generate a UUID v4 for idempotency keys
|
|
function generateUUID() {
|
|
if (crypto && crypto.randomUUID) return crypto.randomUUID();
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
var r = Math.random() * 16 | 0;
|
|
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
|
});
|
|
}
|
|
|
|
// Get or create idempotency key for a tab type
|
|
function getIdempotencyKey(type) {
|
|
var key = '_idempKey_' + type;
|
|
if (!window[key]) {
|
|
try { window[key] = sessionStorage.getItem(key); } catch(e) {}
|
|
if (!window[key]) {
|
|
window[key] = generateUUID();
|
|
try { sessionStorage.setItem(key, window[key]); } catch(e) {}
|
|
}
|
|
}
|
|
return window[key];
|
|
}
|
|
|
|
// Reset idempotency key (on New Patient)
|
|
function resetIdempotencyKey(type) {
|
|
var key = '_idempKey_' + type;
|
|
window[key] = null;
|
|
try { sessionStorage.removeItem(key); } catch(e) {}
|
|
}
|
|
|
|
// Restore saved encounter IDs from sessionStorage (survive page refresh, cleared on tab close)
|
|
(function() {
|
|
['encounter','dictation','ed','hospital','chart','wellvisit','sickvisit','soap'].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;
|
|
// Optimistic locking: send the version we last saw. Server returns 409
|
|
// if the encounter was updated elsewhere (another tab, another device).
|
|
var body = Object.assign({}, opts);
|
|
if (opts.id && window._encounterVersions && window._encounterVersions[opts.id] != null) {
|
|
body.expected_version = window._encounterVersions[opts.id];
|
|
}
|
|
fetch('/api/encounters/saved', {
|
|
method: 'POST',
|
|
headers: getAuthHeaders(),
|
|
body: JSON.stringify(body)
|
|
})
|
|
.then(function(r) { return r.json().then(function(d) { d._status = r.status; return d; }); })
|
|
.then(function(data) {
|
|
_savingInProgress[type] = false;
|
|
if (data._status === 409) {
|
|
showToast('Someone else edited this encounter. Reload to see the latest version.', 'error');
|
|
return;
|
|
}
|
|
if (data.success) {
|
|
showToast('Encounter saved (' + (opts.label || '') + ')', 'success');
|
|
// Track the new version the server assigned
|
|
if (data.id != null && data.version != null) {
|
|
window._encounterVersions = window._encounterVersions || {};
|
|
window._encounterVersions[data.id] = data.version;
|
|
}
|
|
// 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', 'soap'].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 = '<div style="padding:10px 12px;color:var(--g400);font-size:13px;">' + (query ? 'No matches.' : 'No saved encounters.') + '</div>';
|
|
return;
|
|
}
|
|
listEl.innerHTML = filtered.map(function(enc) {
|
|
var date = enc.updated_at ? new Date(enc.updated_at).toLocaleDateString() : '';
|
|
return '<div class="enc-pop-item" data-id="' + enc.id + '" data-type="' + esc(enc.enc_type) + '">' +
|
|
'<div style="flex:1;min-width:0;">' +
|
|
'<div style="font-weight:600;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">' + esc(enc.label) + '</div>' +
|
|
'<div style="font-size:11px;color:var(--g500);">' + esc(enc.enc_type) + ' · ' + date + '</div>' +
|
|
'</div>' +
|
|
'<button class="btn-sm btn-ghost enc-pop-delete" data-id="' + enc.id + '" style="color:var(--red);flex-shrink:0;" title="Delete"><i class="fas fa-trash"></i></button>' +
|
|
'</div>';
|
|
}).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 = '<p style="color:var(--g400);font-size:13px;">No saved encounters.</p>';
|
|
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 '<div class="saved-enc-item" data-id="' + enc.id + '">' +
|
|
'<div style="flex:1;">' +
|
|
'<div style="display:flex;align-items:center;gap:6px;margin-bottom:2px;">' +
|
|
'<span class="saved-enc-label">' + esc(enc.label) + '</span>' +
|
|
'<span class="saved-enc-type">' + esc(enc.enc_type) + '</span>' +
|
|
'</div>' +
|
|
'<div class="saved-enc-meta">' + date + ' · expires ' + expires + (preview ? ' · ' + esc(preview) + '...' : '') + '</div>' +
|
|
'</div>' +
|
|
'<button class="btn-sm btn-primary enc-resume-btn" data-id="' + enc.id + '" data-type="' + esc(enc.enc_type) + '">Resume</button>' +
|
|
'<button class="btn-sm btn-ghost enc-delete-btn" data-id="' + enc.id + '" style="color:var(--red);"><i class="fas fa-trash"></i></button>' +
|
|
'</div>';
|
|
}).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', 'soap'].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;
|
|
// Remember the loaded version so future saves can detect drift
|
|
if (enc && enc.id != null && enc.version != null) {
|
|
window._encounterVersions = window._encounterVersions || {};
|
|
window._encounterVersions[enc.id] = enc.version;
|
|
}
|
|
// Close all popovers
|
|
['enc', 'dict', 'wv', 'sick', 'hosp', 'chart', 'soap'].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',
|
|
ed: 'ed'
|
|
};
|
|
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',
|
|
soap: 'soap-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');
|
|
if (e.target.closest('#btn-soap-save')) saveFromTab('soap', 'soap');
|
|
// 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');
|
|
if (e.target.closest('#btn-soap-load')) openLoadPopover('soap');
|
|
// 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');
|
|
if (e.target.closest('#btn-soap-new')) clearTab('soap');
|
|
// 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','#btn-soap-load'];
|
|
var clickedLoadBtn = loadBtnIds.some(function(id) { return e.target.closest(id); });
|
|
if (!clickedLoadBtn) {
|
|
['enc', 'dict', 'wv', 'sick', 'hosp', 'chart', 'soap'].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', sickvisit: 'sick-note-text', soap: 'soap-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 || '') : '',
|
|
idempotency_key: getIdempotencyKey(type),
|
|
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', soap:'soap' };
|
|
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',
|
|
soap:'soap-text'
|
|
};
|
|
var outputElMap = {
|
|
encounter:'enc-output', dictation:'dict-output',
|
|
hospital:'hc-output', chart:'cr-output',
|
|
wellvisit:'wv-note-output', sickvisit:'sick-note-output',
|
|
soap:'soap-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');
|
|
// Clear refine input (textarea or input) for every tab
|
|
var refineEl = document.getElementById(pfx + '-refine-input');
|
|
if (refineEl) refineEl.value = '';
|
|
// Clear instructions textarea (SOAP, hospital, etc.)
|
|
var instrEl = document.getElementById(pfx + '-instructions');
|
|
if (instrEl) instrEl.value = '';
|
|
// Clear demographic fields
|
|
var ageEl = document.getElementById(pfx + '-age');
|
|
if (ageEl) ageEl.value = '';
|
|
var genderEl = document.getElementById(pfx + '-gender');
|
|
if (genderEl) genderEl.value = '';
|
|
// Reset saved ID and idempotency key (memory + sessionStorage)
|
|
window['_savedEncId_' + type] = null;
|
|
try { sessionStorage.removeItem('_savedEncId_' + type); } catch(e) {}
|
|
resetIdempotencyKey(type);
|
|
// 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, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
|
|
console.log('✅ Encounters module loaded');
|
|
|
|
})();
|