תפריט הסטטוס-הידני הכיל 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>
88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
"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 };
|