feat: implement permissions and actions management
CI / Lint, typecheck, test, build (push) Canceled after 0s
CI / End-to-end tests (push) Canceled after 0s

- Add permissions logic for status options in corrective actions.
- Create tests for permissions functionality.
- Implement queries for user authentication and actions management.
- Develop Actions page to display and manage corrective actions.
- Create authentication pages for login, setup, and password reset.
- Add IncidentDetail page to view incident details and associated actions.
- Implement Settings page for user management and password changes.
This commit is contained in:
jayjennings committed 2026-10-06 11:20:44 -07:00
1 parent 708da6b9f7
commit 3f6dff5f24
94 files changed
+7213 -643

No files matched your search

+33 -39
View File
@@ -1,21 +1,16 @@
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 { severityDotClass, statusClass, typeTone } from '../lib/incidents'
import { incidentTypeLabel, incidentTypeTone } from '../lib/labels'
import { toneClasses } from '../lib/tones'
import type { Incident } from '../lib/types'
import { iconButton } from './styles'
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-[18px]'
const td = 'border-b border-line-soft px-2.5 py-3.5 text-2xs whitespace-nowrap text-body first:pl-6 last:pr-[18px]'
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'
interface IncidentTableProps {
incidents: Incident[]
limit?: number
wide?: boolean
}
export function IncidentTable({ incidents, limit, wide = false }: IncidentTableProps) {
const rows = limit ? incidents.slice(0, limit) : incidents
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]'}`}>
@@ -26,51 +21,50 @@ export function IncidentTable({ incidents, limit, wide = false }: IncidentTableP
<th className={th}>Severity</th>
<th className={th}>Location</th>
<th className={th}>Status</th>
<th className={th}>Date</th>
<th className={th}>
<span className="sr-only">Actions</span>
</th>
<th className={th}>Occurred</th>
</tr>
</thead>
<tbody>
{rows.map((incident) => (
<tr key={incident.id} className="[&:last-child>td]:border-b-0">
{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 place-items-center rounded-[7px] ${toneClasses[typeTone[incident.type]]}`}>
<span className={`grid size-7 flex-none place-items-center rounded-[7px] ${toneClasses[incidentTypeTone[incident.type]]}`}>
<AlertTriangle size={14} />
</span>
<div>
<strong className="block text-xs font-semibold text-ink">{incident.title}</strong>
<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.id} · {incident.reporter}
{incident.reference} · {incident.anonymous ? 'Anonymous' : (incident.reporter?.name ?? 'Unknown')}
</small>
</div>
</div>
</td>
<td className={td}>{incident.type}</td>
<td className={td}>{incidentTypeLabel[incident.type]}</td>
<td className={td}>
<span className="inline-flex items-center gap-[5px]">
<i className={`size-1.5 rounded-full ${severityDotClass[incident.severity]}`} />
{incident.severity}
</span>
<SeverityLabel severity={incident.severity} />
</td>
<td className={td}>
<div className="flex max-w-[120px] flex-wrap items-center gap-1">
<MapPin size={14} />
{incident.location}
<small className="mt-1 ml-[18px] block w-full text-3xs text-faint">{incident.building}</small>
<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}>
<span className={`rounded px-2 py-[5px] text-3xs ${statusClass(incident.status)}`}>{incident.status}</span>
</td>
<td className={`${td} text-[#87938f]`}>{formatDate(incident.date)}</td>
<td className={td}>
<button type="button" className={iconButton} aria-label={`More actions for ${incident.id}`}>
<span className="tracking-[2px] text-[#91aaa0]">•••</span>
</button>
<StatusBadge status={incident.status} />
</td>
<td className={`${td} text-[#87938f]`}>{formatDate(incident.occurredAt)}</td>
</tr>
))}
</tbody>