// ===== CRASH REPORT — 24-field mobile intake (DOTr–DOH–DILG schema) ===== const CRASH_SECTIONS = [ { id: 'loc', n: '01', title: 'Location & time', sub: 'Fields 01–04 · scene context', color: '#2556a8', icon: '📍', fields: [ { id: 'f01', no: '01', label: 'Exact crash location', tag: 'GPS', type: 'gps' }, { id: 'f02', no: '02', label: 'Date & time of crash', type: 'datetime' }, { id: 'f03', no: '03', label: 'Weather', type: 'select', options: ['Clear', 'Cloudy', 'Light rain', 'Heavy rain', 'Fog / mist', 'Typhoon conditions'] }, { id: 'f04', no: '04', label: 'Lighting', type: 'select', options: ['Daylight', 'Dawn / dusk', 'Dark · lit road', 'Dark · unlit road'] }, { id: 'desc', label: 'Plain-language location', type: 'text', optional: true, placeholder: 'e.g. Southbound lane near the underpass' }, ]}, { id: 'road', n: '02', title: 'Road & speed', sub: 'Fields 05–08 · environment & physics', color: '#ff6b2b', icon: '🛣️', fields: [ { id: 'f05', no: '05', label: 'Road surface condition', type: 'chips', options: ['Dry', 'Wet', 'Oil / debris', 'Damaged'] }, { id: 'f06', no: '06', label: 'Road type & geometry', type: 'select', options: ['Six-lane urban arterial', 'Four-lane urban arterial', 'Two-lane provincial road', 'National highway · divided', 'Expressway / tollway', 'Barangay / local road', 'Intersection', 'Bridge / overpass'] }, { id: 'f07', no: '07', label: 'Posted speed limit at site', type: 'number', unit: 'km/h' }, { id: 'f08', no: '08', label: 'Estimated speed at impact', type: 'number', unit: 'km/h', missing: true, help: 'Estimate from skid marks, debris, or witness account. Required under PRSAP Phase 1.' }, ]}, { id: 'veh', n: '03', title: 'Vehicles & drivers', sub: 'Fields 09–14 · who, what, condition', color: '#7b2d8b', icon: '🏍️', fields: [ { id: 'f09', no: '09', label: 'Vehicle type(s) involved', type: 'multi', options: ['Motorcycle', '4-wheel private', 'Tricycle', 'PUJ / Jeepney', 'PUV / bus', 'Truck', 'Bicycle'] }, { id: 'f10', no: '10', label: 'Number of vehicles', type: 'number' }, { id: 'f11', no: '11', label: 'Driver(s) age & sex', type: 'text', placeholder: 'One entry per driver, separated by /' }, { id: 'f12', no: '12', label: 'Driver licence status', type: 'select', options: ['Valid · primary driver', 'Valid · both drivers', 'Expired', 'Suspended', 'No licence', 'Could not verify'] }, { id: 'f13', no: '13', label: 'Helmet / seatbelt use', type: 'select', missing: true, options: ['No helmet (rider) · seatbelt N/A', 'Helmet worn · seatbelt worn', 'Helmet not worn · seatbelt worn', 'Helmet worn · seatbelt not worn', 'Neither worn', 'Could not verify'] }, { id: 'f14', no: '14', label: 'Driver condition (alcohol)', type: 'chips', missing: true, options: ['Negative · tested', 'Positive · BAC ≤ 0.05', 'Positive · BAC > 0.05', 'Refused test', 'Not tested'], help: 'Even "not tested" counts as a recorded value under the joint circular.' }, ]}, { id: 'cas', n: '04', title: 'Casualties', sub: 'Fields 15–20 · victims and outcomes', color: '#ff4757', icon: '🚑', fields: [ { id: 'f15', no: '15', label: 'Pedestrian involvement', type: 'chips', options: ['None', 'Pedestrian struck', 'Pedestrian witness'] }, { id: 'f16', no: '16', label: 'Cyclist involvement', type: 'chips', options: ['None', 'Cyclist struck', 'Cyclist witness'] }, { id: 'f17', no: '17', label: 'Number injured', type: 'number' }, { id: 'f18', no: '18', label: 'Injury severity', type: 'select', options: ['F1 · Fatality at scene', 'S5 · Critical', 'S4 · Severe (ICU)', 'S3 · Serious', 'S2 · Moderate', 'S1 · Minor', 'S0 · No injury'] }, { id: 'f19', no: '19', label: 'Fatalities at scene', type: 'number' }, { id: 'f20', no: '20', label: 'Fatalities ≤ 30 days', type: 'number', missing: true, help: 'Updated via DOH trauma registry linkage after scene close-out.' }, ]}, { id: 'cause', n: '05', title: 'Cause & response', sub: 'Fields 21–23 · classification & handoff', color: '#ffa502', icon: '⚠️', fields: [ { id: 'f21', no: '21', label: 'Cause classification', type: 'multi', max: 3, options: ['C01 · Speeding', 'C02 · DUI alcohol', 'C03 · DUI drugs', 'C04 · Distracted', 'C05 · Failure to yield', 'C06 · Wrong lane', 'C07 · Mechanical', 'C08 · Road condition', 'C09 · Weather'], help: 'Up to three causes. Primary = first selected.' }, { id: 'f22', no: '22', label: 'EMS response time', type: 'number', unit: 'min', help: 'From dispatch to on-scene arrival.' }, { id: 'f23', no: '23', label: 'Hospital admission ID', type: 'text', placeholder: 'Auto-linked to DOH trauma registry on submit' }, ]}, { id: 'resp', n: '06', title: 'Responder metadata', sub: 'Field 24 · chain of custody', color: '#00c48c', icon: '🪖', fields: [ { id: 'f24', no: '24', label: 'Reporting officer / unit', type: 'unit' }, { id: 'notes', label: 'Narrative', type: 'textarea', optional: true, placeholder: 'Describe the sequence of events, contributing factors, witness accounts…' }, ]}, ]; // the 24 core fields that count toward completion const CRASH_CORE = ['f01','f02','f03','f04','f05','f06','f07','f08','f09','f10','f11','f12','f13','f14','f15','f16','f17','f18','f19','f20','f21','f22','f23','f24']; const CRASH_KEY = 'nrsa_crash_draft'; function isFilled(v) { if (v == null) return false; if (Array.isArray(v)) return v.length > 0; if (typeof v === 'object') return Object.values(v).some(x => String(x || '').trim() !== ''); return String(v).trim() !== ''; } function CrashReportScreen({ t, go }) { const now = new Date(); const isoLocal = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().slice(0, 16); const [vals, setVals] = useState(() => { try { const s = JSON.parse(localStorage.getItem(CRASH_KEY)); if (s && typeof s === 'object') return s; } catch (e) {} return { f01: '14.5995° N, 120.9842° E', f02: isoLocal }; }); const [open, setOpen] = useState('loc'); const [saved, setSaved] = useState(''); const [submitted, setSubmitted] = useState(false); // autosave every 30s + on change (debounced via effect) useEffect(() => { const id = setTimeout(() => { localStorage.setItem(CRASH_KEY, JSON.stringify(vals)); }, 600); return () => clearTimeout(id); }, [vals]); useEffect(() => { const id = setInterval(() => { localStorage.setItem(CRASH_KEY, JSON.stringify(vals)); setSaved(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })); }, 30000); return () => clearInterval(id); }, [vals]); const set = (id, v) => setVals(p => ({ ...p, [id]: v })); const done = CRASH_CORE.filter(id => isFilled(vals[id])).length; const pct = Math.round((done / 24) * 100); const recenter = () => set('f01', `${(14.55 + Math.random() * 0.2).toFixed(4)}° N, ${(120.95 + Math.random() * 0.12).toFixed(4)}° E`); if (submitted) { return (

