659 lines
23 KiB
TypeScript
659 lines
23 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { useApiRequest } from './useApi';
|
|
import {
|
|
deleteWorkflowApi,
|
|
deleteWorkflowsApi,
|
|
updateWorkflowApi,
|
|
fetchWorkflows as fetchWorkflowsApi,
|
|
fetchWorkflow as fetchWorkflowByIdApi,
|
|
fetchAttributes as fetchAttributesApi,
|
|
startWorkflowApi,
|
|
stopWorkflowApi,
|
|
deleteMessageApi,
|
|
deleteFileFromMessageApi,
|
|
type Workflow,
|
|
type AttributeDefinition,
|
|
type StartWorkflowRequest
|
|
} from '../api/workflowApi';
|
|
import { useWorkflowSelection } from '../contexts/WorkflowSelectionContext';
|
|
import { usePermissions, type UserPermissions } from './usePermissions';
|
|
|
|
// Workflow interface matching backend
|
|
export interface UserWorkflow {
|
|
id: string;
|
|
mandateId: string;
|
|
status: string;
|
|
name?: string;
|
|
workflowMode?: string;
|
|
[key: string]: any; // Allow additional properties
|
|
}
|
|
|
|
// Re-export AttributeDefinition from workflowApi
|
|
export type { AttributeDefinition } from '../api/workflowApi';
|
|
|
|
// Attribute option interface (from backend)
|
|
export interface AttributeOption {
|
|
value: string | number;
|
|
label: string | { [key: string]: string }; // Can be string or object with language keys
|
|
}
|
|
|
|
// Pagination parameters
|
|
export interface PaginationParams {
|
|
page?: number;
|
|
pageSize?: number;
|
|
sort?: Array<{ field: string; direction: 'asc' | 'desc' }>;
|
|
filters?: Record<string, any>;
|
|
search?: string;
|
|
}
|
|
|
|
/** Get apiBaseUrl from instanceId and featureCode for feature-scoped workflow APIs */
|
|
export function getWorkflowApiBaseUrl(instanceId: string | undefined, featureCode: string | undefined): string | undefined {
|
|
if (!instanceId || !featureCode) return undefined;
|
|
if (featureCode === 'automation') return `/api/automations/${instanceId}`;
|
|
return undefined;
|
|
}
|
|
|
|
// Workflows list hook - pass instanceId and featureCode when in feature context for feature-scoped API
|
|
export function useUserWorkflows(options?: { instanceId?: string; featureCode?: string }) {
|
|
const apiBaseUrl = getWorkflowApiBaseUrl(options?.instanceId, options?.featureCode);
|
|
const [workflows, setWorkflows] = useState<UserWorkflow[]>([]);
|
|
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
|
|
const [permissions, setPermissions] = useState<UserPermissions | null>(null);
|
|
const [pagination, setPagination] = useState<{
|
|
currentPage: number;
|
|
pageSize: number;
|
|
totalItems: number;
|
|
totalPages: number;
|
|
} | null>(null);
|
|
const { request, isLoading: loading, error } = useApiRequest<null, Workflow[]>();
|
|
const { checkPermission } = usePermissions();
|
|
|
|
// Fetch attributes from backend
|
|
const fetchAttributes = useCallback(async () => {
|
|
try {
|
|
const attrs = await fetchAttributesApi(request, 'ChatWorkflow');
|
|
setAttributes(attrs);
|
|
return attrs;
|
|
} catch (error: any) {
|
|
console.error('Error fetching attributes:', error);
|
|
setAttributes([]);
|
|
return [];
|
|
}
|
|
}, [request]);
|
|
|
|
// Fetch permissions from backend
|
|
const fetchPermissions = useCallback(async () => {
|
|
try {
|
|
const perms = await checkPermission('DATA', 'ChatWorkflow');
|
|
setPermissions(perms);
|
|
return perms;
|
|
} catch (error: any) {
|
|
console.error('Error fetching permissions:', error);
|
|
const defaultPerms: UserPermissions = {
|
|
view: false,
|
|
read: 'n',
|
|
create: 'n',
|
|
update: 'n',
|
|
delete: 'n',
|
|
};
|
|
setPermissions(defaultPerms);
|
|
return defaultPerms;
|
|
}
|
|
}, [checkPermission]);
|
|
|
|
const fetchWorkflowsData = useCallback(async (params?: PaginationParams) => {
|
|
try {
|
|
const requestParams: any = {};
|
|
|
|
// Build pagination object if provided
|
|
if (params) {
|
|
const paginationObj: any = {};
|
|
|
|
if (params.page !== undefined) paginationObj.page = params.page;
|
|
if (params.pageSize !== undefined) paginationObj.pageSize = params.pageSize;
|
|
if (params.sort) paginationObj.sort = params.sort;
|
|
if (params.filters) paginationObj.filters = params.filters;
|
|
if (params.search) paginationObj.search = params.search;
|
|
|
|
if (Object.keys(paginationObj).length > 0) {
|
|
requestParams.pagination = JSON.stringify(paginationObj);
|
|
}
|
|
}
|
|
|
|
let data: any;
|
|
if (!apiBaseUrl) {
|
|
console.error('useUserWorkflows: apiBaseUrl is required (missing instanceId/featureCode)');
|
|
return;
|
|
}
|
|
const url = `${apiBaseUrl}/workflows`;
|
|
if (Object.keys(requestParams).length > 0) {
|
|
data = await request({ url, method: 'get', params: requestParams });
|
|
} else {
|
|
data = await fetchWorkflowsApi(request, undefined, apiBaseUrl);
|
|
}
|
|
|
|
// Handle paginated response
|
|
if (data && typeof data === 'object' && 'items' in data) {
|
|
const items = Array.isArray(data.items) ? data.items : [];
|
|
// Map API response to our frontend model
|
|
const mappedWorkflows = items.map((apiWorkflow: any): UserWorkflow => {
|
|
return {
|
|
id: apiWorkflow.id,
|
|
mandateId: apiWorkflow.mandateId || '',
|
|
status: apiWorkflow.status || 'unknown',
|
|
name: apiWorkflow.name,
|
|
workflowMode: apiWorkflow.workflowMode,
|
|
...apiWorkflow // Include any additional properties
|
|
};
|
|
});
|
|
setWorkflows(mappedWorkflows);
|
|
if (data.pagination) {
|
|
setPagination(data.pagination);
|
|
}
|
|
} else {
|
|
// Handle non-paginated response (backward compatibility)
|
|
const items = Array.isArray(data) ? data : [];
|
|
const mappedWorkflows = items.map((apiWorkflow: any): UserWorkflow => {
|
|
return {
|
|
id: apiWorkflow.id,
|
|
mandateId: apiWorkflow.mandateId || '',
|
|
status: apiWorkflow.status || 'unknown',
|
|
name: apiWorkflow.name,
|
|
workflowMode: apiWorkflow.workflowMode,
|
|
...apiWorkflow
|
|
};
|
|
});
|
|
setWorkflows(mappedWorkflows);
|
|
setPagination(null);
|
|
}
|
|
} catch (error: any) {
|
|
// Error is already handled by useApiRequest
|
|
setWorkflows([]);
|
|
setPagination(null);
|
|
}
|
|
}, [request, apiBaseUrl]);
|
|
|
|
// Optimistically remove a workflow from the local state
|
|
const removeOptimistically = (workflowId: string) => {
|
|
setWorkflows(prevWorkflows => prevWorkflows.filter(workflow => workflow.id !== workflowId));
|
|
};
|
|
|
|
// Optimistically update a workflow in the local state
|
|
const updateOptimistically = (workflowId: string, updateData: Partial<UserWorkflow>) => {
|
|
setWorkflows(prevWorkflows =>
|
|
prevWorkflows.map(workflow =>
|
|
workflow.id === workflowId
|
|
? { ...workflow, ...updateData }
|
|
: workflow
|
|
)
|
|
);
|
|
};
|
|
|
|
// Fetch a single workflow by ID
|
|
const fetchWorkflowById = useCallback(async (workflowId: string): Promise<UserWorkflow | null> => {
|
|
try {
|
|
const workflow = await fetchWorkflowByIdApi(request, workflowId, apiBaseUrl);
|
|
return workflow as UserWorkflow | null;
|
|
} catch (error: any) {
|
|
console.error('Error fetching workflow by ID:', error);
|
|
return null;
|
|
}
|
|
}, [request, apiBaseUrl]);
|
|
|
|
// Generate edit fields from attributes dynamically
|
|
const generateEditFieldsFromAttributes = useCallback((): Array<{
|
|
key: string;
|
|
label: string;
|
|
type: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'multiselect' | 'readonly';
|
|
editable?: boolean;
|
|
required?: boolean;
|
|
validator?: (value: any) => string | null;
|
|
minRows?: number;
|
|
maxRows?: number;
|
|
options?: Array<{ value: string | number; label: string }>;
|
|
optionsReference?: string; // For options that need to be fetched (e.g., "user.role")
|
|
}> => {
|
|
if (!attributes || attributes.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const editableFields = attributes
|
|
.filter(attr => {
|
|
// Filter out non-editable fields based on readonly/editable flags
|
|
if (attr.readonly === true || attr.editable === false) {
|
|
return false; // Don't show readonly fields in edit form
|
|
}
|
|
// Also filter out common non-editable fields
|
|
const nonEditableFields = ['id', 'mandateId', '_createdBy', '_hideDelete'];
|
|
return !nonEditableFields.includes(attr.name);
|
|
})
|
|
.map(attr => {
|
|
// Map backend attribute type to form field type
|
|
let fieldType: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'multiselect' | 'readonly' = 'string';
|
|
let options: Array<{ value: string | number; label: string }> | undefined = undefined;
|
|
let optionsReference: string | undefined = undefined;
|
|
|
|
// Map backend types to form field types
|
|
if (attr.type === 'checkbox') {
|
|
fieldType = 'boolean';
|
|
} else if (attr.type === 'email') {
|
|
fieldType = 'email';
|
|
} else if (attr.type === 'date') {
|
|
fieldType = 'date';
|
|
} else if (attr.type === 'select') {
|
|
fieldType = 'enum';
|
|
// Handle options - can be array or string reference
|
|
if (Array.isArray(attr.options)) {
|
|
options = attr.options.map(opt => {
|
|
const labelValue = typeof opt.label === 'string'
|
|
? opt.label
|
|
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
|
|
return {
|
|
value: opt.value,
|
|
label: labelValue
|
|
};
|
|
});
|
|
} else if (typeof attr.options === 'string') {
|
|
optionsReference = attr.options;
|
|
}
|
|
} else if (attr.type === 'multiselect') {
|
|
fieldType = 'multiselect';
|
|
// Handle options - can be array or string reference
|
|
if (Array.isArray(attr.options)) {
|
|
options = attr.options.map(opt => {
|
|
const labelValue = typeof opt.label === 'string'
|
|
? opt.label
|
|
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
|
|
return {
|
|
value: opt.value,
|
|
label: labelValue
|
|
};
|
|
});
|
|
} else if (typeof attr.options === 'string') {
|
|
optionsReference = attr.options;
|
|
}
|
|
} else if (attr.type === 'textarea') {
|
|
fieldType = 'textarea';
|
|
} else if (attr.type === 'text') {
|
|
fieldType = (attr as any).multiline === true ? 'textarea' : 'string';
|
|
}
|
|
// Note: Legacy 'boolean' and 'enum' types are not in the AttributeDefinition type union
|
|
// If needed, they should be handled via type casting: (attr as any).type === 'boolean'
|
|
|
|
// Define validators and required fields
|
|
let required = attr.required === true;
|
|
let validator: ((value: any) => string | null) | undefined = undefined;
|
|
let minRows: number | undefined = undefined;
|
|
let maxRows: number | undefined = undefined;
|
|
|
|
if (attr.name === 'name') {
|
|
required = true;
|
|
validator = (value: any) => {
|
|
if (!value || (typeof value === 'string' && value.trim() === '')) {
|
|
return 'Workflow name cannot be empty';
|
|
}
|
|
if (typeof value === 'string' && value.length > 100) {
|
|
return 'Workflow name cannot exceed 100 characters';
|
|
}
|
|
return null;
|
|
};
|
|
} else if (fieldType === 'textarea') {
|
|
minRows = 4;
|
|
maxRows = 8;
|
|
}
|
|
// Multiselect validation
|
|
else if (fieldType === 'multiselect' && required) {
|
|
validator = (value: any[]) => {
|
|
if (!value || !Array.isArray(value) || value.length === 0) {
|
|
return `${attr.label} is required`;
|
|
}
|
|
return null;
|
|
};
|
|
}
|
|
|
|
return {
|
|
key: attr.name,
|
|
label: attr.label || attr.name,
|
|
type: fieldType,
|
|
editable: attr.editable !== false && attr.readonly !== true,
|
|
required,
|
|
validator,
|
|
minRows,
|
|
maxRows,
|
|
options,
|
|
optionsReference
|
|
};
|
|
});
|
|
|
|
return editableFields;
|
|
}, [attributes]);
|
|
|
|
// Ensure attributes are loaded - can be called by EditActionButton
|
|
const ensureAttributesLoaded = useCallback(async () => {
|
|
// If attributes are already loaded, return them
|
|
if (attributes && attributes.length > 0) {
|
|
return attributes;
|
|
}
|
|
|
|
// Otherwise, fetch them and return the result
|
|
const fetchedAttributes = await fetchAttributes();
|
|
return fetchedAttributes;
|
|
}, [attributes, fetchAttributes]);
|
|
|
|
// Fetch attributes and permissions on mount
|
|
// Note: Do NOT fetch workflows here - let the table component control pagination
|
|
useEffect(() => {
|
|
fetchAttributes();
|
|
fetchPermissions();
|
|
}, [fetchAttributes, fetchPermissions]);
|
|
|
|
// Listen for workflow creation events to refetch workflows list
|
|
useEffect(() => {
|
|
const handleWorkflowCreated = (_event: CustomEvent<{ workflow: UserWorkflow }>) => {
|
|
// Refetch to ensure we have the latest data
|
|
fetchWorkflowsData();
|
|
};
|
|
|
|
window.addEventListener('workflowCreated', handleWorkflowCreated as EventListener);
|
|
|
|
return () => {
|
|
window.removeEventListener('workflowCreated', handleWorkflowCreated as EventListener);
|
|
};
|
|
}, [fetchWorkflowsData]);
|
|
|
|
return {
|
|
data: workflows,
|
|
loading,
|
|
error,
|
|
refetch: fetchWorkflowsData,
|
|
removeOptimistically,
|
|
updateOptimistically,
|
|
attributes,
|
|
permissions,
|
|
pagination,
|
|
fetchWorkflowById,
|
|
generateEditFieldsFromAttributes,
|
|
ensureAttributesLoaded
|
|
};
|
|
}
|
|
|
|
// Workflow operations hook - pass instanceId and featureCode when in feature context for feature-scoped API
|
|
export function useWorkflowOperations(options?: { instanceId?: string; featureCode?: string }) {
|
|
const apiBaseUrl = getWorkflowApiBaseUrl(options?.instanceId, options?.featureCode);
|
|
const [startingWorkflow, setStartingWorkflow] = useState(false);
|
|
const [stoppingWorkflows, setStoppingWorkflows] = useState<Set<string>>(new Set());
|
|
const [deletingWorkflows, setDeletingWorkflows] = useState<Set<string>>(new Set());
|
|
const [editingWorkflows, setEditingWorkflows] = useState<Set<string>>(new Set());
|
|
const [deletingMessages, setDeletingMessages] = useState<Set<string>>(new Set());
|
|
const [deletingFiles, setDeletingFiles] = useState<Set<string>>(new Set());
|
|
|
|
const [startError, setStartError] = useState<string | null>(null);
|
|
const [stopError, setStopError] = useState<string | null>(null);
|
|
const [deleteError, setDeleteError] = useState<string | null>(null);
|
|
const [updateError, setUpdateError] = useState<string | null>(null);
|
|
const [deleteMessageError, setDeleteMessageError] = useState<string | null>(null);
|
|
const [deleteFileError, setDeleteFileError] = useState<string | null>(null);
|
|
|
|
// Workflow selection context - to clear selection if deleted workflow is selected
|
|
const { selectedWorkflowId, clearWorkflow } = useWorkflowSelection();
|
|
|
|
const { request } = useApiRequest();
|
|
|
|
// Generic delete operation handler
|
|
const handleDeleteOperation = async <T>(
|
|
operationKey: string,
|
|
setLoadingSet: React.Dispatch<React.SetStateAction<Set<string>>>,
|
|
setErrorState: React.Dispatch<React.SetStateAction<string | null>>,
|
|
operation: () => Promise<T>,
|
|
errorMessages: { default: string; notFound: string; forbidden: string }
|
|
): Promise<{ success: boolean; error?: string }> => {
|
|
setErrorState(null);
|
|
setLoadingSet(prev => new Set(prev).add(operationKey));
|
|
|
|
try {
|
|
await operation();
|
|
return { success: true };
|
|
} catch (error: any) {
|
|
let errorMessage = error.message || errorMessages.default;
|
|
|
|
if (error.response?.status === 404) {
|
|
errorMessage = errorMessages.notFound;
|
|
return { success: true };
|
|
} else if (error.response?.status === 403) {
|
|
errorMessage = errorMessages.forbidden;
|
|
}
|
|
|
|
setErrorState(errorMessage);
|
|
return { success: false, error: errorMessage };
|
|
} finally {
|
|
setLoadingSet(prev => {
|
|
const newSet = new Set(prev);
|
|
newSet.delete(operationKey);
|
|
return newSet;
|
|
});
|
|
}
|
|
};
|
|
|
|
const startWorkflow = async (
|
|
instanceId: string,
|
|
workflowData: StartWorkflowRequest,
|
|
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }
|
|
) => {
|
|
setStartError(null);
|
|
setStartingWorkflow(true);
|
|
|
|
try {
|
|
const response = await startWorkflowApi(request, instanceId, workflowData, options);
|
|
return { success: true, data: response };
|
|
} catch (error: any) {
|
|
const errorMessage = error.message || 'Failed to start workflow';
|
|
setStartError(errorMessage);
|
|
return { success: false, error: errorMessage };
|
|
} finally {
|
|
setStartingWorkflow(false);
|
|
}
|
|
};
|
|
|
|
const stopWorkflow = async (instanceId: string, workflowId: string) => {
|
|
setStopError(null);
|
|
setStoppingWorkflows(prev => new Set(prev).add(workflowId));
|
|
|
|
try {
|
|
await stopWorkflowApi(request, instanceId, workflowId);
|
|
return { success: true };
|
|
} catch (error: any) {
|
|
const errorMessage = error.message || 'Failed to stop workflow';
|
|
setStopError(errorMessage);
|
|
return { success: false, error: errorMessage };
|
|
} finally {
|
|
setStoppingWorkflows(prev => {
|
|
const newSet = new Set(prev);
|
|
newSet.delete(workflowId);
|
|
return newSet;
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleWorkflowDelete = async (workflowId: string) => {
|
|
const result = await handleDeleteOperation(
|
|
workflowId,
|
|
setDeletingWorkflows,
|
|
setDeleteError,
|
|
() => deleteWorkflowApi(request, workflowId, apiBaseUrl),
|
|
{
|
|
default: 'Failed to delete workflow',
|
|
notFound: 'Workflow not found or has already been deleted.',
|
|
forbidden: 'No permission to delete this workflow.'
|
|
}
|
|
);
|
|
|
|
if (result.success) {
|
|
// Add a small delay to ensure backend has time to process
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
// Dispatch event to notify other components (e.g., dashboard dropdown)
|
|
window.dispatchEvent(new CustomEvent('workflowDeleted', {
|
|
detail: { workflowIds: [workflowId] }
|
|
}));
|
|
|
|
// Clear workflow selection if the deleted workflow was selected
|
|
if (selectedWorkflowId === workflowId) {
|
|
clearWorkflow();
|
|
}
|
|
}
|
|
|
|
return result.success;
|
|
};
|
|
|
|
const handleWorkflowDeleteMultiple = async (workflowIds: string[]) => {
|
|
setDeleteError(null);
|
|
setDeletingWorkflows(prev => {
|
|
const newSet = new Set(prev);
|
|
workflowIds.forEach(id => newSet.add(id));
|
|
return newSet;
|
|
});
|
|
|
|
try {
|
|
// Delete workflows one by one since there's no bulk delete endpoint
|
|
await deleteWorkflowsApi(request, workflowIds, apiBaseUrl);
|
|
|
|
// Add a small delay to ensure backend has time to process
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
// Dispatch event to notify other components (e.g., dashboard dropdown)
|
|
window.dispatchEvent(new CustomEvent('workflowDeleted', {
|
|
detail: { workflowIds }
|
|
}));
|
|
|
|
// Clear workflow selection if the selected workflow was deleted
|
|
if (selectedWorkflowId && workflowIds.includes(selectedWorkflowId)) {
|
|
clearWorkflow();
|
|
}
|
|
|
|
return true;
|
|
} catch (error: any) {
|
|
console.error(`❌ Bulk delete failed:`, error);
|
|
setDeleteError(error.message || 'Bulk delete failed');
|
|
return false;
|
|
} finally {
|
|
setDeletingWorkflows(prev => {
|
|
const newSet = new Set(prev);
|
|
workflowIds.forEach(id => newSet.delete(id));
|
|
return newSet;
|
|
});
|
|
}
|
|
};
|
|
|
|
const deleteMessage = async (workflowId: string, messageId: string) => {
|
|
const operationKey = `${workflowId}:${messageId}`;
|
|
return handleDeleteOperation(
|
|
operationKey,
|
|
setDeletingMessages,
|
|
setDeleteMessageError,
|
|
() => deleteMessageApi(request, workflowId, messageId, apiBaseUrl),
|
|
{
|
|
default: 'Failed to delete message',
|
|
notFound: 'Message not found or has already been deleted.',
|
|
forbidden: 'No permission to delete this message.'
|
|
}
|
|
);
|
|
};
|
|
|
|
const deleteFileFromMessage = async (
|
|
workflowId: string,
|
|
messageId: string,
|
|
fileId: string
|
|
) => {
|
|
const operationKey = `${workflowId}:${messageId}:${fileId}`;
|
|
return handleDeleteOperation(
|
|
operationKey,
|
|
setDeletingFiles,
|
|
setDeleteFileError,
|
|
() => deleteFileFromMessageApi(request, workflowId, messageId, fileId, apiBaseUrl),
|
|
{
|
|
default: 'Failed to delete file',
|
|
notFound: 'File not found or has already been deleted.',
|
|
forbidden: 'No permission to delete this file.'
|
|
}
|
|
);
|
|
};
|
|
|
|
const handleWorkflowUpdate = async (workflowId: string, updateData: Partial<{ name: string; description?: string; tags?: string[] }>, _originalWorkflowData?: any) => {
|
|
setUpdateError(null);
|
|
setEditingWorkflows(prev => new Set(prev).add(workflowId));
|
|
|
|
try {
|
|
const updatedWorkflow = await updateWorkflowApi(request, workflowId, updateData, apiBaseUrl);
|
|
|
|
return { success: true, workflowData: updatedWorkflow };
|
|
} catch (error: any) {
|
|
console.error(`Update failed for workflow ID ${workflowId}:`, error);
|
|
|
|
const errorMessage = error.response?.data?.message || error.message || 'Failed to update workflow';
|
|
const statusCode = error.response?.status;
|
|
|
|
setUpdateError(errorMessage);
|
|
|
|
// Return detailed error information for proper handling
|
|
return {
|
|
success: false,
|
|
error: errorMessage,
|
|
statusCode,
|
|
isPermissionError: statusCode === 403,
|
|
isValidationError: statusCode === 400
|
|
};
|
|
} finally {
|
|
setEditingWorkflows(prev => {
|
|
const newSet = new Set(prev);
|
|
newSet.delete(workflowId);
|
|
return newSet;
|
|
});
|
|
}
|
|
};
|
|
|
|
// Generic inline update handler for FormGeneratorTable
|
|
// Must merge changes with existing row data because backend requires full object
|
|
const handleInlineUpdate = async (workflowId: string, changes: Partial<UserWorkflow>, existingRow?: any) => {
|
|
if (!existingRow) {
|
|
throw new Error(`Existing row data required for inline update`);
|
|
}
|
|
|
|
// Merge changes with existing row data
|
|
const mergedData = {
|
|
name: existingRow.name,
|
|
...changes
|
|
};
|
|
|
|
const result = await handleWorkflowUpdate(workflowId, mergedData);
|
|
if (!result.success) {
|
|
throw new Error(result.error || 'Failed to update');
|
|
}
|
|
return result;
|
|
};
|
|
|
|
return {
|
|
// Loading states
|
|
startingWorkflow,
|
|
stoppingWorkflows,
|
|
deletingWorkflows,
|
|
editingWorkflows,
|
|
deletingMessages,
|
|
deletingFiles,
|
|
// Error states
|
|
startError,
|
|
stopError,
|
|
deleteError,
|
|
updateError,
|
|
deleteMessageError,
|
|
deleteFileError,
|
|
// Operations
|
|
startWorkflow,
|
|
stopWorkflow,
|
|
handleWorkflowDelete,
|
|
handleWorkflowDeleteMultiple,
|
|
handleWorkflowUpdate,
|
|
handleInlineUpdate,
|
|
deleteMessage,
|
|
deleteFileFromMessage
|
|
};
|
|
}
|