frontend_nyla/src/pages/workflows/AutomationTemplatesPage.tsx
2026-02-12 00:34:25 +01:00

264 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* AutomationTemplatesPage
*
* Page for managing automation templates (CRUD).
* System templates (isSystem=true) are read-only for non-SysAdmin, with duplicate option.
* Instance templates can be managed by instance admins/editors.
*/
import React, { useState, useMemo, useEffect } from 'react';
import { useAutomationTemplates, type AutomationTemplate } from '../../hooks/useAutomations';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
import { AutomationEditor } from '../../components/AutomationEditor';
import { FaSync, FaPlus, FaFileAlt, FaCopy, FaLock } from 'react-icons/fa';
import { useToast } from '../../contexts/ToastContext';
import { useCurrentUser } from '../../hooks/useUsers';
import styles from '../admin/Admin.module.css';
export const AutomationTemplatesPage: React.FC = () => {
const {
templates,
attributes,
loading,
error,
permissions,
refetch,
createTemplate,
updateTemplate,
deleteTemplate,
duplicateTemplate,
getTemplate,
} = useAutomationTemplates();
const { user: currentUser } = useCurrentUser();
const isSysAdmin = currentUser?.isSysAdmin || false;
const { showSuccess, showError } = useToast();
// Editor states
const [showEditor, setShowEditor] = useState(false);
const [editingTemplate, setEditingTemplate] = useState<AutomationTemplate | null>(null);
const [saving, setSaving] = useState(false);
// Initial fetch
useEffect(() => {
refetch();
}, []);
// Check permissions
const canCreate = permissions?.create !== 'n';
const canUpdate = permissions?.update !== 'n';
const canDelete = permissions?.delete !== 'n';
// Table columns - FormGeneratorTable auto-renders TextMultilingual in user language
const columns = useMemo(() => [
{ key: 'label', label: 'Label', type: 'string' as const, sortable: true, searchable: true, width: 200 },
{ key: 'overview', label: 'Beschreibung', type: 'string' as const, width: 300 },
{ key: 'isSystem', label: 'Typ', type: 'custom' as const, width: 100, render: (value: boolean) =>
value ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: '0.75rem', padding: '0.125rem 0.5rem', borderRadius: 10, background: 'var(--info-color, #3182ce)', color: '#fff' }}><FaLock style={{ fontSize: '0.625rem' }} /> System</span>
: <span style={{ fontSize: '0.75rem', padding: '0.125rem 0.5rem', borderRadius: 10, background: 'var(--success-color, #38a169)', color: '#fff' }}>Instanz</span>
},
{ key: '_createdByUserName', label: 'Erstellt von', type: 'string' as const, width: 150 },
], []);
// Handle edit click - open editor with template data
const handleEditClick = async (template: AutomationTemplate) => {
// Fetch full template data
const fullTemplate = await getTemplate(template.id);
setEditingTemplate(fullTemplate || template);
setShowEditor(true);
};
// Handle create click - open editor for new template
const handleCreateClick = () => {
setEditingTemplate(null);
setShowEditor(true);
};
// Handle editor save
const handleEditorSave = async (data: Partial<AutomationTemplate>) => {
setSaving(true);
try {
if (editingTemplate) {
await updateTemplate(editingTemplate.id, data);
showSuccess('Vorlage aktualisiert');
} else {
await createTemplate(data as any);
showSuccess('Vorlage erstellt');
}
setShowEditor(false);
setEditingTemplate(null);
await refetch();
} catch (err: any) {
showError(`Fehler: ${err.message}`);
} finally {
setSaving(false);
}
};
// Handle editor cancel
const handleEditorCancel = () => {
setShowEditor(false);
setEditingTemplate(null);
};
// Handle delete by ID (used by DeleteActionButton via hookData)
const handleDelete = async (templateId: string): Promise<boolean> => {
try {
await deleteTemplate(templateId);
showSuccess('Vorlage gelöscht');
return true;
} catch (err: any) {
showError(`Fehler: ${err.message}`);
return false;
}
};
// Handle duplicate
const handleDuplicate = async (template: AutomationTemplate) => {
try {
await duplicateTemplate(template.id);
showSuccess('Vorlage dupliziert');
await refetch();
} catch (err: any) {
showError(`Fehler beim Duplizieren: ${err.message}`);
}
};
// Check if template is editable (system templates only by SysAdmin)
const _canEditTemplate = (template: AutomationTemplate) => {
if ((template as any).isSystem) return isSysAdmin;
return canUpdate;
};
const _canDeleteTemplate = (template: AutomationTemplate) => {
if ((template as any).isSystem) return isSysAdmin;
return canDelete;
};
if (error) {
return (
<div className={styles.adminPage}>
<div className={styles.errorContainer}>
<span className={styles.errorIcon}></span>
<p className={styles.errorMessage}>Fehler beim Laden der Vorlagen: {error}</p>
<button className={styles.secondaryButton} onClick={() => refetch()}>
<FaSync /> Erneut versuchen
</button>
</div>
</div>
);
}
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={() => refetch()}
disabled={loading}
>
<FaSync className={loading ? 'spinning' : ''} /> Aktualisieren
</button>
{canCreate && (
<button
className={styles.primaryButton}
onClick={handleCreateClick}
>
<FaPlus /> Neue Vorlage
</button>
)}
</div>
</div>
<div className={styles.tableContainer}>
{loading && (!templates || templates.length === 0) ? (
<div className={styles.loadingContainer}>
<div className={styles.spinner} />
<span>Lade Vorlagen...</span>
</div>
) : !templates || templates.length === 0 ? (
<div className={styles.emptyState}>
<FaFileAlt className={styles.emptyIcon} />
<h3 className={styles.emptyTitle}>Keine Vorlagen vorhanden</h3>
<p className={styles.emptyDescription}>
Erstellen Sie eine neue Vorlage für Ihre Workflows.
</p>
{canCreate && (
<button
className={styles.primaryButton}
onClick={handleCreateClick}
>
<FaPlus /> Vorlage erstellen
</button>
)}
</div>
) : (
<FormGeneratorTable
data={templates as any[]}
columns={columns}
apiEndpoint="/api/automation-templates"
loading={loading}
pagination={true}
pageSize={25}
searchable={true}
filterable={true}
sortable={true}
selectable={false}
actionButtons={[
{
type: 'custom' as const,
icon: <FaCopy />,
title: 'Duplizieren',
onAction: handleDuplicate,
},
{
type: 'edit' as const,
onAction: handleEditClick,
title: 'Bearbeiten',
disabled: (row: any) => row.isSystem && !isSysAdmin
? { disabled: true, message: 'System-Vorlagen können nur vom SysAdmin bearbeitet werden' }
: !canUpdate
? { disabled: true, message: 'Keine Berechtigung' }
: false,
},
{
type: 'delete' as const,
title: 'Löschen',
disabled: (row: any) => row.isSystem && !isSysAdmin
? { disabled: true, message: 'System-Vorlagen können nur vom SysAdmin gelöscht werden' }
: !canDelete
? { disabled: true, message: 'Keine Berechtigung' }
: false,
},
]}
onDelete={(template) => handleDelete(template.id)}
hookData={{
refetch,
handleDelete,
attributes,
}}
emptyMessage="Keine Vorlagen gefunden"
/>
)}
</div>
{/* Automation Editor */}
{showEditor && (
<AutomationEditor
mode="template"
initialData={editingTemplate}
onSave={handleEditorSave}
onCancel={handleEditorCancel}
saving={saving}
/>
)}
</div>
);
};
export default AutomationTemplatesPage;