Record submitted

Transmitted to the National Road Crash Database. Auto-linked to the DOH trauma registry. Reference NRCD-{now.getFullYear()}-{String(Math.floor(Math.random()*90000)+10000)}.

go('Home')}>Done { localStorage.removeItem(CRASH_KEY); setVals({ f01: '14.5995° N, 120.9842° E', f02: isoLocal }); setSubmitted(false); setOpen('loc'); }}>New report
); } return (
{/* Intake summary card — matches Home/Learn rhythm */}
{done}/24
Real-time intake · v1.0
24-field minimum dataset · DOTr–DOH–DILG joint circular.
● GPS-tagged Autosave 30s
{/* Sections */} {CRASH_SECTIONS.map(sec => { const isOpen = open === sec.id; const secCore = sec.fields.filter(f => CRASH_CORE.includes(f.id)); const secDone = secCore.filter(f => isFilled(vals[f.id])).length; const complete = secDone === secCore.length; return (
{sec.fields.map(f => ( set(f.id, v)} onRecenter={recenter} /> ))}
); })} {/* Actions */}
{done === 24 ? '✓ All 24 fields complete' : `${24 - done} field${24 - done === 1 ? '' : 's'} remaining`} {saved && · saved {saved}}
{ localStorage.setItem(CRASH_KEY, JSON.stringify(vals)); setSaved(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })); }}>Save draft = 23 ? t.danger : t.muted} onClick={() => { if (done < 1) return; if (NRSAApi.isLive()) { NRSAApi.createCrashReport({ occurredAt: vals.f02 ? new Date(vals.f02).toISOString() : undefined, locationText: vals.f01, description: JSON.stringify(vals), }).catch(() => {}); // record locally regardless; backend is best-effort here } setSubmitted(true); }} style={{ opacity: done >= 1 ? 1 : 0.6 }}>Submit to database
NRSA · National Road Crash App & Database · PRSAP 2023–2028
); } // ----- single field renderer ----- function CrashField({ t, f, value, onChange, onRecenter }) { const labelRow = (
{f.no && FIELD {f.no}} {f.label} {f.tag && {f.tag}} {f.missing && commonly missing} {f.optional && optional}
); const inputBase = { width: '100%', border: `1px solid ${t.border}`, background: t.inputBg, color: t.text, borderRadius: 12, padding: '12px 13px', outline: 'none', fontWeight: 500, }; let control = null; if (f.type === 'gps') { control = (
{value || '—'}
); } else if (f.type === 'datetime') { control = onChange(e.target.value)} style={inputBase} />; } else if (f.type === 'number') { control = (
onChange(e.target.value)} placeholder="0" style={{ ...inputBase, paddingRight: f.unit ? 54 : 13 }} /> {f.unit && {f.unit}}
); } else if (f.type === 'text') { control = onChange(e.target.value)} placeholder={f.placeholder} style={inputBase} />; } else if (f.type === 'textarea') { control =