}
const load = (k) => localStorage.getItem('otis-' + k);
const save = (k, v) => localStorage.setItem('otis-' + k, v);
// LOGO
const lInput = document.getElementById('logoInput');
const uLogo = document.getElementById('userLogo');
const uploadDiv = document.getElementById('uploadPrompt');
function applyLogo(d) {
if (!d) return;
uLogo.src = d;
uLogo.style.display = 'block';
if (uploadDiv) uploadDiv.style.display = 'none';
}
const savedLogo = load('user-logo');
if (savedLogo) applyLogo(savedLogo);
document.getElementById('logoClickZone')?.addEventListener('click', () => lInput.click());
if (lInput) {
lInput.onchange = (e) => {
const f = e.target.files[0];
if (!f) return;
if (f.size > 2 * 1024 * 1024) {
alert("File too large. Use < 2MB.");
return;
}
const r = new FileReader();
r.onload = (ev) => {
const d = ev.target.result;
save('user-logo', d);
applyLogo(d);
};
r.readAsDataURL(f);
};
}
// DILUTION
const dTot = document.getElementById('dil-total');
const dRat = document.getElementById('dil-ratio');
const dRes = document.getElementById('dil-result');
function calcDil() {
const t = parseFloat(dTot.value) || 0;
const r = parseFloat(dRat.value) || 0;
const c = t / (r + 1);
const w = t - c;
dRes.innerHTML = `CHEMICAL: ${c.toFixed(1)} oz
WATER: ${w.toFixed(1)} oz`; } if (dTot && dRat) { dTot.oninput = calcDil; dRat.oninput = calcDil; } // DETAILED LOGGING FUNCTIONS (14-day auto-clean) function getLog(key) { let raw = localStorage.getItem('otis-log-' + key); if (!raw) return []; try { let logs = JSON.parse(raw); let now = Date.now(); let cutoff = now - 14 * 86400000; let filtered = logs.filter(l => l.t > cutoff); if (filtered.length !== logs.length) { localStorage.setItem('otis-log-' + key, JSON.stringify(filtered)); } return filtered; } catch(e) { return []; } } function addToLog(key, data) { let logs = getLog(key); logs.push({ t: Date.now(), d: new Date().toLocaleDateString(), ...data }); localStorage.setItem('otis-log-' + key, JSON.stringify(logs)); } // DISPLAY function updateDisplay() { const p = parseFloat(load('daily-profit')) || 0; const h = parseFloat(load('daily-hours')) || 0; const mi = parseInt(load('daily-miles')) || 0; const e = parseFloat(load('daily-expenses')) || 0; document.getElementById('m-profit').innerText = "$" + p.toFixed(2); document.getElementById('m-hours').innerText = h.toFixed(1) + "h"; document.getElementById('m-miles').innerText = mi + " mi"; document.getElementById('m-exp').innerText = "$" + e.toFixed(2); document.getElementById('m-rate').innerText = h > 0 ? "$" + (p / h).toFixed(2) + "/hr" : "$0.00/hr"; const v = load('j-vehicle'); const pr = load('j-price'); document.getElementById('job-detail-val').innerText = v ? v + " | $" + pr : "SET JOB"; const sT = load('start-raw'); if (sT) document.getElementById('start-val').innerText = "START: " + new Date(sT).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); renderFullHistory(); } window.toggleModal = (id) => { const m = document.getElementById(id); if (m && id !== 'license-modal') m.style.display = m.style.display === 'block' ? 'none' : 'block'; }; window.logDetailedJob = () => { let v = prompt("Vehicle:"); let p = prompt("Price:"); if (v && p) { save('j-vehicle', v); save('j-price', p); updateDisplay(); } }; window.logTime = (type) => { const n = new Date(); save(type + '-raw', n.toISOString()); if (type === 'end') { setTimeout(() => { if (confirm("Finalize Job?")) finalize(); }, 300); } updateDisplay(); }; window.handleMileage = function() { let s = load('m-start'); const lb = document.getElementById('miles-val'); if (!s) { let v = prompt("Start Odo:"); if (v) { save('m-start', v); lb.innerText = "START: " + v; } } else { let v = prompt("End Odo:"); if (v) { let t = parseInt(v) - parseInt(s); save('daily-miles', (parseInt(load('daily-miles')) || 0) + t); localStorage.removeItem('otis-m-start'); lb.innerText = "TRIP: " + t; updateDisplay(); } } }; // Save expense (daily total + 14-day log) window.saveExpense = () => { const a = document.getElementById('exp-amt'); const d = document.getElementById('exp-desc'); const amt = parseFloat(a.value) || 0; const desc = d ? d.value : 'Expense'; if (amt > 0) { // Daily total (original) save('daily-expenses', (parseFloat(load('daily-expenses')) || 0) + amt); // 14-day log addToLog('expense', { description: desc, amount: amt }); if (d) d.value = ''; a.value = ''; toggleModal('expense-modal'); updateDisplay(); } }; function finalize() { const p = parseFloat(load('j-price')) || 0; const v = load('j-vehicle') || "Service"; const s = load('start-raw'); const e = load('end-raw'); if (!s || !e) return; const diff = (new Date(e) - new Date(s)) / 3600000; save('daily-profit', (parseFloat(load('daily-profit')) || 0) + p); save('daily-hours', (parseFloat(load('daily-hours')) || 0) + diff); let history = JSON.parse(load('history-data') || "[]"); history.unshift({ date: new Date().toLocaleDateString(), vehicle: v, earned: p, hours: diff.toFixed(1) }); save('history-data', JSON.stringify(history.slice(0, 50))); ['j-vehicle', 'j-price', 'start-raw', 'end-raw'].forEach(k => localStorage.removeItem('otis-' + k)); document.getElementById('start-val').innerText = "START: --:--"; document.getElementById('end-val').innerText = "END: --:--"; document.getElementById('job-detail-val').innerText = "SET JOB"; updateDisplay(); } function renderFullHistory() { const container = document.getElementById('history-log-container'); if (!container) return; let jobs = JSON.parse(load('history-data') || "[]"); let inspections = getLog('inspection'); let expenses = getLog('expense'); let html = '
`;
});
if (inspections.length) {
html += '
`;
});
}
if (expenses.length) {
html += '
`;
});
}
if (!jobs.length && !inspections.length && !expenses.length) {
html = '
WATER: ${w.toFixed(1)} oz`; } if (dTot && dRat) { dTot.oninput = calcDil; dRat.oninput = calcDil; } // DETAILED LOGGING FUNCTIONS (14-day auto-clean) function getLog(key) { let raw = localStorage.getItem('otis-log-' + key); if (!raw) return []; try { let logs = JSON.parse(raw); let now = Date.now(); let cutoff = now - 14 * 86400000; let filtered = logs.filter(l => l.t > cutoff); if (filtered.length !== logs.length) { localStorage.setItem('otis-log-' + key, JSON.stringify(filtered)); } return filtered; } catch(e) { return []; } } function addToLog(key, data) { let logs = getLog(key); logs.push({ t: Date.now(), d: new Date().toLocaleDateString(), ...data }); localStorage.setItem('otis-log-' + key, JSON.stringify(logs)); } // DISPLAY function updateDisplay() { const p = parseFloat(load('daily-profit')) || 0; const h = parseFloat(load('daily-hours')) || 0; const mi = parseInt(load('daily-miles')) || 0; const e = parseFloat(load('daily-expenses')) || 0; document.getElementById('m-profit').innerText = "$" + p.toFixed(2); document.getElementById('m-hours').innerText = h.toFixed(1) + "h"; document.getElementById('m-miles').innerText = mi + " mi"; document.getElementById('m-exp').innerText = "$" + e.toFixed(2); document.getElementById('m-rate').innerText = h > 0 ? "$" + (p / h).toFixed(2) + "/hr" : "$0.00/hr"; const v = load('j-vehicle'); const pr = load('j-price'); document.getElementById('job-detail-val').innerText = v ? v + " | $" + pr : "SET JOB"; const sT = load('start-raw'); if (sT) document.getElementById('start-val').innerText = "START: " + new Date(sT).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); renderFullHistory(); } window.toggleModal = (id) => { const m = document.getElementById(id); if (m && id !== 'license-modal') m.style.display = m.style.display === 'block' ? 'none' : 'block'; }; window.logDetailedJob = () => { let v = prompt("Vehicle:"); let p = prompt("Price:"); if (v && p) { save('j-vehicle', v); save('j-price', p); updateDisplay(); } }; window.logTime = (type) => { const n = new Date(); save(type + '-raw', n.toISOString()); if (type === 'end') { setTimeout(() => { if (confirm("Finalize Job?")) finalize(); }, 300); } updateDisplay(); }; window.handleMileage = function() { let s = load('m-start'); const lb = document.getElementById('miles-val'); if (!s) { let v = prompt("Start Odo:"); if (v) { save('m-start', v); lb.innerText = "START: " + v; } } else { let v = prompt("End Odo:"); if (v) { let t = parseInt(v) - parseInt(s); save('daily-miles', (parseInt(load('daily-miles')) || 0) + t); localStorage.removeItem('otis-m-start'); lb.innerText = "TRIP: " + t; updateDisplay(); } } }; // Save expense (daily total + 14-day log) window.saveExpense = () => { const a = document.getElementById('exp-amt'); const d = document.getElementById('exp-desc'); const amt = parseFloat(a.value) || 0; const desc = d ? d.value : 'Expense'; if (amt > 0) { // Daily total (original) save('daily-expenses', (parseFloat(load('daily-expenses')) || 0) + amt); // 14-day log addToLog('expense', { description: desc, amount: amt }); if (d) d.value = ''; a.value = ''; toggleModal('expense-modal'); updateDisplay(); } }; function finalize() { const p = parseFloat(load('j-price')) || 0; const v = load('j-vehicle') || "Service"; const s = load('start-raw'); const e = load('end-raw'); if (!s || !e) return; const diff = (new Date(e) - new Date(s)) / 3600000; save('daily-profit', (parseFloat(load('daily-profit')) || 0) + p); save('daily-hours', (parseFloat(load('daily-hours')) || 0) + diff); let history = JSON.parse(load('history-data') || "[]"); history.unshift({ date: new Date().toLocaleDateString(), vehicle: v, earned: p, hours: diff.toFixed(1) }); save('history-data', JSON.stringify(history.slice(0, 50))); ['j-vehicle', 'j-price', 'start-raw', 'end-raw'].forEach(k => localStorage.removeItem('otis-' + k)); document.getElementById('start-val').innerText = "START: --:--"; document.getElementById('end-val').innerText = "END: --:--"; document.getElementById('job-detail-val').innerText = "SET JOB"; updateDisplay(); } function renderFullHistory() { const container = document.getElementById('history-log-container'); if (!container) return; let jobs = JSON.parse(load('history-data') || "[]"); let inspections = getLog('inspection'); let expenses = getLog('expense'); let html = '
📋 JOBS
';
jobs.forEach(job => {
html += `${job.date}
${job.vehicle} — $${job.earned} (${job.hours}h)
🔍 PRE-EXISTING DAMAGE
';
inspections.forEach(ins => {
let items = ins.checked || [];
html += `${ins.d}
${items.join(', ') || 'None recorded'}
💰 EXPENSES (14d)
';
expenses.forEach(exp => {
html += `${exp.d}
${exp.description || 'Expense'} — -$${exp.amount || exp.a}
No logs yet
';
}
container.innerHTML = html;
}
window.resetDay = function() {
if (confirm("Reset Totals?")) {
['daily-profit', 'daily-hours', 'daily-miles', 'daily-expenses', 'history-data'].forEach(k => localStorage.removeItem('otis-' + k));
location.reload();
}
};
const inspCont = document.getElementById('check-list-container');
if (inspCont) {
const labs = ["#1 Driver Front", "#2 Driver Cockpit", "#3 Driver Backseat", "#4 Roof", "#5 Driver Rear", "#6 Pass Rear", "#7 Cargo", "#8 Trunk", "#9 Pass Backseat", "#10 Pass Roof", "#11 Pass Cockpit", "#12 Pass Front", "#13 Hood"];
inspCont.innerHTML = '';
labs.forEach(l => {
inspCont.innerHTML += ` ${l}
`;
});
}
// Hook inspection save on modal close
let origToggle = window.toggleModal;
window.toggleModal = function(id) {
if (id === 'inspect-modal' && document.getElementById('inspect-modal').style.display === 'block') {
let checkboxes = document.querySelectorAll('#check-list-container input[type="checkbox"]');
let labels = [];
checkboxes.forEach((cb, idx) => {
if (cb.checked) {
let divs = document.querySelectorAll('#check-list-container div');
let labelText = divs[idx] ? divs[idx].innerText.split('\n')[0] : 'Area ' + (idx + 1);
labels.push(labelText);
}
});
if (labels.length) {
addToLog('inspection', { checked: labels, total: checkboxes.length });
}
}
origToggle(id);
};
updateDisplay();
})();