diff --git a/web-ui/src/app/cases/[caseNumber]/page.tsx b/web-ui/src/app/cases/[caseNumber]/page.tsx index da1f46f..f252362 100644 --- a/web-ui/src/app/cases/[caseNumber]/page.tsx +++ b/web-ui/src/app/cases/[caseNumber]/page.tsx @@ -14,6 +14,7 @@ import { StatusGuide } from "@/components/cases/status-guide"; import { StatusChanger } from "@/components/cases/status-changer"; import { DocumentsPanel } from "@/components/cases/documents-panel"; import { DraftsPanel } from "@/components/cases/drafts-panel"; +import { DecisionBlocksPanel } from "@/components/cases/decision-blocks-panel"; import { LegalArgumentsPanel } from "@/components/cases/legal-arguments-panel"; import { AgentActivityFeed } from "@/components/cases/agent-activity-feed"; import { AgentStatusWidget } from "@/components/cases/agent-status-widget"; @@ -81,6 +82,9 @@ export default function CaseDetailPage({ טיעונים + + ההחלטה + טיוטות והערות @@ -147,6 +151,10 @@ export default function CaseDetailPage({ + + + + = { + empty: "ריק", + draft: "טיוטה", + review: "בבדיקה", + final: "סופי", +}; + +const STATUS_CLASSES: Record = { + empty: "bg-rule-soft text-ink-muted border-rule", + draft: "bg-gold/10 text-gold-deep border-gold/30", + review: "bg-blue-50 text-blue-700 border-blue-200", + final: "bg-success-bg text-success border-success/40", +}; + +function blockLabel(b: DecisionBlock): string { + return BLOCK_LABELS[b.block_id] ?? b.title ?? b.block_id; +} + +/* ── Main panel ───────────────────────────────────────── */ + +export function DecisionBlocksPanel({ caseNumber }: { caseNumber: string }) { + const { data, isLoading, error } = useDecisionBlocks(caseNumber); + + if (isLoading) { + return

טוען...

; + } + if (error) { + return ( +

+ שגיאה בטעינת תוכן ההחלטה: {error.message} +

+ ); + } + if (!data) return null; + + const written = data.blocks.filter((b) => b.word_count > 0).length; + + return ( +
+ {/* ── Source-of-truth warning ── */} + {data.source_of_truth === "docx" && ( +
+ +

+ קיים קובץ DOCX מתוקן המשמש כמקור האמת לתיק זה. עריכת בלוקים כאן + נשמרת ב-DB אך לא תעדכן את ה-DOCX עד הפקת טיוטה + מחדש. +

+
+ )} + + {/* ── Header line ── */} +
+

תוכן ההחלטה לפי בלוקים

+ + {written}/12 בלוקים נכתבו + +
+ + {!data.has_decision && ( +

+ טרם נכתבו בלוקים לתיק זה. ניתן להתחיל לכתוב בכל בלוק להלן — הכתיבה + תיצור את ההחלטה אוטומטית. +

+ )} + + {/* ── 12 blocks ── */} + + {data.blocks.map((block) => ( + + +
+ + {blockLabel(block)} + + + {STATUS_LABELS[block.status]} + + {block.word_count > 0 && ( + + {block.word_count} מילים + + )} +
+
+ + + +
+ ))} +
+
+ ); +} + +/* ── Per-block view / edit ────────────────────────────── */ + +type SaveState = + | { kind: "idle" } + | { kind: "saving" } + | { kind: "saved"; at: Date } + | { kind: "error"; message: string }; + +function BlockEditor({ + caseNumber, + block, +}: { + caseNumber: string; + block: DecisionBlock; +}) { + const [editing, setEditing] = useState(false); + const [value, setValue] = useState(block.content); + const [state, setState] = useState({ kind: "idle" }); + /* The last content known to be persisted — used to skip no-op saves. */ + const [baseline, setBaseline] = useState(block.content); + const save = useSaveBlock(caseNumber); + + /* Re-sync when the upstream query refetches (e.g. after another save) while + * not actively editing. Adjusting state during render — the documented React + * pattern for derived-from-props — avoids a setState-in-effect cascade. */ + if (!editing && block.content !== baseline) { + setBaseline(block.content); + setValue(block.content); + } + + async function handleSave() { + const next = value; + if (next === baseline) return; + setState({ kind: "saving" }); + try { + await save.mutateAsync({ blockId: block.block_id, content: next }); + setBaseline(next); + setState({ kind: "saved", at: new Date() }); + } catch (e) { + setState({ + kind: "error", + message: e instanceof Error ? e.message : "שגיאה בשמירה", + }); + } + } + + if (!editing) { + return ( +
+ {block.content.trim() ? ( + + ) : ( +

בלוק ריק.

+ )} +
+ +
+
+ ); + } + + return ( +
+
+ + + עריכת תוכן הבלוק (Markdown) — נשמר בעת יציאה מהשדה + + +
+