MotorSignal-App/website/components/ops/operations-incident-detail.tsx

798 lines
31 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import {
SafeIncidentDetail,
SafeIncidentNote,
IncidentSeverity,
IncidentStatus,
OperationsRole,
} from './types';
import {
OperationsIncidentModal,
IncidentModalAction,
} from './operations-incident-modal';
import { isValidStatusTransition } from '../../lib/ops-data';
interface OperationsIncidentDetailProps {
caseReference: string;
role: OperationsRole;
onBack: () => void;
}
export function OperationsIncidentDetail({
caseReference,
role,
onBack,
}: OperationsIncidentDetailProps) {
const isOwner = role === 'owner';
const [incident, setIncident] = useState<SafeIncidentDetail | null>(null);
const [notes, setNotes] = useState<SafeIncidentNote[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
// Note form state
const [newNoteBody, setNewNoteBody] = useState('');
const [isAddingNote, setIsAddingNote] = useState(false);
const [noteError, setNoteError] = useState<string | null>(null);
// Mutation state
const [isMutating, setIsMutating] = useState(false);
const [mutationSuccess, setMutationSuccess] = useState<string | null>(null);
// Confirmation Modal state
const [modalOpen, setModalOpen] = useState(false);
const [modalAction, setModalAction] = useState<IncidentModalAction>('close');
const [isSubmittingModal, setIsSubmittingModal] = useState(false);
async function fetchIncidentData() {
setIsLoading(true);
setErrorMessage(null);
try {
const [detailRes, notesRes] = await Promise.all([
fetch(`/api/ops/incidents/${encodeURIComponent(caseReference)}`, {
method: 'GET',
headers: { accept: 'application/json' },
cache: 'no-store',
}),
fetch(`/api/ops/incidents/${encodeURIComponent(caseReference)}/notes`, {
method: 'GET',
headers: { accept: 'application/json' },
cache: 'no-store',
}),
]);
if (detailRes.status === 401 || notesRes.status === 401) {
setErrorMessage('Your Operations session has expired.');
setIsLoading(false);
return;
}
if (detailRes.status === 403 || notesRes.status === 403) {
setErrorMessage('You do not have authority to view this incident.');
setIsLoading(false);
return;
}
if (detailRes.status === 404) {
setErrorMessage('Incident not found.');
setIsLoading(false);
return;
}
if (!detailRes.ok) {
setErrorMessage('Failed to load incident detail.');
setIsLoading(false);
return;
}
const detailData = (await detailRes.json()) as SafeIncidentDetail;
setIncident(detailData);
if (notesRes.ok) {
const notesData = await notesRes.json();
setNotes(Array.isArray(notesData?.notes) ? notesData.notes : []);
}
} catch {
setErrorMessage('Unable to connect to Operations platform.');
} finally {
setIsLoading(false);
}
}
useEffect(() => {
fetchIncidentData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [caseReference]);
// Handle Add Note
async function handleAddNote(e: React.FormEvent) {
e.preventDefault();
if (!newNoteBody.trim()) {
setNoteError('Note body cannot be empty.');
return;
}
setIsAddingNote(true);
setNoteError(null);
try {
const res = await fetch(`/api/ops/incidents/${encodeURIComponent(caseReference)}/notes`, {
method: 'POST',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
body: JSON.stringify({ body: newNoteBody.trim() }),
});
if (res.status === 401) {
setNoteError('Your Operations session has expired.');
setIsAddingNote(false);
return;
}
if (res.status === 403) {
setNoteError('You do not have authority to add notes.');
setIsAddingNote(false);
return;
}
if (res.status === 409) {
setNoteError('Archived incidents cannot be modified.');
setIsAddingNote(false);
return;
}
if (!res.ok) {
setNoteError('Failed to append note.');
setIsAddingNote(false);
return;
}
const addedNote = (await res.json()) as SafeIncidentNote;
setNotes((prev) => [addedNote, ...prev]);
setNewNoteBody('');
} catch {
setNoteError('Unable to connect to Operations platform.');
} finally {
setIsAddingNote(false);
}
}
// Handle Severity Change
async function handleSeverityChange(newSeverity: IncidentSeverity) {
if (!incident || incident.severity === newSeverity || isMutating) return;
setIsMutating(true);
setMutationSuccess(null);
setErrorMessage(null);
try {
const res = await fetch(`/api/ops/incidents/${encodeURIComponent(caseReference)}`, {
method: 'PATCH',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
body: JSON.stringify({ severity: newSeverity }),
});
if (!res.ok) {
const data = await res.json().catch(() => null);
setErrorMessage(data?.detail ?? 'Failed to update severity.');
setIsMutating(false);
return;
}
const updated = (await res.json()) as SafeIncidentDetail;
setIncident(updated);
setMutationSuccess(`Severity updated to ${newSeverity.toUpperCase()}`);
setTimeout(() => setMutationSuccess(null), 4000);
} catch {
setErrorMessage('Unable to connect to Operations platform.');
} finally {
setIsMutating(false);
}
}
// Handle Status Transition via Select
async function handleStatusChange(targetStatus: IncidentStatus) {
if (!incident || incident.status === targetStatus || isMutating) return;
// If target status is closed, route to modal to capture resolution
if (targetStatus === 'closed') {
setModalAction('close');
setModalOpen(true);
return;
}
// If target status is archived, route to modal
if (targetStatus === 'archived') {
setModalAction('archive');
setModalOpen(true);
return;
}
// If target status is resolved without existing resolution, prompt for modal
if (targetStatus === 'resolved' && (!incident.resolution || !incident.resolution.trim())) {
setModalAction('close');
setModalOpen(true);
return;
}
setIsMutating(true);
setMutationSuccess(null);
setErrorMessage(null);
try {
const res = await fetch(`/api/ops/incidents/${encodeURIComponent(caseReference)}`, {
method: 'PATCH',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
body: JSON.stringify({ status: targetStatus }),
});
if (!res.ok) {
const data = await res.json().catch(() => null);
setErrorMessage(data?.detail ?? 'Failed to update incident status.');
setIsMutating(false);
return;
}
const updated = (await res.json()) as SafeIncidentDetail;
setIncident(updated);
setMutationSuccess(`Status transitioned to ${targetStatus.toUpperCase()}`);
setTimeout(() => setMutationSuccess(null), 4000);
} catch {
setErrorMessage('Unable to connect to Operations platform.');
} finally {
setIsMutating(false);
}
}
// Handle Modal Confirmation
async function handleModalConfirm(modalResolution?: string) {
if (!incident || isSubmittingModal) return;
setIsSubmittingModal(true);
setErrorMessage(null);
let patchPayload: Record<string, string> = {};
if (modalAction === 'close') {
patchPayload = {
status: 'closed',
resolution: modalResolution ?? '',
};
} else if (modalAction === 'reopen') {
patchPayload = { status: 'investigating' };
} else if (modalAction === 'archive') {
patchPayload = { status: 'archived' };
}
try {
const res = await fetch(`/api/ops/incidents/${encodeURIComponent(caseReference)}`, {
method: 'PATCH',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
body: JSON.stringify(patchPayload),
});
if (!res.ok) {
const data = await res.json().catch(() => null);
setErrorMessage(data?.detail ?? `Failed to perform ${modalAction} action.`);
setIsSubmittingModal(false);
setModalOpen(false);
return;
}
const updated = (await res.json()) as SafeIncidentDetail;
setIncident(updated);
setModalOpen(false);
setMutationSuccess(`Incident successfully ${modalAction === 'close' ? 'closed' : modalAction === 'reopen' ? 'reopened' : 'archived'}.`);
setTimeout(() => setMutationSuccess(null), 4000);
} catch {
setErrorMessage('Unable to connect to Operations platform.');
} finally {
setIsSubmittingModal(false);
}
}
function formatDate(iso?: string | null) {
if (!iso) return '—';
try {
const d = new Date(iso);
return d.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
});
} catch {
return iso;
}
}
function getSeverityBadge(severity: IncidentSeverity) {
switch (severity) {
case 'critical':
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-semibold bg-error/15 text-error border border-error/30">
Critical
</span>
);
case 'high':
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-semibold bg-amber-500/15 text-amber-400 border border-amber-500/30">
High
</span>
);
case 'medium':
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-semibold bg-primary-container/15 text-primary-container border border-primary-container/30">
Medium
</span>
);
case 'low':
default:
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-medium bg-surface-container text-on-surface-variant border border-outline-variant/30">
Low
</span>
);
}
}
function getStatusBadge(status: IncidentStatus) {
switch (status) {
case 'open':
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-semibold bg-blue-500/15 text-blue-400 border border-blue-500/30">
Open
</span>
);
case 'investigating':
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-semibold bg-amber-500/15 text-amber-300 border border-amber-500/30">
Investigating
</span>
);
case 'monitoring':
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-semibold bg-purple-500/15 text-purple-300 border border-purple-500/30">
Monitoring
</span>
);
case 'resolved':
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-semibold bg-emerald-500/15 text-emerald-400 border border-emerald-500/30">
Resolved
</span>
);
case 'closed':
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-medium bg-surface-container-high text-outline border border-outline-variant/30">
Closed
</span>
);
case 'archived':
default:
return (
<span className="px-2.5 py-1 rounded text-xs font-mono font-medium bg-surface-container-highest/60 text-outline border border-outline-variant/20">
Archived
</span>
);
}
}
if (isLoading) {
return (
<div className="p-16 text-center text-outline bg-surface-container-lowest rounded-xl border border-outline-variant/20 flex flex-col items-center justify-center gap-3">
<span className="material-symbols-outlined text-3xl animate-spin text-primary-container">
progress_activity
</span>
<span className="text-xs font-medium">Loading incident details...</span>
</div>
);
}
if (!incident) {
return (
<div className="space-y-4">
<button
type="button"
onClick={onBack}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-surface-container border border-outline-variant/30 text-xs font-semibold text-on-surface hover:bg-surface-container-high transition-colors"
>
← Back to Incidents
</button>
<div className="p-8 text-center text-error bg-error/10 border border-error/30 rounded-xl text-xs">
{errorMessage ?? 'Incident not found.'}
</div>
</div>
);
}
const isArchived = incident.status === 'archived';
const isClosed = incident.status === 'closed';
return (
<div className="space-y-6">
{/* Navigation & Actions Topbar */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-5 border-b border-outline-variant/30">
<div className="flex items-center gap-3">
<button
type="button"
onClick={onBack}
className="inline-flex items-center gap-1.5 px-4 py-2.5 rounded-xl bg-surface-container hover:bg-surface-container-high border border-outline-variant/30 text-xs sm:text-sm font-semibold text-on-surface transition-colors focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none cursor-pointer"
>
← Back
</button>
<span className="font-mono font-bold text-sm sm:text-base text-primary-container">
{incident.case_reference}
</span>
</div>
<div className="flex flex-wrap items-center gap-3">
<button
type="button"
onClick={() => fetchIncidentData()}
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-surface-container border border-outline-variant/30 text-xs sm:text-sm font-medium text-on-surface-variant hover:text-on-surface transition-colors focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none cursor-pointer"
>
<span className="material-symbols-outlined text-[18px]">refresh</span>
<span>Refresh</span>
</button>
{/* Quick Modal Triggers */}
{!isArchived && !isClosed && (
<button
type="button"
onClick={() => {
setModalAction('close');
setModalOpen(true);
}}
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-surface-container-high border border-outline-variant/40 text-xs sm:text-sm font-semibold text-on-surface hover:bg-surface-container-highest transition-all focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none cursor-pointer"
>
<span className="material-symbols-outlined text-[16px] text-emerald-400">
task_alt
</span>
<span>Close Incident...</span>
</button>
)}
{isClosed && isOwner && (
<button
type="button"
onClick={() => {
setModalAction('reopen');
setModalOpen(true);
}}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg bg-amber-500/15 border border-amber-500/30 text-xs font-bold text-amber-300 hover:bg-amber-500/25 transition-all focus-visible:ring-2 focus-visible:ring-amber-500 focus-visible:outline-none"
>
<span className="material-symbols-outlined text-[16px]">replay</span>
<span>Reopen (Owner)</span>
</button>
)}
{!isArchived && isOwner && (
<button
type="button"
onClick={() => {
setModalAction('archive');
setModalOpen(true);
}}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-error/10 border border-error/25 text-xs font-semibold text-error hover:bg-error/20 transition-all focus-visible:ring-2 focus-visible:ring-error focus-visible:outline-none"
>
<span className="material-symbols-outlined text-[16px]">archive</span>
<span>Archive (Owner)</span>
</button>
)}
</div>
</div>
{/* Alerts */}
{errorMessage && (
<div className="p-4 rounded-xl bg-error/10 border border-error/30 text-error flex items-center gap-3 text-xs">
<span className="material-symbols-outlined text-[18px]">error</span>
<span>{errorMessage}</span>
</div>
)}
{mutationSuccess && (
<div className="p-4 rounded-xl bg-emerald-500/10 border border-emerald-500/30 text-emerald-400 flex items-center gap-3 text-xs">
<span className="material-symbols-outlined text-[18px]">check_circle</span>
<span>{mutationSuccess}</span>
</div>
)}
{/* Incident Header Card */}
<div className="bg-surface-container-low p-6 sm:p-8 rounded-2xl border border-outline-variant/30 shadow-sm space-y-6">
<div className="flex flex-col md:flex-row md:items-start justify-between gap-4">
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-2.5">
<span className="font-mono text-xs sm:text-sm font-bold text-primary-fixed-dim">
{incident.case_reference}
</span>
<div className="flex items-center gap-2">
{getSeverityBadge(incident.severity)}
{getStatusBadge(incident.status)}
</div>
</div>
<h1 className="font-headline-xl text-2xl sm:text-3xl font-extrabold text-on-surface tracking-tight mt-1">
{incident.title}
</h1>
</div>
{/* Mutation Selects (Active incidents only) */}
{!isArchived && (
<div className="flex flex-wrap items-center gap-3 bg-surface-container p-3 rounded-xl border border-outline-variant/30 shrink-0">
{/* Severity Selector */}
<div className="space-y-1">
<span className="block text-[10px] font-mono text-outline">
Severity
</span>
<select
value={incident.severity}
disabled={isMutating}
onChange={(e) => handleSeverityChange(e.target.value as IncidentSeverity)}
className="bg-surface-container-high px-2.5 py-1 rounded-lg border border-outline-variant/40 text-xs font-mono font-semibold capitalize text-on-surface focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none disabled:opacity-50"
>
<option value="critical">Critical</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
</div>
{/* Status Transition Selector */}
<div className="space-y-1">
<span className="block text-[10px] font-mono text-outline">
Transition Status
</span>
<select
value={incident.status}
disabled={isMutating}
onChange={(e) => handleStatusChange(e.target.value as IncidentStatus)}
className="bg-surface-container-high px-2.5 py-1 rounded-lg border border-outline-variant/40 text-xs font-mono font-semibold text-on-surface focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none disabled:opacity-50"
>
<option value={incident.status} disabled>
Current ({incident.status.charAt(0).toUpperCase() + incident.status.slice(1)})
</option>
{(['open', 'investigating', 'monitoring', 'resolved', 'closed', 'archived'] as IncidentStatus[])
.filter((s) => isValidStatusTransition(incident.status, s, role))
.map((s) => (
<option key={s} value={s}>
→ {s.charAt(0).toUpperCase() + s.slice(1)}
</option>
))}
</select>
</div>
</div>
)}
</div>
{/* Metadata Grid */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 pt-4 border-t border-outline-variant/20 text-xs">
<div>
<span className="block text-[10px] font-mono text-outline">Created By</span>
<span className="font-mono text-on-surface-variant break-all">
{incident.created_by_identity}
</span>
</div>
<div>
<span className="block text-[10px] font-mono text-outline">Created At</span>
<span className="font-mono text-on-surface-variant">
{formatDate(incident.created_at)}
</span>
</div>
<div>
<span className="block text-[10px] font-mono text-outline">Last Updated</span>
<span className="font-mono text-on-surface-variant">
{formatDate(incident.updated_at)}
</span>
</div>
<div>
<span className="block text-[10px] font-mono text-outline">Closed</span>
<span className="font-mono text-on-surface-variant">
{incident.closed_at ? formatDate(incident.closed_at) : '—'}
</span>
</div>
</div>
</div>
{/* Summary Card */}
<div className="bg-surface-container-low p-6 rounded-2xl border border-outline-variant/30 space-y-2">
<h2 className="text-xs font-headline-sm font-semibold text-outline">
Incident Summary
</h2>
<div className="text-xs text-on-surface whitespace-pre-wrap leading-relaxed">
{incident.summary}
</div>
</div>
{/* Resolution Card (if present) */}
{incident.resolution && (
<div className="bg-surface-container-low p-6 rounded-2xl border border-emerald-500/30 space-y-2">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-emerald-400 text-[18px]">
task_alt
</span>
<h2 className="text-xs font-headline-sm font-semibold text-emerald-400">
Resolution Record
</h2>
</div>
<div className="text-xs text-on-surface whitespace-pre-wrap leading-relaxed bg-surface-container/60 p-4 rounded-xl border border-outline-variant/20 font-mono">
{incident.resolution}
</div>
</div>
)}
{/* Two Column Layout: Operational Timeline & Append-only Notes */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Append-only Notes Section */}
<div className="bg-surface-container-low p-6 rounded-2xl border border-outline-variant/30 space-y-5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[18px] text-primary-container">
notes
</span>
<h2 className="text-xs font-headline-sm font-semibold text-on-surface">
Operator Notes
</h2>
</div>
<span className="text-[10px] font-mono text-outline">
Append-Only ({notes.length})
</span>
</div>
{/* Add Note Form */}
{!isArchived && (
<form onSubmit={handleAddNote} className="space-y-3 bg-surface-container p-4 rounded-xl border border-outline-variant/30">
<div className="flex items-center justify-between">
<label htmlFor="incident-note-body" className="block text-[11px] font-semibold text-on-surface">
Add Operator Note
</label>
<span className="text-[10px] font-mono text-outline">
{newNoteBody.length} / 4000
</span>
</div>
<textarea
id="incident-note-body"
required
rows={3}
maxLength={4000}
value={newNoteBody}
onChange={(e) => setNewNoteBody(e.target.value)}
placeholder="Record operational observations, actions taken, or debugging findings..."
className="w-full bg-surface-container-high px-3 py-2 rounded-lg border border-outline-variant/40 text-xs text-on-surface placeholder:text-outline focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none resize-y"
/>
{noteError && (
<p className="text-[11px] text-error">{noteError}</p>
)}
<div className="flex justify-end">
<button
type="submit"
disabled={isAddingNote || !newNoteBody.trim()}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg bg-primary-container text-on-primary-container text-xs font-bold shadow-sm hover:bg-primary-container/90 transition-all disabled:opacity-50"
>
{isAddingNote && (
<span className="material-symbols-outlined text-[14px] animate-spin">
progress_activity
</span>
)}
<span>Add Note</span>
</button>
</div>
</form>
)}
{/* Notes Feed */}
{notes.length === 0 ? (
<p className="text-xs text-outline text-center py-6">
No operator notes recorded for this incident yet.
</p>
) : (
<div className="space-y-3 max-h-96 overflow-y-auto pr-1">
{notes.map((note, idx) => (
<div
key={`${note.created_at}-${idx}`}
className="p-3.5 rounded-xl bg-surface-container/60 border border-outline-variant/20 space-y-1.5 text-xs"
>
<div className="flex items-center justify-between text-[10px] font-mono text-outline">
<span className="text-primary-fixed-dim font-bold">
{note.created_by_identity}
</span>
<span>{formatDate(note.created_at)}</span>
</div>
<div className="text-on-surface whitespace-pre-wrap font-mono text-xs leading-relaxed">
{note.body}
</div>
</div>
))}
</div>
)}
</div>
{/* Operational Timeline Section */}
<div className="bg-surface-container-low p-6 rounded-2xl border border-outline-variant/30 space-y-4">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[18px] text-primary-container">
history
</span>
<h2 className="text-xs font-headline-sm font-semibold text-on-surface">
Operational Timeline
</h2>
</div>
<div className="relative pl-6 space-y-6 before:absolute before:left-2 before:top-2 before:bottom-2 before:w-0.5 before:bg-outline-variant/30">
{/* Created Event */}
<div className="relative space-y-1">
<span className="absolute -left-6 top-0.5 w-3 h-3 rounded-full bg-blue-500 border-2 border-surface-container-low" />
<div className="flex items-center justify-between text-[10px] font-mono text-outline">
<span className="font-semibold text-on-surface">Incident Opened</span>
<span>{formatDate(incident.created_at)}</span>
</div>
<p className="text-xs text-on-surface-variant">
Created by <span className="font-mono text-primary-container">{incident.created_by_identity}</span> with initial severity <span className="font-mono capitalize font-medium">{incident.severity}</span>.
</p>
</div>
{/* Notes Count Event */}
{notes.length > 0 && (
<div className="relative space-y-1">
<span className="absolute -left-6 top-0.5 w-3 h-3 rounded-full bg-purple-400 border-2 border-surface-container-low" />
<div className="flex items-center justify-between text-[10px] font-mono text-outline">
<span className="font-semibold text-on-surface">Operator Notes ({notes.length})</span>
<span>{formatDate(notes[0]?.created_at)}</span>
</div>
<p className="text-xs text-on-surface-variant">
{notes.length} append-only operational {notes.length === 1 ? 'note' : 'notes'} recorded.
</p>
</div>
)}
{/* Resolution Event (if present) */}
{incident.resolution && (
<div className="relative space-y-1">
<span className="absolute -left-6 top-0.5 w-3 h-3 rounded-full bg-emerald-400 border-2 border-surface-container-low" />
<div className="flex items-center justify-between text-[10px] font-mono text-outline">
<span className="font-semibold text-emerald-400">Resolution Documented</span>
<span>{formatDate(incident.updated_at)}</span>
</div>
<p className="text-xs text-on-surface-variant">
Resolution summary recorded for this case.
</p>
</div>
)}
{/* Closed Event (if closed) */}
{incident.closed_at && (
<div className="relative space-y-1">
<span className="absolute -left-6 top-0.5 w-3 h-3 rounded-full bg-outline border-2 border-surface-container-low" />
<div className="flex items-center justify-between text-[10px] font-mono text-outline">
<span className="font-semibold text-on-surface">Incident Closed</span>
<span>{formatDate(incident.closed_at)}</span>
</div>
<p className="text-xs text-on-surface-variant">
Closed by <span className="font-mono text-on-surface">{incident.closed_by_identity ?? 'operator'}</span>.
</p>
</div>
)}
</div>
</div>
</div>
{/* Confirmation Dialog Modal */}
<OperationsIncidentModal
isOpen={modalOpen}
action={modalAction}
caseReference={incident.case_reference}
initialResolution={incident.resolution ?? ''}
isSubmitting={isSubmittingModal}
onConfirm={handleModalConfirm}
onClose={() => setModalOpen(false)}
/>
</div>
);
}