// ============================================================ // Fleet Module // ============================================================ let fleetState = { vehicleId: null, tab: 'overview' }; async function renderFleet(params = {}) { // All roles can use fuel slip; techs see simplified fleet page if (!Auth.can('fleet','view')) { return renderTechFleetPage(); } const content = document.getElementById('page-content'); content.innerHTML = `
Vehicles, costs, service schedules and documents
Log fuel and expenses for company vehicles
Capture your fuel slip with odometer reading and a photo of the slip
Failed to load vehicles.
'; return; } const vehicles = res.data.vehicles || []; if (!vehicles.length) { wrap.innerHTML = `Failed.
'; return; } const rows = res.data.summary || []; if (!rows.length) { wrap.innerHTML = '| Vehicle | ${allTypes.map(t=>`${t} | `).join('')}Total |
|---|---|---|
| ${veh} | ${allTypes.map(t => `R ${(data.types[t]||0).toFixed(2)} | `).join('')}R ${data.total.toFixed(2)} |
Not found.
'; return; } const v = res.data.vehicle; content.innerHTML = `${v.registration}
${v.make} ${v.model}
${v.year||'β'}
${v.vin||'β'}
${v.engine_no||'β'}
${v.color||'β'}
${parseInt(v.current_odo).toLocaleString()} km
R ${parseFloat(v.cost_per_km||0).toFixed(2)}
R ${parseFloat(v.quote_rate_per_km||0).toFixed(2)}
${v.notes}
Failed.
'; return; } const costs = res.data.costs || []; if (!costs.length) { wrap.innerHTML = '| Date | Type | Description | ODO | Amount | Slip | By | |
|---|---|---|---|---|---|---|---|
| ${Fmt.date(c.cost_date)} | ${c.cost_type} | ${c.description||'β'} ${c.litres?`(${c.litres}L)`:''} | ${c.odo_reading ? parseInt(c.odo_reading).toLocaleString()+' km' : 'β'} | R ${parseFloat(c.amount).toFixed(2)} | ${c.slip_image_path ? `πΌ` : 'β'} | ${c.created_by_name||'β'} | ${Auth.can('fleet','delete') ? `` : ''} |
| Total | R ${total.toFixed(2)} | ||||||
Failed.
'; return; } const schedules = res.data.schedules || []; if (!schedules.length) { wrap.innerHTML = 'Failed to load history.
'; return; } const history = res.data.history || []; if (!history.length) { wrap.innerHTML = '| Date | Service Type | ODO | Cost | Slip | Notes | Recorded By |
|---|---|---|---|---|---|---|
| ${Fmt.date(h.service_date)} | ${h.service_type} | ${parseInt(h.odo_reading).toLocaleString()} km | ${h.cost_amount ? 'R '+parseFloat(h.cost_amount).toFixed(2) : 'β'} | ${h.slip_image_path ? `π View` : 'No slip'} | ${h.notes||'β'} | ${h.recorded_by_name} |
Failed.
'; return; } const docs = res.data.documents || []; if (!docs.length) { wrap.innerHTML = '${res.message}
`; return; } const logs = res.data.logs || []; const m = res.data.metrics || {}; const monthly = res.data.monthly || []; _travelLogLatestOdo = res.data.latest_odo || 0; // Source badge helper const sourceBadge = (src) => { const map = { manual: 'badge-muted', job_card: 'badge-info', fuel_slip: 'badge-warning' }; const labels = { manual: 'Manual', job_card: 'Job Card', fuel_slip: 'Fuel Slip' }; return `${labels[src]||src}`; }; // Metrics cards const totalKm = m.max_odo && m.min_odo ? m.max_odo - m.min_odo : 0; const days = m.first_date && m.last_date ? Math.max(1, (new Date(m.last_date)-new Date(m.first_date))/(1000*86400)) : 1; const dailyAvg = totalKm / days; const monthlyAvg = dailyAvg * 30.44; if (metrics) metrics.innerHTML = `| Month | Est. km | Readings |
|---|---|---|
| ${mn.month} | ${parseInt(mn.km_this_month||0).toLocaleString()} | ${mn.readings} |
| Date | ODO (km) | +/- km | Source | Reference | Recorded By | |
|---|---|---|---|---|---|---|
| ${Fmt.date(l.reading_date)} | ${parseInt(l.odo_reading).toLocaleString()} km | ${diffStr} | ${sourceBadge(l.source)} | ${l.source_ref ? `${l.source_ref}` : 'β'} | ${l.recorded_by_name||'System'} | ${l.source==='manual'?``:''} |