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:
parent
3449b0efca
commit
2da4401d80
8 changed files with 3699 additions and 39 deletions
3480
package-lock.json
generated
Normal file
3480
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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;}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue