Add delete button for draft files in case drafts panel
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 45s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 45s
- Add DELETE /api/cases/{case_number}/exports/{filename} endpoint
- Add useDeleteDraft hook in exports API
- Add trash icon + confirmation dialog in drafts panel UI
- Final files (סופי-) cannot be deleted as a safety measure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
useExportDocx,
|
||||
useUploadDraft,
|
||||
useMarkFinal,
|
||||
useDeleteDraft,
|
||||
} from "@/lib/api/exports";
|
||||
import {
|
||||
useCaseFeedback,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
Loader2,
|
||||
FileOutput,
|
||||
Plus,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
|
||||
/* Statuses at which a draft is considered ready */
|
||||
@@ -79,9 +81,11 @@ export function DraftsPanel({
|
||||
const exportDocx = useExportDocx(caseNumber);
|
||||
const uploadDraft = useUploadDraft(caseNumber);
|
||||
const markFinal = useMarkFinal(caseNumber);
|
||||
const deleteDraft = useDeleteDraft(caseNumber);
|
||||
const resolveMutation = useResolveFeedback();
|
||||
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
|
||||
|
||||
const isDraftReady = status && DRAFT_READY.includes(status);
|
||||
const openFeedbacks = feedbacks?.filter((f) => !f.resolved) ?? [];
|
||||
@@ -233,16 +237,26 @@ export function DraftsPanel({
|
||||
הורד
|
||||
</Button>
|
||||
{!file.is_final && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 px-2 text-ink-muted"
|
||||
onClick={() => handleMarkFinal(file.filename)}
|
||||
disabled={markFinal.isPending}
|
||||
>
|
||||
<Award className="w-3.5 h-3.5 me-1" />
|
||||
סמן כסופי
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 px-2 text-ink-muted"
|
||||
onClick={() => handleMarkFinal(file.filename)}
|
||||
disabled={markFinal.isPending}
|
||||
>
|
||||
<Award className="w-3.5 h-3.5 me-1" />
|
||||
סמן כסופי
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 px-2 text-red-500 hover:text-red-700 hover:bg-red-50"
|
||||
onClick={() => setDeleteTarget(file.filename)}
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
@@ -252,6 +266,55 @@ export function DraftsPanel({
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete confirmation dialog */}
|
||||
<Dialog
|
||||
open={deleteTarget !== null}
|
||||
onOpenChange={(open) => !open && setDeleteTarget(null)}
|
||||
>
|
||||
<DialogContent className="sm:max-w-sm" dir="rtl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>מחיקת טיוטה</DialogTitle>
|
||||
</DialogHeader>
|
||||
<p className="text-sm text-ink-muted">
|
||||
למחוק את הקובץ{" "}
|
||||
<span className="font-medium text-ink">{deleteTarget}</span>?
|
||||
<br />
|
||||
פעולה זו לא ניתנת לביטול.
|
||||
</p>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setDeleteTarget(null)}
|
||||
>
|
||||
ביטול
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
disabled={deleteDraft.isPending}
|
||||
onClick={() => {
|
||||
if (!deleteTarget) return;
|
||||
deleteDraft.mutate(deleteTarget, {
|
||||
onSuccess: () => {
|
||||
toast.success("הקובץ נמחק");
|
||||
setDeleteTarget(null);
|
||||
},
|
||||
onError: () => toast.error("שגיאה במחיקה"),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{deleteDraft.isPending ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin me-1" />
|
||||
) : (
|
||||
<Trash2 className="w-4 h-4 me-1" />
|
||||
)}
|
||||
מחק
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</section>
|
||||
|
||||
{/* ── Chair feedback ── */}
|
||||
|
||||
@@ -71,6 +71,20 @@ export function useUploadDraft(caseNumber: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteDraft(caseNumber: string) {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (filename: string) =>
|
||||
apiRequest<{ deleted: boolean; filename: string }>(
|
||||
`/api/cases/${caseNumber}/exports/${filename}`,
|
||||
{ method: "DELETE" },
|
||||
),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: exportsKeys.list(caseNumber) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useMarkFinal(caseNumber: string) {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
|
||||
11
web/app.py
11
web/app.py
@@ -1907,6 +1907,17 @@ async def api_download_export(case_number: str, filename: str):
|
||||
)
|
||||
|
||||
|
||||
@app.delete("/api/cases/{case_number}/exports/{filename}")
|
||||
async def api_delete_export(case_number: str, filename: str):
|
||||
"""Delete an exported draft file."""
|
||||
export_dir = config.find_case_dir(case_number) / "exports"
|
||||
path = export_dir / filename
|
||||
if not path.exists() or not path.parent.samefile(export_dir):
|
||||
raise HTTPException(404, "קובץ לא נמצא")
|
||||
path.unlink()
|
||||
return {"deleted": True, "filename": filename}
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/exports/upload")
|
||||
async def api_upload_export(case_number: str, file: UploadFile = File(...)):
|
||||
"""Upload a revised version of a draft."""
|
||||
|
||||
Reference in New Issue
Block a user