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

+98
View File
@@ -0,0 +1,98 @@
import { ArrowLeft, CheckCircle2, MapPin, Printer } from 'lucide-react'
import { Link, useLocation, useParams } from 'react-router-dom'
import { SeverityLabel, StatusBadge } from '../components/Badges'
import { ActionsSection } from '../components/incident/ActionsSection'
import { ActivitySection } from '../components/incident/ActivitySection'
import { AttachmentsSection } from '../components/incident/AttachmentsSection'
import { DetailsSection } from '../components/incident/DetailsSection'
import { InvestigationSection } from '../components/incident/InvestigationSection'
import { WorkflowPanel } from '../components/incident/WorkflowPanel'
import { Page } from '../components/Page'
import { ErrorMessage, Loading } from '../components/QueryState'
import { secondaryButton } from '../components/styles'
import { ApiError } from '../lib/api'
import { formatDateTime } from '../lib/dates'
import { incidentTypeLabel } from '../lib/labels'
import { useIncident } from '../lib/queries'
export function IncidentDetail() {
const number = Number(useParams().number)
const location = useLocation()
const query = useIncident(number)
const incident = query.data
if (query.isPending) return <Loading />
if (!incident) {
return (
<Page>
{query.error instanceof ApiError && query.error.status === 404 ? (
<p className="text-sm text-muted">
This incident doesn’t exist or you don’t have access to it.{' '}
<Link to="/incidents" className="text-coral underline">
Back to incidents
</Link>
</p>
) : (
<ErrorMessage error={query.error} />
)}
</Page>
)
}
const justReported = (location.state as { justReported?: boolean } | null)?.justReported
return (
<Page>
<Link to="/incidents" className="mb-4 inline-flex items-center gap-1 text-2xs font-semibold text-muted hover:text-ink print:hidden">
<ArrowLeft size={14} /> All incidents
</Link>
{justReported && (
<div className="mb-5 flex items-start gap-2 rounded-md bg-mint-soft px-4 py-3 text-xs text-[#2b6e4f] print:hidden" role="status">
<CheckCircle2 size={16} className="mt-px flex-none" />
<span>
<strong>Thanks, {incident.reference} has been reported.</strong> The safety team has been notified{incident.severity === 'high' || incident.severity === 'critical' ? ' straight away' : ''}.
</span>
</div>
)}
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<span className="font-mono text-3xs font-medium tracking-[0.09em] text-[#8da29b] uppercase">
{incident.reference} · {incidentTypeLabel[incident.type]}
</span>
<h1 className="mt-1.5 mb-3 font-display text-[26px] leading-tight font-semibold tracking-[-0.5px] text-ink sm:text-[30px]">{incident.title}</h1>
<div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-2xs text-body">
<StatusBadge status={incident.status} />
<SeverityLabel severity={incident.severity} />
<span className="inline-flex items-center gap-1">
<MapPin size={13} />
{[incident.site.name, incident.area?.name].filter(Boolean).join(' · ')}
</span>
<span>Occurred {formatDateTime(incident.occurredAt)}</span>
<span>
Reported by {incident.anonymous ? 'anonymous' : (incident.reporter?.name ?? 'former user')} · {formatDateTime(incident.reportedAt)}
</span>
</div>
</div>
<button type="button" className={`${secondaryButton} flex-none print:hidden`} onClick={() => window.print()}>
<Printer size={16} /> Print / PDF
</button>
</div>
<div className="grid gap-5 lg:grid-cols-[minmax(0,1fr)_320px] print:block">
<div className="min-w-0 space-y-5">
<DetailsSection incident={incident} />
<InvestigationSection incident={incident} />
<ActionsSection incident={incident} />
<ActivitySection incident={incident} />
</div>
<div className="space-y-5 print:mt-5">
<div className="print:hidden">
<WorkflowPanel incident={incident} />
</div>
<AttachmentsSection incident={incident} />
</div>
</div>
</Page>
)
}