'use client' import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import { FileUpload } from '@/components/incidents/file-upload' import type { IncidentType, MedicalStatus } from '@/lib/incidents/validate' import { useTranslations } from '@/lib/i18n/context' interface Props { zoneToken: string | null zoneName: string | null siteName: string | null trucks: Array<{ id: string; truck_no: string; carrier: string | null }> initialTruckId?: string | null } export function ReportForm({ zoneToken, trucks, initialTruckId }: Props) { const router = useRouter() const t = useTranslations('ReportForm') const itLabels = useTranslations('IncidentType') const msLabels = useTranslations('MedicalStatus') const tdLabels = useTranslations('TypeDetails') const INCIDENT_TYPE_OPTIONS: [IncidentType, string][] = [ ['injury', itLabels.injury], ['near_miss', itLabels.near_miss], ['hazard', itLabels.hazard], ['asset_damage', itLabels.asset_damage], ['environmental', itLabels.environmental], ['security', itLabels.security], ['fire', itLabels.fire], ['transport', itLabels.transport], ] const MEDICAL_STATUS_OPTIONS: [MedicalStatus, string][] = [ ['none', msLabels.none], ['first_aid', msLabels.first_aid], ['medical_treatment', msLabels.medical_treatment], ['lti', msLabels.lti], ] const [submitting, setSubmitting] = useState(false) const [error, setError] = useState(null) const [files, setFiles] = useState([]) const [isOnline, setIsOnline] = useState(true) const [savedOffline, setSavedOffline] = useState(false) const [qualityCheck, setQualityCheck] = useState<{ score: number passes: boolean feedback: string suggestions: string[] } | null>(null) const [overrideQuality, setOverrideQuality] = useState(false) const [form, setForm] = useState({ incident_type: (initialTruckId ? 'transport' : '') as IncidentType | '', description: '', injury_involved: false, medical_status: '' as MedicalStatus | '', asset_involved: false, truck_id: initialTruckId ?? '', }) const [typeDetails, setTypeDetails] = useState>({}) const setDetail = (key: string, value: string | boolean) => setTypeDetails(d => ({ ...d, [key]: value })) const detailText = (key: string, label: string, placeholder = '') => (
setDetail(key, e.target.value)} placeholder={placeholder} className="w-full border border-gray-300 rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
) const detailCheckbox = (key: string, label: string) => ( ) useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect setIsOnline(navigator.onLine) const onOnline = () => setIsOnline(true) const onOffline = () => setIsOnline(false) window.addEventListener('online', onOnline) window.addEventListener('offline', onOffline) return () => { window.removeEventListener('online', onOnline) window.removeEventListener('offline', onOffline) } }, []) async function handleSubmit(e: React.FormEvent) { e.preventDefault() setError(null) setSubmitting(true) // Offline path — save to IndexedDB if (!isOnline) { try { const { addPendingReport } = await import('@/lib/offline/db') await addPendingReport({ zone_token: zoneToken ?? '', incident_type: form.incident_type as IncidentType, description: form.description, injury_involved: form.injury_involved, asset_involved: form.asset_involved, medical_status: form.medical_status || undefined, type_details: Object.keys(typeDetails).length > 0 ? typeDetails : undefined, truck_id: form.truck_id || undefined, created_at: new Date().toISOString(), }) setSavedOffline(true) } catch (err) { console.error('Offline save error:', err) setError(t.errorGeneric) } setSubmitting(false) return } if (!overrideQuality) { try { const qcRes = await fetch('/ims/api/incidents/ai/quality-check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ description: form.description, incident_type: form.incident_type, }), }) if (qcRes.ok) { const qc = await qcRes.json() as { score: number passes: boolean feedback: string suggestions: string[] } setQualityCheck(qc) if (!qc.passes) { setSubmitting(false) return } } } catch { // Quality check failure is non-blocking } } try { const fd = new FormData() if (zoneToken) fd.append('zone_token', zoneToken) fd.append('incident_type', form.incident_type) fd.append('description', form.description) fd.append('injury_involved', String(form.injury_involved)) fd.append('asset_involved', String(form.asset_involved)) if (form.injury_involved && form.medical_status) { fd.append('medical_status', form.medical_status) } if (Object.keys(typeDetails).length > 0) { fd.append('type_details', JSON.stringify(typeDetails)) } if (form.truck_id) fd.append('truck_id', form.truck_id) files.forEach(f => fd.append('files', f)) const res = await fetch('/ims/api/incidents', { method: 'POST', body: fd }) // Parse JSON separately so a non-JSON response (HTML error page) surfaces the status code let data: Record = {} try { data = await res.json() } catch { console.error('Non-JSON response from /api/incidents', res.status, res.statusText) setError(`${t.errorGeneric} (HTTP ${res.status})`) return } if (!res.ok) { const msg = Array.isArray(data.details) ? (data.details as string[]).join('. ') : (data.error as string) setError(msg ?? t.errorGeneric) return } router.push(`/report/success?ref=${data.reference_no}`) } catch (err) { console.error('Submit error:', err) setError(t.errorGeneric) } finally { setSubmitting(false) } } if (savedOffline) { return (
{t.savedOffline}
) } return (
{!isOnline && (
{t.offlineBanner}
)} {error && (
{error}
)}
{form.incident_type === 'environmental' && (
{detailText('substance', tdLabels.substance, tdLabels.substancePlaceholder)} {detailText('estimated_volume', tdLabels.estimatedVolume, tdLabels.estimatedVolumePlaceholder)} {detailCheckbox('containment_deployed', tdLabels.containmentDeployed)}
)} {form.incident_type === 'asset_damage' && (
{detailText('equipment_id', tdLabels.equipmentId, tdLabels.equipmentIdPlaceholder)} {detailCheckbox('loto_applied', tdLabels.lotoApplied)}
)} {form.incident_type === 'security' && (
{detailText('persons_involved', tdLabels.personsInvolved, tdLabels.personsInvolvedPlaceholder)} {detailCheckbox('police_reported', tdLabels.policeReported)}
)} {form.incident_type === 'fire' && (
{detailCheckbox('alarm_raised', tdLabels.alarmRaised)} {detailCheckbox('fire_brigade_called', tdLabels.fireBrigadeCalled)}
)} {form.incident_type === 'transport' && (
)}