Files
mimic/frontend/src/components/SimulationList.tsx

114 lines
3.7 KiB
TypeScript
Raw Normal View History

import { Link } from 'react-router-dom';
import { extractApiError } from '@/api/client';
import { useAuth } from '@/hooks/useAuth';
import { useEngagementSimulations } from '@/hooks/useSimulations';
import { LoadingState } from './LoadingState';
import { ErrorState } from './ErrorState';
import { EmptyState } from './EmptyState';
import { SimulationStatusBadge } from './SimulationStatusBadge';
interface SimulationListProps {
engagementId: number;
}
function formatDate(value: string | null): string {
if (!value) return '—';
return value.replace('T', ' ').slice(0, 16);
}
export function SimulationList({ engagementId }: SimulationListProps): JSX.Element {
const { data, isLoading, isError, error, refetch } = useEngagementSimulations(engagementId);
const { canEditEngagements } = useAuth();
if (isLoading) return <LoadingState label="Loading simulations…" />;
if (isError) {
return (
<ErrorState
message={extractApiError(error, 'Could not load simulations')}
onRetry={() => refetch()}
/>
);
}
if (!data || data.length === 0) {
return (
<EmptyState
title="No simulations yet"
description="Create the first simulation to start tracking red team tests."
action={
canEditEngagements ? (
<Link
to={`/engagements/${engagementId}/simulations/new`}
className="btn-primary"
data-testid="new-simulation-btn"
>
Nouvelle simulation
</Link>
) : undefined
}
/>
);
}
return (
<div className="flex flex-col gap-md">
<div className="flex items-center justify-between">
<h2 className="text-[24px] font-medium text-ink">Simulations</h2>
{canEditEngagements ? (
<Link
to={`/engagements/${engagementId}/simulations/new`}
className="btn-primary"
data-testid="new-simulation-btn"
>
Nouvelle simulation
</Link>
) : null}
</div>
<div className="card-product overflow-hidden p-0">
<table className="w-full text-left">
<thead className="bg-cloud border-b border-hairline">
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
<th className="px-xl py-md">Name</th>
<th className="px-xl py-md">MITRE</th>
<th className="px-xl py-md">Status</th>
<th className="px-xl py-md">Executed at</th>
</tr>
</thead>
<tbody>
{data.map((sim) => (
<tr
key={sim.id}
className="border-b border-hairline last:border-0 hover:bg-cloud cursor-pointer"
onClick={() =>
(window.location.href = `/engagements/${engagementId}/simulations/${sim.id}/edit`)
}
>
<td className="px-xl py-md">
<Link
to={`/engagements/${engagementId}/simulations/${sim.id}/edit`}
className="text-ink font-medium hover:underline"
onClick={(e) => e.stopPropagation()}
>
{sim.name}
</Link>
</td>
<td className="px-xl py-md text-charcoal text-[14px]">
{sim.mitre_technique_id ?? '—'}
</td>
<td className="px-xl py-md">
<SimulationStatusBadge status={sim.status} />
</td>
<td className="px-xl py-md text-charcoal text-[14px]">
{formatDate(sim.executed_at)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}