import { useState, useCallback } from 'react'; import { useApiRequest } from './useApi'; import api from '../api'; import { usePermissions, type UserPermissions } from './usePermissions'; import { fetchAutomations as fetchAutomationsApi, fetchAutomation as fetchAutomationApi, createAutomationApi, updateAutomationApi, deleteAutomationApi, executeAutomationApi, fetchAutomationTemplates as fetchTemplatesApi, type Automation, type AutomationTemplate, type CreateAutomationRequest, type UpdateAutomationRequest } from '../api/automationApi'; // Re-export types export type { Automation, AutomationTemplate, CreateAutomationRequest, UpdateAutomationRequest }; // Attribute definition interface export interface AttributeDefinition { name: string; type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea'; label: string; description?: string; required?: boolean; default?: any; options?: Array<{ value: string | number; label: string | { [key: string]: string } }> | string; validation?: any; readonly?: boolean; editable?: boolean; visible?: boolean; order?: number; sortable?: boolean; filterable?: boolean; searchable?: boolean; width?: number; minWidth?: number; maxWidth?: number; filterOptions?: string[]; } // Automations list hook export function useAutomations() { const [automations, setAutomations] = useState([]); const [attributes, setAttributes] = useState([]); const [permissions, setPermissions] = useState(null); const [pagination, setPagination] = useState<{ currentPage: number; pageSize: number; totalItems: number; totalPages: number; } | null>(null); const { request, isLoading: loading, error } = useApiRequest(); const { checkPermission } = usePermissions(); // Fetch attributes from backend - no fallback, errors should be visible const fetchAttributes = useCallback(async () => { try { const response = await api.get('/api/automations/attributes'); let attrs: AttributeDefinition[] = []; // Backend returns: { attributes: { model: "...", attributes: [...] } } // So we need to access response.data.attributes.attributes if (response.data?.attributes?.attributes && Array.isArray(response.data.attributes.attributes)) { attrs = response.data.attributes.attributes; } else if (response.data?.attributes && Array.isArray(response.data.attributes)) { // Fallback: if attributes is directly an array attrs = response.data.attributes; } else if (Array.isArray(response.data)) { attrs = response.data; } if (attrs.length === 0) { console.warn('No attributes returned from backend for AutomationDefinition'); } setAttributes(attrs); return attrs; } catch (error: any) { console.error('Error fetching automation attributes:', error); setAttributes([]); return []; } }, []); // Fetch permissions from backend const fetchPermissions = useCallback(async () => { try { const perms = await checkPermission('DATA', 'AutomationDefinition'); setPermissions(perms); return perms; } catch (error: any) { console.error('Error fetching automation permissions:', error); const defaultPerms: UserPermissions = { view: false, read: 'n', create: 'n', update: 'n', delete: 'n', }; setPermissions(defaultPerms); return defaultPerms; } }, [checkPermission]); const fetchAutomations = useCallback(async () => { try { const data = await fetchAutomationsApi(request); // Handle paginated response if (data && typeof data === 'object' && 'items' in data) { const items = Array.isArray((data as any).items) ? (data as any).items : []; setAutomations(items); if ((data as any).pagination) { setPagination((data as any).pagination); } } else { const items = Array.isArray(data) ? data : []; setAutomations(items); setPagination(null); } } catch (error: any) { setAutomations([]); setPagination(null); } }, [request]); // Optimistically remove an automation from the local state const removeOptimistically = (automationId: string) => { setAutomations(prev => prev.filter(a => a.id !== automationId)); }; // Optimistically update an automation in the local state const updateOptimistically = (automationId: string, updateData: Partial) => { setAutomations(prev => prev.map(a => a.id === automationId ? { ...a, ...updateData } : a) ); }; // Fetch a single automation by ID const fetchAutomationById = useCallback(async (automationId: string): Promise => { try { return await fetchAutomationApi(request, automationId); } catch (error) { console.error('Error fetching automation by ID:', error); return null; } }, [request]); // 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; minRows?: number; maxRows?: number; options?: Array<{ value: string | number; label: string }>; }> => { if (!attributes || attributes.length === 0) { return []; } // Fields to show in edit form const editableFields = ['label', 'schedule', 'template', 'placeholders', 'active']; return attributes .filter(attr => editableFields.includes(attr.name) && attr.editable !== false) .map(attr => { let fieldType: 'string' | 'boolean' | 'textarea' | 'enum' | 'readonly' = 'string'; if (attr.type === 'checkbox') { fieldType = 'boolean'; } else if (attr.type === 'textarea' || attr.name === 'template' || attr.name === 'placeholders') { fieldType = 'textarea'; } else if (attr.type === 'select' && attr.options) { fieldType = 'enum'; } const field: any = { key: attr.name, label: attr.label || attr.name, type: fieldType, editable: attr.editable !== false, required: attr.required || false, }; if (fieldType === 'textarea') { field.minRows = 3; field.maxRows = 15; } if (fieldType === 'enum' && attr.options) { field.options = Array.isArray(attr.options) ? attr.options.map(opt => ({ value: typeof opt === 'object' ? opt.value : opt, label: typeof opt === 'object' ? (typeof opt.label === 'object' ? opt.label['en'] || opt.label['de'] : opt.label) : opt })) : []; } return field; }); }, [attributes]); // Generate create fields from attributes const generateCreateFieldsFromAttributes = useCallback(() => { return generateEditFieldsFromAttributes(); }, [generateEditFieldsFromAttributes]); // Ensure attributes are loaded const ensureAttributesLoaded = useCallback(async () => { if (attributes.length === 0) { await fetchAttributes(); } }, [attributes.length, fetchAttributes]); // Initial data fetch const refetch = useCallback(async () => { await Promise.all([ fetchAutomations(), fetchAttributes(), fetchPermissions() ]); }, [fetchAutomations, fetchAttributes, fetchPermissions]); return { automations, data: automations, // Alias for FormGenerator compatibility loading, error, refetch, removeOptimistically, updateOptimistically, attributes, permissions, pagination, fetchAutomationById, generateEditFieldsFromAttributes, generateCreateFieldsFromAttributes, ensureAttributesLoaded }; } // Automation operations hook export function useAutomationOperations() { const { request } = useApiRequest(); const [deletingAutomations, setDeletingAutomations] = useState>(new Set()); const [creatingAutomation, setCreatingAutomation] = useState(false); const [executingAutomations, setExecutingAutomations] = useState>(new Set()); const [deleteError, setDeleteError] = useState(null); const [createError, setCreateError] = useState(null); const [updateError, setUpdateError] = useState(null); // Create a new automation const handleAutomationCreate = useCallback(async (data: CreateAutomationRequest): Promise => { setCreatingAutomation(true); setCreateError(null); try { // Validate required fields - mandateId and featureInstanceId must be provided if (!data.mandateId || !data.featureInstanceId) { throw new Error('mandateId and featureInstanceId are required'); } // Convert placeholders to ensure all values are strings if (data.placeholders) { const convertedPlaceholders: Record = {}; for (const [key, value] of Object.entries(data.placeholders)) { if (value === null || value === undefined) { convertedPlaceholders[key] = ''; } else if (typeof value === 'object') { convertedPlaceholders[key] = JSON.stringify(value); } else { convertedPlaceholders[key] = String(value); } } data.placeholders = convertedPlaceholders; } const newAutomation = await createAutomationApi(request, data); return newAutomation; } catch (error: any) { console.error('Error creating automation:', error); setCreateError(error.message || 'Failed to create automation'); return null; } finally { setCreatingAutomation(false); } }, [request]); // Update an existing automation const handleAutomationUpdate = useCallback(async ( automationId: string, data: UpdateAutomationRequest ): Promise => { setUpdateError(null); try { await updateAutomationApi(request, automationId, data); return true; } catch (error: any) { console.error('Error updating automation:', error); setUpdateError(error.message || 'Failed to update automation'); return false; } }, [request]); // Delete an automation const handleAutomationDelete = useCallback(async (automationId: string): Promise => { setDeletingAutomations(prev => new Set(prev).add(automationId)); setDeleteError(null); try { await deleteAutomationApi(request, automationId); return true; } catch (error: any) { console.error('Error deleting automation:', error); setDeleteError(error.message || 'Failed to delete automation'); return false; } finally { setDeletingAutomations(prev => { const newSet = new Set(prev); newSet.delete(automationId); return newSet; }); } }, [request]); // Execute an automation const handleAutomationExecute = useCallback(async (automationId: string): Promise => { setExecutingAutomations(prev => new Set(prev).add(automationId)); try { const result = await executeAutomationApi(request, automationId); return result; } catch (error: any) { console.error('Error executing automation:', error); throw error; } finally { setExecutingAutomations(prev => { const newSet = new Set(prev); newSet.delete(automationId); return newSet; }); } }, [request]); // Toggle automation active status const handleAutomationToggleActive = useCallback(async ( automationId: string, currentActive: boolean ): Promise => { try { await updateAutomationApi(request, automationId, { active: !currentActive }); return true; } catch (error: any) { console.error('Error toggling automation active status:', error); return false; } }, [request]); // Generic inline update handler for FormGeneratorTable const handleInlineUpdate = useCallback(async ( automationId: string, changes: Partial, existingRow?: any ) => { if (!existingRow) { throw new Error('Existing row data required for inline update'); } const result = await handleAutomationUpdate(automationId, changes); if (!result) { throw new Error(updateError || 'Failed to update'); } return { success: true }; }, [handleAutomationUpdate, updateError]); // Fetch templates const fetchTemplates = useCallback(async (): Promise => { try { return await fetchTemplatesApi(request); } catch (error: any) { console.error('Error fetching templates:', error); return []; } }, [request]); return { handleAutomationCreate, handleAutomationUpdate, handleAutomationDelete, handleAutomationExecute, handleAutomationToggleActive, handleInlineUpdate, fetchTemplates, deletingAutomations, creatingAutomation, executingAutomations, deleteError, createError, updateError }; }