import { useRef, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { ChevronDown } from 'lucide-react'; import { extractApiError } from '@/api/client'; import type { SimulationTemplate } from '@/api/types'; import { useAuth } from '@/hooks/useAuth'; import { useEngagementSimulations, useCreateSimulation } from '@/hooks/useSimulations'; import { useToast } from '@/hooks/useToast'; import { LoadingState } from './LoadingState'; import { ErrorState } from './ErrorState'; import { EmptyState } from './EmptyState'; import { SimulationStatusBadge } from './SimulationStatusBadge'; import { TemplatePickerModal } from './TemplatePickerModal'; interface SimulationListProps { engagementId: number; } function formatDate(value: string | null): string { if (!value) return '—'; return value.replace('T', ' ').slice(0, 16); } function NewSimulationDropdown({ engagementId }: { engagementId: number }): JSX.Element { const navigate = useNavigate(); const { push } = useToast(); const [open, setOpen] = useState(false); const [showPicker, setShowPicker] = useState(false); const btnRef = useRef(null); const createMutation = useCreateSimulation(engagementId); const handleBlank = () => { setOpen(false); navigate(`/engagements/${engagementId}/simulations/new`); }; const handleFromTemplate = () => { setOpen(false); setShowPicker(true); }; const handleSelectTemplate = async (template: SimulationTemplate) => { try { const sim = await createMutation.mutateAsync({ name: template.name, template_id: template.id }); setShowPicker(false); push(`Created "${sim.name}" from template`, 'success'); navigate(`/engagements/${engagementId}/simulations/${sim.id}/edit`); } catch (err) { push(extractApiError(err, 'Could not create simulation from template'), 'error'); } }; return (
{open ? (
) : null} {showPicker ? ( setShowPicker(false)} onInstantiated={(simId) => { setShowPicker(false); navigate(`/engagements/${engagementId}/simulations/${simId}/edit`); }} onSelectTemplate={handleSelectTemplate} isPending={createMutation.isPending} /> ) : null}
); } export function SimulationList({ engagementId }: SimulationListProps): JSX.Element { const { data, isLoading, isError, error, refetch } = useEngagementSimulations(engagementId); const { canEditEngagements } = useAuth(); const navigate = useNavigate(); if (isLoading) return ; if (isError) { return ( refetch()} /> ); } if (!data || data.length === 0) { return ( New simulation ) : undefined } /> ); } return (

Simulations

{canEditEngagements ? ( ) : null}
{data.map((sim) => ( navigate(`/engagements/${engagementId}/simulations/${sim.id}/edit`) } > ))}
Name MITRE Status Executed at
e.stopPropagation()} > {sim.name} {(() => { const items = [ ...(sim.tactics ?? []).map((t) => t.id), ...sim.techniques.map((t) => t.id), ]; if (items.length === 0) return '—'; if (items.length === 1) return items[0]; return `${items[0]} +${items.length - 1}`; })()} {formatDate(sim.executed_at)}
); }