"use client"; import { useEffect, useState } from "react"; import { Upload, Loader2, CheckCircle2, AlertCircle } from "lucide-react"; import { toast } from "sonner"; import { useQueryClient } from "@tanstack/react-query"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, } from "@/components/ui/sheet"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Progress } from "@/components/ui/progress"; import { useUploadPrecedent, libraryKeys, type PracticeArea, type SourceType, } from "@/lib/api/precedent-library"; import { useProgress } from "@/lib/api/documents"; import { PRACTICE_AREAS, PRECEDENT_LEVELS, SOURCE_TYPES, } from "./practice-area"; const ACCEPT = ".pdf,.docx,.doc,.rtf,.txt,.md"; type Props = { open: boolean; onOpenChange: (open: boolean) => void; }; export function PrecedentUploadSheet({ open, onOpenChange }: Props) { const [file, setFile] = useState(null); const [citation, setCitation] = useState(""); const [caseName, setCaseName] = useState(""); const [court, setCourt] = useState(""); const [decisionDate, setDecisionDate] = useState(""); const [sourceType, setSourceType] = useState(""); const [precedentLevel, setPrecedentLevel] = useState(""); const [practiceArea, setPracticeArea] = useState(""); const [appealSubtype, setAppealSubtype] = useState(""); const [subjectTags, setSubjectTags] = useState(""); const [headnote, setHeadnote] = useState(""); const [isBinding, setIsBinding] = useState(true); const [taskId, setTaskId] = useState(null); const upload = useUploadPrecedent(); const progress = useProgress(taskId); const qc = useQueryClient(); // Reset form when the sheet closes — fields, file input, and any in-flight // task subscription. We accept the cascade-render warning because resetting // form state on close is exactly the intended side effect. useEffect(() => { if (open) return; // eslint-disable-next-line react-hooks/set-state-in-effect setFile(null); setCitation(""); setCaseName(""); setCourt(""); // eslint-disable-next-line react-hooks/set-state-in-effect setDecisionDate(""); setSourceType(""); setPrecedentLevel(""); // eslint-disable-next-line react-hooks/set-state-in-effect setPracticeArea(""); setAppealSubtype(""); setSubjectTags(""); // eslint-disable-next-line react-hooks/set-state-in-effect setHeadnote(""); setIsBinding(true); setTaskId(null); }, [open]); // Auto-close on completion + refresh library list/stats so the new // row appears with up-to-date counts (halachot, approved). The mutation's // onSuccess fires when POST returns the task_id; we need a second // invalidation when SSE reports terminal status, otherwise the table // shows stale data. useEffect(() => { if (progress?.status === "completed") { qc.invalidateQueries({ queryKey: libraryKeys.all }); toast.success("הפסיקה הוכנסה לקורפוס. ההלכות ממתינות לאישור."); const t = window.setTimeout(() => onOpenChange(false), 1200); return () => window.clearTimeout(t); } if (progress?.status === "failed") { qc.invalidateQueries({ queryKey: libraryKeys.all }); toast.error(`כשל בעיבוד: ${progress.error || "שגיאה לא ידועה"}`); } }, [progress, onOpenChange, qc]); const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!file) { toast.error("בחר קובץ"); return; } if (!citation.trim()) { toast.error("מראה המקום (citation) חובה"); return; } if (!practiceArea) { toast.error("בחר תחום משפט"); return; } try { const tags = subjectTags .split(",") .map((t) => t.trim()) .filter(Boolean); const res = await upload.mutateAsync({ file, citation: citation.trim(), case_name: caseName.trim(), court: court.trim(), decision_date: decisionDate || undefined, source_type: sourceType || undefined, precedent_level: precedentLevel || undefined, practice_area: practiceArea, appeal_subtype: appealSubtype.trim(), subject_tags: tags, is_binding: isBinding, headnote: headnote.trim(), }); setTaskId(res.task_id); } catch (err) { toast.error(err instanceof Error ? err.message : "כשל בהעלאה"); } }; const isProcessing = taskId !== null && progress?.status !== "completed" && progress?.status !== "failed"; const stage = (progress as { stage?: string; percent?: number; step?: string } | null)?.stage; const percent = (progress as { percent?: number } | null)?.percent ?? 0; return ( העלאת פסיקה לקורפוס הסמכותי הקובץ יעבור חילוץ טקסט, יצירת embeddings, וחילוץ הלכות אוטומטי. ההלכות יחכו לאישורך לפני שהן זמינות לסוכני הכתיבה.
{/* File */}
setFile(e.target.files?.[0] ?? null)} disabled={isProcessing} />
{/* Citation */}
setCitation(e.target.value)} placeholder={`עע"מ 3975/22 ב. קרן-נכסים נ' ועדה מקומית`} disabled={isProcessing} dir="rtl" />
{/* Two-col grid */}
setCaseName(e.target.value)} placeholder="ב. קרן-נכסים" disabled={isProcessing} />
setCourt(e.target.value)} placeholder='בית משפט עליון / בג"ץ / מנהלי / ועדת ערר' disabled={isProcessing} />
setDecisionDate(e.target.value)} disabled={isProcessing} />
setAppealSubtype(e.target.value)} placeholder="שימוש חורג / סופיות ההחלטה" disabled={isProcessing} />
{/* Practice area (required radio) */}
{PRACTICE_AREAS.map((a) => ( ))}
setSubjectTags(e.target.value)} placeholder="חניה, קווי בניין, שיקול דעת" disabled={isProcessing} />