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, fetchAutomationTemplateById, createAutomationTemplateApi, updateAutomationTemplateApi, deleteAutomationTemplateApi, fetchAutomationTemplateAttributes, fetchWorkflowActions as fetchWorkflowActionsApi, type Automation, type AutomationTemplate, type TextMultilingual, type WorkflowAction, type CreateAutomationRequest, type UpdateAutomationRequest } from '../api/automationApi'; // Re-export types export type { Automation, AutomationTemplate, TextMultilingual, WorkflowAction, 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', 'data.automation.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 // NOTE: Backend PUT expects full AutomationDefinition object including id const handleAutomationToggleActive = useCallback(async ( automationId: string, currentActive: boolean, fullAutomation?: Automation ): Promise => { try { // Build full update data - backend expects AutomationDefinition with all fields const sourceAutomation = fullAutomation || await fetchAutomationApi(request, automationId); // Backend requires id in body to match URL parameter const updateData = { id: automationId, // MUST match URL parameter mandateId: sourceAutomation.mandateId, featureInstanceId: sourceAutomation.featureInstanceId, label: sourceAutomation.label, schedule: sourceAutomation.schedule, template: typeof sourceAutomation.template === 'object' ? JSON.stringify(sourceAutomation.template) : sourceAutomation.template, placeholders: sourceAutomation.placeholders || {}, active: !currentActive }; await updateAutomationApi(request, automationId, updateData as any); return true; } catch (error: any) { console.error('Error toggling automation active status:', error); return false; } }, [request]); // Generic inline update handler for FormGeneratorTable // NOTE: Backend PUT requires full object, so we merge changes with existing row data const handleInlineUpdate = useCallback(async ( automationId: string, changes: Partial, existingRow?: Automation ) => { if (!existingRow) { throw new Error('Existing row data required for inline update'); } try { // Merge changes with existing row data and send all required fields const updateData = { id: automationId, // MUST match URL parameter mandateId: existingRow.mandateId, featureInstanceId: existingRow.featureInstanceId, label: existingRow.label, schedule: existingRow.schedule, template: typeof existingRow.template === 'object' ? JSON.stringify(existingRow.template) : existingRow.template, placeholders: existingRow.placeholders || {}, // Apply the changes (e.g., active: true/false) ...changes }; await updateAutomationApi(request, automationId, updateData as any); return { success: true }; } catch (error: any) { console.error('Error in inline update:', error); throw new Error(error.message || 'Failed to update'); } }, [request]); // 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 }; } // ============================================================================ // AUTOMATION TEMPLATES (DB) HOOK // ============================================================================ /** * Hook for managing AutomationTemplates from database */ export function useAutomationTemplates() { const [templates, setTemplates] = useState([]); const [attributes, setAttributes] = useState([]); const [pagination, setPagination] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const { request } = useApiRequest(); const { checkPermission } = usePermissions(); const [permissions, setPermissions] = useState(null); const fetchTemplates = useCallback(async (params?: any) => { setLoading(true); setError(null); try { const data = await fetchTemplatesApi(request, params); if (data && typeof data === 'object' && 'items' in data) { setTemplates(Array.isArray(data.items) ? data.items : []); if (data.pagination) setPagination(data.pagination); } else { setTemplates(Array.isArray(data) ? data : []); setPagination(null); } } catch (e: any) { console.error('Error fetching templates:', e); setError(e.message || 'Failed to fetch templates'); setTemplates([]); setPagination(null); } finally { setLoading(false); } }, [request]); const fetchAttributes = useCallback(async () => { try { const attrs = await fetchAutomationTemplateAttributes(request); setAttributes(attrs); return attrs; } catch (e: any) { console.error('Error fetching template attributes:', e); setAttributes([]); return []; } }, [request]); const fetchPermissions = useCallback(async () => { try { const perms = await checkPermission('DATA', 'data.automation.AutomationTemplate'); setPermissions(perms); return perms; } catch (e: any) { console.error('Error fetching template permissions:', e); const defaultPerms: UserPermissions = { view: false, read: 'n', create: 'n', update: 'n', delete: 'n', }; setPermissions(defaultPerms); return defaultPerms; } }, [checkPermission]); const getTemplate = useCallback(async (templateId: string) => { return await fetchAutomationTemplateById(request, templateId); }, [request]); const createTemplate = useCallback(async (data: Omit) => { return await createAutomationTemplateApi(request, data); }, [request]); const updateTemplate = useCallback(async (templateId: string, data: Partial) => { return await updateAutomationTemplateApi(request, templateId, data); }, [request]); const deleteTemplate = useCallback(async (templateId: string) => { await deleteAutomationTemplateApi(request, templateId); }, [request]); const duplicateTemplate = useCallback(async (templateId: string) => { const response = await request({ url: `/api/automation-templates/${templateId}/duplicate`, method: 'post' }); return response; }, [request]); const refetch = useCallback(async () => { await Promise.all([ fetchTemplates(), fetchAttributes(), fetchPermissions() ]); }, [fetchTemplates, fetchAttributes, fetchPermissions]); return { templates, data: templates, attributes, loading, error, permissions, pagination, refetch, fetchTemplates, fetchAttributes, fetchPermissions, getTemplate, createTemplate, updateTemplate, deleteTemplate, duplicateTemplate, }; } // ============================================================================ // WORKFLOW ACTIONS HOOK // ============================================================================ /** * Hook for fetching available workflow actions (for Actions panel) */ export function useWorkflowActions() { const [actions, setActions] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const { request } = useApiRequest(); const fetchActions = useCallback(async () => { setLoading(true); setError(null); try { const data = await fetchWorkflowActionsApi(request); setActions(data); } catch (e: any) { console.error('Error fetching workflow actions:', e); setError(e.message || 'Failed to fetch actions'); setActions([]); } finally { setLoading(false); } }, [request]); return { actions, loading, error, fetchActions }; }