/** * GraphicalEditorTemplatesPage * * Template management with scope tabs (Meine / Instanz / Mandant / System). * Uses FormGeneratorTable for the data list. * Actions: Copy to my workflows, Share (scope upgrade), Delete. */ import React, { useState, useCallback, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { FaCopy, FaSync, FaShareAlt } from 'react-icons/fa'; import { FormGeneratorTable, type ColumnConfig } from '../../../components/FormGenerator'; import { useInstanceId } from '../../../hooks/useCurrentInstance'; import { useApiRequest } from '../../../hooks/useApi'; import { fetchTemplates, copyTemplate, shareTemplate, deleteWorkflow, type AutoWorkflowTemplate, type AutoTemplateScope, } from '../../../api/workflowApi'; import { useToast } from '../../../contexts/ToastContext'; import { formatUnixTimestamp } from '../../../utils/time'; import styles from '../../../pages/admin/Admin.module.css'; const SCOPE_LABELS: Record = { user: 'Meine', instance: 'Instanz', mandate: 'Mandant', system: 'System', }; function _formatTs(ts?: number): string { if (ts == null || ts <= 0) return '—'; const sec = ts < 1e12 ? ts : ts / 1000; const { time } = formatUnixTimestamp(sec, undefined, { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }); return time; } export const GraphicalEditorTemplatesPage: React.FC = () => { const instanceId = useInstanceId(); const { mandateId } = useParams<{ mandateId: string }>(); const { request } = useApiRequest(); const navigate = useNavigate(); const { showSuccess, showError } = useToast(); const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); const [activeScope, setActiveScope] = useState('all'); const [copyingId, setCopyingId] = useState(null); const [sharingId, setSharingId] = useState(null); const [paginationMeta, setPaginationMeta] = useState(null); const load = useCallback(async (paginationParams?: any) => { if (!instanceId) return; setLoading(true); try { const scope = activeScope === 'all' ? undefined : activeScope; const result = await fetchTemplates(request, instanceId, scope, paginationParams); if (result && typeof result === 'object' && 'items' in result) { setTemplates(result.items as AutoWorkflowTemplate[]); setPaginationMeta(result.pagination); } else { setTemplates(result as AutoWorkflowTemplate[]); setPaginationMeta(null); } } catch (e) { console.error('[graphicalEditor] load templates failed', e); showError('Fehler beim Laden der Vorlagen'); } finally { setLoading(false); } }, [instanceId, request, showError, activeScope]); useEffect(() => { load(); }, [load]); const handleDelete = useCallback( async (templateId: string): Promise => { if (!instanceId) return false; try { await deleteWorkflow(request, instanceId, templateId); showSuccess('Vorlage gelöscht'); await load(); return true; } catch (e: any) { showError(`Fehler: ${e?.message || 'Löschen fehlgeschlagen'}`); return false; } }, [instanceId, request, showSuccess, showError, load] ); const handleCopy = useCallback( async (row: AutoWorkflowTemplate) => { if (!instanceId) return; setCopyingId(row.id); try { await copyTemplate(request, instanceId, row.id); showSuccess('Vorlage als Workflow kopiert'); } catch (e: any) { showError(`Fehler: ${e?.message || 'Kopieren fehlgeschlagen'}`); } finally { setCopyingId(null); } }, [instanceId, request, showSuccess, showError] ); const handleShare = useCallback( async (row: AutoWorkflowTemplate) => { if (!instanceId) return; const currentScope = row.templateScope || 'user'; const nextScope: AutoTemplateScope = currentScope === 'user' ? 'instance' : currentScope === 'instance' ? 'mandate' : 'mandate'; setSharingId(row.id); try { await shareTemplate(request, instanceId, row.id, nextScope); showSuccess(`Vorlage freigegeben (Scope: ${SCOPE_LABELS[nextScope]})`); await load(); } catch (e: any) { showError(`Fehler: ${e?.message || 'Freigabe fehlgeschlagen'}`); } finally { setSharingId(null); } }, [instanceId, request, showSuccess, showError, load] ); const handleEdit = useCallback( (row: AutoWorkflowTemplate) => { if (!mandateId || !instanceId) return; navigate(`/mandates/${mandateId}/graphicalEditor/${instanceId}/editor?workflowId=${row.id}`); }, [mandateId, instanceId, navigate] ); const columns: ColumnConfig[] = [ { key: 'label', label: 'Vorlage', type: 'string', width: 220, sortable: true }, { key: 'templateScope', label: 'Scope', type: 'string', width: 100, formatter: (v: string) => SCOPE_LABELS[v as AutoTemplateScope] ?? v ?? '—', }, { key: 'sharedReadOnly', label: 'Freigegeben', type: 'boolean', width: 100, formatter: (v: boolean) => v ? ( Ja ) : ( Nein ), }, { key: 'sysCreatedBy', label: 'Erstellt von', type: 'string', width: 140, }, { key: 'sysCreatedAt', label: 'Erstellt', type: 'number', width: 140, formatter: (v: number) => _formatTs(v), }, ]; if (!instanceId) { return (

Keine Feature-Instanz gefunden.

); } return (

Workflow-Vorlagen

Vorlagen verwalten, kopieren und freigeben

{(['all', 'user', 'instance', 'mandate', 'system'] as const).map((s) => ( ))}
data={templates} columns={columns} loading={loading} pagination={true} pageSize={25} searchable={true} filterable={true} sortable={true} selectable={false} actionButtons={[ { type: 'edit', title: 'Im Editor öffnen', onAction: handleEdit, }, { type: 'delete', title: 'Löschen', }, ]} customActions={[ { id: 'copy', icon: , title: 'Als Workflow kopieren', onClick: (row) => handleCopy(row), loading: (row) => copyingId === row.id, }, { id: 'share', icon: , title: 'Scope erweitern (freigeben)', onClick: (row) => handleShare(row), loading: (row) => sharingId === row.id, visible: (row) => (row.templateScope || 'user') !== 'system', }, ]} onDelete={(row) => handleDelete(row.id)} hookData={{ refetch: load, handleDelete: (id: string) => handleDelete(id), pagination: paginationMeta }} emptyMessage="Keine Vorlagen gefunden. Erstelle eine Vorlage aus einem bestehenden Workflow." />
); };