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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user