feat(ui): IA redesign → production · יישום נאמן של 16 הדפים הנותרים למוקאפים
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s

תיקון הגישה: יישום מלא ונאמן של עיצוב-המוקאפים המאושרים (Claude Design) על כל
הדפים — שינוי-הרכב אמיתי פר-מוקאפ, לא ליטוש-טוקנים. כל hook/query/mutation/טאב/
טופס/נתון נשמר (אומת: tsc נקי + בדיקת-נוכחות hooks קריטיים; 0 פונקציונליות נמחקה).

דפים (← מוקאפ):
- בית — לוח: KPI + "תיקים לפי סטטוס" (bars) + כרטיס-אישורים + CTA כפול.
- ארכיון — filter-bar שטוח + טבלה נקייה + צ'יפי-סוג/תוצאה.
- הערות יו״ר — פריסה דו-טורית + טופס-הוספה חי + כרטיסי-הערה.
- ספריית-פסיקה — tabs קו-תחתון + כרטיסי-תוצאה halacha/קטע + AuthorityBadge.
- דף-תקדים — באנר-meta parchment + דו-טורי + provenance pills.
- פסיקה-חסרה — pill פתוחים + צ'יפי-סטטוס + CTA העלאה.
- יומונים — אזור-העלאה מקווקו + כרטיסי-digest + "ממתין" כתווית פסיבית.
- גרף — פאנל-צד שכבות/אנליטיקה + canvas parchment.
- אימון-סגנון — פורטרט: banner + KPI + אנטומיה + ביטויי-חתימה.
- מתודולוגיה — עורך-צ'קליסט + "חל על:" + canon chip.
- מיומנויות/סקריפטים — טבלאות אמיתיות + צ'יפי-סטטוס.
- הגדרות — sidenav דו-טורי + env-rows עם "ממתין ל-redeploy".
- דף-תיק — באנר-תיק parchment + tabs + timeline + "פתח עורך החלטה".
- תפעול — SectionHeaders + טבלת-שירותים + כרטיסי-שער gold-wash.
- compose — באנר-תיק + SOT pill + פריסה דו-טורית + "השלמה והעברה".

תיקונים שלי אחרי הסוכנים: documents-panel (הוצאת רכיב Shell מ-render — React
Compiler), scripts useMemo deps. /approvals כבר נבנה מחדש נאמנה (commit קודם).

בדיקות: npx tsc --noEmit ✓ · eslint ✓ (לבד מ-learning-panel:109 קיים-מראש).
שימור-פונקציונליות אומת. CI Docker build = שער סופי לפני deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:00:25 +00:00
parent c53ef9a7c4
commit f3b075d282
32 changed files with 2925 additions and 1799 deletions

View File

