Pre-Alpha to Alpha Ready
This commit is contained in:
240
pre-alpha/quote-persistence.js
Normal file
240
pre-alpha/quote-persistence.js
Normal file
@@ -0,0 +1,240 @@
|
||||
(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);
|
||||
Reference in New Issue
Block a user