mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-05 13:52:40 +02:00
refactor: integrate useDocumentSearch hook into DocumentsSidebar for improved search functionality and performance, while enhancing user feedback with localized toast messages
This commit is contained in:
parent
62c4a3befc
commit
be0bfb3d56
8 changed files with 162 additions and 121 deletions
127
surfsense_web/hooks/use-document-search.ts
Normal file
127
surfsense_web/hooks/use-document-search.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
||||
import { documentsApiService } from "@/lib/apis/documents-api.service";
|
||||
import { type DocumentDisplay, toDisplayDoc } from "./use-documents";
|
||||
|
||||
const SEARCH_INITIAL_SIZE = 20;
|
||||
const SEARCH_SCROLL_SIZE = 5;
|
||||
|
||||
/**
|
||||
* Paginated document search hook.
|
||||
*
|
||||
* Handles title-based search with server-side filtering,
|
||||
* pagination via skip/page_size, and staleness detection
|
||||
* so fast typing never renders stale results.
|
||||
*
|
||||
* @param searchSpaceId - The search space to search within
|
||||
* @param query - The debounced search query
|
||||
* @param activeTypes - Document types to filter by
|
||||
* @param enabled - When false the hook resets and stops fetching
|
||||
*/
|
||||
export function useDocumentSearch(
|
||||
searchSpaceId: number,
|
||||
query: string,
|
||||
activeTypes: DocumentTypeEnum[],
|
||||
enabled: boolean
|
||||
) {
|
||||
const [documents, setDocuments] = useState<DocumentDisplay[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const [hasMore, setHasMore] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const apiLoadedRef = useRef(0);
|
||||
const queryRef = useRef(query);
|
||||
|
||||
const isActive = enabled && !!query.trim();
|
||||
const activeTypesKey = activeTypes.join(",");
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: activeTypesKey serializes activeTypes
|
||||
useEffect(() => {
|
||||
if (!isActive || !searchSpaceId) {
|
||||
setDocuments([]);
|
||||
setHasMore(false);
|
||||
setError(false);
|
||||
apiLoadedRef.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
queryRef.current = query;
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
|
||||
documentsApiService
|
||||
.searchDocuments({
|
||||
queryParams: {
|
||||
search_space_id: searchSpaceId,
|
||||
page: 0,
|
||||
page_size: SEARCH_INITIAL_SIZE,
|
||||
title: query.trim(),
|
||||
...(activeTypes.length > 0 && { document_types: activeTypes }),
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (cancelled || queryRef.current !== query) return;
|
||||
setDocuments(response.items.map(toDisplayDoc));
|
||||
setHasMore(response.has_more);
|
||||
apiLoadedRef.current = response.items.length;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (cancelled) return;
|
||||
console.error("[useDocumentSearch] Search failed:", err);
|
||||
setError(true);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [query, searchSpaceId, isActive, activeTypesKey]);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: activeTypesKey serializes activeTypes
|
||||
const loadMore = useCallback(async () => {
|
||||
if (loadingMore || !isActive || !hasMore) return;
|
||||
|
||||
setLoadingMore(true);
|
||||
try {
|
||||
const response = await documentsApiService.searchDocuments({
|
||||
queryParams: {
|
||||
search_space_id: searchSpaceId,
|
||||
skip: apiLoadedRef.current,
|
||||
page_size: SEARCH_SCROLL_SIZE,
|
||||
title: query.trim(),
|
||||
...(activeTypes.length > 0 && { document_types: activeTypes }),
|
||||
},
|
||||
});
|
||||
if (queryRef.current !== query) return;
|
||||
|
||||
setDocuments((prev) => [...prev, ...response.items.map(toDisplayDoc)]);
|
||||
setHasMore(response.has_more);
|
||||
apiLoadedRef.current += response.items.length;
|
||||
} catch (err) {
|
||||
console.error("[useDocumentSearch] Load more failed:", err);
|
||||
} finally {
|
||||
setLoadingMore(false);
|
||||
}
|
||||
}, [loadingMore, isActive, hasMore, searchSpaceId, query, activeTypesKey]);
|
||||
|
||||
const removeItems = useCallback((ids: number[]) => {
|
||||
const idSet = new Set(ids);
|
||||
setDocuments((prev) => prev.filter((item) => !idSet.has(item.id)));
|
||||
}, []);
|
||||
|
||||
return {
|
||||
documents,
|
||||
loading,
|
||||
loadingMore,
|
||||
hasMore,
|
||||
loadMore,
|
||||
error,
|
||||
removeItems,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue