diff --git a/frontend/src/app/useDocumentPreview.ts b/frontend/src/app/useDocumentPreview.ts index 851fd92..98f1307 100644 --- a/frontend/src/app/useDocumentPreview.ts +++ b/frontend/src/app/useDocumentPreview.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef, useMemo } from 'react'; import type { Dispatch, MutableRefObject, @@ -36,6 +36,8 @@ interface UseDocumentPreviewResult { openDocumentPreview: (documentId: DocumentId, options?: { replace?: boolean }) => void; closeDocumentPreview: (folderId?: FolderId) => void; resetPreviewState: () => void; + previewWorkspaceDocument: Document | null; + previewActive: boolean; } const useDocumentPreview = ({ @@ -127,11 +129,22 @@ const useDocumentPreview = ({ }; }, [routeDocumentId, ensurePreviewData, notifyApiError, closeDocumentPreview]); + const previewWorkspaceDocument = useMemo(() => { + if (!routeDocumentId) { + return null; + } + return documentsManager.getById(routeDocumentId); + }, [routeDocumentId, documentsManager]); + + const previewActive = Boolean(routeDocumentId && previewWorkspaceDocument); + return { ensurePreviewData, openDocumentPreview, closeDocumentPreview, resetPreviewState, + previewWorkspaceDocument, + previewActive, }; }; diff --git a/frontend/src/app/useManagementModals.tsx b/frontend/src/app/useManagementModals.tsx index 76b0126..b893ea6 100644 --- a/frontend/src/app/useManagementModals.tsx +++ b/frontend/src/app/useManagementModals.tsx @@ -19,6 +19,7 @@ interface UseManagementModalsArgs { onTagDelete?: (...args: any[]) => void | Promise; correspondents?: Correspondent[]; correspondentLookupById?: Map | null; + correspondentLookupByName?: Map | null; refreshCorrespondents?: () => void | Promise; onCorrespondentCreate?: (...args: any[]) => void | Promise; onCorrespondentUpdate?: (...args: any[]) => void | Promise; diff --git a/frontend/src/documents/data/useCorrespondents.ts b/frontend/src/documents/data/useCorrespondents.ts index fdd0806..e8e6908 100644 --- a/frontend/src/documents/data/useCorrespondents.ts +++ b/frontend/src/documents/data/useCorrespondents.ts @@ -1,4 +1,4 @@ -import { useCallback, useSyncExternalStore } from 'react'; +import { useCallback, useSyncExternalStore, useMemo } from 'react'; import { useStatusToast } from '../../lib/context/StatusToastContext'; import type { Correspondent } from '../../types/documents'; import type { Identifier } from '../../types/identifiers'; @@ -114,9 +114,20 @@ const useCorrespondents = ({ [documentsManager, notifyApiError, correspondentManager, showToast], ); + const correspondentLookupByName = useMemo(() => { + const map = new Map(); + for (const correspondent of correspondents) { + if (correspondent.name) { + map.set(correspondent.name.toLowerCase(), correspondent); + } + } + return map; + }, [correspondents]); + return { correspondents, correspondentLookupById: correspondentsSnapshot, + correspondentLookupByName, refreshCorrespondents, handleCorrespondentCreate, handleCorrespondentUpdate, diff --git a/frontend/src/documents/data/useDocumentCorrespondentMutations.ts b/frontend/src/documents/data/useDocumentCorrespondentMutations.ts index c056af0..e32a5ac 100644 --- a/frontend/src/documents/data/useDocumentCorrespondentMutations.ts +++ b/frontend/src/documents/data/useDocumentCorrespondentMutations.ts @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import { useStatusToast } from '../../lib/context/StatusToastContext'; import type { Identifier } from '../../types/identifiers'; import type { Correspondent } from '../../types/documents'; @@ -21,22 +21,12 @@ const useDocumentCorrespondentMutations = ({ const notifyApiError = useNotifyApiError(); const { - correspondents, correspondentManager, + correspondentLookupByName, } = correspondentsState; const { documentsManager } = documentsState; - const correspondentLookupByName = useMemo(() => { - const map = new Map(); - correspondents.forEach((correspondent) => { - if (correspondent?.name) { - map.set(correspondent.name.toLowerCase(), correspondent); - } - }); - return map; - }, [correspondents]); - const handleDocumentCorrespondentAttach = useCallback( async ( { diff --git a/frontend/src/documents/data/useDocumentMutations.ts b/frontend/src/documents/data/useDocumentMutations.ts index a13c65c..e0b5d07 100644 --- a/frontend/src/documents/data/useDocumentMutations.ts +++ b/frontend/src/documents/data/useDocumentMutations.ts @@ -17,7 +17,6 @@ import type { SelectionState, TagsState, CorrespondentsState, - ActionsState, } from '../types/workspaceTypes'; import type { Tag, Correspondent } from '../../types/documents'; import useDocumentCorrespondentMutations from './useDocumentCorrespondentMutations'; @@ -36,7 +35,7 @@ interface UseDocumentMutationsArgs { selectionState: SelectionState; tagsState: TagsState; correspondentsState: CorrespondentsState; - actions: ActionsState; + closeDocumentPreview: () => void; previewDocumentId?: DocumentId | null; } @@ -68,7 +67,6 @@ interface UseDocumentMutationsResult { handleDocumentCorrespondentAttach: (args: { documentId: DocumentId; correspondentId: DocumentId; correspondent?: Correspondent | Partial | null }) => Promise; handleDocumentCorrespondentDetach: (args: { documentId: DocumentId; correspondentId: DocumentId }) => Promise; handleDocumentCorrespondentAdd: (args: { document: { id?: string }; name?: string; input?: HTMLInputElement | null; option?: Correspondent | Partial | string | null }) => Promise; - correspondentLookupByName: Map; } const useDocumentMutations = ({ @@ -77,7 +75,7 @@ const useDocumentMutations = ({ selectionState, tagsState, correspondentsState, - actions, + closeDocumentPreview, previewDocumentId, }: UseDocumentMutationsArgs): UseDocumentMutationsResult => { const { showToast } = useStatusToast(); @@ -102,7 +100,6 @@ const useDocumentMutations = ({ handleDocumentCorrespondentAttach, handleDocumentCorrespondentDetach, handleDocumentCorrespondentAdd, - correspondentLookupByName, } = useDocumentCorrespondentMutations({ correspondentsState, documentsState: { documentsManager: documentsState.documentsManager }, @@ -115,13 +112,13 @@ const useDocumentMutations = ({ showToast('Analysis queued.', 'info'); // Close preview if it's the current one to allow refresh? if (previewDocumentId === documentId) { - actions.closeDocumentPreview(); + closeDocumentPreview(); } } catch (error) { notifyApiError(error, 'Failed to queue analysis.'); } }, - [actions, notifyApiError, previewDocumentId, showToast], + [closeDocumentPreview, notifyApiError, previewDocumentId, showToast], ); const handleDocumentsDelete = useCallback( @@ -240,7 +237,6 @@ const useDocumentMutations = ({ handleDocumentCorrespondentAttach, handleDocumentCorrespondentDetach, handleDocumentCorrespondentAdd, - correspondentLookupByName, }; }; diff --git a/frontend/src/documents/data/useDocumentsWorkspace.ts b/frontend/src/documents/data/useDocumentsWorkspace.ts index 0a22f43..340c65a 100644 --- a/frontend/src/documents/data/useDocumentsWorkspace.ts +++ b/frontend/src/documents/data/useDocumentsWorkspace.ts @@ -273,6 +273,13 @@ const useDocumentsWorkspace = ({ foldersManager, }; + const resolveFolderPath = useCallback( + (folderId) => { + return resolveBreadcrumbs(folderId || 'root', folderNodes as any); + }, + [folderNodes], + ); + const [currentSubfolders, setCurrentSubfolders] = useState>([]); const foldersSnapshot = useSyncExternalStore( @@ -384,18 +391,7 @@ const useDocumentsWorkspace = ({ } }, [selectedFolder, documentsSortField, documentsSortDirection, fetchFolderData, updateViewState, notifyApiError]); - const { - searchQuery, - setSearchQuery, - searchResultIds, - setSearchResultIds, - searchLoading, - activeTagFilters, - setActiveTagFilters, - activeCorrespondentFilters, - setActiveCorrespondentFilters, - documentsFilterValue, - } = useDocumentsSearch({ + const documentsSearch = useDocumentsSearch({ api: apiClient, selectedFolder, locationPathname: location.pathname, @@ -407,8 +403,8 @@ const useDocumentsWorkspace = ({ documentsManager, }); - const documentsFilter = documentsFilterValue; - const showingSearchResults = searchResultIds !== null; + const documentsFilter = documentsSearch.documentsFilterValue; + const showingSearchResults = documentsSearch.searchResultIds !== null; // Live Filter: Ensure we only show documents that actually belong to the current folder. // Since 'documents' is reactive, if a document is moved, its folder_id updates immediately. @@ -431,7 +427,7 @@ const useDocumentsWorkspace = ({ } = useWorkspaceViewData({ documents: liveFilteredDocuments, documentLookup, - searchResultIds, + searchResultIds: documentsSearch.searchResultIds, showingSearchResults, currentSubfolders: visibleSubfolders, }); @@ -440,6 +436,8 @@ const useDocumentsWorkspace = ({ openDocumentPreview, closeDocumentPreview, resetPreviewState, + previewWorkspaceDocument, + previewActive, } = useDocumentPreview({ routeDocumentId: previewDocumentId, documentsManager, @@ -471,7 +469,7 @@ const useDocumentsWorkspace = ({ useWorkspaceSelectionSync({ showingSearchResults, - searchQuery, + searchQuery: documentsSearch.searchQuery, setSelectedEntries, setSelectionOrder, selectionOrderRef, @@ -486,7 +484,7 @@ const useDocumentsWorkspace = ({ const tagsState = useTags({ tenantIdRef, tagManager, - setActiveTagFilters, + setActiveTagFilters: documentsSearch.setActiveTagFilters, documentsManager, }); @@ -584,10 +582,10 @@ const useDocumentsWorkspace = ({ selectionAnchorRef.current = null; setDraggedDocumentIds([]); setDraggedFolderId(null); - setSearchResultIds(null); - setSearchQuery(''); - setActiveTagFilters([]); - setActiveCorrespondentFilters([]); + documentsSearch.setSearchResultIds(null); + documentsSearch.setSearchQuery(''); + documentsSearch.setActiveTagFilters([]); + documentsSearch.setActiveCorrespondentFilters([]); setActivePreviewId(null); detailPanelControlRef.current.close(); assetManager.reset(); @@ -612,10 +610,7 @@ const useDocumentsWorkspace = ({ setDocuments, setDraggedDocumentIds, setDraggedFolderId, - setSearchResultIds, - setSearchQuery, - setActiveTagFilters, - setActiveCorrespondentFilters, + documentsSearch, setActivePreviewId, resetPreviewState, upload, @@ -631,16 +626,12 @@ const useDocumentsWorkspace = ({ const documentsState = { documentLookup, setDocuments, - setSearchResultIds, + setSearchResultIds: documentsSearch.setSearchResultIds, documentsManager, extractDocumentFromResponse, ingestDocuments: (docs: unknown[]) => documentsManager.ingest(docs), }; - const actionsState = { - closeDocumentPreview, - }; - const documentMutationsResult = useDocumentMutations({ documentsState, folderState, @@ -653,13 +644,12 @@ const useDocumentsWorkspace = ({ ...correspondentsState, correspondentManager, }, - actions: actionsState, + closeDocumentPreview, previewDocumentId, }); const { moveDocumentsToFolder, - handleThumbnailRegeneration, handleDocumentsDelete, handleDocumentTagAdd, handleDocumentTagAttach, @@ -669,7 +659,6 @@ const useDocumentsWorkspace = ({ handleDocumentCorrespondentAttach, handleDocumentCorrespondentDetach, handleDocumentCorrespondentAdd, - correspondentLookupByName, } = documentMutationsResult; const mutations = { @@ -802,7 +791,7 @@ const useDocumentsWorkspace = ({ handleDeleteSelection, } = useBulkDocumentActions({ resolveTargetDocumentIds, - correspondentLookupByName, + correspondentLookupByName: correspondentsState.correspondentLookupByName, handleCorrespondentCreate: correspondentsState.handleCorrespondentCreate, selectedDocumentIds, selectedFolderIds, @@ -874,7 +863,8 @@ const useDocumentsWorkspace = ({ onTagDelete: async (tagId: string) => { await tagsState.handleTagDelete(tagId); }, correspondents: correspondentsState.correspondents, correspondentLookupById: correspondentsState.correspondentLookupById, - refreshCorrespondents: correspondentsState.refreshCorrespondents, + correspondentLookupByName: correspondentsState.correspondentLookupByName, + refreshCorrespondents, onCorrespondentCreate: correspondentsState.handleCorrespondentCreate, onCorrespondentUpdate: correspondentsState.handleCorrespondentUpdate, onCorrespondentDelete: correspondentsState.handleCorrespondentDelete, @@ -903,21 +893,11 @@ const useDocumentsWorkspace = ({ return () => window.removeEventListener('keydown', handleKeyDown); }, [settingsOpen]); - const { - detailPanelProps, - detailPanelOpen, - openDetailPanel, - previewActive, - previewWorkspaceDocument, - resolveFolderPath, - } = useDetailWorkspace({ - documents: viewDocuments, + const detailPanel = useDetailWorkspace({ documentLookup, folderNodes, detailPanelControlRef, detailFolderFetchRef, - previewDocumentId, - activePreviewId, openDocumentPreview: openDocumentPreviewForDetail, handleDocumentTitleUpdate, handleDocumentIssuedUpdate, @@ -932,6 +912,7 @@ const useDocumentsWorkspace = ({ tags: tagsState.tags, tagLookupById: tagsState.tagLookupById, correspondentLookupById: correspondentsState.correspondentLookupById, + resolveFolderPath, }); const handleEntryPointerCore = useEntryPointerCore({ @@ -985,7 +966,7 @@ const useDocumentsWorkspace = ({ const tags = { ...tagsState, tagManager, - activeTagFilters, + activeTagFilters: documentsSearch.activeTagFilters, // Add derived/action handlers that were previously in tagsContext handleDocumentTagAttach, handleDocumentTagDetach, @@ -996,7 +977,7 @@ const useDocumentsWorkspace = ({ const correspondents = { ...correspondentsState, - activeCorrespondentFilters, + activeCorrespondentFilters: documentsSearch.activeCorrespondentFilters, // Add derived/action handlers handleDocumentCorrespondentAttach, handleDocumentCorrespondentDetach, @@ -1013,26 +994,19 @@ const useDocumentsWorkspace = ({ closeDocumentPreview, ensureAssetUrl, getDocumentAsset, - handleThumbnailRegeneration, - }; - - const detailPanel = { - detailPanelProps, - detailPanelOpen, - openDetailPanel, }; const search = { - searchQuery, + searchQuery: documentsSearch.searchQuery, documentsFilter, - searchLoading, + searchLoading: documentsSearch.searchLoading, documentsViewMode, handleDocumentsViewModeChange, documentsSortField, documentsSortDirection, handleDocumentsSortFieldChange, handleDocumentsSortDirectionToggle, - searchResultIds, + searchResultIds: documentsSearch.searchResultIds, documents: viewDocuments, }; diff --git a/frontend/src/documents/types/workspaceTypes.ts b/frontend/src/documents/types/workspaceTypes.ts index 46f0148..4000768 100644 --- a/frontend/src/documents/types/workspaceTypes.ts +++ b/frontend/src/documents/types/workspaceTypes.ts @@ -22,8 +22,6 @@ export interface DocumentsManagerInterface { remove(ids: Array): boolean; } -type CloseDocumentPreview = () => void; - export interface DocumentsState { documentLookup: Map; setDocuments: Dispatch>; @@ -62,16 +60,11 @@ export interface TagsState { export interface CorrespondentsState { correspondents: Correspondent[]; correspondentLookupById: Map; + correspondentLookupByName?: Map; refreshCorrespondents: () => Promise; correspondentManager: CorrespondentManager; } -export interface ActionsState { - closeDocumentPreview: CloseDocumentPreview; - handleFileDrop?: (dataTransfer: DataTransfer, folderId: FolderId) => Promise | void; - moveDocumentsToFolder?: (docIds: FolderId[], folderId: FolderId) => Promise; -} - export interface DragState { draggedDocumentIds: DocumentId[]; draggedFolderId: FolderId | null; diff --git a/frontend/src/viewer/logic/useDetailWorkspace.ts b/frontend/src/viewer/logic/useDetailWorkspace.ts index f050aa5..1ea27d3 100644 --- a/frontend/src/viewer/logic/useDetailWorkspace.ts +++ b/frontend/src/viewer/logic/useDetailWorkspace.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useEffect } from 'react'; import type { MutableRefObject } from 'react'; import { resolveDocumentAssetUrl } from '../../lib/assets/AssetManager'; import { useDetailPanel } from '../../app/useDetailPanel'; @@ -6,7 +6,6 @@ import type { DocumentInfoPanelProps } from '../components/DocumentInfoPanel'; import type { EnsureAssetUrl, GetAsset } from '../../lib/assets/AssetManager'; import type { Identifier } from '../../types/identifiers'; import type { Document } from '../../types/documents'; -import { resolveBreadcrumbs } from '../../documents/logic/breadcrumbs'; import type { Tag, Correspondent } from '../../types/documents'; import { listFolderContents } from '../../lib/api/apiClient'; import { useStatusToast } from '../../lib/context/StatusToastContext'; @@ -19,13 +18,10 @@ interface FolderNode { } interface UseDetailWorkspaceArgs { - documents: Document[]; documentLookup: Map; folderNodes: Map; detailPanelControlRef: MutableRefObject<{ open?: (documentId: Identifier) => void; close?: () => void } | null>; detailFolderFetchRef: MutableRefObject>; - previewDocumentId?: Identifier | null; - activePreviewId?: Identifier | null; openDocumentPreview?: (args: { documentIds: Identifier[] }) => void; handleDocumentTitleUpdate?: (docId: Identifier, title: string) => Promise | boolean; handleDocumentIssuedUpdate?: (docId: Identifier, issued: number | null) => Promise | boolean; @@ -40,6 +36,7 @@ interface UseDetailWorkspaceArgs { tags?: unknown[]; tagLookupById?: Map | null; correspondentLookupById?: Map | null; + resolveFolderPath?: (folderId?: Identifier | 'root') => Array<{ id: Identifier | 'root'; name: string }>; } interface UseDetailWorkspaceResult { @@ -49,20 +46,15 @@ interface UseDetailWorkspaceResult { closeDetailPanel: ReturnType['closeDetailPanel']; handleDetailPanelClose: () => void; inspectDocument: (docId: Identifier | null) => void; - previewActive: boolean; - previewWorkspaceDocument: Document | null; resolveThumbnailUrlForDoc: (doc: Document | null) => string | null; resolveFolderPath: (folderId?: Identifier | 'root') => Array<{ id: Identifier | 'root'; name: string }>; } const useDetailWorkspace = ({ - documents, documentLookup, folderNodes, detailPanelControlRef, detailFolderFetchRef, - previewDocumentId, - activePreviewId, openDocumentPreview, handleDocumentTitleUpdate, handleDocumentIssuedUpdate, @@ -77,6 +69,7 @@ const useDetailWorkspace = ({ tags, tagLookupById, correspondentLookupById, + resolveFolderPath, }: UseDetailWorkspaceArgs): UseDetailWorkspaceResult => { const { showToast } = useStatusToast(); const notifyApiError = useNotifyApiError(); @@ -97,12 +90,6 @@ const useDetailWorkspace = ({ }; }, [detailPanelControlRef, openDetailPanel, closeDetailPanel]); - useEffect(() => { - if (detailPanelOpen && activePreviewId) { - openDetailPanel(activePreviewId); - } - }, [detailPanelOpen, activePreviewId, openDetailPanel]); - // Prefetch folder ancestors for breadcrumb display useEffect(() => { const folderId = detailPanelDocument?.folder_id; @@ -144,24 +131,6 @@ const useDetailWorkspace = ({ } }, [detailPanelDocument, folderNodes, detailFolderFetchRef]); - const resolveFolderPath = useCallback( - (folderId) => { - return resolveBreadcrumbs(folderId || 'root', folderNodes as any); - }, - [folderNodes], - ); - - const previewWorkspaceDocument = useMemo(() => { - if (!previewDocumentId) { - return null; - } - return documentLookup.get(previewDocumentId) - || documents.find((doc) => doc.id === previewDocumentId) - || null; - }, [previewDocumentId, documentLookup, documents]); - - const previewActive = Boolean(previewDocumentId && previewWorkspaceDocument); - const resolveThumbnailUrlForDoc = useCallback( (doc) => resolveDocumentAssetUrl(doc, 'thumbnail', { @@ -219,7 +188,6 @@ const useDetailWorkspace = ({ onTagAdd: handleDetailTagAdd, onTagRemove: handleDetailTagRemove, onOpenPreview: openDocumentPreview, - activePreviewId, onUpdateTitle: handleDocumentTitleUpdate, onUpdateIssued: handleDocumentIssuedUpdate, ensureAssetUrl, @@ -240,8 +208,6 @@ const useDetailWorkspace = ({ closeDetailPanel, handleDetailPanelClose, inspectDocument, - previewActive, - previewWorkspaceDocument, resolveThumbnailUrlForDoc, resolveFolderPath, };