From 3fa01dd68660c32138b870bc47e69a666033cb77 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 11 Apr 2026 15:21:31 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Warnung=20bei=20ungespeicherten=20Workf?= =?UTF-8?q?low-=C3=84nderungen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #0: Ungespeicherte Änderungen gehen verloren beim "Zurück"-Klick Implementiert: - hasUnsavedChanges State tracking - Warnung beim "Zurück"-Button (navigate zu /admin/prompts) - Warnung beim "Neu"-Button (nur wenn unsaved changes) - Browser beforeunload Event (warnt bei Browser-Back/Refresh) Tracking für alle Änderungen: - onNodesChange/onEdgesChange (Node-Bewegung, Löschen via Delete-Taste) - onConnect (neue Edges) - handleAddNode (Node hinzufügen) - handleNodeUpdate (Node-Daten ändern) - handleDeleteNode (Node löschen via Button) - workflowName onChange (Titel ändern) Flag wird cleared: - Nach erfolgreichem Save (Update/Create) - Nach erfolgreichem Load - Bei "Neu" (nach User-Bestätigung) UX: - Klare Warnung: "Du hast ungespeicherte Änderungen" - Kein Datenverlust mehr durch versehentliches Zurück - Browser warnt auch bei Refresh/Close Co-Authored-By: Claude Opus 4.6 --- frontend/src/pages/WorkflowEditorPage.jsx | 79 +++++++++++++++++++---- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/frontend/src/pages/WorkflowEditorPage.jsx b/frontend/src/pages/WorkflowEditorPage.jsx index 42a875a..fb1f4a5 100644 --- a/frontend/src/pages/WorkflowEditorPage.jsx +++ b/frontend/src/pages/WorkflowEditorPage.jsx @@ -77,11 +77,23 @@ export default function WorkflowEditorPage() { const [placeholderPickerTarget, setPlaceholderPickerTarget] = useState('end') // 'end' | 'inline' const endNodeTextareaRef = useRef(null) const inlineTemplateTextareaRef = useRef(null) + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false) // Toast & Confirm Dialog const [toast, setToast] = useState(null) const [confirmDialog, setConfirmDialog] = useState(null) + // Wrapped handlers to track unsaved changes + const handleNodesChange = useCallback((changes) => { + onNodesChange(changes) + setHasUnsavedChanges(true) + }, [onNodesChange]) + + const handleEdgesChange = useCallback((changes) => { + onEdgesChange(changes) + setHasUnsavedChanges(true) + }, [onEdgesChange]) + // Load available basis prompts for Analysis nodes useEffect(() => { async function loadPrompts() { @@ -112,10 +124,26 @@ export default function WorkflowEditorPage() { setValidationWarnings(warnings) }, [nodes, edges]) + // Warn on browser back/refresh if unsaved changes + useEffect(() => { + const handleBeforeUnload = (e) => { + if (hasUnsavedChanges) { + e.preventDefault() + e.returnValue = '' // Chrome requires returnValue to be set + } + } + + window.addEventListener('beforeunload', handleBeforeUnload) + return () => window.removeEventListener('beforeunload', handleBeforeUnload) + }, [hasUnsavedChanges]) + // ── Handlers ────────────────────────────────────────────────────────────── const onConnect = useCallback( - (params) => setEdges((eds) => addEdge(params, eds)), + (params) => { + setEdges((eds) => addEdge(params, eds)) + setHasUnsavedChanges(true) + }, [setEdges] ) @@ -144,6 +172,7 @@ export default function WorkflowEditorPage() { } } setNodes((nds) => [...nds, base]) + setHasUnsavedChanges(true) } const handleNodeUpdate = (nodeId, updates) => { @@ -153,6 +182,7 @@ export default function WorkflowEditorPage() { console.log('📝 Nodes after update:', updated.find(n => n.id === nodeId)) return updated }) + setHasUnsavedChanges(true) } const handleDeleteNode = () => { @@ -161,6 +191,7 @@ export default function WorkflowEditorPage() { setNodes((nds) => nds.filter((n) => n.id !== selectedNode.id)) setEdges((eds) => eds.filter((e) => e.source !== selectedNode.id && e.target !== selectedNode.id)) setSelectedNodeId(null) + setHasUnsavedChanges(true) } const handleSave = async () => { @@ -217,6 +248,9 @@ export default function WorkflowEditorPage() { console.log('🚀 Navigating to:', `/workflow-editor/${result.id}`) navigate(`/workflow-editor/${result.id}`) } + + // Clear unsaved changes flag after successful save + setHasUnsavedChanges(false) } catch (e) { console.error('❌ handleSave error:', e) setError(e.message) @@ -275,6 +309,9 @@ export default function WorkflowEditorPage() { ) nodeIdCounter = maxId + 1 console.log('✅ Workflow loaded successfully, nodes:', loadedNodes.length, 'edges:', loadedEdges.length) + + // Clear unsaved changes flag after successful load + setHasUnsavedChanges(false) } catch (e) { console.error('❌ loadWorkflow error:', e) setError(e.message) @@ -296,14 +333,18 @@ export default function WorkflowEditorPage() { } const handleNew = () => { - if (confirm('Neuen Workflow erstellen? Ungespeicherte Änderungen gehen verloren.')) { - setNodes([]) - setEdges([]) - setCurrentPrompt(null) - setWorkflowName('Neuer Workflow') - setSelectedNodeId(null) - navigate('/workflow-editor/new') + if (hasUnsavedChanges) { + if (!confirm('Neuen Workflow erstellen? Ungespeicherte Änderungen gehen verloren.')) { + return + } } + setNodes([]) + setEdges([]) + setCurrentPrompt(null) + setWorkflowName('Neuer Workflow') + setSelectedNodeId(null) + setHasUnsavedChanges(false) + navigate('/workflow-editor/new') } const handleDelete = async () => { @@ -328,6 +369,17 @@ export default function WorkflowEditorPage() { setExecutionResult(result) } + const handleBack = () => { + if (hasUnsavedChanges) { + const confirm = window.confirm( + 'Du hast ungespeicherte Änderungen.\n\n' + + 'Möchtest du wirklich zurück gehen? Alle Änderungen gehen verloren.' + ) + if (!confirm) return + } + navigate('/admin/prompts') + } + const handlePlaceholderSelect = (placeholderString) => { if (!selectedNode) return @@ -392,14 +444,17 @@ export default function WorkflowEditorPage() {
{/* Toolbar */}
- setWorkflowName(e.target.value)} + onChange={(e) => { + setWorkflowName(e.target.value) + setHasUnsavedChanges(true) + }} placeholder="Interner Workflow-Name (Slug-Basis)" title="Technischer Name in der Datenbank. Den sichtbaren Titel für die KI-Analyse setzt du in der Start-Node." style={{ flex: 1, padding: '8px', borderRadius: '4px', border: '1px solid var(--border)' }} @@ -498,8 +553,8 @@ export default function WorkflowEditorPage() { nodes={nodes} edges={edges} nodeTypes={nodeTypes} - onNodesChange={onNodesChange} - onEdgesChange={onEdgesChange} + onNodesChange={handleNodesChange} + onEdgesChange={handleEdgesChange} onConnect={onConnect} onNodeClick={onNodeClick} />