🚽
PumpPro

Redirecting to SiteloopPro…

Not redirecting? Sign in β†’
🚨 0 urgent calls open Tap to view β†’
Dashboard

Loading dashboard…

Routes Today
πŸ—Ί Fleet Map
πŸ—Ί
Fleet map β€” trucks shown in real time
Requires Google Maps API key in Settings
`); w.document.close(); }catch(e){toast(e.message,'error');} } // ── LEADS ──────────────────────────────────────────────── async function loadLeads(){ const status=gv('leads-status-f'); try{ const d=await api(`leads&status=${status}`); $('leads-list').innerHTML=(d.leads||[]).map(l=>`
${l.company_name||'Unknown'}
${l.site_address||''}, ${l.site_city||''} ${l.site_zip||''}
${l.contact_name||''} Β· ${l.contact_phone||''}
${l.est_units_needed?`🚽 ~${l.est_units_needed} units`:''} ${l.est_people_on_site?`πŸ‘· ~${l.est_people_on_site} people`:''} ${l.est_value?`πŸ’° ~${fmt$(l.est_value)}`:''} ${l.est_start_date?`πŸ“… Start: ${fmtD(l.est_start_date)}`:''} ${l.lead_source?.replace('_',' ')||''}
${l.notes?`
${l.notes}
`:''}
${statusBadge(l.status)} ${statusBadge(l.priority)}
${l.status!=='won'?``:''}
`).join('')||'

No leads

Add leads from the field or office

'; }catch(e){toast(e.message,'error');} } async function editLead(id){ try{const d=await api(`leads&status=`);/* Would need lead detail β€” stub */openModal('modal-lead');}catch(e){} } async function saveLead(){ const data={id:gv('nl-id')||null,company_name:gv('nl-company'),contact_name:gv('nl-contact'),contact_phone:gv('nl-phone'),contact_email:gv('nl-email'),site_address:gv('nl-addr'),site_city:gv('nl-city'),site_state:gv('nl-state'),site_zip:gv('nl-zip'),site_type:gv('nl-stype'),est_people_on_site:gv('nl-people')||null,est_units_needed:gv('nl-units')||null,est_value:gv('nl-value')||null,est_start_date:gv('nl-start')||null,lead_source:gv('nl-source'),priority:gv('nl-priority'),status:'new',notes:gv('nl-notes')}; if(!data.company_name){toast('Company name required','error');return;} try{await api('save_lead','POST',data);toast('Lead saved','ok');closeModal('modal-lead');loadLeads();}catch(e){toast(e.message,'error');} } async function convertLead(id){ if(!confirm('Convert this lead to a customer account?')) return; try{const d=await api('convert_lead','POST',{id});toast(`Customer created: ${d.account_num}`,'ok');loadLeads();}catch(e){toast(e.message,'error');} } // ── ROUTES ─────────────────────────────────────────────── async function loadRoutes(){ const date=gv('routes-date'),type=gv('routes-type-f'); try{ const d=await api(`routes&date=${date}&type=${type}`); $('routes-list').innerHTML=(d.routes||[]).map(r=>`
${{pump:'πŸš›',flatbed:'🚚',water:'πŸ’§',service:'πŸ”§'}[r.route_type]||'πŸš›'}
${r.route_name}
${r.driver_name||'Unassigned'} Β· ${r.truck_num||'No truck'} Β· ${fmtD(r.route_date)}
${statusBadge(r.status)}
${r.completed_stops||0} / ${r.total_stops||0} stops Β· ${r.total_gallons_pumped?parseFloat(r.total_gallons_pumped).toFixed(0)+' gal':''}
${r.status==='draft'?``:''}
`).join('')||'

No routes

Use Dispatch Board to build routes

'; }catch(e){toast(e.message,'error');} } // ── DRIVER ROUTE VIEW ──────────────────────────────────── async function loadMyRoute(){ try{ const d=await api('dashboard'); if(!d.driver_view){$('my-route-content').innerHTML='

Not a driver account

';return;} const route=d.route,stops=d.stops||[],urgents=d.urgents||[],notifs=d.notifications||[]; // Check unread urgents if(urgents.length>0){const banner=$('urgent-banner');if(banner){banner.style.display='flex';$('urgent-count').textContent=urgents.length+' urgent call'+(urgents.length>1?'s':'')+' assigned to you';}} $('my-route-content').innerHTML=`
${!route?`
🚽

No route assigned for today

Contact the office if you're expecting a route

`:`
${truckIcon(route.route_type)} ${route.route_name}
Truck: ${route.truck_num||'β€”'}
${route.total_stops||0}
Total Stops
${route.completed_stops||0}
Done
${(route.total_stops||0)-(route.completed_stops||0)}
Remaining
${urgents.map(u=>`
🚨URGENT β€” ${u.customer_name||''}
${u.stop_name||u.address||''}
${u.description||''}
πŸ—Ί Get Directions
`).join('')} ${stops.map((s,idx)=>`
${s.status==='completed'?'βœ“':idx+1}
${s.stop_name||'β€”'}
${s.address||''}, ${s.city||''}
${s.customer_name||''} ${s.customer_phone?'Β· '+s.customer_phone:''}
${statusBadge(s.status)}
${s.access_notes?`
πŸ“‹ ${s.access_notes}
`:''} ${s.gate_code?`
πŸ”’ Gate Code: ${s.gate_code}
`:''} ${s.unit_num?`
🚽 Unit: ${s.unit_num} ${s.unit_type?'('+unitTypeLbl(s.unit_type)+')':''}
`:''} ${s.gallons_pumped?`
βœ“ Completed β€” ${s.gallons_pumped} gallons pumped
`:''}
πŸ—Ί Directions ${s.status!=='completed'?``:''}
`).join('')} `}
`; }catch(e){$('my-route-content').innerHTML=`

