From 3288624349b09343835b4bbb62475becd805a8bc Mon Sep 17 00:00:00 2001 From: Chaim Date: Wed, 15 Apr 2026 16:30:39 +0000 Subject: [PATCH] Add methodology settings page with golden ratios, discussion rules, and checklists New /methodology page with 3 tabs for viewing and editing decision writing methodology. Uses DB override pattern: hardcoded Python constants serve as defaults, edits saved to appeal_type_rules table, delete restores default. Backend: 3 generic endpoints (GET/PUT/DELETE /api/methodology/{category}/{key}) with validation per category type. Frontend: methodology.ts hooks, GoldenRatiosPanel (number inputs per outcome/section), DiscussionRulesPanel (accordion with textarea per rule), ContentChecklistsPanel (markdown editor with preview toggle). Co-Authored-By: Claude Opus 4.6 (1M context) --- web-ui/src/app/methodology/page.tsx | 47 +++++ web-ui/src/components/app-shell.tsx | 1 + .../methodology/content-checklists-panel.tsx | 181 +++++++++++++++++ .../methodology/discussion-rules-panel.tsx | 188 ++++++++++++++++++ .../methodology/golden-ratios-panel.tsx | 177 +++++++++++++++++ web-ui/src/lib/api/methodology.ts | 71 +++++++ web/app.py | 101 ++++++++++ 7 files changed, 766 insertions(+) create mode 100644 web-ui/src/app/methodology/page.tsx create mode 100644 web-ui/src/components/methodology/content-checklists-panel.tsx create mode 100644 web-ui/src/components/methodology/discussion-rules-panel.tsx create mode 100644 web-ui/src/components/methodology/golden-ratios-panel.tsx create mode 100644 web-ui/src/lib/api/methodology.ts diff --git a/web-ui/src/app/methodology/page.tsx b/web-ui/src/app/methodology/page.tsx new file mode 100644 index 0000000..62767b0 --- /dev/null +++ b/web-ui/src/app/methodology/page.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { AppShell } from "@/components/app-shell"; +import { Card, CardContent } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { GoldenRatiosPanel } from "@/components/methodology/golden-ratios-panel"; +import { DiscussionRulesPanel } from "@/components/methodology/discussion-rules-panel"; +import { ContentChecklistsPanel } from "@/components/methodology/content-checklists-panel"; + +export default function MethodologyPage() { + return ( + +
+
+

מתודולוגיה

+

+ הגדרות ניסוח — יחסי אורך, כללי דיון, וצ׳קליסטים לפי סוג ערר +

+
+ + + + + + יחסי זהב + כללי דיון + צ׳קליסטים + + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/web-ui/src/components/app-shell.tsx b/web-ui/src/components/app-shell.tsx index 102d0b4..b8c13c6 100644 --- a/web-ui/src/components/app-shell.tsx +++ b/web-ui/src/components/app-shell.tsx @@ -25,6 +25,7 @@ type NavItem = { const NAV_ITEMS: NavItem[] = [ { href: "/", label: "בית" }, { href: "/training", label: "אימון סגנון" }, + { href: "/methodology", label: "מתודולוגיה" }, { href: "/skills", label: "מיומנויות" }, { href: "/diagnostics", label: "אבחון" }, { href: "/settings", label: "הגדרות" }, diff --git a/web-ui/src/components/methodology/content-checklists-panel.tsx b/web-ui/src/components/methodology/content-checklists-panel.tsx new file mode 100644 index 0000000..6e3970e --- /dev/null +++ b/web-ui/src/components/methodology/content-checklists-panel.tsx @@ -0,0 +1,181 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Card, CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Textarea } from "@/components/ui/textarea"; +import { Markdown } from "@/components/ui/markdown"; +import { + useMethodology, + useUpdateMethodology, + useResetMethodology, +} from "@/lib/api/methodology"; +import { toast } from "sonner"; +import { Save, RotateCcw, Eye, EyeOff, Loader2 } from "lucide-react"; + +const CHECKLIST_LABELS: Record = { + licensing_substantive: "ערר רישוי מהותי", + licensing_threshold: "ערר רישוי סף/סמכות", + licensing_property: "ערר רישוי קנייני", + tama38: "תמ\"א 38", + betterment_levy: "היטל השבחה", +}; + +const CHECKLIST_ORDER = [ + "licensing_substantive", + "licensing_threshold", + "licensing_property", + "tama38", + "betterment_levy", +]; + +type ChecklistItem = { + key: string; + label: string; + original: string; + draft: string; + isOverride: boolean; + dirty: boolean; +}; + +export function ContentChecklistsPanel() { + const { data, isLoading } = useMethodology("content_checklists"); + const update = useUpdateMethodology("content_checklists"); + const reset = useResetMethodology("content_checklists"); + const [items, setItems] = useState([]); + const [active, setActive] = useState(CHECKLIST_ORDER[0]); + const [preview, setPreview] = useState(false); + + useEffect(() => { + if (!data?.items) return; + setItems( + CHECKLIST_ORDER + .filter((k) => k in data.items) + .map((key) => ({ + key, + label: CHECKLIST_LABELS[key] ?? key, + original: data.items[key].value, + draft: data.items[key].value, + isOverride: data.items[key].is_override, + dirty: false, + })), + ); + }, [data]); + + const current = items.find((i) => i.key === active); + + const updateDraft = (text: string) => { + setItems((prev) => + prev.map((i) => + i.key === active + ? { ...i, draft: text, dirty: text !== i.original } + : i, + ), + ); + }; + + const handleSave = () => { + if (!current) return; + update.mutate( + { key: current.key, value: current.draft }, + { + onSuccess: () => toast.success(`${current.label} נשמר`), + onError: () => toast.error("שגיאה בשמירה"), + }, + ); + }; + + const handleReset = () => { + if (!current) return; + reset.mutate(current.key, { + onSuccess: () => toast.success(`${current.label} אופס`), + onError: () => toast.error("שגיאה באיפוס"), + }); + }; + + if (isLoading) { + return ( +
+ + טוען... +
+ ); + } + + return ( +
+ {/* Tab selector */} +
+ {items.map((item) => ( + + ))} +
+ + {/* Editor / Preview */} + {current && ( + + +
+

{current.label}

+ +
+ + {preview ? ( +
+ +
+ ) : ( +