Fabio Antonini Claude Sonnet 4.6 commited on
Commit
01c12ca
·
1 Parent(s): fb4aa20

feat: add Clear analyses button to project detail page

Browse files

- Backend: DELETE /analysis/project/{project_id} endpoint
- Frontend: Cancella analisi button with confirm dialog (visible only when analyses exist)
- i18n: add clear_analyses and clear_analyses_confirm keys in it/en

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

backend/routers/analysis.py CHANGED
@@ -29,7 +29,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
29
  from fastapi.responses import StreamingResponse
30
  from PIL import Image
31
  from pydantic import BaseModel
32
- from sqlalchemy import select
33
  from sqlalchemy.ext.asyncio import AsyncSession
34
 
35
  from backend.auth.dependencies import get_current_user
@@ -283,3 +283,14 @@ async def list_analyses(
283
  .order_by(Analysis.created_at.desc())
284
  )
285
  return result.scalars().all()
 
 
 
 
 
 
 
 
 
 
 
 
29
  from fastapi.responses import StreamingResponse
30
  from PIL import Image
31
  from pydantic import BaseModel
32
+ from sqlalchemy import delete, select
33
  from sqlalchemy.ext.asyncio import AsyncSession
34
 
35
  from backend.auth.dependencies import get_current_user
 
283
  .order_by(Analysis.created_at.desc())
284
  )
285
  return result.scalars().all()
286
+
287
+
288
+ @router.delete("/project/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
289
+ async def clear_analyses(
290
+ project_id: int,
291
+ db: AsyncSession = Depends(get_db),
292
+ current_user: User = Depends(get_current_user),
293
+ ) -> None:
294
+ await _check_project_access(project_id, db, current_user)
295
+ await db.execute(delete(Analysis).where(Analysis.project_id == project_id))
296
+ await db.commit()
frontend/src/i18n/en.json CHANGED
@@ -58,6 +58,8 @@
58
  "pipeline": "Full forensic pipeline",
59
  "dating": "Document dating"
60
  },
 
 
61
  "running": "Running analysis…",
62
  "done": "Analysis complete"
63
  },
 
58
  "pipeline": "Full forensic pipeline",
59
  "dating": "Document dating"
60
  },
61
+ "clear_analyses": "Clear analyses",
62
+ "clear_analyses_confirm": "Delete all analyses for this case? This cannot be undone.",
63
  "running": "Running analysis…",
64
  "done": "Analysis complete"
65
  },
frontend/src/i18n/it.json CHANGED
@@ -58,6 +58,8 @@
58
  "pipeline": "Perizia forense completa",
59
  "dating": "Datazione documento"
60
  },
 
 
61
  "running": "Analisi in corso…",
62
  "done": "Analisi completata",
63
  "select_reference_doc": "Seleziona documento di riferimento",
 
58
  "pipeline": "Perizia forense completa",
59
  "dating": "Datazione documento"
60
  },
61
+ "clear_analyses": "Cancella analisi",
62
+ "clear_analyses_confirm": "Eliminare tutte le analisi di questa perizia? L'operazione non è reversibile.",
63
  "running": "Analisi in corso…",
64
  "done": "Analisi completata",
65
  "select_reference_doc": "Seleziona documento di riferimento",
frontend/src/lib/api.ts CHANGED
@@ -156,6 +156,7 @@ export const analysisApi = {
156
  runPipeline: (projectId: number, documentId: number) =>
157
  api.post<Analysis>("/analysis/pipeline", { project_id: projectId, document_id: documentId }),
158
  list: (projectId: number) => api.get<Analysis[]>(`/analysis/project/${projectId}`),
 
159
  }
160
 
161
  // RAG
 
156
  runPipeline: (projectId: number, documentId: number) =>
157
  api.post<Analysis>("/analysis/pipeline", { project_id: projectId, document_id: documentId }),
158
  list: (projectId: number) => api.get<Analysis[]>(`/analysis/project/${projectId}`),
159
+ clearAll: (projectId: number) => api.delete(`/analysis/project/${projectId}`),
160
  }
161
 
162
  // RAG
frontend/src/pages/ProjectDetailPage.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { useEffect, useRef, useState } from "react"
2
  import { useParams, useNavigate } from "react-router-dom"
3
  import { useTranslation } from "react-i18next"
4
- import { ArrowLeft, Upload, Play, Trash2, FileText, ChevronDown, ChevronUp } from "lucide-react"
5
  import { Button } from "@/components/ui/button"
6
  import { Badge } from "@/components/ui/badge"
7
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -119,6 +119,12 @@ export default function ProjectDetailPage() {
119
  }
120
  }
121
 
 
 
 
 
 
 
122
  if (loading) return <div className="p-6 text-muted-foreground">{t("common.loading")}</div>
123
  if (!project) return <div className="p-6">{t("common.error")}</div>
124
 
@@ -234,7 +240,15 @@ export default function ProjectDetailPage() {
234
 
235
  {/* Right column: analyses results */}
236
  <div className="lg:col-span-2 space-y-3">
237
- <h2 className="text-sm font-medium text-muted-foreground">{t("project.analyses")}</h2>
 
 
 
 
 
 
 
 
238
  {analyses.length === 0 ? (
239
  <p className="text-sm text-muted-foreground">{t("project.no_analyses")}</p>
240
  ) : (
 
1
  import { useEffect, useRef, useState } from "react"
2
  import { useParams, useNavigate } from "react-router-dom"
3
  import { useTranslation } from "react-i18next"
4
+ import { ArrowLeft, Upload, Play, Trash2, FileText, ChevronDown, ChevronUp, X } from "lucide-react"
5
  import { Button } from "@/components/ui/button"
6
  import { Badge } from "@/components/ui/badge"
7
  import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
 
119
  }
120
  }
121
 
122
+ async function handleClearAnalyses() {
123
+ if (!confirm(t("project.clear_analyses_confirm"))) return
124
+ await analysisApi.clearAll(projectId)
125
+ setAnalyses([])
126
+ }
127
+
128
  if (loading) return <div className="p-6 text-muted-foreground">{t("common.loading")}</div>
129
  if (!project) return <div className="p-6">{t("common.error")}</div>
130
 
 
240
 
241
  {/* Right column: analyses results */}
242
  <div className="lg:col-span-2 space-y-3">
243
+ <div className="flex items-center justify-between">
244
+ <h2 className="text-sm font-medium text-muted-foreground">{t("project.analyses")}</h2>
245
+ {analyses.length > 0 && (
246
+ <Button variant="ghost" size="sm" className="h-7 text-xs text-destructive hover:text-destructive gap-1" onClick={handleClearAnalyses}>
247
+ <X className="h-3 w-3" />
248
+ {t("project.clear_analyses")}
249
+ </Button>
250
+ )}
251
+ </div>
252
  {analyses.length === 0 ? (
253
  <p className="text-sm text-muted-foreground">{t("project.no_analyses")}</p>
254
  ) : (