${e.message}

`;} } function openCompleteStop(stopId,stopName){ $('cstop-id').value=stopId; $('cstop-title').textContent='Complete: '+stopName; $('cstop-gallons').value='';$('cstop-chem').value='';$('cstop-notes').value=''; $('cstop-damage').checked=false;$('damage-section').style.display='none'; clearSig(); openModal('modal-complete-stop'); // First mark as arrived api('update_stop_status','POST',{id:stopId,status:'arrived'}).catch(()=>{}); } $('cstop-damage')?.addEventListener('change',function(){$('damage-section').style.display=this.checked?'':'none';}); async function completeStop(){ const id=gv('cstop-id'); if(!id){toast('No stop selected','error');return;} const sig=getSig(); const data={id,status:'completed',gallons_pumped:gv('cstop-gallons')||0,chemicals_used:gv('cstop-chem')||0,driver_notes:gv('cstop-notes'),damage_reported:$('cstop-damage')?.checked?1:0,damage_notes:$('cstop-damage')?.checked?gv('cstop-damage-notes'):'',customer_signature:sig||''}; try{ await api('update_stop_status','POST',data); toast('Stop completed!','ok'); closeModal('modal-complete-stop'); loadMyRoute(); }catch(e){toast(e.message,'error');} } // ── STAFF ──────────────────────────────────────────────── async function loadStaff(){ try{ const d=await api('staff'); $('staff-list').innerHTML=`
${(d.staff||[]).map(s=>` `).join('')||''}
NameRolePhoneCDL ExpiryLicense ExpiryStatus
${s.name} ${statusBadge(s.role)} ${s.phone||'β€”'} ${fmtD(s.cdl_expiry)} ${fmtD(s.license_expiry)} ${statusBadge(s.status)}
No staff added
`; }catch(e){toast(e.message,'error');} } // ── MANIFESTS ──────────────────────────────────────────── async function loadManifests(){ const from=gv('mfst-from'),to=gv('mfst-to'); try{ const d=await api(`manifests&date_from=${from}&date_to=${to}`); $('manifests-list').innerHTML=`
${(d.manifests||[]).map(m=>` `).join('')||''}
Manifest #DateDriverTruckGallonsDump SiteStatus
${m.manifest_num} ${fmtD(m.manifest_date)} ${m.driver_name||'β€”'} ${m.truck_num||'β€”'} ${parseFloat(m.gallons_collected||0).toFixed(1)} ${m.dump_site||'β€”'} ${statusBadge(m.status)}
No manifests found
`; }catch(e){toast(e.message,'error');} } // ── REPORTS ────────────────────────────────────────────── async function loadReports(){ const now=new Date(); const defaultFrom=`${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-01`; const defaultTo=new Date(now.getFullYear(),now.getMonth()+1,0).toISOString().slice(0,10); $('reports-content').innerHTML=`
to
`; } async function runReport(){ const from=gv('rpt-from'),to=gv('rpt-to'); try{ const d=await api(`reports&type=month-end&date_from=${from}&date_to=${to}`); const b=d.billed||{},col=d.collected||{},out=d.outstanding||{},st=d.stops||{},rt=d.routes||{}; const collectionRate=parseFloat(b.total||0)>0?Math.round(parseFloat(col.total||0)/parseFloat(b.total||0)*100):0; $('rpt-output').innerHTML=`
Total Billed
${fmt$(b.total||0)}
${b.cnt||0} invoices
Collected
${fmt$(col.total||0)}
${collectionRate}% collection rate
Outstanding
${fmt$(out.total||0)}
unpaid
Stops Completed
${st.cnt||0}
${parseFloat(st.gallons||0).toFixed(0)} gal pumped
Routes Completed
${rt.cnt||0}
in period
Customer Ledger
${(d.ledger||[]).map(c=>` `).join('')||''}
AccountCustomerPhoneChargesPaymentsBalance Due
${c.account_num||'β€”'} ${c.name} ${c.phone||'β€”'} ${fmt$(c.charges_month||0)} ${fmt$(c.payments_month||0)} ${fmt$(c.balance_current||0)}
No customer activity in this period
`; }catch(e){toast(e.message,'error');} } // ── SETTINGS ───────────────────────────────────────────── async function loadSettings(){ try{ const d=await api('company'); const c=d.company||{}; $('settings-content').innerHTML=`
Company Settings
`; }catch(e){toast(e.message,'error');} } async function saveSettings(){ const data={name:gv('set-name'),phone:gv('set-phone'),email:gv('set-email'),address:gv('set-addr'),dump_site_default:gv('set-dump'),tax_rate:parseFloat(gv('set-tax')||0)/100,invoice_terms:gv('set-terms'),invoice_notes:gv('set-notes'),google_maps_key:gv('set-maps')}; try{await api('save_company','POST',data);toast('Settings saved','ok');}catch(e){toast(e.message,'error');} } // ── SIGNATURE PAD ──────────────────────────────────────── function initSigPad(){ const canvas=$('sig-canvas'); if(!canvas)return; const ctx=canvas.getContext('2d'); let drawing=false,lastX=0,lastY=0; function getPos(e){ const rect=canvas.getBoundingClientRect(); const scaleX=canvas.width/rect.width,scaleY=canvas.height/rect.height; const src=e.touches?e.touches[0]:e; return{x:(src.clientX-rect.left)*scaleX,y:(src.clientY-rect.top)*scaleY}; } function start(e){e.preventDefault();drawing=true;const p=getPos(e);[lastX,lastY]=[p.x,p.y];} function draw(e){ if(!drawing)return;e.preventDefault(); const p=getPos(e); ctx.strokeStyle='#111';ctx.lineWidth=2;ctx.lineCap='round'; ctx.beginPath();ctx.moveTo(lastX,lastY);ctx.lineTo(p.x,p.y);ctx.stroke(); [lastX,lastY]=[p.x,p.y]; } function end(){drawing=false;} canvas.addEventListener('mousedown',start);canvas.addEventListener('mousemove',draw);canvas.addEventListener('mouseup',end); canvas.addEventListener('touchstart',start,{passive:false});canvas.addEventListener('touchmove',draw,{passive:false});canvas.addEventListener('touchend',end); } function clearSig(){const canvas=$('sig-canvas');if(canvas)canvas.getContext('2d').clearRect(0,0,canvas.width,canvas.height);} function getSig(){const canvas=$('sig-canvas');if(!canvas)return'';return canvas.toDataURL();} // ── PRINT STATEMENTS ───────────────────────────────────── let _reportLedger=[]; async function printStatement(custId,custName){ const from=gv('rpt-from'),to=gv('rpt-to'); toast('Loading statement…','info'); try{const d=await api(`reports&type=month-end&date_from=${from}&date_to=${to}`);openStatementWindow(d.ledger?.filter(c=>c.id===custId)||[],from,to);}catch(e){toast(e.message,'error');} } async function printAllStatements(){ const from=gv('rpt-from'),to=gv('rpt-to'); toast('Generating statements…','info'); try{ const d=await api(`reports&type=month-end&date_from=${from}&date_to=${to}`); openStatementWindow((d.ledger||[]).filter(c=>parseFloat(c.balance_current||0)>0||parseFloat(c.charges_month||0)>0),from,to); }catch(e){toast(e.message,'error');} } function openStatementWindow(customers,from,to){ if(!customers.length){toast('No customers with activity to print','error');return;} const w=window.open('','_blank','width=800,height=900'); const html=`Customer Statements ${customers.map(c=>{ const bal=parseFloat(c.balance_current||0),charged=parseFloat(c.charges_month||0),paid=parseFloat(c.payments_month||0); return `
PumpPro
Port-A-Potty Service

Account Statement

${from} to ${to}
Acct # ${c.account_num||'β€”'}
${c.name}
${c.phone||''}
Amount Due
${bal>0.01?'$'+bal.toFixed(2):'PAID IN FULL'}
Charges This Period$${charged.toFixed(2)}
Payments Received($${paid.toFixed(2)})
BALANCE DUE${bal>0.01?'$'+bal.toFixed(2):'$0.00'}
Please make checks payable to PumpPro Β· Thank you for your business!
`; }).join('')} `; w.document.write(html);w.document.close();setTimeout(()=>w.print(),600); } // ── INIT ───────────────────────────────────────────────── window.addEventListener('DOMContentLoaded',checkSession); $('cstop-damage')?.addEventListener('change',function(){if($('damage-section'))$('damage-section').style.display=this.checked?'':'none';}); // ═══════════════════════════════════════════════════════ // PRICING & RATES MODULE // ═══════════════════════════════════════════════════════ const UNIT_TYPES = [ {key:'portable_toilet', label:'Portable Toilet', icon:'🚽'}, {key:'restroom_trailer', label:'Restroom Trailer', icon:'🚌'}, {key:'comfort_station', label:'Comfort Station', icon:'🚿'}, {key:'vip_toilet', label:'VIP Toilet', icon:'✨'}, {key:'guard_shed', label:'Guard Shed', icon:'πŸ›‘'}, {key:'storage_shed', label:'Storage Shed', icon:'πŸ“¦'}, {key:'holding_tank', label:'Holding Tank', icon:'πŸ›’'}, {key:'handicap_toilet', label:'Handicap Toilet', icon:'β™Ώ'}, {key:'fresh_water_delivery', label:'Fresh Water Delivery', icon:'πŸ’§'}, {key:'highrise_toilet', label:'High Rise Toilet', icon:'πŸ—'}, {key:'sink', label:'Sink', icon:'🚰'}, {key:'porcelain_toilet_system',label:'Porcelain Toilet System', icon:'πŸͺ£'}, ]; const SVC_TYPES = [ {key:'rental', label:'Rental', desc:'Daily / Weekly / Monthly rates'}, {key:'service', label:'Service', desc:'Hourly, Daily or Weekly service fees'}, {key:'sale', label:'Sale', desc:'Outright purchase price'}, {key:'same_day_delivery', label:'Same Day Delivery', desc:'Rush surcharge added on top of rental'}, ]; let _pricingData = {}; let _bundleData = []; async function loadPricing() { try { const pd = await api('pricing'); const bd = await api('bundles'); _pricingData = {}; (pd.pricing || []).forEach(function(p) { _pricingData[p.unit_type + '|' + p.service_type] = p; }); _bundleData = bd.bundles || []; renderPricingPage(); } catch(e) { toast(e.message, 'error'); } } function pricingTableRows(svcKey) { var rows = ''; UNIT_TYPES.forEach(function(u) { var row = _pricingData[u.key + '|' + svcKey] || {}; var cols = ''; if (svcKey === 'rental') { cols += ''; cols += ''; cols += ''; cols += ''; } if (svcKey === 'service') { cols += ''; cols += ''; cols += ''; } if (svcKey === 'sale') { cols += ''; } if (svcKey === 'same_day_delivery') { cols += ''; cols += 'Added on top of rental price'; } cols += ''; cols += ''; rows += '' + u.icon + ' ' + u.label + '' + cols + ''; }); return rows; } function pricingTableHead(svcKey) { var th = 'Unit Type'; if (svcKey === 'rental') th += 'DailyWeeklyMonthlyMin Days'; if (svcKey === 'service') th += 'HourlyDailyWeekly'; if (svcKey === 'sale') th += 'Sale Price'; if (svcKey === 'same_day_delivery') th += 'Same-Day Fee'; th += 'Active'; return '' + th + ''; } function renderPricingPage() { var html = '
'; html += '
Set rental rates, service fees, and sale prices for each unit type. Bundle discounts apply automatically when invoicing.
'; html += '
'; SVC_TYPES.forEach(function(svc) { html += '
'; html += '
' + svc.label; html += '' + svc.desc + '
'; html += '
'; html += pricingTableHead(svc.key); html += '' + pricingTableRows(svc.key) + '
'; html += '
'; html += '
'; }); // Bundles html += '
Bundle Deals'; html += 'Auto-discount for 2+ units'; html += '
'; if (!_bundleData.length) { html += '

No bundle deals configured

Add discounts for customers renting multiple units

'; } else { html += ''; _bundleData.forEach(function(b) { var types = []; try { types = JSON.parse(b.unit_types || '[]'); } catch(e) {} var typeLabels = types.length === 0 ? 'All types' : types.map(function(t) { var found = UNIT_TYPES.find(function(u) { return u.key === t; }); return found ? found.label : t; }).join(', '); var discLabel = b.discount_type === 'percent' ? b.discount_value + '% off' : b.discount_type === 'flat_per_unit' ? '$' + b.discount_value + '/unit off' : '$' + b.discount_value + ' total off'; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; }); html += '
NameUnit TypesMin QtyDiscountPeriodActive
' + b.bundle_name + '' + typeLabels + '' + b.min_quantity + '+' + discLabel + '' + (b.applies_to === 'all' ? 'All periods' : b.applies_to) + '' + (b.is_active ? 'Active' : 'Off') + ''; html += '
'; } html += '
'; // Price calculator var typeOpts = UNIT_TYPES.map(function(u) { return ''; }).join(''); html += '
Price Calculator'; html += 'Test pricing and bundle calculations
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; html += '
'; $('pricing-content').innerHTML = html; } async function savePricingRow(unitType, svcType) { var data = { unit_type: unitType, service_type: svcType }; if (svcType === 'rental') { data.price_daily = $('p-'+unitType+'-rental-daily')?.value || null; data.price_weekly = $('p-'+unitType+'-rental-weekly')?.value || null; data.price_monthly = $('p-'+unitType+'-rental-monthly')?.value || null; data.min_rental_days = $('p-'+unitType+'-rental-mindays')?.value || 1; } if (svcType === 'service') { data.price_hourly = $('p-'+unitType+'-service-hourly')?.value || null; data.price_daily = $('p-'+unitType+'-service-daily')?.value || null; data.price_weekly = $('p-'+unitType+'-service-weekly')?.value || null; } if (svcType === 'sale') { data.price_flat = $('p-'+unitType+'-sale-flat')?.value || null; } if (svcType === 'same_day_delivery') { data.same_day_fee = $('p-'+unitType+'-same_day_delivery-fee')?.value || null; } data.is_active = $('p-'+unitType+'-'+svcType+'-active')?.checked ? 1 : 0; try { await api('save_pricing', 'POST', data); toast(unitTypeLbl(unitType) + ' rate saved', 'ok'); await loadPricing(); } catch(e) { toast(e.message, 'error'); } } async function saveAllPricing(svcType) { var saved = 0, errs = 0; for (var i = 0; i < UNIT_TYPES.length; i++) { try { await savePricingRow(UNIT_TYPES[i].key, svcType); saved++; } catch(e) { errs++; } } toast('Saved ' + saved + ' ' + svcType + ' prices' + (errs ? ', ' + errs + ' errors' : ''), errs ? 'warn' : 'ok'); } function openBundleModal(bundle) { var b = bundle || {}; var selTypes = []; try { selTypes = JSON.parse(b.unit_types || '[]'); } catch(e) {} // Remove any existing bundle modal var existing = document.getElementById('modal-bundle-inner'); if (existing) existing.remove(); // Build modal using DOM β€” no HTML string quoting issues var overlay = document.createElement('div'); overlay.className = 'overlay'; overlay.id = 'modal-bundle-inner'; overlay.style.display = 'flex'; overlay.addEventListener('click', function(e) { if (e.target === overlay) overlay.remove(); }); var modal = document.createElement('div'); modal.className = 'modal modal-wide'; modal.innerHTML = [ '', '', '
', '', '', '
', '
', '', '', '
', '
', '', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '
', '', '', '
', '', ].join(''); overlay.appendChild(modal); document.body.appendChild(overlay); // Now safely set values using DOM properties (no quoting needed) document.getElementById('bd-name').value = b.bundle_name || ''; document.getElementById('bd-desc').value = b.description || ''; document.getElementById('bd-minqty').value = b.min_quantity || 2; document.getElementById('bd-dval').value = b.discount_value || 0; document.getElementById('bd-notes').value = b.notes || ''; if (b.discount_type) document.getElementById('bd-dtype').value = b.discount_type; if (b.applies_to) document.getElementById('bd-period').value = b.applies_to; // Build unit type checkboxes safely var checksContainer = document.getElementById('bd-type-checks'); UNIT_TYPES.forEach(function(u) { var lbl = document.createElement('label'); lbl.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer'; var chk = document.createElement('input'); chk.type = 'checkbox'; chk.name = 'bd-types'; chk.value = u.key; if (selTypes.indexOf(u.key) >= 0) chk.checked = true; lbl.appendChild(chk); lbl.appendChild(document.createTextNode(' ' + u.icon + ' ' + u.label)); checksContainer.appendChild(lbl); }); // Wire up buttons var bundleId = b.id || 0; document.getElementById('bd-close-btn').addEventListener('click', function() { overlay.remove(); }); document.getElementById('bd-cancel-btn').addEventListener('click', function() { overlay.remove(); }); document.getElementById('bd-save-btn').addEventListener('click', function() { saveBundle(bundleId); }); } function editBundle(id) { var b = _bundleData.find(function(x) { return x.id === id; }); if (b) openBundleModal(b); } async function saveBundle(id) { var checked = Array.from(document.querySelectorAll('input[name="bd-types"]:checked')).map(function(el) { return el.value; }); var data = { id: id || null, bundle_name: gv('bd-name'), description: gv('bd-desc'), unit_types: checked, min_quantity: gv('bd-minqty') || 2, discount_type: gv('bd-dtype'), discount_value: gv('bd-dval') || 0, applies_to: gv('bd-period'), is_active: 1, notes: gv('bd-notes'), }; if (!data.bundle_name) { toast('Bundle name required', 'error'); return; } try { await api('save_bundle', 'POST', data); toast('Bundle saved', 'ok'); var modal = document.getElementById('modal-bundle-inner'); if (modal) modal.remove(); loadPricing(); } catch(e) { toast(e.message, 'error'); } } async function deleteBundle(id) { if (!confirm('Delete this bundle deal?')) return; try { await api('delete_bundle', 'POST', {id}); toast('Bundle deleted', 'ok'); loadPricing(); } catch(e) { toast(e.message, 'error'); } } async function runCalculator() { var unitType = gv('calc-type'); var svcType = gv('calc-svc'); var period = gv('calc-period'); var qty = parseInt(gv('calc-qty') || '1'); var sameDay = $('calc-sameday')?.checked ? 1 : 0; var url = 'calculate_price&unit_type=' + unitType + '&service_type=' + svcType + '&billing_period=' + period + '&quantity=' + qty + '&same_day=' + sameDay; try { var r = await api(url); var html = ''; if (!r.unit_price) { html = '

' + (r.message || 'No price configured for this combination') + '

'; } else { html = '
'; html += '
'; html += '
Unit Price
'; html += '
$' + parseFloat(r.unit_price).toFixed(2) + '
'; html += '
per ' + r.billing_period + '
'; html += '
Qty x ' + r.quantity + '
'; html += '
$' + parseFloat(r.base_total).toFixed(2) + '
'; html += '
base total
'; html += '
Total Due
'; html += '
$' + parseFloat(r.total).toFixed(2) + '
'; html += '
' + r.billing_period + '
'; if (r.bundle_applied) { html += '
Bundle: ' + r.bundle_applied + ' β€” saved $' + parseFloat(r.bundle_discount).toFixed(2) + '
'; } if (r.same_day_fee > 0) { html += '
Same-day delivery fee: +$' + parseFloat(r.same_day_fee).toFixed(2) + '
'; } html += '
'; } $('calc-result').innerHTML = html; } catch(e) { toast(e.message, 'error'); } }