/* Admin → Reports: attendance, transport, finance */ function ReportsScreen() { const [log, setLog] = React.useState([]); React.useEffect(() => { const read = () => { try { setLog(JSON.parse(localStorage.getItem('jl_incidentlog') || '[]')); } catch(e){} }; read(); const onStore = (e) => { if (e.key === 'jl_incidentlog') read(); }; window.addEventListener('storage', onStore); const iv = setInterval(read, 1500); return () => { window.removeEventListener('storage', onStore); clearInterval(iv); }; }, []); const fmt = (t) => new Date(t).toLocaleString([], { month:'short', day:'numeric', hour:'numeric', minute:'2-digit' }); const dur = (a,b) => { const m = Math.max(1, Math.round((b-a)/60000)); return m<60?m+'m':Math.floor(m/60)+'h '+(m%60)+'m'; }; const downloadCSV = (name, rows) => { const csv = rows.map(r => r.map(c => { const s = String(c==null?'':c); return /[",\n]/.test(s) ? '"'+s.replace(/"/g,'""')+'"' : s; }).join(',')).join('\n'); const blob = new Blob([csv], { type:'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = name; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>URL.revokeObjectURL(url), 1000); jlToast('Downloaded ' + name, { icon:'file-down', color:'#1F9D57' }); }; const CSVDATA = { 'Attendance Summary': [['Day','Attendance %'], ...week.map(w=>[w.d, w.a]), ['Average', 93.4]], 'Transport Utilization': [['Route','Bus','Riders'],['KMS Loop 3','#12',9],['Elm & Park','#07',14],['North Ridge','#19',9],['Cinco West','#23',12],['South Gate','#04',0]], 'Financial Statement': [['Category','Collected','Outstanding'],['Lunch accounts','$18,240','$1,120'],['Field-trip fees','$4,560','$380'],['Fundraisers','$15,040','—']], }; const genReport = (c) => { if (c.title === 'Incident Log') { if (!log.length) { jlToast('No incidents to export', { icon:'shield-check' }); return; } downloadCSV('incident-log.csv', [['Incident','Bus','Driver','Reported','Resolved','Response'], ...log.map(r=>[r.label, r.bus, r.driver, fmt(r.reportedAt), fmt(r.resolvedAt), dur(r.reportedAt, r.resolvedAt)])]); return; } downloadCSV(c.title.toLowerCase().replace(/[^a-z]+/g,'-').replace(/^-|-$/g,'') + '.csv', CSVDATA[c.title] || [['Report'],[c.title]]); }; const week = [ { d:'Mon', a:96 }, { d:'Tue', a:94 }, { d:'Wed', a:97 }, { d:'Thu', a:91 }, { d:'Fri', a:89 }, ]; const cards = [ { title:'Attendance Summary', sub:'Daily & by grade', icon:'user-check', tint:'var(--blue-100)', ic:'var(--blue-500)' }, { title:'Transport Utilization', sub:'Riders per route', icon:'bus', tint:'var(--gold-100)', ic:'var(--gold-600)' }, { title:'Financial Statement', sub:'Collections & dues', icon:'banknote', tint:'var(--success-100)', ic:'var(--success-600)' }, { title:'Incident Log', sub:'Safety & behavior', icon:'shield-alert', tint:'var(--danger-100)', ic:'var(--danger-500)' }, ]; return (