Files
legal-ai/web-ui/src/components/training/subject-donut.tsx
Chaim fb1f73fa25 Phase 5: secondary screens (diagnostics, skills, training)
Port the remaining read views from the vanilla UI to Next.js:

- /diagnostics — system health snapshot (DB connected, table counts,
  active tasks, failed and stuck documents). Uses the existing
  /api/system/diagnostics payload with a 10s refetchInterval so the
  page self-updates while the user watches.
- /skills — Paperclip skill inventory with sync status (DB-only,
  disk-only, synced, not-synced) as a card grid driven by
  /api/admin/skills.
- /training — Dafna's style portrait as three tabs on one page:
  * Report: corpus KPIs + CSS conic-gradient subject donut
    (SubjectDonut ported from index.html renderHero) + horizontal
    anatomy bars + top-12 signature phrases.
  * Corpus: TanStack Table of style_corpus rows with an inline
    delete mutation (useDeleteCorpusEntry invalidates both the
    corpus list and the style report so KPIs update).
  * Compare: two-decision selector backed by /api/training/compare,
    side-by-side panels plus shared / only-A / only-B pattern
    lists.

New API modules: lib/api/system.ts, lib/api/skills.ts,
lib/api/training.ts. All three use TanStack Query with staleTime
profiles tuned per endpoint (10s for diagnostics, 30s for skills,
60s for training reports).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 17:33:33 +00:00

73 lines
2.1 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";
/*
* Corpus subject-distribution donut.
*
* Pure CSS conic-gradient — same recipe as the cases StatusDonut, but
* uses a palette-of-gold instead of a status-tone palette. Ported from
* legal-ai/web/static/index.html `renderHero`.
*/
const DONUT_COLORS = [
"var(--color-navy)",
"var(--color-gold)",
"var(--color-info)",
"var(--color-warn)",
"var(--color-success)",
"var(--color-ink-muted)",
"var(--color-gold-deep)",
];
export function SubjectDonut({
segments,
total,
}: {
segments: Array<{ label: string; count: number }>;
total: number;
}) {
let pct = 0;
const parts = segments.map((s, i) => {
const start = total === 0 ? 0 : (pct / total) * 360;
pct += s.count;
const end = total === 0 ? 360 : (pct / total) * 360;
return { ...s, start, end, color: DONUT_COLORS[i % DONUT_COLORS.length] };
});
const background =
total === 0
? "conic-gradient(var(--color-rule-soft) 0deg 360deg)"
: `conic-gradient(${parts
.map((p) => `${p.color} ${p.start}deg ${p.end}deg`)
.join(", ")})`;
return (
<div className="flex items-center gap-6">
<div
className="relative w-[140px] h-[140px] rounded-full shadow-sm shrink-0"
style={{ background }}
aria-label="פיזור נושאים בקורפוס"
>
<div className="absolute inset-[18px] bg-surface rounded-full flex flex-col items-center justify-center">
<span className="font-display text-2xl font-black text-navy leading-none">
{total}
</span>
<span className="text-[0.7rem] text-ink-muted mt-1">החלטות</span>
</div>
</div>
<ul className="flex flex-col gap-1.5 text-sm min-w-0">
{parts.map((p) => (
<li key={p.label} className="flex items-center gap-2">
<span
className="inline-block w-2.5 h-2.5 rounded-full shrink-0"
style={{ background: p.color }}
/>
<span className="text-ink-soft truncate">{p.label}</span>
<span className="text-ink-muted tabular-nums ms-1">{p.count}</span>
</li>
))}
</ul>
</div>
);
}