30 KiB
Automation Templates – Frontend Implementation Concept
Basis: doc_automation_templates_db_and_editor_concept.md
✅ STATUS: IMPLEMENTIERT (2026-02-03)
Backend ist vollständig implementiert:
- ✅ API:
GET/POST/PUT/DELETE /api/automation-templates - ✅ Model:
AutomationTemplatemitTextMultilingualFeldern - ✅ Navigation:
/workflows/automation-templatesin mainSystem.py - ✅ API:
GET /api/automations/actions- Actions-Katalog
Frontend implementiert:
| Komponente | Status |
|---|---|
automationApi.ts - Typen + API-Funktionen |
✅ Implementiert |
useAutomations.ts - Hooks (useWorkflowActions) |
✅ Implementiert |
AutomationTemplatesPage.tsx |
✅ Implementiert |
AutomationEditor.tsx |
✅ NEU - Full-screen Editor |
ActionsPanel.tsx |
✅ Implementiert |
App.tsx - Route |
✅ Implementiert |
pageRegistry.tsx - Icon |
✅ Implementiert |
Neue Dateien:
src/components/AutomationEditor/AutomationEditor.tsxsrc/components/AutomationEditor/AutomationEditor.module.csssrc/components/AutomationEditor/index.ts
WICHTIGE HINWEISE
FormGenerator: Automatische Multilingual-Unterstützung
| Komponente | TextMultilingual-Handling |
|---|---|
| FormGeneratorTable | ✅ Automatisch - erkennt TextMultilingual und rendert in User-Sprache |
| FormGeneratorForm | ✅ Automatisch - mit type: 'multilingual' in Attribut-Definition |
Kein manueller Code nötig! Einfach Daten mit TextMultilingual-Feldern übergeben.
Bestehende vs. Neue API-Typen
| Typ | Quelle | Verwendung |
|---|---|---|
AutomationTemplate |
subAutomationTemplates.py (Legacy) |
Alte hardcoded Templates |
AutomationTemplateDB |
DB (neu) | Neue datenbankbasierte Templates |
Die bestehende fetchAutomationTemplates() Funktion lädt Legacy-Templates. Für die neuen DB-Templates wird fetchAutomationTemplatesDB() verwendet.
1. Übersicht der Komponenten
| Komponente | Zweck | Status |
|---|---|---|
automationApi.ts |
API-Funktionen für Templates + Actions | Erweitern |
useAutomations.ts |
Hooks für Templates + Actions | Erweitern |
AutomationsPage.tsx |
Liste der Definitions; öffnet Editor | Erweitern |
AutomationTemplatesPage.tsx |
Liste der Templates (CRUD) | Neu |
AutomationEditor.tsx |
Editor mit Modus-Flag (Definition/Template) | Neu |
ActionsPanel.tsx |
Actions-Katalog mit Copy/Paste | Neu |
2. API-Erweiterungen (automationApi.ts)
2.1 Neuer Typ: AutomationTemplate (DB-Version, MultiLanguage)
// Multilingual text type (matches backend TextMultilingual)
export interface TextMultilingual {
en: string;
ge?: string;
fr?: string;
it?: string;
}
// NEW: AutomationTemplate from DB (not the old in-memory structure)
export interface AutomationTemplateDB {
id: string;
label: TextMultilingual;
overview?: TextMultilingual;
template: string; // JSON string with {{KEY:...}} placeholders
_createdAt?: number;
_createdBy?: string;
_createdByUserName?: string;
}
// 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<string, any>;
execResultLabel: string;
};
}
export interface WorkflowActionParameter {
name: string;
type: string;
frontendType: string;
required: boolean;
default?: any;
description: string;
frontendOptions?: string | string[];
}
2.2 Neue API-Funktionen
// ============================================================================
// AUTOMATION TEMPLATES (DB) API
// ============================================================================
/**
* Fetch all automation templates (RBAC-filtered: own templates)
* Endpoint: GET /api/automation-templates
*/
export async function fetchAutomationTemplatesDB(
request: ApiRequestFunction
): Promise<AutomationTemplateDB[]> {
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 template by ID
* Endpoint: GET /api/automation-templates/{id}
*/
export async function fetchAutomationTemplateById(
request: ApiRequestFunction,
templateId: string
): Promise<AutomationTemplateDB | null> {
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<AutomationTemplateDB, 'id' | '_createdAt' | '_createdBy'>
): Promise<AutomationTemplateDB> {
return await request({
url: '/api/automation-templates',
method: 'post',
data: templateData
});
}
/**
* Update automation template
* Endpoint: PUT /api/automation-templates/{id}
*/
export async function updateAutomationTemplateApi(
request: ApiRequestFunction,
templateId: string,
templateData: Partial<AutomationTemplateDB>
): Promise<AutomationTemplateDB> {
return await request({
url: `/api/automation-templates/${templateId}`,
method: 'put',
data: templateData
});
}
/**
* Delete automation template
* Endpoint: DELETE /api/automation-templates/{id}
*/
export async function deleteAutomationTemplateApi(
request: ApiRequestFunction,
templateId: string
): Promise<void> {
await request({
url: `/api/automation-templates/${templateId}`,
method: 'delete'
});
}
// ============================================================================
// WORKFLOW ACTIONS API
// ============================================================================
/**
* Fetch available workflow actions (RBAC-filtered)
* Endpoint: GET /api/automations/actions
*/
export async function fetchWorkflowActions(
request: ApiRequestFunction
): Promise<WorkflowAction[]> {
const data = await request({
url: '/api/automations/actions',
method: 'get'
});
return data?.actions || [];
}
3. Hooks-Erweiterungen (useAutomations.ts)
3.1 Neuer Hook: useAutomationTemplates
/**
* Hook for managing AutomationTemplates (DB)
*/
export function useAutomationTemplates() {
const [templates, setTemplates] = useState<AutomationTemplateDB[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { request } = useApiRequest();
const { checkPermission } = usePermissions();
const [permissions, setPermissions] = useState<UserPermissions | null>(null);
const fetchTemplates = useCallback(async () => {
setLoading(true);
try {
const data = await fetchAutomationTemplatesDB(request);
setTemplates(data);
} catch (e: any) {
setError(e.message);
setTemplates([]);
} finally {
setLoading(false);
}
}, [request]);
const fetchPermissions = useCallback(async () => {
const perms = await checkPermission('DATA', 'AutomationTemplate');
setPermissions(perms);
return perms;
}, [checkPermission]);
const createTemplate = useCallback(async (data: Omit<AutomationTemplateDB, 'id'>) => {
return await createAutomationTemplateApi(request, data);
}, [request]);
const updateTemplate = useCallback(async (id: string, data: Partial<AutomationTemplateDB>) => {
return await updateAutomationTemplateApi(request, id, data);
}, [request]);
const deleteTemplate = useCallback(async (id: string) => {
await deleteAutomationTemplateApi(request, id);
}, [request]);
return {
templates,
loading,
error,
permissions,
fetchTemplates,
fetchPermissions,
createTemplate,
updateTemplate,
deleteTemplate,
};
}
3.2 Neuer Hook: useWorkflowActions
/**
* Hook for fetching available workflow actions (RBAC-filtered)
*/
export function useWorkflowActions() {
const [actions, setActions] = useState<WorkflowAction[]>([]);
const [loading, setLoading] = useState(false);
const { request } = useApiRequest();
const fetchActions = useCallback(async () => {
setLoading(true);
try {
const data = await fetchWorkflowActions(request);
setActions(data);
} catch (e) {
console.error('Error fetching actions:', e);
setActions([]);
} finally {
setLoading(false);
}
}, [request]);
return { actions, loading, fetchActions };
}
4. Neue Seite: AutomationTemplatesPage.tsx
4.1 Imports
import React, { useState, useMemo, useEffect } from 'react';
import { FaSync, FaPlus, FaFileAlt } from 'react-icons/fa';
import { useLanguage } from '../../providers/language/LanguageContext';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
import { useAutomationTemplates, AutomationTemplateDB, TextMultilingual } from '../../hooks/useAutomations';
import { AutomationEditor } from '../../components/AutomationEditor'; // Neu
import styles from '../admin/Admin.module.css';
4.2 Struktur
/**
* AutomationTemplatesPage
*
* CRUD für AutomationTemplates (eigene Templates verwalten).
* Nutzt FormGeneratorTable wie AutomationsPage.
*/
export const AutomationTemplatesPage: React.FC = () => {
const {
templates,
loading,
permissions,
fetchTemplates,
fetchPermissions,
createTemplate,
updateTemplate,
deleteTemplate,
} = useAutomationTemplates();
const [showEditor, setShowEditor] = useState(false);
const [editingTemplate, setEditingTemplate] = useState<AutomationTemplateDB | null>(null);
useEffect(() => {
fetchTemplates();
fetchPermissions();
}, []);
// Check permissions
const canCreate = permissions?.create !== 'n';
const canUpdate = permissions?.update !== 'n';
const canDelete = permissions?.delete !== 'n';
// Open Editor in "Template mode"
const handleEdit = (template: AutomationTemplateDB) => {
setEditingTemplate(template);
setShowEditor(true);
};
const handleCreate = () => {
setEditingTemplate(null); // New template
setShowEditor(true);
};
const handleSave = async (data: Partial<AutomationTemplateDB>) => {
if (editingTemplate) {
await updateTemplate(editingTemplate.id, data);
} else {
await createTemplate(data as any);
}
setShowEditor(false);
fetchTemplates();
};
// Columns - FormGeneratorTable rendert TextMultilingual automatisch in User-Sprache!
const columns = useMemo(() => [
{ key: 'label', label: 'Label', type: 'string' as const, sortable: true, searchable: true },
{ key: 'overview', label: 'Beschreibung', type: 'string' as const, width: 300 },
{ key: '_createdByUserName', label: 'Erstellt von', type: 'string' as const },
], []);
// Handle delete
const handleDelete = async (template: AutomationTemplateDB) => {
await deleteTemplate(template.id);
fetchTemplates();
};
return (
<div className={styles.adminPage}>
<div className={styles.pageHeader}>
<div>
<h1 className={styles.pageTitle}>Automation-Vorlagen</h1>
<p className={styles.pageSubtitle}>Verwalten Sie Ihre Workflow-Vorlagen</p>
</div>
<div className={styles.headerActions}>
<button className={styles.secondaryButton} onClick={() => fetchTemplates()}>
<FaSync /> Aktualisieren
</button>
{canCreate && (
<button className={styles.primaryButton} onClick={handleCreate}>
<FaPlus /> Neue Vorlage
</button>
)}
</div>
</div>
<div className={styles.tableContainer}>
<FormGeneratorTable
data={templates as any[]}
columns={columns}
loading={loading}
pagination={true}
pageSize={25}
searchable={true}
sortable={true}
actionButtons={[
...(canUpdate ? [{
type: 'edit' as const,
onAction: handleEdit,
title: 'Bearbeiten',
}] : []),
...(canDelete ? [{
type: 'delete' as const,
title: 'Löschen',
}] : []),
]}
onDelete={handleDelete}
emptyMessage="Keine Vorlagen vorhanden"
/>
</div>
{showEditor && (
<AutomationEditor
mode="template"
initialData={editingTemplate}
onSave={handleSave}
onCancel={() => setShowEditor(false)}
/>
)}
</div>
);
};
5. Neue Komponente: AutomationEditor.tsx
5.1 Konzept: Saubere Lösung via FormGeneratorForm
Kernidee: FormGeneratorForm nutzen für ALLE Felder inkl. Multilingual!
/**
* Attribute-Definitionen je nach Modus
* FormGeneratorForm rendert type:'multilingual' automatisch mit Sprach-Tabs
*/
function getEditorAttributes(mode: 'definition' | 'template'): AttributeDefinition[] {
const baseAttributes: AttributeDefinition[] = [
{
name: 'template',
label: 'Template JSON',
type: 'textarea',
required: true,
minRows: 15,
maxRows: 30,
description: 'Workflow-Definition als JSON'
}
];
if (mode === 'template') {
return [
{ name: 'label', label: 'Label', type: 'multilingual', required: true },
{ name: 'overview', label: 'Beschreibung', type: 'multilingual' },
...baseAttributes
];
} else {
return [
{ name: 'label', label: 'Label', type: 'text', required: true },
{ name: 'schedule', label: 'Schedule (Cron)', type: 'text' },
{ name: 'active', label: 'Aktiv', type: 'checkbox' },
...baseAttributes,
{ name: 'placeholders', label: 'Platzhalter', type: 'textarea', description: 'JSON-Objekt' }
];
}
}
5.2 Komponente
interface AutomationEditorProps {
mode: 'definition' | 'template';
initialData?: AutomationTemplateDB | Automation | null;
onSave: (data: any) => Promise<void>;
onCancel: () => void;
}
export const AutomationEditor: React.FC<AutomationEditorProps> = ({
mode,
initialData,
onSave,
onCancel,
}) => {
const [showActionsPanel, setShowActionsPanel] = useState(false);
// Daten für FormGeneratorForm vorbereiten
const formData = useMemo(() => {
if (!initialData) return {};
return {
...initialData,
template: typeof initialData.template === 'string'
? initialData.template
: JSON.stringify(initialData.template, null, 2)
};
}, [initialData]);
return (
<div className={styles.editorOverlay}>
<div className={styles.editorContainer}>
<header>
<h2>{mode === 'template' ? 'Template bearbeiten' : 'Automation bearbeiten'}</h2>
<button onClick={onCancel}><FaTimes /></button>
</header>
<div className={styles.editorContent}>
{/* Main Form - FormGeneratorForm macht alles! */}
<div className={styles.editorMain}>
<FormGeneratorForm
attributes={getEditorAttributes(mode)}
data={formData}
mode={initialData ? 'edit' : 'create'}
onSubmit={onSave}
onCancel={onCancel}
submitButtonText="Speichern"
cancelButtonText="Abbrechen"
/>
</div>
{/* Actions Panel (optional, für Copy/Paste) */}
<div className={styles.actionsPanel}>
<button onClick={() => setShowActionsPanel(!showActionsPanel)}>
{showActionsPanel ? 'Actions ausblenden' : 'Actions anzeigen'}
</button>
{showActionsPanel && <ActionsPanel />}
</div>
</div>
</div>
</div>
);
};
Vorteile dieser Lösung:
- Kein manueller Code für Multilingual-Inputs
- FormGeneratorForm validiert automatisch
- Konsistent mit dem Rest der Anwendung
- Weniger Code, weniger Bugs
6. Neue Komponente: ActionsPanel.tsx
6.1 Struktur
interface ActionsPanelProps {
onInsert: (actionJson: string) => void;
onCopy?: (actionJson: string) => void;
}
export const ActionsPanel: React.FC<ActionsPanelProps> = ({ onInsert, onCopy }) => {
const { actions, loading, fetchActions } = useWorkflowActions();
const [filter, setFilter] = useState('');
const [expandedAction, setExpandedAction] = useState<string | null>(null);
useEffect(() => {
fetchActions();
}, []);
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)
);
}, [actions, filter]);
// Group by method
const groupedActions = useMemo(() => {
const groups: Record<string, WorkflowAction[]> = {};
filteredActions.forEach(action => {
if (!groups[action.method]) {
groups[action.method] = [];
}
groups[action.method].push(action);
});
return groups;
}, [filteredActions]);
const handleCopy = (action: WorkflowAction) => {
const json = JSON.stringify(action.exampleJson, null, 2);
navigator.clipboard.writeText(json);
onCopy?.(json);
};
const handleInsert = (action: WorkflowAction) => {
const json = JSON.stringify(action.exampleJson, null, 2);
onInsert(json);
};
return (
<div className={styles.actionsPanel}>
<h3>Verfügbare Actions</h3>
<input
type="text"
placeholder="Suchen..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
{loading ? (
<div>Lade Actions...</div>
) : (
<div className={styles.actionsList}>
{Object.entries(groupedActions).map(([method, methodActions]) => (
<div key={method} className={styles.methodGroup}>
<h4>{method}</h4>
{methodActions.map(action => (
<div key={action.actionId} className={styles.actionItem}>
<div
className={styles.actionHeader}
onClick={() => setExpandedAction(
expandedAction === action.actionId ? null : action.actionId
)}
>
<span className={styles.actionName}>
{action.action}
</span>
<span className={styles.actionDesc}>
{action.description}
</span>
</div>
{expandedAction === action.actionId && (
<div className={styles.actionDetails}>
<h5>Parameter:</h5>
<ul>
{action.parameters.map(p => (
<li key={p.name}>
<strong>{p.name}</strong>
{p.required && <span className={styles.required}>*</span>}
: {p.description}
</li>
))}
</ul>
<h5>Beispiel JSON:</h5>
<pre>{JSON.stringify(action.exampleJson, null, 2)}</pre>
<div className={styles.actionButtons}>
<button onClick={() => handleCopy(action)}>
<FaCopy /> Kopieren
</button>
<button onClick={() => handleInsert(action)}>
<FaPlus /> Einfügen
</button>
</div>
</div>
)}
</div>
))}
</div>
))}
</div>
)}
</div>
);
};
7. Anpassungen in AutomationsPage.tsx
7.1 Editor-Integration
Bestehende handleEditClick und Template-Auswahl anpassen:
// State for editor
const [showEditor, setShowEditor] = useState(false);
const [editorMode, setEditorMode] = useState<'definition' | 'template'>('definition');
const [editorInitialData, setEditorInitialData] = useState<Automation | AutomationTemplateDB | null>(null);
// Edit existing definition
const handleEditClick = async (automation: Automation) => {
const fullAutomation = await fetchAutomationById(automation.id);
setEditorInitialData(fullAutomation);
setEditorMode('definition');
setShowEditor(true);
};
// Create from template
const handleCreateFromTemplate = async (template: AutomationTemplateDB) => {
// Extract label in user's language
const userLang = getUserLanguage(); // e.g., 'de' or 'en'
const label = template.label[userLang] || template.label.en || 'New Automation';
// Pre-fill new definition
const newDefinition: Partial<Automation> = {
label: label,
template: template.template,
placeholders: {},
schedule: '0 22 * * *',
active: false,
mandateId: mandateId,
featureInstanceId: featureInstanceId,
};
setEditorInitialData(newDefinition as Automation);
setEditorMode('definition');
setShowEditor(true);
};
// Save handler from editor
const handleEditorSave = async (data: any) => {
if (editorMode === 'definition') {
if (editorInitialData?.id) {
await handleAutomationUpdate(editorInitialData.id, data);
} else {
await handleAutomationCreate(data);
}
}
// Template save is handled in AutomationTemplatesPage
setShowEditor(false);
refetch();
};
7.2 Template-Auswahl Modal anpassen
Bisherige Template-Auswahl nutzt jetzt AutomationTemplateDB statt altes Format:
const [templatesDB, setTemplatesDB] = useState<AutomationTemplateDB[]>([]);
const loadTemplates = async () => {
setLoadingTemplates(true);
const data = await fetchAutomationTemplatesDB(request);
setTemplatesDB(data);
setLoadingTemplates(false);
};
// In Template-Modal: Display label in current language
// Nutze inline-Zugriff oder FormGeneratorTable für Liste
const { currentLanguage } = useLanguage();
const getLang = (ml: TextMultilingual | undefined) =>
ml?.[currentLanguage as keyof TextMultilingual] || ml?.en || '';
{templatesDB.map(template => (
<div key={template.id} onClick={() => handleCreateFromTemplate(template)}>
<h4>{getLang(template.label)}</h4>
<p>{getLang(template.overview)}</p>
</div>
))}
8. Navigation / Routing
Architektur-Hinweis: ObjectKey-Formate
| Kontext | Format | Beispiel | Verwendung |
|---|---|---|---|
| Backend Navigation | ui.system.xxx |
ui.system.automations |
NAVIGATION_SECTIONS in mainSystem.py |
| Frontend Icons | page.system.xxx |
page.system.automations |
pageRegistry.tsx PAGE_ICONS |
| RBAC DATA | data.automation.xxx |
data.automation.AutomationTemplate |
Tabellen-Zugriffsregeln |
| RBAC UI | ui.feature.automation.xxx |
ui.feature.automation.templates |
Feature-UI-Objekte |
Die Sidebar-Navigation kommt vom Backend (/api/navigation), nicht aus pageRegistry. PageRegistry liefert nur die Icons zu den vom Backend gelieferten Items.
8.1 Backend: mainSystem.py (NAVIGATION_SECTIONS)
WICHTIG: Die Navigation wird vom Backend gesteuert! In gateway/modules/system/mainSystem.py unter NAVIGATION_SECTIONS im Abschnitt "workflows" hinzufügen:
{
"id": "automation-templates",
"objectKey": "ui.system.automation-templates",
"label": {"en": "Templates", "de": "Vorlagen", "fr": "Modèles"},
"icon": "FaFileAlt", # Oder FaCopy für Vorlagen
"path": "/workflows/automation-templates",
"order": 35, # Nach automations (30)
"public": True,
},
8.2 Frontend: App.tsx
Route innerhalb des <Route path="workflows"> Blocks hinzufügen:
// In App.tsx, im workflows-Block:
<Route path="workflows">
<Route path="playground" element={<PlaygroundPage />} />
<Route path="list" element={<WorkflowsPage />} />
<Route path="automations" element={<AutomationsPage />} />
<Route path="automation-templates" element={<AutomationTemplatesPage />} /> {/* NEU */}
</Route>
Import hinzufügen:
import { AutomationTemplatesPage } from './pages/workflows/AutomationTemplatesPage';
8.3 Frontend: pageRegistry.tsx
WICHTIG: Format ist page.system.xxx, nicht ui.system.xxx!
// Import hinzufügen (FaFileAlt ist bereits importiert):
// import { ..., FaFileAlt, ... } from 'react-icons/fa';
// In PAGE_ICONS hinzufügen:
'page.system.automation-templates': <FaFileAlt />,
8.4 Frontend: pages/workflows/index.ts
Export hinzufügen:
export { PlaygroundPage } from './PlaygroundPage';
export { WorkflowsPage } from './WorkflowsPage';
export { AutomationsPage } from './AutomationsPage';
export { AutomationTemplatesPage } from './AutomationTemplatesPage'; // NEU
8.5 Locales (de.ts, en.ts, fr.ts)
Übersetzungen hinzufügen:
de.ts:
'nav.automation-templates': 'Vorlagen',
'automation-templates.title': 'Automation-Vorlagen',
'automation-templates.create': 'Neue Vorlage',
'automation-templates.edit': 'Vorlage bearbeiten',
'automation-templates.empty': 'Keine Vorlagen vorhanden',
en.ts:
'nav.automation-templates': 'Templates',
'automation-templates.title': 'Automation Templates',
'automation-templates.create': 'New Template',
'automation-templates.edit': 'Edit Template',
'automation-templates.empty': 'No templates available',
9. Hilfsfunktionen
Hinweis:
getMultilingualText()ist NICHT nötig - FormGeneratorTable rendert TextMultilingual automatisch in der User-Sprache viaformatTextMultilingual()intern.
9.1 extractPlaceholdersFromJson
/**
* Extract {{KEY:name}} placeholders from JSON string
*/
export function extractPlaceholdersFromJson(jsonString: string): string[] {
const regex = /\{\{KEY:(\w+)\}\}/g;
const keys: string[] = [];
let match;
while ((match = regex.exec(jsonString)) !== null) {
if (!keys.includes(match[1])) {
keys.push(match[1]);
}
}
return keys;
}
10. Zusammenfassung der Dateien
| Datei | Änderung |
|---|---|
api/automationApi.ts |
Neue Typen + API-Funktionen für Templates (DB) und Actions |
hooks/useAutomations.ts |
Neue Hooks: useAutomationTemplates, useWorkflowActions |
pages/workflows/AutomationsPage.tsx |
Editor-Integration, Template-Auswahl mit DB-Templates |
pages/workflows/AutomationTemplatesPage.tsx |
Neu: CRUD für eigene Templates |
components/AutomationEditor/AutomationEditor.tsx |
Neu: Editor mit Modus-Flag |
components/AutomationEditor/ActionsPanel.tsx |
Neu: Actions-Katalog |
App.tsx |
Neue Route für AutomationTemplatesPage |
config/pageRegistry.tsx |
Icon für neue Seite |
11. Implementierungsreihenfolge
- API-Typen und Funktionen (
automationApi.ts) - Hooks (
useAutomations.tserweitern) - ActionsPanel (unabhängig testbar)
- AutomationEditor (Kern-Komponente)
- AutomationTemplatesPage (CRUD für Templates)
- AutomationsPage anpassen (Editor-Integration)
- Routing (App.tsx, Navigation)