diff --git a/public/js/ed-encounters.js b/public/js/ed-encounters.js index d411035..494ad5b 100644 --- a/public/js/ed-encounters.js +++ b/public/js/ed-encounters.js @@ -445,6 +445,7 @@ var modelEl = document.getElementById('ed-model-select'); var selectedModel = modelEl ? modelEl.value : ''; + var encounterState = _state; showBusy('Consolidating note + generating MDM...'); fetch('/api/ed-encounters/finalize', { method: 'POST', @@ -461,6 +462,7 @@ }) .then(function(r) { return r.json(); }) .then(function(data) { + if (_state !== encounterState) return; if (!data.success) { hideBusy(); showToast(data.error || 'Finalize failed', 'error'); return; } _state.finalNote = data.finalNote || ''; _state.mdm = data.mdm; @@ -484,7 +486,6 @@ generated_note: composeFinalNoteForSave(_state.finalNote, _state.mdm), partial_data: partial, status: 'final', - idempotency_key: 'ed-final-' + Date.now(), onSaved: function(id) { window._savedEncId_ed = id; try { sessionStorage.setItem('_savedEncId_ed', id); } catch(e) {} @@ -497,7 +498,10 @@ hideBusy(); } }) - .catch(function(err) { hideBusy(); showToast(err.message, 'error'); }); + .catch(function(err) { + if (_state !== encounterState) return; + hideBusy(); showToast(err.message, 'error'); + }); } function composeFinalNoteForSave(noteText, mdm) { @@ -530,7 +534,6 @@ generated_note: lastNote, partial_data: partial, status: 'draft', - idempotency_key: 'ed-draft-' + (window._savedEncId_ed || 'new'), onSaved: function(id) { window._savedEncId_ed = id; try { sessionStorage.setItem('_savedEncId_ed', id); } catch(e) {} @@ -547,6 +550,7 @@ // ── Reset / new patient ────────────────────────────────────────────── function resetEncounter() { _state = freshState(); + hideBusy(); setVal('ed-label', ''); setVal('ed-age', ''); setVal('ed-gender', ''); @@ -561,6 +565,7 @@ hideEl('ed-mdm-card'); window._savedEncId_ed = null; try { sessionStorage.removeItem('_savedEncId_ed'); } catch(e) {} + if (typeof window.resetIdempotencyKey === 'function') window.resetIdempotencyKey('ed'); clearLocal(); updateBadge(); showToast('ED encounter cleared for new patient', 'info'); diff --git a/public/js/encounters.js b/public/js/encounters.js index 19ba7ca..8a28274 100644 --- a/public/js/encounters.js +++ b/public/js/encounters.js @@ -93,6 +93,8 @@ function setupEncountersModule() { try { sessionStorage.removeItem(key); } catch(e) {} } + window.resetIdempotencyKey = resetIdempotencyKey; + // Restore saved encounter IDs from sessionStorage (survive page refresh, cleared on tab close) function restoreSavedEncounterIds() { ['encounter','dictation','ed','hospital','chart','wellvisit','sickvisit','soap'].forEach(function(t) { @@ -116,11 +118,13 @@ function setupEncountersModule() { 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; + var identityKey = getIdempotencyKey(type); + if (_savingInProgress[type] === identityKey) { showToast('Save in progress…', 'info'); return; } + _savingInProgress[type] = identityKey; // 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 (!body.idempotency_key) body.idempotency_key = identityKey; if (opts.id && window._encounterVersions && window._encounterVersions[opts.id] != null) { body.expected_version = window._encounterVersions[opts.id]; } @@ -131,6 +135,8 @@ function setupEncountersModule() { }) .then(function(r) { return r.json().then(function(d) { d._status = r.status; return d; }); }) .then(function(data) { + // A completed save must not attach the previous patient's ID after New Patient. + if (getIdempotencyKey(type) !== identityKey) return; _savingInProgress[type] = false; if (data._status === 409) { showToast('Someone else edited this encounter. Reload to see the latest version.', 'error'); @@ -153,7 +159,11 @@ function setupEncountersModule() { showToast(data.error || 'Save failed', 'error'); } }) - .catch(function() { _savingInProgress[type] = false; showToast('Save failed', 'error'); }); + .catch(function() { + if (getIdempotencyKey(type) !== identityKey) return; + _savingInProgress[type] = false; + showToast('Save failed', 'error'); + }); }; // Load list and refresh all displays (settings page + open popovers) diff --git a/public/js/learningHub/quizRenderer.js b/public/js/learningHub/quizRenderer.js index 0df9607..f3eb155 100644 --- a/public/js/learningHub/quizRenderer.js +++ b/public/js/learningHub/quizRenderer.js @@ -10,7 +10,7 @@ export function renderQuizQuestions(questions) { var nameAttr = isMulti ? 'lh-qm-' + q.id : 'lh-q-' + q.id; var hint = isMulti ? '