import { memo } from 'react' import { Handle, Position, type NodeProps } from '@xyflow/react' import { HelpCircle, Zap, CheckCircle, ChevronDown, ChevronRight, AlertCircle } from 'lucide-react' import { cn } from '@/lib/utils' import type { TreeStructure, NodeType } from '@/types' const NODE_TYPE_CONFIG: Record, { icon: typeof HelpCircle label: string borderClass: string badgeClass: string minimapColor: string }> = { decision: { icon: HelpCircle, label: 'Decision', borderClass: 'border-l-4 border-l-blue-500', badgeClass: 'bg-blue-500/20 text-blue-400', minimapColor: '#3b82f6', }, action: { icon: Zap, label: 'Action', borderClass: 'border-l-4 border-l-yellow-500', badgeClass: 'bg-yellow-500/20 text-yellow-400', minimapColor: '#eab308', }, solution: { icon: CheckCircle, label: 'Solution', borderClass: 'border-l-4 border-l-green-500', badgeClass: 'bg-green-500/20 text-green-400', minimapColor: '#22c55e', }, } export interface FlowCanvasNodeData { node: TreeStructure hasChildren: boolean isCollapsed: boolean hasValidationErrors: boolean isNew: boolean onToggleCollapse: (nodeId: string) => void onContextMenu?: (e: React.MouseEvent, nodeId: string) => void onAcceptSuggestion?: (nodeId: string) => void onDismissSuggestion?: (nodeId: string) => void } function FlowCanvasNodeComponent({ data, selected }: NodeProps) { const { node, hasChildren, isCollapsed, hasValidationErrors, isNew, onToggleCollapse, onContextMenu, onAcceptSuggestion, onDismissSuggestion } = data as unknown as FlowCanvasNodeData const isGhost = !!(node as unknown as Record)._suggestion const nodeType = node.type as Exclude const config = NODE_TYPE_CONFIG[nodeType] ?? NODE_TYPE_CONFIG.decision const Icon = config.icon const title = node.type === 'decision' ? (node.question || 'Untitled Decision') : (node.title || `Untitled ${config.label}`) const optionCount = node.options?.length ?? 0 return ( <> {/* Target handle at top */}
onContextMenu?.(e, node.id)} className={cn( 'w-[280px] rounded-xl border border-border bg-card shadow-sm cursor-pointer transition-all', config.borderClass, selected && 'ring-1 ring-primary shadow-md', isGhost && 'border-dashed !border-primary/40 opacity-60' )} > {/* Header */}
{/* Type badge */} {/* Title */} {title} {/* Badges */} {isNew && ( New )} {hasValidationErrors && ( )}
{/* Decision options preview */} {node.type === 'decision' && optionCount > 0 && (
{optionCount} option{optionCount !== 1 ? 's' : ''}
{node.options!.slice(0, 3).map((opt, i) => (
{String.fromCharCode(65 + i)}{' '} {opt.label || '(empty)'}
))} {optionCount > 3 && (
+{optionCount - 3} more
)}
)} {/* Description preview for action/solution */} {(node.type === 'action' || node.type === 'solution') && node.description && (
{node.description}
)} {/* Collapse chevron */} {hasChildren && (
)} {/* Ghost node accept/dismiss overlay */} {isGhost && (
)}
{/* Source handle at bottom */} ) } export const FlowCanvasNode = memo(FlowCanvasNodeComponent) export { NODE_TYPE_CONFIG }