Files
legal-ai/web-ui/src/components/cases/status-donut.tsx
Chaim ba542f9c21
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
refactor(cases): צמצום תפריט-סטטוס 17→10 + מקור-אמת יחיד (UI-B1/G2)
תפריט הסטטוס-הידני הכיל 17 סטטוסים שמתוכם ~9 דקורציה טהורה — שלבי-ביניים
שאף קוד בפייפליין לא קבע ושום לוגיקה לא הסתעפה לפיהם, עם רשימות כפולות
לא-עקביות ב-6+ קבצים (UI-B1) ו-exported כסטטוס-רפאים (באג agent-audit).

הליבה (10): new, processing, documents_ready, outcome_set, direction_approved,
qa_review, drafted, exported, reviewed, final.

- SSoT חדש web-ui/src/lib/api/case-status.ts (רשימה/שלבים/תוויות/statusLabel);
  כל הצרכנים (badge/changer/timeline/guide/donut/kpi/compose) מייבאים משם.
- statusLabel() מבטיח תווית עברית תמיד — גם לערך-מורשת (נפילה עברית, לא סלאג).
- בקאנד: STATUS_ORDER 10, models.CaseStatus מיושר, set_outcome קובע
  outcome_set/direction_approved (במקום in_progress) כמו endpoint ה-web.
- exported מוקשח אחרי export-DOCX מוצלח (forward-only); widget "נכשל ב-QA"
  עודכן ל-qa_review (הסטטוס שנקבע בפועל בכשל-QA).
- scripts/backfill_case_status_trim.py: מיפוי שורות-מורשת לסטטוס-הליבה הקודם.

Invariants: UI-B1 (מקור-אמת יחיד)  · G2 (אין מסלול מקביל)  · GAP-42 (חלקי).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:47:13 +00:00

88 lines
3.2 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 type { Case } from "@/lib/api/cases";
import { phaseOf, type PhaseKey } from "@/lib/api/case-status";
import { STATUS_LABELS } from "@/components/cases/status-badge";
/*
* Conic-gradient donut — ported from legal-ai/web/static/index.html renderHero().
* Kept deliberately dependency-free (no D3/recharts) — a single background-image.
* Five status groups (= the SSoT phases) map onto navy/gold/info/warn/success.
*/
type GroupKey = PhaseKey;
const GROUP_META: Record<GroupKey, { label: string; color: string }> = {
intake: { label: "חדש / בעיבוד", color: "var(--color-ink-muted)" },
prep: { label: "הכנה", color: "var(--color-info)" },
thinking: { label: "ניתוח וכיוון", color: "var(--color-gold)" },
writing: { label: "בכתיבה", color: "var(--color-warn)" },
done: { label: "מוכן", color: "var(--color-success)" },
};
export function StatusDonut({ cases }: { cases?: Case[] }) {
const counts: Record<GroupKey, number> = {
intake: 0, prep: 0, thinking: 0, writing: 0, done: 0,
};
(cases ?? []).forEach((c) => {
const g = phaseOf(c.status);
if (g) counts[g] += 1;
});
const total = Object.values(counts).reduce((a, b) => a + b, 0);
const segments: { key: GroupKey; start: number; end: number }[] = [];
let pct = 0;
(Object.keys(counts) as GroupKey[]).forEach((k) => {
if (counts[k] === 0) return;
const start = total === 0 ? 0 : (pct / total) * 360;
pct += counts[k];
const end = total === 0 ? 360 : (pct / total) * 360;
segments.push({ key: k, start, end });
});
const background =
total === 0
? "conic-gradient(var(--color-rule-soft) 0deg 360deg)"
: `conic-gradient(${segments
.map((s) => `${GROUP_META[s.key].color} ${s.start}deg ${s.end}deg`)
.join(", ")})`;
return (
<div className="flex flex-col items-center gap-5">
<div
className="relative w-[150px] h-[150px] rounded-full shadow-sm"
style={{ background }}
aria-label="פיזור תיקים לפי סטטוס"
>
<div className="absolute inset-[20px] bg-surface rounded-full flex flex-col items-center justify-center">
<span className="font-display text-3xl font-black text-navy leading-none tabular-nums">
{total}
</span>
<span className="text-[0.7rem] text-ink-muted mt-1 tracking-wide">תיקים</span>
</div>
</div>
<ul className="grid grid-cols-1 gap-1.5 text-sm w-full">
{(Object.keys(GROUP_META) as GroupKey[]).map((k) => (
<li
key={k}
className="grid grid-cols-[auto_1fr_auto] items-center gap-2 py-1 px-2 rounded-md hover:bg-rule-soft/40 transition-colors"
>
<span
className="inline-block w-2.5 h-2.5 rounded-full shrink-0"
style={{ background: GROUP_META[k].color }}
/>
<span className="text-ink-soft truncate">{GROUP_META[k].label}</span>
<span className="text-ink font-semibold tabular-nums">
{counts[k]}
</span>
</li>
))}
</ul>
</div>
);
}
/* Exported for the legend tests / docs if ever needed */
export { STATUS_LABELS };