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 { HelpCircle, Zap, CheckCircle, Plus, X } from 'lucide-react'
|
||||||
import { useTreeEditorStore, findNodeInTree } from '@/store/treeEditorStore'
|
import { useTreeEditorStore, findNodeInTree } from '@/store/treeEditorStore'
|
||||||
import { TreeCanvasNode } from './TreeCanvasNode'
|
import { TreeCanvasNode } from './TreeCanvasNode'
|
||||||
|
import { AnswerStubCard } from './AnswerStubCard'
|
||||||
import type { TreeStructure, NodeType } from '@/types'
|
import type { TreeStructure, NodeType } from '@/types'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
@@ -203,6 +204,28 @@ export function TreeCanvas() {
|
|||||||
(nodeId: string, updates: Partial<TreeStructure>) => {
|
(nodeId: string, updates: Partial<TreeStructure>) => {
|
||||||
updateNode(nodeId, updates)
|
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
|
// Resolve pending link for new nodes
|
||||||
const link = pendingLinks.get(nodeId)
|
const link = pendingLinks.get(nodeId)
|
||||||
if (link) {
|
if (link) {
|
||||||
@@ -278,6 +301,16 @@ export function TreeCanvas() {
|
|||||||
[duplicateNode]
|
[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 ──
|
// ── Add node flow ──
|
||||||
const handleAddNodeSelect = useCallback(
|
const handleAddNodeSelect = useCallback(
|
||||||
(type: NodeType, parentId: string, optionId?: string) => {
|
(type: NodeType, parentId: string, optionId?: string) => {
|
||||||
@@ -464,22 +497,30 @@ export function TreeCanvas() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* The node card itself */}
|
{/* The node card — answer stubs get their own component */}
|
||||||
<TreeCanvasNode
|
{node.type === 'answer' ? (
|
||||||
node={node}
|
<AnswerStubCard
|
||||||
depth={0}
|
node={node}
|
||||||
fromOption={optionLabel}
|
fromOption={optionLabel}
|
||||||
isExpanded={isExpanded}
|
onSelectType={handleSelectAnswerType}
|
||||||
isNew={isNew}
|
/>
|
||||||
onToggleExpand={() => handleToggleExpand(node.id)}
|
) : (
|
||||||
onSave={handleSave}
|
<TreeCanvasNode
|
||||||
onCancelNew={handleCancelNew}
|
node={node}
|
||||||
onDelete={handleDelete}
|
depth={0}
|
||||||
onDuplicate={handleDuplicate}
|
fromOption={optionLabel}
|
||||||
onDragStart={handleDragStart}
|
isExpanded={isExpanded}
|
||||||
onDragOver={(e) => handleDragOver(e, parentId, index)}
|
isNew={isNew}
|
||||||
onDrop={(e) => handleDrop(e, parentId, index)}
|
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) */}
|
{/* Unlinked option add buttons (decision nodes with unlinked options) */}
|
||||||
{!isExpanded && unlinkedOptions.length > 0 && (
|
{!isExpanded && unlinkedOptions.length > 0 && (
|
||||||
@@ -586,6 +627,7 @@ export function TreeCanvas() {
|
|||||||
handleCancelNew,
|
handleCancelNew,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
handleDuplicate,
|
handleDuplicate,
|
||||||
|
handleSelectAnswerType,
|
||||||
handleDragStart,
|
handleDragStart,
|
||||||
handleDragOver,
|
handleDragOver,
|
||||||
handleDrop,
|
handleDrop,
|
||||||
|
|||||||
Reference in New Issue
Block a user