Add status icons, descriptions, status guide, manual status changer, and merge action buttons into overview tab
- StatusBadge: added icons (lucide-react) and Hebrew descriptions for all 13 statuses - WorkflowTimeline: added phase icons and current-status description display - StatusGuide: new collapsible component showing all statuses grouped by phase with explanations - StatusChanger: new dropdown for manual status override on the case detail sidebar - Case detail page: merged action buttons into overview tab, removed separate actions tab Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
FilePlus2, Upload, Loader2, FileCheck, Target,
|
||||
Lightbulb, Compass, PenLine, SearchCheck, FileText,
|
||||
FileOutput, CheckCircle2, Award,
|
||||
} from "lucide-react";
|
||||
import type { CaseStatus } from "@/lib/api/cases";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
const STATUS_LABELS: Record<CaseStatus, string> = {
|
||||
new: "חדש",
|
||||
@@ -17,6 +23,38 @@ const STATUS_LABELS: Record<CaseStatus, string> = {
|
||||
final: "סופי",
|
||||
};
|
||||
|
||||
const STATUS_ICONS: Record<CaseStatus, LucideIcon> = {
|
||||
new: FilePlus2,
|
||||
uploading: Upload,
|
||||
processing: Loader2,
|
||||
documents_ready: FileCheck,
|
||||
outcome_set: Target,
|
||||
brainstorming: Lightbulb,
|
||||
direction_approved: Compass,
|
||||
drafting: PenLine,
|
||||
qa_review: SearchCheck,
|
||||
drafted: FileText,
|
||||
exported: FileOutput,
|
||||
reviewed: CheckCircle2,
|
||||
final: Award,
|
||||
};
|
||||
|
||||
const STATUS_DESCRIPTIONS: Record<CaseStatus, string> = {
|
||||
new: "התיק נוצר וממתין להעלאת מסמכים",
|
||||
uploading: "מסמכים בתהליך העלאה לשרת",
|
||||
processing: "המערכת מעבדת ומנתחת את המסמכים",
|
||||
documents_ready: "כל המסמכים עובדו ומוכנים לעבודה",
|
||||
outcome_set: "נקבעה תוצאה צפויה לערר",
|
||||
brainstorming: "ניתוח כיוונים אפשריים להחלטה",
|
||||
direction_approved: "כיוון ההחלטה אושר — ניתן להתחיל כתיבה",
|
||||
drafting: "טיוטת ההחלטה בתהליך כתיבה",
|
||||
qa_review: "הטיוטה בבדיקת איכות אוטומטית",
|
||||
drafted: "טיוטה ראשונה מוכנה לעיון",
|
||||
exported: "ההחלטה יוצאה לקובץ DOCX",
|
||||
reviewed: "ההחלטה נבדקה ע\"י היו\"ר",
|
||||
final: "החלטה סופית — מוכנה להגשה",
|
||||
};
|
||||
|
||||
/* Status color groups:
|
||||
* intake → new, uploading, processing (muted parchment)
|
||||
* prep → documents_ready, outcome_set (info blue)
|
||||
@@ -40,14 +78,16 @@ const STATUS_TONE: Record<CaseStatus, string> = {
|
||||
};
|
||||
|
||||
export function StatusBadge({ status }: { status: CaseStatus }) {
|
||||
const Icon = STATUS_ICONS[status];
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`rounded-full px-2.5 py-0.5 text-[0.72rem] font-medium ${STATUS_TONE[status] ?? ""}`}
|
||||
className={`rounded-full px-2.5 py-0.5 text-[0.72rem] font-medium inline-flex items-center gap-1 ${STATUS_TONE[status] ?? ""}`}
|
||||
>
|
||||
{Icon && <Icon className="w-3 h-3 shrink-0" />}
|
||||
{STATUS_LABELS[status] ?? status}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
export { STATUS_LABELS };
|
||||
export { STATUS_LABELS, STATUS_ICONS, STATUS_DESCRIPTIONS, STATUS_TONE };
|
||||
|
||||
84
web-ui/src/components/cases/status-changer.tsx
Normal file
84
web-ui/src/components/cases/status-changer.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { STATUS_LABELS, STATUS_ICONS } from "@/components/cases/status-badge";
|
||||
import { useUpdateCase, type CaseStatus } from "@/lib/api/cases";
|
||||
|
||||
/*
|
||||
* Dropdown for manually overriding the case status — skip a step
|
||||
* or revert to an earlier stage. Calls PUT /api/cases/:caseNumber
|
||||
* with { status: newValue }.
|
||||
*/
|
||||
|
||||
const ALL_STATUSES: CaseStatus[] = [
|
||||
"new", "uploading", "processing",
|
||||
"documents_ready", "outcome_set",
|
||||
"brainstorming", "direction_approved",
|
||||
"drafting", "qa_review", "drafted",
|
||||
"exported", "reviewed", "final",
|
||||
];
|
||||
|
||||
export function StatusChanger({
|
||||
caseNumber,
|
||||
currentStatus,
|
||||
}: {
|
||||
caseNumber: string;
|
||||
currentStatus?: CaseStatus;
|
||||
}) {
|
||||
const [selected, setSelected] = useState<CaseStatus | "">(currentStatus ?? "");
|
||||
const mutate = useUpdateCase(caseNumber);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!selected || selected === currentStatus) return;
|
||||
try {
|
||||
await mutate.mutateAsync({ status: selected });
|
||||
toast.success(`הסטטוס עודכן ל${STATUS_LABELS[selected]}`);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : "שגיאה בעדכון הסטטוס");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-4 border-t border-rule pt-3 space-y-2">
|
||||
<label className="text-[0.72rem] text-ink-muted block">שינוי סטטוס ידני</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
value={selected || "__current__"}
|
||||
onValueChange={(v) => setSelected(v === "__current__" ? "" : v as CaseStatus)}
|
||||
dir="rtl"
|
||||
>
|
||||
<SelectTrigger className="text-[0.75rem] h-8">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{ALL_STATUSES.map((s) => {
|
||||
const Icon = STATUS_ICONS[s];
|
||||
return (
|
||||
<SelectItem key={s} value={s} className="text-[0.75rem]">
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<Icon className="w-3 h-3 shrink-0" />
|
||||
{STATUS_LABELS[s]}
|
||||
</span>
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-8 text-[0.72rem] px-3 shrink-0"
|
||||
disabled={!selected || selected === currentStatus || mutate.isPending}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{mutate.isPending ? "שומר…" : "עדכן"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
72
web-ui/src/components/cases/status-guide.tsx
Normal file
72
web-ui/src/components/cases/status-guide.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
import type { CaseStatus } from "@/lib/api/cases";
|
||||
import { STATUS_LABELS, STATUS_ICONS, STATUS_DESCRIPTIONS, STATUS_TONE } from "@/components/cases/status-badge";
|
||||
|
||||
/*
|
||||
* Collapsible guide showing all 13 statuses grouped by phase,
|
||||
* each with its icon, Hebrew label, color badge, and description.
|
||||
* Intended to sit below the WorkflowTimeline in the case sidebar.
|
||||
*/
|
||||
|
||||
type PhaseGroup = {
|
||||
label: string;
|
||||
statuses: CaseStatus[];
|
||||
};
|
||||
|
||||
const PHASE_GROUPS: PhaseGroup[] = [
|
||||
{ label: "קליטה ועיבוד", statuses: ["new", "uploading", "processing"] },
|
||||
{ label: "הכנת תיק", statuses: ["documents_ready", "outcome_set"] },
|
||||
{ label: "ניתוח וכיוון", statuses: ["brainstorming", "direction_approved"] },
|
||||
{ label: "כתיבת טיוטה", statuses: ["drafting", "qa_review", "drafted"] },
|
||||
{ label: "סגירה", statuses: ["exported", "reviewed", "final"] },
|
||||
];
|
||||
|
||||
export function StatusGuide() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="mt-4 border-t border-rule pt-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex items-center gap-1.5 text-[0.72rem] text-ink-muted hover:text-navy transition-colors w-full"
|
||||
>
|
||||
{open ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
||||
מפת סטטוסים
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="mt-3 space-y-3">
|
||||
{PHASE_GROUPS.map((group) => (
|
||||
<div key={group.label}>
|
||||
<h4 className="text-[0.7rem] font-semibold text-navy mb-1.5">
|
||||
{group.label}
|
||||
</h4>
|
||||
<ul className="space-y-1.5">
|
||||
{group.statuses.map((s) => {
|
||||
const Icon = STATUS_ICONS[s];
|
||||
return (
|
||||
<li key={s} className="flex items-start gap-2">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[0.65rem] font-medium shrink-0 ${STATUS_TONE[s]}`}
|
||||
>
|
||||
<Icon className="w-2.5 h-2.5" />
|
||||
{STATUS_LABELS[s]}
|
||||
</span>
|
||||
<span className="text-[0.65rem] text-ink-muted leading-snug">
|
||||
{STATUS_DESCRIPTIONS[s]}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import type { CaseStatus } from "@/lib/api/cases";
|
||||
import { STATUS_LABELS } from "@/components/cases/status-badge";
|
||||
import { STATUS_LABELS, STATUS_ICONS, STATUS_DESCRIPTIONS } from "@/components/cases/status-badge";
|
||||
import {
|
||||
FolderInput, ClipboardList, Brain, PenLine, CheckCircle2,
|
||||
} from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
/*
|
||||
* Vertical RTL workflow timeline showing the 13-status case pipeline.
|
||||
@@ -13,15 +17,16 @@ import { STATUS_LABELS } from "@/components/cases/status-badge";
|
||||
type Phase = {
|
||||
key: string;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
statuses: CaseStatus[];
|
||||
};
|
||||
|
||||
const PHASES: Phase[] = [
|
||||
{ key: "intake", label: "קליטה ועיבוד", statuses: ["new", "uploading", "processing"] },
|
||||
{ key: "prep", label: "הכנת תיק", statuses: ["documents_ready", "outcome_set"] },
|
||||
{ key: "thinking", label: "ניתוח וכיוון", statuses: ["brainstorming", "direction_approved"] },
|
||||
{ key: "writing", label: "כתיבת טיוטה", statuses: ["drafting", "qa_review", "drafted"] },
|
||||
{ key: "done", label: "סגירה", statuses: ["exported", "reviewed", "final"] },
|
||||
{ key: "intake", label: "קליטה ועיבוד", icon: FolderInput, statuses: ["new", "uploading", "processing"] },
|
||||
{ key: "prep", label: "הכנת תיק", icon: ClipboardList, statuses: ["documents_ready", "outcome_set"] },
|
||||
{ key: "thinking", label: "ניתוח וכיוון", icon: Brain, statuses: ["brainstorming", "direction_approved"] },
|
||||
{ key: "writing", label: "כתיבת טיוטה", icon: PenLine, statuses: ["drafting", "qa_review", "drafted"] },
|
||||
{ key: "done", label: "סגירה", icon: CheckCircle2, statuses: ["exported", "reviewed", "final"] },
|
||||
];
|
||||
|
||||
function phaseIndexOf(status?: CaseStatus): number {
|
||||
@@ -55,19 +60,36 @@ export function WorkflowTimeline({ status }: { status?: CaseStatus }) {
|
||||
: state === "current" ? "text-navy font-semibold"
|
||||
: "text-ink-muted";
|
||||
|
||||
const iconTone =
|
||||
state === "done" ? "text-success"
|
||||
: state === "current" ? "text-gold-deep"
|
||||
: "text-ink-muted/50";
|
||||
|
||||
const PhaseIcon = phase.icon;
|
||||
const StatusIcon = status ? STATUS_ICONS[status] : null;
|
||||
|
||||
return (
|
||||
<li key={phase.key} className="relative flex items-start gap-3 ps-7">
|
||||
<span
|
||||
className={`absolute right-[5px] top-1 inline-block w-3 h-3 rounded-full border-2 ${dotTone}`}
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className={`text-sm ${labelTone}`}>{phase.label}</span>
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className={`text-sm flex items-center gap-1.5 ${labelTone}`}>
|
||||
<PhaseIcon className={`w-3.5 h-3.5 shrink-0 ${iconTone}`} />
|
||||
{phase.label}
|
||||
</span>
|
||||
{state === "current" && status && (
|
||||
<span className="text-[0.72rem] text-gold-deep mt-0.5">
|
||||
<span className="text-[0.72rem] text-gold-deep flex items-center gap-1">
|
||||
{StatusIcon && <StatusIcon className="w-3 h-3 shrink-0" />}
|
||||
{STATUS_LABELS[status]}
|
||||
</span>
|
||||
)}
|
||||
{state === "current" && status && STATUS_DESCRIPTIONS[status] && (
|
||||
<span className="text-[0.65rem] text-ink-muted leading-snug mt-0.5">
|
||||
{STATUS_DESCRIPTIONS[status]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user