241 lines
8.6 KiB
JavaScript
241 lines
8.6 KiB
JavaScript
(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);
|