/** * Exports domain hooks — draft DOCX files for a case. */ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "./client"; import { casesKeys } from "./cases"; export type ExportFile = { filename: string; size: number; created_at: number; is_final: boolean; }; export type ActiveDraft = { active_draft_path: string | null; filename: string | null; exists: boolean; }; export type Revision = { id: string; type: "insert_after" | "insert_before" | "replace" | "delete"; anchor_bookmark: string; content?: string; style?: "body" | "heading" | "quote" | "bold"; reason?: string; }; export type UploadResult = { filename: string; size: number; version: number; active_draft?: string; bookmarks_added?: string[]; missing_blocks?: string[]; structural_fallback?: string[]; apply_status?: string; }; export type ReviseResult = { status: string; output_path: string; version: number; applied: number; failed: number; results: { id: string; status: string; error?: string }[]; }; export const exportsKeys = { all: ["exports"] as const, list: (caseNumber: string) => [...exportsKeys.all, "list", caseNumber] as const, activeDraft: (caseNumber: string) => [...exportsKeys.all, "active-draft", caseNumber] as const, bookmarks: (caseNumber: string) => [...exportsKeys.all, "bookmarks", caseNumber] as const, }; export function useExports(caseNumber: string | undefined) { return useQuery({ queryKey: exportsKeys.list(caseNumber ?? ""), queryFn: ({ signal }) => apiRequest(`/api/cases/${caseNumber}/exports`, { signal }), enabled: Boolean(caseNumber), staleTime: 5_000, refetchInterval: 5_000, }); } export function useExportDocx(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: () => apiRequest<{ status: string; path: string; message: string }>( `/api/cases/${caseNumber}/export-docx`, { method: "POST" }, ), onSuccess: () => { qc.invalidateQueries({ queryKey: exportsKeys.list(caseNumber) }); qc.invalidateQueries({ queryKey: casesKeys.detail(caseNumber) }); }, }); } export function useUploadDraft(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async (file: File): Promise => { const form = new FormData(); form.append("file", file); const res = await fetch(`/api/cases/${caseNumber}/exports/upload`, { method: "POST", body: form, }); if (!res.ok) { const err = await res.json().catch(() => ({ detail: "שגיאה בהעלאה" })); throw new Error(err.detail ?? "שגיאה בהעלאה"); } return res.json() as Promise; }, onSuccess: () => { qc.invalidateQueries({ queryKey: exportsKeys.list(caseNumber) }); qc.invalidateQueries({ queryKey: exportsKeys.activeDraft(caseNumber) }); qc.invalidateQueries({ queryKey: exportsKeys.bookmarks(caseNumber) }); }, }); } export function useActiveDraft(caseNumber: string | undefined) { return useQuery({ queryKey: exportsKeys.activeDraft(caseNumber ?? ""), queryFn: ({ signal }) => apiRequest(`/api/cases/${caseNumber}/active-draft`, { signal }), enabled: Boolean(caseNumber), staleTime: 5_000, }); } export function useBookmarks(caseNumber: string | undefined) { return useQuery({ queryKey: exportsKeys.bookmarks(caseNumber ?? ""), queryFn: ({ signal }) => apiRequest<{ status: string; active_draft_path?: string; bookmarks?: string[]; }>(`/api/cases/${caseNumber}/exports/bookmarks`, { signal }), enabled: Boolean(caseNumber), staleTime: 10_000, }); } export function useReviseDraft(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: (payload: { revisions: Revision[]; author?: string }) => apiRequest(`/api/cases/${caseNumber}/exports/revise`, { method: "POST", body: payload, }), onSuccess: () => { qc.invalidateQueries({ queryKey: exportsKeys.list(caseNumber) }); qc.invalidateQueries({ queryKey: exportsKeys.activeDraft(caseNumber) }); }, }); } export function useRetrofit(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: (filename: string) => apiRequest<{ status: string; active_draft_path: string; bookmarks_added: string[]; missing_blocks: string[]; structural_fallback?: string[]; }>(`/api/cases/${caseNumber}/exports/${filename}/retrofit`, { method: "POST", }), onSuccess: () => { qc.invalidateQueries({ queryKey: exportsKeys.activeDraft(caseNumber) }); qc.invalidateQueries({ queryKey: exportsKeys.bookmarks(caseNumber) }); }, }); } export function useDeleteDraft(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: (filename: string) => apiRequest<{ deleted: boolean; filename: string }>( `/api/cases/${caseNumber}/exports/${filename}`, { method: "DELETE" }, ), onSuccess: () => { qc.invalidateQueries({ queryKey: exportsKeys.list(caseNumber) }); }, }); } /* ── Chair's signed final decision — clean upload path + staged pipeline ── */ export type FinalUploadResult = { final_filename: string; training_copy: string; pair_id: string | null; draft_words: number; final_words: number; status: string; }; export type FinalTaskResult = { status: string; sub_issue_id?: string; curator_id?: string; reason?: string; error?: string; }; /** Upload Dafna's signed final decision (distinct from "upload a revised draft"). */ export function useUploadFinalDecision(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: async (file: File): Promise => { const form = new FormData(); form.append("file", file); const res = await fetch(`/api/cases/${caseNumber}/final/upload`, { method: "POST", body: form, }); if (!res.ok) { const err = await res .json() .catch(() => ({ detail: "שגיאה בהעלאת ההחלטה הסופית" })); throw new Error(err.detail ?? "שגיאה בהעלאת ההחלטה הסופית"); } return res.json() as Promise; }, onSuccess: () => { qc.invalidateQueries({ queryKey: exportsKeys.list(caseNumber) }); qc.invalidateQueries({ queryKey: casesKeys.detail(caseNumber) }); }, }); } /** Staged step 1 — voice learning (Opus distillation + DeepSeek+Gemini style panel). */ export function useRunFinalLearning(caseNumber: string) { return useMutation({ mutationFn: () => apiRequest( `/api/cases/${caseNumber}/final/run-learning`, { method: "POST" }, ), }); } /** Staged step 2 — halacha validation (cited-halacha extraction + 3-judge panel). */ export function useRunFinalHalacha(caseNumber: string) { return useMutation({ mutationFn: () => apiRequest( `/api/cases/${caseNumber}/final/run-halacha`, { method: "POST" }, ), }); } export function useMarkFinal(caseNumber: string) { const qc = useQueryClient(); return useMutation({ mutationFn: (filename: string) => apiRequest<{ final_filename: string; status: string }>( `/api/cases/${caseNumber}/exports/${filename}/mark-final`, { method: "POST" }, ), onSuccess: () => { qc.invalidateQueries({ queryKey: exportsKeys.list(caseNumber) }); qc.invalidateQueries({ queryKey: casesKeys.detail(caseNumber) }); }, }); }