Redirecting to SiteloopPro…

FuelPro uses SiteloopPro for authentication. You'll be redirected automatically.

Not redirecting? Sign in at SiteloopPro →
Dashboard

Loading…

`; const w=window.open('','_blank','width=820,height=940'); if(!w){toast('Pop-up blocked — allow pop-ups for this site','error');return;} w.document.write(html); w.document.close(); setTimeout(()=>w.print(),800); } // SETTINGS // ═══════════════════════════════════════════ async function pgSettings() { $('content').innerHTML=`

Loading settings…

`; try{ const [co,products,tiers,taxRates]=await Promise.all([api('company'),api('products'),api('price-tiers'),api('tax-rates')]); const typeColors={federal:'badge-blue',state:'badge-gray',county:'badge-yellow',city:'badge-green',special:'badge-orange'}; $('content').innerHTML=`
Company Settings
Price Tiers
${tiers.map(t=>`
${t.name}
Default margin: ${t.margin_pct}%
`).join('')}
Products / Fuel Types
${products.map(p=>`
${p.name} ${fuelBadge(p.type)}
Rack: ${fmt$(p.rack_price)}/gal · Unit: ${p.unit}
${p.active?'Active':'Inactive'}
`).join('')}
Tax Brackets by Location
Tax rates stack automatically based on the customer's delivery address — federal, then state, then county, then city. Each component is calculated separately and shown as a line item on the invoice.
${taxRates.rates?.length ? `
${(taxRates.rates||[]).map(r=>` `).join('')}
Name Type Applies To Location Rate Status
${r.name} ${r.tax_type} ${r.fuel_type==='all'?'All fuels':r.fuel_type.replace('_',' ')} ${[r.zip_code,r.city,r.county,r.state_code?r.state_code:''].filter(Boolean).join(', ')||'All locations'} ${r.rate_method==='per_gallon'?'$'+parseFloat(r.rate).toFixed(5)+'/gal':parseFloat(r.rate).toFixed(3)+'%'} ${r.active?'Active':'Inactive'}
` : `
🏛️
No tax rates configured
Add federal, state, county, and city tax rates. The system will automatically apply the correct stack based on each customer's delivery address.
`}
${productModal()}${tierModal()}${taxModal()}`; }catch(e){$('content').innerHTML=`

${e.message}

