wiki/z-archive/implementation/doc_automation_templates_impl_frontend.md

933 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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: `AutomationTemplate` mit `TextMultilingual` Feldern
- ✅ Navigation: `/workflows/automation-templates` in 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.tsx`
- `src/components/AutomationEditor/AutomationEditor.module.css`
- `src/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)
```typescript
// 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
```typescript
// ============================================================================
// 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
```typescript
/**
* 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
```typescript
/**
* 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
```typescript
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
```typescript
/**
* 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!
```typescript
/**
* 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
```typescript
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
```typescript
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:
```typescript
// 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:
```typescript
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:
```python
{
"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:
```typescript
// 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:**
```typescript
import { AutomationTemplatesPage } from './pages/workflows/AutomationTemplatesPage';
```
### 8.3 Frontend: pageRegistry.tsx
**WICHTIG:** Format ist `page.system.xxx`, nicht `ui.system.xxx`!
```typescript
// 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:
```typescript
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:**
```typescript
'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:**
```typescript
'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 via `formatTextMultilingual()` intern.
### 9.1 extractPlaceholdersFromJson
```typescript
/**
* 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
1. **API-Typen und Funktionen** (`automationApi.ts`)
2. **Hooks** (`useAutomations.ts` erweitern)
3. **ActionsPanel** (unabhängig testbar)
4. **AutomationEditor** (Kern-Komponente)
5. **AutomationTemplatesPage** (CRUD für Templates)
6. **AutomationsPage anpassen** (Editor-Integration)
7. **Routing** (App.tsx, Navigation)