feat(kiosk): implement kiosk report submission functionality with offline support test(outbox): add tests for outbox functionality including report submission and error handling feat(outbox): create outbox management for queued reports with IndexedDB persistence feat(uuid): add UUID generation utility for client IDs feat(qr): implement QR code generation for reporting links by site and area feat(kiosk): create Kiosk page for reporting incidents without an account
75 lines
3.4 KiB
TypeScript
75 lines
3.4 KiB
TypeScript
import { AlertTriangle, MapPin } from 'lucide-react'
|
|
import { Link, useNavigate } from 'react-router-dom'
|
|
import type { Incident } from '../../shared/api-types.ts'
|
|
import { formatDate } from '../lib/dates'
|
|
import { incidentTypeLabel, incidentTypeTone } from '../lib/labels'
|
|
import { toneClasses } from '../lib/tones'
|
|
import { SeverityLabel, StatusBadge } from './Badges'
|
|
|
|
const th = 'border-b border-line px-2.5 py-3 font-mono text-3xs font-medium tracking-[0.06em] text-faint uppercase first:pl-6 last:pr-6'
|
|
const td = 'border-b border-line-soft px-2.5 py-3.5 text-2xs whitespace-nowrap text-body first:pl-6 last:pr-6'
|
|
|
|
export function IncidentTable({ incidents, wide = false }: { incidents: Incident[]; wide?: boolean }) {
|
|
const navigate = useNavigate()
|
|
return (
|
|
<div className="overflow-auto">
|
|
<table className={`w-full border-collapse text-left ${wide ? 'min-w-[900px]' : 'min-w-[720px]'}`}>
|
|
<thead>
|
|
<tr>
|
|
<th className={th}>Incident</th>
|
|
<th className={th}>Type</th>
|
|
<th className={th}>Severity</th>
|
|
<th className={th}>Location</th>
|
|
<th className={th}>Status</th>
|
|
<th className={th}>Occurred</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{incidents.map((incident) => (
|
|
<tr
|
|
key={incident.id}
|
|
className="cursor-pointer hover:bg-canvas [&:last-child>td]:border-b-0"
|
|
onClick={() => navigate(`/incidents/${incident.number}`)}
|
|
>
|
|
<td className={td}>
|
|
<div className="flex min-w-[250px] items-center gap-2.5">
|
|
<span className={`grid size-7 flex-none place-items-center rounded-[7px] ${toneClasses[incidentTypeTone[incident.type]]}`}>
|
|
<AlertTriangle size={14} />
|
|
</span>
|
|
<div className="min-w-0">
|
|
<Link
|
|
to={`/incidents/${incident.number}`}
|
|
className="block max-w-[320px] truncate text-xs font-semibold text-ink hover:underline"
|
|
onClick={(event) => event.stopPropagation()}
|
|
>
|
|
{incident.title}
|
|
</Link>
|
|
<small className="mt-1 block text-3xs text-faint">
|
|
{incident.reference} · {incident.anonymous ? 'Anonymous' : (incident.reporter?.name ?? incident.reporterName ?? 'Unknown')}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className={td}>{incidentTypeLabel[incident.type]}</td>
|
|
<td className={td}>
|
|
<SeverityLabel severity={incident.severity} />
|
|
</td>
|
|
<td className={td}>
|
|
<div className="flex max-w-[160px] flex-wrap items-center gap-1">
|
|
<MapPin size={14} className="flex-none" />
|
|
<span className="truncate">{incident.area?.name ?? incident.site.name}</span>
|
|
<small className="mt-1 ml-[18px] block w-full truncate text-3xs text-faint">{incident.locationDetail || incident.site.name}</small>
|
|
</div>
|
|
</td>
|
|
<td className={td}>
|
|
<StatusBadge status={incident.status} />
|
|
</td>
|
|
<td className={`${td} text-[#87938f]`}>{formatDate(incident.occurredAt)}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)
|
|
}
|