(function(global) { 'use strict'; const SAVE_KEY = 'svs-msp-quote-v1'; const QUOTE_REF_KEY = 'svs-msp-quote-ref'; let saveTimer; function saveState() { try { const state = { clientName: document.getElementById('clientName')?.value || '', users: parseInt(document.getElementById('userCount')?.value, 10) || 0, endpoints: parseInt(document.getElementById('endpointCount')?.value, 10) || 0, servers: parseInt(document.getElementById('serverCount')?.value, 10) || 0, byol: document.getElementById('rateBYOL')?.checked || false, addExtHours: document.getElementById('addExtHours')?.checked || false, addPWM: document.getElementById('addPWM')?.checked || false, addINKY: document.getElementById('addINKY')?.checked || false, addZT: document.getElementById('addZT')?.checked || false, addUSB: document.getElementById('addUSB')?.checked || false, addBMB: document.getElementById('addBMB')?.checked || false, ztSeats: parseInt(document.getElementById('ztNetSeats')?.value, 10) || 0, ztRouters: parseInt(document.getElementById('ztNetRouters')?.value, 10) || 0, voipTier: (document.querySelector('input[name="voipTier"]:checked') || {}).value || 'basic', voipSeats: parseInt(document.getElementById('voipSeats')?.value, 10) || 0, addVoipPhone: document.getElementById('addVoipPhone')?.checked || false, addVoipFax: document.getElementById('addVoipFax')?.checked || false, phoneBill: parseFloat(document.getElementById('currentPhoneBill')?.value) || 0, contractTerm: (document.querySelector('input[name="contractTerm"]:checked') || {}).value || 'm2m', hstEnabled: document.getElementById('hstToggle')?.checked || false, oneTimeFee: parseFloat(document.getElementById('oneTimeFee')?.value) || 0, adminWaived: document.getElementById('adminWaived')?.checked || false, onboardingWaived: document.getElementById('onboardingWaived')?.checked || false, onboardingManual: document.getElementById('oneTimeFee')?.dataset.manual === '1', repName: document.getElementById('repName')?.value || '', quoteNotes: document.getElementById('quoteNotes')?.value || '' }; localStorage.setItem(SAVE_KEY, JSON.stringify(state)); } catch (e) { console.warn('saveState: failed to persist quote', e); } } function debouncedSave() { clearTimeout(saveTimer); saveTimer = setTimeout(saveState, 400); } function syncBodyScrollLock() { const panelOpen = document.getElementById('mobileQuotePanel')?.classList.contains('open'); const modalOpen = document.getElementById('resetConfirmModal')?.classList.contains('open'); const sidebarFocusOpen = document.body.classList.contains('sidebar-focus-open'); document.body.style.overflow = (panelOpen || modalOpen || sidebarFocusOpen) ? 'hidden' : ''; } function openResetConfirm() { const modal = document.getElementById('resetConfirmModal'); if (!modal) return; modal.classList.add('open'); modal.setAttribute('aria-hidden', 'false'); syncBodyScrollLock(); document.getElementById('resetConfirmCancel')?.focus(); } function closeResetConfirm() { const modal = document.getElementById('resetConfirmModal'); if (!modal) return; modal.classList.remove('open'); modal.setAttribute('aria-hidden', 'true'); syncBodyScrollLock(); } function confirmResetQuote() { clearTimeout(saveTimer); try { localStorage.removeItem(SAVE_KEY); localStorage.removeItem(QUOTE_REF_KEY); } catch (e) { console.warn('confirmResetQuote: failed to clear saved quote state', e); } closeResetConfirm(); window.location.reload(); } function trapFocusInModal(e) { var modal = document.getElementById('resetConfirmModal'); if (!modal || !modal.classList.contains('open')) return; var focusable = modal.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); if (!focusable.length) return; var first = focusable[0]; var last = focusable[focusable.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } } document.addEventListener('keydown', function(e) { const modalOpen = document.getElementById('resetConfirmModal')?.classList.contains('open'); if (e.key === 'Escape' && modalOpen) { closeResetConfirm(); e.preventDefault(); e.stopImmediatePropagation(); return; } if (e.key === 'Tab' && modalOpen) { trapFocusInModal(e); } }); function restoreState() { try { const raw = localStorage.getItem(SAVE_KEY); if (!raw) return false; const s = JSON.parse(raw); const set = (id, val) => { const el = document.getElementById(id); if (el) el.value = val; }; const check = (id, val) => { const el = document.getElementById(id); if (el) el.checked = !!val; }; set('clientName', s.clientName); set('userCount', s.users); set('endpointCount', s.endpoints); set('serverCount', s.servers); check('rateBYOL', s.byol); check('rateM365', !s.byol); check('addExtHours', s.addExtHours); check('addPWM', s.addPWM); check('addINKY', s.addINKY); check('addZT', s.addZT); check('addUSB', s.addUSB); check('addBMB', s.addBMB); set('ztNetSeats', s.ztSeats); set('ztNetRouters', s.ztRouters); const tierEl = document.querySelector(`input[name="voipTier"][value="${s.voipTier}"]`); if (tierEl) tierEl.checked = true; set('voipSeats', s.voipSeats); check('addVoipPhone', s.addVoipPhone); check('addVoipFax', s.addVoipFax); set('currentPhoneBill', s.phoneBill); const termEl = document.querySelector(`input[name="contractTerm"][value="${s.contractTerm || 'm2m'}"]`); if (termEl) termEl.checked = true; check('hstToggle', s.hstEnabled); check('adminWaived', s.adminWaived); check('onboardingWaived', s.onboardingWaived); if (s.onboardingManual && !s.onboardingWaived) { set('oneTimeFee', s.oneTimeFee || 0); const feeEl = document.getElementById('oneTimeFee'); if (feeEl) feeEl.dataset.manual = '1'; } set('repName', s.repName || ''); const notesEl = document.getElementById('quoteNotes'); if (notesEl) notesEl.value = s.quoteNotes || ''; const rowMap = { addExtHours: 'row-ext', addPWM: 'row-pwm', addINKY: 'row-inky', addZT: 'row-zt', addBMB: 'row-bmb', addUSB: 'row-usb', addVoipPhone: 'row-vphone', addVoipFax: 'row-vfax' }; ['addExtHours', 'addPWM', 'addINKY', 'addZT', 'addBMB', 'addUSB', 'addVoipPhone', 'addVoipFax'].forEach(id => { const cb = document.getElementById(id); if (cb?.checked) { const row = document.getElementById(rowMap[id]); if (row) row.classList.add('selected'); } }); return true; } catch (e) { return false; } } function getOrCreateQuoteRef(now) { const currentDate = now || new Date(); const dateStr = `${currentDate.getFullYear()}${String(currentDate.getMonth() + 1).padStart(2, '0')}${String(currentDate.getDate()).padStart(2, '0')}`; const savedRef = localStorage.getItem(QUOTE_REF_KEY); if (savedRef) { const match = savedRef.match(/^SVS-(\d{4})(\d{2})(\d{2})-/); const refDate = match ? new Date(+match[1], +match[2] - 1, +match[3]) : null; const ageMs = refDate ? currentDate - refDate : Infinity; if (ageMs <= 30 * 24 * 60 * 60 * 1000) { return savedRef; } } const quoteRef = `SVS-${dateStr}-${String(Math.floor(Math.random() * 9000) + 1000)}`; localStorage.setItem(QUOTE_REF_KEY, quoteRef); return quoteRef; } global.SVSQuotePersistence = { SAVE_KEY, QUOTE_REF_KEY, saveState, debouncedSave, syncBodyScrollLock, openResetConfirm, closeResetConfirm, confirmResetQuote, restoreState, getOrCreateQuoteRef }; global.SAVE_KEY = SAVE_KEY; global.QUOTE_REF_KEY = QUOTE_REF_KEY; global.saveState = saveState; global.debouncedSave = debouncedSave; global.syncBodyScrollLock = syncBodyScrollLock; global.openResetConfirm = openResetConfirm; global.closeResetConfirm = closeResetConfirm; global.confirmResetQuote = confirmResetQuote; global.restoreState = restoreState; global.getOrCreateQuoteRef = getOrCreateQuoteRef; })(window);