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:
2026-04-13 15:19:28 +00:00
parent bf595975bf
commit 62a67e3f31
6 changed files with 300 additions and 27 deletions

View File

@@ -908,16 +908,71 @@
"priority": "high",
"subtasks": [],
"updatedAt": "2026-04-11T19:20:56.040Z"
},
{
"id": 92,
"title": "הסרת אפליקציית Flask הישנה מ-Coolify",
"description": "ארכיון והסרה של אפליקציית Flask הישנה מ-Coolify, וכיוון DNS כך ש-legal-ai.nautilus.marcusgroup.org יצביע על אפליקציית Next.js",
"details": "## פסאודו-קוד:\n```\n1. גיבוי הגדרות Flask מ-Coolify לפני מחיקה\n2. ב-Coolify dashboard:\n - מצא את הקונטיינר legal-ai-flask (או שם דומה)\n - עצור את הקונטיינר\n - צור snapshot או ארכיון של ההגדרות\n - מחק את הקונטיינר והסרוויס\n3. ב-DNS (Cloudflare/Coolify proxy):\n - שנה את legal-ai.nautilus.marcusgroup.org\n - הפנה ל-IP/service של legal-ai-next (Next.js app)\n4. ב-Next.js app (Coolify):\n - הוסף domain alias: legal-ai.nautilus.marcusgroup.org\n - עדכן SSL certificate\n```\n\n## קבצים מושפעים:\n- Coolify dashboard settings\n- DNS records (Cloudflare או ספק אחר)\n- Coolify proxy/Traefik configuration\n\n## הערות:\n- **אין שינויים בקוד** - רק הגדרות תשתית\n- ודא שה-Next.js app עובד עם שני הדומיינים במקביל לפני הסרת Flask\n- שמור לוגים מ-Flask לפני מחיקה למקרה של rollback",
"testStrategy": "## בדיקות:\n1. **לפני הסרה**: ודא ש-legal-ai-next.nautilus.marcusgroup.org עובד תקין\n2. **אחרי שינוי DNS**: \n - `curl -I https://legal-ai.nautilus.marcusgroup.org` - צריך להחזיר 200\n - בדוק SSL certificate תקין\n3. **בדיקת UI**: \n - פתח את legal-ai.nautilus.marcusgroup.org בדפדפן\n - ודא שזה אותו UI כמו legal-ai-next\n4. **בדיקת API**: \n - `curl https://legal-ai.nautilus.marcusgroup.org/api/cases`\n - ודא שמחזיר נתונים",
"priority": "high",
"dependencies": [],
"status": "pending",
"subtasks": []
},
{
"id": 93,
"title": "עדכון סטטוסים ב-WorkflowTimeline וב-status-badge",
"description": "עדכון רשימת הסטטוסים בממשק לפי ה-pipeline החדש: new → proofread → documents_ready → analyst_verified → research_complete → outcome_set → direction_approved → drafted → qa_passed → exported, כולל qa_failed ו-blocked",
"details": "## קבצים לעדכון:\n1. `web-ui/src/lib/api/cases.ts` - עדכון type CaseStatus\n2. `web-ui/src/components/cases/status-badge.tsx` - תוויות ועיצוב\n3. `web-ui/src/components/cases/workflow-timeline.tsx` - שלבי pipeline\n\n## פסאודו-קוד:\n\n### 1. cases.ts - עדכון הטיפוס:\n```typescript\nexport type CaseStatus =\n | \"new\"\n | \"proofread\"\n | \"documents_ready\"\n | \"analyst_verified\"\n | \"research_complete\"\n | \"outcome_set\"\n | \"direction_approved\"\n | \"drafted\"\n | \"qa_passed\"\n | \"exported\"\n | \"qa_failed\"\n | \"blocked\";\n```\n\n### 2. status-badge.tsx - תוויות עבריות וצבעים:\n```typescript\nconst STATUS_LABELS: Record<CaseStatus, string> = {\n new: \"חדש\",\n proofread: \"הוגה\",\n documents_ready: \"מסמכים מוכנים\",\n analyst_verified: \"אומת ע״י אנליסט\",\n research_complete: \"מחקר הושלם\",\n outcome_set: \"תוצאה נקבעה\",\n direction_approved: \"כיוון אושר\",\n drafted: \"טיוטה\",\n qa_passed: \"עבר QA\",\n exported: \"יוצא\",\n qa_failed: \"נכשל QA\",\n blocked: \"חסום\",\n};\n\nconst STATUS_TONE: Record<CaseStatus, string> = {\n new: \"bg-rule-soft text-ink-muted border-rule\",\n proofread: \"bg-info-bg text-info border-info/30\",\n documents_ready: \"bg-info-bg text-info border-info/40\",\n analyst_verified: \"bg-info-bg text-info border-info/50\",\n research_complete: \"bg-gold-wash text-gold-deep border-gold/40\",\n outcome_set: \"bg-gold-wash text-gold-deep border-gold/50\",\n direction_approved: \"bg-gold-wash text-gold-deep border-gold/60\",\n drafted: \"bg-warn-bg text-warn border-warn/40\",\n qa_passed: \"bg-success-bg text-success border-success/40\",\n exported: \"bg-success-bg text-success border-success/60\",\n qa_failed: \"bg-danger-bg text-danger border-danger/40\",\n blocked: \"bg-danger-bg text-danger border-danger/50\",\n};\n```\n\n### 3. workflow-timeline.tsx - קבוצות שלבים חדשות:\n```typescript\nconst PHASES: Phase[] = [\n { key: \"intake\", label: \"קליטה ועיבוד\", statuses: [\"new\", \"proofread\", \"documents_ready\"] },\n { key: \"analysis\", label: \"ניתוח\", statuses: [\"analyst_verified\", \"research_complete\"] },\n { key: \"direction\", label: \"קביעת כיוון\", statuses: [\"outcome_set\", \"direction_approved\"] },\n { key: \"writing\", label: \"כתיבה וביקורת\", statuses: [\"drafted\", \"qa_passed\"] },\n { key: \"done\", label: \"סגירה\", statuses: [\"exported\"] },\n];\n\n// טיפול בסטטוסי שגיאה (qa_failed, blocked) - הצגה מיוחדת\nif (status === \"qa_failed\" || status === \"blocked\") {\n // הצג באדום עם אייקון אזהרה\n}\n```",
"testStrategy": "## בדיקות:\n1. **Unit Tests** (אם קיימים):\n - ודא שכל הסטטוסים מופו נכון\n - בדוק שאין סטטוס חסר ב-STATUS_LABELS ו-STATUS_TONE\n\n2. **Visual Testing**:\n - צור/ערוך תיק ידנית ב-DB לכל סטטוס\n - ודא שהתווית מוצגת בעברית נכונה\n - ודא שהצבע מתאים (כחול לעיבוד, זהב לניתוח, ירוק להצלחה, אדום לשגיאה)\n\n3. **WorkflowTimeline**:\n - ודא שהשלב הנוכחי מודגש בצהוב\n - ודא ששלבים שהושלמו מסומנים בירוק\n - ודא שסטטוסי שגיאה (qa_failed, blocked) מוצגים עם אינדיקציה ויזואלית מיוחדת",
"priority": "high",
"dependencies": [],
"status": "pending",
"subtasks": []
},
{
"id": 94,
"title": "דף/קומפוננטה להצגת כל הסטטוסים עם הסברים",
"description": "יצירת דף /statuses או מודל עזרה שמסביר את כל הסטטוסים האפשריים - מה כל סטטוס אומר, איזה agent קובע אותו, ומה קורה אחר כך",
"details": "## אפשרויות מימוש:\n\n### אפשרות A: Popover tooltip בתוך WorkflowTimeline (מומלץ)\n```typescript\n// web-ui/src/components/cases/workflow-timeline.tsx\n// הוסף אייקון (?) ליד הכותרת שפותח popover\n\nconst STATUS_INFO: Record<CaseStatus, StatusInfo> = {\n new: {\n description: \"תיק נוצר, ממתין להעלאת מסמכים\",\n agent: \"משתמש\",\n nextStep: \"העלאת מסמכים → proofread\"\n },\n proofread: {\n description: \"מסמכים הועלו, עוברים הגהה אוטומטית\",\n agent: \"Proofread Agent\",\n nextStep: \"הגהה הושלמה → documents_ready\"\n },\n documents_ready: {\n description: \"מסמכים מוכנים לניתוח\",\n agent: \"Document Processor\",\n nextStep: \"בדיקת אנליסט → analyst_verified\"\n },\n analyst_verified: {\n description: \"אנליסט אימת את חילוץ הטענות\",\n agent: \"Analyst Agent\",\n nextStep: \"מחקר → research_complete\"\n },\n research_complete: {\n description: \"מחקר משפטי הושלם, פסיקה זוהתה\",\n agent: \"Research Agent\",\n nextStep: \"קביעת תוצאה → outcome_set\"\n },\n outcome_set: {\n description: \"דפנה קבעה את התוצאה (דחייה/קבלה)\",\n agent: \"משתמש (דפנה)\",\n nextStep: \"אישור כיוון → direction_approved\"\n },\n direction_approved: {\n description: \"כיוון ההחלטה אושר, מוכן לכתיבה\",\n agent: \"משתמש\",\n nextStep: \"כתיבה → drafted\"\n },\n drafted: {\n description: \"טיוטת החלטה נכתבה\",\n agent: \"Writing Agent\",\n nextStep: \"בדיקת QA → qa_passed\"\n },\n qa_passed: {\n description: \"טיוטה עברה בדיקת איכות\",\n agent: \"QA Agent\",\n nextStep: \"ייצוא → exported\"\n },\n exported: {\n description: \"ההחלטה יוצאה כ-DOCX\",\n agent: \"Export Service\",\n nextStep: \"הושלם\"\n },\n qa_failed: {\n description: \"טיוטה נכשלה בבדיקת QA\",\n agent: \"QA Agent\",\n nextStep: \"חזרה לכתיבה → drafted\"\n },\n blocked: {\n description: \"תיק חסום - דורש התערבות ידנית\",\n agent: \"מערכת\",\n nextStep: \"טיפול ידני\"\n },\n};\n```\n\n### אפשרות B: דף /statuses נפרד\n```typescript\n// web-ui/src/app/statuses/page.tsx\n// דף עצמאי עם טבלה של כל הסטטוסים\n```\n\n## המלצה: אפשרות A - פשוטה יותר ומשתלבת ב-UX הקיים",
"testStrategy": "## בדיקות:\n1. **UI Testing**:\n - לחיצה על אייקון העזרה פותחת popover/tooltip\n - כל סטטוס מציג: תיאור, agent, שלב הבא\n - סגירת ה-popover עובדת (לחיצה מחוץ/Escape)\n\n2. **Accessibility**:\n - ה-popover נגיש למקלדת (Tab, Enter, Escape)\n - aria-label מתאים\n - RTL מוצג נכון\n\n3. **Content Review**:\n - כל ההסברים בעברית תקנית\n - הזרימה בין סטטוסים מובנת",
"priority": "medium",
"dependencies": [
93
],
"status": "pending",
"subtasks": []
},
{
"id": 95,
"title": "עריכה ידנית של סטטוס בדף התיק",
"description": "הוספת dropdown או מודל בדף התיק לשינוי סטטוס ידני, לטיפול במקרים שבהם ה-pipeline נתקע או צריך reset",
"details": "## מיקום: בתוך הכרטיס של WorkflowTimeline (בצד ימין של דף התיק)\n\n## פסאודו-קוד:\n\n### 1. קומפוננטת StatusEditor:\n```typescript\n// web-ui/src/components/cases/status-editor.tsx\n\nimport { Select } from \"@/components/ui/select\";\nimport { Button } from \"@/components/ui/button\";\nimport { useUpdateCase } from \"@/lib/api/cases\";\nimport { toast } from \"sonner\";\n\nexport function StatusEditor({ caseNumber, currentStatus }: Props) {\n const [selectedStatus, setSelectedStatus] = useState(currentStatus);\n const updateCase = useUpdateCase(caseNumber);\n\n const handleSave = async () => {\n if (selectedStatus === currentStatus) return;\n \n try {\n await updateCase.mutateAsync({ status: selectedStatus });\n toast.success(\"סטטוס עודכן בהצלחה\");\n } catch (error) {\n toast.error(\"שגיאה בעדכון הסטטוס\");\n }\n };\n\n return (\n <div className=\"flex items-center gap-2 mt-4\">\n <Select value={selectedStatus} onValueChange={setSelectedStatus}>\n {ALL_STATUSES.map(status => (\n <SelectItem key={status} value={status}>\n {STATUS_LABELS[status]}\n </SelectItem>\n ))}\n </Select>\n <Button \n onClick={handleSave} \n disabled={selectedStatus === currentStatus || updateCase.isPending}\n size=\"sm\"\n >\n עדכן\n </Button>\n </div>\n );\n}\n```\n\n### 2. שילוב בדף התיק:\n```typescript\n// web-ui/src/app/cases/[caseNumber]/page.tsx\n// בתוך הכרטיס של WorkflowTimeline\n\n<Card className=\"bg-surface border-rule shadow-sm h-fit\">\n <CardContent className=\"px-6 py-5\">\n <h2 className=\"text-navy text-base mb-4\">שלב בתהליך</h2>\n <WorkflowTimeline status={data?.status} />\n {data && <StatusEditor caseNumber={caseNumber} currentStatus={data.status} />}\n </CardContent>\n</Card>\n```\n\n### 3. עדכון ה-API (אם נדרש):\nה-`useUpdateCase` כבר תומך ב-status field לפי `caseUpdateSchema`.",
"testStrategy": "## בדיקות:\n1. **Functionality**:\n - בחירת סטטוס חדש מה-dropdown\n - לחיצה על \"עדכן\" שולחת PUT request ל-API\n - הסטטוס מתעדכן ב-UI אחרי הצלחה\n - toast הודעה מוצגת\n\n2. **Edge Cases**:\n - לחיצה על \"עדכן\" כשהסטטוס לא השתנה - כפתור disabled\n - טיפול בשגיאת API - הודעת שגיאה\n - כפתור disabled בזמן loading\n\n3. **Integration**:\n - ה-WorkflowTimeline מתעדכן מיד אחרי שינוי סטטוס\n - ה-StatusBadge בכותרת מתעדכן\n - הנתונים מסונכרנים עם ה-DB",
"priority": "medium",
"dependencies": [
93
],
"status": "pending",
"subtasks": []
},
{
"id": 96,
"title": "מיזוג כפתורי פעולות לכרטיס הסקירה הראשי",
"description": "העברת הכפתורים 'פתח בעורך ההחלטה' ו'עריכת פרטי תיק' מהלשונית 'פעולות' לכרטיס הכותרת העליון, והסרת הלשונית המיותרת",
"details": "## קבצים לעדכון:\n- `web-ui/src/app/cases/[caseNumber]/page.tsx`\n- `web-ui/src/components/cases/case-header.tsx`\n\n## פסאודו-קוד:\n\n### 1. עדכון CaseHeader להוספת כפתורי פעולה:\n```typescript\n// web-ui/src/components/cases/case-header.tsx\n\nimport Link from \"next/link\";\nimport { Button } from \"@/components/ui/button\";\nimport { CaseEditDialog } from \"@/components/cases/case-edit-dialog\";\n\nexport function CaseHeader({ data }: { data?: CaseDetail }) {\n return (\n <Card className=\"bg-surface border-rule shadow-sm\">\n <CardContent className=\"px-6 py-5\">\n {/* ... breadcrumb קיים ... */}\n \n <div className=\"flex items-start justify-between gap-6 flex-wrap\">\n <div className=\"space-y-2\">\n {/* ... כותרת וסטטוס קיימים ... */}\n </div>\n\n {/* כפתורי פעולה - חדש */}\n <div className=\"flex items-center gap-3 flex-wrap\">\n <Button asChild className=\"bg-navy hover:bg-navy-soft text-parchment\">\n <Link href={`/cases/${data?.case_number}/compose`}>\n פתח בעורך ההחלטה\n </Link>\n </Button>\n {data && <CaseEditDialog data={data} />}\n </div>\n </div>\n\n {/* ... תאריכים קיימים ... */}\n </CardContent>\n </Card>\n );\n}\n```\n\n### 2. הסרת לשונית \"פעולות\" מדף התיק:\n```typescript\n// web-ui/src/app/cases/[caseNumber]/page.tsx\n\n// הסר את TabsTrigger value=\"actions\"\n<TabsList className=\"bg-rule-soft/60\">\n <TabsTrigger value=\"overview\">סקירה</TabsTrigger>\n <TabsTrigger value=\"documents\">מסמכים (...)</TabsTrigger>\n {/* הוסר: <TabsTrigger value=\"actions\">פעולות</TabsTrigger> */}\n</TabsList>\n\n// הסר את TabsContent value=\"actions\"\n// הקוד הבא נמחק:\n// <TabsContent value=\"actions\" className=\"mt-5\">\n// <div className=\"flex items-center gap-3 flex-wrap\">\n// <Button asChild>...</Button>\n// {data && <CaseEditDialog data={data} />}\n// </div>\n// </TabsContent>\n```\n\n### 3. עדכון CaseHeader props:\n```typescript\n// צריך להעביר caseNumber ל-CaseHeader אם עדיין לא קיים\n<CaseHeader data={data} caseNumber={caseNumber} />\n```",
"testStrategy": "## בדיקות:\n1. **Visual**:\n - כפתורי הפעולה מופיעים בכרטיס העליון\n - הכפתורים מיושרים ימינה (RTL)\n - responsive - נגלשים נכון במסכים קטנים\n\n2. **Functionality**:\n - \"פתח בעורך ההחלטה\" מנווט ל-/cases/{caseNumber}/compose\n - \"עריכת פרטי תיק\" פותח את ה-CaseEditDialog\n - ה-dialog עובד כרגיל\n\n3. **Removal**:\n - לשונית \"פעולות\" לא מופיעה יותר ב-Tabs\n - אין שגיאות קונסול\n - ניווט ל-#actions לא עובד (ולא אמור)\n\n4. **Regression**:\n - לשוניות \"סקירה\" ו\"מסמכים\" עובדות כרגיל\n - שאר הדף לא נפגע",
"priority": "low",
"dependencies": [],
"status": "pending",
"subtasks": []
}
],
"metadata": {
"version": "1.0.0",
"lastModified": "2026-04-11T19:20:56.040Z",
"taskCount": 60,
"completedCount": 57,
"tags": [
"master"
]
"created": "2026-04-13T14:20:54.888Z",
"updated": "2026-04-13T14:20:54.888Z",
"description": "Tasks for master context"
}
}
}

