diff --git a/backend/main.py b/backend/main.py index d792412..d7397ad 100644 --- a/backend/main.py +++ b/backend/main.py @@ -877,6 +877,39 @@ async def review_job(job_id: str, body: ReviewRequest): return JSONResponse(_job_row_to_dict(row)) +@app.delete("/api/jobs/{job_id}") +async def delete_job(job_id: str): + """Delete a job record and its stored image.""" + try: + uuid.UUID(job_id) + except ValueError: + raise HTTPException(status_code=400, detail="Invalid job ID.") + + try: + with get_db() as conn: + with conn.cursor() as cur: + cur.execute( + "DELETE FROM ocr_jobs WHERE id = %s RETURNING image_path", + (job_id,), + ) + row = cur.fetchone() + except Exception as exc: + print(f"delete_job DB error: {exc}") + raise HTTPException(status_code=500, detail="Database error.") + + if not row: + raise HTTPException(status_code=404, detail="Job not found.") + + # Best-effort removal of the stored image file + try: + if row["image_path"] and os.path.isfile(row["image_path"]): + os.remove(row["image_path"]) + except Exception: + pass + + return JSONResponse({"deleted": job_id}) + + if __name__ == "__main__": host = env_config("API_HOST", default="0.0.0.0") port = env_config("API_PORT", default=8000, cast=int) diff --git a/frontend/src/components/JobsPanel.jsx b/frontend/src/components/JobsPanel.jsx index 8d26796..8348803 100644 --- a/frontend/src/components/JobsPanel.jsx +++ b/frontend/src/components/JobsPanel.jsx @@ -3,7 +3,7 @@ import { useSuggestions } from '../hooks/useSuggestions' import { motion, AnimatePresence } from 'framer-motion' import { Search, ChevronLeft, ChevronRight, CheckCircle2, Clock, - FileText, Loader2, Save, RefreshCw, + FileText, Loader2, Save, RefreshCw, Trash2, } from 'lucide-react' import axios from 'axios' @@ -31,7 +31,7 @@ function StatusBadge({ status }) { // ───────────────────────────────────────────────────────────── // Full-screen Job Detail // ───────────────────────────────────────────────────────────── -function JobDetail({ jobId, onClose, onReviewed, suggestions = {} }) { +function JobDetail({ jobId, onClose, onReviewed, onDeleted, suggestions = {} }) { const [job, setJob] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -45,6 +45,8 @@ function JobDetail({ jobId, onClose, onReviewed, suggestions = {} }) { const [submitting, setSubmitting] = useState(false) const [saveResult, setSaveResult] = useState(null) + const [confirmDelete, setConfirmDelete] = useState(false) + const [deleting, setDeleting] = useState(false) useEffect(() => { let cancelled = false @@ -99,6 +101,19 @@ function JobDetail({ jobId, onClose, onReviewed, suggestions = {} }) { } } + const handleDelete = async () => { + setDeleting(true) + try { + await axios.delete(`${API_BASE}/jobs/${jobId}`) + onDeleted(jobId) + } catch (err) { + setSaveResult({ success: false, error: err.response?.data?.detail || err.message }) + setConfirmDelete(false) + } finally { + setDeleting(false) + } + } + const isReviewed = job?.status === 'reviewed' return ( @@ -125,6 +140,38 @@ function JobDetail({ jobId, onClose, onReviewed, suggestions = {} }) { {job.id} > )} +