frontend_nyla/src/pages/admin/AdminAutomationEventsPage.tsx
ValueOn AG d2a2cfbd10 Fixed issues:
AdminAutomationEventsPage.tsx — removed the unused _event variable entirely (it was never referenced).
AutomationTemplatesPage.tsx — removed FaCopy from the import (no longer used after switching to type: 'copy').
AutomationsPage.tsx — same, removed the now-unused FaCopy import.
2026-02-12 00:41:30 +01:00

222 lines
6.2 KiB
TypeScript
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.

/**
* AdminAutomationEventsPage
*
* Admin page for viewing and managing automation scheduler events.
* SysAdmin-only: displays all automation definitions with scheduler status.
* Uses FormGeneratorTable for consistent look with other admin pages.
*/
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { FaSync } from 'react-icons/fa';
import api from '../../api';
import styles from './Admin.module.css';
import { FormGeneratorTable, type ColumnConfig } from '../../components/FormGenerator/FormGeneratorTable';
interface AutomationEvent {
eventId: string;
automationId: string;
name: string;
nextRunTime: string | null;
trigger: string | null;
createdBy: string;
mandate: string;
featureInstance: string;
}
const _formatNextRun = (nextRunTime: string | null): string => {
if (!nextRunTime || nextRunTime === 'None') return '';
try {
const date = new Date(nextRunTime);
return date.toLocaleString('de-CH', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
} catch {
return nextRunTime;
}
};
export const AdminAutomationEventsPage: React.FC = () => {
const [events, setEvents] = useState<AutomationEvent[]>([]);
const [loading, setLoading] = useState(true);
const [syncing, setSyncing] = useState(false);
const [error, setError] = useState<string | null>(null);
const [syncResult, setSyncResult] = useState<string | null>(null);
const _fetchEvents = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await api.get('/api/admin/automation-events');
// Map eventId to id for FormGeneratorTable compatibility
setEvents(response.data.map((e: any) => ({ ...e, id: e.eventId })));
} catch (err: any) {
setError(err.response?.data?.detail || 'Fehler beim Laden der Events');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
_fetchEvents();
}, [_fetchEvents]);
const _handleSync = async () => {
try {
setSyncing(true);
setError(null);
setSyncResult(null);
const response = await api.post('/api/admin/automation-events/sync');
const data = response.data;
setSyncResult(`Sync erfolgreich: ${data.synced} Automationen synchronisiert`);
await _fetchEvents();
} catch (err: any) {
setError(err.response?.data?.detail || 'Fehler beim Synchronisieren');
} finally {
setSyncing(false);
}
};
const _handleDelete = useCallback(async (eventId: string) => {
try {
setError(null);
const encodedId = encodeURIComponent(eventId);
await api.post(`/api/admin/automation-events/${encodedId}/remove`);
setEvents(prev => prev.filter(e => e.eventId !== eventId));
} catch (err: any) {
setError(err.response?.data?.detail || 'Fehler beim Entfernen des Events');
throw err;
}
}, [events]);
const columns: ColumnConfig[] = useMemo(() => [
{
key: 'name',
label: 'Name',
type: 'string' as const,
sortable: true,
searchable: true,
width: 200,
minWidth: 120,
},
{
key: 'mandate',
label: 'Mandant',
type: 'string' as const,
sortable: true,
filterable: true,
width: 150,
minWidth: 100,
},
{
key: 'createdBy',
label: 'Erstellt von',
type: 'string' as const,
sortable: true,
filterable: true,
width: 130,
minWidth: 80,
},
{
key: 'featureInstance',
label: 'Feature',
type: 'string' as const,
sortable: true,
filterable: true,
width: 130,
minWidth: 80,
},
{
key: 'nextRunTime',
label: 'Nächste Ausführung',
type: 'string' as const,
sortable: true,
width: 170,
minWidth: 130,
formatter: (value: any) => {
const formatted = _formatNextRun(value);
if (!formatted) return <span style={{ color: 'var(--text-tertiary, #999)' }}></span>;
return formatted;
},
},
{
key: 'trigger',
label: 'Trigger',
type: 'string' as const,
sortable: false,
width: 160,
minWidth: 100,
},
], []);
return (
<div className={styles.adminPage}>
<div className={styles.pageHeader}>
<div>
<h1 className={styles.pageTitle}>Automation Events</h1>
<p className={styles.pageSubtitle}>
Aktive Scheduler-Jobs ({events.length} Events)
</p>
</div>
<div className={styles.headerActions}>
<button
className={styles.secondaryButton}
onClick={_fetchEvents}
disabled={loading}
>
<FaSync className={loading ? 'spinning' : ''} /> Aktualisieren
</button>
<button
className={styles.primaryButton}
onClick={_handleSync}
disabled={syncing}
>
<FaSync className={syncing ? 'spinning' : ''} /> Sync All
</button>
</div>
</div>
{syncResult && (
<div className={styles.infoBox} style={{ background: 'var(--success-bg, #f0fff4)', borderColor: 'var(--success-color, #38a169)' }}>
<span style={{ marginRight: 8, color: 'var(--success-color, #38a169)' }}>&#10003;</span>
{syncResult}
</div>
)}
{error && (
<div className={styles.infoBox} style={{ background: 'var(--warning-bg, #fffbeb)', borderColor: 'var(--warning-color, #d69e2e)' }}>
<span style={{ marginRight: 8, color: 'var(--warning-color, #d69e2e)' }}>!</span>
{error}
</div>
)}
<FormGeneratorTable
data={events}
columns={columns}
loading={loading}
pagination={true}
pageSize={25}
searchable={true}
filterable={true}
sortable={true}
selectable={false}
actionButtons={[
{
type: 'delete' as const,
title: 'Event entfernen',
},
]}
hookData={{
handleDelete: _handleDelete,
refetch: _fetchEvents,
}}
emptyMessage="Keine Automationen gefunden. Nutzen Sie 'Sync All', um Automationen zu synchronisieren."
/>
</div>
);
};
export default AdminAutomationEventsPage;