From 2ef458ec7291dd537d5df559bda85d87269e67a9 Mon Sep 17 00:00:00 2001 From: DJP Date: Mon, 23 Feb 2026 21:48:10 -0500 Subject: [PATCH] Add individual analysis deletion from AnalysisView and ProjectDetail - Delete button on AnalysisView header (red, with confirmation dialog) - Trash icon on each analysis card in ProjectDetail grid - useDeleteAnalysis hook with query invalidation - Navigates back after successful deletion Co-Authored-By: Claude Opus 4.6 --- frontend/src/hooks/useAnalysis.ts | 15 +++++++-- frontend/src/pages/AnalysisView.tsx | 20 ++++++++++-- frontend/src/pages/ProjectDetail.tsx | 47 ++++++++++++++++++++++++---- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/frontend/src/hooks/useAnalysis.ts b/frontend/src/hooks/useAnalysis.ts index 87425c9..c3249c3 100644 --- a/frontend/src/hooks/useAnalysis.ts +++ b/frontend/src/hooks/useAnalysis.ts @@ -1,5 +1,5 @@ -import { useQuery } from "@tanstack/react-query"; -import { getAnalysis, getAnalysisStatus } from "../api/analysis"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { getAnalysis, getAnalysisStatus, deleteAnalysis } from "../api/analysis"; import type { AnalysisDetail, AnalysisStatus } from "../types/analysis"; export function useAnalysis(analysisId: string | undefined) { @@ -10,6 +10,17 @@ export function useAnalysis(analysisId: string | undefined) { }); } +export function useDeleteAnalysis() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (analysisId: string) => deleteAnalysis(analysisId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["projects"] }); + queryClient.invalidateQueries({ queryKey: ["project"] }); + }, + }); +} + export function useAnalysisStatus( analysisId: string | undefined, enabled: boolean = true, diff --git a/frontend/src/pages/AnalysisView.tsx b/frontend/src/pages/AnalysisView.tsx index b83596a..ed30893 100644 --- a/frontend/src/pages/AnalysisView.tsx +++ b/frontend/src/pages/AnalysisView.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import { useParams } from "react-router-dom"; -import { useAnalysis } from "../hooks/useAnalysis"; +import { useParams, useNavigate } from "react-router-dom"; +import { useAnalysis, useDeleteAnalysis } from "../hooks/useAnalysis"; import { useAnalysisStore, type AnalysisTab } from "../stores/analysisStore"; import { getAnalysisImageUrl, checkAIInsightsAvailable, generateAIInsights } from "../api/analysis"; import type { Insight } from "../types/analysis"; @@ -125,7 +125,9 @@ const tabs: { key: AnalysisTab; label: string }[] = [ export default function AnalysisView() { const { analysisId } = useParams<{ analysisId: string }>(); + const navigate = useNavigate(); const { data: analysis, isLoading, error } = useAnalysis(analysisId); + const deleteAnalysisMutation = useDeleteAnalysis(); const activeTab = useAnalysisStore((s) => s.activeTab); const setActiveTab = useAnalysisStore((s) => s.setActiveTab); const setCurrentAnalysis = useAnalysisStore((s) => s.setCurrentAnalysis); @@ -180,6 +182,13 @@ export default function AnalysisView() { }; }, [analysis, setCurrentAnalysis, reset]); + const handleDeleteAnalysis = async () => { + if (!analysisId) return; + if (!window.confirm("Are you sure you want to delete this analysis? This cannot be undone.")) return; + await deleteAnalysisMutation.mutateAsync(analysisId); + navigate(-1); + }; + const handleDownloadPdf = async () => { if (!analysisId) return; try { @@ -241,6 +250,13 @@ export default function AnalysisView() { )} + diff --git a/frontend/src/pages/ProjectDetail.tsx b/frontend/src/pages/ProjectDetail.tsx index 70359f8..4feaf67 100644 --- a/frontend/src/pages/ProjectDetail.tsx +++ b/frontend/src/pages/ProjectDetail.tsx @@ -1,5 +1,7 @@ +import { useState } from "react"; import { useParams, Link, useNavigate } from "react-router-dom"; import { useProject, useDeleteProject } from "../hooks/useProjects"; +import { useDeleteAnalysis } from "../hooks/useAnalysis"; import { getAnalysisImageUrl } from "../api/analysis"; import Card from "../components/common/Card"; import Button from "../components/common/Button"; @@ -10,6 +12,20 @@ export default function ProjectDetail() { const navigate = useNavigate(); const { data: project, isLoading, error } = useProject(projectId); const deleteProject = useDeleteProject(); + const deleteAnalysis = useDeleteAnalysis(); + const [deletingId, setDeletingId] = useState(null); + + const handleDeleteAnalysis = async (e: React.MouseEvent, analysisId: string, analysisName: string) => { + e.preventDefault(); + e.stopPropagation(); + if (!window.confirm(`Delete analysis "${analysisName}"? This cannot be undone.`)) return; + setDeletingId(analysisId); + try { + await deleteAnalysis.mutateAsync(analysisId); + } finally { + setDeletingId(null); + } + }; const handleDelete = async () => { if (!projectId) return; @@ -131,12 +147,31 @@ export default function ProjectDetail() {
-

- {analysis.name} -

+
+

+ {analysis.name} +

+ +
{analysis.model}