/** * CanvasHeader - Workflow controls (Neu, Speichern, laden, Ausführen), version selector, and execute result. */ import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react'; import { FaCog, FaPlay, FaSpinner, FaCloudUploadAlt, FaCloudDownloadAlt, FaArchive, FaDatabase, FaBookmark, FaCaretDown, FaSitemap } from 'react-icons/fa'; import type { Automation2Workflow, ExecuteGraphResponse, AutoVersion, AutoTemplateScope } from '../../../api/workflowApi'; import styles from './Automation2FlowEditor.module.css'; import { useLanguage } from '../../../providers/language/LanguageContext'; import { getUserDataCache } from '../../../utils/userCache'; interface CanvasHeaderProps { workflows: Automation2Workflow[]; currentWorkflowId: string | null; onWorkflowSelect: (workflowId: string | null) => void; onNew: () => void; onSave: () => void; onExecute: () => void; onWorkflowSettings?: () => void; onToggleChat?: () => void; saving: boolean; executing: boolean; hasNodes: boolean; /** Phase-4 Schicht-4: when set, the Run button is disabled and the message * is shown as a tooltip. Click triggers `onExecuteBlockedClick` so the * parent can navigate the user to the first offending node. */ executeBlockedReason?: string | null; onExecuteBlockedClick?: () => void; executeResult: ExecuteGraphResponse | null; versions?: AutoVersion[]; currentVersionId?: string | null; onVersionSelect?: (versionId: string | null) => void; onPublishVersion?: (versionId: string) => void; onUnpublishVersion?: (versionId: string) => void; onArchiveVersion?: (versionId: string) => void; onCreateDraft?: () => void; versionLoading?: boolean; onSaveAsTemplate?: (scope: AutoTemplateScope) => void; templateSaving?: boolean; onNewFromTemplate?: () => void; onWorkflowRename?: (workflowId: string, newName: string) => void; onAutoLayout?: () => void; /** Sysadmin-only: when true, NodeConfigPanel renders the static * "Schema (Typ-Referenz)" block and per-parameter type-badges. */ verboseSchema?: boolean; onVerboseSchemaChange?: (next: boolean) => void; } function _getStatusBadge(t: (key: string) => string): Record { return { draft: { label: t('Entwurf'), color: 'var(--warning-color, #ffc107)' }, published: { label: t('Veröffentlicht'), color: 'var(--success-color, #28a745)' }, archived: { label: t('Archiviert'), color: 'var(--text-secondary, #666)' }, }; } export const CanvasHeader: React.FC = ({ workflows, currentWorkflowId, onWorkflowSelect, onNew, onSave, onExecute, onWorkflowSettings, onToggleChat, saving, executing, hasNodes, executeBlockedReason, onExecuteBlockedClick, executeResult, versions, currentVersionId, onVersionSelect, onPublishVersion, onUnpublishVersion, onArchiveVersion, onCreateDraft, versionLoading, onSaveAsTemplate, templateSaving, onNewFromTemplate, onWorkflowRename, onAutoLayout, verboseSchema, onVerboseSchemaChange, }) => { const { t } = useLanguage(); const _isSysAdmin = getUserDataCache()?.isSysAdmin === true; const statusBadge = _getStatusBadge(t); const currentVersion = versions?.find((v) => v.id === currentVersionId); const currentStatus = currentVersion?.status || 'draft'; const badge = statusBadge[currentStatus] || statusBadge.draft; const [newMenuOpen, setNewMenuOpen] = useState(false); const newMenuRef = useRef(null); const [templateMenuOpen, setTemplateMenuOpen] = useState(false); const templateMenuRef = useRef(null); const [editingName, setEditingName] = useState(false); const [nameValue, setNameValue] = useState(''); const nameInputRef = useRef(null); const currentWorkflow = workflows.find((w) => w.id === currentWorkflowId); const _startNameEdit = useCallback(() => { if (!currentWorkflowId || !onWorkflowRename) return; setNameValue(currentWorkflow?.label || ''); setEditingName(true); }, [currentWorkflowId, currentWorkflow?.label, onWorkflowRename]); const _commitNameEdit = useCallback(() => { setEditingName(false); const trimmed = nameValue.trim(); if (!trimmed || !currentWorkflowId || !onWorkflowRename) return; if (trimmed !== currentWorkflow?.label) { onWorkflowRename(currentWorkflowId, trimmed); } }, [nameValue, currentWorkflowId, currentWorkflow?.label, onWorkflowRename]); useEffect(() => { if (editingName && nameInputRef.current) { nameInputRef.current.focus(); nameInputRef.current.select(); } }, [editingName]); useEffect(() => { const _handleClickOutside = (e: MouseEvent) => { if (newMenuRef.current && !newMenuRef.current.contains(e.target as Node)) setNewMenuOpen(false); if (templateMenuRef.current && !templateMenuRef.current.contains(e.target as Node)) setTemplateMenuOpen(false); }; document.addEventListener('mousedown', _handleClickOutside); return () => document.removeEventListener('mousedown', _handleClickOutside); }, []); const scopeLabels = useMemo( () => ({ user: t('Meine Vorlagen'), instance: t('Instanz'), mandate: t('Mandant'), }) as Record, [t] ); const _titleHint = onWorkflowRename && currentWorkflow ? `${currentWorkflow.label} — ${t('Klicken zum Umbenennen')}` : currentWorkflow?.label; return (
{currentWorkflowId && currentWorkflow ? ( editingName ? ( setNameValue(e.target.value)} onBlur={_commitNameEdit} onKeyDown={(e) => { if (e.key === 'Enter') _commitNameEdit(); if (e.key === 'Escape') setEditingName(false); }} /> ) : (

{currentWorkflow.label}

) ) : (

{t('Neuer Workflow')}

)}
{onWorkflowSettings && ( )}
{newMenuOpen && (
{onNewFromTemplate && ( )}
)}
{onAutoLayout && ( )} {currentWorkflowId && onSaveAsTemplate && (
{templateMenuOpen && (
{(['user', 'instance', 'mandate'] as const).map((s) => ( ))}
)}
)} {onToggleChat && ( )} {_isSysAdmin && onVerboseSchemaChange && ( )}
{currentWorkflowId && versions && versions.length > 0 && (
{t('Version:')} {badge.label} {currentVersion && currentStatus === 'draft' && onPublishVersion && ( )} {currentVersion && currentStatus === 'published' && onUnpublishVersion && ( )} {currentVersion && currentStatus !== 'archived' && onArchiveVersion && ( )} {onCreateDraft && ( )} {versionLoading && }
)} {executeResult && (
{executeResult.success ? ( executeResult.warning ? ( <>⚠ {executeResult.warning} ) : ( <>{t('Ausführung abgeschlossen')} ) ) : (executeResult as { paused?: boolean }).paused ? ( <> ⏸ Workflow pausiert. Öffne {t('Workflows/Tasks')} in der Sidebar, um den Task zu bearbeiten. ) : ( <>✗ {executeResult.error ?? t('Unbekannter Fehler')} )}
)}
); };