198 lines
7.5 KiB
TypeScript
198 lines
7.5 KiB
TypeScript
/**
|
||
* 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>
|
||
);
|
||
};
|