feat: Refactor sidebar props to simplify data flow
This commit is contained in:
@@ -14,11 +14,11 @@ import useDocumentsShell from './useDocumentsShell';
|
|||||||
import useFolderData from '../documents/data/useFolderData';
|
import useFolderData from '../documents/data/useFolderData';
|
||||||
|
|
||||||
const DocumentsInner: React.FC<{
|
const DocumentsInner: React.FC<{
|
||||||
sidebarProps: ComponentProps<typeof Sidebar> | null;
|
|
||||||
surfaceConfig: any;
|
surfaceConfig: any;
|
||||||
folderData: any;
|
folderData: any;
|
||||||
onNavigate: (documentId: string) => void;
|
onNavigate: (documentId: string) => void;
|
||||||
}> = ({ sidebarProps, surfaceConfig, folderData, onNavigate }) => {
|
sidebarProps: ComponentProps<typeof Sidebar> | null;
|
||||||
|
}> = ({ surfaceConfig, folderData, onNavigate, sidebarProps }) => {
|
||||||
const { openPreview } = usePreviewContext();
|
const { openPreview } = usePreviewContext();
|
||||||
const { collapsed: sidebarCollapsed } = useSidebarContext();
|
const { collapsed: sidebarCollapsed } = useSidebarContext();
|
||||||
const {
|
const {
|
||||||
@@ -112,10 +112,10 @@ const DocumentsRouteContent: React.FC = () => {
|
|||||||
onNavigate={handleDocumentNavigate}
|
onNavigate={handleDocumentNavigate}
|
||||||
>
|
>
|
||||||
<DocumentsInner
|
<DocumentsInner
|
||||||
sidebarProps={sidebarProps}
|
|
||||||
surfaceConfig={surfaceConfig}
|
surfaceConfig={surfaceConfig}
|
||||||
folderData={folderData}
|
folderData={folderData}
|
||||||
onNavigate={handleDocumentNavigate}
|
onNavigate={handleDocumentNavigate}
|
||||||
|
sidebarProps={sidebarProps}
|
||||||
/>
|
/>
|
||||||
</PreviewProvider>
|
</PreviewProvider>
|
||||||
</DocumentsFilterProvider>
|
</DocumentsFilterProvider>
|
||||||
|
|||||||
@@ -157,9 +157,6 @@ const useDocumentsWorkspace = ({
|
|||||||
const { client: apiClient } = useApi();
|
const { client: apiClient } = useApi();
|
||||||
|
|
||||||
const tenantRecord = (tenant ?? null) as TenantOption | null;
|
const tenantRecord = (tenant ?? null) as TenantOption | null;
|
||||||
const tenantNameCandidate = tenantRecord?.name ?? tenantRecord?.slug ?? null;
|
|
||||||
const tenantName = tenantNameCandidate ? String(tenantNameCandidate) : null;
|
|
||||||
|
|
||||||
const currentTenantId: Identifier | null = (tenantRecord?.id ?? null) as Identifier | null;
|
const currentTenantId: Identifier | null = (tenantRecord?.id ?? null) as Identifier | null;
|
||||||
|
|
||||||
const tenantOptions: TenantOption[] = Array.isArray(tenantOptionsRaw)
|
const tenantOptions: TenantOption[] = Array.isArray(tenantOptionsRaw)
|
||||||
@@ -606,7 +603,6 @@ const useDocumentsWorkspace = ({
|
|||||||
const {
|
const {
|
||||||
dropOverlayState,
|
dropOverlayState,
|
||||||
handleFileDrop,
|
handleFileDrop,
|
||||||
handleFileSelection,
|
|
||||||
uploadQueue,
|
uploadQueue,
|
||||||
clearUploadQueue,
|
clearUploadQueue,
|
||||||
resetUploadsState,
|
resetUploadsState,
|
||||||
@@ -1242,7 +1238,6 @@ const useDocumentsWorkspace = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const sidebarProps = useSidebarProps({
|
const sidebarProps = useSidebarProps({
|
||||||
folderNodes,
|
|
||||||
folderClickHandlers,
|
folderClickHandlers,
|
||||||
handleFolderDelete,
|
handleFolderDelete,
|
||||||
handleFolderRename,
|
handleFolderRename,
|
||||||
@@ -1252,20 +1247,6 @@ const useDocumentsWorkspace = ({
|
|||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
handlePromptCreateFolder,
|
handlePromptCreateFolder,
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
tags,
|
|
||||||
handleTagCreate,
|
|
||||||
correspondents,
|
|
||||||
handleCorrespondentCreate,
|
|
||||||
appStatus,
|
|
||||||
previewActive,
|
|
||||||
handleLogout,
|
|
||||||
tenantName,
|
|
||||||
tenantOptions,
|
|
||||||
currentTenantId,
|
|
||||||
handleTenantSelect,
|
|
||||||
openSettings,
|
|
||||||
handleFileSelection,
|
|
||||||
uploadQueue,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
@@ -1320,6 +1301,10 @@ const useDocumentsWorkspace = ({
|
|||||||
documentsManager,
|
documentsManager,
|
||||||
foldersManager,
|
foldersManager,
|
||||||
handleBreadcrumbNavigate,
|
handleBreadcrumbNavigate,
|
||||||
|
tenant: tenantRecord,
|
||||||
|
tenants: tenantOptions,
|
||||||
|
tenantOptions,
|
||||||
|
handleTenantSelect,
|
||||||
};
|
};
|
||||||
|
|
||||||
// hook callers handle rendering / routing
|
// hook callers handle rendering / routing
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
import React, { useRef } from 'react';
|
import React, { useRef, useSyncExternalStore, useCallback } from 'react';
|
||||||
|
import { useAppShell } from '../lib/context/AppShellContext';
|
||||||
import { usePanelManager, usePanelResizeBindings } from '../app/PanelManagerContext';
|
import FoldersManager from '../documents/FoldersManager';
|
||||||
|
import { usePanelResizeBindings } from '../app/PanelManagerContext';
|
||||||
import type { Identifier } from '../types/identifiers';
|
import type { Identifier } from '../types/identifiers';
|
||||||
|
|
||||||
import SidebarFolderList from './components/SidebarFolderList';
|
import SidebarFolderList from './components/SidebarFolderList';
|
||||||
import SidebarTagList, { TagEntry } from './components/SidebarTagList';
|
import SidebarTagList from './components/SidebarTagList';
|
||||||
import SidebarCorrespondentList, { CorrespondentEntry } from './components/SidebarCorrespondentList';
|
import SidebarCorrespondentList from './components/SidebarCorrespondentList';
|
||||||
import { FolderIdentifier, FolderTreeNode } from './components/SidebarFolderNode';
|
import { FolderIdentifier } from './components/SidebarFolderNode';
|
||||||
import { TenantOption } from './components/SidebarMenu';
|
import { TenantOption } from './components/SidebarMenu';
|
||||||
import SidebarHeader from './components/SidebarHeader';
|
import SidebarHeader from './components/SidebarHeader';
|
||||||
import SidebarSearch from './components/SidebarSearch';
|
import SidebarSearch from './components/SidebarSearch';
|
||||||
|
|
||||||
type UploadHandler = (
|
|
||||||
files: FileList | File[] | Iterable<File>,
|
|
||||||
targetFolderId?: FolderIdentifier | null,
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
folderNodes: Map<FolderIdentifier, FolderTreeNode>;
|
|
||||||
onToggle: (folderId: FolderIdentifier) => void;
|
onToggle: (folderId: FolderIdentifier) => void;
|
||||||
onSelect: (folderId: FolderIdentifier) => void;
|
onSelect: (folderId: FolderIdentifier) => void;
|
||||||
onDrop: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
|
onDrop: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
|
||||||
@@ -29,26 +25,11 @@ interface SidebarProps {
|
|||||||
onFolderDragStart?: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
|
onFolderDragStart?: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
|
||||||
onFolderDragEnd?: (event: React.DragEvent<HTMLDivElement>) => void;
|
onFolderDragEnd?: (event: React.DragEvent<HTMLDivElement>) => void;
|
||||||
draggedFolderId?: FolderIdentifier | null;
|
draggedFolderId?: FolderIdentifier | null;
|
||||||
onCreateFolder?: (parentId?: FolderIdentifier | null) => void;
|
onCreateFolder?: (parentId?: Identifier | null) => void;
|
||||||
creatingFolder?: boolean;
|
creatingFolder?: boolean;
|
||||||
tags?: TagEntry[];
|
|
||||||
untaggedFilterId?: Identifier | null;
|
|
||||||
correspondents?: CorrespondentEntry[];
|
|
||||||
onManageTags?: () => void;
|
|
||||||
onManageCorrespondents?: () => void;
|
|
||||||
onCreateTag?: (label: string) => Promise<void> | void;
|
|
||||||
onCreateCorrespondent?: (name: string) => Promise<void> | void;
|
|
||||||
onLogout?: () => void;
|
|
||||||
tenantName?: string | null;
|
|
||||||
tenants?: TenantOption[];
|
|
||||||
activeTenantId?: Identifier | null;
|
|
||||||
onSelectTenant?: (tenant: TenantOption | null, options?: { refreshOnly?: boolean }) => void;
|
|
||||||
onOpenSettings?: () => void;
|
|
||||||
onUploadFiles?: UploadHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sidebar: React.FC<SidebarProps> = ({
|
const Sidebar: React.FC<SidebarProps> = ({
|
||||||
folderNodes,
|
|
||||||
onToggle,
|
onToggle,
|
||||||
onSelect,
|
onSelect,
|
||||||
onDrop,
|
onDrop,
|
||||||
@@ -62,24 +43,46 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
draggedFolderId = null,
|
draggedFolderId = null,
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
creatingFolder = false,
|
creatingFolder = false,
|
||||||
tags = [],
|
|
||||||
untaggedFilterId = null,
|
|
||||||
correspondents = [],
|
|
||||||
onManageTags,
|
|
||||||
onManageCorrespondents,
|
|
||||||
onCreateTag,
|
|
||||||
onCreateCorrespondent,
|
|
||||||
onLogout,
|
|
||||||
tenantName,
|
|
||||||
tenants = [],
|
|
||||||
activeTenantId = null,
|
|
||||||
onSelectTenant,
|
|
||||||
onOpenSettings,
|
|
||||||
onUploadFiles,
|
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
sidebarSuppressed,
|
sidebarSuppressed,
|
||||||
} = usePanelManager();
|
openTagsModal,
|
||||||
|
openCorrespondentsModal,
|
||||||
|
handleLogout,
|
||||||
|
tags = [],
|
||||||
|
correspondents = [],
|
||||||
|
foldersManager,
|
||||||
|
tenant,
|
||||||
|
tenants,
|
||||||
|
tenantOptions,
|
||||||
|
handleTenantSelect,
|
||||||
|
openSettings,
|
||||||
|
handleFileSelection, // Used for upload
|
||||||
|
} = useAppShell();
|
||||||
|
|
||||||
|
// Subscribe to folder tree updates
|
||||||
|
const subscribeToTree = useCallback((callback: () => void) => {
|
||||||
|
if (!foldersManager) return () => { };
|
||||||
|
return (foldersManager as FoldersManager).subscribe(callback);
|
||||||
|
}, [foldersManager]);
|
||||||
|
|
||||||
|
const getTreeSnapshot = useCallback(() => {
|
||||||
|
if (!foldersManager) return [];
|
||||||
|
return (foldersManager as FoldersManager).getTreeSnapshot();
|
||||||
|
}, [foldersManager]);
|
||||||
|
|
||||||
|
const roots = useSyncExternalStore(subscribeToTree, getTreeSnapshot);
|
||||||
|
|
||||||
|
const onManageTags = openTagsModal;
|
||||||
|
const onManageCorrespondents = openCorrespondentsModal;
|
||||||
|
|
||||||
|
// Derived or context-based handlers/values
|
||||||
|
const tenantName = (tenant as TenantOption)?.name;
|
||||||
|
const effectiveTenants = (tenants || tenantOptions || []);
|
||||||
|
const activeTenantId = (tenant as TenantOption)?.id;
|
||||||
|
|
||||||
|
// Upload handler wrapper (if needed, or pass handleFileSelection directly if signature matches)
|
||||||
|
const onUploadFiles = handleFileSelection;
|
||||||
|
|
||||||
const sidebarRef = useRef<HTMLDivElement | null>(null);
|
const sidebarRef = useRef<HTMLDivElement | null>(null);
|
||||||
const {
|
const {
|
||||||
@@ -109,11 +112,11 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
|
|
||||||
<SidebarHeader
|
<SidebarHeader
|
||||||
tenantName={tenantName}
|
tenantName={tenantName}
|
||||||
tenants={tenants}
|
tenants={effectiveTenants}
|
||||||
activeTenantId={activeTenantId}
|
activeTenantId={activeTenantId}
|
||||||
onSelectTenant={onSelectTenant}
|
onSelectTenant={handleTenantSelect}
|
||||||
onOpenSettings={onOpenSettings}
|
onOpenSettings={openSettings}
|
||||||
onLogout={onLogout}
|
onLogout={handleLogout}
|
||||||
onUploadFiles={onUploadFiles}
|
onUploadFiles={onUploadFiles}
|
||||||
selectedFolder={selectedFolder}
|
selectedFolder={selectedFolder}
|
||||||
/>
|
/>
|
||||||
@@ -122,7 +125,7 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
<SidebarSearch />
|
<SidebarSearch />
|
||||||
|
|
||||||
<SidebarFolderList
|
<SidebarFolderList
|
||||||
folderNodes={folderNodes}
|
roots={roots}
|
||||||
selectedFolder={selectedFolder}
|
selectedFolder={selectedFolder}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
@@ -140,14 +143,12 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
|
|
||||||
<SidebarTagList
|
<SidebarTagList
|
||||||
tags={tags}
|
tags={tags}
|
||||||
untaggedFilterId={untaggedFilterId}
|
untaggedFilterId={null}
|
||||||
onCreateTag={onCreateTag}
|
|
||||||
onManageTags={onManageTags}
|
onManageTags={onManageTags}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SidebarCorrespondentList
|
<SidebarCorrespondentList
|
||||||
correspondents={correspondents}
|
correspondents={correspondents}
|
||||||
onCreateCorrespondent={onCreateCorrespondent}
|
|
||||||
onManageCorrespondents={onManageCorrespondents}
|
onManageCorrespondents={onManageCorrespondents}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { PlusIcon, SettingsIcon } from '../../components/icons';
|
|||||||
import type { Identifier } from '../../types/identifiers';
|
import type { Identifier } from '../../types/identifiers';
|
||||||
import { useDocumentsFilter } from '../../documents/context/DocumentsFilterContext';
|
import { useDocumentsFilter } from '../../documents/context/DocumentsFilterContext';
|
||||||
|
|
||||||
export interface CorrespondentEntry {
|
interface CorrespondentEntry {
|
||||||
id: Identifier;
|
id: Identifier;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import React, { useCallback } from 'react';
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { Identifier } from '../../types/identifiers';
|
import type { Identifier } from '../../types/identifiers';
|
||||||
import { FolderPlusIcon } from '../../components/icons';
|
import { FolderPlusIcon } from '../../components/icons';
|
||||||
import FolderNode, { FolderIdentifier, FolderTreeNode } from './SidebarFolderNode';
|
import FolderNode, { FolderIdentifier } from './SidebarFolderNode';
|
||||||
|
import type { FolderTreeNode } from '../../lib/api/apiTypes';
|
||||||
|
|
||||||
interface SidebarFolderListProps {
|
interface SidebarFolderListProps {
|
||||||
folderNodes: Map<FolderIdentifier, FolderTreeNode>;
|
roots: FolderTreeNode[];
|
||||||
selectedFolder: FolderIdentifier | null;
|
selectedFolder: FolderIdentifier | null;
|
||||||
onToggle: (folderId: FolderIdentifier) => void;
|
onToggle: (folderId: FolderIdentifier) => void;
|
||||||
onSelect: (folderId: FolderIdentifier) => void;
|
onSelect: (folderId: FolderIdentifier) => void;
|
||||||
@@ -22,7 +23,7 @@ interface SidebarFolderListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
|
const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
|
||||||
folderNodes,
|
roots,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
onToggle,
|
onToggle,
|
||||||
onSelect,
|
onSelect,
|
||||||
@@ -37,20 +38,44 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
|
|||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
}) => {
|
}) => {
|
||||||
const rootNode = folderNodes.get('root');
|
// Local expansion state for the UI
|
||||||
|
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set(['root']));
|
||||||
|
|
||||||
|
// Override validation or external toggle if needed
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (selectedFolder) {
|
||||||
|
// ensure parent paths are expanded?
|
||||||
|
// Without a parent map, we can't easily auto-expand up.
|
||||||
|
// Rely on user or initial state for now, or add parent pointers.
|
||||||
|
}
|
||||||
|
}, [selectedFolder]);
|
||||||
|
|
||||||
|
const handleToggle = useCallback((folderId: FolderIdentifier) => {
|
||||||
|
setExpandedIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
const id = String(folderId);
|
||||||
|
if (next.has(id)) {
|
||||||
|
next.delete(id);
|
||||||
|
} else {
|
||||||
|
next.add(id);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
onToggle(folderId); // propagate event just in case parent needs it
|
||||||
|
}, [onToggle]);
|
||||||
|
|
||||||
const renderNodes = useCallback(
|
const renderNodes = useCallback(
|
||||||
(ids: FolderIdentifier[], depth: number): ReactNode =>
|
(nodes: FolderTreeNode[], depth: number): ReactNode =>
|
||||||
ids.map((id) => {
|
nodes.map((node) => {
|
||||||
const node = folderNodes.get(id);
|
const isExpanded = expandedIds.has(String(node.id));
|
||||||
if (!node) return null;
|
|
||||||
return (
|
return (
|
||||||
<FolderNode
|
<FolderNode
|
||||||
key={id}
|
key={node.id}
|
||||||
node={node}
|
node={node}
|
||||||
depth={depth}
|
depth={depth}
|
||||||
isSelected={selectedFolder === id}
|
isSelected={selectedFolder === node.id}
|
||||||
onToggle={onToggle}
|
onToggle={handleToggle}
|
||||||
|
expanded={isExpanded}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
@@ -66,9 +91,9 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
folderNodes,
|
expandedIds,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
onToggle,
|
handleToggle,
|
||||||
onSelect,
|
onSelect,
|
||||||
onDrop,
|
onDrop,
|
||||||
onDragOver,
|
onDragOver,
|
||||||
@@ -101,7 +126,7 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<ul className="folder-tree">
|
<ul className="folder-tree">
|
||||||
{rootNode && renderNodes([rootNode.id], 0)}
|
{renderNodes(roots, 0)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,16 +9,9 @@ import {
|
|||||||
} from '../../components/icons';
|
} from '../../components/icons';
|
||||||
import type { Identifier } from '../../types/identifiers';
|
import type { Identifier } from '../../types/identifiers';
|
||||||
|
|
||||||
export type FolderIdentifier = Identifier | 'root';
|
import type { FolderTreeNode } from '../../lib/api/apiTypes';
|
||||||
|
|
||||||
export interface FolderTreeNode {
|
export type FolderIdentifier = Identifier | 'root';
|
||||||
id: FolderIdentifier;
|
|
||||||
name?: string;
|
|
||||||
children: FolderIdentifier[];
|
|
||||||
hasChildren?: boolean;
|
|
||||||
loaded?: boolean;
|
|
||||||
expanded?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FolderNodeProps {
|
interface FolderNodeProps {
|
||||||
node: FolderTreeNode;
|
node: FolderTreeNode;
|
||||||
@@ -31,7 +24,8 @@ interface FolderNodeProps {
|
|||||||
onDragLeave: (event: React.DragEvent<HTMLDivElement>) => void;
|
onDragLeave: (event: React.DragEvent<HTMLDivElement>) => void;
|
||||||
onDelete: (folderId: FolderIdentifier) => void;
|
onDelete: (folderId: FolderIdentifier) => void;
|
||||||
onRename?: (folderId: FolderIdentifier, name: string) => void;
|
onRename?: (folderId: FolderIdentifier, name: string) => void;
|
||||||
renderChildren: (ids: FolderIdentifier[], depth: number) => ReactNode;
|
renderChildren: (nodes: FolderTreeNode[], depth: number) => ReactNode;
|
||||||
|
expanded: boolean;
|
||||||
onFolderDragStart?: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
|
onFolderDragStart?: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
|
||||||
onFolderDragEnd?: (event: React.DragEvent<HTMLDivElement>) => void;
|
onFolderDragEnd?: (event: React.DragEvent<HTMLDivElement>) => void;
|
||||||
draggingFolderId?: FolderIdentifier | null;
|
draggingFolderId?: FolderIdentifier | null;
|
||||||
@@ -50,20 +44,21 @@ const FolderNode: React.FC<FolderNodeProps> = ({
|
|||||||
onDelete,
|
onDelete,
|
||||||
onRename,
|
onRename,
|
||||||
renderChildren,
|
renderChildren,
|
||||||
|
expanded,
|
||||||
onFolderDragStart,
|
onFolderDragStart,
|
||||||
onFolderDragEnd,
|
onFolderDragEnd,
|
||||||
draggingFolderId,
|
draggingFolderId,
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
}) => {
|
}) => {
|
||||||
const isRoot = node.id === 'root';
|
const isRoot = node.id === 'root';
|
||||||
const childIds = Array.isArray(node.children) ? node.children : [];
|
const childNodes = node.children || [];
|
||||||
const hasChildren = Boolean(node.hasChildren || childIds.length);
|
const hasChildren = childNodes.length > 0;
|
||||||
const canToggle = hasChildren || !node.loaded;
|
const canToggle = hasChildren; // Simplified: only toggle if we have children to show
|
||||||
const showChevron = canToggle;
|
const showChevron = canToggle;
|
||||||
const icon = showChevron ? <ChevronIcon className="toggle-icon" /> : null;
|
const icon = showChevron ? <ChevronIcon className="toggle-icon" /> : null;
|
||||||
const canDrag = !isRoot;
|
const canDrag = !isRoot;
|
||||||
const isDragging = draggingFolderId === node.id;
|
const isDragging = draggingFolderId === node.id;
|
||||||
const isExpanded = Boolean(node.expanded);
|
const isExpanded = expanded;
|
||||||
const rowClasses = ['folder-row'];
|
const rowClasses = ['folder-row'];
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
rowClasses.push('active');
|
rowClasses.push('active');
|
||||||
@@ -162,9 +157,9 @@ const FolderNode: React.FC<FolderNodeProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isExpanded && childIds.length > 0 && (
|
{isExpanded && childNodes.length > 0 && (
|
||||||
<ul className={`folder-children${depth === 0 ? ' folder-children--level1' : ''}`}>
|
<ul className={`folder-children${depth === 0 ? ' folder-children--level1' : ''}`}>
|
||||||
{renderChildren(childIds, depth + 1)}
|
{renderChildren(childNodes, depth + 1)}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getTagColorStyle } from '../../utils/colors';
|
|||||||
import type { Identifier } from '../../types/identifiers';
|
import type { Identifier } from '../../types/identifiers';
|
||||||
import { useDocumentsFilter } from '../../documents/context/DocumentsFilterContext';
|
import { useDocumentsFilter } from '../../documents/context/DocumentsFilterContext';
|
||||||
|
|
||||||
export interface TagEntry {
|
interface TagEntry {
|
||||||
id: Identifier;
|
id: Identifier;
|
||||||
label: string;
|
label: string;
|
||||||
color?: string | null;
|
color?: string | null;
|
||||||
|
|||||||
@@ -1,20 +1,7 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type { DragEvent } from 'react';
|
import type { DragEvent } from 'react';
|
||||||
import { TAG_FILTER_UNTAGGED } from '../app/workspaceUtils';
|
|
||||||
import type { Identifier } from '../types/identifiers';
|
import type { Identifier } from '../types/identifiers';
|
||||||
|
|
||||||
interface FolderTreeNode {
|
|
||||||
id: Identifier;
|
|
||||||
name?: string;
|
|
||||||
hasChildren?: boolean;
|
|
||||||
loaded?: boolean;
|
|
||||||
expanded?: boolean;
|
|
||||||
children: Identifier[];
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
type FolderNodeMap = Map<Identifier, FolderTreeNode>;
|
|
||||||
|
|
||||||
type FolderToggleHandler = (folderId: Identifier) => void | Promise<void>;
|
type FolderToggleHandler = (folderId: Identifier) => void | Promise<void>;
|
||||||
type FolderDropHandler = (
|
type FolderDropHandler = (
|
||||||
event: DragEvent<HTMLElement>,
|
event: DragEvent<HTMLElement>,
|
||||||
@@ -29,45 +16,8 @@ interface FolderClickHandlers {
|
|||||||
onDragLeave: (event: DragEvent<HTMLElement>) => void;
|
onDragLeave: (event: DragEvent<HTMLElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TagOption {
|
|
||||||
id?: Identifier;
|
|
||||||
label?: string;
|
|
||||||
color?: string | null;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CorrespondentOption {
|
|
||||||
id?: Identifier;
|
|
||||||
name?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TenantOption {
|
|
||||||
id?: Identifier;
|
|
||||||
name?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UploadQueueItem {
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
size?: number | null;
|
|
||||||
folderId?: Identifier | 'root';
|
|
||||||
status?: string;
|
|
||||||
error?: unknown;
|
|
||||||
code?: number | null;
|
|
||||||
document?: unknown;
|
|
||||||
conflictDocumentId?: Identifier | null;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadFilesHandler = (
|
|
||||||
files: FileList | File[] | Iterable<File>,
|
|
||||||
targetFolderId: Identifier | 'root',
|
|
||||||
) => void | Promise<void>;
|
|
||||||
|
|
||||||
interface UseSidebarPropsArgs {
|
interface UseSidebarPropsArgs {
|
||||||
folderNodes: FolderNodeMap;
|
|
||||||
folderClickHandlers: FolderClickHandlers;
|
folderClickHandlers: FolderClickHandlers;
|
||||||
handleFolderDelete: (folderId: Identifier) => void | Promise<void>;
|
handleFolderDelete: (folderId: Identifier) => void | Promise<void>;
|
||||||
handleFolderRename: (folderId: Identifier, name: string) => void | Promise<void>;
|
handleFolderRename: (folderId: Identifier, name: string) => void | Promise<void>;
|
||||||
@@ -77,28 +27,9 @@ interface UseSidebarPropsArgs {
|
|||||||
draggedFolderId: Identifier | null;
|
draggedFolderId: Identifier | null;
|
||||||
handlePromptCreateFolder?: (parentId?: Identifier | null) => void;
|
handlePromptCreateFolder?: (parentId?: Identifier | null) => void;
|
||||||
creatingFolder: boolean;
|
creatingFolder: boolean;
|
||||||
tags: TagOption[];
|
|
||||||
handleTagCreate: (payload: { label?: string }) => void | Promise<void>;
|
|
||||||
correspondents: CorrespondentOption[];
|
|
||||||
handleCorrespondentCreate: (payload: { name?: string }) =>
|
|
||||||
| Promise<CorrespondentOption | null | void>
|
|
||||||
| CorrespondentOption
|
|
||||||
| null
|
|
||||||
| void;
|
|
||||||
appStatus: string;
|
|
||||||
previewActive: boolean;
|
|
||||||
handleLogout: () => void | Promise<void>;
|
|
||||||
tenantName: string | null;
|
|
||||||
tenantOptions: TenantOption[];
|
|
||||||
currentTenantId: Identifier | null;
|
|
||||||
handleTenantSelect: (tenant: TenantOption | null, options?: { refreshOnly?: boolean }) => void | Promise<void>;
|
|
||||||
openSettings?: () => void;
|
|
||||||
handleFileSelection?: UploadFilesHandler;
|
|
||||||
uploadQueue?: UploadQueueItem[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarHookResult {
|
interface SidebarHookResult {
|
||||||
folderNodes: FolderNodeMap;
|
|
||||||
onToggle: FolderClickHandlers['onToggle'];
|
onToggle: FolderClickHandlers['onToggle'];
|
||||||
onSelect: FolderClickHandlers['onSelect'];
|
onSelect: FolderClickHandlers['onSelect'];
|
||||||
onDrop: FolderClickHandlers['onDrop'];
|
onDrop: FolderClickHandlers['onDrop'];
|
||||||
@@ -112,24 +43,9 @@ interface SidebarHookResult {
|
|||||||
draggedFolderId: Identifier | null;
|
draggedFolderId: Identifier | null;
|
||||||
onCreateFolder: UseSidebarPropsArgs['handlePromptCreateFolder'];
|
onCreateFolder: UseSidebarPropsArgs['handlePromptCreateFolder'];
|
||||||
creatingFolder: boolean;
|
creatingFolder: boolean;
|
||||||
tags: TagOption[];
|
|
||||||
untaggedFilterId: typeof TAG_FILTER_UNTAGGED;
|
|
||||||
onCreateTag: (label: string) => void;
|
|
||||||
correspondents: CorrespondentOption[];
|
|
||||||
onCreateCorrespondent: (name: string) => void;
|
|
||||||
previewActive: boolean;
|
|
||||||
onLogout: UseSidebarPropsArgs['handleLogout'];
|
|
||||||
tenantName: string | null;
|
|
||||||
tenants: TenantOption[];
|
|
||||||
activeTenantId: Identifier | null;
|
|
||||||
onSelectTenant: UseSidebarPropsArgs['handleTenantSelect'];
|
|
||||||
onOpenSettings: UseSidebarPropsArgs['openSettings'];
|
|
||||||
onUploadFiles: (files: FileList | File[] | Iterable<File>, targetFolderId?: Identifier | 'root') => void;
|
|
||||||
uploadQueue?: UploadQueueItem[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useSidebarProps = ({
|
const useSidebarProps = ({
|
||||||
folderNodes,
|
|
||||||
folderClickHandlers,
|
folderClickHandlers,
|
||||||
handleFolderDelete,
|
handleFolderDelete,
|
||||||
handleFolderRename,
|
handleFolderRename,
|
||||||
@@ -139,24 +55,9 @@ const useSidebarProps = ({
|
|||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
handlePromptCreateFolder,
|
handlePromptCreateFolder,
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
tags,
|
|
||||||
handleTagCreate,
|
|
||||||
correspondents,
|
|
||||||
handleCorrespondentCreate,
|
|
||||||
appStatus,
|
|
||||||
previewActive,
|
|
||||||
handleLogout,
|
|
||||||
tenantName,
|
|
||||||
tenantOptions,
|
|
||||||
currentTenantId,
|
|
||||||
handleTenantSelect,
|
|
||||||
openSettings,
|
|
||||||
handleFileSelection,
|
|
||||||
uploadQueue,
|
|
||||||
}: UseSidebarPropsArgs): SidebarHookResult =>
|
}: UseSidebarPropsArgs): SidebarHookResult =>
|
||||||
useMemo(
|
useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
folderNodes,
|
|
||||||
onToggle: folderClickHandlers.onToggle,
|
onToggle: folderClickHandlers.onToggle,
|
||||||
onSelect: folderClickHandlers.onSelect,
|
onSelect: folderClickHandlers.onSelect,
|
||||||
onDrop: folderClickHandlers.onDrop,
|
onDrop: folderClickHandlers.onDrop,
|
||||||
@@ -170,60 +71,17 @@ const useSidebarProps = ({
|
|||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
onCreateFolder: (parentId) => handlePromptCreateFolder?.(parentId),
|
onCreateFolder: (parentId) => handlePromptCreateFolder?.(parentId),
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
tags,
|
|
||||||
untaggedFilterId: TAG_FILTER_UNTAGGED,
|
|
||||||
onCreateTag: (label) => handleTagCreate({ label }),
|
|
||||||
correspondents,
|
|
||||||
onCreateCorrespondent: (name) => handleCorrespondentCreate({ name }),
|
|
||||||
appStatus,
|
|
||||||
previewActive,
|
|
||||||
onLogout: handleLogout,
|
|
||||||
tenantName,
|
|
||||||
tenants: tenantOptions,
|
|
||||||
activeTenantId: currentTenantId,
|
|
||||||
onSelectTenant: handleTenantSelect,
|
|
||||||
onOpenSettings: openSettings,
|
|
||||||
onUploadFiles: (files, targetFolderId) => {
|
|
||||||
if (!handleFileSelection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!files) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const normalizedFiles = Array.isArray(files) ? files : Array.from(files);
|
|
||||||
if (normalizedFiles.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const folderId = targetFolderId ?? selectedFolder ?? 'root';
|
|
||||||
handleFileSelection(normalizedFiles, folderId);
|
|
||||||
},
|
|
||||||
uploadQueue,
|
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
handleFileSelection,
|
|
||||||
uploadQueue,
|
|
||||||
appStatus,
|
|
||||||
correspondents,
|
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
currentTenantId,
|
|
||||||
folderClickHandlers,
|
folderClickHandlers,
|
||||||
folderNodes,
|
|
||||||
handleCorrespondentCreate,
|
|
||||||
handleFolderDragEnd,
|
handleFolderDragEnd,
|
||||||
handleFolderDragStart,
|
handleFolderDragStart,
|
||||||
handleFolderDelete,
|
handleFolderDelete,
|
||||||
handleFolderRename,
|
handleFolderRename,
|
||||||
handleLogout,
|
|
||||||
handlePromptCreateFolder,
|
handlePromptCreateFolder,
|
||||||
handleTagCreate,
|
|
||||||
handleTenantSelect,
|
|
||||||
openSettings,
|
|
||||||
previewActive,
|
|
||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
selectedFolder,
|
selectedFolder,
|
||||||
tags,
|
|
||||||
tenantName,
|
|
||||||
tenantOptions,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user