/* Admin → Bus Routes editor: manage routes, stops, and bus/driver assignment. */
function RoutesScreen() {
const DRIVERS = ['Marcus Reed', 'Dana Cole', 'Sam Ortiz', 'Priya Nair', 'Leon Baptiste', 'Unassigned'];
const BUSES = ['#12', '#07', '#19', '#23', '#04', 'Unassigned'];
const [routes, setRoutes] = React.useState([
{ id:'r3', name:'KMS Loop 3', period:'AM', bus:'#12', driver:'Marcus Reed', stops:[
{ id:1, name:'Oak & 3rd', time:'6:52 AM', students:2 },
{ id:2, name:'Maple Court', time:'6:58 AM', students:1 },
{ id:3, name:'Katy Elm Village', time:'7:05 AM', students:3 },
{ id:4, name:'Riverstone Gate', time:'7:12 AM', students:1 },
{ id:5, name:'Cinco Ranch Pkwy', time:'7:20 AM', students:2 },
]},
{ id:'r1', name:'Elm & Park', period:'AM', bus:'#07', driver:'Dana Cole', stops:[
{ id:1, name:'Park Place', time:'6:48 AM', students:4 },
{ id:2, name:'Elm & 5th', time:'6:55 AM', students:5 },
{ id:3, name:'Grand Vista', time:'7:04 AM', students:5 },
]},
{ id:'r2', name:'North Ridge', period:'AM', bus:'#19', driver:'Sam Ortiz', stops:[
{ id:1, name:'Ridge Top', time:'6:40 AM', students:3 },
{ id:2, name:'Summit Way', time:'6:50 AM', students:6 },
]},
{ id:'r4', name:'South Gate', period:'PM', bus:'Unassigned', driver:'Unassigned', stops:[
{ id:1, name:'Gateway Blvd', time:'3:45 PM', students:0 },
]},
]);
const [selId, setSelId] = React.useState('r3');
const [dirty, setDirty] = React.useState(false);
const sel = routes.find(r => r.id === selId);
// Publish route→bus assignments so the Fleet Map reflects them live.
React.useEffect(() => {
try {
const map = {};
routes.forEach(r => {
if (r.bus && r.bus !== 'Unassigned') {
const key = r.bus.replace('#','');
map[key] = { route:r.name, driver:r.driver, students:r.stops.reduce((m,s)=>m+(parseInt(s.students)||0),0), stops:r.stops.length };
}
});
localStorage.setItem('jl_routes', JSON.stringify(map));
} catch(e) {}
}, [routes]);
const patch = (fn) => { setRoutes(rs => rs.map(r => r.id===selId ? fn(r) : r)); setDirty(true); };
const setField = (k, v) => patch(r => ({ ...r, [k]:v }));
const setStop = (sid, k, v) => patch(r => ({ ...r, stops:r.stops.map(s => s.id===sid ? { ...s, [k]:v } : s) }));
const addStop = () => patch(r => ({ ...r, stops:[...r.stops, { id:Date.now(), name:'New stop', time:'—', students:0 }] }));
const rmStop = (sid) => patch(r => ({ ...r, stops:r.stops.filter(s => s.id!==sid) }));
const totalStops = routes.reduce((n,r)=>n+r.stops.length,0);
const totalStudents = routes.reduce((n,r)=>n+r.stops.reduce((m,s)=>m+ (parseInt(s.students)||0),0),0);
const unassigned = routes.filter(r=>r.bus==='Unassigned'||r.driver==='Unassigned').length;
return (
{/* route list */}
{ const id='r'+Date.now(); setRoutes(rs=>[...rs, { id, name:'New Route', period:'AM', bus:'Unassigned', driver:'Unassigned', stops:[] }]); setSelId(id); setDirty(true); }} style={{ display:'flex', alignItems:'center', gap:6, background:'var(--navy-600)', color:'#fff', border:0, borderRadius:9, padding:'8px 13px', fontWeight:700, fontSize:12.5, cursor:'pointer', fontFamily:'var(--font-sans)' }}>Add} pad={false}>
{routes.map((r,i) => {
const on = r.id===selId;
const needs = r.bus==='Unassigned'||r.driver==='Unassigned';
return (
);
})}
{/* editor */}
{ setDirty(false); jlToast(sel.name + ' saved', { icon:'check-check' }); }} style={{ display:'flex', alignItems:'center', gap:6, background:'var(--gold-400)', color:'var(--on-gold)', border:0, borderRadius:9, padding:'8px 13px', fontWeight:700, fontSize:12.5, cursor:'pointer', fontFamily:'var(--font-sans)', boxShadow:'var(--shadow-gold)' }}>Save Route}>
{sel && (
{/* route meta */}
{['AM','PM'].map(p => (
))}
{/* stops */}
Stops ({sel.stops.length})
{sel.stops.length===0 &&
No stops yet — add the first stop.
}
{sel.stops.map((s, idx) => (
{idx+1}
setStop(s.id,'name',e.target.value)} style={{ ...rtField, flex:2, marginTop:0 }} />
setStop(s.id,'time',e.target.value)} style={{ ...rtField, width:90, marginTop:0 }} />
setStop(s.id,'students',e.target.value.replace(/[^0-9]/g,''))} style={{ ...rtField, width:46, marginTop:0, textAlign:'center', fontFamily:'var(--font-mono)' }} />
))}
)}
);
}
const rtLbl = { display:'block', fontSize:11.5, fontWeight:700, color:'var(--gray-500)', marginBottom:5 };
const rtField = { width:'100%', boxSizing:'border-box', border:'1px solid var(--gray-200)', borderRadius:9, padding:'9px 11px', fontSize:13.5, fontFamily:'var(--font-sans)', color:'var(--ink)', outline:'none', background:'#fff' };
Object.assign(window, { RoutesScreen });