diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx index 978cdf219..a0f079e7d 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsFilters.tsx @@ -28,6 +28,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import type { ColumnVisibility } from "./types"; +import { DocumentTypeEnum } from "@/contracts/types/document.types"; const fadeInScale: Variants = { hidden: { opacity: 0, scale: 0.95 }, @@ -46,13 +47,13 @@ export function DocumentsFilters({ columnVisibility, onToggleColumn, }: { - typeCounts: Record; + typeCounts: Record; selectedIds: Set; onSearch: (v: string) => void; searchValue: string; onBulkDelete: () => Promise; - onToggleType: (type: string, checked: boolean) => void; - activeTypes: string[]; + onToggleType: (type: DocumentTypeEnum, checked: boolean) => void; + activeTypes: DocumentTypeEnum[]; columnVisibility: ColumnVisibility; onToggleColumn: (id: keyof ColumnVisibility, checked: boolean) => void; }) { @@ -61,7 +62,7 @@ export function DocumentsFilters({ const inputRef = useRef(null); const uniqueTypes = useMemo(() => { - return Object.keys(typeCountsRecord).sort(); + return Object.keys(typeCountsRecord).sort() as DocumentTypeEnum[]; }, [typeCountsRecord]); const typeCounts = useMemo(() => { @@ -156,7 +157,7 @@ export function DocumentsFilters({
Filters
- {uniqueTypes.map((value, i) => ( + {uniqueTypes.map((value : DocumentTypeEnum, i) => ( (value: T, delay = 250) { const [debounced, setDebounced] = useState(value); @@ -30,7 +36,7 @@ export default function DocumentsTable() { const [search, setSearch] = useState(""); const debouncedSearch = useDebounced(search, 250); - const [activeTypes, setActiveTypes] = useState([]); + const [activeTypes, setActiveTypes] = useState([]); const [columnVisibility, setColumnVisibility] = useState({ title: true, document_type: true, @@ -42,63 +48,80 @@ export default function DocumentsTable() { const [sortKey, setSortKey] = useState("title"); const [sortDesc, setSortDesc] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); - const [typeCounts, setTypeCounts] = useState>({}); + const {data: typeCounts} = useAtomValue(documentTypeCountsAtom) ; + + // Build query parameters for fetching documents + const queryParams = useMemo( + () => ({ + search_space_id: searchSpaceId, + page: pageIndex, + page_size: pageSize, + ...(activeTypes.length > 0 && { document_types: activeTypes }), + }), + [searchSpaceId, pageIndex, pageSize, activeTypes] + ); + + // Build search query parameters + const searchQueryParams = useMemo( + () => ({ + search_space_id: searchSpaceId, + page: pageIndex, + page_size: pageSize, + title: debouncedSearch.trim(), + ...(activeTypes.length > 0 && { document_types: activeTypes }), + }), + [searchSpaceId, pageIndex, pageSize, activeTypes, debouncedSearch] + ); + + // Use query for fetching documents + const { + data: documentsResponse, + isLoading: isDocumentsLoading, + refetch: refetchDocuments, + } = useQuery({ + queryKey: cacheKeys.documents.globalQueryParams(queryParams), + queryFn: () => documentsApiService.getDocuments({ queryParams }), + staleTime: 3 * 60 * 1000, // 3 minutes + enabled: !!searchSpaceId && !debouncedSearch.trim(), + }); + + // Use query for searching documents + const { + data: searchResponse, + isLoading: isSearchLoading, + refetch: refetchSearch, + } = useQuery({ + queryKey: cacheKeys.documents.globalQueryParams(searchQueryParams), + queryFn: () => documentsApiService.searchDocuments({ queryParams: searchQueryParams }), + staleTime: 3 * 60 * 1000, // 3 minutes + enabled: !!searchSpaceId && !!debouncedSearch.trim(), + }); + + // Extract documents and total based on search state + const documents = debouncedSearch.trim() + ? searchResponse?.items || [] + : documentsResponse?.items || []; + const total = debouncedSearch.trim() + ? searchResponse?.total || 0 + : documentsResponse?.total || 0; + const loading = debouncedSearch.trim() ? isSearchLoading : isDocumentsLoading; // Use server-side pagination, search, and filtering const { - documents, - total, - loading, error, - fetchDocuments, - searchDocuments, deleteDocument, - getDocumentTypeCounts, } = useDocuments(searchSpaceId, { page: pageIndex, pageSize: pageSize, }); - // Fetch document type counts on mount and when search space changes - useEffect(() => { - if (searchSpaceId && getDocumentTypeCounts) { - getDocumentTypeCounts().then(setTypeCounts); - } - }, [searchSpaceId, getDocumentTypeCounts]); - - // Refetch when pagination changes or when search/filters change - useEffect(() => { - if (searchSpaceId) { - if (debouncedSearch.trim()) { - // Use search endpoint if there's a search query - searchDocuments?.( - debouncedSearch, - pageIndex, - pageSize, - activeTypes.length > 0 ? activeTypes : undefined - ); - } else { - // Use regular fetch if no search - fetchDocuments?.(pageIndex, pageSize, activeTypes.length > 0 ? activeTypes : undefined); - } - } - }, [ - pageIndex, - pageSize, - debouncedSearch, - activeTypes, - searchSpaceId, - fetchDocuments, - searchDocuments, - ]); - // Display server-filtered results directly const displayDocs = documents || []; const displayTotal = total; const pageStart = pageIndex * pageSize; const pageEnd = Math.min(pageStart + pageSize, displayTotal); - const onToggleType = (type: string, checked: boolean) => { + const onToggleType = (type: DocumentTypeEnum, checked: boolean) => { setActiveTypes((prev) => (checked ? [...prev, type] : prev.filter((t) => t !== type))); setPageIndex(0); }; @@ -109,16 +132,11 @@ export default function DocumentsTable() { const refreshCurrentView = useCallback(async () => { if (debouncedSearch.trim()) { - await searchDocuments?.( - debouncedSearch, - pageIndex, - pageSize, - activeTypes.length > 0 ? activeTypes : undefined - ); + await refetchSearch(); } else { - await fetchDocuments?.(pageIndex, pageSize, activeTypes.length > 0 ? activeTypes : undefined); + await refetchDocuments(); } - }, [debouncedSearch, pageIndex, pageSize, activeTypes, searchDocuments, fetchDocuments]); + }, [debouncedSearch, refetchSearch, refetchDocuments]); const onBulkDelete = async () => { if (selectedIds.size === 0) { @@ -159,7 +177,7 @@ export default function DocumentsTable() { className="w-full px-6 py-4" > ([]); - const [total, setTotal] = useState(0); - const [loading, setLoading] = useState(!lazy); // Don't show loading initially for lazy mode const [error, setError] = useState(null); - const [isLoaded, setIsLoaded] = useState(false); // Memoization flag - - const fetchDocuments = useCallback( - async (fetchPage?: number, fetchPageSize?: number, fetchDocumentTypes?: string[]) => { - if (isLoaded && lazy) return; // Avoid redundant calls in lazy mode - - try { - setLoading(true); - - // Build query params - const params = new URLSearchParams({ - search_space_id: searchSpaceId.toString(), - }); - - // Use passed parameters or fall back to state/options - const effectivePage = fetchPage !== undefined ? fetchPage : page; - const effectivePageSize = fetchPageSize !== undefined ? fetchPageSize : pageSize; - const effectiveDocumentTypes = - fetchDocumentTypes !== undefined ? fetchDocumentTypes : documentTypes; - - if (effectivePage !== undefined) { - params.append("page", effectivePage.toString()); - } - if (effectivePageSize !== undefined) { - params.append("page_size", effectivePageSize.toString()); - } - if (effectiveDocumentTypes && effectiveDocumentTypes.length > 0) { - params.append("document_types", effectiveDocumentTypes.join(",")); - } - - const response = await authenticatedFetch( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents?${params.toString()}`, - { method: "GET" } - ); - - if (!response.ok) { - toast.error("Failed to fetch documents"); - throw new Error("Failed to fetch documents"); - } - - const data = await response.json(); - const normalized = normalizeListResponse(data); - setDocuments(normalized.items); - setTotal(normalized.total); - setError(null); - setIsLoaded(true); - } catch (err: any) { - setError(err.message || "Failed to fetch documents"); - console.error("Error fetching documents:", err); - } finally { - setLoading(false); - } - }, - [searchSpaceId, page, pageSize, documentTypes, isLoaded, lazy] - ); - - useEffect(() => { - if (!lazy && searchSpaceId) { - fetchDocuments(); - } - }, [searchSpaceId, lazy, fetchDocuments]); - - // Function to refresh the documents list - const refreshDocuments = useCallback(async () => { - setIsLoaded(false); // Reset memoization flag to allow refetch - await fetchDocuments(); - }, [fetchDocuments]); - - // Function to search documents by title - const searchDocuments = useCallback( - async ( - searchQuery: string, - fetchPage?: number, - fetchPageSize?: number, - fetchDocumentTypes?: string[] - ) => { - if (!searchQuery.trim()) { - // If search is empty, fetch all documents - return fetchDocuments(fetchPage, fetchPageSize, fetchDocumentTypes); - } - - try { - setLoading(true); - - // Build query params - const params = new URLSearchParams({ - search_space_id: searchSpaceId.toString(), - title: searchQuery, - }); - - // Use passed parameters or fall back to state/options - const effectivePage = fetchPage !== undefined ? fetchPage : page; - const effectivePageSize = fetchPageSize !== undefined ? fetchPageSize : pageSize; - const effectiveDocumentTypes = - fetchDocumentTypes !== undefined ? fetchDocumentTypes : documentTypes; - - if (effectivePage !== undefined) { - params.append("page", effectivePage.toString()); - } - if (effectivePageSize !== undefined) { - params.append("page_size", effectivePageSize.toString()); - } - if (effectiveDocumentTypes && effectiveDocumentTypes.length > 0) { - params.append("document_types", effectiveDocumentTypes.join(",")); - } - - const response = await authenticatedFetch( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/search?${params.toString()}`, - { method: "GET" } - ); - - if (!response.ok) { - toast.error("Failed to search documents"); - throw new Error("Failed to search documents"); - } - - const data = await response.json(); - const normalized = normalizeListResponse(data); - setDocuments(normalized.items); - setTotal(normalized.total); - setError(null); - } catch (err: any) { - setError(err.message || "Failed to search documents"); - console.error("Error searching documents:", err); - } finally { - setLoading(false); - } - }, - [searchSpaceId, page, pageSize, documentTypes, fetchDocuments] - ); // Function to delete a document const deleteDocument = useCallback( @@ -195,52 +60,19 @@ export function useDocuments(searchSpaceId: number, options?: UseDocumentsOption } toast.success("Document deleted successfully"); - // Update the local state after successful deletion - setDocuments(documents.filter((doc) => doc.id !== documentId)); - return true; - } catch (err: any) { - toast.error(err.message || "Failed to delete document"); - console.error("Error deleting document:", err); - return false; - } + // Note: The caller should handle refetching the documents list + return true; + } catch (err: any) { + toast.error(err.message || "Failed to delete document"); + console.error("Error deleting document:", err); + return false; + } }, - [documents] + [] ); - // Function to get document type counts - const getDocumentTypeCounts = useCallback(async () => { - try { - const params = new URLSearchParams({ - search_space_id: searchSpaceId.toString(), - }); - - const response = await authenticatedFetch( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/type-counts?${params.toString()}`, - { method: "GET" } - ); - - if (!response.ok) { - throw new Error("Failed to fetch document type counts"); - } - - const counts = await response.json(); - return counts as Record; - } catch (err: any) { - console.error("Error fetching document type counts:", err); - return {}; - } - }, [searchSpaceId]); - return { - documents, - total, - loading, error, - isLoaded, - fetchDocuments, // Manual fetch function for lazy mode - searchDocuments, // Search function - refreshDocuments, deleteDocument, - getDocumentTypeCounts, // Get type counts function }; }