fix: Phase 5 - Workflow Editor UX Fixes (Round 3)
Frontend Fixes: - AdminPromptsPage: Edit button navigates to workflow-editor for workflow type prompts - WorkflowEditorPage: Fixed save navigation (alert before navigate) - WorkflowEditorPage: selectedNode derived from selectedNodeId (eliminates stale state) - FallbackConfig: Show node labels instead of IDs in fallback edge dropdown - WorkflowCanvas: Enable edge deletion with deletable: true - WorkflowEditorPage: Hide sidebar when config panel is open Bugs Fixed: - C1: Save error "Method Not Allowed" after success - C2: Edit button in admin doesn't open workflow editor - H1: Prompt selection not displayed when re-editing node - H2: Fallback edge dropdown shows node_1/node_2 instead of names - H3: Cannot delete edges - M1: Sidebar takes space when config panel open Technical Changes: - Replaced useState(selectedNode) with useState(selectedNodeId) + derived selectedNode - Removed sync useEffect (no longer needed with derived state) - Added nodes prop to FallbackConfig for label lookup - Swapped alert/navigate order to prevent navigation errors Testing: Manual testing required (see manual test cases)
This commit is contained in:
parent
7d22b052dd
commit
2f70a39052
|
|
@ -41,7 +41,8 @@ export function WorkflowCanvas({
|
||||||
maxZoom={2}
|
maxZoom={2}
|
||||||
defaultEdgeOptions={{
|
defaultEdgeOptions={{
|
||||||
animated: false,
|
animated: false,
|
||||||
style: { strokeWidth: 2 }
|
style: { strokeWidth: 2 },
|
||||||
|
deletable: true
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Background
|
<Background
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,22 @@
|
||||||
* Props:
|
* Props:
|
||||||
* - node: React Flow Node object
|
* - node: React Flow Node object
|
||||||
* - edges: Array of React Flow edges
|
* - edges: Array of React Flow edges
|
||||||
|
* - nodes: Array of React Flow nodes (for label lookup)
|
||||||
* - onChange: (nodeId, updates) => void
|
* - onChange: (nodeId, updates) => void
|
||||||
*/
|
*/
|
||||||
export function FallbackConfig({ node, edges, onChange }) {
|
export function FallbackConfig({ node, edges, nodes, onChange }) {
|
||||||
const fallbackStrategy = node.data.fallback_strategy || 'conservative_skip'
|
const fallbackStrategy = node.data.fallback_strategy || 'conservative_skip'
|
||||||
const fallbackEdge = node.data.fallback_edge || null
|
const fallbackEdge = node.data.fallback_edge || null
|
||||||
|
|
||||||
// Outgoing Edges von diesem Node
|
// Outgoing Edges von diesem Node
|
||||||
const outgoingEdges = edges.filter(e => e.source === node.id)
|
const outgoingEdges = edges.filter(e => e.source === node.id)
|
||||||
|
|
||||||
|
// Helper: Get node label by ID
|
||||||
|
const getNodeLabel = (nodeId) => {
|
||||||
|
const targetNode = nodes.find(n => n.id === nodeId)
|
||||||
|
return targetNode?.data?.label || nodeId
|
||||||
|
}
|
||||||
|
|
||||||
const handleStrategyChange = (e) => {
|
const handleStrategyChange = (e) => {
|
||||||
const strategy = e.target.value
|
const strategy = e.target.value
|
||||||
onChange(node.id, { fallback_strategy: strategy })
|
onChange(node.id, { fallback_strategy: strategy })
|
||||||
|
|
@ -53,7 +60,7 @@ export function FallbackConfig({ node, edges, onChange }) {
|
||||||
<option value="">-- Kante wählen --</option>
|
<option value="">-- Kante wählen --</option>
|
||||||
{outgoingEdges.map(e => (
|
{outgoingEdges.map(e => (
|
||||||
<option key={e.id} value={e.id}>
|
<option key={e.id} value={e.id}>
|
||||||
{e.data?.label || `Edge → ${e.target}`}
|
{e.data?.label || `Edge → ${getNodeLabel(e.target)}`}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -530,7 +530,13 @@ export default function AdminPromptsPage() {
|
||||||
justifyContent: 'flex-end'
|
justifyContent: 'flex-end'
|
||||||
}}>
|
}}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditingPrompt(prompt)}
|
onClick={() => {
|
||||||
|
if (prompt.type === 'workflow') {
|
||||||
|
navigate(`/workflow-editor/${prompt.id}`)
|
||||||
|
} else {
|
||||||
|
setEditingPrompt(prompt)
|
||||||
|
}
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
background: 'none',
|
background: 'none',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ export default function WorkflowEditorPage() {
|
||||||
// State
|
// State
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([])
|
const [nodes, setNodes, onNodesChange] = useNodesState([])
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([])
|
const [edges, setEdges, onEdgesChange] = useEdgesState([])
|
||||||
const [selectedNode, setSelectedNode] = useState(null)
|
const [selectedNodeId, setSelectedNodeId] = useState(null)
|
||||||
|
const selectedNode = selectedNodeId ? nodes.find(n => n.id === selectedNodeId) : null
|
||||||
const [currentPrompt, setCurrentPrompt] = useState(null)
|
const [currentPrompt, setCurrentPrompt] = useState(null)
|
||||||
const [workflowName, setWorkflowName] = useState('Neuer Workflow')
|
const [workflowName, setWorkflowName] = useState('Neuer Workflow')
|
||||||
const [workflowDescription, setWorkflowDescription] = useState('')
|
const [workflowDescription, setWorkflowDescription] = useState('')
|
||||||
|
|
@ -73,16 +74,6 @@ export default function WorkflowEditorPage() {
|
||||||
setValidationWarnings(warnings)
|
setValidationWarnings(warnings)
|
||||||
}, [nodes, edges])
|
}, [nodes, edges])
|
||||||
|
|
||||||
// Keep selectedNode in sync with nodes array (wichtig für Config Panel!)
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedNode) {
|
|
||||||
const updatedNode = nodes.find(n => n.id === selectedNode.id)
|
|
||||||
if (updatedNode && updatedNode !== selectedNode) {
|
|
||||||
setSelectedNode(updatedNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [nodes])
|
|
||||||
|
|
||||||
// ── Handlers ──────────────────────────────────────────────────────────────
|
// ── Handlers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback(
|
||||||
|
|
@ -91,7 +82,7 @@ export default function WorkflowEditorPage() {
|
||||||
)
|
)
|
||||||
|
|
||||||
const onNodeClick = useCallback((event, node) => {
|
const onNodeClick = useCallback((event, node) => {
|
||||||
setSelectedNode(node)
|
setSelectedNodeId(node.id)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleAddNode = (nodeType) => {
|
const handleAddNode = (nodeType) => {
|
||||||
|
|
@ -118,7 +109,7 @@ export default function WorkflowEditorPage() {
|
||||||
|
|
||||||
setNodes((nds) => nds.filter((n) => n.id !== selectedNode.id))
|
setNodes((nds) => nds.filter((n) => n.id !== selectedNode.id))
|
||||||
setEdges((eds) => eds.filter((e) => e.source !== selectedNode.id && e.target !== selectedNode.id))
|
setEdges((eds) => eds.filter((e) => e.source !== selectedNode.id && e.target !== selectedNode.id))
|
||||||
setSelectedNode(null)
|
setSelectedNodeId(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
|
|
@ -157,8 +148,8 @@ export default function WorkflowEditorPage() {
|
||||||
graph_data
|
graph_data
|
||||||
})
|
})
|
||||||
setCurrentPrompt({ id: result.id, name: workflowName })
|
setCurrentPrompt({ id: result.id, name: workflowName })
|
||||||
navigate(`/workflow-editor/${result.id}`)
|
|
||||||
alert('Workflow erstellt!')
|
alert('Workflow erstellt!')
|
||||||
|
navigate(`/workflow-editor/${result.id}`)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e.message)
|
setError(e.message)
|
||||||
|
|
@ -219,7 +210,7 @@ export default function WorkflowEditorPage() {
|
||||||
setCurrentPrompt(null)
|
setCurrentPrompt(null)
|
||||||
setWorkflowName('Neuer Workflow')
|
setWorkflowName('Neuer Workflow')
|
||||||
setWorkflowDescription('')
|
setWorkflowDescription('')
|
||||||
setSelectedNode(null)
|
setSelectedNodeId(null)
|
||||||
navigate('/workflow-editor/new')
|
navigate('/workflow-editor/new')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +287,7 @@ export default function WorkflowEditorPage() {
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="workflow-content">
|
<div className="workflow-content">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="workflow-sidebar">
|
<div className="workflow-sidebar" style={{ display: selectedNode ? 'none' : 'block' }}>
|
||||||
<div className="sidebar-section">
|
<div className="sidebar-section">
|
||||||
<h3>Workflow-Knoten</h3>
|
<h3>Workflow-Knoten</h3>
|
||||||
<div className="node-palette">
|
<div className="node-palette">
|
||||||
|
|
@ -357,7 +348,7 @@ export default function WorkflowEditorPage() {
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
<h2 style={{ margin: 0 }}>Node-Konfiguration</h2>
|
<h2 style={{ margin: 0 }}>Node-Konfiguration</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedNode(null)}
|
onClick={() => setSelectedNodeId(null)}
|
||||||
style={{
|
style={{
|
||||||
background: 'none',
|
background: 'none',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
|
|
@ -435,7 +426,7 @@ export default function WorkflowEditorPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<QuestionAugmentationPanel node={selectedNode} onChange={handleNodeUpdate} />
|
<QuestionAugmentationPanel node={selectedNode} onChange={handleNodeUpdate} />
|
||||||
<FallbackConfig node={selectedNode} edges={edges} onChange={handleNodeUpdate} />
|
<FallbackConfig node={selectedNode} edges={edges} nodes={nodes} onChange={handleNodeUpdate} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -447,7 +438,7 @@ export default function WorkflowEditorPage() {
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onChange={handleNodeUpdate}
|
onChange={handleNodeUpdate}
|
||||||
/>
|
/>
|
||||||
<FallbackConfig node={selectedNode} edges={edges} onChange={handleNodeUpdate} />
|
<FallbackConfig node={selectedNode} edges={edges} nodes={nodes} onChange={handleNodeUpdate} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -464,8 +455,7 @@ export default function WorkflowEditorPage() {
|
||||||
{validationErrors.map((err, i) => (
|
{validationErrors.map((err, i) => (
|
||||||
<div key={i} className="validation-error" onClick={() => {
|
<div key={i} className="validation-error" onClick={() => {
|
||||||
if (err.nodeId) {
|
if (err.nodeId) {
|
||||||
const node = nodes.find(n => n.id === err.nodeId)
|
setSelectedNodeId(err.nodeId)
|
||||||
if (node) setSelectedNode(node)
|
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
❌ {err.message}
|
❌ {err.message}
|
||||||
|
|
@ -475,8 +465,7 @@ export default function WorkflowEditorPage() {
|
||||||
{validationWarnings.map((warn, i) => (
|
{validationWarnings.map((warn, i) => (
|
||||||
<div key={i} className="validation-warning" onClick={() => {
|
<div key={i} className="validation-warning" onClick={() => {
|
||||||
if (warn.nodeId) {
|
if (warn.nodeId) {
|
||||||
const node = nodes.find(n => n.id === warn.nodeId)
|
setSelectedNodeId(warn.nodeId)
|
||||||
if (node) setSelectedNode(node)
|
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
⚠️ {warn.message}
|
⚠️ {warn.message}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user