import { useState, useMemo } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { X, Search, Plus, Loader2, CheckCircle, FlaskConical, } from "lucide-react"; import { getTests } from "../api/tests"; import { addTestToCampaign } from "../api/campaigns"; import type { Test, TestState } from "../types/models"; const stateBadge: Record = { draft: "bg-gray-800/50 text-gray-400 border-gray-600/30", red_executing: "bg-orange-900/50 text-orange-400 border-orange-500/30", blue_evaluating: "bg-indigo-900/50 text-indigo-400 border-indigo-500/30", in_review: "bg-blue-900/50 text-blue-400 border-blue-500/30", validated: "bg-green-900/50 text-green-400 border-green-500/30", rejected: "bg-red-900/50 text-red-400 border-red-500/30", }; interface AddTestToCampaignModalProps { campaignId: string; existingTestIds: string[]; open: boolean; onClose: () => void; onSuccess: () => void; } export default function AddTestToCampaignModal({ campaignId, existingTestIds, open, onClose, onSuccess, }: AddTestToCampaignModalProps) { const queryClient = useQueryClient(); const [searchText, setSearchText] = useState(""); const [addedIds, setAddedIds] = useState>(new Set()); const { data: allTests, isLoading } = useQuery({ queryKey: ["tests", "for-campaign-picker"], queryFn: () => getTests({ limit: 200 }), enabled: open, }); const filteredTests = useMemo(() => { if (!allTests) return []; const alreadyIn = new Set([...existingTestIds, ...addedIds]); let results = allTests.filter((t) => !alreadyIn.has(t.id)); if (searchText.trim()) { const q = searchText.toLowerCase(); results = results.filter( (t) => t.name.toLowerCase().includes(q) || (t.technique_mitre_id && t.technique_mitre_id.toLowerCase().includes(q)) || (t.technique_name && t.technique_name.toLowerCase().includes(q)) ); } return results; }, [allTests, searchText, existingTestIds, addedIds]); const addMutation = useMutation({ mutationFn: (testId: string) => addTestToCampaign(campaignId, { test_id: testId }), onSuccess: (_data, testId) => { setAddedIds((prev) => new Set(prev).add(testId)); queryClient.invalidateQueries({ queryKey: ["campaign", campaignId] }); onSuccess(); }, }); if (!open) return null; return (
{/* Header */}

Add Tests to Campaign

{/* Search */}
setSearchText(e.target.value)} placeholder="Search tests by name or technique..." autoFocus className="w-full rounded-lg border border-gray-700 bg-gray-800 pl-9 pr-3 py-2.5 text-sm text-gray-300 placeholder-gray-500 focus:border-cyan-500 focus:outline-none" />
{/* Test list */}
{isLoading ? (
) : filteredTests.length === 0 ? (

{searchText ? "No tests match your search." : "All available tests are already in this campaign."}

) : (
{filteredTests.map((test: Test) => (
{test.name} {test.state.replace(/_/g, " ")}
{test.technique_mitre_id && ( {test.technique_mitre_id} )} {test.technique_name && ( {test.technique_name} )} {test.platform && ( {test.platform} )}
))}
)}
{/* Footer */}
{addedIds.size > 0 && ( {addedIds.size} test{addedIds.size !== 1 ? "s" : ""} added )}
); }