Files
legal-ai/web-ui/src/components/methodology/generic-methodology-panel.tsx
Chaim e4fbda6c1f feat(style-acq T12): /methodology — קטגוריות ביטויי-מעבר + אנטי-דפוסים
מרחיב את עורך-הפרופיל ב-/methodology עם 2 קטגוריות נוספות שהכותב (T15)
והמדד (T7) צורכים — כך שהיו"ר עורכת אותן והעריכה זורמת לכתיבה:

- app.py: _METHODOLOGY_DEFAULTS += transition_phrases (מקובץ לפי תוצאה) +
  anti_patterns (מ-lessons.ANTI_PATTERNS). דרך ה-CRUD הגנרי הקיים (appeal_type_rules).
- block_writer (T15 loop): קורא overrides גם ל-transition_phrases + anti_patterns.
- web-ui: GenericMethodologyPanel (עורך key→JSON) + 2 טאבים ב-/methodology.

voice_invariants (doc) — נדחה (לא key-value). G11, INV-LRN4.

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

104 lines
3.8 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 { useState } from "react";
import { Loader2, Save, RotateCcw } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Textarea } from "@/components/ui/textarea";
import {
useMethodology,
useUpdateMethodology,
useResetMethodology,
} from "@/lib/api/methodology";
/**
* Generic key→JSON editor for methodology categories whose value shape is
* arbitrary (T12: transition_phrases, anti_patterns). Each key is editable as
* formatted JSON; Save validates + upserts an override, Reset restores default.
* These edits flow to the writer via the accumulated-learning channel (T15).
*/
function Row({ category, k, value, isOverride }: {
category: string; k: string; value: unknown; isOverride: boolean;
}) {
const [draft, setDraft] = useState(JSON.stringify(value, null, 2));
const [err, setErr] = useState<string | null>(null);
const update = useUpdateMethodology(category);
const reset = useResetMethodology(category);
const dirty = draft !== JSON.stringify(value, null, 2);
const onSave = () => {
let parsed: unknown;
try {
parsed = JSON.parse(draft);
} catch {
setErr("JSON לא תקין");
return;
}
setErr(null);
update.mutate({ key: k, value: parsed }, {
onSuccess: () => toast.success(`${k} נשמר`),
onError: (e) => toast.error(e instanceof Error ? e.message : "שגיאה"),
});
};
const onReset = () => {
reset.mutate(k, {
onSuccess: () => { toast.success(`${k} אופס לברירת-מחדל`); setDraft(""); },
onError: (e) => toast.error(e instanceof Error ? e.message : "שגיאה"),
});
};
return (
<div className="rounded-lg border border-rule bg-surface p-3 space-y-2">
<div className="flex items-center justify-between gap-2">
<span className="font-mono text-sm text-navy">{k}</span>
<Badge variant={isOverride ? "default" : "secondary"} className="text-[0.65rem]">
{isOverride ? "מותאם" : "ברירת-מחדל"}
</Badge>
</div>
<Textarea
value={draft} rows={Math.min(12, draft.split("\n").length + 1)} dir="ltr"
onChange={(e) => setDraft(e.target.value)}
className="font-mono text-[0.78rem] leading-snug"
/>
{err && <p className="text-danger text-[0.72rem]">{err}</p>}
<div className="flex items-center gap-2 justify-end">
{isOverride && (
<Button size="sm" variant="ghost" disabled={reset.isPending}
onClick={onReset} className="text-ink-muted">
<RotateCcw className="w-3.5 h-3.5 me-1" /> איפוס
</Button>
)}
<Button size="sm" disabled={!dirty || update.isPending} onClick={onSave}
className="bg-navy text-parchment hover:bg-navy-soft">
{update.isPending ? <Loader2 className="w-3.5 h-3.5 animate-spin me-1" />
: <Save className="w-3.5 h-3.5 me-1" />}
שמור
</Button>
</div>
</div>
);
}
export function GenericMethodologyPanel({ category, hint }: { category: string; hint?: string }) {
const { data, isPending, error } = useMethodology(category);
if (error) return <p className="text-danger text-sm">שגיאה בטעינה.</p>;
if (isPending) return <Loader2 className="w-5 h-5 animate-spin text-ink-muted" />;
const items = data?.items ?? {};
return (
<div className="space-y-3">
{hint && <p className="text-[0.8rem] text-ink-muted">{hint}</p>}
{Object.entries(items).map(([k, item]) => (
<Row key={k} category={category} k={k} value={item.value} isOverride={item.is_override} />
))}
{Object.keys(items).length === 0 && (
<p className="text-ink-muted text-sm">אין פריטים.</p>
)}
</div>
);
}