feat: transport incidents with truck number support
- DB: trucks table + incidents.truck_id FK + transport enum value - Validation: transport type requires truck_id - Create API: validates truck exists/active, persists truck_id - Report form: truck dropdown shown when type=transport (required) - Admin: TruckManager CRUD + /api/admin/trucks route - Detail: trucks join surfaced in incident-detail + detail page query - Inbox (HSE + supervisor): truck filter, transport in TYPE_OPTIONS, fixed stale enum values (dropped dangerous_occurrence/mhe_asset/occupational_disease) - List: transport label + truck number badge in rows - i18n: transport + truckLabel/truckPlaceholder in en/ms/zh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
This commit is contained in:
@@ -10,9 +10,10 @@ interface Props {
|
||||
zoneToken: string | null
|
||||
zoneName: string | null
|
||||
siteName: string | null
|
||||
trucks: Array<{ id: string; truck_no: string; carrier: string | null }>
|
||||
}
|
||||
|
||||
export function ReportForm({ zoneToken }: Props) {
|
||||
export function ReportForm({ zoneToken, trucks }: Props) {
|
||||
const router = useRouter()
|
||||
const t = useTranslations('ReportForm')
|
||||
const itLabels = useTranslations('IncidentType')
|
||||
@@ -27,6 +28,7 @@ export function ReportForm({ zoneToken }: Props) {
|
||||
['environmental', itLabels.environmental],
|
||||
['security', itLabels.security],
|
||||
['fire', itLabels.fire],
|
||||
['transport', itLabels.transport],
|
||||
]
|
||||
|
||||
const MEDICAL_STATUS_OPTIONS: [MedicalStatus, string][] = [
|
||||
@@ -55,6 +57,7 @@ export function ReportForm({ zoneToken }: Props) {
|
||||
injury_involved: false,
|
||||
medical_status: '' as MedicalStatus | '',
|
||||
asset_involved: false,
|
||||
truck_id: '',
|
||||
})
|
||||
const [typeDetails, setTypeDetails] = useState<Record<string, string | boolean>>({})
|
||||
|
||||
@@ -116,6 +119,7 @@ export function ReportForm({ zoneToken }: Props) {
|
||||
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)
|
||||
@@ -168,6 +172,7 @@ export function ReportForm({ zoneToken }: Props) {
|
||||
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 })
|
||||
@@ -217,7 +222,7 @@ export function ReportForm({ zoneToken }: Props) {
|
||||
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"
|
||||
value={form.incident_type}
|
||||
onChange={e => {
|
||||
setForm(f => ({ ...f, incident_type: e.target.value as IncidentType }))
|
||||
setForm(f => ({ ...f, incident_type: e.target.value as IncidentType, truck_id: '' }))
|
||||
setTypeDetails({})
|
||||
}}
|
||||
>
|
||||
@@ -254,6 +259,27 @@ export function ReportForm({ zoneToken }: Props) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{form.incident_type === 'transport' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t.truckLabel} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
required
|
||||
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"
|
||||
value={form.truck_id}
|
||||
onChange={e => setForm(f => ({ ...f, truck_id: e.target.value }))}
|
||||
>
|
||||
<option value="">{t.truckPlaceholder}</option>
|
||||
{trucks.map(tk => (
|
||||
<option key={tk.id} value={tk.id}>
|
||||
{tk.truck_no}{tk.carrier ? ` — ${tk.carrier}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t.descriptionLabel} <span className="text-red-500">*</span>
|
||||
@@ -344,7 +370,7 @@ export function ReportForm({ zoneToken }: Props) {
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting || !form.incident_type}
|
||||
disabled={submitting || !form.incident_type || (form.incident_type === 'transport' && !form.truck_id)}
|
||||
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium text-sm
|
||||
hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user