"use client"; import { useEffect, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { CheckCircle2, Clock, Eye, Loader2, Trash2, XCircle, } from "lucide-react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@/lib/api/client"; import { casesKeys } from "@/lib/api/cases"; import type { CaseDetail, CaseDocument } from "@/lib/api/cases"; /* * Document list for the case detail "מסמכים" tab. Uses the real document * row shape returned by the FastAPI case_get endpoint — see db.list_documents * and the `documents` schema in legal_mcp/services/db.py: * id · case_id · doc_type · title · file_path · extraction_status · * page_count · created_at · practice_area · appeal_subtype */ const DOC_TYPE_LABELS: Record = { appeal: "כתב ערר", response: "כתב תשובה", protocol: "פרוטוקול", decision: "החלטת ועדה מקומית", plan: "תכנית", reference: "חומר רקע", auto: "—", }; function doctypeLabel(t: string): string { return DOC_TYPE_LABELS[t] ?? t; } function doctypeTone(t: string): string { switch (t) { case "appeal": return "bg-info-bg text-info border-info/40"; case "response": return "bg-gold-wash text-gold-deep border-gold/40"; case "decision": return "bg-success-bg text-success border-success/40"; case "protocol": return "bg-warn-bg text-warn border-warn/40"; default: return "bg-rule-soft text-ink-muted border-rule"; } } const STATUS_LABELS: Record = { pending: "בהמתנה", processing: "בעיבוד", completed: "הושלם", proofread: "הוגה", failed: "נכשל", error: "שגיאה", }; /** Sort priority — lower = higher in list */ const STATUS_ORDER: Record = { failed: 0, error: 0, processing: 1, pending: 2, completed: 3, proofread: 3, }; function statusOrder(s: string): number { return STATUS_ORDER[s] ?? 3; } function StatusIcon({ status }: { status: string }) { switch (status) { case "completed": case "proofread": return ; case "processing": return ; case "pending": return ; case "failed": case "error": return ; default: return ; } } function formatDate(iso: string) { if (!iso) return "—"; try { return new Date(iso).toLocaleDateString("he-IL"); } catch { return iso; } } function filenameFromPath(path: string): string { const parts = path.split("/"); return parts[parts.length - 1] || path; } /* ── Document text preview dialog ──────────────────────────────── */ function DocumentPreviewDialog({ doc, open, onOpenChange, }: { doc: CaseDocument; open: boolean; onOpenChange: (v: boolean) => void; }) { const [text, setText] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!open) { setText(null); setError(null); return; } let cancelled = false; setLoading(true); setError(null); apiRequest<{ text: string }>(`/api/documents/${doc.id}/text`) .then((res) => { if (!cancelled) setText(res.text || "(ריק)"); }) .catch(() => { if (!cancelled) setError("המסמך עדיין לא עובד או שאין בו טקסט"); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [open, doc.id]); const displayName = doc.title || filenameFromPath(doc.file_path); return ( {displayName}
{loading && (
טוען מסמך...
)} {error && (
{error}
)} {text !== null && !loading && (
                {text}
              
)}
); } /* ── Delete confirmation dialog ────────────────────────────────── */ function DeleteConfirmDialog({ doc, caseNumber, open, onOpenChange, }: { doc: CaseDocument; caseNumber: string; open: boolean; onOpenChange: (v: boolean) => void; }) { const qc = useQueryClient(); const deleteMutation = useMutation({ mutationFn: () => apiRequest(`/api/cases/${caseNumber}/documents/${doc.id}`, { method: "DELETE", }), onSuccess: () => { qc.invalidateQueries({ queryKey: casesKeys.all }); onOpenChange(false); }, }); const displayName = doc.title || filenameFromPath(doc.file_path); return ( מחיקת מסמך

האם למחוק את המסמך “{displayName}”?
פעולה זו אינה ניתנת לביטול.

); } /* ── Single document row ───────────────────────────────────────── */ function DocumentRow({ doc, caseNumber, }: { doc: CaseDocument; caseNumber: string; }) { const [previewOpen, setPreviewOpen] = useState(false); const [deleteOpen, setDeleteOpen] = useState(false); const displayName = doc.title || filenameFromPath(doc.file_path); const canPreview = doc.extraction_status === "completed" || doc.extraction_status === "proofread"; return ( <>
  • {doc.doc_type && ( {doctypeLabel(doc.doc_type)} )}
  • {previewOpen && ( )} {deleteOpen && ( )} ); } /* ── Main panel ────────────────────────────────────────────────── */ export function DocumentsPanel({ data, }: { data?: CaseDetail; }) { const docs = data?.documents ?? []; const caseNumber = data?.case_number ?? ""; if (docs.length === 0) { return (

    אין מסמכים בתיק זה

    ); } const sorted = [...docs].sort( (a, b) => statusOrder(a.extraction_status) - statusOrder(b.extraction_status), ); const done = docs.filter( (d) => d.extraction_status === "completed" || d.extraction_status === "proofread", ).length; const processing = docs.filter((d) => d.extraction_status === "processing").length; const pending = docs.filter((d) => d.extraction_status === "pending").length; const failed = docs.filter( (d) => d.extraction_status === "failed" || d.extraction_status === "error", ).length; const hasIncomplete = processing > 0 || pending > 0 || failed > 0; const pct = docs.length > 0 ? Math.round((done / docs.length) * 100) : 0; return (
    {hasIncomplete && (
    {done > 0 && ( {done} {STATUS_LABELS.completed} )} {processing > 0 && ( {processing} {STATUS_LABELS.processing} )} {pending > 0 && ( {pending} {STATUS_LABELS.pending} )} {failed > 0 && ( {failed} {STATUS_LABELS.failed} )}
    0 && done === 0 ? "[&>div]:bg-danger" : ""} />
    )}
      {sorted.map((doc) => ( ))}
    ); }