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 <noreply@anthropic.com>
This commit is contained in:
parent
92062b254d
commit
2ef458ec72
3 changed files with 72 additions and 10 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
</Button>
|
||||
)}
|
||||
<Button onClick={handleDownloadPdf}>Download PDF</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
onClick={handleDeleteAnalysis}
|
||||
loading={deleteAnalysisMutation.isPending}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string | null>(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() {
|
|||
</span>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<h3
|
||||
className="font-medium truncate"
|
||||
style={{ color: "#1a1a2e" }}
|
||||
>
|
||||
{analysis.name}
|
||||
</h3>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3
|
||||
className="font-medium truncate"
|
||||
style={{ color: "#1a1a2e" }}
|
||||
>
|
||||
{analysis.name}
|
||||
</h3>
|
||||
<button
|
||||
onClick={(e) => handleDeleteAnalysis(e, analysis.id, analysis.name)}
|
||||
disabled={deletingId === analysis.id}
|
||||
className="shrink-0 p-1 text-gray-300 hover:text-red-500 transition-colors rounded"
|
||||
title="Delete analysis"
|
||||
>
|
||||
{deletingId === analysis.id ? (
|
||||
<svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-1 text-xs text-gray-400">
|
||||
<span>{analysis.model}</span>
|
||||
<span>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue