Files
legal-ai/web-ui/src/components/compose/chair-editor.tsx
Chaim 1f1a025509 fix(lint): תיקון 10 שגיאות ESLint + ניקוי directives מיותרים
10 שגיאות (כולן קיימות-מראש, לא מהפיצ'רים האחרונים):
- react/no-unescaped-entities (3): legal-arguments-panel, precedent-edit-sheet
  — escaping של מרכאות ב-JSX (“/")
- react-hooks/set-state-in-effect (6): documents-panel, chair-editor,
  content-checklists, discussion-rules, golden-ratios, documents.ts
  — disable-comment לדפוסי sync/reset לגיטימיים (false-positive ידוע)
- React Compiler reassign (1): subject-donut — refactor לחישוב prefix-sums
  ללא mutable accumulator

ניקוי: הסרת 5 eslint-disable directives מיותרים (halacha-review-panel,
precedent-upload-sheet). תוצאה: 0 errors (היה 10), 24→ warnings (היה 29).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:31:31 +00:00

98 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useRef, useState } from "react";
import { useSaveChairPosition } from "@/lib/api/research";
/*
* Chair-position editor for a single threshold claim or issue.
*
* Autosaves on blur, with an optimistic in-memory "last saved" value so the
* user sees immediate feedback. No debounced per-keystroke save — the user
* writes in long paragraphs and the backend writes to a file, so per-blur
* is the right granularity (matches the vanilla UI's behavior).
*/
type SaveState =
| { kind: "idle" }
| { kind: "saving" }
| { kind: "saved"; at: Date }
| { kind: "error"; message: string };
export function ChairEditor({
caseNumber,
sectionId,
initialValue,
}: {
caseNumber: string;
sectionId: string;
initialValue: string;
}) {
const [value, setValue] = useState(initialValue);
const [state, setState] = useState<SaveState>({ kind: "idle" });
const lastSaved = useRef(initialValue);
const mutate = useSaveChairPosition(caseNumber);
/* Reset when the upstream analysis refetches (e.g. after initial load) */
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect -- sync from server
setValue(initialValue);
lastSaved.current = initialValue;
}, [initialValue]);
const save = async () => {
const trimmed = value.trim();
if (trimmed === lastSaved.current.trim()) return;
setState({ kind: "saving" });
try {
await mutate.mutateAsync({ sectionId, position: trimmed });
lastSaved.current = trimmed;
setState({ kind: "saved", at: new Date() });
} catch (e) {
setState({
kind: "error",
message: e instanceof Error ? e.message : "שגיאה בשמירה",
});
}
};
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<span className="text-[0.78rem] font-semibold text-navy">
עמדת ועדת הערר
</span>
<SaveIndicator state={state} />
</div>
<textarea
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={save}
rows={6}
dir="rtl"
placeholder="כתבי כאן את עמדתך לגבי סוגיה זו. הטקסט נשמר אוטומטית כשעוזבת את השדה."
className="
w-full resize-y rounded border border-rule bg-parchment
px-3 py-2 text-sm leading-relaxed text-ink
shadow-inner focus:border-gold focus:outline-none
focus:ring-2 focus:ring-gold/30
"
/>
</div>
);
}
function SaveIndicator({ state }: { state: SaveState }) {
if (state.kind === "idle") return null;
if (state.kind === "saving") {
return <span className="text-[0.72rem] text-ink-muted"> שומר</span>;
}
if (state.kind === "saved") {
const time = state.at.toLocaleTimeString("he-IL", {
hour: "2-digit",
minute: "2-digit",
});
return <span className="text-[0.72rem] text-success"> נשמר {time}</span>;
}
return <span className="text-[0.72rem] text-danger"> {state.message}</span>;
}