Fix: admin models/2FA loading, save doubles, sidebar collapse, SSHADESS copy

- Fix 2FA status stuck at "Loading...": export load2FAStatus globally so
  app.js tabChanged handler can call it when navigating via sidebar
- Fix admin models silent failure: add error message + retry button on catch
- Fix save encounter doubles: persist _savedEncId_* to sessionStorage so
  page refresh reuses existing record instead of inserting a duplicate;
  add _savingInProgress guard to prevent race-condition double-saves
- Desktop sidebar collapsible: add collapse/expand buttons (not collapsed
  by default), state saved to localStorage; hidden on mobile
- Copy to Visit Note now also copies SSHADESS assessment to wv-shadess-text
- Store selected visit age in window._wellVisitAge + sessionStorage so
  other tabs can read the patient's age
- Bigger screening/vaccine boxes: min-height on wv-vax-list, wv-screen-list,
  wv-visit-detail; slightly more padding on vaccine items
- Wire New Patient (clearTab) buttons to document click handler
This commit is contained in:
Daniel Onyejesi 2026-03-22 18:33:28 -04:00
parent 3449b0efca
commit 2da4401d80
8 changed files with 3699 additions and 39 deletions

3480
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -54,7 +54,8 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(--
.app-body{display:flex;min-height:calc(100vh - 66px);}
/* SIDEBAR */
.sidebar{width:210px;flex-shrink:0;background:white;border-right:1.5px solid var(--g200);display:flex;flex-direction:column;position:sticky;top:0;height:calc(100vh - 66px);overflow-y:auto;z-index:100;}
.sidebar{width:210px;flex-shrink:0;background:white;border-right:1.5px solid var(--g200);display:flex;flex-direction:column;position:sticky;top:0;height:calc(100vh - 66px);overflow-y:auto;z-index:100;transition:width 0.22s ease;}
.sidebar.collapsed{width:0;border:none;overflow:hidden;}
.sidebar-header{display:none;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--g200);}
.sidebar-title{font-size:13px;font-weight:700;color:var(--g700);}
.sidebar-nav{display:flex;flex-direction:column;padding:8px 0;flex:1;}
@ -66,6 +67,11 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(--
.tab-btn span{overflow:hidden;text-overflow:ellipsis;}
.sidebar-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:199;}
.btn-menu-toggle{display:none;}
/* Sidebar collapse/expand buttons (desktop only) */
.sidebar-pin-btn{display:flex;align-items:center;justify-content:center;padding:10px;border:none;background:none;color:var(--g400);cursor:pointer;font-size:13px;border-top:1px solid var(--g200);transition:color 0.15s;flex-shrink:0;}
.sidebar-pin-btn:hover{color:var(--blue);}
.sidebar-expand-btn{position:fixed;left:0;top:50%;transform:translateY(-50%);z-index:101;background:white;border:1.5px solid var(--g200);border-left:none;border-radius:0 8px 8px 0;padding:10px 6px;color:var(--g500);cursor:pointer;font-size:13px;box-shadow:2px 0 8px rgba(0,0,0,0.06);transition:color 0.15s;}
.sidebar-expand-btn:hover{color:var(--blue);}
/* MAIN */
.main-content{flex:1;min-width:0;padding:20px 20px;}
@ -244,6 +250,9 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(--
.sidebar.open{left:0;box-shadow:4px 0 20px rgba(0,0,0,0.18);}
.sidebar.open~.sidebar-overlay{display:block;}
.sidebar-header{display:flex;}
/* Hide desktop collapse controls on mobile */
.sidebar-pin-btn{display:none;}
.sidebar-expand-btn{display:none !important;}
.main-content{padding:12px;}
.demographics-bar{flex-direction:column;}
.demo-field input,.demo-field select{min-width:100%;}
@ -295,7 +304,7 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(--
.wv-visit-select{padding:8px 14px;border:1.5px solid var(--g300);border-radius:8px;font-size:14px;font-family:inherit;color:var(--g800);background:white;cursor:pointer;min-width:200px;}
.wv-visit-select:focus{outline:none;border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-light);}
.wv-visit-detail{display:flex;flex-direction:column;gap:14px;}
.wv-visit-detail{display:flex;flex-direction:column;gap:14px;min-height:400px;}
.wv-section{background:white;border-radius:var(--radius);box-shadow:var(--shadow);padding:16px 20px;}
.wv-section-title{font-size:14px;font-weight:700;color:var(--g700);margin-bottom:12px;display:flex;align-items:center;gap:8px;}
.wv-section-title i{color:var(--blue);}
@ -316,16 +325,18 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--g50);color:var(--
.wv-chip-range{background:var(--amber-light);color:#92400e;}
/* Vaccines */
.wv-vax-list{display:flex;flex-direction:column;gap:10px;}
.wv-vax-item{background:var(--g50);border-radius:8px;padding:10px 14px;border-left:3px solid var(--green);}
.wv-vax-list{display:flex;flex-direction:column;gap:10px;min-height:80px;}
.wv-vax-item{background:var(--g50);border-radius:8px;padding:12px 16px;border-left:3px solid var(--green);}
.wv-vax-name{font-size:14px;font-weight:600;color:var(--g800);display:flex;align-items:center;gap:8px;}
.wv-vax-dot{color:var(--green);font-size:10px;}
.wv-vax-dose{display:inline-block;margin-top:4px;font-size:11px;font-weight:700;padding:2px 10px;background:var(--green-light);color:#065f46;border-radius:10px;}
.wv-vax-note{margin-top:6px;font-size:12px;color:var(--g500);display:flex;align-items:flex-start;gap:6px;}
.wv-vax-note i{color:var(--blue);margin-top:2px;flex-shrink:0;}
/* Vaccine status row */
.wv-vax-status-row{display:flex;gap:4px;flex-wrap:wrap;margin-top:6px;}
/* Screens */
.wv-screen-list{display:flex;flex-direction:column;gap:8px;}
.wv-screen-list{display:flex;flex-direction:column;gap:10px;min-height:60px;}
.wv-screen-item{display:flex;align-items:center;gap:10px;}
.wv-status-badge{flex-shrink:0;font-size:11px;font-weight:700;padding:2px 10px;border-radius:10px;text-transform:uppercase;letter-spacing:0.4px;}
.wv-status-dot{background:var(--green-light);color:#065f46;}

View file

@ -196,7 +196,15 @@
<span>Admin</span>
</button>
</div>
<!-- Desktop collapse button (hidden on mobile) -->
<button id="btn-sidebar-pin" class="sidebar-pin-btn" title="Collapse sidebar">
<i class="fas fa-angles-left"></i>
</button>
</nav>
<!-- Desktop sidebar expand button (shown when sidebar is collapsed) -->
<button id="btn-sidebar-expand" class="sidebar-expand-btn" title="Expand sidebar" style="display:none;">
<i class="fas fa-angles-right"></i>
</button>
<div class="sidebar-overlay" id="sidebar-overlay"></div>
@ -241,6 +249,7 @@
</div>
<button id="btn-enc-save" class="btn-sm btn-ghost"><i class="fas fa-floppy-disk"></i> Save</button>
<button id="btn-enc-load" class="btn-sm btn-ghost"><i class="fas fa-folder-open"></i> Load</button>
<button id="btn-enc-new" class="btn-sm btn-ghost" title="Clear all and start new patient" style="color:var(--red);"><i class="fas fa-rotate-left"></i> New</button>
</div>
<div id="enc-load-popover" class="enc-load-popover hidden">
<div class="enc-load-popover-inner">
@ -318,6 +327,7 @@
</div>
<button id="btn-dict-save" class="btn-sm btn-ghost"><i class="fas fa-floppy-disk"></i> Save</button>
<button id="btn-dict-load" class="btn-sm btn-ghost"><i class="fas fa-folder-open"></i> Load</button>
<button id="btn-dict-new" class="btn-sm btn-ghost" title="Clear all and start new patient" style="color:var(--red);"><i class="fas fa-rotate-left"></i> New</button>
</div>
<div id="dict-load-popover" class="enc-load-popover hidden">
<div class="enc-load-popover-inner">
@ -416,6 +426,7 @@
</div>
<button id="btn-hosp-save" class="btn-sm btn-ghost"><i class="fas fa-floppy-disk"></i> Save</button>
<button id="btn-hosp-load" class="btn-sm btn-ghost"><i class="fas fa-folder-open"></i> Load</button>
<button id="btn-hosp-new" class="btn-sm btn-ghost" title="Clear all and start new patient" style="color:var(--red);"><i class="fas fa-rotate-left"></i> New</button>
</div>
<div id="hosp-load-popover" class="enc-load-popover hidden">
<div class="enc-load-popover-inner">
@ -562,6 +573,7 @@
</div>
<button id="btn-chart-save" class="btn-sm btn-ghost"><i class="fas fa-floppy-disk"></i> Save</button>
<button id="btn-chart-load" class="btn-sm btn-ghost"><i class="fas fa-folder-open"></i> Load</button>
<button id="btn-chart-new" class="btn-sm btn-ghost" title="Clear all and start new patient" style="color:var(--red);"><i class="fas fa-rotate-left"></i> New</button>
</div>
<div id="chart-load-popover" class="enc-load-popover hidden">
<div class="enc-load-popover-inner">
@ -1030,7 +1042,7 @@
<button class="wv-subtab-btn" data-subtab="catchup">
<i class="fas fa-rotate"></i> Catch-Up Schedule
</button>
<button class="wv-subtab-btn" data-subtab="shadess">
<button class="wv-subtab-btn" data-subtab="shadess" style="display:none;">
<i class="fas fa-brain"></i> SSHADESS (12+)
</button>
<button class="wv-subtab-btn" data-subtab="note">
@ -1112,6 +1124,7 @@
</div>
<button id="btn-wv-save" class="btn-sm btn-ghost"><i class="fas fa-floppy-disk"></i> Save</button>
<button id="btn-wv-load" class="btn-sm btn-ghost"><i class="fas fa-folder-open"></i> Load</button>
<button id="btn-wv-new" class="btn-sm btn-ghost" title="Clear and start new patient" style="color:var(--red);"><i class="fas fa-rotate-left"></i> New</button>
</div>
<div id="wv-load-popover" class="enc-load-popover hidden">
<div class="enc-load-popover-inner">
@ -1250,6 +1263,7 @@
</div>
<button id="btn-sick-save" class="btn-sm btn-ghost"><i class="fas fa-floppy-disk"></i> Save</button>
<button id="btn-sick-load" class="btn-sm btn-ghost"><i class="fas fa-folder-open"></i> Load</button>
<button id="btn-sick-new" class="btn-sm btn-ghost" title="Clear and start new patient" style="color:var(--red);"><i class="fas fa-rotate-left"></i> New</button>
</div>
<div id="sick-load-popover" class="enc-load-popover hidden">
<div class="enc-load-popover-inner">

View file

@ -515,13 +515,22 @@
// ---- MODELS ----
function loadModels() {
var container = document.getElementById('cms-models-list');
fetch('/api/admin/config/models', { headers: getAuthHeaders() })
.then(function(r) { return r.json(); })
.then(function(data) {
if (!data.success) return;
if (!data.success) {
if (container) container.innerHTML = '<p style="color:var(--red);font-size:13px;">Failed to load models: ' + (data.error || 'Unknown error') + '</p>';
return;
}
renderModelsList(data.models || [], data.custom || []);
})
.catch(function() {});
.catch(function(e) {
console.error('[Admin] Models load failed:', e);
if (container) container.innerHTML = '<p style="color:var(--red);font-size:13px;"><i class="fas fa-circle-xmark"></i> Failed to load models. <button id="btn-retry-models" style="color:var(--blue);background:none;border:none;cursor:pointer;text-decoration:underline;font-size:13px;">Retry</button></p>';
var retryBtn = document.getElementById('btn-retry-models');
if (retryBtn) retryBtn.addEventListener('click', loadModels);
});
}
function renderModelsList(models, custom) {

View file

@ -72,6 +72,34 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
// --- DESKTOP SIDEBAR COLLAPSE ---
var sidebarPinBtn = document.getElementById('btn-sidebar-pin');
var sidebarExpandBtn = document.getElementById('btn-sidebar-expand');
// Restore collapse state
try {
if (localStorage.getItem('ped_sidebar_collapsed') === '1') {
if (sidebar) sidebar.classList.add('collapsed');
if (sidebarExpandBtn) sidebarExpandBtn.style.display = 'flex';
if (sidebarPinBtn) sidebarPinBtn.style.display = 'none';
}
} catch(e) {}
if (sidebarPinBtn) {
sidebarPinBtn.addEventListener('click', function() {
if (sidebar) sidebar.classList.add('collapsed');
if (sidebarExpandBtn) sidebarExpandBtn.style.display = 'flex';
if (sidebarPinBtn) sidebarPinBtn.style.display = 'none';
try { localStorage.setItem('ped_sidebar_collapsed', '1'); } catch(e) {}
});
}
if (sidebarExpandBtn) {
sidebarExpandBtn.addEventListener('click', function() {
if (sidebar) sidebar.classList.remove('collapsed');
if (sidebarExpandBtn) sidebarExpandBtn.style.display = 'none';
if (sidebarPinBtn) sidebarPinBtn.style.display = 'flex';
try { localStorage.setItem('ped_sidebar_collapsed', '0'); } catch(e) {}
});
}
// Load settings data when settings tab is activated
document.addEventListener('tabChanged', function(e) {
if (e.detail && e.detail.tab === 'settings') {

View file

@ -399,8 +399,13 @@ document.addEventListener('DOMContentLoaded', function() {
if (disableBtn) disableBtn.style.display = 'none';
}
})
.catch(function() {});
.catch(function() {
var status = document.getElementById('2fa-status');
if (status) status.textContent = 'Status: Unable to load';
});
}
// Expose globally so app.js tabChanged handler can call it
window.load2FAStatus = load2FAStatus;
// Check registration status
fetch('/api/auth/registration-status')

View file

@ -62,6 +62,17 @@
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) {
@ -71,8 +82,12 @@
// Save current encounter state
window.saveEncounter = function(opts) {
// opts: { id, label, enc_type, transcript, generated_note, partial_data }
// 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(),
@ -80,16 +95,20 @@
})
.then(function(r) { return r.json(); })
.then(function(data) {
_savingInProgress[type] = false;
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);
// 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() { showToast('Save failed', 'error'); });
.catch(function() { _savingInProgress[type] = false; showToast('Save failed', 'error'); });
};
// Load list and refresh all displays (settings page + open popovers)
@ -318,6 +337,13 @@
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); });
@ -355,6 +381,38 @@
});
}
// ── 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) {}
showToast('Ready for new patient', 'info');
}
function esc(str) {
if (!str) return '';
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');

View file

@ -75,11 +75,35 @@
};
// ─── Module-level state for visit statuses ────────────────────────────────
// keyed by visitId + '.' + itemKey
// keyed by visitId + '.' + itemKey — persisted to localStorage
var _visitStatuses = {};
// SSHADESS is only relevant for 12+ year visits
var SSHADESS_VISITS = ['12y','13y','14y','15y','16y','17y','18y','19y','20y','21y'];
function saveStatusesToStorage() {
try { localStorage.setItem('ped_visit_statuses', JSON.stringify(_visitStatuses)); } catch(e) {}
}
function clearCurrentVisit() {
var visitId = document.getElementById('wv-visit-select').value;
if (!visitId) return;
Object.keys(_visitStatuses).forEach(function(k) {
if (k.indexOf(visitId + '.') === 0) delete _visitStatuses[k];
});
saveStatusesToStorage();
renderVisitPanel(visitId);
if (typeof showToast === 'function') showToast('Visit statuses cleared', 'info');
}
// ─── Init ──────────────────────────────────────────────────────────────────
function init() {
// Restore statuses from localStorage
try {
var saved = localStorage.getItem('ped_visit_statuses');
if (saved) _visitStatuses = JSON.parse(saved) || {};
} catch(e) { _visitStatuses = {}; }
populateVisitSelect();
renderCatchUp();
renderFullSchedule();
@ -89,6 +113,30 @@
btn.addEventListener('click', function () { switchSubtab(btn.dataset.subtab); });
});
// Wire the visit-detail panel click/input ONCE here (not inside renderVisitPanel)
// which would accumulate a new listener on every visit change — causing "can't click Done"
var panel = document.getElementById('wv-visit-detail');
if (panel) {
panel.addEventListener('click', function (e) {
var visitId = document.getElementById('wv-visit-select').value;
var btn = e.target.closest('.visit-status-btn');
if (btn) { handleVisitStatusClick(btn, visitId); saveStatusesToStorage(); }
if (e.target.closest('#btn-wv-copy-to-note')) copyVisitStatusesToNote(visitId);
if (e.target.closest('#btn-wv-clear-visit')) clearCurrentVisit();
});
panel.addEventListener('input', function (e) {
var noteInput = e.target.closest('.visit-note-input');
if (noteInput) {
var sk = noteInput.dataset.statusKey;
if (!_visitStatuses[sk] || typeof _visitStatuses[sk] === 'string') {
_visitStatuses[sk] = { status: _visitStatuses[sk] || '', note: '' };
}
_visitStatuses[sk].note = noteInput.value;
saveStatusesToStorage();
}
});
}
// Show first real visit by default
var sel = document.getElementById('wv-visit-select');
if (sel && sel.value) onVisitChange();
@ -127,7 +175,22 @@
function onVisitChange() {
var visitId = document.getElementById('wv-visit-select').value;
if (!visitId) return;
// Share selected visit age globally so other tabs can use it
var sel = document.getElementById('wv-visit-select');
var selectedOption = sel ? sel.options[sel.selectedIndex] : null;
window._wellVisitAge = selectedOption ? selectedOption.textContent : visitId;
try { sessionStorage.setItem('ped_visit_age', window._wellVisitAge); } catch(e) {}
renderVisitPanel(visitId);
// Show SSHADESS subtab only for age 12+ visits
var shadessBtn = document.querySelector('.wv-subtab-btn[data-subtab="shadess"]');
if (shadessBtn) {
var showShadess = SSHADESS_VISITS.indexOf(visitId) !== -1;
shadessBtn.style.display = showShadess ? '' : 'none';
// If SSHADESS was active but should now be hidden, fall back to byvisit
if (!showShadess && shadessBtn.classList.contains('active')) {
switchSubtab('byvisit');
}
}
}
function switchSubtab(name) {
@ -242,34 +305,15 @@
html += '<div class="wv-section wv-notes-box"><i class="fas fa-circle-info"></i> ' + esc(data.notes) + '</div>';
}
// ── Copy to Visit Note button ──
html += '<div style="margin-top:12px;padding:8px 0;">';
html += '<button class="btn-sm btn-primary" id="btn-wv-copy-to-note" style="font-size:12px;"><i class="fas fa-clipboard-check"></i> Copy Statuses to Visit Note</button>';
html += '<span style="font-size:11px;color:var(--g400);margin-left:8px;">Copies vaccine + screening statuses into the Screenings & Vaccines fields</span>';
// ── Action buttons ──
html += '<div style="margin-top:12px;padding:8px 0;display:flex;align-items:center;gap:8px;flex-wrap:wrap;">';
html += '<button class="btn-sm btn-primary" id="btn-wv-copy-to-note"><i class="fas fa-clipboard-check"></i> Copy to Visit Note</button>';
html += '<button class="btn-sm btn-ghost" id="btn-wv-clear-visit" style="color:var(--red);"><i class="fas fa-rotate-left"></i> Clear This Visit</button>';
html += '<span style="font-size:11px;color:var(--g400);">Copies vaccine + screening statuses to the Visit Note tab</span>';
html += '</div>';
panel.innerHTML = html || '<p class="wv-empty">No specific recommendations found for this visit.</p>';
// Wire up status buttons
panel.addEventListener('click', function (e) {
var btn = e.target.closest('.visit-status-btn');
if (btn) handleVisitStatusClick(btn, visitId);
if (e.target.closest('#btn-wv-copy-to-note')) {
copyVisitStatusesToNote(visitId);
}
});
// Wire up notes inputs
panel.addEventListener('input', function (e) {
var noteInput = e.target.closest('.visit-note-input');
if (noteInput) {
var sk = noteInput.dataset.statusKey;
if (!_visitStatuses[sk]) _visitStatuses[sk] = {};
if (typeof _visitStatuses[sk] === 'string') _visitStatuses[sk] = { status: _visitStatuses[sk] };
_visitStatuses[sk].note = noteInput.value;
}
});
// Note: click/input listeners are wired once in init() — NOT here
}
function renderVaxStatusBtns(statusKey, curStatus) {
@ -392,6 +436,17 @@
screenEl.value = existingS ? existingS + '\n' + screenLines.join('\n') : screenLines.join('\n');
}
// Also copy SSHADESS assessment if present
var shadessText = document.getElementById('shadess-result-text');
var wvShadessEl = document.getElementById('wv-shadess-text');
if (shadessText && wvShadessEl) {
var shadessContent = (shadessText.innerText || shadessText.textContent || '').trim();
if (shadessContent) {
var existingSh = wvShadessEl.value.trim();
wvShadessEl.value = existingSh ? existingSh + '\n\n' + shadessContent : shadessContent;
}
}
if (typeof showToast === 'function') showToast('Visit statuses copied to Visit Note tab', 'success');
}