refactor: centralize documents route configuration into new useDocumentsShell hook and export related types.
This commit is contained in:
@@ -1,70 +1,22 @@
|
|||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAppShell } from '../appShellContext';
|
|
||||||
import {
|
import {
|
||||||
DocumentsFilterProvider,
|
DocumentsFilterProvider,
|
||||||
} from '../documents/context/DocumentsFilterContext';
|
} from '../documents/context/DocumentsFilterContext';
|
||||||
import type { DocumentsFilterValue } from '../documents/context/DocumentsFilterContext';
|
|
||||||
import { useWorkspaceSurface } from './useWorkspaceSurface';
|
import { useWorkspaceSurface } from './useWorkspaceSurface';
|
||||||
import { DocumentsHeaderBreadcrumb } from '../documents/panel/DocumentsPanelHeader';
|
import { DocumentsHeaderBreadcrumb } from '../documents/panel/DocumentsPanelHeader';
|
||||||
import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext';
|
import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext';
|
||||||
import { PanelManagerProvider, usePanelManager } from './PanelManagerContext';
|
import { PanelManagerProvider, usePanelManager } from './PanelManagerContext';
|
||||||
import Sidebar from '../sidebar/Sidebar';
|
import Sidebar from '../sidebar/Sidebar';
|
||||||
import type { Identifier } from '../types/identifiers';
|
import useDocumentsShell from './useDocumentsShell';
|
||||||
|
|
||||||
type EnsureAssetUrl = (
|
|
||||||
docId: Identifier,
|
|
||||||
asset: unknown,
|
|
||||||
options?: Record<string, unknown>,
|
|
||||||
) => Promise<unknown> | void;
|
|
||||||
|
|
||||||
type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
|
||||||
type GetDocumentAsset = (document: unknown, assetType: string) => unknown;
|
|
||||||
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
|
||||||
|
|
||||||
interface DocumentsTableProps {
|
|
||||||
breadcrumbs?: DocumentsHeaderBreadcrumb[] | null;
|
|
||||||
onBreadcrumbNavigate?: (crumb: DocumentsHeaderBreadcrumb) => void;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DocumentsRouteAppShell {
|
|
||||||
sidebarProps?: Record<string, unknown> | null;
|
|
||||||
documentsTableProps?: DocumentsTableProps | null;
|
|
||||||
detailPanelProps?: Record<string, unknown> | null;
|
|
||||||
detailPanelOpen?: boolean;
|
|
||||||
openTagsModal?: () => void;
|
|
||||||
openCorrespondentsModal?: () => void;
|
|
||||||
previewWorkspaceDocument?: unknown;
|
|
||||||
documentLink?: unknown;
|
|
||||||
previewDocumentId?: Identifier | null;
|
|
||||||
closeDocumentPreview?: () => void;
|
|
||||||
ensurePreviewData?: EnsurePreviewData;
|
|
||||||
ensureAssetUrl?: EnsureAssetUrl;
|
|
||||||
getDocumentAsset?: GetDocumentAsset;
|
|
||||||
notifyApiError?: NotifyApiError;
|
|
||||||
documentsFilter: DocumentsFilterValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DocumentsRouteContent: React.FC = () => {
|
const DocumentsRouteContent: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
sidebarProps,
|
sidebarProps,
|
||||||
documentsTableProps,
|
surfaceConfig,
|
||||||
detailPanelProps,
|
|
||||||
detailPanelOpen,
|
|
||||||
openTagsModal,
|
|
||||||
openCorrespondentsModal,
|
|
||||||
previewWorkspaceDocument,
|
|
||||||
documentLink,
|
|
||||||
previewDocumentId,
|
|
||||||
closeDocumentPreview,
|
|
||||||
ensurePreviewData,
|
|
||||||
ensureAssetUrl,
|
|
||||||
getDocumentAsset,
|
|
||||||
notifyApiError,
|
|
||||||
documentsFilter,
|
documentsFilter,
|
||||||
} = useAppShell() as unknown as DocumentsRouteAppShell;
|
} = useDocumentsShell();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { collapsed: sidebarCollapsed } = useSidebarContext();
|
const { collapsed: sidebarCollapsed } = useSidebarContext();
|
||||||
const {
|
const {
|
||||||
@@ -72,20 +24,6 @@ const DocumentsRouteContent: React.FC = () => {
|
|||||||
expandSidebar,
|
expandSidebar,
|
||||||
} = usePanelManager();
|
} = usePanelManager();
|
||||||
|
|
||||||
const safeSidebarProps = useMemo<Record<string, unknown>>(
|
|
||||||
() => (sidebarProps && Object(sidebarProps) === sidebarProps ? sidebarProps : {}),
|
|
||||||
[sidebarProps],
|
|
||||||
);
|
|
||||||
|
|
||||||
const sidebarPropsWithActions = useMemo(
|
|
||||||
() => ({
|
|
||||||
...safeSidebarProps,
|
|
||||||
onManageTags: openTagsModal,
|
|
||||||
onManageCorrespondents: openCorrespondentsModal,
|
|
||||||
}),
|
|
||||||
[safeSidebarProps, openTagsModal, openCorrespondentsModal],
|
|
||||||
);
|
|
||||||
|
|
||||||
const sidebarHidden = sidebarCollapsed || sidebarSuppressed;
|
const sidebarHidden = sidebarCollapsed || sidebarSuppressed;
|
||||||
|
|
||||||
const handleHeaderBreadcrumbClick = useCallback((crumb: DocumentsHeaderBreadcrumb) => {
|
const handleHeaderBreadcrumbClick = useCallback((crumb: DocumentsHeaderBreadcrumb) => {
|
||||||
@@ -97,25 +35,16 @@ const DocumentsRouteContent: React.FC = () => {
|
|||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
const documentsTablePropsWithNav = useMemo(() => (
|
const documentsTablePropsWithNav = useMemo(() => (
|
||||||
documentsTableProps
|
surfaceConfig.documentsTableProps
|
||||||
? { ...documentsTableProps, onBreadcrumbNavigate: handleHeaderBreadcrumbClick }
|
? { ...surfaceConfig.documentsTableProps, onBreadcrumbNavigate: handleHeaderBreadcrumbClick }
|
||||||
: null
|
: null
|
||||||
), [documentsTableProps, handleHeaderBreadcrumbClick]);
|
), [surfaceConfig.documentsTableProps, handleHeaderBreadcrumbClick]);
|
||||||
|
|
||||||
const { surface } = useWorkspaceSurface({
|
const { surface } = useWorkspaceSurface({
|
||||||
sidebarHidden,
|
sidebarHidden,
|
||||||
onExpandSidebar: expandSidebar,
|
onExpandSidebar: expandSidebar,
|
||||||
|
...surfaceConfig,
|
||||||
documentsTableProps: documentsTablePropsWithNav,
|
documentsTableProps: documentsTablePropsWithNav,
|
||||||
detailPanelProps,
|
|
||||||
detailPanelOpen,
|
|
||||||
previewWorkspaceDocument,
|
|
||||||
documentLink,
|
|
||||||
previewDocumentId,
|
|
||||||
ensureAssetUrl,
|
|
||||||
ensurePreviewData,
|
|
||||||
getDocumentAsset,
|
|
||||||
notifyApiError,
|
|
||||||
closeDocumentPreview,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -127,7 +56,7 @@ const DocumentsRouteContent: React.FC = () => {
|
|||||||
|
|
||||||
const renderSurface = () => {
|
const renderSurface = () => {
|
||||||
const layoutClass = `documents-main${sidebarHidden ? ' documents-main--sidebar-hidden' : ''}`;
|
const layoutClass = `documents-main${sidebarHidden ? ' documents-main--sidebar-hidden' : ''}`;
|
||||||
const sidebarNode = !sidebarHidden ? <Sidebar {...sidebarPropsWithActions} /> : null;
|
const sidebarNode = !sidebarHidden && sidebarProps ? <Sidebar {...sidebarProps} /> : null;
|
||||||
const surfaceDetail = surface && (surface as { detail?: ReactNode }).detail ? (surface as { detail?: ReactNode }).detail : null;
|
const surfaceDetail = surface && (surface as { detail?: ReactNode }).detail ? (surface as { detail?: ReactNode }).detail : null;
|
||||||
const surfaceBody = surface ? surface.content : null;
|
const surfaceBody = surface ? surface.content : null;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { useAppShell } from '../appShellContext';
|
||||||
|
import type { DocumentsFilterValue } from '../documents/context/DocumentsFilterContext';
|
||||||
|
import type Sidebar from '../sidebar/Sidebar';
|
||||||
|
import type { UseWorkspaceSurfaceArgs } from './useWorkspaceSurface';
|
||||||
|
import type { Identifier } from '../types/identifiers';
|
||||||
|
|
||||||
|
type WorkspaceSurfaceConfig = Omit<UseWorkspaceSurfaceArgs, 'sidebarHidden' | 'onExpandSidebar'>;
|
||||||
|
|
||||||
|
interface DocumentsShellView {
|
||||||
|
sidebarProps: ComponentProps<typeof Sidebar> | null;
|
||||||
|
surfaceConfig: WorkspaceSurfaceConfig;
|
||||||
|
documentsFilter: DocumentsFilterValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useDocumentsShell = (): DocumentsShellView => {
|
||||||
|
const shell = useAppShell();
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const sidebar = (shell.sidebarProps as ComponentProps<typeof Sidebar> | undefined) || null;
|
||||||
|
const surfaceConfig: WorkspaceSurfaceConfig = {
|
||||||
|
documentsTableProps: (shell.documentsTableProps ?? null) as WorkspaceSurfaceConfig['documentsTableProps'],
|
||||||
|
detailPanelProps: (shell.detailPanelProps ?? null) as WorkspaceSurfaceConfig['detailPanelProps'],
|
||||||
|
detailPanelOpen: Boolean(shell.detailPanelOpen),
|
||||||
|
previewWorkspaceDocument: shell.previewWorkspaceDocument,
|
||||||
|
documentLink: shell.documentLink,
|
||||||
|
previewDocumentId: (shell.previewDocumentId as Identifier | null | undefined) ?? null,
|
||||||
|
closeDocumentPreview: shell.closeDocumentPreview as WorkspaceSurfaceConfig['closeDocumentPreview'],
|
||||||
|
ensurePreviewData: shell.ensurePreviewData as WorkspaceSurfaceConfig['ensurePreviewData'],
|
||||||
|
ensureAssetUrl: shell.ensureAssetUrl as WorkspaceSurfaceConfig['ensureAssetUrl'],
|
||||||
|
getDocumentAsset: shell.getDocumentAsset as WorkspaceSurfaceConfig['getDocumentAsset'],
|
||||||
|
notifyApiError: shell.notifyApiError as WorkspaceSurfaceConfig['notifyApiError'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const sidebarWithActions = sidebar
|
||||||
|
? {
|
||||||
|
...sidebar,
|
||||||
|
onManageTags: shell.openTagsModal as (() => void) | undefined,
|
||||||
|
onManageCorrespondents: shell.openCorrespondentsModal as (() => void) | undefined,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
sidebarProps: sidebarWithActions,
|
||||||
|
surfaceConfig,
|
||||||
|
documentsFilter: shell.documentsFilter as DocumentsFilterValue,
|
||||||
|
};
|
||||||
|
}, [shell]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDocumentsShell;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ComponentProps, ReactNode } from 'react';
|
||||||
import { SidebarExpandIcon } from '../ui/icons';
|
import { SidebarExpandIcon } from '../ui/icons';
|
||||||
import DocumentsPanel from '../documents/panel/DocumentsPanel';
|
import DocumentsPanel from '../documents/panel/DocumentsPanel';
|
||||||
import DocumentViewerPanel from '../preview/DocumentViewerPanel';
|
import DocumentViewerPanel from '../preview/DocumentViewerPanel';
|
||||||
@@ -7,18 +7,33 @@ import { usePanelManager } from './PanelManagerContext';
|
|||||||
import { FolderManagerProvider } from '../folders/FolderManagerContext';
|
import { FolderManagerProvider } from '../folders/FolderManagerContext';
|
||||||
import type { Identifier } from '../types/identifiers';
|
import type { Identifier } from '../types/identifiers';
|
||||||
|
|
||||||
type EnsureAssetUrl = (docId: Identifier, asset: unknown, options?: Record<string, unknown>) => Promise<unknown> | void;
|
export type EnsureAssetUrl = (
|
||||||
type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
docId: Identifier,
|
||||||
type GetDocumentAsset = (document: unknown, assetType: string) => unknown;
|
asset: unknown,
|
||||||
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
options?: Record<string, unknown>,
|
||||||
|
) => Promise<unknown> | void;
|
||||||
|
export type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
||||||
|
export type GetDocumentAsset = (document: unknown, assetType: string) => unknown;
|
||||||
|
export type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
||||||
|
|
||||||
type WorkspaceSurface = { content: ReactNode; detail?: ReactNode | null } | null;
|
export type DocumentsTableProps = ComponentProps<typeof DocumentsPanel> | null;
|
||||||
|
export type DetailPanelProps = (ComponentProps<typeof DocumentViewerPanel> & {
|
||||||
|
onClose?: () => void;
|
||||||
|
onOpenPreview?: (args: { documentIds: Array<string> }) => void;
|
||||||
|
folderNodes?: Map<Identifier | 'root', unknown>;
|
||||||
|
ensureFolderData?: (
|
||||||
|
folderId: Identifier | 'root',
|
||||||
|
options?: { force?: boolean; includeDocuments?: boolean },
|
||||||
|
) => Promise<void>;
|
||||||
|
}) | null;
|
||||||
|
|
||||||
interface UseWorkspaceSurfaceArgs {
|
export type WorkspaceSurface = { content: ReactNode; detail?: ReactNode | null } | null;
|
||||||
|
|
||||||
|
export interface UseWorkspaceSurfaceArgs {
|
||||||
sidebarHidden?: boolean;
|
sidebarHidden?: boolean;
|
||||||
onExpandSidebar?: () => void;
|
onExpandSidebar?: () => void;
|
||||||
documentsTableProps?: Record<string, any> | null;
|
documentsTableProps?: DocumentsTableProps;
|
||||||
detailPanelProps?: (Record<string, any> & { onClose?: () => void }) | null;
|
detailPanelProps?: DetailPanelProps;
|
||||||
detailPanelOpen?: boolean;
|
detailPanelOpen?: boolean;
|
||||||
previewWorkspaceDocument?: unknown;
|
previewWorkspaceDocument?: unknown;
|
||||||
documentLink?: unknown;
|
documentLink?: unknown;
|
||||||
@@ -30,7 +45,7 @@ interface UseWorkspaceSurfaceArgs {
|
|||||||
closeDocumentPreview?: () => void;
|
closeDocumentPreview?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseWorkspaceSurfaceResult {
|
export interface UseWorkspaceSurfaceResult {
|
||||||
surface: WorkspaceSurface;
|
surface: WorkspaceSurface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ const splitLabelSegments = (input: unknown): string[] => {
|
|||||||
const buildFolderTreeOptions = (tree?: FolderTreeNode[] | null): SelectionAssignmentMenuItem[] => {
|
const buildFolderTreeOptions = (tree?: FolderTreeNode[] | null): SelectionAssignmentMenuItem[] => {
|
||||||
const entries: SelectionAssignmentMenuItem[] = [];
|
const entries: SelectionAssignmentMenuItem[] = [];
|
||||||
|
|
||||||
const traverse = (nodes: FolderTreeNode[] | undefined | null, parentSegments: string[]) => {
|
const traverse = (nodes: FolderTreeNode[] | null, parentSegments: string[]) => {
|
||||||
if (!Array.isArray(nodes) || nodes.length === 0) {
|
if (!Array.isArray(nodes) || nodes.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ const useWorkspaceBreadcrumbs = ({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fallbackName: string | null | undefined = '…';
|
let fallbackName: string | null = '…';
|
||||||
let parentId: FolderId | null | undefined = null;
|
let parentId: FolderId | null = null;
|
||||||
|
|
||||||
if (currentFolder && currentFolder.id === currentId) {
|
if (currentFolder && currentFolder.id === currentId) {
|
||||||
fallbackName = currentFolder.name;
|
fallbackName = currentFolder.name;
|
||||||
|
|||||||
Reference in New Issue
Block a user