`;} } function taxModal(){ return ` `; } function toggleTaxRateLabel(){ const m=$('tax-method')?.value; if($('tax-rate-label')) $('tax-rate-label').textContent = m==='per_gallon'?'Rate ($/gal)':'Rate (%)'; if($('tax-rate')) $('tax-rate').placeholder = m==='per_gallon'?'0.17000':'8.000'; } function openTaxModal(r=null){ $('tax-modal-title').textContent = r?'Edit Tax Rate':'Add Tax Rate'; $('tax-id').value = r?.id||''; $('tax-name').value = r?.name||''; $('tax-type').value = r?.tax_type||'state'; $('tax-fuel').value = r?.fuel_type||'all'; $('tax-method').value = r?.rate_method||'per_gallon'; $('tax-rate').value = r?.rate||''; $('tax-state').value = r?.state_code||''; $('tax-county').value = r?.county||''; $('tax-city').value = r?.city||''; $('tax-zip').value = r?.zip_code||''; $('tax-active').value = r?.active===0?'0':'1'; $('tax-notes').value = r?.notes||''; toggleTaxRateLabel(); openModal('modal-tax'); } let _taxRateCache=[]; async function editTaxRate(id){ if(!_taxRateCache.length){ const d=await api('tax-rates'); _taxRateCache=d.rates||[]; } const r=_taxRateCache.find(x=>x.id==id); if(r) openTaxModal(r); } async function saveTaxRate(){ const name=$('tax-name')?.value?.trim(); if(!name){toast('Name required','error');return;} const rate=parseFloat($('tax-rate')?.value); if(isNaN(rate)||rate<0){toast('Enter a valid rate','error');return;} try{ await api('save-tax-rate','POST',{ id:$('tax-id')?.value||null, name, tax_type:$('tax-type')?.value, fuel_type:$('tax-fuel')?.value, rate_method:$('tax-method')?.value, rate, state_code:$('tax-state')?.value?.toUpperCase()||null, county:$('tax-county')?.value||null, city:$('tax-city')?.value||null, zip_code:$('tax-zip')?.value||null, active:parseInt($('tax-active')?.value)||0, notes:$('tax-notes')?.value||'' }); toast('Tax rate saved','success'); closeModal('modal-tax'); _taxRateCache=[]; pgSettings(); }catch(e){toast(e.message,'error');} } async function deleteTaxRate(id){ if(!confirm('Delete this tax rate? This won\'t affect past invoices.')) return; try{ await api('delete-tax-rate','POST',{id}); toast('Tax rate deleted','success'); _taxRateCache=[]; pgSettings(); }catch(e){toast(e.message,'error');} } async function loadBurnHistory(customerId, containerId){ try { const d = await api(`burn-history&customer_id=${customerId}&limit=12`); const history = d.history || []; const stats = d.stats; const el = document.getElementById(containerId); if (!el) return; if (!history.length) { el.innerHTML = `
No burn history yet — recorded automatically after each completed delivery.
`; return; } // Stats bar el.innerHTML = `
Avg Daily Burn
${stats?.avg_daily_burn||'—'} gal
Avg K-Factor
${stats?.avg_k_factor||'—'}
Total Deliveries
${stats?.total_deliveries||0}
Total Consumed
${stats?.total_gallons_consumed||0} gal
${history.map(h => { const kDrift = h.k_factor_actual && h.k_factor_previous ? (parseFloat(h.k_factor_actual) - parseFloat(h.k_factor_previous)).toFixed(4) : null; const driftColor = kDrift > 0.01 ? 'color:var(--yellow)' : kDrift < -0.01 ? 'color:var(--green)' : ''; return ``; }).join('')}
Tank Period Days Consumed Daily Avg Degree Days K-Factor Actual K-Factor Before
${h.tank_name} ${fmtDate(h.period_start)} → ${fmtDate(h.period_end)} ${h.days_elapsed}d ${parseFloat(h.gallons_consumed).toFixed(1)} gal ${parseFloat(h.avg_daily_burn||0).toFixed(2)}/day ${h.degree_days_period ? parseFloat(h.degree_days_period).toFixed(0)+' HDD' : '—'} ${h.k_factor_actual||'—'} ${h.k_factor_previous||'—'}${kDrift!==null?` ${kDrift>0?'+':''}${kDrift}`:''}
`; } catch(e) { const el = document.getElementById(containerId); if (el) el.innerHTML = `
Could not load burn history.
`; } } async function saveCompanySettings(){ try{ await api('company','POST',{name:$('set-name')?.value,phone:$('set-phone')?.value,email:$('set-email')?.value,address:$('set-addr')?.value,invoice_prefix:$('set-inv-prefix')?.value,default_payment_terms:parseInt($('set-terms')?.value)||30,tax_rate:parseFloat($('set-tax')?.value)||0,email_from:$('set-from-email')?.value}); toast('Settings saved','success'); }catch(e){toast(e.message,'error');} } function productModal(){return` `;} function tierModal(){return` `;} function openAddProduct(){$('prod-modal-title').textContent='Add Product';$('prod-id').value='';$('prod-name').value='';$('prod-type').value='heating_oil';$('prod-unit').value='gallon';$('prod-rack').value='';$('prod-active').value=1;openModal('modal-product');} async function editProductFn(id){try{const p=await api(`products&id=${id}`);$('prod-modal-title').textContent='Edit Product';$('prod-id').value=p.id;$('prod-name').value=p.name;$('prod-type').value=p.type;$('prod-unit').value=p.unit;$('prod-rack').value=p.rack_price;$('prod-active').value=p.active;openModal('modal-product');}catch(e){toast(e.message,'error');}} async function saveProductFn(){const name=$('prod-name')?.value?.trim();if(!name){toast('Name required','error');return;}try{await api('products','POST',{id:$('prod-id')?.value||null,name,type:$('prod-type')?.value,unit:$('prod-unit')?.value,rack_price:parseFloat($('prod-rack')?.value)||0,active:parseInt($('prod-active')?.value)||0});toast('Product saved','success');closeModal('modal-product');pgSettings();}catch(e){toast(e.message,'error');}} function openAddTier(){$('tier-modal-title').textContent='Add Tier';$('tier-id').value='';$('tier-name').value='';$('tier-margin').value=15;$('tier-desc').value='';openModal('modal-tier');} async function editTierFn(id){try{const t=await api(`price-tiers&id=${id}`);$('tier-modal-title').textContent='Edit Tier';$('tier-id').value=t.id;$('tier-name').value=t.name;$('tier-margin').value=t.margin_pct;$('tier-desc').value=t.description||'';openModal('modal-tier');}catch(e){toast(e.message,'error');}} async function saveTierFn(){const name=$('tier-name')?.value?.trim();if(!name){toast('Name required','error');return;}try{await api('price-tiers','POST',{id:$('tier-id')?.value||null,name,margin_pct:parseFloat($('tier-margin')?.value)||15,description:$('tier-desc')?.value});toast('Tier saved','success');closeModal('modal-tier');pgSettings();}catch(e){toast(e.message,'error');}} // ═══════════════════════════════════════════ // SHARED SELECT LOADERS // ═══════════════════════════════════════════ async function loadCustomersIntoSelect(selectId, selectedId=null) { const sel=$(selectId); if(!sel)return; try{const data=await api('customers');sel.innerHTML='';data.forEach(c=>{const o=el('option');o.value=c.id;o.textContent=c.name+(c.account_num?` (${c.account_num})`:'');if(String(c.id)===String(selectedId))o.selected=true;sel.appendChild(o);});}catch(e){} } async function loadProductsIntoSelect(selectId, selectedId=null) { const sel=$(selectId); if(!sel)return; try{const data=await api('products');sel.innerHTML='';data.filter(p=>p.active).forEach(p=>{const o=el('option');o.value=p.id;o.textContent=p.name;if(String(p.id)===String(selectedId))o.selected=true;sel.appendChild(o);});}catch(e){} } async function loadTiersIntoSelect(selectId, selectedId=null) { const sel=$(selectId); if(!sel)return; try{const data=await api('price-tiers');sel.innerHTML='';data.forEach(t=>{const o=el('option');o.value=t.id;o.textContent=t.name;if(String(t.id)===String(selectedId))o.selected=true;sel.appendChild(o);});}catch(e){} } async function loadDriversIntoSelect(selectId, filter=null, selectedId=null) { const sel=$(selectId); if(!sel)return; try{ const data=await api('drivers'); sel.innerHTML=''; data.filter(d=>{ if(d.status==='inactive') return false; if(filter==='fuel') return d.driver_type==='fuel'||d.driver_type==='both'; if(filter==='service') return d.driver_type==='service'||d.driver_type==='both'; return true; }).forEach(d=>{ const o=el('option'); o.value=d.id; o.textContent=d.name + (d.driver_type==='both'?' (Fuel+Svc)':d.driver_type==='service'?' (Svc)':''); if(String(d.id)===String(selectedId))o.selected=true; sel.appendChild(o); }); }catch(e){} } async function loadTrucksIntoSelect(selectId, selectedId=null) { const sel=$(selectId); if(!sel)return; try{const data=await api('trucks');sel.innerHTML='';data.filter(t=>t.status==='available'||t.status==='on_route').forEach(t=>{const o=el('option');o.value=t.id;o.textContent=`${t.name} (${fmtGal(t.tank_capacity)})`;if(String(t.id)===String(selectedId))o.selected=true;sel.appendChild(o);});}catch(e){} } // ── Driver type badge helper ───────────────── function driverTypeBadge(type) { const map = { fuel: ['badge-blue', '⛽ Fuel Driver'], service: ['badge-orange', '🔧 Service Tech'], both: ['badge-purple', '⛽🔧 Both'], }; const [cls, label] = map[type] || ['badge-gray', 'Fuel Driver']; return `${label}`; } // ── Service Tech mobile board ───────────────── async function pgServiceBoard() { $('content').innerHTML=`

