This commit is contained in:
2025-11-09 13:03:44 +01:00
parent 4bc7357e41
commit bc33f68209
24 changed files with 5665 additions and 5261 deletions
+238
View File
@@ -0,0 +1,238 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
TAG_FILTER_UNTAGGED,
resolveDocumentRowKey,
isDocumentRowKey,
} from './appLayoutUtils';
const useDocumentsSearch = ({
api,
assetManager,
token,
selectedFolder,
navigate,
locationPathname,
isDocumentsRoute,
selectionHelpers,
searchIncludeDescendants,
documentsSortField,
documentsSortDirection,
notifyApiError,
setLoading,
setSearchIncludeDescendants,
}) => {
const [searchQuery, setSearchQuery] = useState('');
const [activeTagFilters, setActiveTagFilters] = useState([]);
const [activeCorrespondentFilters, setActiveCorrespondentFilters] = useState([]);
const [searchResults, setSearchResults] = useState(null);
const [searchLoading, setSearchLoading] = useState(false);
const toggleTagFilter = useCallback((tagId) => {
if (!tagId) return;
setActiveTagFilters((previous) => {
if (tagId === TAG_FILTER_UNTAGGED) {
return previous.includes(TAG_FILTER_UNTAGGED) ? [] : [TAG_FILTER_UNTAGGED];
}
const sanitized = previous.filter((id) => id !== TAG_FILTER_UNTAGGED);
if (sanitized.includes(tagId)) {
return sanitized.filter((id) => id !== tagId);
}
return sanitized.concat([tagId]);
});
}, []);
const toggleCorrespondentFilter = useCallback((correspondentId) => {
setActiveCorrespondentFilters((previous) => {
if (!correspondentId) {
return [];
}
return previous.includes(correspondentId) ? [] : [correspondentId];
});
}, []);
const isFilterActive = useMemo(
() =>
searchQuery.trim().length > 0
|| activeTagFilters.length > 0
|| activeCorrespondentFilters.length > 0,
[searchQuery, activeTagFilters, activeCorrespondentFilters],
);
const clearFilters = useCallback(() => {
setSearchQuery('');
setActiveTagFilters([]);
setActiveCorrespondentFilters([]);
setSearchLoading(false);
setSearchIncludeDescendants(true);
}, [
setSearchIncludeDescendants,
]);
const handleSearchChange = useCallback((value) => {
setSearchQuery(value);
}, []);
const handleSearchSubmit = useCallback(() => {
if (!navigate) return;
const targetFolder = selectedFolder && selectedFolder !== 'root' ? selectedFolder : 'root';
const targetPath = targetFolder === 'root' ? '/documents' : `/documents/folder/${targetFolder}`;
if (!isDocumentsRoute || locationPathname !== targetPath) {
navigate(targetPath, { replace: false });
}
}, [navigate, selectedFolder, isDocumentsRoute, locationPathname]);
useEffect(() => {
if (!token) return undefined;
if (!isFilterActive) {
setSearchResults(null);
setSearchLoading(false);
return undefined;
}
let cancelled = false;
let started = false;
setSearchLoading(true);
const debounce = setTimeout(async () => {
started = true;
setLoading(true);
try {
const params = {};
const trimmedQuery = searchQuery.trim();
if (trimmedQuery.length) {
params.query = trimmedQuery;
}
if (activeTagFilters.length) {
const onlyUntagged = activeTagFilters.length === 1
&& activeTagFilters[0] === TAG_FILTER_UNTAGGED;
if (onlyUntagged) {
params.tags = 'none';
} else {
const tagIds = activeTagFilters.filter((id) => id !== TAG_FILTER_UNTAGGED);
if (tagIds.length) {
params.tags = tagIds.join(',');
}
}
}
if (activeCorrespondentFilters.length) {
params.correspondents = activeCorrespondentFilters.join(',');
}
const folderIdentifier = selectedFolder === 'root' ? null : selectedFolder;
if (folderIdentifier) {
params.folder_id = folderIdentifier;
}
if (!searchIncludeDescendants) {
params.include_descendants = false;
}
if (documentsSortField) {
params.sort = documentsSortField;
}
if (documentsSortDirection) {
params.dir = documentsSortDirection;
}
const { data } = await api.get('/documents', { params });
if (cancelled) return;
const results = assetManager.hydrateDocuments(data || []);
setSearchResults(results);
if (!results.length) {
setSearchLoading(false);
selectionHelpers.setSelectedEntries([]);
selectionHelpers.setFocusedDocumentId(null);
selectionHelpers.selectionOrderRef.current = [];
selectionHelpers.setSelectionOrder([]);
selectionHelpers.selectionAnchorRef.current = null;
return;
}
const resultKeys = results
.map((doc) => resolveDocumentRowKey(doc.id))
.filter(Boolean);
let targetKey = null;
let nextSelectionKeys = [];
selectionHelpers.setSelectedEntries((previous) => {
const previousDocKeys = previous.filter(isDocumentRowKey);
const filtered = previousDocKeys.filter((key) => resultKeys.includes(key));
if (filtered.length) {
targetKey = filtered[filtered.length - 1];
nextSelectionKeys = filtered;
return filtered;
}
targetKey = null;
nextSelectionKeys = [];
return [];
});
selectionHelpers.selectionOrderRef.current = nextSelectionKeys;
selectionHelpers.setSelectionOrder(nextSelectionKeys);
selectionHelpers.setFocusedDocumentId((previous) => {
if (previous && resultKeys.includes(resolveDocumentRowKey(previous))) {
return previous;
}
return null;
});
selectionHelpers.selectionAnchorRef.current = targetKey;
} catch (error) {
if (cancelled) return;
notifyApiError(error, 'Search failed. Please try again.');
setSearchResults(null);
} finally {
if (!cancelled && started) {
setLoading(false);
setSearchLoading(false);
}
}
}, 300);
return () => {
cancelled = true;
clearTimeout(debounce);
if (started) {
setLoading(false);
setSearchLoading(false);
}
};
}, [
api,
token,
isFilterActive,
searchQuery,
activeTagFilters,
activeCorrespondentFilters,
searchIncludeDescendants,
documentsSortField,
documentsSortDirection,
selectedFolder,
notifyApiError,
assetManager,
selectionHelpers,
setLoading,
]);
return {
searchQuery,
setSearchQuery,
searchResults,
setSearchResults,
searchLoading,
setSearchLoading,
activeTagFilters,
setActiveTagFilters,
activeCorrespondentFilters,
setActiveCorrespondentFilters,
toggleTagFilter,
toggleCorrespondentFilter,
isFilterActive,
clearFilters,
handleSearchChange,
handleSearchSubmit,
};
};
export default useDocumentsSearch;