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

@@ -3,7 +3,10 @@
import { Card, CardContent } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { SubjectDonut } from "@/components/training/subject-donut";
import { useStyleReport } from "@/lib/api/training";
import { useStyleReport, useCuratorStats } from "@/lib/api/training";
// Mockup 12 anatomy palette — info · gold · gold-deep · success, cycling.
const ANATOMY_COLORS = ["#4e6a8c", "#a97d3a", "#8b6428", "#4a7c59"];
function KPICard({
label,
@@ -16,15 +19,13 @@ function KPICard({
}) {
return (
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-5 py-4 flex flex-col gap-0.5">
<span className="text-[0.72rem] uppercase tracking-[0.08em] text-ink-muted">
{label}
</span>
<span className="font-display text-[2rem] font-black leading-none text-navy">
<CardContent className="px-[18px] py-4 flex flex-col">
<span className="font-display text-[1.85rem] font-bold leading-[1.1] text-navy tabular-nums">
{value}
</span>
<span className="text-[0.81rem] text-ink-soft mt-1">{label}</span>
{caption && (
<span className="text-[0.78rem] text-ink-muted mt-1">{caption}</span>
<span className="text-[0.72rem] text-ink-muted mt-0.5">{caption}</span>
)}
</CardContent>
</Card>
@@ -33,6 +34,7 @@ function KPICard({
export function StyleReportPanel() {
const { data, isPending, error } = useStyleReport();
const curator = useCuratorStats();
if (error) {
return (
@@ -63,14 +65,13 @@ export function StyleReportPanel() {
return (
<div className="space-y-6">
{/* Headline */}
<Card className="bg-gold-wash border-gold/40 shadow-sm">
<CardContent className="px-6 py-4">
<p className="font-display text-gold-deep text-lg font-semibold leading-snug">
{c.headline}
</p>
</CardContent>
</Card>
{/* Headline banner — gold-wash, ★ aligned to start (mockup 12) */}
<div className="flex items-start gap-3 rounded-lg border border-gold bg-gold-wash px-5 py-4 shadow-sm">
<span className="text-gold-deep text-xl leading-tight shrink-0"></span>
<p className="text-ink-soft text-[0.95rem] leading-relaxed m-0">
{c.headline}
</p>
</div>
{/* KPIs */}
<div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
@@ -121,23 +122,22 @@ export function StyleReportPanel() {
{data.anatomy.sections.length === 0 ? (
<p className="text-ink-muted text-sm">אין נתונים על מבנה</p>
) : (
<ul className="space-y-2.5">
{data.anatomy.sections.map((s) => {
<ul className="space-y-3.5">
{data.anatomy.sections.map((s, i) => {
const pct = Math.round(s.pct * 100);
const fill = ANATOMY_COLORS[i % ANATOMY_COLORS.length];
return (
<li key={s.type} className="space-y-1">
<div className="flex items-center justify-between text-[0.78rem]">
<span className="text-ink-soft font-medium">
{s.label}
</span>
<span className="text-ink-muted tabular-nums">
<li key={s.type} className="space-y-1.5">
<div className="flex items-center justify-between text-[0.81rem]">
<span className="text-ink-soft">{s.label}</span>
<span className="text-navy font-semibold tabular-nums">
{pct}% · {s.avg_chars.toLocaleString()} תווים
</span>
</div>
<div className="h-2 rounded bg-rule-soft overflow-hidden">
<div className="h-2.5 rounded-full bg-rule-soft overflow-hidden">
<div
className="h-full bg-gradient-to-l from-gold to-gold-deep"
style={{ width: `${pct}%` }}
className="h-full rounded-full"
style={{ width: `${pct}%`, backgroundColor: fill }}
/>
</div>
</li>
@@ -149,50 +149,92 @@ export function StyleReportPanel() {
</Card>
</div>
{/* Signature phrases */}
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5">
<h3 className="text-navy text-lg mb-1">ביטויי חתימה</h3>
{data.signature_phrases.headline && (
<p className="text-[0.78rem] text-gold-deep mb-4">
{data.signature_phrases.headline}
</p>
)}
{data.signature_phrases.items.length === 0 ? (
<p className="text-ink-muted text-sm">אין ביטויים שחולצו עדיין</p>
) : (
<ol className="space-y-2">
{data.signature_phrases.items.slice(0, 12).map((p, i) => (
<li
key={`${p.type}-${i}`}
className="flex items-start gap-3 rounded border border-rule bg-parchment/40 px-3 py-2"
>
<span className="text-[0.7rem] text-ink-muted tabular-nums shrink-0 mt-0.5">
#{i + 1}
</span>
<div className="flex-1 min-w-0">
<p className="text-ink leading-relaxed text-sm">{p.text}</p>
{p.context && (
<p className="text-[0.7rem] text-ink-muted mt-0.5">
{p.context}
</p>
)}
</div>
<span
className="
shrink-0 text-[0.72rem] rounded-full
bg-gold-wash text-gold-deep border border-gold/40
px-2 py-0.5 tabular-nums
"
{/* Signature phrases + curator stat — two columns (mockup 12) */}
<div className="grid gap-6 lg:grid-cols-2 items-start">
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5">
<h3 className="text-navy text-lg mb-1">ביטויי חתימה</h3>
{data.signature_phrases.headline && (
<p className="text-[0.78rem] text-gold-deep mb-3">
{data.signature_phrases.headline}
</p>
)}
{data.signature_phrases.items.length === 0 ? (
<p className="text-ink-muted text-sm">אין ביטויים שחולצו עדיין</p>
) : (
<ol>
{data.signature_phrases.items.slice(0, 12).map((p, i) => (
<li
key={`${p.type}-${i}`}
className="flex items-baseline gap-2.5 py-2.5 border-b border-rule-soft last:border-b-0"
>
×{p.frequency}
</span>
</li>
))}
</ol>
)}
</CardContent>
</Card>
<span className="w-5 shrink-0 text-gold-deep font-bold tabular-nums text-sm">
{i + 1}
</span>
<div className="flex-1 min-w-0">
<p className="text-ink-soft leading-relaxed text-[0.85rem] m-0">
{p.text}
</p>
{p.context && (
<p className="text-[0.7rem] text-ink-muted mt-0.5 m-0">
{p.context}
</p>
)}
</div>
<span className="shrink-0 text-[0.72rem] font-semibold rounded-full bg-gold-wash text-gold-deep border border-rule px-2.5 py-0.5 tabular-nums whitespace-nowrap">
×{p.frequency}
</span>
</li>
))}
</ol>
)}
</CardContent>
</Card>
{/* Curator — surfaced style findings (INV-LRN1 writer gate) */}
<Card className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5 flex flex-col gap-4">
<h3 className="text-navy text-lg m-0">אוצֵר ממצאי-סגנון</h3>
<div className="flex items-center gap-4 rounded-lg border border-success bg-success-bg px-[18px] py-3.5">
<span className="text-success font-bold text-[1.75rem] leading-none tabular-nums">
{curator.data ? curator.data.findings_approved : "—"}
</span>
<div className="min-w-0">
<b className="text-navy text-sm font-semibold block">
ממצאים מאושרים (זורמים לכותב)
</b>
<span className="text-ink-muted text-[0.78rem]">
אושרו ע״י היו״ר · review_status=approved
</span>
</div>
</div>
<div className="flex gap-3.5">
<div className="flex-1 rounded-lg border border-rule bg-warn-bg px-3.5 py-3">
<div className="text-warn font-bold text-[1.35rem] tabular-nums leading-none">
{curator.data
? Math.max(
0,
curator.data.total_findings -
curator.data.findings_approved,
)
: "—"}
</div>
<div className="text-[0.78rem] text-ink-soft mt-1">לא-מאושרים</div>
</div>
<div className="flex-1 rounded-lg border border-rule bg-rule-soft px-3.5 py-3">
<div className="text-ink-muted font-bold text-[1.35rem] tabular-nums leading-none">
{curator.data ? curator.data.total_findings : "—"}
</div>
<div className="text-[0.78rem] text-ink-soft mt-1">סך ממצאים</div>
</div>
</div>
<p className="text-[0.72rem] text-ink-muted leading-relaxed m-0">
רק ממצא מאושר זורם לכותב (INV-LRN1). ממתינים ונדחים אינם משפיעים על
הטיוטות.
</p>
</CardContent>
</Card>
</div>
</div>
);
}