feat: implement permissions and actions management
- 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:
1 parent
708da6b9f7
commit
3f6dff5f24
94 files changed
+7213
-643
No files matched your search
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user