import React, { useMemo, useState, useRef, useEffect, useCallback, } from 'react'; import TagRemovalZone from '../components/TagRemovalZone'; import { DocumentsList, DocumentsGrid } from '../DocumentsView'; import type { ReactNode } from 'react'; import type { DocumentsListEntry, } from '../../types/documents'; const EntryType = { folder: 'folder' as const, document: 'document' as const, }; import DesktopWorkspace from '../../desktop/components/DesktopWorkspace'; import { WorkspaceSelectionProvider, useWorkspaceSelectionContext, } from '../../app/WorkspaceSelectionContext'; import DocumentsPanelHeader, { DocumentsPanelHeaderConfig, } from './DocumentsPanelHeader'; import { SelectionFloatingPanel } from '../features/selection/SelectionFloatingActions'; import { createDocumentsTableHeaderActions } from './DocumentsToolbar'; import { useDocumentsFilter } from '../context/DocumentsFilterContext'; import { DEFAULT_GRID_ICON_SIZE, DEFAULT_LIST_ICON_SIZE, DEFAULT_DESKTOP_CARD_SIZE, } from '../../constants/documents'; import { DocumentsAssetContext } from '../context/DocumentsAssetContext'; import { DocumentsViewStateContext } from '../context/DocumentsViewStateContext'; import { DocumentsCommandContext } from '../context/DocumentsCommandContext'; import { useDocumentsContextValues } from './useDocumentsContextValues'; import { useAppShell } from '../../lib/context/AppShellContext'; import type { TagInteractionHandlers } from '../interactions/useTagInteractions'; export interface DocumentsViewProps { entries: DocumentsListEntry[]; tagHandlers?: TagInteractionHandlers; [key: string]: any; } interface DocumentsPanelProps { headerLeading?: ReactNode; } const DocumentsPanelInner: React.FC = React.memo((props) => { const { headerLeading } = props; const shell = useAppShell(); const { search: { documents, searchResultIds, documentsViewMode: viewMode, documentsSortField: sortField, documentsSortDirection: sortDirection, handleDocumentsViewModeChange: onViewModeChange, handleDocumentsSortFieldChange: onSortFieldChange, handleDocumentsSortDirectionToggle: onSortDirectionToggle }, folderTree: { currentFolderName, breadcrumbs, currentSubfolders: subfolders, folderOptions, moveDocumentsToFolder: onMoveDocumentsToFolder, refreshCurrentFolder: onRefresh, handleBreadcrumbNavigate: onBreadcrumbNavigate }, selection: { handleDeleteSelection: onDeleteSelection }, managers: { documentLookup }, tags: { tags, handleBulkTagAddFromDetail: onBulkTagAdd, handleBulkTagRemoveFromDetail: onBulkTagRemove }, correspondents: { correspondents, handleBulkCorrespondentAdd: onBulkCorrespondentAdd, handleBulkCorrespondentRemove: onBulkCorrespondentRemove }, mutations: { handleBulkSelectionReanalyze: onBulkReanalyze }, } = shell as any; const { assetContextValue, viewStateContextValue, commandContextValue, tagHandlers, scrollRef, hasDocumentEntries, } = useDocumentsContextValues(); const { clearSelection, } = useWorkspaceSelectionContext(); const { isActive: isFilterActive, includeDescendants, toggleIncludeDescendants, } = useDocumentsFilter(); const searchDocuments = useMemo( () => Array.isArray(searchResultIds) ? searchResultIds .map((id: any) => documentLookup?.get?.(id) || null) .filter((doc: any): doc is Record => Boolean(doc)) : null, [searchResultIds, documentLookup], ); const showingSearchResults = Array.isArray(searchResultIds); const rows = showingSearchResults && searchDocuments ? searchDocuments : documents; const headerTitle = showingSearchResults ? 'Search results' : currentFolderName || 'Documents'; const headerActions = useMemo( () => createDocumentsTableHeaderActions({ viewMode, onViewModeChange, onRefresh, sortField, onSortFieldChange, sortDirection, onSortDirectionToggle, isFilterActive, includeDescendants, onToggleIncludeDescendants: toggleIncludeDescendants, }), [ viewMode, onViewModeChange, onRefresh, sortField, onSortFieldChange, sortDirection, onSortDirectionToggle, isFilterActive, includeDescendants, toggleIncludeDescendants, ], ); const { tagLookupById, correspondentLookupById } = (shell as any).tags; const floatingActions = useMemo(() => ( ), [ documentLookup, tags, tagLookupById, correspondents, correspondentLookupById, onBulkTagAdd, onBulkTagRemove, onBulkCorrespondentAdd, onBulkCorrespondentRemove, onBulkReanalyze, onDeleteSelection, folderOptions, onMoveDocumentsToFolder, clearSelection, ]); const headerConfig: DocumentsPanelHeaderConfig = useMemo(() => ({ title: headerTitle, subtitle: null, leading: headerLeading, actions: headerActions, breadcrumbs, floatingActions, }), [ headerTitle, headerLeading, headerActions, breadcrumbs, floatingActions, ]); const currentFolderId = useMemo(() => { if (showingSearchResults) { return null; } const trail = Array.isArray(breadcrumbs) ? breadcrumbs : []; if (trail.length === 0) { return 'root'; } return trail[trail.length - 1]?.id || 'root'; }, [breadcrumbs, showingSearchResults]); // Context marker logic for clearing selection on nav const selectionContextRef = useRef(null); useEffect(() => { const nextContext = showingSearchResults ? { type: 'search', marker: searchResultIds } : { type: 'folder', marker: currentFolderId || 'root' }; const previous = selectionContextRef.current; selectionContextRef.current = nextContext; if (!previous) { return; } const changed = previous.type !== nextContext.type || previous.marker !== nextContext.marker; if (changed) { clearSelection(); } }, [showingSearchResults, currentFolderId, searchResultIds, clearSelection]); const entries = useMemo(() => { const list: DocumentsListEntry[] = []; // Explicit type if (!showingSearchResults) { (subfolders || []).forEach((folder: any) => { if (!folder || !folder.id) { return; } list.push({ type: EntryType.folder, id: folder.id, key: `folder:${folder.id}`, folder }); }); } (rows || []).forEach((doc: any) => { if (!doc || !doc.id) { return; } list.push({ type: EntryType.document, id: doc.id, key: `document:${doc.id}`, document: doc }); }); return list; }, [showingSearchResults, subfolders, rows]); const isGridView = viewMode === 'grid'; const isDeskView = viewMode === 'desk'; const viewProps = { entries, viewId: currentFolderId, tagHandlers, }; const [iconSizes] = useState({ list: DEFAULT_LIST_ICON_SIZE, grid: DEFAULT_GRID_ICON_SIZE, desk: DEFAULT_DESKTOP_CARD_SIZE, }); const isSearchLoading = (shell as any).search?.isSearchLoading || false; const renderBody = () => { const hasEntries = entries.length > 0; const isSearchEmpty = (showingSearchResults || isFilterActive) && !hasDocumentEntries && !isSearchLoading; if (isSearchEmpty) { return (
No documents match the current filters.
); } if (!hasEntries) { return (
No documents to show here yet. Drop files to make this space come alive.
); } switch (viewMode) { case 'desk': return ; case 'grid': return ; case 'list': default: return ; } }; const panelVariant = isDeskView ? 'desk' : isGridView ? 'grid' : 'list'; const shouldHandlePanelInteractions = !isDeskView && entries.length > 0; const handleSectionClick = useCallback((event: React.MouseEvent) => { if (!shouldHandlePanelInteractions || event.target !== event.currentTarget) { return; } clearSelection(); }, [shouldHandlePanelInteractions, clearSelection]); return (
{renderBody()}
); }); DocumentsPanelInner.displayName = 'DocumentsPanelInner'; const DocumentsPanel: React.FC = (props) => { const shell = useAppShell(); const selectionValue = (shell as any).selection?.selectionValue; return ( ); }; export default DocumentsPanel;