diff --git a/frontend/src/app/DocumentsRoute.tsx b/frontend/src/app/DocumentsRoute.tsx index 694e91f..42af412 100644 --- a/frontend/src/app/DocumentsRoute.tsx +++ b/frontend/src/app/DocumentsRoute.tsx @@ -40,7 +40,7 @@ const DocumentsInner: React.FC<{ const handleOpenSidepanel = useCallback((docId: string) => { if (openDetailPanel) { - openDetailPanel({ documentIds: [docId] }); + openDetailPanel(docId); } }, [openDetailPanel]); diff --git a/frontend/src/app/useDetailPanel.ts b/frontend/src/app/useDetailPanel.ts index af7c594..3df14c1 100644 --- a/frontend/src/app/useDetailPanel.ts +++ b/frontend/src/app/useDetailPanel.ts @@ -1,97 +1,46 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import type { Identifier } from '../types/identifiers'; interface DetailDocument { - id?: string; + id?: Identifier; [key: string]: unknown; } interface UseDetailPanelOptions { - documentLookup: Map; - orderedSelectedDocuments: DetailDocument[]; -} - -interface OpenDetailPanelArgs { - documentId?: string; - document?: DetailDocument | null; - documentIds?: Array; - documents?: DetailDocument[]; + documentLookup: Map; } export const useDetailPanel = ({ documentLookup, - orderedSelectedDocuments, }: UseDetailPanelOptions) => { - const [detailPanelOpen, setDetailPanelOpen] = useState(false); - const [detailPanelDocId, setDetailPanelDocId] = useState(null); + const [detailPanelDocId, setDetailPanelDocId] = useState(null); const [detailPanelDocument, setDetailPanelDocument] = useState(null); - const latestOrderedDocsRef = useRef([]); - useEffect(() => { - latestOrderedDocsRef.current = orderedSelectedDocuments; - if (detailPanelOpen && orderedSelectedDocuments.length) { - const nextDoc = orderedSelectedDocuments[orderedSelectedDocuments.length - 1]; - if (nextDoc?.id) { - setDetailPanelDocId(nextDoc.id); - setDetailPanelDocument(nextDoc); - } - } - }, [orderedSelectedDocuments, detailPanelOpen]); + const detailPanelOpen = detailPanelDocId !== null; useEffect(() => { if (!detailPanelDocId) { - if (!detailPanelOpen) { - setDetailPanelDocument(null); - } + setDetailPanelDocument(null); return; } - const resolved = documentLookup.get(detailPanelDocId); - if (resolved && resolved !== detailPanelDocument) { + const resolved = documentLookup.get(detailPanelDocId) ?? null; + if (resolved !== detailPanelDocument) { setDetailPanelDocument(resolved); } - }, [detailPanelDocId, documentLookup, detailPanelDocument, detailPanelOpen]); + }, [detailPanelDocId, documentLookup, detailPanelDocument]); const openDetailPanel = useCallback( - ({ documentId, document, documentIds, documents }: OpenDetailPanelArgs = {}) => { - let targetDoc = document || null; - let targetId = documentId ?? document?.id ?? null; - - if (!targetDoc && Array.isArray(documents) && documents.length) { - targetDoc = documents[documents.length - 1]; - targetId = targetDoc?.id ?? targetId; - } - - if (!targetDoc && Array.isArray(documentIds) && documentIds.length) { - targetId = documentIds[documentIds.length - 1]; - } - - if (!targetDoc && targetId != null) { - targetDoc = documentLookup.get(String(targetId)) || null; - } - - if (!targetDoc) { - const fallbackDocs = latestOrderedDocsRef.current; - const fallbackDoc = Array.isArray(fallbackDocs) && fallbackDocs.length - ? fallbackDocs[fallbackDocs.length - 1] - : null; - if (fallbackDoc) { - targetDoc = fallbackDoc; - targetId = fallbackDoc.id; - } - } - - if (!targetDoc && targetId == null) { + (documentId: Identifier) => { + if (!documentId) { return; } - - setDetailPanelDocId(targetDoc?.id ?? targetId ?? null); - setDetailPanelDocument(targetDoc || null); - setDetailPanelOpen(Boolean(targetDoc || targetId)); + setDetailPanelDocId(documentId); }, - [documentLookup], + [], ); const closeDetailPanel = useCallback(() => { - setDetailPanelOpen(false); + setDetailPanelDocId(null); }, []); return { @@ -99,6 +48,5 @@ export const useDetailPanel = ({ detailPanelDocument, openDetailPanel, closeDetailPanel, - setDetailPanelOpen, }; }; diff --git a/frontend/src/app/useDocumentsShell.ts b/frontend/src/app/useDocumentsShell.ts index d1b0fe0..509f1a8 100644 --- a/frontend/src/app/useDocumentsShell.ts +++ b/frontend/src/app/useDocumentsShell.ts @@ -7,7 +7,7 @@ import type { UseWorkspaceSurfaceArgs } from './useWorkspaceSurface'; import type { Identifier } from '../types/identifiers'; type WorkspaceSurfaceConfig = Omit & { - openDetailPanel?: (args: { documentIds?: Identifier[] }) => void; + openDetailPanel?: (documentId: Identifier) => void; closeDetailPanel?: () => void; }; diff --git a/frontend/src/detail/useDetailWorkspace.ts b/frontend/src/detail/useDetailWorkspace.ts index 438cdb0..5df41d9 100644 --- a/frontend/src/detail/useDetailWorkspace.ts +++ b/frontend/src/detail/useDetailWorkspace.ts @@ -3,7 +3,6 @@ import type { MutableRefObject } from 'react'; import { resolveDocumentAssetUrl } from '../asset_manager'; import { useDetailPanel } from '../app/useDetailPanel'; import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils'; -import { getEntryId, isDocumentEntry } from '../app/entryKey'; import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel'; import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr'; import type { Identifier } from '../types/identifiers'; @@ -17,12 +16,10 @@ interface FolderNode { interface UseDetailWorkspaceArgs { documents: Document[]; - selectionOrder: string[]; - selectedDocumentIds: Identifier[]; documentLookup: Map; folderNodes: Map; ensureFolderData: (folderId: Identifier | 'root', options?: { force?: boolean; includeDocuments?: boolean }) => Promise; - detailPanelControlRef: MutableRefObject<{ open?: (args?: { documentIds?: Identifier[] }) => void; close?: () => void } | null>; + detailPanelControlRef: MutableRefObject<{ open?: (documentId: Identifier) => void; close?: () => void } | null>; detailFolderFetchRef: MutableRefObject>; previewDocumentId?: Identifier | null; activePreviewId?: Identifier | null; @@ -56,8 +53,6 @@ interface UseDetailWorkspaceResult { const useDetailWorkspace = ({ documents, - selectionOrder, - selectedDocumentIds, documentLookup, folderNodes, ensureFolderData, @@ -79,37 +74,6 @@ const useDetailWorkspace = ({ tags, tagLookupById, }: UseDetailWorkspaceArgs): UseDetailWorkspaceResult => { - const orderedSelectedDocuments = useMemo(() => { - const ordered = []; - const seen = new Set(); - - const pushDoc = (doc) => { - if (doc?.id && !seen.has(doc.id)) { - ordered.push(doc); - seen.add(doc.id); - } - }; - - selectionOrder.forEach((key) => { - if (!isDocumentEntry(key)) { - return; - } - const docId = getEntryId(key); - const doc = documentLookup.get(docId) || null; - pushDoc(doc); - }); - - selectedDocumentIds.forEach((docId) => { - if (seen.has(docId)) { - return; - } - const doc = documentLookup.get(docId) || null; - pushDoc(doc); - }); - - return ordered; - }, [selectionOrder, documentLookup, selectedDocumentIds]); - const { detailPanelOpen, detailPanelDocument, @@ -117,7 +81,6 @@ const useDetailWorkspace = ({ closeDetailPanel, } = useDetailPanel({ documentLookup, - orderedSelectedDocuments, }); useEffect(() => { @@ -127,52 +90,46 @@ const useDetailWorkspace = ({ }; }, [detailPanelControlRef, openDetailPanel, closeDetailPanel]); + // Prefetch folder ancestors for breadcrumb display useEffect(() => { - if (!orderedSelectedDocuments.length) { + const folderId = detailPanelDocument?.folder_id; + if (!folderId) { return; } const visited = new Set(); + let currentId = folderId; + let guard = 0; - orderedSelectedDocuments.forEach((doc) => { - const folderId = doc?.folder_id; - if (!folderId) { - return; + while (currentId && currentId !== 'root' && guard < 32) { + guard += 1; + if (visited.has(currentId)) { + break; + } + visited.add(currentId); + + const node = folderNodes.get(currentId); + if (!node) { + if (!detailFolderFetchRef.current.has(currentId)) { + detailFolderFetchRef.current.add(currentId); + ensureFolderData(currentId, { force: false, includeDocuments: false }) + .catch((error) => { + console.warn('Failed to preload folder metadata for detail path', currentId, error); + }) + .finally(() => { + detailFolderFetchRef.current.delete(currentId); + }); + } + break; } - let currentId = folderId; - let guard = 0; - - while (currentId && currentId !== 'root' && guard < 32) { - guard += 1; - if (visited.has(currentId)) { - break; - } - visited.add(currentId); - - const node = folderNodes.get(currentId); - if (!node) { - if (!detailFolderFetchRef.current.has(currentId)) { - detailFolderFetchRef.current.add(currentId); - ensureFolderData(currentId, { force: false, includeDocuments: false }) - .catch((error) => { - console.warn('Failed to preload folder metadata for detail path', currentId, error); - }) - .finally(() => { - detailFolderFetchRef.current.delete(currentId); - }); - } - break; - } - - const parentId = node.parentId ?? 'root'; - if (!parentId || parentId === 'root') { - break; - } - currentId = parentId; + const parentId = node.parentId ?? 'root'; + if (!parentId || parentId === 'root') { + break; } - }); - }, [orderedSelectedDocuments, folderNodes, ensureFolderData, detailFolderFetchRef]); + currentId = parentId; + } + }, [detailPanelDocument, folderNodes, ensureFolderData, detailFolderFetchRef]); const resolveFolderPath = useCallback( (folderId) => { @@ -240,11 +197,11 @@ const useDetailWorkspace = ({ ); const inspectDocument = useCallback( - (documentId) => { + (documentId: Identifier) => { if (!documentId) { return; } - openDetailPanel({ documentIds: [documentId] }); + openDetailPanel(documentId); }, [openDetailPanel], ); diff --git a/frontend/src/hooks/documents/useDocumentsWorkspace.ts b/frontend/src/hooks/documents/useDocumentsWorkspace.ts index 4f6f657..ae983cd 100644 --- a/frontend/src/hooks/documents/useDocumentsWorkspace.ts +++ b/frontend/src/hooks/documents/useDocumentsWorkspace.ts @@ -257,7 +257,6 @@ const useDocumentsWorkspace = ({ selectedDocumentIds, selectedFolderIds, setSelectedEntries, - selectionOrder, setSelectionOrder, selectionOrderRef, selectionAnchorRef, @@ -1074,8 +1073,6 @@ const useDocumentsWorkspace = ({ resolveFolderPath, } = useDetailWorkspace({ documents: viewDocuments, - selectionOrder, - selectedDocumentIds, documentLookup, folderNodes, ensureFolderData,