import { useRef, useState, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import Nav from "../components/Nav"; import { listDocuments, uploadDocument, deleteDocument, downloadDocument, getDocumentStatus, listCategories, createCategory, assignCategory, removeCategory, type DocumentOut, type CategoryOut, } from "../api/client"; function StatusBadge({ status }: { status: DocumentOut["status"] }) { const colors: Record = { pending: "#f4a261", processing: "#2196f3", done: "#2a9d8f", failed: "#e63946", }; return ( {status} ); } function DocumentRow({ doc, categories, onDelete, }: { doc: DocumentOut; categories: CategoryOut[]; onDelete: (id: string) => void; }) { const [expanded, setExpanded] = useState(false); const qc = useQueryClient(); // Poll status while pending/processing const { data: liveStatus } = useQuery({ queryKey: ["docStatus", doc.id], queryFn: () => getDocumentStatus(doc.id), // v5: refetchInterval receives the Query object; data lives in query.state.data refetchInterval: (query) => { const s = query.state.data?.status; return s === "pending" || s === "processing" ? 3000 : false; }, enabled: doc.status === "pending" || doc.status === "processing", }); useEffect(() => { if (liveStatus?.status === "done" || liveStatus?.status === "failed") { qc.invalidateQueries({ queryKey: ["documents"] }); } }, [liveStatus?.status, qc]); const assignMut = useMutation({ mutationFn: ({ catId }: { catId: string }) => assignCategory(doc.id, catId), onSuccess: () => qc.invalidateQueries({ queryKey: ["documents"] }), }); const removeCatMut = useMutation({ mutationFn: ({ catId }: { catId: string }) => removeCategory(doc.id, catId), onSuccess: () => qc.invalidateQueries({ queryKey: ["documents"] }), }); const assignedIds = new Set(doc.categories.map((c) => c.id)); const unassigned = categories.filter((c) => !assignedIds.has(c.id)); let extractedData: Record | null = null; if (doc.extracted_data) { try { extractedData = JSON.parse(doc.extracted_data); } catch { // ignore } } const tags: string[] = []; if (doc.tags) { try { const parsed = JSON.parse(doc.tags); if (Array.isArray(parsed)) tags.push(...parsed); } catch { // ignore } } return (
setExpanded((e) => !e)} > {doc.filename} {doc.document_type && ( {doc.document_type} )} {(doc.file_size / 1024).toFixed(0)} KB
{expanded && (
{tags.length > 0 && (
Tags:{" "} {tags.map((t) => ( {t} ))}
)} {extractedData && (
Extracted data: {Object.entries(extractedData) .filter(([k]) => k !== "tags") .map(([k, v]) => ( ))}
{k} {Array.isArray(v) ? v.length === 0 ? "—" : JSON.stringify(v, null, 2) : v !== null && v !== undefined && v !== "" ? String(v) : "—"}
)} {doc.error_message && (
Error: {doc.error_message}
)}
Categories:{" "} {doc.categories.map((c) => ( {c.name}{" "} ))} {unassigned.length > 0 && ( )}
)}
); } export default function DocumentsPage() { const qc = useQueryClient(); const fileRef = useRef(null); const [newCatName, setNewCatName] = useState(""); const [uploadError, setUploadError] = useState(null); const { data: documents = [], isLoading } = useQuery({ queryKey: ["documents"], queryFn: listDocuments, }); const { data: categories = [] } = useQuery({ queryKey: ["categories"], queryFn: listCategories, }); const uploadMut = useMutation({ mutationFn: uploadDocument, onSuccess: () => { setUploadError(null); qc.invalidateQueries({ queryKey: ["documents"] }); }, onError: (err: unknown) => { const msg = (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail ?? "Upload failed"; setUploadError(msg); }, }); const deleteMut = useMutation({ mutationFn: deleteDocument, onSuccess: () => qc.invalidateQueries({ queryKey: ["documents"] }), }); const createCatMut = useMutation({ mutationFn: createCategory, onSuccess: () => { setNewCatName(""); qc.invalidateQueries({ queryKey: ["categories"] }); }, }); const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) uploadMut.mutate(file); e.target.value = ""; }; return ( <>