diff --git a/frontend/src/app/AppLayout.tsx b/frontend/src/app/AppLayout.tsx index 08afd8c..1e15ed3 100644 --- a/frontend/src/app/AppLayout.tsx +++ b/frontend/src/app/AppLayout.tsx @@ -33,7 +33,6 @@ const AppLayout: React.FC = () => { onToggleSearchIncludeDescendants: documentsPreferences.toggleSearchIncludeDescendants, onSetSearchIncludeDescendants: documentsPreferences.setSearchIncludeDescendants, sortRefreshReadyRef: documentsPreferences.sortRefreshReadyRef, - handleDeskExit: documentsPreferences.handleDeskExit, }); if (['logged-out', 'authenticating', 'selecting-tenant'].includes(appStatus)) { diff --git a/frontend/src/app/DocumentsRoute.tsx b/frontend/src/app/DocumentsRoute.tsx index 3a73233..605a723 100644 --- a/frontend/src/app/DocumentsRoute.tsx +++ b/frontend/src/app/DocumentsRoute.tsx @@ -34,8 +34,6 @@ interface DocumentsRouteAppShell { documentsTableProps?: DocumentsTableProps | null; detailPanelProps?: Record | null; detailPanelOpen?: boolean; - documentsViewMode?: string; - deskWorkspaceProps?: Record | null; openTagsModal?: () => void; openCorrespondentsModal?: () => void; previewWorkspaceDocument?: unknown; @@ -55,8 +53,6 @@ const DocumentsRouteContent: React.FC = () => { documentsTableProps, detailPanelProps, detailPanelOpen, - documentsViewMode, - deskWorkspaceProps, openTagsModal, openCorrespondentsModal, previewWorkspaceDocument, @@ -106,8 +102,6 @@ const DocumentsRouteContent: React.FC = () => { documentsTableProps, detailPanelProps, detailPanelOpen, - viewMode: documentsViewMode, - deskWorkspaceProps, previewWorkspaceDocument, previewWorkspaceEntry, previewDocumentId, diff --git a/frontend/src/app/useWorkspaceSurface.tsx b/frontend/src/app/useWorkspaceSurface.tsx index 155cf0e..53c68b9 100644 --- a/frontend/src/app/useWorkspaceSurface.tsx +++ b/frontend/src/app/useWorkspaceSurface.tsx @@ -3,7 +3,6 @@ import type { ReactNode } from 'react'; import { SidebarExpandIcon } from '../ui/icons'; import { createDocumentsSurface } from '../documents/DocumentsPanel'; import { createDocumentViewerSurface } from '../preview/DocumentViewerPanel'; -import createDesktopSurface from '../desktop/createDesktopSurface'; import { usePanelManager } from './PanelManagerContext'; type Identifier = string | number; @@ -16,8 +15,7 @@ type NotifyApiError = (error: unknown, fallbackMessage?: string) => void; type DocumentsSurface = ReturnType | null; type PreviewSurface = ReturnType | null; -type DesktopSurface = ReturnType | null; -type WorkspaceSurface = DocumentsSurface | PreviewSurface | DesktopSurface | null; +type WorkspaceSurface = DocumentsSurface | PreviewSurface | null; interface UseWorkspaceSurfaceArgs { sidebarHidden?: boolean; @@ -25,8 +23,6 @@ interface UseWorkspaceSurfaceArgs { documentsTableProps?: Record | null; detailPanelProps?: (Record & { onClose?: () => void }) | null; detailPanelOpen?: boolean; - viewMode?: string; - deskWorkspaceProps?: Record | null; previewWorkspaceDocument?: unknown; previewWorkspaceEntry?: unknown; previewDocumentId?: Identifier | null; @@ -48,8 +44,6 @@ export const useWorkspaceSurface = ({ documentsTableProps, detailPanelProps, detailPanelOpen = false, - viewMode = 'list', - deskWorkspaceProps, previewWorkspaceDocument, previewWorkspaceEntry, previewDocumentId, @@ -161,33 +155,12 @@ export const useWorkspaceSurface = ({ detailPanelProps, ]); - const workspaceSurface = useMemo(() => { - if (viewMode !== 'desk') { - return null; - } - return createDesktopSurface({ - workspaceProps: deskWorkspaceProps, - renderSidebarToggle, - detailProps: detailPanelProps, - detailOpen: detailPanelOpen, - }); - }, [ - viewMode, - deskWorkspaceProps, - renderSidebarToggle, - detailPanelProps, - detailPanelOpen, - ]); - const surface = useMemo(() => { if (showPreviewWorkspace) { return previewSurface; } - if (viewMode === 'desk') { - return workspaceSurface; - } return documentsSurface; - }, [showPreviewWorkspace, viewMode, previewSurface, workspaceSurface, documentsSurface]); + }, [showPreviewWorkspace, previewSurface, documentsSurface]); return { surface }; }; diff --git a/frontend/src/desktop/DesktopWorkspace.tsx b/frontend/src/desktop/DesktopWorkspace.tsx index df85365..04b5761 100644 --- a/frontend/src/desktop/DesktopWorkspace.tsx +++ b/frontend/src/desktop/DesktopWorkspace.tsx @@ -130,8 +130,6 @@ interface DesktopWorkspaceProps { activeTagIds?: Array; selectedDocumentIds?: Identifier[]; onClearSelection?: () => void; - detailPanelOpen?: boolean; - onCloseDetailPanel?: () => void; tenantId?: Identifier | null; viewId?: string | null; } @@ -175,8 +173,6 @@ interface DesktopWorkspaceViewProps { onPromoteSelection?: DesktopWorkspaceProps['onPromoteSelection']; selectedDocumentIds: Identifier[]; onClearSelection?: DesktopWorkspaceProps['onClearSelection']; - detailPanelOpen: boolean; - onCloseDetailPanel?: DesktopWorkspaceProps['onCloseDetailPanel']; documentLookup: Map; resolveBaseMetrics: (doc: DeskDocument | null, cardWidth: number, cardHeight: number) => { baseWidth: number; @@ -210,8 +206,6 @@ const DesktopWorkspace: React.FC = ({ activeTagIds = [], selectedDocumentIds = [], onClearSelection = null, - detailPanelOpen = false, - onCloseDetailPanel = null, tenantId = null, viewId = 'default', previewEntries, @@ -788,8 +782,6 @@ const DesktopWorkspace: React.FC = ({ onPromoteSelection, selectedDocumentIds, onClearSelection, - detailPanelOpen, - onCloseDetailPanel, documentLookup, resolveBaseMetrics, bringToFront, @@ -831,7 +823,6 @@ const DesktopWorkspace: React.FC = ({ layoutRef, layoutSnapshot, onClearSelection, - onCloseDetailPanel, onDocumentStackSelect, onEntryPointer, onPromoteSelection, @@ -847,7 +838,6 @@ const DesktopWorkspace: React.FC = ({ resolveBaseMetrics, setDraggingId, selectedDocumentIds, - detailPanelOpen, onInspectDocument, markLayoutDirty, tagDropTargetId, @@ -895,8 +885,6 @@ function DesktopWorkspaceView({ onPromoteSelection, selectedDocumentIds, onClearSelection, - detailPanelOpen, - onCloseDetailPanel, documentLookup, resolveBaseMetrics, bringToFront, @@ -951,8 +939,6 @@ function DesktopWorkspaceView({ onPromoteSelection, onInspectDocument, selectedDocumentIds, - detailPanelOpen, - onCloseDetailPanel, openOverlayForDoc, }) as { getCardPointerHandlers: (doc: DeskDocument) => React.HTMLAttributes; @@ -970,14 +956,6 @@ function DesktopWorkspaceView({ } }, [focusShell, selectedDocumentIds.length]); - useEffect(() => { - if (!detailPanelOpen) { - focusShell(); - } - }, [detailPanelOpen, focusShell]); - - - const allSizesReady = items.every((doc) => Boolean(ensureDocumentSize(doc))); return ( diff --git a/frontend/src/desktop/createDesktopSurface.tsx b/frontend/src/desktop/createDesktopSurface.tsx deleted file mode 100644 index 3a6fd02..0000000 --- a/frontend/src/desktop/createDesktopSurface.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import React, { ReactNode } from 'react'; -import { SelectionFloatingPanel } from '../documents/SelectionFloatingActions'; -import { createDocumentsTableHeaderActions } from '../documents/DocumentsPanel'; -import createWorkspaceSurfaceConfig from '../documents/workspaceHeader'; -import DocumentViewerPanel from '../preview/DocumentViewerPanel'; -import DesktopWorkspace from './DesktopWorkspace'; - -type WorkspaceProps = Record; - -interface DetailProps extends Record { - onClose?: () => void; - onOpenPreview?: () => void; - tags?: unknown; -} - -interface CreateDesktopSurfaceArgs { - workspaceProps?: WorkspaceProps | null; - renderSidebarToggle?: () => ReactNode; - detailProps?: DetailProps | null; - detailOpen?: boolean; -} - -const createDesktopSurface = ({ - workspaceProps, - renderSidebarToggle, - detailProps = null, - detailOpen = false, -}: CreateDesktopSurfaceArgs) => { - if (!workspaceProps) { - return null; - } - - const { - currentFolderName, - searchResults, - onRefresh, - viewMode, - onViewModeChange, - onDeleteSelection, - tags, - correspondents, - documentLookup, - tagLookupById, - onBulkTagAdd, - onBulkTagRemove, - onBulkCorrespondentAdd, - onBulkCorrespondentRemove, - onBulkReanalyze, - folderOptions, - onMoveDocumentsToFolder, - searchIncludeDescendants, - onToggleSearchIncludeDescendants, - } = workspaceProps; - - 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: viewMode || 'desk', - onViewModeChange, - onRefresh, - 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 surfaceConfig = createWorkspaceSurfaceConfig({ - key: 'workspace', - variant: 'workspace', - title, - subtitle, - sidebarToggle, - actions, - breadcrumbs: workspaceProps?.breadcrumbs || null, - selectionLabel: null, - floatingActions, - content: ( - - ), - detail, - }); - - return { - ...surfaceConfig, - supportsDetail: Boolean(detailProps), - }; -}; - -export default createDesktopSurface; diff --git a/frontend/src/desktop/pointer/useDeskPointer.js b/frontend/src/desktop/pointer/useDeskPointer.js index 0200166..5c87d61 100644 --- a/frontend/src/desktop/pointer/useDeskPointer.js +++ b/frontend/src/desktop/pointer/useDeskPointer.js @@ -37,8 +37,6 @@ export const useDeskPointer = ({ onPromoteSelection, onInspectDocument, selectedDocumentIds, - detailPanelOpen, - onCloseDetailPanel, openOverlayForDoc = null, }) => { const pointerIntentRef = useRef(null); @@ -404,12 +402,8 @@ export const useDeskPointer = ({ return; } - if (detailPanelOpen) { - event.preventDefault(); - safeInvoke(onCloseDetailPanel); - } }, - [detailPanelOpen, onCloseDetailPanel, openOverlayForDoc, selectedDocumentIds], + [openOverlayForDoc, selectedDocumentIds], ); return { diff --git a/frontend/src/desktop/useDeskWorkspaceProps.ts b/frontend/src/desktop/useDeskWorkspaceProps.ts deleted file mode 100644 index de330a9..0000000 --- a/frontend/src/desktop/useDeskWorkspaceProps.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { useCallback, useMemo } from 'react'; - -type Identifier = string | number; - -type DocumentEntry = { id?: Identifier } & Record; -type InspectTarget = DocumentEntry | Identifier | null; - -type WorkspaceViewMode = 'desk' | 'grid' | 'list' | string; - -type DeskDocumentStackSelectHandler = (docIds: Identifier[]) => void; - -type ResolveRowKey = (id: Identifier) => Identifier | string | null; - -type ApplySelection = ( - keys: Array, - options: { anchor?: Identifier | string | null; interactedKeys?: Array }, -) => void; - -interface UseDeskWorkspacePropsArgs { - documents?: DocumentEntry[]; - searchResults?: DocumentEntry[]; - breadcrumbs?: unknown[]; - currentFolderName?: string | null; - documentsViewMode?: WorkspaceViewMode; - handleDocumentsViewModeChange?: (mode: WorkspaceViewMode) => void; - handleDeskExit?: () => void; - refreshCurrentFolder?: () => Promise | void; - inspectDocument?: (doc: InspectTarget) => void; - handleEntryPointerCore?: (...args: unknown[]) => void; - promoteSelectionOrder?: (...args: unknown[]) => void; - currentTenantId?: Identifier | null; - selectedDocumentIds?: Identifier[]; - selectedFolderIds?: Identifier[]; - clearDocumentSelection?: () => void; - detailPanelOpen?: boolean; - handleDetailPanelClose?: () => void; - resolveThumbnailUrlForDoc?: (doc: DocumentEntry) => string | null; - handleDocumentTagDrop?: (...args: unknown[]) => void; - ensureAssetUrl?: (...args: unknown[]) => void; - getDocumentAsset?: (...args: unknown[]) => unknown; - activeTagFilters?: Identifier[]; - handleDeleteSelection?: () => void; - tags?: unknown[]; - correspondents?: unknown[]; - documentLookup?: unknown; - tagLookupById?: unknown; - handleBulkTagAddFromDetail?: (...args: unknown[]) => void; - handleBulkTagRemoveFromDetail?: (...args: unknown[]) => void; - handleBulkCorrespondentAdd?: (...args: unknown[]) => void; - handleBulkCorrespondentRemove?: (...args: unknown[]) => void; - handleBulkSelectionReanalyze?: (...args: unknown[]) => void; - folderOptions?: unknown[]; - moveDocumentsToFolder?: (...args: unknown[]) => void; - searchIncludeDescendants?: boolean; - toggleSearchIncludeDescendants?: () => void; - selectedEntries?: Array; - selectionAnchorRef: { current: Identifier | string | null }; - applySelection: ApplySelection; - resolveDocumentRowKey: ResolveRowKey; - showingSearchResults?: boolean; - searchQuery?: string; - activeCorrespondentFilters?: Identifier[]; - selectedFolder?: Identifier | string | null; - previewEntries?: Map; - ensureDownloadUrl?: (documentId: Identifier, options?: { force?: boolean }) => Promise<{ url?: string | null; contentType?: string | null } | null>; -} - -const useDeskWorkspaceProps = ({ - documents, - searchResults, - breadcrumbs, - currentFolderName, - documentsViewMode = 'desk', - handleDocumentsViewModeChange, - handleDeskExit, - refreshCurrentFolder, - inspectDocument, - handleEntryPointerCore, - promoteSelectionOrder, - currentTenantId, - selectedDocumentIds, - selectedFolderIds, - clearDocumentSelection, - detailPanelOpen, - handleDetailPanelClose, - resolveThumbnailUrlForDoc, - handleDocumentTagDrop, - ensureAssetUrl, - getDocumentAsset, - activeTagFilters = [], - handleDeleteSelection, - tags, - correspondents, - documentLookup, - tagLookupById, - handleBulkTagAddFromDetail, - handleBulkTagRemoveFromDetail, - handleBulkCorrespondentAdd, - handleBulkCorrespondentRemove, - handleBulkSelectionReanalyze, - folderOptions, - moveDocumentsToFolder, - searchIncludeDescendants, - toggleSearchIncludeDescendants, - selectedEntries = [], - selectionAnchorRef, - applySelection, - resolveDocumentRowKey, - showingSearchResults = false, - searchQuery = '', - activeCorrespondentFilters = [], - selectedFolder, - previewEntries, - ensureDownloadUrl, -}: UseDeskWorkspacePropsArgs) => { - const handleDeskDocumentStackSelect: DeskDocumentStackSelectHandler = useCallback( - (docIds) => { - if (!Array.isArray(docIds) || docIds.length === 0) { - return; - } - - const rowKeys = docIds - .map((id) => resolveDocumentRowKey(id)) - .filter((value): value is Identifier | string => Boolean(value)); - - if (!rowKeys.length) { - return; - } - - const nextKeys = [...selectedEntries]; - rowKeys.forEach((key) => { - if (!nextKeys.includes(key)) { - nextKeys.push(key); - } - }); - - const anchor = (rowKeys[0] - || selectionAnchorRef.current - || nextKeys[nextKeys.length - 1]) as Identifier | string | null; - - applySelection(nextKeys, { - anchor, - interactedKeys: rowKeys, - }); - }, - [applySelection, resolveDocumentRowKey, selectedEntries, selectionAnchorRef], - ); - - const deskViewId = useMemo(() => { - if (showingSearchResults) { - const trimmedQuery = searchQuery.trim(); - const tagsKey = [...activeTagFilters].sort().join(','); - const correspondentsKey = [...activeCorrespondentFilters].sort().join(','); - return `search:${trimmedQuery}|tags:${tagsKey}|corr:${correspondentsKey}`; - } - - const folderKey = selectedFolder && selectedFolder !== '' ? selectedFolder : 'root'; - return `folder:${folderKey}`; - }, [ - showingSearchResults, - searchQuery, - activeTagFilters, - activeCorrespondentFilters, - selectedFolder, - ]); - - return useMemo( - () => ({ - documents, - searchResults, - breadcrumbs, - currentFolderName, - viewMode: documentsViewMode, - onViewModeChange: handleDocumentsViewModeChange, - onExit: handleDeskExit, - onRefresh: refreshCurrentFolder, - onInspectDocument: inspectDocument, - onEntryPointer: handleEntryPointerCore, - onDocumentStackSelect: handleDeskDocumentStackSelect, - onPromoteSelection: promoteSelectionOrder, - tenantId: currentTenantId, - viewId: deskViewId, - selectedDocumentIds, - selectedFolderIds, - onClearSelection: clearDocumentSelection, - detailPanelOpen, - onCloseDetailPanel: handleDetailPanelClose, - resolveThumbnailUrl: resolveThumbnailUrlForDoc, - onAssignTagToDocument: handleDocumentTagDrop, - ensureAssetUrl, - getDocumentAsset, - activeTagIds: activeTagFilters, - onDeleteSelection: handleDeleteSelection, - tags, - correspondents, - documentLookup, - tagLookupById, - onBulkTagAdd: handleBulkTagAddFromDetail, - onBulkTagRemove: handleBulkTagRemoveFromDetail, - onBulkCorrespondentAdd: handleBulkCorrespondentAdd, - onBulkCorrespondentRemove: handleBulkCorrespondentRemove, - onBulkReanalyze: handleBulkSelectionReanalyze, - folderOptions, - onMoveDocumentsToFolder: moveDocumentsToFolder, - searchIncludeDescendants, - onToggleSearchIncludeDescendants: toggleSearchIncludeDescendants, - previewEntries, - ensureDownloadUrl, - }), - [ - documents, - searchResults, - breadcrumbs, - currentFolderName, - documentsViewMode, - handleDocumentsViewModeChange, - handleDeskExit, - refreshCurrentFolder, - inspectDocument, - handleEntryPointerCore, - handleDeskDocumentStackSelect, - promoteSelectionOrder, - currentTenantId, - deskViewId, - selectedDocumentIds, - selectedFolderIds, - clearDocumentSelection, - detailPanelOpen, - handleDetailPanelClose, - resolveThumbnailUrlForDoc, - handleDocumentTagDrop, - ensureAssetUrl, - getDocumentAsset, - activeTagFilters, - handleDeleteSelection, - tags, - correspondents, - documentLookup, - tagLookupById, - handleBulkTagAddFromDetail, - handleBulkTagRemoveFromDetail, - handleBulkCorrespondentAdd, - handleBulkCorrespondentRemove, - handleBulkSelectionReanalyze, - folderOptions, - moveDocumentsToFolder, - searchIncludeDescendants, - toggleSearchIncludeDescendants, - previewEntries, - ensureDownloadUrl, - ], - ); -}; - -export default useDeskWorkspaceProps; diff --git a/frontend/src/documents/panel/DocumentsPanel.tsx b/frontend/src/documents/panel/DocumentsPanel.tsx index 6d462f2..19094a5 100644 --- a/frontend/src/documents/panel/DocumentsPanel.tsx +++ b/frontend/src/documents/panel/DocumentsPanel.tsx @@ -1,8 +1,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { ViewListIcon, ViewGridIcon, IconFileStack } from '../../ui/icons'; -import BreadcrumbTrail from '../../ui/BreadcrumbTrail'; import DocumentsGrid from '../DocumentsGrid'; import DocumentsList from '../DocumentsList'; +import DesktopWorkspace from '../../desktop/DesktopWorkspace'; import { isTagTransferEvent } from '../tagTransfer'; import PreviewZoomOverlay from '../../detail/PreviewZoomOverlay'; import { isPointerModifierEvent, isPrimaryPointerEvent } from '../useEntryPointer'; @@ -24,9 +23,8 @@ const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null; export type PreviewEntryLike = { url?: string | null; contentType?: string | null }; const DocumentsPanel: React.FC = ({ - currentFolderName, + currentFolderName: _currentFolderName, breadcrumbs, - onRefresh, subfolders, documents, searchResults, @@ -54,10 +52,10 @@ const DocumentsPanel: React.FC = ({ isSearchLoading = false, onDocumentTagDrop, viewMode = 'list', - onViewModeChange, - showHeader = true, + onViewModeChange: _onViewModeChange, previewEntries, ensureDownloadUrl, + deskWorkspaceProps = null, }) => { const { selectedEntries, @@ -403,20 +401,7 @@ const DocumentsPanel: React.FC = ({ ], ); - const isListView = viewMode === 'list'; const gridIconSize = DEFAULT_GRID_ICON_SIZE; - const handleSetViewMode = useCallback( - (nextMode) => { - if (!onViewModeChange) { - return; - } - onViewModeChange(nextMode); - if (scrollRef.current) { - scrollRef.current.scrollTop = 0; - } - }, - [onViewModeChange], - ); useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = 0; @@ -590,181 +575,152 @@ const DocumentsPanel: React.FC = ({ const showDefaultEmptyState = !showingSearchResults && !isFilterActive && entries.length === 0; const showListSearchEmptyState = showingSearchResults && !hasDocumentEntries && !isGridView && !isSearchLoading; const showGridSearchEmptyState = isGridView && showingSearchResults && !hasDocumentEntries && !isSearchLoading; - const breadcrumbEntries = useMemo(() => (Array.isArray(breadcrumbs) ? breadcrumbs.filter(Boolean) : []), [breadcrumbs]); - const trailEntries = useMemo(() => { - if (!breadcrumbEntries.length) { - return [{ id: 'current-folder', label: currentFolderName }]; - } - const lastIndex = breadcrumbEntries.length - 1; - return breadcrumbEntries.map((crumb, index) => ({ - id: crumb.id ?? index, - label: crumb.name ?? crumb.label ?? crumb.title ?? '', - onClick: index < lastIndex && onFolderSelect - ? () => onFolderSelect(crumb.id) - : null, - })); - }, [breadcrumbEntries, currentFolderName, onFolderSelect]); - - return ( - <> -
- {showHeader ? ( -
-
-

- -

- {showingSearchResults && ( -
Search results
- )} -
-
-
- - - + const renderBody = () => { + if (isDeskView) { + return ( +
+ {deskWorkspaceProps ? ( + + ) : ( +
+ Desk view is unavailable.
- -
+ )}
- ) : null} - {showDefaultEmptyState ? ( + ); + } + + if (showDefaultEmptyState) { + return (
Drop files anywhere or onto a folder to upload documents.
- ) : showGridSearchEmptyState ? ( + ); + } + + if (showGridSearchEmptyState) { + return (
No documents match the current filters.
- ) : showListSearchEmptyState ? ( + ); + } + + if (showListSearchEmptyState) { + return (
No documents match the current filters.
- ) : ( -
-
{ - if (event.target === scrollRef.current) { - handlePanelFocus(); - } - }} - onKeyDown={(event) => { - if (event.target !== scrollRef.current) { - return; - } - handlePanelKeyDown(event); - }} - onClick={(event) => { - if (event.target === event.currentTarget) { - clearSelection(); - } - }} - aria-activedescendant={isGridView ? undefined : activeDescendantId} - > - {isGridView ? ( - - ) : !showTableRows ? null : ( - - )} -
+ ); + } + + if (!showTableRows) { + return null; + } + + return ( +
+
{ + if (event.target === scrollRef.current) { + handlePanelFocus(); + } + }} + onKeyDown={(event) => { + if (event.target !== scrollRef.current) { + return; + } + handlePanelKeyDown(event); + }} + onClick={(event) => { + if (event.target === event.currentTarget) { + clearSelection(); + } + }} + aria-activedescendant={isGridView ? undefined : activeDescendantId} + > + {isGridView ? ( + + ) : ( + + )}
- )} -
+ + ); + }; + + const panelVariant = isDeskView ? 'desk' : isGridView ? 'grid' : 'list'; + + return ( + <> +
+ {renderBody()} +
), diff --git a/frontend/src/hooks/documents/useDocumentsWorkspace.ts b/frontend/src/hooks/documents/useDocumentsWorkspace.ts index c3faf8d..fb70348 100644 --- a/frontend/src/hooks/documents/useDocumentsWorkspace.ts +++ b/frontend/src/hooks/documents/useDocumentsWorkspace.ts @@ -18,7 +18,6 @@ import useDocumentsSelection from '../../documents/hooks/useDocumentsSelection'; import useBulkDocumentActions from '../../documents/hooks/useBulkDocumentActions'; import useDocumentsPanelProps from '../../documents/hooks/useDocumentsPanelProps'; import useDocumentPreview from '../../app/useDocumentPreview'; -import useDeskWorkspaceProps from '../../desktop/useDeskWorkspaceProps'; import useSidebarProps from '../../sidebar/useSidebarProps'; import { ASSET_PRESIGN_TTL_MS, @@ -97,7 +96,6 @@ interface UseDocumentsWorkspaceOptions { onToggleSearchIncludeDescendants?: () => void; onSetSearchIncludeDescendants?: (value: boolean) => void; sortRefreshReadyRef?: MutableRefObject; - handleDeskExit?: () => void; } const useDocumentsWorkspace = ({ @@ -113,14 +111,12 @@ const useDocumentsWorkspace = ({ onToggleSearchIncludeDescendants, onSetSearchIncludeDescendants, sortRefreshReadyRef, - handleDeskExit, }: UseDocumentsWorkspaceOptions = {}) => { const handleDocumentsViewModeChange = onDocumentsViewModeChange || noop; const handleDocumentsSortFieldChange = onDocumentsSortFieldChange || noop; const handleDocumentsSortDirectionToggle = onDocumentsSortDirectionToggle || noop; const toggleSearchIncludeDescendants = onToggleSearchIncludeDescendants || noop; const setSearchIncludeDescendants = onSetSearchIncludeDescendants || noop; - const handleDeskExitSafe = handleDeskExit || noop; const fallbackSortFieldRef = useRef(documentsSortField); const activeSortFieldRef = documentsSortFieldRef || fallbackSortFieldRef; @@ -1236,12 +1232,10 @@ const useDocumentsWorkspace = ({ detailPanelProps, detailPanelOpen, openDetailPanel, - handleDetailPanelClose, inspectDocument, previewActive, previewWorkspaceDocument, previewWorkspaceEntry, - resolveThumbnailUrlForDoc, resolveFolderPath, } = useDetailWorkspace({ documents, @@ -1404,7 +1398,97 @@ const useDocumentsWorkspace = ({ }); - const documentsTableProps = useDocumentsPanelProps({ + const handleDeskDocumentStackSelect = useCallback( + (docIds: Array) => { + if (!Array.isArray(docIds) || docIds.length === 0) { + return; + } + + const rowKeys = docIds + .map((id) => resolveDocumentRowKey(id as Identifier)) + .filter((value): value is string => typeof value === 'string'); + + if (!rowKeys.length) { + return; + } + + const nextKeys = [...selectedEntries]; + rowKeys.forEach((key) => { + if (!nextKeys.includes(key)) { + nextKeys.push(key); + } + }); + + const anchor = (rowKeys[0] + || selectionAnchorRef.current + || nextKeys[nextKeys.length - 1]) as Identifier | string | null; + + applySelection(nextKeys, { + anchor, + interactedKeys: rowKeys, + }); + }, + [applySelection, selectedEntries, selectionAnchorRef], + ); + + const deskViewId = useMemo(() => { + if (showingSearchResults) { + const trimmedQuery = searchQuery.trim(); + const tagsKey = [...activeTagFilters].sort().join(','); + const correspondentsKey = [...activeCorrespondentFilters].sort().join(','); + return `search:${trimmedQuery}|tags:${tagsKey}|corr:${correspondentsKey}`; + } + + const folderKey = selectedFolder && selectedFolder !== '' ? selectedFolder : 'root'; + return `folder:${folderKey}`; + }, [ + showingSearchResults, + searchQuery, + activeTagFilters, + activeCorrespondentFilters, + selectedFolder, + ]); + + const deskWorkspaceProps = useMemo( + () => ({ + documents, + searchResults, + onInspectDocument: inspectDocumentForDesk, + onEntryPointer: handleEntryPointerCore, + onDocumentStackSelect: handleDeskDocumentStackSelect, + onPromoteSelection: promoteSelectionOrder, + onAssignTagToDocument: handleDocumentTagDrop, + ensureAssetUrl, + getDocumentAsset, + activeTagIds: activeTagFilters, + selectedDocumentIds, + onClearSelection: clearDocumentSelection, + tenantId: currentTenantId, + viewId: deskViewId, + previewEntries, + ensureDownloadUrl, + }), + [ + documents, + searchResults, + inspectDocumentForDesk, + handleEntryPointerCore, + handleDeskDocumentStackSelect, + promoteSelectionOrder, + handleDocumentTagDrop, + ensureAssetUrl, + getDocumentAsset, + activeTagFilters, + selectedDocumentIds, + clearDocumentSelection, + currentTenantId, + deskViewId, + previewEntries, + ensureDownloadUrl, + ], + ); + + const documentsPanelProps = useDocumentsPanelProps({ currentFolderName, breadcrumbs, refreshCurrentFolder, @@ -1457,6 +1541,14 @@ const useDocumentsWorkspace = ({ ensureDownloadUrl, }); + const documentsTableProps = useMemo( + () => ({ + ...documentsPanelProps, + deskWorkspaceProps, + }), + [documentsPanelProps, deskWorkspaceProps], + ); + const sidebarProps = useSidebarProps({ folderNodes, folderClickHandlers, @@ -1495,57 +1587,6 @@ const useDocumentsWorkspace = ({ uploadQueue, }); - - - const deskWorkspaceProps = useDeskWorkspaceProps({ - documents, - searchResults, - breadcrumbs, - currentFolderName, - documentsViewMode, - handleDocumentsViewModeChange, - handleDeskExit: handleDeskExitSafe, - refreshCurrentFolder, - inspectDocument: inspectDocumentForDesk, - handleEntryPointerCore, - promoteSelectionOrder, - currentTenantId, - selectedDocumentIds, - selectedFolderIds, - clearDocumentSelection, - detailPanelOpen, - handleDetailPanelClose, - resolveThumbnailUrlForDoc, - handleDocumentTagDrop, - ensureAssetUrl, - getDocumentAsset, - activeTagFilters, - handleDeleteSelection, - tags, - correspondents, - documentLookup, - tagLookupById, - handleBulkTagAddFromDetail, - handleBulkTagRemoveFromDetail, - handleBulkCorrespondentAdd, - handleBulkCorrespondentRemove, - handleBulkSelectionReanalyze, - folderOptions, - moveDocumentsToFolder, - searchIncludeDescendants, - toggleSearchIncludeDescendants, - selectedEntries, - selectionAnchorRef, - applySelection, - resolveDocumentRowKey, - showingSearchResults, - searchQuery, - activeCorrespondentFilters, - selectedFolder, - previewEntries, - ensureDownloadUrl, - }); - const contextValue = useMemo( () => ({ token, @@ -1585,7 +1626,6 @@ const useDocumentsWorkspace = ({ documentsTableProps, detailPanelProps, documentsViewMode, - deskWorkspaceProps, ensurePreviewData, ensureAssetUrl, resolveFolderPath, @@ -1637,7 +1677,6 @@ const useDocumentsWorkspace = ({ documentsTableProps, detailPanelProps, documentsViewMode, - deskWorkspaceProps, ensurePreviewData, ensureAssetUrl, resolveFolderPath, diff --git a/frontend/src/utils/colors.ts b/frontend/src/utils/colors.ts index a14554d..15043af 100644 --- a/frontend/src/utils/colors.ts +++ b/frontend/src/utils/colors.ts @@ -93,6 +93,19 @@ const srgbChannelToLinear = (value: number) => { return ((normalized + 0.055) / 1.055) ** 2.4; }; +export const hexToRgb = (input?: string): (RgbColor & { hex: string }) | null => { + if (!input) return null; + const match = HEX_COLOR_PATTERN.exec(input.trim()); + if (!match) return null; + const value = parseInt(match[1], 16); + return { + r: (value >> 16) & 0xff, + g: (value >> 8) & 0xff, + b: value & 0xff, + hex: `#${match[1].toLowerCase()}`, + }; +}; + const hexToOklch = (input?: string) => { const rgb = hexToRgb(input); if (!rgb) { @@ -150,19 +163,6 @@ const buildTagStyle = (backgroundHex: string) => { }; }; -export const hexToRgb = (input?: string): (RgbColor & { hex: string }) | null => { - if (!input) return null; - const match = HEX_COLOR_PATTERN.exec(input.trim()); - if (!match) return null; - const value = parseInt(match[1], 16); - return { - r: (value >> 16) & 0xff, - g: (value >> 8) & 0xff, - b: value & 0xff, - hex: `#${match[1].toLowerCase()}`, - }; -}; - export const relativeLuminance = ({ r, g, b }: RgbColor): number => { const toLinear = (channel) => { const normalized = channel / 255; @@ -295,7 +295,6 @@ export const getTagColorStyle = (hex) => { adjustments.forEach((delta) => registerCandidate(baseHsl.l + delta)); let bestBackground = baseHex; - let bestText = baseText; let bestRatio = baseRatio; if (bestRatio >= TARGET_RATIO) { @@ -303,7 +302,6 @@ export const getTagColorStyle = (hex) => { } let compliantBackground = null; - let compliantText = null; let compliantDelta = Infinity; let compliantRatio = -Infinity; @@ -314,7 +312,6 @@ export const getTagColorStyle = (hex) => { if (ratio > bestRatio) { bestBackground = candidateHex; - bestText = textHex; bestRatio = ratio; } @@ -330,7 +327,6 @@ export const getTagColorStyle = (hex) => { || (isSimilarDistance && improvesRatio) ) { compliantBackground = candidateHex; - compliantText = textHex; compliantDelta = delta; compliantRatio = ratio; }