From 8d86c166d00cb4ac53ff7efce44d40cbe0e9bc16 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Tue, 3 Feb 2026 21:29:53 +0100 Subject: [PATCH] automation template management and fix admin center --- src/App.tsx | 3 +- src/api/automationApi.ts | 208 ++++++++++--- .../ActionsPanel/ActionsPanel.module.css | 289 ++++++++++++++++++ src/components/ActionsPanel/ActionsPanel.tsx | 216 +++++++++++++ src/components/ActionsPanel/index.ts | 2 + src/config/pageRegistry.tsx | 1 + src/hooks/useAutomations.ts | 157 +++++++++- src/pages/admin/AccessManagementHub.tsx | 3 + src/pages/admin/AdminMandatesPage.tsx | 11 +- src/pages/admin/AdminUsersPage.tsx | 9 +- .../workflows/AutomationTemplatesPage.tsx | 269 ++++++++++++++++ src/pages/workflows/index.ts | 1 + 12 files changed, 1128 insertions(+), 41 deletions(-) create mode 100644 src/components/ActionsPanel/ActionsPanel.module.css create mode 100644 src/components/ActionsPanel/ActionsPanel.tsx create mode 100644 src/components/ActionsPanel/index.ts create mode 100644 src/pages/workflows/AutomationTemplatesPage.tsx diff --git a/src/App.tsx b/src/App.tsx index c9da740..95e21b5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -44,7 +44,7 @@ import { FeatureViewPage } from './pages/FeatureView'; import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage } from './pages/admin'; // Workflow Pages (global) -import { PlaygroundPage, WorkflowsPage, AutomationsPage } from './pages/workflows'; +import { PlaygroundPage, WorkflowsPage, AutomationsPage, AutomationTemplatesPage } from './pages/workflows'; // Basedata Pages (global) import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata'; @@ -114,6 +114,7 @@ function App() { } /> } /> } /> + } /> {/* ============================================== */} diff --git a/src/api/automationApi.ts b/src/api/automationApi.ts index 2004c0b..b6f0628 100644 --- a/src/api/automationApi.ts +++ b/src/api/automationApi.ts @@ -32,17 +32,49 @@ export interface AutomationLog { messages?: string[]; } +// Multilingual text type (matches backend TextMultilingual) +export interface TextMultilingual { + en: string; + ge?: string; + fr?: string; + it?: string; +} + +// AutomationTemplate from DB export interface AutomationTemplate { - template: { - overview?: string; - tasks?: Array<{ - description?: string; - objective?: string; - [key: string]: any; - }>; - [key: string]: any; + id: string; + label: TextMultilingual; + overview?: TextMultilingual; + template: string; // JSON string with {{KEY:...}} placeholders + _createdAt?: number; + _createdBy?: string; + _createdByUserName?: string; +} + +// Workflow action definition from backend +export interface WorkflowAction { + method: string; + action: string; + actionId: string; + description: string; + category?: string; + parameters: WorkflowActionParameter[]; + exampleJson: { + execMethod: string; + execAction: string; + execParameters: Record; + execResultLabel: string; }; - parameters?: Record; +} + +export interface WorkflowActionParameter { + name: string; + type: string; + frontendType: string; + required: boolean; + default?: any; + description: string; + frontendOptions?: string | string[]; } export interface CreateAutomationRequest { @@ -188,34 +220,6 @@ export async function executeAutomationApi( }); } -/** - * Fetch automation templates - * Endpoint: GET /api/automations/templates - */ -export async function fetchAutomationTemplates( - request: ApiRequestFunction -): Promise { - const data = await request({ - url: '/api/automations/templates', - method: 'get' - }); - - if (Array.isArray(data)) { - return data; - } - - if (data && typeof data === 'object') { - if (Array.isArray(data.sets)) { - return data.sets; - } - if (Array.isArray(data.templates)) { - return data.templates; - } - } - - return []; -} - /** * Fetch automation attributes for dynamic form generation * Endpoint: GET /api/attributes/AutomationDefinition @@ -238,3 +242,133 @@ export async function fetchAutomationAttributes( return []; } + +// ============================================================================ +// AUTOMATION TEMPLATES API +// ============================================================================ + +/** + * Fetch all automation templates (RBAC-filtered: own templates) + * Endpoint: GET /api/automation-templates + */ +export async function fetchAutomationTemplates( + request: ApiRequestFunction +): Promise { + const data = await request({ + url: '/api/automation-templates', + method: 'get' + }); + + if (data?.items && Array.isArray(data.items)) { + return data.items; + } + return Array.isArray(data) ? data : []; +} + +/** + * Fetch single automation template by ID + * Endpoint: GET /api/automation-templates/{templateId} + */ +export async function fetchAutomationTemplateById( + request: ApiRequestFunction, + templateId: string +): Promise { + try { + return await request({ + url: `/api/automation-templates/${templateId}`, + method: 'get' + }); + } catch (error) { + console.error('Error fetching template:', error); + return null; + } +} + +/** + * Create new automation template + * Endpoint: POST /api/automation-templates + */ +export async function createAutomationTemplateApi( + request: ApiRequestFunction, + templateData: Omit +): Promise { + return await request({ + url: '/api/automation-templates', + method: 'post', + data: templateData + }); +} + +/** + * Update automation template + * Endpoint: PUT /api/automation-templates/{templateId} + */ +export async function updateAutomationTemplateApi( + request: ApiRequestFunction, + templateId: string, + templateData: Partial +): Promise { + return await request({ + url: `/api/automation-templates/${templateId}`, + method: 'put', + data: templateData + }); +} + +/** + * Delete automation template + * Endpoint: DELETE /api/automation-templates/{templateId} + */ +export async function deleteAutomationTemplateApi( + request: ApiRequestFunction, + templateId: string +): Promise { + await request({ + url: `/api/automation-templates/${templateId}`, + method: 'delete' + }); +} + +/** + * Fetch automation template attributes for dynamic form generation + * Endpoint: GET /api/automation-templates/attributes + */ +export async function fetchAutomationTemplateAttributes( + request: ApiRequestFunction +): Promise { + const data = await request({ + url: '/api/automation-templates/attributes', + method: 'get' + }); + + // Backend returns: { attributes: { model: "...", attributes: [...] } } + if (data?.attributes?.attributes && Array.isArray(data.attributes.attributes)) { + return data.attributes.attributes; + } + + // Fallback: direct attributes array + if (data?.attributes && Array.isArray(data.attributes)) { + return data.attributes; + } + + return Array.isArray(data) ? data : []; +} + +// ============================================================================ +// WORKFLOW ACTIONS API +// ============================================================================ + +/** + * Fetch available workflow actions (RBAC-filtered) + * Endpoint: GET /api/automations/actions + */ +export async function fetchWorkflowActions( + request: ApiRequestFunction +): Promise { + const data = await request({ + url: '/api/automations/actions', + method: 'get' + }); + + return data?.actions || []; +} diff --git a/src/components/ActionsPanel/ActionsPanel.module.css b/src/components/ActionsPanel/ActionsPanel.module.css new file mode 100644 index 0000000..0aa9d92 --- /dev/null +++ b/src/components/ActionsPanel/ActionsPanel.module.css @@ -0,0 +1,289 @@ +/* ActionsPanel Styles */ + +.panel { + display: flex; + flex-direction: column; + height: 100%; + background: var(--bg-secondary, #f5f5f5); + border-radius: 8px; + overflow: hidden; +} + +.header { + padding: 1rem; + border-bottom: 1px solid var(--border-color, #e0e0e0); + background: var(--bg-primary, #ffffff); +} + +.title { + margin: 0 0 0.75rem 0; + font-size: 1rem; + font-weight: 600; + color: var(--text-primary, #333); +} + +.searchBox { + display: flex; + align-items: center; + background: var(--bg-secondary, #f5f5f5); + border-radius: 6px; + padding: 0.5rem 0.75rem; +} + +.searchIcon { + color: var(--text-secondary, #666); + margin-right: 0.5rem; + font-size: 0.875rem; +} + +.searchInput { + flex: 1; + border: none; + background: transparent; + font-size: 0.875rem; + color: var(--text-primary, #333); + outline: none; +} + +.searchInput::placeholder { + color: var(--text-tertiary, #999); +} + +.actionsList { + flex: 1; + overflow-y: auto; + padding: 0.5rem; +} + +.loading, +.error, +.empty { + padding: 2rem; + text-align: center; + color: var(--text-secondary, #666); +} + +.error { + color: var(--error-color, #dc3545); +} + +.retryButton { + margin-top: 1rem; + padding: 0.5rem 1rem; + background: var(--primary-color, #007bff); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.retryButton:hover { + background: var(--primary-hover, #0056b3); +} + +/* Method Groups */ +.methodGroup { + margin-bottom: 0.5rem; + background: var(--bg-primary, #ffffff); + border-radius: 6px; + overflow: hidden; +} + +.methodHeader { + display: flex; + align-items: center; + width: 100%; + padding: 0.75rem 1rem; + background: transparent; + border: none; + cursor: pointer; + text-align: left; + font-size: 0.875rem; + font-weight: 600; + color: var(--text-primary, #333); + transition: background 0.2s; +} + +.methodHeader:hover { + background: var(--bg-hover, #f0f0f0); +} + +.methodHeader svg { + margin-right: 0.5rem; + font-size: 0.75rem; + color: var(--text-secondary, #666); +} + +.methodName { + flex: 1; + text-transform: capitalize; +} + +.methodCount { + background: var(--primary-color, #007bff); + color: white; + padding: 0.125rem 0.5rem; + border-radius: 10px; + font-size: 0.75rem; + font-weight: 500; +} + +/* Method Actions */ +.methodActions { + border-top: 1px solid var(--border-color, #e0e0e0); +} + +.actionItem { + border-bottom: 1px solid var(--border-light, #f0f0f0); +} + +.actionItem:last-child { + border-bottom: none; +} + +.actionHeader { + display: flex; + align-items: center; + padding: 0.75rem 1rem; + cursor: pointer; + transition: background 0.2s; +} + +.actionHeader:hover { + background: var(--bg-hover, #f5f5f5); +} + +.actionInfo { + flex: 1; + min-width: 0; +} + +.actionName { + display: block; + font-weight: 500; + font-size: 0.875rem; + color: var(--text-primary, #333); +} + +.actionDesc { + display: block; + font-size: 0.75rem; + color: var(--text-secondary, #666); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.copyButton { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: var(--bg-secondary, #f5f5f5); + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 4px; + cursor: pointer; + color: var(--text-secondary, #666); + transition: all 0.2s; +} + +.copyButton:hover { + background: var(--primary-color, #007bff); + border-color: var(--primary-color, #007bff); + color: white; +} + +/* Action Details */ +.actionDetails { + padding: 0.75rem 1rem; + background: var(--bg-secondary, #f8f9fa); + border-top: 1px solid var(--border-light, #f0f0f0); +} + +.actionDetails h5 { + margin: 0 0 0.5rem 0; + font-size: 0.75rem; + font-weight: 600; + color: var(--text-secondary, #666); + text-transform: uppercase; +} + +/* Parameters */ +.parameters { + margin-bottom: 1rem; +} + +.parameters ul { + margin: 0; + padding: 0; + list-style: none; +} + +.param { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.5rem; + padding: 0.25rem 0; + font-size: 0.8125rem; +} + +.paramName { + font-weight: 500; + color: var(--text-primary, #333); +} + +.required { + color: var(--error-color, #dc3545); + margin-left: 2px; +} + +.paramType { + font-family: monospace; + font-size: 0.75rem; + background: var(--bg-code, #e9ecef); + padding: 0.125rem 0.375rem; + border-radius: 3px; + color: var(--text-secondary, #666); +} + +.paramDesc { + width: 100%; + font-size: 0.75rem; + color: var(--text-tertiary, #888); +} + +/* Example JSON */ +.exampleJson { + margin-bottom: 1rem; +} + +.exampleJson pre { + margin: 0; + padding: 0.75rem; + background: var(--bg-code, #1e1e1e); + color: var(--text-code, #d4d4d4); + border-radius: 4px; + font-size: 0.75rem; + overflow-x: auto; + white-space: pre-wrap; + word-break: break-all; +} + +.insertButton { + width: 100%; + padding: 0.5rem 1rem; + background: var(--primary-color, #007bff); + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.875rem; + font-weight: 500; + transition: background 0.2s; +} + +.insertButton:hover { + background: var(--primary-hover, #0056b3); +} diff --git a/src/components/ActionsPanel/ActionsPanel.tsx b/src/components/ActionsPanel/ActionsPanel.tsx new file mode 100644 index 0000000..58c2b04 --- /dev/null +++ b/src/components/ActionsPanel/ActionsPanel.tsx @@ -0,0 +1,216 @@ +/** + * ActionsPanel + * + * Displays available workflow actions for copy/paste into templates. + * Groups actions by method and shows parameters + example JSON. + */ + +import React, { useState, useMemo, useEffect } from 'react'; +import { useWorkflowActions, type WorkflowAction } from '../../hooks/useAutomations'; +import { FaSearch, FaCopy, FaChevronDown, FaChevronRight, FaCheck } from 'react-icons/fa'; +import { useToast } from '../../contexts/ToastContext'; +import styles from './ActionsPanel.module.css'; + +interface ActionsPanelProps { + /** Callback when action JSON is inserted (optional) */ + onInsert?: (actionJson: string) => void; + /** Callback when action JSON is copied (optional) */ + onCopy?: (actionJson: string) => void; +} + +export const ActionsPanel: React.FC = ({ onInsert, onCopy }) => { + const { actions, loading, error, fetchActions } = useWorkflowActions(); + const { showSuccess } = useToast(); + + const [filter, setFilter] = useState(''); + const [expandedMethods, setExpandedMethods] = useState>(new Set()); + const [expandedAction, setExpandedAction] = useState(null); + const [copiedAction, setCopiedAction] = useState(null); + + useEffect(() => { + fetchActions(); + }, [fetchActions]); + + // Filter actions by search term + const filteredActions = useMemo(() => { + if (!filter) return actions; + const lower = filter.toLowerCase(); + return actions.filter(a => + a.method.toLowerCase().includes(lower) || + a.action.toLowerCase().includes(lower) || + a.description.toLowerCase().includes(lower) || + a.actionId.toLowerCase().includes(lower) + ); + }, [actions, filter]); + + // Group actions by method + const groupedActions = useMemo(() => { + const groups: Record = {}; + filteredActions.forEach(action => { + if (!groups[action.method]) { + groups[action.method] = []; + } + groups[action.method].push(action); + }); + return groups; + }, [filteredActions]); + + // Toggle method expansion + const toggleMethod = (method: string) => { + setExpandedMethods(prev => { + const newSet = new Set(prev); + if (newSet.has(method)) { + newSet.delete(method); + } else { + newSet.add(method); + } + return newSet; + }); + }; + + // Toggle action details + const toggleAction = (actionId: string) => { + setExpandedAction(prev => prev === actionId ? null : actionId); + }; + + // Copy action JSON to clipboard + const handleCopy = async (action: WorkflowAction) => { + const json = JSON.stringify(action.exampleJson, null, 2); + try { + await navigator.clipboard.writeText(json); + setCopiedAction(action.actionId); + setTimeout(() => setCopiedAction(null), 2000); + showSuccess('JSON kopiert'); + onCopy?.(json); + } catch (err) { + console.error('Failed to copy:', err); + } + }; + + // Insert action JSON + const handleInsert = (action: WorkflowAction) => { + const json = JSON.stringify(action.exampleJson, null, 2); + onInsert?.(json); + }; + + if (loading) { + return ( +
+
Lade Actions...
+
+ ); + } + + if (error) { + return ( +
+
Fehler: {error}
+ +
+ ); + } + + return ( +
+
+

Verfügbare Actions

+
+ + setFilter(e.target.value)} + className={styles.searchInput} + /> +
+
+ +
+ {Object.keys(groupedActions).length === 0 ? ( +
Keine Actions gefunden
+ ) : ( + Object.entries(groupedActions).map(([method, methodActions]) => ( +
+ + + {expandedMethods.has(method) && ( +
+ {methodActions.map(action => ( +
+
toggleAction(action.actionId)} + > +
+ {action.action} + {action.description} +
+ +
+ + {expandedAction === action.actionId && ( +
+ {action.parameters.length > 0 && ( +
+
Parameter:
+
    + {action.parameters.map(param => ( +
  • + + {param.name} + {param.required && *} + + {param.type} + {param.description && ( + {param.description} + )} +
  • + ))} +
+
+ )} + +
+
Beispiel JSON:
+
{JSON.stringify(action.exampleJson, null, 2)}
+
+ + {onInsert && ( + + )} +
+ )} +
+ ))} +
+ )} +
+ )) + )} +
+
+ ); +}; + +export default ActionsPanel; diff --git a/src/components/ActionsPanel/index.ts b/src/components/ActionsPanel/index.ts new file mode 100644 index 0000000..6db9c3b --- /dev/null +++ b/src/components/ActionsPanel/index.ts @@ -0,0 +1,2 @@ +export { ActionsPanel } from './ActionsPanel'; +export { default } from './ActionsPanel'; diff --git a/src/config/pageRegistry.tsx b/src/config/pageRegistry.tsx index 2368238..62f8828 100644 --- a/src/config/pageRegistry.tsx +++ b/src/config/pageRegistry.tsx @@ -39,6 +39,7 @@ export const PAGE_ICONS: Record = { 'page.system.playground': , 'page.system.chats': , 'page.system.automations': , + 'page.system.automation-templates': , 'page.system.prompts': , 'page.system.files': , 'page.system.connections': , diff --git a/src/hooks/useAutomations.ts b/src/hooks/useAutomations.ts index fbf5834..4893fea 100644 --- a/src/hooks/useAutomations.ts +++ b/src/hooks/useAutomations.ts @@ -10,14 +10,29 @@ import { 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, CreateAutomationRequest, UpdateAutomationRequest }; +export type { + Automation, + AutomationTemplate, + TextMultilingual, + WorkflowAction, + CreateAutomationRequest, + UpdateAutomationRequest +}; // Attribute definition interface export interface AttributeDefinition { @@ -446,3 +461,143 @@ export function useAutomationOperations() { updateError }; } + +// ============================================================================ +// AUTOMATION TEMPLATES (DB) HOOK +// ============================================================================ + +/** + * Hook for managing AutomationTemplates from database + */ +export function useAutomationTemplates() { + const [templates, setTemplates] = useState([]); + const [attributes, setAttributes] = useState([]); + 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 () => { + setLoading(true); + setError(null); + try { + const data = await fetchTemplatesApi(request); + setTemplates(data); + } catch (e: any) { + console.error('Error fetching templates:', e); + setError(e.message || 'Failed to fetch templates'); + setTemplates([]); + } 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', '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 refetch = useCallback(async () => { + await Promise.all([ + fetchTemplates(), + fetchAttributes(), + fetchPermissions() + ]); + }, [fetchTemplates, fetchAttributes, fetchPermissions]); + + return { + templates, + data: templates, // Alias for FormGenerator compatibility + attributes, + loading, + error, + permissions, + refetch, + fetchTemplates, + fetchAttributes, + fetchPermissions, + getTemplate, + createTemplate, + updateTemplate, + deleteTemplate, + }; +} + +// ============================================================================ +// 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 + }; +} diff --git a/src/pages/admin/AccessManagementHub.tsx b/src/pages/admin/AccessManagementHub.tsx index f31561b..1c11a25 100644 --- a/src/pages/admin/AccessManagementHub.tsx +++ b/src/pages/admin/AccessManagementHub.tsx @@ -398,6 +398,9 @@ export const AccessManagementHub: React.FC = () => { Mandanten verwalten + + Mandant-Benutzer + {viewMode === 'hierarchy' ? ( diff --git a/src/pages/admin/AdminMandatesPage.tsx b/src/pages/admin/AdminMandatesPage.tsx index 3921bcb..0713a9c 100644 --- a/src/pages/admin/AdminMandatesPage.tsx +++ b/src/pages/admin/AdminMandatesPage.tsx @@ -5,13 +5,15 @@ */ import React, { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useAdminMandates, type Mandate } from '../../hooks/useMandates'; import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable'; import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm'; -import { FaPlus, FaSync, FaBuilding } from 'react-icons/fa'; +import { FaPlus, FaSync, FaBuilding, FaUsers } from 'react-icons/fa'; import styles from './Admin.module.css'; export const AdminMandatesPage: React.FC = () => { + const navigate = useNavigate(); const { mandates, attributes, @@ -100,6 +102,13 @@ export const AdminMandatesPage: React.FC = () => {

Verwalten Sie alle Mandanten im System

+
+ +
+ + ); + } + + return ( +
+
+
+

Automation-Vorlagen

+

Verwalten Sie Ihre Workflow-Vorlagen

+
+
+ + {canCreate && ( + + )} +
+
+ +
+ {loading && (!templates || templates.length === 0) ? ( +
+
+ Lade Vorlagen... +
+ ) : !templates || templates.length === 0 ? ( +
+ +

Keine Vorlagen vorhanden

+

+ Erstellen Sie eine neue Vorlage für Ihre Workflows. +

+ {canCreate && ( + + )} +
+ ) : ( + handleDelete(template.id)} + hookData={{ + refetch, + handleDelete, + attributes, + }} + emptyMessage="Keine Vorlagen gefunden" + /> + )} +
+ + {/* Create Modal */} + {showCreateModal && ( +
setShowCreateModal(false)}> +
e.stopPropagation()}> +
+

Neue Vorlage

+ +
+
+ {formAttributes.length === 0 ? ( +
+
+ Lade Formular... +
+ ) : ( + setShowCreateModal(false)} + submitButtonText="Erstellen" + cancelButtonText="Abbrechen" + /> + )} +
+
+
+ )} + + {/* Edit Modal */} + {editingTemplate && ( +
setEditingTemplate(null)}> +
e.stopPropagation()}> +
+

Vorlage bearbeiten

+ +
+
+ {formAttributes.length === 0 ? ( +
+
+ Lade Formular... +
+ ) : ( + setEditingTemplate(null)} + submitButtonText="Speichern" + cancelButtonText="Abbrechen" + /> + )} +
+
+
+ )} +
+ ); +}; + +export default AutomationTemplatesPage; diff --git a/src/pages/workflows/index.ts b/src/pages/workflows/index.ts index a54bffb..2c60e26 100644 --- a/src/pages/workflows/index.ts +++ b/src/pages/workflows/index.ts @@ -1,3 +1,4 @@ export { PlaygroundPage } from './PlaygroundPage'; export { WorkflowsPage } from './WorkflowsPage'; export { AutomationsPage } from './AutomationsPage'; +export { AutomationTemplatesPage } from './AutomationTemplatesPage';