/* Pickup / Dismissal plan */
function PickupScreen({ onBack }) {
const days = ['Mon','Tue','Wed','Thu','Fri'];
const [day, setDay] = React.useState('Wed');
const [person, setPerson] = React.useState(null);
const METHODS = [
{ id:'car', label:'Car Rider', icon:'car-front', time:'3:30 PM' },
{ id:'bus', label:'Bus Rider', icon:'bus-front', time:'3:25 PM' },
{ id:'walk', label:'Walker', icon:'footprints', time:'3:35 PM' },
{ id:'after', label:'Aftercare', icon:'clock', time:'5:30 PM' },
];
const [method, setMethod] = React.useState('car');
const [sheet, setSheet] = React.useState(false);
const [people, setPeople] = React.useState([
{ initials:'JH', color:'navy', name:'Jermaine Hatten', rel:'Parent', phone:'(281) 555-0142' },
{ initials:'AH', color:'cyan', name:'Ashley Hatten', rel:'Parent', phone:'(281) 555-0177' },
]);
const [addOpen, setAddOpen] = React.useState(false);
const [qr, setQr] = React.useState(false);
const [checkedIn, setCheckedIn] = React.useState(false);
const [pos, setPos] = React.useState(4);
React.useEffect(() => {
if (!checkedIn || pos <= 1) return;
const t = setTimeout(() => setPos(p => Math.max(1, p - 1)), 3200);
return () => clearTimeout(t);
}, [checkedIn, pos]);
const doCheckIn = () => {
setQr(false); setCheckedIn(true); setPos(4);
jlToast('Checked in · the school knows you\u2019re here', { icon:'check-check' });
};
const [fName, setFName] = React.useState('');
const [fRel, setFRel] = React.useState('');
const [fPhone, setFPhone] = React.useState('');
const ADD_COLORS = ['gold','green','red','blue','navy','cyan'];
const initialsOf = (n) => n.trim().split(/\s+/).map(w=>w[0]).slice(0,2).join('').toUpperCase() || '?';
const canSave = fName.trim() && fRel.trim();
const savePerson = () => {
if (!canSave) return;
const p = { initials: initialsOf(fName), color: ADD_COLORS[people.length % ADD_COLORS.length], name: fName.trim(), rel: fRel.trim(), phone: fPhone.trim() || 'No phone added' };
setPeople(ps => [...ps, p]);
setAddOpen(false); setFName(''); setFRel(''); setFPhone('');
jlToast(p.name + ' added as authorized', { icon:'user-check' });
};
const cur = METHODS.find(m => m.id===method);
return (
{/* day picker */}
{days.map(d => (
setDay(d)} style={{
flex:1, padding:'10px 0', borderRadius:12, border:0, cursor:'pointer',
fontWeight:700, fontSize:13, fontFamily:'var(--font-sans)',
background: day===d ? 'var(--navy-600)' : '#fff',
color: day===d ? '#fff' : 'var(--gray-600)',
boxShadow: day===d ? 'none' : 'inset 0 0 0 1px var(--gray-200)',
}}>{d}
))}
{/* today's pickup */}
TODAY'S PICKUP
{cur.time}
You · Katy Middle School
{!checkedIn ? (
setQr(true)} style={{ width:'100%', marginTop:16, display:'flex', alignItems:'center', justifyContent:'center', gap:9, background:'var(--gold-400)', color:'var(--on-gold)', border:0, borderRadius:12, padding:'14px', fontWeight:800, fontSize:15, cursor:'pointer', fontFamily:'var(--font-sans)', boxShadow:'var(--shadow-gold)' }}>
I'm Here — Check In
) : (
Checked in at {new Date().toLocaleTimeString([], {hour:'numeric', minute:'2-digit'})}
)}
setSheet(true)} style={{ width:'100%', marginTop:9, background:'#fff', color:'var(--navy-600)', border:'1px solid var(--gray-200)', borderRadius:12, padding:'12px', fontWeight:700, fontSize:14, cursor:'pointer', fontFamily:'var(--font-sans)' }}>Change Pickup Method
{/* live queue (after check-in) */}
{checkedIn && (
{pos>1 ? '#'+pos : 'Next'}
{pos>1 ? 'in the car-rider line' : 'EJ is heading out now'}
{[1,2,3,4,5].map(n => (
= pos ? 'var(--gold-400)' : 'rgba(255,255,255,.22)' }} />
))}
{pos>1 ? 'Staff are calling students as cars arrive. Please stay in your vehicle.' : 'A staff member is walking EJ to your car.'}
)}
{/* authorized pickups */}
Authorized Pickups
{people.map((p, i) => (
{i > 0 && }
setPerson(p)} />
))}
setAddOpen(true)} style={{ width:'100%', marginTop:12, display:'flex', alignItems:'center', justifyContent:'center', gap:8, background:'#fff', color:'var(--navy-600)', border:'1.5px dashed var(--gray-300)', borderRadius:12, padding:'13px', fontWeight:700, fontSize:14, cursor:'pointer', fontFamily:'var(--font-sans)' }}>
Add Authorized Person
{/* proximity banner */}
Live from your location · we'll tell the school you're on the way.
{/* bottom sheet: choose method */}
{sheet && (
setSheet(false)} style={{ position:'absolute', inset:0, zIndex:60, background:'rgba(10,20,40,.4)', display:'flex', alignItems:'flex-end' }}>
e.stopPropagation()} style={{ width:'100%', background:'#fff', borderRadius:'22px 22px 0 0', padding:'10px 18px 34px' }}>
Change Pickup Method
{METHODS.map(m => {
const on = m.id===method;
return (
{ setMethod(m.id); setSheet(false); }} style={{
width:'100%', display:'flex', alignItems:'center', gap:13, padding:'13px 14px', marginBottom:9,
border: on?'1.5px solid var(--blue-500)':'1px solid var(--gray-200)', borderRadius:14,
background: on?'var(--blue-100)':'#fff', cursor:'pointer', fontFamily:'var(--font-sans)', textAlign:'left',
}}>
{m.label}
Dismissal {m.time}
{on && }
);
})}
)}
{/* bottom sheet: QR check-in */}
{qr && (
setQr(false)} style={{ position:'absolute', inset:0, zIndex:60, background:'rgba(10,20,40,.4)', display:'flex', alignItems:'flex-end' }}>
e.stopPropagation()} style={{ width:'100%', background:'#fff', borderRadius:'22px 22px 0 0', padding:'10px 18px 32px' }}>
Check In for Pickup
Hold this code up at the gate scanner
Check In with GPS Instead
setQr(false)} style={{ width:'100%', marginTop:9, background:'transparent', color:'var(--gray-500)', border:0, padding:'10px', fontWeight:700, fontSize:14, cursor:'pointer', fontFamily:'var(--font-sans)' }}>Cancel
)}
{/* bottom sheet: authorized person detail */}
{person && (
setPerson(null)} style={{ position:'absolute', inset:0, zIndex:60, background:'rgba(10,20,40,.4)', display:'flex', alignItems:'flex-end' }}>
e.stopPropagation()} style={{ width:'100%', background:'#fff', borderRadius:'22px 22px 0 0', padding:'10px 18px 34px' }}>
{person.name}
{person.rel} · Authorized
Verified
{person.phone}
Can pick up EJ Hatten
{ jlToast('Calling '+person.name+'…', { icon:'phone' }); setPerson(null); }} style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center', gap:8, background:'var(--navy-600)', color:'#fff', border:0, borderRadius:12, padding:'14px', fontWeight:700, fontSize:14.5, cursor:'pointer', fontFamily:'var(--font-sans)' }}> Call
{ setPeople(ps => ps.filter(x => x.name !== person.name)); jlToast(person.name+' removed', { icon:'user-minus', color:'#E5484D' }); setPerson(null); }} style={{ flex:'0 0 auto', display:'flex', alignItems:'center', justifyContent:'center', gap:8, background:'#fff', color:'var(--danger-500)', border:'1px solid var(--gray-200)', borderRadius:12, padding:'14px 18px', fontWeight:700, fontSize:14.5, cursor:'pointer', fontFamily:'var(--font-sans)' }}> Remove
)}
{/* bottom sheet: add authorized person */}
{addOpen && (
setAddOpen(false)} style={{ position:'absolute', inset:0, zIndex:60, background:'rgba(10,20,40,.4)', display:'flex', alignItems:'flex-end' }}>
e.stopPropagation()} style={{ width:'100%', background:'#fff', borderRadius:'22px 22px 0 0', padding:'10px 18px 30px' }}>
Add Authorized Person
Who can pick up EJ Hatten?
Add Person
setAddOpen(false)} style={{ width:'100%', marginTop:9, background:'transparent', color:'var(--gray-500)', border:0, padding:'10px', fontWeight:700, fontSize:14, cursor:'pointer', fontFamily:'var(--font-sans)' }}>Cancel
)}
);
}
function AddField({ label, value, onChange, placeholder, icon }) {
return (
);
}
function PersonRow({ initials, color, name, rel, onClick }) {
return (
);
}
/* Stylized QR — deterministic module grid (a visual mock, not a real code). */
function FakeQR({ size = 176 }) {
const N = 21;
const finderCell = (r, c) => {
// relative position within whichever 7x7 finder this cell is in
let rr = r, cc = c;
if (r >= N - 7) rr = r - (N - 7);
if (c >= N - 7) cc = c - (N - 7);
rr = rr % 7; cc = cc % 7;
const ring = rr === 0 || rr === 6 || cc === 0 || cc === 6;
const core = rr >= 2 && rr <= 4 && cc >= 2 && cc <= 4;
return ring || core;
};
const cells = [];
for (let r = 0; r < N; r++) {
for (let c = 0; c < N; c++) {
const inFinder = (r < 7 && c < 7) || (r < 7 && c >= N - 7) || (r >= N - 7 && c < 7);
let on;
if (inFinder) on = finderCell(r, c);
else on = ((r * 13 + c * 7 + r * c * 3 + 5) % 5) < 2;
cells.push(on);
}
}
return (
{cells.map((on, i) => (
))}
);
}
Object.assign(window, { PickupScreen });