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 <Form> 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) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 16:21:21 +00:00
parent 03b25bc273
commit 9fcf4f2dc7
14 changed files with 808 additions and 15 deletions

View File

@@ -8,8 +8,9 @@
* surfaces as a runtime TypeScript error the first time a property is touched.
*/
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "./client";
import type { CaseCreateInput, CaseUpdateInput } from "@/lib/schemas/case";
export type CaseStatus =
| "new"
@@ -95,6 +96,41 @@ export type WorkflowStatus = {
[key: string]: unknown;
};
export function useCreateCase() {
const qc = useQueryClient();
return useMutation({
mutationFn: (input: CaseCreateInput) =>
apiRequest<{ case_number?: string; message?: string; [k: string]: unknown }>(
`/api/cases/create`,
{ method: "POST", body: input },
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: casesKeys.all });
},
});
}
export function useUpdateCase(caseNumber: string | undefined) {
const qc = useQueryClient();
return useMutation({
mutationFn: (input: CaseUpdateInput) =>
apiRequest<CaseDetail>(`/api/cases/${caseNumber}`, {
method: "PUT",
body: input,
}),
onSuccess: (data) => {
/* Patch cached detail and nudge the list to refetch on next focus */
if (caseNumber) {
qc.setQueryData<CaseDetail | undefined>(
casesKeys.detail(caseNumber),
(prev) => (prev ? { ...prev, ...data } : prev),
);
}
qc.invalidateQueries({ queryKey: casesKeys.all });
},
});
}
export function useWorkflowStatus(caseNumber: string | undefined) {
return useQuery({
queryKey: [...casesKeys.all, "workflow", caseNumber ?? ""] as const,