ui-nyla/src/pages/workflows/AutomationsPage.tsx
2026-01-23 21:05:36 +01:00

334 lines
11 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.

/**
* AutomationsPage
*
* Page for viewing and managing workflow automations using FormGeneratorTable.
* Follows the pattern established in AdminUsersPage/WorkflowsPage.
*/
import React, { useState, useMemo, useEffect } from 'react';
import { useAutomations, useAutomationOperations } from '../../hooks/useAutomations';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
import { FaSync, FaRobot, FaPlay, FaPlus, FaToggleOn, FaToggleOff } from 'react-icons/fa';
import styles from '../admin/Admin.module.css';
interface Automation {
id: string;
label: string;
schedule?: string;
active: boolean;
status?: string;
template?: string;
placeholders?: any;
[key: string]: any;
}
export const AutomationsPage: React.FC = () => {
// Data hook
const {
data: automations,
attributes,
permissions,
pagination,
loading,
error,
refetch,
fetchAutomationById,
updateOptimistically,
} = useAutomations();
// Operations hook
const {
handleAutomationCreate,
handleAutomationUpdate,
handleAutomationDelete,
handleAutomationExecute,
handleAutomationToggleActive,
handleInlineUpdate,
deletingAutomations,
executingAutomations,
creatingAutomation,
} = useAutomationOperations();
const [showCreateModal, setShowCreateModal] = useState(false);
const [editingAutomation, setEditingAutomation] = useState<Automation | null>(null);
// Initial fetch
useEffect(() => {
refetch();
}, []);
// Generate columns from attributes
const columns = useMemo(() => {
return (attributes || []).map(attr => ({
key: attr.name,
label: attr.label || attr.name,
type: attr.type as any,
sortable: attr.sortable !== false,
filterable: attr.filterable !== false,
searchable: attr.searchable !== false,
width: attr.width || 150,
minWidth: attr.minWidth || 100,
maxWidth: attr.maxWidth || 400,
}));
}, [attributes]);
// Check permissions
const canCreate = permissions?.create !== 'n';
const canUpdate = permissions?.update !== 'n';
const canDelete = permissions?.delete !== 'n';
// Handle edit click
const handleEditClick = async (automation: Automation) => {
const fullAutomation = await fetchAutomationById(automation.id);
if (fullAutomation) {
setEditingAutomation(fullAutomation as Automation);
}
};
// Handle create submit
const handleCreateSubmit = async (data: Partial<Automation>) => {
const result = await handleAutomationCreate(data as any);
if (result) {
setShowCreateModal(false);
refetch();
}
};
// Handle edit submit
const handleEditSubmit = async (data: Partial<Automation>) => {
if (!editingAutomation) return;
const success = await handleAutomationUpdate(editingAutomation.id, data);
if (success) {
setEditingAutomation(null);
refetch();
}
};
// Handle delete single automation
const handleDelete = async (automation: Automation) => {
if (window.confirm(`Möchten Sie die Automatisierung "${automation.label}" wirklich löschen?`)) {
const success = await handleAutomationDelete(automation.id);
if (success) {
refetch();
}
}
};
// Handle execute automation
const handleExecute = async (automation: Automation) => {
try {
await handleAutomationExecute(automation.id);
// Show success feedback (could use toast)
console.log('Automation started:', automation.label);
} catch (err: any) {
console.error('Error executing automation:', err);
}
};
// Handle toggle active
const handleToggleActive = async (automation: Automation) => {
// Optimistic update
updateOptimistically(automation.id, { active: !automation.active });
const success = await handleAutomationToggleActive(automation.id, automation.active);
if (!success) {
// Revert on failure
updateOptimistically(automation.id, { active: automation.active });
}
};
// Form attributes for create/edit modal
const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', '_createdBy', '_createdAt', '_modifiedAt', 'status'];
return (attributes || [])
.filter(attr => !excludedFields.includes(attr.name));
}, [attributes]);
if (error) {
return (
<div className={styles.adminPage}>
<div className={styles.errorContainer}>
<span className={styles.errorIcon}></span>
<p className={styles.errorMessage}>Fehler beim Laden der Automatisierungen: {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}>Automatisierungen</h1>
<p className={styles.pageSubtitle}>Geplante und automatisierte Workflows</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={() => setShowCreateModal(true)}
>
<FaPlus /> Neue Automatisierung
</button>
)}
</div>
</div>
<div className={styles.tableContainer}>
{loading && (!automations || automations.length === 0) ? (
<div className={styles.loadingContainer}>
<div className={styles.spinner} />
<span>Lade Automatisierungen...</span>
</div>
) : !automations || automations.length === 0 ? (
<div className={styles.emptyState}>
<FaRobot className={styles.emptyIcon} />
<h3 className={styles.emptyTitle}>Keine Automatisierungen vorhanden</h3>
<p className={styles.emptyDescription}>
Erstellen Sie eine neue Automatisierung, um Workflows zeitgesteuert auszuführen.
</p>
{canCreate && (
<button
className={styles.primaryButton}
onClick={() => setShowCreateModal(true)}
>
<FaPlus /> Erste Automatisierung erstellen
</button>
)}
</div>
) : (
<FormGeneratorTable
data={automations}
columns={columns}
loading={loading}
pagination={true}
pageSize={25}
searchable={true}
filterable={true}
sortable={true}
selectable={false}
actionButtons={[
...(canUpdate ? [{
type: 'edit' as const,
onAction: handleEditClick,
title: 'Bearbeiten',
}] : []),
...(canDelete ? [{
type: 'delete' as const,
title: 'Löschen',
loading: (row: Automation) => deletingAutomations.has(row.id),
}] : []),
]}
customActions={[
{
id: 'execute',
icon: <FaPlay />,
onClick: handleExecute,
title: 'Ausführen',
loading: (row: Automation) => executingAutomations.has(row.id),
},
{
id: 'toggleActive',
icon: (row: Automation) => row.active ? <FaToggleOn /> : <FaToggleOff />,
onClick: handleToggleActive,
title: (row: Automation) => row.active ? 'Deaktivieren' : 'Aktivieren',
} as any,
]}
onDelete={handleDelete}
hookData={{
refetch,
permissions,
pagination,
handleDelete: handleAutomationDelete,
handleInlineUpdate,
updateOptimistically,
}}
emptyMessage="Keine Automatisierungen gefunden"
/>
)}
</div>
{/* Create Modal */}
{showCreateModal && (
<div className={styles.modalOverlay} onClick={() => setShowCreateModal(false)}>
<div className={styles.modal} onClick={e => e.stopPropagation()}>
<div className={styles.modalHeader}>
<h2 className={styles.modalTitle}>Neue Automatisierung</h2>
<button
className={styles.modalClose}
onClick={() => setShowCreateModal(false)}
>
</button>
</div>
<div className={styles.modalContent}>
{formAttributes.length === 0 ? (
<div className={styles.loadingContainer}>
<div className={styles.spinner} />
<span>Lade Formular...</span>
</div>
) : (
<FormGeneratorForm
attributes={formAttributes}
mode="create"
onSubmit={handleCreateSubmit}
onCancel={() => setShowCreateModal(false)}
submitButtonText="Erstellen"
cancelButtonText="Abbrechen"
/>
)}
</div>
</div>
</div>
)}
{/* Edit Modal */}
{editingAutomation && (
<div className={styles.modalOverlay} onClick={() => setEditingAutomation(null)}>
<div className={styles.modal} onClick={e => e.stopPropagation()}>
<div className={styles.modalHeader}>
<h2 className={styles.modalTitle}>Automatisierung bearbeiten</h2>
<button
className={styles.modalClose}
onClick={() => setEditingAutomation(null)}
>
</button>
</div>
<div className={styles.modalContent}>
{formAttributes.length === 0 ? (
<div className={styles.loadingContainer}>
<div className={styles.spinner} />
<span>Lade Formular...</span>
</div>
) : (
<FormGeneratorForm
attributes={formAttributes}
data={editingAutomation}
mode="edit"
onSubmit={handleEditSubmit}
onCancel={() => setEditingAutomation(null)}
submitButtonText="Speichern"
cancelButtonText="Abbrechen"
/>
)}
</div>
</div>
</div>
)}
</div>
);
};
export default AutomationsPage;