/* Admin → Dashboard overview */
function Sparkline({ data, color }) {
const w = 96, h = 30, max = Math.max(...data), min = Math.min(...data);
const rng = max - min || 1;
const pts = data.map((v, i) => [ (i/(data.length-1))*w, h - ((v-min)/rng)*(h-4) - 2 ]);
const d = pts.map((p,i)=> (i?'L':'M')+p[0].toFixed(1)+' '+p[1].toFixed(1)).join(' ');
const area = d + ` L${w} ${h} L0 ${h} Z`;
const gid = 'sg' + color.replace(/[^a-z0-9]/gi,'');
return (
);
}
function StatTile({ icon, tint, ic, value, label, delta, deltaColor, trend, spark, onClick }) {
const [hover, setHover] = React.useState(false);
const up = trend === 'up', down = trend === 'down';
const pillBg = up ? 'var(--success-100)' : down ? 'var(--danger-100)' : 'var(--gray-100)';
const pillFg = up ? 'var(--success-600)' : down ? 'var(--danger-600)' : 'var(--gray-600)';
return (
setHover(true)} onMouseLeave={()=>setHover(false)} style={{
background:'#fff', border:'1px solid var(--gray-200)', borderRadius:16, padding:'18px',
boxShadow: hover ? 'var(--shadow-md)' : 'var(--shadow-sm)', cursor: onClick?'pointer':'default',
transform: hover ? 'translateY(-2px)' : 'none', transition:'transform .16s ease, box-shadow .16s ease',
position:'relative', overflow:'hidden',
}}>
{delta && (
{(up||down) && }
{delta}
)}
{value}
);
}
function Panel({ title, action, children, pad=true }) {
return (
);
}
function AdminMap() {
const BUSES = [
{ id:'12', driver:'Mark Thompson', route:'Route 1 · East', riders:24, eta:'5 min', status:'On Route', color:'var(--success-500)', x:22, y:30, path:'M40 200 L40 120 L150 120 L150 60 L300 60' },
{ id:'7', driver:'Carla Reyes', route:'Route 3 · West Loop', riders:19, eta:'8 min late', status:'Delayed', color:'var(--danger-500)', x:60, y:56, path:'M300 60 L300 150 L180 150 L180 220' },
{ id:'15', driver:'Maya Okafor', route:'Route 4 · South', riders:17, eta:'On time', status:'On Route', color:'var(--success-500)', x:42, y:74, path:'M120 230 L120 150 L60 150 L60 60' },
];
const [sel, setSel] = React.useState('12');
const [hoverPin, setHoverPin] = React.useState(null);
const cur = BUSES.find(b => b.id === sel);
return (
{/* terrain */}
{/* water */}
{/* roads */}
{/* selected route line */}
{/* school marker */}
{/* bus pins */}
{BUSES.map(b => {
const on = b.id === sel, hov = hoverPin === b.id;
return (
);
})}
{/* live chip */}
{BUSES.length} buses live
{/* selected bus detail bar */}
Bus #{cur.id} · {cur.driver}
{cur.route} · {cur.riders} riders
{cur.status==='Delayed'?:}{cur.eta}
);
}
function DashboardScreen({ onNav }) {
const [alerts, setAlerts] = React.useState([
{ id:1, icon:'bus', tint:'var(--danger-100)', ic:'var(--danger-500)', t:'EJ Hatten missed Bus #12', s:'Self-reported · Stayed after class', time:'just now' },
{ id:2, icon:'alert-triangle', tint:'var(--danger-100)', ic:'var(--danger-500)', t:'Bus #7 running 8 min late', s:'Route 3 · West Loop', time:'3m ago' },
{ id:3, icon:'user-x', tint:'var(--warning-100)', ic:'var(--warning-600)', t:'Unverified pickup attempt', s:'Katy Middle · Sophia P.', time:'12m ago' },
]);
const dismissAlert = (id) => { setAlerts(a => a.filter(x => x.id !== id)); jlToast('Alert resolved', { icon:'check' }); };
const [queue, setQueue] = React.useState([
{ n:'EJ Hatten', g:'Grade 6', ty:'Car Rider', t:'3:30 PM', st:'Waiting' },
{ n:'James Walker', g:'Grade 7', ty:'Car Rider', t:'3:31 PM', st:'Waiting' },
{ n:'Sophia Perez', g:'Grade 6', ty:'Walker', t:'3:31 PM', st:'Picked Up' },
{ n:'Liam Carter', g:'Grade 8', ty:'Bus #12', t:'3:32 PM', st:'On Bus' },
]);
const ST = {
'Waiting': { bg:'var(--gray-200)', fg:'var(--gray-600)' },
'Picked Up': { bg:'var(--success-100)', fg:'var(--success-600)' },
'On Bus': { bg:'var(--blue-100)', fg:'var(--blue-600)' },
};
const release = (i) => { setQueue(q => q.map((r,idx)=> idx===i ? { ...r, st:'Picked Up' } : r)); jlToast(queue[i].n + ' marked picked up', { icon:'check-circle-2' }); };
const waiting = queue.filter(r => r.st==='Waiting').length;
return (
{/* stat row */}
onNav && onNav('students')} />
onNav && onNav('transportation')} />
onNav && onNav('pickup')} />
onNav && onNav('payments')} />
{/* two columns */}
Live} pad={false}>
jlToast('Showing all alerts', { icon:'bell' })} style={{ fontSize:12.5, fontWeight:700, color:'var(--blue-500)', cursor:'pointer' }}>View All}>
{alerts.length === 0 && (
All caught up — no active alerts
)}
{alerts.map((a,i)=>(
{a.time}
))}
{/* charts row */}
{/* pickup queue table */}
onNav && onNav('pickup')} style={{ fontSize:12.5, fontWeight:700, color:'var(--blue-500)', cursor:'pointer' }}>Open Pickup Manager} pad={false}>
StudentGradeMethodTimeStatus
{queue.map((r,i)=>(
w[0]).join('')} color={['blue','navy','gold','green'][i%4]} size={30} />{r.n}
{r.g}
{r.ty}
{r.t}
{r.st === 'Waiting'
?
: {r.st}}
))}
);
}
Object.assign(window, { DashboardScreen, StatTile, Panel, AdminMap });