View File

@@ -10,6 +10,8 @@ import { Skeleton } from "@/components/ui/skeleton";
import { CaseHeader } from "@/components/cases/case-header";
import { CaseEditDialog } from "@/components/cases/case-edit-dialog";
import { WorkflowTimeline } from "@/components/cases/workflow-timeline";
import { StatusGuide } from "@/components/cases/status-guide";
import { StatusChanger } from "@/components/cases/status-changer";
import { DocumentsPanel } from "@/components/cases/documents-panel";
import { UploadSheet } from "@/components/documents/upload-sheet";
import { expectedOutcomes } from "@/lib/schemas/case";
@@ -76,7 +78,6 @@ export default function CaseDetailPage({
</span>
)}
</TabsTrigger>
<TabsTrigger value="actions">פעולות</TabsTrigger>
</TabsList>
<UploadSheet caseNumber={caseNumber} />
</div>
@@ -101,14 +102,7 @@ export default function CaseDetailPage({
</dd>
</dl>
</div>
</TabsContent>
<TabsContent value="documents" className="mt-5">
<DocumentsPanel data={data} />
</TabsContent>
<TabsContent value="actions" className="mt-5">
<div className="flex items-center gap-3 flex-wrap">
<div className="flex items-center gap-3 flex-wrap pt-2 border-t border-rule">
<Button asChild className="bg-navy hover:bg-navy-soft text-parchment">
<Link href={`/cases/${caseNumber}/compose`}>
פתח בעורך ההחלטה
@@ -117,6 +111,10 @@ export default function CaseDetailPage({
{data && <CaseEditDialog data={data} />}
</div>
</TabsContent>
<TabsContent value="documents" className="mt-5">
<DocumentsPanel data={data} />
</TabsContent>
</Tabs>
</CardContent>
</Card>
@@ -125,6 +123,8 @@ export default function CaseDetailPage({
<CardContent className="px-6 py-5">
<h2 className="text-navy text-base mb-4">שלב בתהליך</h2>
<WorkflowTimeline status={data?.status} />
<StatusChanger caseNumber={caseNumber} currentStatus={data?.status} />
<StatusGuide />
</CardContent>
</Card>
</div>

View File

@@ -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 };

View 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>
);
}

View 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>
);
}

View File

@@ -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>
);