"use client"; import { useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { ArrowDown, ArrowUp, CalendarDays, HelpCircle, Link2, Loader2, Plus, RotateCw, Sparkles, } from "lucide-react"; import { toast } from "sonner"; import { CHANGE_LABELS_HE, PROTOCOL_PARTY_LABELS_HE, PROTOCOL_PARTY_ORDER, useAnalyzeProtocol, useProtocolAnalysis, type ProtocolAnalysisRow, type ProtocolChangeType, type ProtocolPartyRole, } from "@/lib/api/protocol-analysis"; const PARTY_MARK_TONE: Record = { appellant: "bg-info", respondent: "bg-gold-deep", committee: "bg-success", permit_applicant: "bg-warn", unknown: "bg-ink-muted", "": "bg-ink-muted", }; const CHANGE_BADGE_TONE: Record = { strengthened: "bg-gold-wash text-gold-deep border-gold/40", newly_raised: "bg-emerald-50 text-emerald-900 border-emerald-200", dropped: "bg-rule-soft text-ink-soft border-rule", }; const CHANGE_ACCENT: Record = { strengthened: "border-s-gold", newly_raised: "border-s-emerald-500", dropped: "border-s-rule", }; function ChangeIcon({ type }: { type: ProtocolChangeType }) { if (type === "strengthened") return ; if (type === "newly_raised") return ; return ; } type FilterKey = "all" | ProtocolChangeType; function AttendeeColumn({ label, dotTone, names, }: { label: string; dotTone: string; names: string[]; }) { if (!names.length) return null; return (
{label}
{names.join(" · ")}
); } function DevelopmentCard({ row }: { row: ProtocolAnalysisRow }) { return (
{CHANGE_LABELS_HE[row.change_type]} {row.argument_title}
{row.summary && (

{row.summary}

)} {row.sharpened_question && (
השאלה שהתחדדה
{row.sharpened_question}
)} {row.evidence_quote && (
“{row.evidence_quote}” — מתוך פרוטוקול הדיון
)}
{row.change_type === "newly_raised" ? ( אין מקבילה בכתב — עלה לראשונה בדיון ) : row.argument_id ? ( מבוסס על טיעון כתוב קיים ) : null}
); } type HearingChangesPanelProps = { caseNumber: string; }; export function HearingChangesPanel({ caseNumber }: HearingChangesPanelProps) { const { data, isPending, isError, error } = useProtocolAnalysis(caseNumber); const analyze = useAnalyzeProtocol(caseNumber); const [filter, setFilter] = useState("all"); const handleAnalyze = () => { analyze.mutate(undefined, { onSuccess: (res) => { if (res.status === "queued") { toast.success("נשלח למנתח המשפטי — הניתוח ירוץ ברקע; רענן בעוד כמה דקות."); } else { toast.warning( `לא ניתן להריץ אוטומטית (${res.reason}). ניתן להריץ ידנית מ-Claude Code.`, ); } }, onError: (e) => toast.error(`שגיאה: ${(e as Error).message}`), }); }; const header = data?.header; const counts = data?.by_change ?? {}; const strengthened = counts.strengthened ?? 0; const newlyRaised = counts.newly_raised ?? 0; const dropped = counts.dropped ?? 0; const sections = useMemo(() => { if (!data) return []; return PROTOCOL_PARTY_ORDER.map((role) => { const rows = (data.by_party[role] ?? []).filter( (r) => filter === "all" || r.change_type === filter, ); return { role, rows }; }).filter((s) => s.rows.length > 0); }, [data, filter]); const runButton = ( ); return (
{isPending ? (
) : isError ? (

שגיאה בטעינת ניתוח-הפרוטוקול: {(error as Error).message}

) : !data?.total ? (

טרם נותח פרוטוקול

כשיש בתיק פרוטוקול של ועדת-הערר וטיעונים מאוגדים, הניתוח מזהה מה התחזק ומה נטען לראשונה בדיון. ההרצה נשלחת למנתח המשפטי ורצה ברקע.

{runButton}
) : ( <>
{runButton}
{/* hearing header strip */} {header && (
{header.protocol_title || "פרוטוקול דיון"}
{header.hearing_date ? `תאריך דיון: ${header.hearing_date}` : "תאריך דיון לא זוהה"} {header.panel_members.length > 0 && ` · מותב: ${header.panel_members.join(", ")}`}
פרוטוקול ועדת-הערר
{(header.appellants_present.length > 0 || header.respondents_present.length > 0) && (
)}
)} {/* filter pills */}
setFilter("all")} label="הכל" count={data.total} /> {strengthened > 0 && ( setFilter("strengthened")} label="התחזקו" count={strengthened} icon={} /> )} {newlyRaised > 0 && ( setFilter("newly_raised")} label="נטענו לראשונה" count={newlyRaised} icon={} /> )} {dropped > 0 && ( setFilter("dropped")} label="נזנחו" count={dropped} icon={} /> )}
{/* per-party sections */} {sections.map(({ role, rows }) => (
{PROTOCOL_PARTY_LABELS_HE[role]} {rows.length}
{rows.map((r) => ( ))}
))} )}
); } function FilterPill({ active, onClick, label, count, icon, }: { active: boolean; onClick: () => void; label: string; count: number; icon?: React.ReactNode; }) { return ( ); }