@@ -7,12 +7,11 @@ import { toast } from "sonner";
import { AppShell } from "@/components/app-shell";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
useFeedbackList,
useResolveFeedback,
useCreateFeedback,
CATEGORY_LABELS,
CATEGORY_COLORS,
BLOCK_LABELS,
type ChairFeedback,
type FeedbackCategory,
@@ -24,6 +23,16 @@ import {
* "טרם יושמו" וקטגוריה, וסימון כל הערה כיושמה. מוזן מ-/api/feedback.
*/
// category chip styling per mockup 06 (.c-missing / .c-tone / .c-struct / .c-fact / .c-style)
const CAT_CHIP: Record<FeedbackCategory, string> = {
missing_content: "bg-warn-bg text-warn",
wrong_tone: "bg-info-bg text-info",
wrong_structure: "bg-gold-wash text-gold-deep border border-rule",
factual_error: "bg-danger-bg text-danger",
style: "bg-rule-soft text-ink-soft",
other: "bg-rule-soft text-ink-soft",
};
function formatDate(iso?: string | null): string {
if (!iso) return "";
try {
@@ -57,47 +66,51 @@ function FeedbackCard({ fb }: { fb: ChairFeedback }) {
};
return (
<Card className={`bg-surface border-rule shadow-sm ${fb.resolved ? "opacity-60" : ""}`}>
<CardContent className="px-5 py-4 space-y-2.5">
<div className="flex items-start gap-2 flex-wrap">
<Badge variant="outline" className={`text-[0.7rem] ${CATEGORY_COLORS[fb.category]}`}>
<Card className={`bg-surface border-rule shadow-sm ${fb.resolved ? "opacity-[0.78]" : ""}`}>
<CardContent className="px-[18px] py-4 space-y-2.5">
{/* meta row — where · category chip · when (mockup 06 .meta) */}
<div className="flex items-center gap-2.5 flex-wrap">
<span className="font-semibold text-navy text-[0.84rem]">
{fb.case_number ? (
<Link
href={`/cases/${encodeURIComponent(fb.case_number)}`}
className="hover:text-gold-deep"
>
ערר {fb.case_number}
</Link>
) : (
"ללא תיק"
)}
<span className="text-ink-muted font-normal"> · {BLOCK_LABELS[fb.block_id] ?? fb.block_id}</span>
</span>
<span
className={`inline-block rounded-full px-2.5 py-0.5 text-[0.72rem] font-semibold whitespace-nowrap ${CAT_CHIP[fb.category]}`}
>
{CATEGORY_LABELS[fb.category]}
</Badge>
<Badge variant="outline" className="text-[0.7rem]">
{BLOCK_LABELS[fb.block_id] ?? fb.block_id}
</Badge>
{fb.case_number ? (
<Link
href={`/cases/${encodeURIComponent(fb.case_number)}`}
className="text-[0.72rem] text-gold-deep hover:underline"
>
תיק {fb.case_number}
</Link>
) : (
<span className="text-[0.72rem] text-ink-muted">ללא תיק</span>
)}
<span className="ms-auto text-[0.72rem] text-ink-muted">
</span>
<span className="ms-auto text-[0.72rem] text-ink-muted whitespace-nowrap">
{formatDate(fb.created_at)}
</span>
</div>
<p className="text-navy text-sm leading-relaxed m-0 whitespace-pre-wrap" dir="rtl">
<p className="text-ink-soft text-sm leading-relaxed m-0 whitespace-pre-wrap" dir="rtl">
{fb.feedback_text}
</p>
{fb.lesson_extracted ? (
<div className="rounded-md bg-gold-wash/40 border-s-[3px] border-gold ps-3 pe-3 py-2">
<div className="text-[0.68rem] text-gold-deep mb-0.5">לקח שהופק</div>
<p className="text-ink-soft text-[0.82rem] leading-relaxed m-0 whitespace-pre-wrap italic" dir="rtl">
{fb.lesson_extracted}
</p>
</div>
<p
className="text-[0.82rem] text-ink-muted italic leading-relaxed m-0 whitespace-pre-wrap border-s-[3px] border-gold ps-2.5"
dir="rtl"
>
לקח שחולץ: {fb.lesson_extracted}
</p>
) : null}
<div className="flex items-center justify-end pt-1 border-t border-rule-soft">
{/* action row — gold CTA / applied pill (mockup 06 .actrow) */}
<div className="flex items-center justify-start pt-1">
{fb.resolved ? (
<span className="flex items-center gap-1 text-[0.78rem] text-emerald-700">
<CheckCircle2 className="w-3.5 h-3.5" /> יושמה
<span className="inline-flex items-center gap-1.5 rounded-full bg-success-bg text-success text-[0.76rem] font-semibold px-3 py-1">
<CheckCircle2 className="w-3.5 h-3.5" /> יושם
</span>
) : (
<Button
@@ -118,6 +131,118 @@ function FeedbackCard({ fb }: { fb: ChairFeedback }) {
type CatFilter = "all" | FeedbackCategory;
const BLOCK_OPTIONS = [
"block-vav",
"block-zayin",
"block-chet",
"block-tet",
"block-yod",
"block-yod-alef",
];
function AddFeedbackForm() {
const create = useCreateFeedback();
const [caseNumber, setCaseNumber] = useState("");
const [blockId, setBlockId] = useState("block-yod");
const [category, setCategory] = useState<FeedbackCategory>("missing_content");
const [text, setText] = useState("");
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!text.trim()) {
toast.error("יש להזין את תוכן ההערה");
return;
}
create.mutate(
{
case_number: caseNumber.trim() || undefined,
block_id: blockId,
category,
feedback_text: text.trim(),
},
{
onSuccess: () => {
toast.success("ההערה נשמרה");
setCaseNumber("");
setText("");
},
onError: (err) =>
toast.error(err instanceof Error ? err.message : "שגיאה בשמירה"),
},
);
};
const fieldCls =
"w-full text-[0.84rem] text-ink bg-parchment border border-rule rounded-md px-3 py-2";
const labelCls = "block text-[0.76rem] text-ink-muted font-medium mt-2.5 mb-1";
return (
<Card className="bg-surface border-rule shadow-sm lg:sticky lg:top-6">
<CardContent className="px-5 py-4">
<h3 className="text-navy text-base font-semibold mb-3.5">הוסף הערה</h3>
<form onSubmit={onSubmit}>
<label className={labelCls} htmlFor="fb-case">מספר ערר</label>
<input
id="fb-case"
type="text"
value={caseNumber}
onChange={(e) => setCaseNumber(e.target.value)}
placeholder="לדוגמה: 1126-08-25"
className={fieldCls}
dir="rtl"
/>
<label className={labelCls} htmlFor="fb-block">בלוק</label>
<select
id="fb-block"
value={blockId}
onChange={(e) => setBlockId(e.target.value)}
className={`${fieldCls} cursor-pointer`}
>
{BLOCK_OPTIONS.map((b) => (
<option key={b} value={b}>
{BLOCK_LABELS[b]}
</option>
))}
</select>
<label className={labelCls} htmlFor="fb-cat">קטגוריה</label>
<select
id="fb-cat"
value={category}
onChange={(e) => setCategory(e.target.value as FeedbackCategory)}
className={`${fieldCls} cursor-pointer`}
>
{(Object.keys(CATEGORY_LABELS) as FeedbackCategory[]).map((c) => (
<option key={c} value={c}>
{CATEGORY_LABELS[c]}
</option>
))}
</select>
<label className={labelCls} htmlFor="fb-text">תוכן ההערה</label>
<textarea
id="fb-text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="מה צריך לתקן ולמה…"
className={`${fieldCls} min-h-[90px] resize-y leading-relaxed`}
dir="rtl"
/>
<Button
type="submit"
disabled={create.isPending}
className="w-full mt-4 bg-gold text-white hover:bg-gold-deep border-transparent"
>
{create.isPending ? "שומר…" : "שמור הערה"}
</Button>
</form>
</CardContent>
</Card>
);
}
export default function FeedbackPage() {
const [unresolvedOnly, setUnresolvedOnly] = useState(true);
const [category, setCategory] = useState<CatFilter>("all");
@@ -159,14 +284,19 @@ export default function FeedbackPage() {
<div className="text-[0.75rem] uppercase tracking-[0.12em] text-gold-deep">
שערים אנושיים · יו״ר הוועדה
</div>
<h1 className="text-navy mb-0">הערות יו״ר</h1>
<div className="flex items-center gap-3 flex-wrap">
<h1 className="text-navy mb-0">הערות יו״ר</h1>
<span className="inline-block rounded-full bg-gold-wash border border-rule text-ink-muted text-[0.76rem] px-3 py-0.5">
נגיש גם מתוך מרכז האישורים
</span>
</div>
<p className="text-ink-muted text-sm mt-1 max-w-2xl leading-relaxed">
כל ההערות שנרשמו על טיוטות מכל התיקים. סמן כל הערה כיושמה
לאחר שהלקח הוטמע.
</p>
</div>
<div className="text-end">
<div className="text-3xl font-semibold text-navy leading-none">
<div className="text-3xl font-semibold text-navy leading-none tabular-nums">
{unresolvedCount}
</div>
<div className="text-[0.72rem] uppercase tracking-[0.08em] text-ink-muted mt-1">
@@ -178,15 +308,35 @@ export default function FeedbackPage() {
<div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />
{/* Filters */}
{/* category filter chips (mockup 06 .chips) + resolved-state toggle */}
<div className="flex items-center gap-3 flex-wrap">
<div className="flex items-center gap-1">
<div className="flex items-center gap-2 flex-wrap">
{categories.map((c) => {
const on = category === c.key;
return (
<button
key={c.key}
type="button"
onClick={() => setCategory(c.key)}
className={`text-[0.8rem] font-medium px-3.5 py-1.5 rounded-full border transition-colors ${
on
? "bg-navy text-white border-navy"
: "bg-surface text-ink-soft border-rule hover:bg-rule-soft"
}`}
>
{c.label}
</button>
);
})}
</div>
<div className="flex items-center gap-1 ms-auto">
<button
type="button"
onClick={() => setUnresolvedOnly(true)}
className={`text-[0.78rem] px-3 py-1.5 rounded border transition-colors ${
unresolvedOnly
? "bg-navy text-parchment border-navy"
? "bg-navy text-white border-navy"
: "bg-surface text-ink-muted border-rule hover:bg-rule-soft"
}`}
>
@@ -197,60 +347,50 @@ export default function FeedbackPage() {
onClick={() => setUnresolvedOnly(false)}
className={`text-[0.78rem] px-3 py-1.5 rounded border transition-colors ${
!unresolvedOnly
? "bg-navy text-parchment border-navy"
? "bg-navy text-white border-navy"
: "bg-surface text-ink-muted border-rule hover:bg-rule-soft"
}`}
>
הכל
</button>
</div>
<div className="flex items-center gap-1 flex-wrap ms-auto">
{categories.map((c) => (
<button
key={c.key}
type="button"
onClick={() => setCategory(c.key)}
className={`text-[0.74rem] px-2.5 py-1 rounded border transition-colors ${
category === c.key
? "bg-gold-wash text-gold-deep border-gold/40"
: "bg-surface text-ink-muted border-rule hover:bg-rule-soft"
}`}
>
{c.label}
</button>
))}
</div>
</div>
{error ? (
<Card className="bg-surface border-rule">
<CardContent className="px-6 py-5 text-ink-muted text-sm">
שגיאה בטעינת ההערות. נסה לרענן.
</CardContent>
</Card>
) : isPending ? (
<div className="space-y-3">
{[0, 1, 2].map((i) => (
<Card key={i} className="bg-surface border-rule shadow-sm">
<CardContent className="px-5 py-4 h-28 animate-pulse" />
{/* two-column body — feedback list + sticky add-form rail (mockup 06 .wrap grid) */}
<div className="grid gap-6 lg:grid-cols-[1fr_340px] items-start">
<div>
{error ? (
<Card className="bg-surface border-rule">
<CardContent className="px-6 py-5 text-ink-muted text-sm">
שגיאה בטעינת ההערות. נסה לרענן.
</CardContent>
</Card>
))}
</div>
) : items.length === 0 ? (
<div className="text-center text-ink-muted py-16">
<p className="text-lg">אין הערות בקטגוריה זו.</p>
{unresolvedOnly && (
<p className="text-sm mt-2">כל ההערות יושמו </p>
) : isPending ? (
<div className="space-y-3.5">
{[0, 1, 2].map((i) => (
<Card key={i} className="bg-surface border-rule shadow-sm">
<CardContent className="px-[18px] py-4 h-28 animate-pulse" />
</Card>
))}
</div>
) : items.length === 0 ? (
<div className="text-center text-ink-muted py-16">
<p className="text-lg">אין הערות בקטגוריה זו.</p>
{unresolvedOnly && (
<p className="text-sm mt-2">כל ההערות יושמו </p>
)}
</div>
) : (
<div className="space-y-3.5">
{items.map((fb) => (
<FeedbackCard key={fb.id} fb={fb} />
))}
</div>
)}
</div>
) : (
<div className="space-y-3">
{items.map((fb) => (
<FeedbackCard key={fb.id} fb={fb} />
))}
</div>
)}
<AddFeedbackForm />
</div>
</section>
</AppShell>
);