'use client' import { useCallback, useMemo, useState, ChangeEvent } from 'react' import { Handle, Position, NodeProps, useReactFlow } from 'reactflow' import { nanoid } from 'nanoid' import { useEditorContext } from '@/components/editor/EditorContext' import OptionConditionEditor from '@/components/editor/OptionConditionEditor' import type { Condition } from '@/types/flowchart' type ChoiceOption = { id: string label: string condition?: Condition } type ChoiceNodeData = { prompt: string options: ChoiceOption[] } const MIN_OPTIONS = 2 const MAX_OPTIONS = 6 export default function ChoiceNode({ id, data }: NodeProps) { const { setNodes } = useReactFlow() const [editingConditionOptionId, setEditingConditionOptionId] = useState(null) const updatePrompt = useCallback( (e: ChangeEvent) => { setNodes((nodes) => nodes.map((node) => node.id === id ? { ...node, data: { ...node.data, prompt: e.target.value } } : node ) ) }, [id, setNodes] ) const updateOptionLabel = useCallback( (optionId: string, label: string) => { setNodes((nodes) => nodes.map((node) => node.id === id ? { ...node, data: { ...node.data, options: node.data.options.map((opt: ChoiceOption) => opt.id === optionId ? { ...opt, label } : opt ), }, } : node ) ) }, [id, setNodes] ) const updateOptionCondition = useCallback( (optionId: string, condition: Condition | undefined) => { setNodes((nodes) => nodes.map((node) => node.id === id ? { ...node, data: { ...node.data, options: node.data.options.map((opt: ChoiceOption) => opt.id === optionId ? { ...opt, condition } : opt ), }, } : node ) ) }, [id, setNodes] ) const addOption = useCallback(() => { if (data.options.length >= MAX_OPTIONS) return setNodes((nodes) => nodes.map((node) => node.id === id ? { ...node, data: { ...node.data, options: [ ...node.data.options, { id: nanoid(), label: '' }, ], }, } : node ) ) }, [id, data.options.length, setNodes]) const removeOption = useCallback( (optionId: string) => { if (data.options.length <= MIN_OPTIONS) return setNodes((nodes) => nodes.map((node) => node.id === id ? { ...node, data: { ...node.data, options: node.data.options.filter( (opt: ChoiceOption) => opt.id !== optionId ), }, } : node ) ) }, [id, data.options.length, setNodes] ) const editingOption = useMemo(() => { if (!editingConditionOptionId) return null return data.options.find((opt) => opt.id === editingConditionOptionId) || null }, [editingConditionOptionId, data.options]) const hasInvalidConditionReference = useCallback( (option: ChoiceOption) => { if (!option.condition?.variableId) return false return !variables.some((v) => v.id === option.condition!.variableId) }, [variables] ) return ( <>
Choice
{data.options.map((option, index) => (
updateOptionLabel(option.id, e.target.value)} placeholder={`Option ${index + 1}`} className="flex-1 rounded border border-green-300 bg-white px-2 py-1 text-sm focus:border-green-500 focus:outline-none focus:ring-1 focus:ring-green-500 dark:border-green-600 dark:bg-zinc-800 dark:text-white dark:placeholder-zinc-400" />
{option.condition?.variableId && (
if {option.condition.variableName} {option.condition.operator} {String(option.condition.value)}
)}
))}
{data.options.map((option, index) => (
updateOptionLabel(option.id, e.target.value)} placeholder={`Option ${index + 1}`} className="flex-1 rounded border border-green-300 bg-white px-2 py-1 text-sm focus:border-green-500 focus:outline-none focus:ring-1 focus:ring-green-500 dark:border-green-600 dark:bg-zinc-800 dark:text-white dark:placeholder-zinc-400" />
{option.condition && (
if {option.condition.variableName} {option.condition.operator} {option.condition.value}
)}
))}
{editingOption && ( setEditingConditionOptionId(null)} /> )} ) }