From 9fcf4f2dc7d82abac9b2ef9b11e3385e53299672 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 11 Apr 2026 16:21:21 +0000 Subject: [PATCH] Phase 4a: shadcn form primitives + case inline edit Add dialog/select/textarea/label/progress/sonner components and wire a Toaster into Providers. New zod schemas in lib/schemas/case.ts mirror CaseCreateRequest/CaseUpdateRequest and feed react-hook-form validation. CaseEditDialog on the case detail Actions tab posts PUT /api/cases/{n} with optimistic cache patching via useUpdateCase, showing toast feedback on success/error. shadcn's
registry entry skipped at init (missing from the nova preset); the dialog uses RHF directly against the same Input/ Textarea/Select primitives. Co-Authored-By: Claude Opus 4.6 (1M context) --- .taskmaster/tasks/tasks.json | 13 +- web-ui/package-lock.json | 22 ++ web-ui/package.json | 2 + web-ui/src/app/cases/[caseNumber]/page.tsx | 18 +- .../src/components/cases/case-edit-dialog.tsx | 160 +++++++++++++++ web-ui/src/components/ui/dialog.tsx | 168 +++++++++++++++ web-ui/src/components/ui/label.tsx | 24 +++ web-ui/src/components/ui/progress.tsx | 31 +++ web-ui/src/components/ui/select.tsx | 192 ++++++++++++++++++ web-ui/src/components/ui/sonner.tsx | 49 +++++ web-ui/src/components/ui/textarea.tsx | 18 ++ web-ui/src/lib/api/cases.ts | 38 +++- web-ui/src/lib/providers.tsx | 6 +- web-ui/src/lib/schemas/case.ts | 82 ++++++++ 14 files changed, 808 insertions(+), 15 deletions(-) create mode 100644 web-ui/src/components/cases/case-edit-dialog.tsx create mode 100644 web-ui/src/components/ui/dialog.tsx create mode 100644 web-ui/src/components/ui/label.tsx create mode 100644 web-ui/src/components/ui/progress.tsx create mode 100644 web-ui/src/components/ui/select.tsx create mode 100644 web-ui/src/components/ui/sonner.tsx create mode 100644 web-ui/src/components/ui/textarea.tsx create mode 100644 web-ui/src/lib/schemas/case.ts diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index 48a1cab..f190bf1 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -820,13 +820,13 @@ "description": "Port the 3 highest-value screens. Use the frontend-design Claude Code skill to generate layout + composition, passing design tokens (navy/gold/parchment, Heebo), editorial voice, and typed API hooks. Use shadcn Card/Badge/Tabs/Sheet/ScrollArea as primitives. Port the custom donut chart into component. TanStack Query staleTime:5000 for case detail replaces manual 5s polling. Plan: ~/.claude/plans/joyful-marinating-sutton.md.", "details": "See full plan at ~/.claude/plans/joyful-marinating-sutton.md for architecture, critical files, risks, and open questions. This task is phase 3 of 7 in the legal-ai UI rewrite from vanilla HTML to Next.js 15 + shadcn/ui.", "testStrategy": "Users can browse case list, open a case detail, and view the compose screen with live data from FastAPI. All 3 screens visually match the existing legal-ai identity.", - "status": "in-progress", + "status": "done", "dependencies": [ "84" ], "priority": "high", "subtasks": [], - "updatedAt": "2026-04-11T15:55:49.327Z" + "updatedAt": "2026-04-11T16:09:18.006Z" }, { "id": "86", @@ -834,12 +834,13 @@ "description": "Port new case wizard, bulk upload, inline forms on case detail. Use react-hook-form + zod with schemas in lib/schemas/.ts. Build shared from shadcn Card + Progress + Tabs. Build (react-dropzone + shadcn). Integrate SSE for upload progress via lib/sse.ts. Plan: ~/.claude/plans/joyful-marinating-sutton.md.", "details": "See full plan at ~/.claude/plans/joyful-marinating-sutton.md for architecture, critical files, risks, and open questions. This task is phase 4 of 7 in the legal-ai UI rewrite from vanilla HTML to Next.js 15 + shadcn/ui.", "testStrategy": "Users can create a new case via the multi-step wizard (case appears in Gitea + Paperclip), upload documents with live SSE progress, and edit case fields inline.", - "status": "pending", + "status": "in-progress", "dependencies": [ "85" ], "priority": "medium", - "subtasks": [] + "subtasks": [], + "updatedAt": "2026-04-11T16:18:28.714Z" }, { "id": "87", @@ -883,9 +884,9 @@ ], "metadata": { "version": "1.0.0", - "lastModified": "2026-04-11T15:55:49.330Z", + "lastModified": "2026-04-11T16:18:28.714Z", "taskCount": 58, - "completedCount": 51, + "completedCount": 52, "tags": [ "master" ] diff --git a/web-ui/package-lock.json b/web-ui/package-lock.json index 98496b9..1de4c1a 100644 --- a/web-ui/package-lock.json +++ b/web-ui/package-lock.json @@ -15,12 +15,14 @@ "clsx": "^2.1.1", "lucide-react": "^1.8.0", "next": "16.2.3", + "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "19.2.4", "react-dom": "19.2.4", "react-dropzone": "^15.0.0", "react-hook-form": "^7.72.1", "shadcn": "^4.2.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0", "zod": "^4.3.6" @@ -8836,6 +8838,16 @@ } } }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -10474,6 +10486,16 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/web-ui/package.json b/web-ui/package.json index 6fdfc99..f3e4288 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -17,12 +17,14 @@ "clsx": "^2.1.1", "lucide-react": "^1.8.0", "next": "16.2.3", + "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "19.2.4", "react-dom": "19.2.4", "react-dropzone": "^15.0.0", "react-hook-form": "^7.72.1", "shadcn": "^4.2.0", + "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0", "zod": "^4.3.6" diff --git a/web-ui/src/app/cases/[caseNumber]/page.tsx b/web-ui/src/app/cases/[caseNumber]/page.tsx index 96344d8..00fea46 100644 --- a/web-ui/src/app/cases/[caseNumber]/page.tsx +++ b/web-ui/src/app/cases/[caseNumber]/page.tsx @@ -8,6 +8,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; 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 { DocumentsPanel } from "@/components/cases/documents-panel"; import { useCase } from "@/lib/api/cases"; @@ -95,14 +96,17 @@ export default function CaseDetailPage({ -
- +
+
+ + {data && } +

- מעבר לעורך 12 הבלוקים לכתיבת טיוטה. + עריכת פרטי התיק נשמרת מיד דרך PUT /api/cases/{caseNumber}.

diff --git a/web-ui/src/components/cases/case-edit-dialog.tsx b/web-ui/src/components/cases/case-edit-dialog.tsx new file mode 100644 index 0000000..07fc842 --- /dev/null +++ b/web-ui/src/components/cases/case-edit-dialog.tsx @@ -0,0 +1,160 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { toast } from "sonner"; +import { + Dialog, DialogContent, DialogDescription, DialogFooter, + DialogHeader, DialogTitle, DialogTrigger, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Label } from "@/components/ui/label"; +import { + Select, SelectContent, SelectItem, SelectTrigger, SelectValue, +} from "@/components/ui/select"; +import { useUpdateCase } from "@/lib/api/cases"; +import { caseUpdateSchema, expectedOutcomes, type CaseUpdateInput } from "@/lib/schemas/case"; +import type { CaseDetail } from "@/lib/api/cases"; + +/* + * Inline edit dialog for core case fields. Uses react-hook-form + zod + * directly (shadcn's registry entry wasn't available at init + * time, so the styling is reproduced by hand in a lean form layout). + */ + +function FieldError({ message }: { message?: string }) { + if (!message) return null; + return

{message}

; +} + +export function CaseEditDialog({ data }: { data: CaseDetail }) { + const [open, setOpen] = useState(false); + const mutate = useUpdateCase(data.case_number); + + const form = useForm({ + resolver: zodResolver(caseUpdateSchema), + defaultValues: { + title: data.title ?? "", + subject: data.subject ?? "", + hearing_date: data.hearing_date ?? "", + notes: "", + expected_outcome: data.expected_outcome ?? "", + }, + }); + + /* Re-sync the form when the underlying case refetches after save */ + useEffect(() => { + if (!open) return; + form.reset({ + title: data.title ?? "", + subject: data.subject ?? "", + hearing_date: data.hearing_date ?? "", + notes: "", + expected_outcome: data.expected_outcome ?? "", + }); + }, [open, data, form]); + + const onSubmit = form.handleSubmit(async (values) => { + try { + await mutate.mutateAsync(values); + toast.success("פרטי התיק עודכנו"); + setOpen(false); + } catch (e) { + toast.error(e instanceof Error ? e.message : "שגיאה בעדכון התיק"); + } + }); + + return ( + + + + + + + עריכת פרטי תיק {data.case_number} + + השינויים נשמרים ישירות ל-FastAPI. השדות הריקים נשארים ללא שינוי. + + + + +
+ + + +
+ +
+ + + +
+ +
+
+ + + +
+ +
+ + +
+
+ +
+ +