feat: auto-create answer stubs on decision save, render AnswerStubCard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState, useCallback, useRef, useEffect } from 'react'
|
||||
import { HelpCircle, Zap, CheckCircle, Plus, X } from 'lucide-react'
|
||||
import { useTreeEditorStore, findNodeInTree } from '@/store/treeEditorStore'
|
||||
import { TreeCanvasNode } from './TreeCanvasNode'
|
||||
import { AnswerStubCard } from './AnswerStubCard'
|
||||
import type { TreeStructure, NodeType } from '@/types'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
@@ -203,6 +204,28 @@ export function TreeCanvas() {
|
||||
(nodeId: string, updates: Partial<TreeStructure>) => {
|
||||
updateNode(nodeId, updates)
|
||||
|
||||
// For decision nodes: create answer stubs for any option without a next_node_id
|
||||
if (updates.options) {
|
||||
const options = updates.options
|
||||
const stubsToCreate: Array<{ opt: typeof options[number]; stubId: string }> = []
|
||||
|
||||
options.forEach((opt) => {
|
||||
if (!opt.next_node_id && opt.label.trim()) {
|
||||
const stubId = addNode(nodeId, 'answer')
|
||||
updateNode(stubId, { title: opt.label })
|
||||
stubsToCreate.push({ opt, stubId })
|
||||
}
|
||||
})
|
||||
|
||||
if (stubsToCreate.length > 0) {
|
||||
const updatedOptions = options.map((o) => {
|
||||
const stub = stubsToCreate.find((s) => s.opt.id === o.id)
|
||||
return stub ? { ...o, next_node_id: stub.stubId } : o
|
||||
})
|
||||
updateNode(nodeId, { options: updatedOptions })
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve pending link for new nodes
|
||||
const link = pendingLinks.get(nodeId)
|
||||
if (link) {
|
||||
@@ -278,6 +301,16 @@ export function TreeCanvas() {
|
||||
[duplicateNode]
|
||||
)
|
||||
|
||||
// ── Convert answer stub to a real node type ──
|
||||
const handleSelectAnswerType = useCallback(
|
||||
(nodeId: string, type: 'decision' | 'action' | 'solution') => {
|
||||
updateNode(nodeId, { type })
|
||||
setExpandedNodeId(nodeId)
|
||||
selectNode(nodeId)
|
||||
},
|
||||
[updateNode, selectNode]
|
||||
)
|
||||
|
||||
// ── Add node flow ──
|
||||
const handleAddNodeSelect = useCallback(
|
||||
(type: NodeType, parentId: string, optionId?: string) => {
|
||||
@@ -464,22 +497,30 @@ export function TreeCanvas() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* The node card itself */}
|
||||
<TreeCanvasNode
|
||||
node={node}
|
||||
depth={0}
|
||||
fromOption={optionLabel}
|
||||
isExpanded={isExpanded}
|
||||
isNew={isNew}
|
||||
onToggleExpand={() => handleToggleExpand(node.id)}
|
||||
onSave={handleSave}
|
||||
onCancelNew={handleCancelNew}
|
||||
onDelete={handleDelete}
|
||||
onDuplicate={handleDuplicate}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={(e) => handleDragOver(e, parentId, index)}
|
||||
onDrop={(e) => handleDrop(e, parentId, index)}
|
||||
/>
|
||||
{/* The node card — answer stubs get their own component */}
|
||||
{node.type === 'answer' ? (
|
||||
<AnswerStubCard
|
||||
node={node}
|
||||
fromOption={optionLabel}
|
||||
onSelectType={handleSelectAnswerType}
|
||||
/>
|
||||
) : (
|
||||
<TreeCanvasNode
|
||||
node={node}
|
||||
depth={0}
|
||||
fromOption={optionLabel}
|
||||
isExpanded={isExpanded}
|
||||
isNew={isNew}
|
||||
onToggleExpand={() => handleToggleExpand(node.id)}
|
||||
onSave={handleSave}
|
||||
onCancelNew={handleCancelNew}
|
||||
onDelete={handleDelete}
|
||||
onDuplicate={handleDuplicate}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={(e) => handleDragOver(e, parentId, index)}
|
||||
onDrop={(e) => handleDrop(e, parentId, index)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Unlinked option add buttons (decision nodes with unlinked options) */}
|
||||
{!isExpanded && unlinkedOptions.length > 0 && (
|
||||
@@ -586,6 +627,7 @@ export function TreeCanvas() {
|
||||
handleCancelNew,
|
||||
handleDelete,
|
||||
handleDuplicate,
|
||||
handleSelectAnswerType,
|
||||
handleDragStart,
|
||||
handleDragOver,
|
||||
handleDrop,
|
||||
|
||||
Reference in New Issue
Block a user