ui-nyla/src/pages/views/automation/AutomationTemplatesView.tsx

198 lines
7.5 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.

/**
* AutomationTemplatesView
*
* View 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, FaLock } from 'react-icons/fa';
import { useToast } from '../../../contexts/ToastContext';
import { useCurrentUser } from '../../../hooks/useUsers';
import styles from '../../admin/Admin.module.css';
export const AutomationTemplatesView: React.FC = () => {
const {
templates,
attributes,
loading,
error,
permissions,
refetch,
fetchTemplates,
pagination,
createTemplate,
updateTemplate,
deleteTemplate,
duplicateTemplate,
getTemplate,
} = useAutomationTemplates();
const { user: currentUser } = useCurrentUser();
const isSysAdmin = currentUser?.isSysAdmin || false;
const { showSuccess, showError } = useToast();
const [showEditor, setShowEditor] = useState(false);
const [editingTemplate, setEditingTemplate] = useState<AutomationTemplate | null>(null);
const [saving, setSaving] = useState(false);
useEffect(() => { refetch(); }, []);
const canCreate = permissions?.create !== 'n';
const canUpdate = permissions?.update !== 'n';
const canDelete = permissions?.delete !== 'n';
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: 'boolean' as const, width: 100, formatter: (value: any) =>
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 },
], []);
const handleEditClick = async (template: AutomationTemplate) => {
const fullTemplate = await getTemplate(template.id);
setEditingTemplate(fullTemplate || template);
setShowEditor(true);
};
const handleCreateClick = () => {
setEditingTemplate(null);
setShowEditor(true);
};
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);
}
};
const handleEditorCancel = () => {
setShowEditor(false);
setEditingTemplate(null);
};
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;
}
};
const handleDuplicate = async (template: AutomationTemplate) => {
try {
await duplicateTemplate(template.id);
showSuccess('Vorlage dupliziert');
await refetch();
} catch (err: any) {
showError(`Fehler beim Duplizieren: ${err.message}`);
}
};
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} ${styles.adminPageFill}`}>
<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: 'copy' as const, 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: fetchTemplates, pagination, handleDelete, attributes }}
emptyMessage="Keine Vorlagen gefunden"
/>
)}
</div>
{showEditor && (
<AutomationEditor
mode="template"
initialData={editingTemplate}
onSave={handleEditorSave}
onCancel={handleEditorCancel}
saving={saving}
/>
)}
</div>
);
};