feat(frontend): sprint 2 — simulations UI + MITRE picker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-05-26 11:13:14 +02:00
parent 006c4c2c5f
commit 765bb5a1a4
16 changed files with 1778 additions and 7 deletions

View File

@@ -0,0 +1,76 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import {
createSimulation,
deleteSimulation,
getSimulation,
listSimulations,
transitionSimulation,
updateSimulation,
} from '@/api/simulations';
import type { SimulationCreateInput, SimulationPatchInput, SimulationStatus } from '@/api/types';
function simulationsKey(engagementId: number) {
return ['engagements', engagementId, 'simulations'] as const;
}
function simulationKey(id: number) {
return ['simulations', id] as const;
}
export function useEngagementSimulations(engagementId: number | undefined) {
return useQuery({
queryKey: engagementId ? simulationsKey(engagementId) : ['simulations', 'none'],
queryFn: () => listSimulations(engagementId as number),
enabled: typeof engagementId === 'number' && !Number.isNaN(engagementId),
});
}
export function useSimulation(id: number | undefined) {
return useQuery({
queryKey: id ? simulationKey(id) : ['simulations', 'none'],
queryFn: () => getSimulation(id as number),
enabled: typeof id === 'number' && !Number.isNaN(id),
});
}
export function useCreateSimulation(engagementId: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: (input: SimulationCreateInput) => createSimulation(engagementId, input),
onSuccess: () => qc.invalidateQueries({ queryKey: simulationsKey(engagementId) }),
});
}
export function useUpdateSimulation(id: number, engagementId: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: (patch: SimulationPatchInput) => updateSimulation(id, patch),
onSuccess: () => {
qc.invalidateQueries({ queryKey: simulationKey(id) });
qc.invalidateQueries({ queryKey: simulationsKey(engagementId) });
},
});
}
export function useDeleteSimulation(engagementId: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: number) => deleteSimulation(id),
onSuccess: (_data, id) => {
qc.invalidateQueries({ queryKey: simulationKey(id) });
qc.invalidateQueries({ queryKey: simulationsKey(engagementId) });
},
});
}
export function useTransitionSimulation(id: number, engagementId: number) {
const qc = useQueryClient();
return useMutation({
mutationFn: (to: Extract<SimulationStatus, 'review_required' | 'done'>) =>
transitionSimulation(id, to),
onSuccess: () => {
qc.invalidateQueries({ queryKey: simulationKey(id) });
qc.invalidateQueries({ queryKey: simulationsKey(engagementId) });
},
});
}