diff --git a/frontend/src/app/useWorkspaceSurface.tsx b/frontend/src/app/useWorkspaceSurface.tsx index dc66a3e..4e98b78 100644 --- a/frontend/src/app/useWorkspaceSurface.tsx +++ b/frontend/src/app/useWorkspaceSurface.tsx @@ -1,8 +1,8 @@ import { useCallback, useEffect, useMemo } from 'react'; import type { ReactNode } from 'react'; import { SidebarExpandIcon } from '../ui/icons'; -import { createDocumentsSurface } from '../documents/DocumentsPanel'; -import { createDocumentViewerSurface } from '../preview/DocumentViewerPanel'; +import DocumentsPanel from '../documents/panel/DocumentsPanel'; +import DocumentViewerPanel from '../preview/DocumentViewerPanel'; import { usePanelManager } from './PanelManagerContext'; type Identifier = string | number; @@ -13,9 +13,7 @@ type GetDocumentAsset = (document: unknown, assetType: string) => unknown; type ResolveApiPath = (path: string) => string; type NotifyApiError = (error: unknown, fallbackMessage?: string) => void; -type DocumentsSurface = ReturnType | null; -type PreviewSurface = ReturnType | null; -type WorkspaceSurface = DocumentsSurface | PreviewSurface | null; +type WorkspaceSurface = { content: ReactNode; detail?: ReactNode | null } | null; interface UseWorkspaceSurfaceArgs { sidebarHidden?: boolean; @@ -84,29 +82,52 @@ export const useWorkspaceSurface = ({ ); }, [sidebarHidden, onExpandSidebar]); - const documentsSurface = useMemo(() => { + const documentsSurface = useMemo(() => { if (!documentsTableProps) { return null; } - return createDocumentsSurface({ - tableProps: documentsTableProps, - renderSidebarToggle, - detailProps: detailPanelProps, - detailOpen: detailPanelOpen, - }); + + const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null; + + const detail = detailPanelOpen && detailPanelProps + ? (() => { + const { onClose, onOpenPreview, tags: tagOptions, ...restDetailProps } = detailPanelProps; + return ( + + ); + })() + : null; + + return { + content: ( + + ), + detail, + }; }, [ documentsTableProps, renderSidebarToggle, - detailPanelProps, detailPanelOpen, + detailPanelProps, ]); const showPreviewWorkspace = Boolean(previewDocumentId); - const previewSurface = useMemo(() => { - if (!showPreviewWorkspace) { + const previewSurface = useMemo(() => { + if (!showPreviewWorkspace || !previewWorkspaceDocument) { return null; } + + const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null; const detailExtras = detailPanelProps || {}; const { tagLookupById, @@ -120,38 +141,45 @@ export const useWorkspaceSurface = ({ onUpdateIssued, resolveFolderPath, } = detailExtras; - return createDocumentViewerSurface({ - document: previewWorkspaceDocument, - documentLink, - ensureAssetUrl, - ensurePreviewData, - getDocumentAsset, - resolveApiPath, - notifyApiError, - onClose: closeDocumentPreview, - renderSidebarToggle, - tagLookupById, - tagOptions, - onTagAdd, - onTagRemove, - correspondents, - onCorrespondentAdd, - onCorrespondentRemove, - onUpdateTitle, - onUpdateIssued, - resolveFolderPath, - }); + + return { + content: ( + + ), + detail: null, + }; }, [ showPreviewWorkspace, previewWorkspaceDocument, documentLink, - ensureAssetUrl, ensurePreviewData, + ensureAssetUrl, getDocumentAsset, resolveApiPath, notifyApiError, - closeDocumentPreview, renderSidebarToggle, + closeDocumentPreview, detailPanelProps, ]); diff --git a/frontend/src/documents/DocumentsPanel.ts b/frontend/src/documents/DocumentsPanel.ts index d076f21..3f7c199 100644 --- a/frontend/src/documents/DocumentsPanel.ts +++ b/frontend/src/documents/DocumentsPanel.ts @@ -1,3 +1,2 @@ export { default } from './panel/DocumentsPanel'; -export { default as createDocumentsSurface } from './panel/createDocumentsSurface'; export { createDocumentsTableHeaderActions } from './panel/DocumentsToolbar'; diff --git a/frontend/src/documents/panel/DocumentsPanel.tsx b/frontend/src/documents/panel/DocumentsPanel.tsx index 4bfc052..65ee46f 100644 --- a/frontend/src/documents/panel/DocumentsPanel.tsx +++ b/frontend/src/documents/panel/DocumentsPanel.tsx @@ -11,6 +11,8 @@ import DocumentsPanelHeader, { DocumentsPanelHeaderConfig, DocumentsHeaderBreadcrumb, } from './DocumentsPanelHeader'; +import { SelectionFloatingPanel } from '../SelectionFloatingActions'; +import { createDocumentsTableHeaderActions } from './DocumentsToolbar'; const DEFAULT_GRID_ICON_SIZE = 144; @@ -20,7 +22,7 @@ const EntryType = { }; interface DocumentsPanelProps { - headerConfig?: DocumentsPanelHeaderConfig; + headerLeading?: ReactNode; onBreadcrumbNavigate?: (crumb: DocumentsHeaderBreadcrumb) => void; [key: string]: any; } @@ -30,9 +32,9 @@ const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null; export type DocumentLinkLike = { url?: string | null; contentType?: string | null }; const DocumentsPanel: React.FC = ({ - headerConfig, + headerLeading = null, onBreadcrumbNavigate, - currentFolderName: _currentFolderName, + currentFolderName, breadcrumbs, subfolders, documents, @@ -61,10 +63,28 @@ const DocumentsPanel: React.FC = ({ isSearchLoading = false, onDocumentTagDrop, viewMode = 'list', - onViewModeChange: _onViewModeChange, + onViewModeChange, documentLinks, ensureDownloadUrl, deskWorkspaceProps = null, + onRefresh = () => {}, + sortField, + sortDirection, + onSortFieldChange, + onSortDirectionToggle, + searchIncludeDescendants, + onToggleSearchIncludeDescendants, + onDeleteSelection, + documentLookup, + tags, + correspondents, + onBulkTagAdd, + onBulkTagRemove, + onBulkCorrespondentAdd, + onBulkCorrespondentRemove, + onBulkReanalyze, + folderOptions, + onMoveDocumentsToFolder, }): ReactNode => { const { selectedEntries, @@ -76,6 +96,85 @@ const DocumentsPanel: React.FC = ({ const showingSearchResults = searchResults !== null; const rows = showingSearchResults ? searchResults : documents; const documentLinkMap = documentLinks instanceof Map ? documentLinks : null; + const searchResultCount = Array.isArray(searchResults) ? searchResults.length : 0; + const headerTitle = showingSearchResults + ? 'Search results' + : currentFolderName || 'Documents'; + const headerSubtitle = showingSearchResults + ? `${searchResultCount} matching document${searchResultCount === 1 ? '' : 's'}` + : null; + const headerActions = useMemo( + () => createDocumentsTableHeaderActions({ + viewMode, + onViewModeChange, + onRefresh, + sortField, + onSortFieldChange, + sortDirection, + onSortDirectionToggle, + isFilterActive, + includeDescendants: searchIncludeDescendants, + onToggleIncludeDescendants: onToggleSearchIncludeDescendants, + }), + [ + viewMode, + onViewModeChange, + onRefresh, + sortField, + onSortFieldChange, + sortDirection, + onSortDirectionToggle, + isFilterActive, + searchIncludeDescendants, + onToggleSearchIncludeDescendants, + ], + ); + const floatingActions = useMemo(() => ( + + ), [ + documentLookup, + tags, + tagLookupById, + correspondents, + onBulkTagAdd, + onBulkTagRemove, + onBulkCorrespondentAdd, + onBulkCorrespondentRemove, + onBulkReanalyze, + onDeleteSelection, + folderOptions, + onMoveDocumentsToFolder, + clearSelection, + ]); + const headerConfig: DocumentsPanelHeaderConfig = useMemo(() => ({ + title: headerTitle, + subtitle: headerSubtitle, + leading: headerLeading, + actions: headerActions, + breadcrumbs, + floatingActions, + }), [ + headerTitle, + headerSubtitle, + headerLeading, + headerActions, + breadcrumbs, + floatingActions, + ]); const currentFolderId = useMemo(() => { if (showingSearchResults) { @@ -702,12 +801,10 @@ const DocumentsPanel: React.FC = ({ return ( <> - {headerConfig ? ( - - ) : null} +
; - renderSidebarToggle?: () => ReactNode; - detailProps?: Record | null; - detailOpen?: boolean; -} - -const createDocumentsSurface = ({ - tableProps, - renderSidebarToggle, - detailProps, - detailOpen = false, -}: CreateDocumentsSurfaceArgs) => { - const { - currentFolderName, - breadcrumbs, - searchResults, - isFilterActive, - viewMode, - onViewModeChange, - onRefresh, - sortField, - sortDirection, - onSortFieldChange, - onSortDirectionToggle, - onDeleteSelection, - tags, - correspondents, - documentLookup, - tagLookupById, - onBulkTagAdd, - onBulkTagRemove, - onBulkCorrespondentAdd, - onBulkCorrespondentRemove, - onBulkReanalyze, - folderOptions, - onMoveDocumentsToFolder, - searchIncludeDescendants, - onToggleSearchIncludeDescendants, - onInspectDocument, - } = tableProps; - - const isDeskView = viewMode === 'desk'; - - const title = Array.isArray(searchResults) ? 'Search results' : currentFolderName; - const subtitle = Array.isArray(searchResults) - ? `${searchResults.length} matching document${searchResults.length === 1 ? '' : 's'}` - : null; - - const actions = createDocumentsTableHeaderActions({ - viewMode, - onViewModeChange, - onRefresh, - sortField, - onSortFieldChange, - sortDirection, - onSortDirectionToggle, - isFilterActive, - includeDescendants: searchIncludeDescendants, - onToggleIncludeDescendants: onToggleSearchIncludeDescendants, - }); - - const floatingActions = ( - - ); - - const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null; - const detail = detailOpen && detailProps - ? (() => { - const { onClose, onOpenPreview, tags: tagOptions, ...restDetailProps } = detailProps; - return ( - - ); - })() - : null; - - const headerConfig: DocumentsPanelHeaderConfig = { - title, - subtitle, - leading: sidebarToggle, - actions, - breadcrumbs, - floatingActions, - }; - - return createWorkspaceSurfaceConfig({ - key: isDeskView ? 'workspace' : 'documents', - variant: isDeskView ? 'workspace' : 'documents', - title, - subtitle, - sidebarToggle, - actions, - breadcrumbs, - selectionLabel: null, - floatingActions, - content: ( - - ), - detail, - }); -}; - -export default createDocumentsSurface; diff --git a/frontend/src/documents/workspaceHeader.tsx b/frontend/src/documents/workspaceHeader.tsx deleted file mode 100644 index badd0d7..0000000 --- a/frontend/src/documents/workspaceHeader.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { ReactNode } from 'react'; - -interface WorkspaceSurfaceHeaderConfig { - title: ReactNode; - subtitle?: ReactNode; - leading?: ReactNode; - actions?: ReactNode; - breadcrumbs?: ReactNode; - selectionLabel?: ReactNode; - floatingActions?: ReactNode; -} - -interface WorkspaceSurfaceConfig { - key: string; - variant: string; - header: WorkspaceSurfaceHeaderConfig; - content?: ReactNode; - detail?: ReactNode; -} - -interface CreateWorkspaceSurfaceConfigArgs { - title: ReactNode; - subtitle?: ReactNode; - sidebarToggle?: ReactNode; - actions?: ReactNode; - breadcrumbs?: ReactNode; - selectionLabel?: ReactNode; - floatingActions?: ReactNode; - content?: ReactNode; - detail?: ReactNode; - variant?: string; - key?: string; -} - -export const createWorkspaceSurfaceConfig = ({ - title, - subtitle = null, - sidebarToggle = null, - actions = null, - breadcrumbs = null, - selectionLabel = null, - floatingActions = null, - content = null, - detail = null, - variant = 'documents', - key = 'documents', -}: CreateWorkspaceSurfaceConfigArgs): WorkspaceSurfaceConfig => { - const leading = sidebarToggle ? <>{sidebarToggle} : null; - - return { - key, - variant, - header: { - title, - subtitle, - leading, - actions, - breadcrumbs, - selectionLabel, - floatingActions, - }, - content, - detail, - }; -}; - -export default createWorkspaceSurfaceConfig; diff --git a/frontend/src/preview/DocumentViewerPanel.tsx b/frontend/src/preview/DocumentViewerPanel.tsx index fe8ecb2..e4c287b 100644 --- a/frontend/src/preview/DocumentViewerPanel.tsx +++ b/frontend/src/preview/DocumentViewerPanel.tsx @@ -546,62 +546,3 @@ const DocumentViewerPanel: React.FC = ({ }; export default DocumentViewerPanel; - -export const createDocumentViewerSurface = ({ - document, - documentLink, - ensureAssetUrl, - ensurePreviewData, - getDocumentAsset, - resolveApiPath, - notifyApiError, - onClose, - renderSidebarToggle, - tagLookupById, - tagOptions, - onTagAdd, - onTagRemove, - correspondents, - onCorrespondentAdd, - onCorrespondentRemove, - onUpdateTitle, - onUpdateIssued, - resolveFolderPath, -}) => { - if (!document) { - return null; - } - - const sidebarToggle = renderSidebarToggle ? renderSidebarToggle() : null; - - return { - key: 'preview', - variant: 'preview', - header: null, - content: ( - - ), - supportsDetail: false, - }; -};