diff --git a/frontend/src/documents/data/useDocumentMutations.ts b/frontend/src/documents/data/useDocumentMutations.ts index c56628b..6ccbe5a 100644 --- a/frontend/src/documents/data/useDocumentMutations.ts +++ b/frontend/src/documents/data/useDocumentMutations.ts @@ -37,8 +37,6 @@ type EnsureFolderData = ( options?: { includeDocuments?: boolean }, ) => Promise; -type ApplySelectedFolder = (folderId: FolderId, contents?: FolderContents | null) => void; - type RemoveDocumentsFromCaches = (documentIds: DocumentId[]) => void; type CloseDocumentPreview = () => void; @@ -86,7 +84,6 @@ interface UseDocumentMutationsArgs { selectedFolder: FolderId; setSelectedFolder: Dispatch>; setDocuments: Dispatch>; - setFolderContents: Dispatch>>; setSearchResultIds: Dispatch>; setSelectedEntries: Dispatch>; setSelectionOrder: Dispatch>; @@ -99,7 +96,6 @@ interface UseDocumentMutationsArgs { notifyApiError: NotifyApiError; setStatusMessage: SetStatusMessage; mapDocumentCaches: MapDocumentCaches; - applySelectedFolder: ApplySelectedFolder; folderNodes: Map; setFolderNodes: Dispatch>>; removeDocumentsFromCaches: RemoveDocumentsFromCaches; @@ -159,7 +155,6 @@ const useDocumentMutations = ({ selectedFolder, setSelectedFolder, setDocuments, - setFolderContents, setSearchResultIds, setSelectedEntries, setSelectionOrder, @@ -172,7 +167,6 @@ const useDocumentMutations = ({ notifyApiError, setStatusMessage, mapDocumentCaches, - applySelectedFolder, folderNodes, setFolderNodes, removeDocumentsFromCaches, @@ -291,26 +285,8 @@ const useDocumentMutations = ({ return filtered.length === prev.length ? prev : filtered; }); setDocuments((prev) => prev.filter((doc) => !uniqueIdSet.has(doc.id as DocumentId))); - setFolderContents((prev: Map) => { - if (!prev.size) { - return prev; - } - let changed = false; - const next = new Map(prev); - movedDocs.forEach(({ id, sourceFolderId }) => { - const sourceKey = (sourceFolderId || 'root') as FolderId; - const entry = next.get(sourceKey); - if (!entry?.documents?.length) { - return; - } - const filteredDocs = entry.documents.filter((doc) => doc.id !== id); - if (filteredDocs.length !== entry.documents.length) { - changed = true; - next.set(sourceKey, { ...entry, documents: filteredDocs }); - } - }); - return changed ? next : prev; - }); + setDocuments((prev) => prev.filter((doc) => !uniqueIdSet.has(doc.id as DocumentId))); + // setFolderContents removed as we don't hold full cache anymore setSelectedEntries((prev) => pruneRow(prev)); setSelectionOrder((prev) => pruneRow(prev)); @@ -350,7 +326,6 @@ const useDocumentMutations = ({ selectedFolder, setSearchResultIds, setDocuments, - setFolderContents, setSelectedEntries, setSelectionOrder, selectionOrderRef, @@ -696,22 +671,17 @@ const useDocumentMutations = ({ }); } } - return next; }); - setFolderContents((prev: Map) => { - const next = new Map(prev); - next.delete(folderId); - return next; - }); + // setFolderContents removed if (selectedFolder === folderId) { const node = folderNodes.get(folderId); const parentId = node?.parentId || 'root'; setSelectedFolder(parentId); - const parentContents = await ensureFolderData(parentId); - applySelectedFolder(parentId, parentContents); + // ensureFolderData(parentId) will be called by useDocumentsWorkspace effect when selectedFolder changes } else if (selectedFolder !== 'root') { + // If deleted folder was not selected, just check if we need to refresh (maybe redundant) await ensureFolderData(selectedFolder); } @@ -734,9 +704,7 @@ const useDocumentMutations = ({ selectedFolder, folderNodes, setSelectedFolder, - applySelectedFolder, setFolderNodes, - setFolderContents, notifyApiError, setStatusMessage, ], diff --git a/frontend/src/documents/data/useDocumentsWorkspace.ts b/frontend/src/documents/data/useDocumentsWorkspace.ts index ca2864e..62b4534 100644 --- a/frontend/src/documents/data/useDocumentsWorkspace.ts +++ b/frontend/src/documents/data/useDocumentsWorkspace.ts @@ -18,7 +18,7 @@ import useApiError from '../../hooks/useApiError'; import TagManager from '../../lib/assets/TagManager'; import { useManagementModals } from '../../app/useManagementModals'; import { useAppDispatch, useAppState } from '../../lib/store/appState'; -import { fetchAsset } from '../../lib/api/apiClient'; +import { fetchAsset, listFolderContents } from '../../lib/api/apiClient'; import { useApi } from '../../lib/context/ApiContext'; import { useWorkspaceSelection } from '../../app/useWorkspaceSelection'; import { useEntryPointer as useEntryPointerCore } from '../features/selection/useEntryPointer'; @@ -34,7 +34,12 @@ import { createRootNode, mergeAssetIntoDocument, } from '../../app/workspaceUtils'; -import { createDocumentEntryKey, createFolderEntryKey } from '../../app/entryKey'; +import { + createDocumentEntryKey, + createFolderEntryKey, + isFolderEntry, + isDocumentEntry +} from '../../app/entryKey'; import useDocumentsSearch from '../../app/useDocumentsSearch'; import { useStatusToast, type ToastVariant } from '../../lib/context/StatusToastContext'; import useAuthManager from './useAuthManager'; @@ -281,27 +286,6 @@ const useDocumentsWorkspace = ({ configureSelectionEnvironment, } = selection; - const selectionHelpers = useMemo( - () => ({ - setSelectedEntries, - setSelectionOrder, - selectionOrderRef, - selectionAnchorRef, - setFocusedDocumentId, - focusedDocumentId, - selectionInitializedRef, - }), - [ - setSelectedEntries, - setSelectionOrder, - selectionOrderRef, - selectionAnchorRef, - setFocusedDocumentId, - focusedDocumentId, - selectionInitializedRef, - ], - ); - const [folderContents, setFolderContents] = useState>( () => new Map(), ); @@ -339,27 +323,102 @@ const useDocumentsWorkspace = ({ setFolderNodes, selectedFolder, setSelectedFolder, - currentFolder, - setCurrentFolder, - currentSubfolders, - setCurrentSubfolders, currentFolderName, folderOptions, folderLabelMap, - applySelectedFolder, - ensureFolderData, isInvalidFolderDrop, } = useFolderTree({ initialSelectedFolder: routeFolderId || 'root', - documentsSortFieldRef: activeSortFieldRef, - documentsSortDirectionRef: activeSortDirectionRef, - selectionHelpers, - setDocuments, - setFolderContents, - folderContentsRef, foldersManager, }); + const [currentFolder, setCurrentFolder] = useState(null); + const [currentSubfolders, setCurrentSubfolders] = useState([]); + + const ensureFolderData = useCallback( + async ( + folderId: FolderNodeId, + options: { includeDocuments?: boolean } = {} + ) => { + try { + const path = folderId === 'root' ? 'root' : folderId; + const includeDocuments = options.includeDocuments ?? true; + const params: Record = { + include_documents: includeDocuments, + sort: activeSortFieldRef.current, + dir: activeSortDirectionRef.current, + }; + + const data = await listFolderContents(path, params); + + // Only update UI state if we are fetching for the currently selected folder + if (folderId === selectedFolder) { + // Update documents state if included + if (includeDocuments) { + setDocuments((data.documents || []) as Document[]); + } + setCurrentSubfolders((data.subfolders || []) as any[]); + setCurrentFolder(data.folder || null); + + // Update selection state based on new documents + if (includeDocuments) { + const docs = (data.documents || []) as Document[]; + const subfolders = (data.subfolders || []) as any[]; + + const availableDocKeys = docs + .map((doc) => createDocumentEntryKey(doc?.id as Identifier)) + .filter(Boolean); + const availableDocKeySet = new Set(availableDocKeys); + const availableFolderKeys = new Set( + subfolders + .map((folder) => createFolderEntryKey(folder?.id as Identifier)) + .filter(Boolean), + ); + + setSelectedEntries((previous) => { + const previousFolderKeys = previous + .filter(isFolderEntry) + .filter((key) => availableFolderKeys.has(key)); + const previousDocKeys = previous.filter(isDocumentEntry); + const nextDocKeys = previousDocKeys.filter((key) => availableDocKeySet.has(key)); + const mergedSelection = [...previousFolderKeys, ...nextDocKeys]; + return mergedSelection; + }); + } + } + return data; // Return data for consumers (e.g. useDocumentMutations) + } catch (error) { + notifyApiError(error, 'Failed to fetch folder contents'); + throw error; + } + }, + [ + activeSortFieldRef, + activeSortDirectionRef, + setDocuments, + setCurrentFolder, + setSelectedEntries, + notifyApiError, + setCurrentSubfolders, + selectedFolder, + ] + ); + + // Reactively fetch documents when selectedFolder changes + useEffect(() => { + if (selectedFolder) { + ensureFolderData(selectedFolder); + } + }, [selectedFolder, ensureFolderData]); + + // Also re-fetch when sort changes + useEffect(() => { + if (selectedFolder && sortRefreshReadyRef?.current) { + ensureFolderData(selectedFolder); + } + }, [documentsSortField, documentsSortDirection, selectedFolder, ensureFolderData, sortRefreshReadyRef]); + + const { searchQuery, setSearchQuery, @@ -426,7 +485,7 @@ const useDocumentsWorkspace = ({ () => showingSearchResults ? [] - : currentSubfolders + : (currentSubfolders || []) .map((folder) => createFolderEntryKey(folder.id)) .filter(Boolean), [showingSearchResults, currentSubfolders], @@ -577,9 +636,10 @@ const useDocumentsWorkspace = ({ ); const refreshCurrentFolder = useCallback(async () => { - const contents = await ensureFolderData(selectedFolder); - applySelectedFolder(selectedFolder, contents); - }, [selectedFolder, ensureFolderData, applySelectedFolder]); + if (selectedFolder) { + await ensureFolderData(selectedFolder); + } + }, [selectedFolder, ensureFolderData]); const { handleBulkTagAddFromDetail, @@ -601,15 +661,16 @@ const useDocumentsWorkspace = ({ uploadQueue, clearUploadQueue, resetUploadsState, + handleFileSelection, } = useDocumentUploads({ token, selectedFolder, currentFolderName, ensureFolderData, refreshCurrentFolder, + shellRef, notifyApiError, setStatusMessage, - shellRef, }); const { @@ -618,15 +679,15 @@ const useDocumentsWorkspace = ({ handleFolderDragStart, handleFolderDragEnd, } = useDocumentDragHandlers({ + documentLookup, + setDraggedDocumentIds, + setDraggedFolderId, + documentsViewMode, selectedEntries, selectedDocumentIds, selectedFolderIds, applySelection, handleEntrySelection, - documentLookup, - setDraggedDocumentIds, - setDraggedFolderId, - documentsViewMode, }); useEffect(() => { @@ -649,7 +710,6 @@ const useDocumentsWorkspace = ({ const resetWorkspaceState = useCallback(() => { const rootNode = createRootNode(); setFolderNodes(new Map([[rootNode.id, rootNode]])); - setFolderContents(new Map()); setSelectedFolder('root'); setCurrentFolder(null); setCurrentSubfolders([]); @@ -687,7 +747,7 @@ const useDocumentsWorkspace = ({ setSelectedEntries, setSelectionOrder, setFolderNodes, - setFolderContents, + // setFolderContents, setSelectedFolder, setCurrentFolder, setCurrentSubfolders, @@ -730,35 +790,11 @@ const useDocumentsWorkspace = ({ return filtered.length === prev.length ? prev : filtered; }); - setFolderContents((prev: Map) => { - if (!prev.size) { - return prev; - } - let changed = false; - const next = new Map(); - prev.forEach((contents, key) => { - const docs = Array.isArray(contents?.documents) ? contents.documents : null; - if (!docs || docs.length === 0) { - next.set(key, contents); - return; - } - const filtered = docs.filter((doc) => !idSet.has(doc.id)); - if (filtered.length !== docs.length) { - changed = true; - next.set(key, { ...contents, documents: filtered }); - } else { - next.set(key, contents); - } - }); - return changed ? next : prev; - }); - removeDocumentsFromLookup(Array.from(idSet)); }, [ setDocuments, setSearchResultIds, - setFolderContents, removeDocumentsFromLookup, ], ); @@ -780,7 +816,6 @@ const useDocumentsWorkspace = ({ selectedFolder, setSelectedFolder, setDocuments, - setFolderContents, setSearchResultIds, setSelectedEntries, setSelectionOrder, @@ -793,7 +828,6 @@ const useDocumentsWorkspace = ({ notifyApiError, setStatusMessage, mapDocumentCaches, - applySelectedFolder, folderNodes, setFolderNodes, removeDocumentsFromCaches, @@ -822,14 +856,9 @@ const useDocumentsWorkspace = ({ setFolderNodes, selectedFolder, setSelectedFolder, - ensureFolderData, - applySelectedFolder, notifyApiError, setStatusMessage, - setFolderContents, setCurrentFolder, - setSearchResultIds, - isFilterActive, navigate, handleFileDrop, moveDocumentsToFolder, @@ -883,8 +912,8 @@ const useDocumentsWorkspace = ({ return; } - const hasData = folderContents.has(targetParam); - if (targetParam !== selectedFolder || !hasData) { + // Checking cache (folderContents) is removed, now we rely on selectedFolder effect to fetch. + if (targetParam !== selectedFolder) { selectFolder(targetParam, { immediate: true }); } }, [ @@ -893,7 +922,6 @@ const useDocumentsWorkspace = ({ routeFolderId, routeDocumentId, selectedFolder, - folderContents, isFilterActive, selectFolder, ]); @@ -1302,6 +1330,7 @@ const useDocumentsWorkspace = ({ tenants: tenantOptions, tenantOptions, handleTenantSelect, + handleFileSelection, }; // hook callers handle rendering / routing diff --git a/frontend/src/documents/features/folders/useFolderTree.ts b/frontend/src/documents/features/folders/useFolderTree.ts index 4cdc4e6..77d953a 100644 --- a/frontend/src/documents/features/folders/useFolderTree.ts +++ b/frontend/src/documents/features/folders/useFolderTree.ts @@ -1,51 +1,11 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { createRootNode, DEFAULT_FOLDER_NAME, flattenFolderTree } from '../../../app/workspaceUtils'; -import { listFolderContents } from '../../../lib/api/apiClient'; -import { - getEntryId, - isDocumentEntry, - isFolderEntry, - createDocumentEntryKey, - createFolderEntryKey, -} from '../../../app/entryKey'; -import type { FolderNodeId, Identifier, FolderId } from '../../../types/identifiers'; -import type { Document, Folder, FolderContents } from '../../../types/documents'; +import type { FolderNodeId, FolderId } from '../../../types/identifiers'; +import type { FolderNode } from '../../../types/documents'; import type FoldersManager from '../../FoldersManager'; -interface FolderContentsEntry extends FolderContents { - __includesDocuments?: boolean; - __sortField?: string | null; - __sortDirection?: string | null; - [key: string]: unknown; -} - -interface FolderTreeNode extends Folder { - id: FolderNodeId; - children: FolderNodeId[]; - expanded?: boolean; - loaded?: boolean; - hasChildren?: boolean; - parentId?: FolderNodeId; -} - -interface SelectionHelpers { - focusedDocumentId: Identifier | null; - setFocusedDocumentId: Dispatch>; - setSelectedEntries: Dispatch>; - setSelectionOrder: Dispatch>; - selectionOrderRef: MutableRefObject; - selectionAnchorRef: MutableRefObject; -} - interface UseFolderTreeOptions { initialSelectedFolder?: FolderNodeId; - documentsSortFieldRef: MutableRefObject; - documentsSortDirectionRef: MutableRefObject; - selectionHelpers: SelectionHelpers; - setDocuments: Dispatch>; - setFolderContents: Dispatch>>; - folderContentsRef: MutableRefObject>; foldersManager?: FoldersManager; } @@ -56,16 +16,10 @@ interface FolderOption { const useFolderTree = ({ initialSelectedFolder = 'root', - documentsSortFieldRef, - documentsSortDirectionRef, - selectionHelpers, - setDocuments, - setFolderContents, - folderContentsRef, foldersManager, }: UseFolderTreeOptions) => { - const [folderNodes, setFolderNodes] = useState>(() => { - const rootNode = createRootNode() as FolderTreeNode; + const [folderNodes, setFolderNodes] = useState>(() => { + const rootNode = createRootNode() as FolderNode; return new Map([[rootNode.id, rootNode]]); }); @@ -110,7 +64,7 @@ const useFolderTree = ({ const root = next.get('root'); if (root) { next.set('root', { - ...(root as FolderTreeNode), + ...(root as FolderNode), children: rootChildren, hasChildren: rootChildren.length > 0, loaded: true, @@ -126,158 +80,6 @@ const useFolderTree = ({ }, [foldersManager]); const [selectedFolder, setSelectedFolder] = useState(initialSelectedFolder || 'root'); - const [currentFolder, setCurrentFolder] = useState(null); - const [currentSubfolders, setCurrentSubfolders] = useState([]); - - const { - focusedDocumentId, - setFocusedDocumentId, - setSelectedEntries, - setSelectionOrder, - selectionOrderRef, - selectionAnchorRef, - } = selectionHelpers; - - const focusedDocumentIdRef = useRef(focusedDocumentId); - useEffect(() => { - focusedDocumentIdRef.current = focusedDocumentId; - }, [focusedDocumentId]); - - const applySelectedFolder = useCallback( - (folderId: FolderNodeId, contents?: FolderContentsEntry | null) => { - const subfolders = Array.isArray(contents?.subfolders) ? contents.subfolders : []; - const docs = Array.isArray(contents?.documents) ? contents.documents : []; - const folderInfo = contents?.folder ?? null; - - setCurrentSubfolders(subfolders); - setDocuments(docs); - setCurrentFolder(folderInfo); - - const availableDocKeys = docs - .map((doc) => createDocumentEntryKey((doc as Document)?.id as Identifier)) - .filter(Boolean); - const availableDocKeySet = new Set(availableDocKeys); - const availableFolderKeys = new Set( - subfolders - .map((folder) => createFolderEntryKey(folder?.id as Identifier)) - .filter(Boolean), - ); - - let nextDocKeys: string[] = []; - let mergedSelection: string[] = []; - - setSelectedEntries((previous) => { - const previousFolderKeys = previous - .filter(isFolderEntry) - .filter((key) => availableFolderKeys.has(key)); - const previousDocKeys = previous.filter(isDocumentEntry); - nextDocKeys = previousDocKeys.filter((key) => availableDocKeySet.has(key)); - mergedSelection = [...previousFolderKeys, ...nextDocKeys]; - return mergedSelection; - }); - - const nextFocus = (() => { - const currentFocusedId = focusedDocumentIdRef.current; - if (currentFocusedId) { - const currentFocusedKey = createDocumentEntryKey(currentFocusedId); - if (currentFocusedKey && availableDocKeySet.has(currentFocusedKey)) { - return currentFocusedId; - } - } - if (nextDocKeys.length) { - const lastDocKey = nextDocKeys[nextDocKeys.length - 1]; - return getEntryId(lastDocKey) || null; - } - return null; - })(); - - setFocusedDocumentId(nextFocus); - const nextAnchor = mergedSelection.length ? mergedSelection[mergedSelection.length - 1] : null; - selectionAnchorRef.current = nextAnchor; - selectionOrderRef.current = mergedSelection; - setSelectionOrder(mergedSelection); - }, - [ - selectionAnchorRef, - selectionOrderRef, - setDocuments, - setFocusedDocumentId, - setSelectedEntries, - setSelectionOrder, - ], - ); - - const ensureFolderData = useCallback( - async ( - folderId: FolderNodeId, - { - includeDocuments = true, - sortField = documentsSortFieldRef.current, - sortDirection = documentsSortDirectionRef.current, - }: { - includeDocuments?: boolean; - sortField?: string; - sortDirection?: string; - } = {}, - ): Promise => { - const cached = folderContentsRef.current.get(folderId); - const cachedSortField = cached?.__sortField || documentsSortFieldRef.current; - const cachedSortDirection = cached?.__sortDirection || documentsSortDirectionRef.current; - const cachedSortMatches = cachedSortField === sortField && cachedSortDirection === sortDirection; - - if (cached) { - const includesDocuments = Boolean(cached.__includesDocuments); - if (!includeDocuments || (includesDocuments && cachedSortMatches)) { - return cached; - } - } - - // We use listFolderContents purely for Documents now, assuming Tree is authoritative for subfolders. - const path = folderId === 'root' ? 'root' : folderId; - const params: Record = {}; - if (!includeDocuments) { - params.include_documents = false; - } else { - params.sort = sortField; - params.dir = sortDirection; - } - - const data = await listFolderContents(path, params); - - const enriched = { - ...data, - documents: (data.documents || []) as Document[], - __includesDocuments: includeDocuments, - __sortField: includeDocuments ? sortField : cachedSortField, - __sortDirection: includeDocuments ? sortDirection : cachedSortDirection, - }; - - setFolderContents((prev: Map) => { - const next = new Map(prev); - if (includeDocuments) { - next.set(folderId, enriched); - } else { - const existing = next.get(folderId); - next.set(folderId, { - ...enriched, - documents: existing?.__includesDocuments ? existing.documents : (enriched.documents as Document[]), - __includesDocuments: existing?.__includesDocuments || false, - }); - } - folderContentsRef.current = next; - return next; - }); - - return enriched; - }, - [ - documentsSortDirectionRef, - documentsSortFieldRef, - setFolderContents, - folderContentsRef, - // Removed foldersManager dependency as we don't call it here anymore - ], - ); const isInvalidFolderDrop = useCallback( (sourceId: FolderNodeId | null, targetId: FolderNodeId | null) => { @@ -298,7 +100,7 @@ const useFolderTree = ({ } const node = folderNodes.get(current); if (!node) break; - current = node.parentId ?? 'root'; + current = (node.parentId ?? 'root') as FolderNodeId; } return false; }, @@ -306,18 +108,16 @@ const useFolderTree = ({ ); const resetFolderTreeState = useCallback(() => { - const rootNode = createRootNode() as FolderTreeNode; - setFolderNodes(new Map([[rootNode.id, rootNode]])); - setFolderContents(new Map()); + const rootNode = createRootNode() as FolderNode; + setFolderNodes(new Map([[rootNode.id, rootNode]])); setSelectedFolder('root'); - setCurrentFolder(null); - setCurrentSubfolders([]); - }, [setFolderContents]); + }, []); const currentFolderName = useMemo(() => { - if (selectedFolder === 'root' || !currentFolder) return DEFAULT_FOLDER_NAME; - return currentFolder.name; - }, [selectedFolder, currentFolder]); + if (selectedFolder === 'root') return DEFAULT_FOLDER_NAME; + const node = folderNodes.get(selectedFolder); + return node?.name || DEFAULT_FOLDER_NAME; + }, [selectedFolder, folderNodes]); const folderOptions: FolderOption[] = useMemo(() => { const cache = new Map(); @@ -376,15 +176,9 @@ const useFolderTree = ({ setFolderNodes, selectedFolder, setSelectedFolder, - currentFolder, - setCurrentFolder, - currentSubfolders, - setCurrentSubfolders, currentFolderName, folderOptions, folderLabelMap, - applySelectedFolder, - ensureFolderData, isInvalidFolderDrop, resetFolderTreeState, }; diff --git a/frontend/src/documents/features/folders/useFolderTreeActions.ts b/frontend/src/documents/features/folders/useFolderTreeActions.ts index de82706..360dd15 100644 --- a/frontend/src/documents/features/folders/useFolderTreeActions.ts +++ b/frontend/src/documents/features/folders/useFolderTreeActions.ts @@ -8,31 +8,10 @@ import { renameFolder as renameFolderRequest, } from '../../../lib/api/apiClient'; import type { FolderId } from '../../../types/identifiers'; -import type { MessageOptions } from '../../../types/documents'; +import type { MessageOptions, FolderNode } from '../../../types/documents'; type FolderKey = FolderId | 'root'; -interface FolderNode { - id: FolderKey; - name?: string; - parentId?: FolderKey | null; - children: FolderId[]; - expanded?: boolean; - loaded?: boolean; - hasChildren?: boolean; -} - -interface FolderContentsState { - folder?: { id: FolderKey; name?: string }; - subfolders?: any[]; - documents?: any[]; - [key: string]: unknown; -} - -interface EnsureFolderOptions { - includeDocuments?: boolean; -} - interface LoadFolderOptions { preserveSearch?: boolean; } @@ -55,16 +34,9 @@ interface UseFolderTreeActionsOptions { setFolderNodes: (updater: (prev: Map) => Map) => void; selectedFolder: FolderKey; setSelectedFolder: (folderId: FolderKey) => void; - ensureFolderData: (folderId: FolderKey, options?: EnsureFolderOptions) => Promise; - applySelectedFolder: (folderId: FolderKey, contents: any) => void; notifyApiError: (error: unknown, message?: string) => void; setStatusMessage: (message: string, level?: string) => void; - setFolderContents: ( - updater: (prev: Map) => Map, - ) => void; setCurrentFolder: (updater: (prev: any) => any) => void; - setSearchResultIds: (value: any) => void; - isFilterActive: boolean; navigate?: (path: string, options?: { replace?: boolean }) => void; handleFileDrop: (dataTransfer: DataTransfer, folderId: FolderKey) => Promise | void; moveDocumentsToFolder: (docIds: FolderId[], folderId: FolderKey) => Promise; @@ -82,14 +54,9 @@ const useFolderTreeActions = ({ setFolderNodes, selectedFolder, setSelectedFolder, - ensureFolderData, - applySelectedFolder, notifyApiError, setStatusMessage, - setFolderContents, setCurrentFolder, - setSearchResultIds, - isFilterActive, navigate, handleFileDrop, moveDocumentsToFolder, @@ -165,15 +132,7 @@ const useFolderTreeActions = ({ return next; }); - const refreshTargets = new Set([previousParentKey, targetKey]); - await Promise.all( - Array.from(refreshTargets).map((key) => - ensureFolderData(key), - ), - ); - if (selectedFolder === folderId) { - await ensureFolderData(folderId); setSelectedFolder(folderId); } @@ -181,17 +140,9 @@ const useFolderTreeActions = ({ } catch (error) { const message = error.response?.data?.error || 'Failed to move folder.'; notifyApiError(error, message); - - const refreshTargets = new Set([previousParentKey, targetKey]); - await Promise.all( - Array.from(refreshTargets).map((key) => - ensureFolderData(key), - ), - ); } }, [ - ensureFolderData, folderNodes, notifyApiError, selectedFolder, @@ -202,35 +153,12 @@ const useFolderTreeActions = ({ ); const loadFolder = useCallback( - async (folderId: FolderKey | null, { preserveSearch = false }: LoadFolderOptions = {}) => { + async (folderId: FolderKey | null, { preserveSearch: _preserveSearch = false }: LoadFolderOptions = {}) => { const targetId = folderId || 'root'; setSelectedFolder(targetId); - try { - const contents = await ensureFolderData(targetId); - if (targetId !== 'root') { - try { - await ensureFolderData('root', { - includeDocuments: false, - }); - } catch (error) { - console.warn('Failed to refresh root folder tree', error); - } - } - applySelectedFolder(targetId, contents); - if (!preserveSearch) { - setSearchResultIds(null); - } - } catch (error) { - notifyApiError(error, 'Failed to load folder contents.'); - } + // Data fetching is now reactive in the parent component based on selectedFolder }, - [ - applySelectedFolder, - ensureFolderData, - notifyApiError, - setSearchResultIds, - setSelectedFolder, - ], + [setSelectedFolder], ); const selectFolder = useCallback( @@ -238,8 +166,7 @@ const useFolderTreeActions = ({ const targetId = folderId && folderId !== 'root' ? folderId : 'root'; if (!navigate || immediate) { - await loadFolder(targetId, { preserveSearch: isFilterActive }); - setSelectedFolder(targetId); + await loadFolder(targetId); return; } @@ -247,10 +174,8 @@ const useFolderTreeActions = ({ navigate(path, { replace }); }, [ - isFilterActive, loadFolder, navigate, - setSelectedFolder, ], ); @@ -277,19 +202,6 @@ const useFolderTreeActions = ({ return next; }); - setFolderContents((prev) => { - if (!prev.has(folderId)) { - return prev; - } - const next = new Map(prev); - const existing = next.get(folderId) || {}; - const folderInfo = existing.folder - ? { ...existing.folder, name: trimmed } - : { id: folderId, name: trimmed }; - next.set(folderId, { ...existing, folder: folderInfo }); - return next; - }); - setCurrentFolder((prev) => (prev?.id === folderId ? { ...prev, name: trimmed } : prev)); setStatusMessage('Folder renamed.', 'success'); return true; @@ -302,7 +214,6 @@ const useFolderTreeActions = ({ [ notifyApiError, setCurrentFolder, - setFolderContents, setFolderNodes, setStatusMessage, token, @@ -361,10 +272,6 @@ const useFolderTreeActions = ({ return next; }); - // Refresh the parent folder to ensure consistency - const refreshTarget = targetParentId || 'root'; - await ensureFolderData(refreshTarget); - await selectFolder(folderData.id, { immediate: true }); succeeded = true; return true; @@ -380,7 +287,6 @@ const useFolderTreeActions = ({ } }, [ - ensureFolderData, notifyApiError, selectFolder, selectedFolder, @@ -407,16 +313,6 @@ const useFolderTreeActions = ({ } try { - const contents = await ensureFolderData(folderId); - const hasChildren = (contents.subfolders || []).length > 0; - const hasDocs = (contents.documents || []).length > 0; - if (hasChildren || hasDocs) { - if (showMessage) { - setStatusMessage('Folder must be empty before it can be deleted.', 'error'); - } - return false; - } - await deleteFolder(folderId); setFolderNodes((prev) => { @@ -438,20 +334,10 @@ const useFolderTreeActions = ({ return next; }); - setFolderContents((prev) => { - const next = new Map(prev); - next.delete(folderId); - return next; - }); - if (selectedFolder === folderId) { const node = folderNodes.get(folderId); const parentId = node?.parentId || 'root'; setSelectedFolder(parentId); - const parentContents = await ensureFolderData(parentId); - applySelectedFolder(parentId, parentContents); - } else if (selectedFolder !== 'root') { - await ensureFolderData(selectedFolder); } if (showMessage) { @@ -469,12 +355,9 @@ const useFolderTreeActions = ({ }, [ token, - applySelectedFolder, - ensureFolderData, folderNodes, notifyApiError, selectedFolder, - setFolderContents, setFolderNodes, setSelectedFolder, setStatusMessage, diff --git a/frontend/src/types/documents.ts b/frontend/src/types/documents.ts index e4623bc..83001c1 100644 --- a/frontend/src/types/documents.ts +++ b/frontend/src/types/documents.ts @@ -14,13 +14,6 @@ export interface DocumentCorrespondent { count?: number; } -export interface FolderContents { - folder: Folder | null; - documents: Document[]; - subfolders: Folder[]; - [key: string]: unknown; -} - /** * A tag entity as returned by the API (includes usage_count). * Use DocumentTag for the embedded version on documents. @@ -111,3 +104,17 @@ export type DocumentsListEntry = FolderEntry | DocumentEntry; import type { MouseEvent } from 'react'; export type FolderEventHandler = (folder: Folder, event: MouseEvent) => void; + +/** + * Represents a folder node in the UI tree structure (flat map representation). + */ +export interface FolderNode { + id: Identifier | 'root'; + name?: string; + parentId?: Identifier | 'root' | null; + children: (Identifier | 'root')[]; + expanded?: boolean; + loaded?: boolean; + hasChildren?: boolean; + [key: string]: unknown; +}