Files
legal-ai/web-ui/src/app/approvals/page.tsx
Chaim 54948eb8ab feat(nav): הסרת דף מדגם-זהב (goldset) מה-UI
הכיול החד-פעמי של ולידטורי חילוץ-ההלכות (#81.8) הסתיים — הוסר מה-UI:
- web-ui/src/app/goldset/page.tsx (הדף)
- web-ui/src/components/goldset/goldset-panel.tsx (הרכיב)
- web-ui/src/lib/api/goldset.ts (מודול ה-API)
- הקישור "מדגם-זהב" מתפריט "פסיקה" + השטחת התת-כותרת "ניתוח וכיול"
  (נותר רק "מפת הקורפוס" → רשימה שטוחה)
- ניקוי אזכורי gold-set מהערות approvals/page.tsx ו-chair.ts

ה-backend נשאר במכוון: טבלת halacha_goldset, ה-endpoints (/api/goldset*)
ופונקציות ה-DB משמשים את סקריפטי ה-eval/benchmark ומחזיקים נתוני-תיוג
אנושיים — אין מחיקת DB ואין שבירת סקריפטים. /api/chair/pending ממילא לא
כלל goldset, אז אין קישור שבור במרכז-האישורים.

Invariants: G2 (הסרת יכולת-UF מיותרת ללא יצירת מסלול מקביל).

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

175 lines
6.4 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 Link from "next/link";
import { AppShell } from "@/components/app-shell";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
usePendingApprovals,
type ApprovalCategory,
type ApprovalSeverity,
} from "@/lib/api/chair";
/**
* מרכז אישורים — דפנה (INV-G10).
*
* עמוד אחד שמרכז את כל השערים האנושיים הממתינים להכרעת היו"ר: אישור הלכות,
* פסיקה חסרה, הערות שטרם יושמו, ותיקים שנכשלו ב-QA. המטרה:
* שאף פריט הדורש את אישורך לא יישכח. הנתונים נשלפים חי מ-/api/chair/pending.
*/
const SEVERITY_BADGE: Record<ApprovalSeverity, string> = {
high: "bg-gold text-navy border-transparent",
medium: "bg-gold-wash text-gold-deep border-gold/40",
low: "bg-rule-soft text-ink-muted border-rule",
ok: "bg-emerald-50 text-emerald-800 border-emerald-300/60",
};
function formatDate(iso?: string | null): string {
if (!iso) return "";
try {
return new Date(iso).toLocaleDateString("he-IL", {
day: "numeric",
month: "long",
year: "numeric",
});
} catch {
return "";
}
}
function ApprovalCard({ cat }: { cat: ApprovalCategory }) {
const cleared = cat.count === 0;
return (
<Card className="bg-surface border-rule shadow-sm flex flex-col">
<CardContent className="px-6 py-5 flex flex-col gap-3 grow">
<div className="flex items-start justify-between gap-3">
<h2 className="text-navy text-lg mb-0 leading-snug">{cat.label}</h2>
<span
className={`inline-flex items-center justify-center min-w-[2.25rem] h-7 px-2 rounded-full border text-sm font-semibold ${SEVERITY_BADGE[cat.severity]}`}
aria-label={`${cat.count} פריטים ממתינים`}
>
{cat.count}
</span>
</div>
<p className="text-ink-muted text-[0.85rem] leading-relaxed mb-0">
{cat.description}
</p>
{cat.oldest_at && cat.count > 0 ? (
<p className="text-[0.78rem] text-gold-deep mb-0">
הישן ביותר ממתין מ־{formatDate(cat.oldest_at)}
</p>
) : null}
{cat.extra ? (
<p className="text-[0.78rem] text-ink-muted mb-0">
סך {cat.extra.total} שאילתות · {cat.extra.reviewed} אושרו על־ידך
</p>
) : null}
{cleared ? (
<p className="text-[0.85rem] text-emerald-700 mb-0">אין פריטים ממתינים </p>
) : cat.sample && cat.sample.length > 0 ? (
<ul className="space-y-1.5 mt-1">
{cat.sample.map((s, i) => {
const body = (
<>
<span className="line-clamp-2">{s.text || "—"}</span>
{s.source ? (
<span className="text-ink-muted text-[0.72rem]"> · {s.source}</span>
) : null}
</>
);
return (
<li key={i} className="text-[0.82rem] text-ink leading-snug border-s-2 border-rule ps-2.5">
{s.href ? (
<Link href={s.href} className="hover:text-gold-deep hover:underline block">
{body}
</Link>
) : (
body
)}
</li>
);
})}
</ul>
) : null}
<div className="mt-auto pt-2">
{cat.href ? (
<Button asChild variant="outline" size="sm" className="border-rule">
<Link href={cat.href}>לטיפול </Link>
</Button>
) : (
<span className="text-[0.75rem] text-ink-muted">סקירה ידנית (ראה דוח FU-5)</span>
)}
</div>
</CardContent>
</Card>
);
}
export default function ApprovalsPage() {
const { data, isPending, error } = usePendingApprovals();
return (
<AppShell>
<section className="space-y-6">
<header className="space-y-1.5">
<nav className="text-[0.78rem] text-ink-muted mb-1">
<Link href="/" className="hover:text-gold-deep">בית</Link>
<span aria-hidden> · </span>
<span className="text-navy">מרכז אישורים</span>
</nav>
<div className="flex items-end justify-between gap-4 flex-wrap">
<div className="space-y-1">
<div className="text-[0.75rem] uppercase tracking-[0.12em] text-gold-deep">
שערים אנושיים · יו״ר הוועדה
</div>
<h1 className="text-navy mb-0">מרכז אישורים</h1>
<p className="text-ink-muted text-sm mt-1 max-w-2xl leading-relaxed">
כל מה שממתין להכרעתך במקום אחד כדי שאף פריט לא יישכח. מתעדכן חי.
</p>
</div>
{typeof data?.total_pending === "number" ? (
<div className="text-end">
<div className="text-3xl font-semibold text-navy leading-none">
{data.total_pending}
</div>
<div className="text-[0.72rem] uppercase tracking-[0.08em] text-ink-muted mt-1">
פריטים ממתינים
</div>
</div>
) : null}
</div>
</header>
<div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />
{error ? (
<Card className="bg-surface border-rule">
<CardContent className="px-6 py-5 text-ink-muted text-sm">
שגיאה בטעינת מרכז האישורים. נסה לרענן.
</CardContent>
</Card>
) : isPending ? (
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
{[0, 1, 2, 3, 4].map((i) => (
<Card key={i} className="bg-surface border-rule shadow-sm">
<CardContent className="px-6 py-5 h-44 animate-pulse" />
</Card>
))}
</div>
) : (
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
{(data?.categories ?? []).map((cat) => (
<ApprovalCard key={cat.key} cat={cat} />
))}
</div>
)}
</section>
</AppShell>
);
}