Loading your service calls…

`; try { const calls = await api(`service-calls&tech_id=me&date=${today()}`); $('content').innerHTML=`
${ICONS.service}Your service calls for today, ${new Date().toLocaleDateString('en-US',{weekday:'long',month:'long',day:'numeric'})}
${calls.length ? calls.map((s,i) => `
CALL ${i+1} · ${s.call_num||'—'}
${s.customer_name}
${statusBadge(s.status)}
${statusBadge(s.type)} ${s.priority} priority ${s.scheduled_date}
${s.description ? `
📋 ${s.description}
` : ''} ${s.status!=='completed'&&s.status!=='cancelled' ? `
COMPLETE CALL
` : `
✓ Completed
`}
`).join('') : `
${ICONS.check}

No service calls today

Check back or contact dispatch

`}`; } catch(e) { $('content').innerHTML=`

${e.message}

`; } } async function techCompleteService(id) { const work = document.getElementById(`work-${id}`)?.value?.trim(); if (!work) { toast('Please describe work performed','error'); return; } try { await api('service-calls','POST',{ id, status:'completed', work_performed:work, completed_at:new Date().toISOString() }); toast('Service call completed!','success'); pgServiceBoard(); } catch(e) { toast(e.message,'error'); } } // ── Boot ────────────────────────────────── window.addEventListener('DOMContentLoaded', checkSession);