diff --git a/surfsense_web/components/chat/DocumentsDataTable.tsx b/surfsense_web/components/chat/DocumentsDataTable.tsx index 331c1b404..48c8b130e 100644 --- a/surfsense_web/components/chat/DocumentsDataTable.tsx +++ b/surfsense_web/components/chat/DocumentsDataTable.tsx @@ -10,6 +10,7 @@ import { import { ArrowUpDown, Calendar, FileText, Filter, Plus, Search } from "lucide-react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; @@ -32,6 +33,9 @@ import { } from "@/components/ui/table"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { type Document, type DocumentType, useDocuments } from "@/hooks/use-documents"; +import { documentsApiService } from "@/lib/apis/documents-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; +import { DocumentTypeEnum } from "@/contracts/types/document.types"; interface DocumentsDataTableProps { searchSpaceId: number; @@ -182,18 +186,62 @@ export function DocumentsDataTable({ const [sorting, setSorting] = useState([]); const [search, setSearch] = useState(""); const debouncedSearch = useDebounced(search, 300); - const [documentTypeFilter, setDocumentTypeFilter] = useState([]); + const [documentTypeFilter, setDocumentTypeFilter] = useState([]); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); const [typeCounts, setTypeCounts] = useState>({}); + const fetchQueryParams = useMemo( + () => ({ + search_space_id: searchSpaceId, + page: pageIndex , + page_size: pageSize, + ...(documentTypeFilter.length > 0 && { document_types: documentTypeFilter }), + }), + [searchSpaceId, pageIndex, pageSize, documentTypeFilter, debouncedSearch] + ); + + const searchQueryParams = useMemo(() => { + return { + ...fetchQueryParams, + title : debouncedSearch, + } + },[debouncedSearch]) + + // Use query for fetching documents + const { + data: documents, + isLoading: isDocumentsLoading, + } = useQuery({ + queryKey: cacheKeys.documents.withQueryParams(fetchQueryParams), + queryFn: () => documentsApiService.getDocuments({ queryParams : fetchQueryParams }), + staleTime: 3 * 60 * 1000, // 3 minutes + enabled: !!searchSpaceId && !debouncedSearch.trim(), + }); + + // Seaching + const { + data: searchedDocuments, + isLoading: isSearchedDocumentsLoading, + } = useQuery({ + queryKey: cacheKeys.documents.withQueryParams(searchQueryParams), + queryFn: () => documentsApiService.searchDocuments({ queryParams : searchQueryParams }), + staleTime: 3 * 60 * 1000, // 3 minutes + enabled: !!searchSpaceId && !!debouncedSearch.trim(), + }); + // Use server-side pagination, search, and filtering - const { documents, total, loading, fetchDocuments, searchDocuments, getDocumentTypeCounts } = + const { getDocumentTypeCounts } = useDocuments(searchSpaceId, { page: pageIndex, pageSize: pageSize, }); + // Use query data when not searching, otherwise use hook data + const actualDocuments = debouncedSearch.trim() ? searchedDocuments?.items|| [] : documents?.items|| []; + const actualTotal = debouncedSearch.trim() ? searchedDocuments?.total || 0 : documents?.total || 0; + const actualLoading = debouncedSearch.trim() ? isSearchedDocumentsLoading : isDocumentsLoading; + // Fetch document type counts on mount useEffect(() => { if (searchSpaceId && getDocumentTypeCounts) { @@ -201,34 +249,6 @@ export function DocumentsDataTable({ } }, [searchSpaceId, getDocumentTypeCounts]); - // Refetch when pagination changes or when search/filters change - useEffect(() => { - if (searchSpaceId) { - if (debouncedSearch.trim()) { - searchDocuments?.( - debouncedSearch, - pageIndex, - pageSize, - documentTypeFilter.length > 0 ? documentTypeFilter : undefined - ); - } else { - fetchDocuments?.( - pageIndex, - pageSize, - documentTypeFilter.length > 0 ? documentTypeFilter : undefined - ); - } - } - }, [ - pageIndex, - pageSize, - debouncedSearch, - documentTypeFilter, - searchSpaceId, - fetchDocuments, - searchDocuments, - ]); - // Memoize initial row selection to prevent infinite loops const initialRowSelection = useMemo(() => { if (!initialSelectedDocuments.length) return {}; @@ -272,14 +292,14 @@ export function DocumentsDataTable({ // Update the selected documents map when row selection changes useEffect(() => { - if (!documents || documents.length === 0) return; + if (!actualDocuments || actualDocuments.length === 0) return; setSelectedDocumentsMap((prev) => { const newMap = new Map(prev); let hasChanges = false; // Process only current page documents - for (const doc of documents) { + for (const doc of actualDocuments) { const docId = doc.id; const isSelected = rowSelection[docId.toString()]; const wasInMap = newMap.has(docId); @@ -319,14 +339,14 @@ export function DocumentsDataTable({ }, [selectedDocumentsArray, onSelectionChange]); const table = useReactTable({ - data: documents || [], + data: actualDocuments || [], columns, getRowId: (row) => row.id.toString(), onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), onRowSelectionChange: setRowSelection, manualPagination: true, - pageCount: Math.ceil(total / pageSize), + pageCount: Math.ceil(actualTotal / pageSize), state: { sorting, rowSelection, pagination: { pageIndex, pageSize } }, }); @@ -344,7 +364,7 @@ export function DocumentsDataTable({ setRowSelection(newSelection); }, [table, rowSelection]); - const handleToggleType = useCallback((type: string, checked: boolean) => { + const handleToggleType = useCallback((type: DocumentTypeEnum, checked: boolean) => { setDocumentTypeFilter((prev) => { if (checked) { return [...prev, type]; @@ -358,7 +378,7 @@ export function DocumentsDataTable({ // Get available document types from type counts (memoized) const availableTypes = useMemo(() => { - const types = Object.keys(typeCounts); + const types = Object.keys(typeCounts) as DocumentTypeEnum[]; return types.length > 0 ? types.sort() : []; }, [typeCounts]); @@ -435,7 +455,7 @@ export function DocumentsDataTable({
- {selectedCount} selected {loading && "· Loading..."} + {selectedCount} selected {actualLoading && "· Loading..."}
@@ -453,7 +473,7 @@ export function DocumentsDataTable({ size="sm" onClick={handleSelectPage} className="text-xs sm:text-sm" - disabled={loading} + disabled={actualLoading} > Select Page @@ -490,7 +510,7 @@ export function DocumentsDataTable({ {/* Table Container */}
- {loading ? ( + {actualLoading ? (
@@ -561,31 +581,31 @@ export function DocumentsDataTable({ {/* Footer Pagination */}
- Showing {pageIndex * pageSize + 1} to {Math.min((pageIndex + 1) * pageSize, total)} of{" "} - {total} documents + Showing {pageIndex * pageSize + 1} to {Math.min((pageIndex + 1) * pageSize, actualTotal)} of{" "} + {actualTotal} documents
Page - {pageIndex + 1} - of - {Math.ceil(total / pageSize)} -
+ {pageIndex + 1} + of + {Math.ceil(actualTotal / pageSize)} +
diff --git a/surfsense_web/contracts/types/document.types.ts b/surfsense_web/contracts/types/document.types.ts index c5d777080..941ed5052 100644 --- a/surfsense_web/contracts/types/document.types.ts +++ b/surfsense_web/contracts/types/document.types.ts @@ -60,7 +60,7 @@ export const getDocumentsRequest = z.object({ queryParams: paginationQueryParams .extend({ search_space_id: z.number().or(z.string()).optional(), - document_type: z.array(documentTypeEnum).optional(), + document_types: z.array(documentTypeEnum).optional(), }) .nullish(), }); @@ -109,7 +109,7 @@ export const searchDocumentsRequest = z.object({ queryParams: paginationQueryParams .extend({ search_space_id: z.number().or(z.string()).optional(), - document_type: z.array(documentTypeEnum).optional(), + document_types: z.array(documentTypeEnum).optional(), title: z.string().optional(), }) .nullish(), @@ -179,3 +179,4 @@ export type UpdateDocumentRequest = z.infer; export type UpdateDocumentResponse = z.infer; export type DeleteDocumentRequest = z.infer; export type DeleteDocumentResponse = z.infer; +export type DocumentTypeEnum = z.infer diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index 813ba62cf..72fac6dc5 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -15,6 +15,7 @@ export const cacheKeys = { documents: { globalQueryParams: (queries: GetDocumentsRequest["queryParams"]) => ["documents", ...(queries ? Object.values(queries) : [])] as const, + withQueryParams :(queries: GetDocumentsRequest["queryParams"]) => ["documents-with-queries", ...(queries ? Object.values(queries) : [])] as const, document: (documentId: string) => ["document", documentId] as const, typeCounts: (searchSpaceId?: string) => ["documents", "type-counts", searchSpaceId] as const, byChunk: (chunkId: string) => ["documents", "by-chunk", chunkId] as const,