frontend_nyla/src/core/PageManager/pageInterface.ts
2026-01-21 10:59:34 +01:00

426 lines
18 KiB
TypeScript

import React from 'react';
import { IconType } from 'react-icons';
import { DragDropConfig } from '../../components/UiComponents/DragDropOverlay/DragDropOverlay';
// Generic privilege checker function type
export type PrivilegeChecker = () => boolean | Promise<boolean>;
// Form field configuration for create/edit buttons
export interface ButtonFormField {
key: string;
label: string | LanguageText;
type: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'readonly' | 'multiselect' | 'number';
required?: boolean;
placeholder?: string | LanguageText;
minRows?: number;
maxRows?: number;
validator?: (value: any) => string | null;
defaultValue?: any;
options?: string[] | Array<{ value: string | number; label: string }>; // For enum/multiselect fields
optionsReference?: string; // Reference to a data source for dynamic options (e.g., 'TrusteeOrganisation', 'User')
}
// Dropdown configuration for header dropdown buttons
export interface DropdownConfig<T = any> {
type: 'dropdown';
items: Array<{
id: string | number;
label: string | LanguageText;
value: T;
metadata?: Record<string, any>;
}>;
selectedItemId?: string | number | null;
onSelect: (item: { id: string | number; label: string | LanguageText; value: T; metadata?: Record<string, any> } | null, hookData?: any) => void | Promise<void>;
placeholder?: string | LanguageText;
emptyMessage?: string | LanguageText;
headerText?: string | LanguageText;
// Optional: name of property in hookData that provides items, selectedItemId, and onSelect
dataSource?: {
itemsProperty?: string; // Property name in hookData that contains items array
selectedIdProperty?: string; // Property name in hookData that contains selectedItemId
onSelectMethod?: string; // Method name in hookData for onSelect callback
loadingProperty?: string; // Property name in hookData that contains loading state
};
}
// Button configuration for header actions
export interface PageButton {
id: string;
label: string | LanguageText;
variant?: 'primary' | 'secondary' | 'danger' | 'success' | 'warning';
size?: 'sm' | 'md' | 'lg';
icon?: IconType;
onClick?: (hookData?: any) => void | Promise<void>;
disabled?: boolean | ((hookData?: any) => boolean | { disabled: boolean; message?: string });
// Form configuration for create buttons
formConfig?: {
fields: ButtonFormField[];
popupTitle?: string | LanguageText;
popupSize?: 'small' | 'medium' | 'large';
createOperationName?: string; // Name of the create operation in hookData (e.g., 'handlePromptCreate')
successMessage?: string | LanguageText;
errorMessage?: string | LanguageText;
multiStep?: boolean; // Enable multi-step form mode
};
// Dropdown configuration for dropdown selection buttons
dropdownConfig?: DropdownConfig;
}
// Input form configuration
export interface InputFormConfig {
hookFactory?: () => () => GenericDataHook; // Optional, can use page-level hook from tableConfig if exists
placeholder?: string | LanguageText;
buttonLabel?: string | LanguageText;
stopButtonLabel?: string | LanguageText; // Label for stop button when workflow is running
buttonIcon?: IconType;
stopButtonIcon?: IconType; // Icon for stop button when workflow is running
buttonVariant?: 'primary' | 'secondary' | 'danger' | 'success' | 'warning';
stopButtonVariant?: 'primary' | 'secondary' | 'danger' | 'success' | 'warning'; // Variant for stop button
buttonSize?: 'sm' | 'md' | 'lg';
textFieldSize?: 'sm' | 'md' | 'lg';
showFileUpload?: boolean; // Whether to show file upload button (default: true if hook provides file upload)
}
// Settings field configuration
export interface SettingsFieldConfig {
id: string;
type: 'text' | 'select' | 'toggle';
label: string | LanguageText;
description?: string | LanguageText;
required?: boolean;
disabled?: boolean | ((data: any) => boolean);
inputType?: 'text' | 'email' | 'tel';
placeholder?: string | LanguageText;
options?: Array<{id: string | number; label: string | LanguageText; value: any}>;
dataKey: string; // Dot-notation path (e.g., "mandate_general.company_name")
onSave?: (value: any, data: any) => Promise<void> | void;
}
// Settings section configuration
export interface SettingsSectionConfig {
id: string;
title: string | LanguageText;
description?: string | LanguageText;
icon?: IconType; // Optional icon for section header
// Fields will be loaded from backend based on sectionId
sectionId: string; // Identifier sent to backend to fetch fields
// Save handler per section (uses Button component)
onSave?: (sectionId: string, data: any) => Promise<void>;
saveButtonLabel?: string | LanguageText;
saveButtonVariant?: 'primary' | 'secondary' | 'danger' | 'success' | 'warning';
saveButtonSize?: 'sm' | 'md' | 'lg';
// Optional: Static field overrides from config (takes precedence over backend)
staticFields?: SettingsFieldConfig[];
// Optional: Conditional rendering - if provided, section only renders if condition returns true
renderCondition?: (formData: any) => boolean;
// Optional: Alternative content to render when renderCondition returns false
renderAlternative?: (formData: any, t: (key: string) => string, resolveLanguageText: (text: string | LanguageText, t: (key: string) => string) => string) => React.ReactNode;
}
// Settings configuration
export interface SettingsConfig {
sections: SettingsSectionConfig[];
hookFactory?: () => () => GenericDataHook; // For data fetching and field loading
}
// Content section for paragraphs
export interface PageContent {
id: string;
type: 'paragraph' | 'heading' | 'list' | 'code' | 'divider' | 'custom' | 'table' | 'inputForm' | 'messages' | 'settings' | 'log' | 'tabs' | 'columns' | 'chatHistory';
content?: string | LanguageText; // Optional for dividers
level?: number; // For headings (1-6)
items?: (string | LanguageText)[]; // For lists
language?: string; // For code blocks
customComponent?: React.ComponentType<any>;
// Table-specific properties
tableConfig?: TableContentConfig;
// Input form-specific properties
inputFormConfig?: InputFormConfig;
// Messages-specific properties
messagesConfig?: {
variant?: 'chat' | 'log'; // Message display variant: 'chat' for bubble UI, 'log' for list/table UI
dataSource?: 'messages' | 'logs' | 'both'; // Data source to render: 'messages' (default), 'logs', or 'both' (logs use log variant when merged)
showDocuments?: boolean;
showMetadata?: boolean;
showProgress?: boolean;
emptyMessage?: string | LanguageText;
};
// Settings-specific properties
settingsConfig?: SettingsConfig;
// Log-specific properties
logConfig?: {
emptyMessage?: string | LanguageText;
};
// Chat history-specific properties
chatHistoryConfig?: {
emptyMessage?: string | LanguageText;
};
// Tabs-specific properties
tabsConfig?: {
tabs: Array<{
id: string;
label: string | LanguageText;
content: PageContent[]; // Nested content sections for each tab
}>;
defaultTabId?: string;
};
// Columns-specific properties
columnsConfig?: {
columns: Array<{
id: string;
width?: string; // CSS width (e.g., "3fr", "1fr", "75%", "25%")
content: PageContent[]; // Nested content sections for each column
}>;
gap?: string; // CSS gap value
};
}
// Generic hook interface for data fetching
export interface GenericDataHook {
data: any[];
loading: boolean;
isRefetching?: boolean; // True when refetching data (keeps existing data visible)
error: string | null;
refetch?: (params?: { page?: number; pageSize?: number; sort?: Array<{field: string; direction: 'asc' | 'desc'}>; filters?: any; search?: string }) => Promise<void>;
pagination?: {
currentPage: number;
pageSize: number;
totalItems: number;
totalPages: number;
sort?: Array<{ field: string; direction: 'asc' | 'desc' }>;
filters?: any;
} | null;
removeFileOptimistically?: (fileId: string) => void; // For optimistic updates
columns?: any[]; // Optional columns configuration
// File operations
handleUpload?: (file: File) => Promise<{ success: boolean; data: any }>; // For file upload functionality
handleFileUpload?: (file: File) => Promise<{ success: boolean; data: any }>; // Alias for handleUpload
handleDownload?: (fileId: string, fileName: string) => Promise<boolean>; // For file download functionality
handleDelete?: (fileId: string, onOptimisticDelete?: () => void) => Promise<boolean>; // For file delete functionality
handleFileDelete?: ((fileId: string, onOptimisticDelete?: () => void) => Promise<boolean>) | ((file: any) => Promise<void>); // Can accept fileId or WorkflowFile
handlePreview?: (fileId: string, fileName: string, mimeType?: string) => Promise<any>; // For file preview functionality
// File management properties
workflowFiles?: any[]; // Files connected to workflow
pendingFiles?: any[]; // Files pending attachment
allUserFiles?: any[]; // All user files
handleFileRemove?: ((fileId: string) => Promise<void> | void) | ((file: any) => Promise<void> | void); // Can accept fileId or WorkflowFile
handleFileAttach?: (fileId: string) => Promise<void>; // Attach file to workflow (always returns Promise)
handleFileUploadAndAttach?: (file: File) => Promise<{ success: boolean; data: any }>; // Upload and attach file
uploadingFile?: boolean; // Loading state for file upload
deletingFiles?: Set<string>; // Set of file IDs being deleted
previewingFiles?: Set<string>; // Set of file IDs being previewed
removingFiles?: Set<string>; // Set of file IDs being removed
isFileAttachmentPopupOpen?: boolean; // Whether file attachment popup is open
setIsFileAttachmentPopupOpen?: (open: boolean) => void; // Set file attachment popup state
// FormGenerator specific handlers
onDelete?: (row: any) => Promise<void>; // For single item deletion
onDeleteMultiple?: (rows: any[]) => Promise<void>; // For multiple item deletion
// Input form operations
inputValue?: string;
onInputChange?: (value: string) => void;
handleSubmit?: () => Promise<void>; // No parameters, uses internal inputValue
isSubmitting?: boolean;
// Prompt selector properties
promptPermission?: {
view?: boolean;
read?: string;
};
promptItems?: Array<{ id: string | number; label: string; value: any }>;
selectedPromptId?: string | number | null;
onPromptSelect?: (item: { id: string | number; label: string; value: any } | null) => void | Promise<void>;
promptsLoading?: boolean;
// Workflow mode selector properties
workflowModeItems?: Array<{ id: string | number; label: string; value: any }>;
selectedWorkflowMode?: string | number | null;
onWorkflowModeSelect?: (item: { id: string | number; label: string; value: any } | null) => void | Promise<void>;
// Workflow lifecycle state
workflowId?: string;
workflowStatus?: string;
workflowData?: {
currentRound?: number;
[key: string]: any;
};
isRunning?: boolean;
currentRound?: number; // Current workflow round
latestStats?: any; // Latest workflow statistics
// Messages from workflow
messages?: any[];
// Logs from workflow
logs?: any[];
// Dashboard log tree
dashboardTree?: any; // Dashboard log tree structure
onToggleOperationExpanded?: (operationId: string) => void;
getChildOperations?: (parentId: string | null) => string[];
// Settings-specific properties
settingsData?: any; // Unified data object for settings fields
settingsFields?: Record<string, SettingsFieldConfig[]>; // Field definitions per sectionId
settingsLoading?: Record<string, boolean>; // Loading state per section
settingsErrors?: Record<string, string | null>; // Error state per section
saveSection?: (sectionId: string, data: any) => Promise<void>; // Save handler for a section
// Dropdown data source loading property
[key: string]: any; // Allow additional properties for dynamic data sources
}
// Standard action button configuration (built-in actions: edit, delete, view, copy, connect, play)
export interface ActionButtonConfig {
type: 'view' | 'edit' | 'delete' | 'copy' | 'connect' | 'play';
onAction?: (row: any) => Promise<void> | void; // Optional for delete buttons since they handle their own logic
title?: string | LanguageText;
disabled?: (row: any, hookData?: any) => boolean | { disabled: boolean; message?: string };
loading?: (row: any, hookData?: any) => boolean;
// Field mappings for flexible data access
idField?: string; // Field name for the unique identifier (default: 'id')
nameField?: string; // Field name for display name (default: 'name' or 'file_name')
typeField?: string; // Field name for type/mime type (default: 'type' or 'mime_type')
contentField?: string; // Field name for content (default: 'content')
statusField?: string; // Field name for status (used by connect action)
// Operation and loading state names
operationName?: string; // Name of the operation function in hookData
loadingStateName?: string; // Name of the loading state in hookData
fetchItemFunctionName?: string; // Name of the function in hookData to fetch a single item by ID (for edit button)
// Navigation and mode (for play action)
navigateTo?: string; // Path to navigate to after action
mode?: string; // Mode to set (e.g., 'prompt', 'workflow')
}
// Custom action button configuration (for entity-specific actions like download, connect, play, sendPasswordLink)
export interface CustomActionConfig {
id: string; // Unique identifier for the action
icon: React.ReactNode; // Icon component to display
onClick: (row: any, hookData?: any) => Promise<void> | void; // Handler function
visible?: (row: any, hookData?: any) => boolean; // Show/hide based on row data (default: true)
disabled?: (row: any, hookData?: any) => boolean | { disabled: boolean; message?: string }; // Disable based on row data
loading?: (row: any, hookData?: any) => boolean; // Loading state based on row data
title?: string | LanguageText | ((row: any) => string); // Tooltip text
className?: string; // Optional custom CSS class
// Field mappings (optional, for convenience)
idField?: string; // Field name for the unique identifier (default: 'id')
}
// Table content configuration
export interface TableContentConfig {
hookFactory: () => () => GenericDataHook; // Hook factory that returns a hook function
columns?: any[]; // Column configuration (optional - can be generated dynamically from attributes via hookData.columns)
actionButtons?: ActionButtonConfig[]; // Standard action buttons configuration (edit, delete, view, copy)
customActions?: CustomActionConfig[]; // Custom action buttons (download, connect, play, sendPasswordLink, etc.)
searchable?: boolean;
filterable?: boolean;
sortable?: boolean;
resizable?: boolean;
pagination?: boolean;
pageSize?: number;
className?: string;
emptyMessage?: string; // Custom message to display when table is empty
}
// Language-aware text interface
export interface LanguageText {
de: string;
en: string;
fr: string;
}
// Utility function to resolve language text
export const resolveLanguageText = (text: string | LanguageText | undefined, t?: (key: string, fallback?: string) => string): string => {
if (!text) return '';
if (typeof text === 'string') {
// Always use the translation function for strings (language keys)
if (t) {
return t(text);
}
return text;
}
// For LanguageText objects, we should convert them to language keys
// For now, fallback to the first available language
return text.de || text.en || text.fr || '';
};
// Generic page data interface
export interface GenericPageData {
// Core identification
id: string;
path: string;
name: string;
description?: string | LanguageText;
// Navigation
parentPath?: string; // For subpages/subsubpages
order?: number;
showInSidebar?: boolean;
// Visual
icon?: IconType;
title: string | LanguageText;
subtitle?: string | LanguageText;
// Header configuration
headerButtons?: PageButton[];
// Content sections
content?: PageContent[];
// Page behavior
persistent?: boolean;
preserveState?: boolean;
preload?: boolean;
moduleEnabled?: boolean;
hide?: boolean; // If true, page is completely hidden and not rendered
// Subpage support
hasSubpages?: boolean;
// Lifecycle hooks
onActivate?: () => void | Promise<void>;
onDeactivate?: () => void | Promise<void>;
onLoad?: () => void | Promise<void>;
onUnload?: () => void | Promise<void>;
// Custom component override (optional)
customComponent?: React.ComponentType<any>;
// Privilege checker - if provided, page will only render if checker returns true
privilegeChecker?: PrivilegeChecker;
// Drag and drop configuration
dragDropConfig?: DragDropConfig;
}
// Page data file structure
export interface PageDataFile {
page: GenericPageData;
subpages?: PageDataFile[];
}
// Sidebar item interface for compatibility
export interface SidebarItem {
id: string;
name: string;
link: string | undefined; // Allow undefined for parent groups that aren't clickable pages
icon?: IconType | React.ComponentType<React.SVGProps<SVGSVGElement>>; // Allow both IconType and SVG components
moduleEnabled: boolean;
order: number;
submenu?: SidebarSubmenuItemData[];
}
// Sidebar submenu item data interface
export interface SidebarSubmenuItemData {
id: string;
name: string;
link: string;
icon?: IconType;
}
// Page instance for PageManager
export interface PageInstance {
path: string;
component: React.ReactElement;
isActive: boolean;
shouldPreserve: boolean;
pageData: GenericPageData;
}
// Page manager props
export interface PageManagerProps {
loadingComponent: React.ComponentType;
errorComponent: React.ComponentType;
}