'use client' import { useCallback, useMemo, useState } from 'react' import { ReactFlow, ReactFlowProvider, Background, Controls, MiniMap, addEdge, useNodesState, useEdgesState, useReactFlow, type Connection, type Node, } from '@xyflow/react' import '@xyflow/react/dist/style.css' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Switch } from '@/components/ui/switch' import { Plus, Variable } from 'lucide-react' import { workflowNodeTypes } from './workflow-nodes' import { WorkflowTriggersPanel } from './workflow-triggers-panel' import { WorkflowNodeInspector } from './workflow-node-inspector' import { AutomationDomainProvider } from './automation-domain-context' import { PALETTE_NODE_TYPES, NODE_TYPE_LABELS, NODE_TYPE_DESCRIPTIONS, } from '@/lib/mail-automation/node-definitions' import { createFlowNode, flowToWorkflow, workflowEdgesToFlow, workflowNodesToFlow, } from '@/lib/mail-automation/workflow-flow' import type { ApiRule } from '@/lib/api/types' import type { ExecVariable, RuleEditorState, WorkflowNodeType } from '@/lib/mail-automation/types' import { nextNodeId } from '@/lib/mail-automation/defaults' interface RuleWorkflowEditorProps { state: RuleEditorState allRules: ApiRule[] onChange: (state: RuleEditorState) => void readOnly?: boolean } export function RuleWorkflowEditor(props: RuleWorkflowEditorProps) { return ( ) } function RuleWorkflowEditorInner({ state, allRules, onChange, readOnly, }: RuleWorkflowEditorProps) { const { setCenter, getNodes } = useReactFlow() const initialNodes = useMemo(() => workflowNodesToFlow(state.workflow.nodes), [state.workflow.nodes]) const initialEdges = useMemo(() => workflowEdgesToFlow(state.workflow.edges), [state.workflow.edges]) const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes) const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges) const [selectedId, setSelectedId] = useState(null) const selectedNode = nodes.find((n) => n.id === selectedId) ?? null const syncWorkflow = useCallback( (nextNodes: Node[], nextEdges: typeof edges) => { onChange({ ...state, workflow: flowToWorkflow( state.rule_kind, state.workflow.triggers, state.workflow.variables, nextNodes, nextEdges ), }) }, [onChange, state] ) const applySelection = useCallback( (nodeId: string | null) => { setSelectedId(nodeId) setNodes((nds) => nds.map((n) => ({ ...n, selected: nodeId !== null && n.id === nodeId, })) ) }, [setNodes] ) const focusNode = useCallback( (nodeId: string) => { requestAnimationFrame(() => { const node = getNodes().find((n) => n.id === nodeId) if (!node) return const w = node.measured?.width ?? 200 const h = node.measured?.height ?? 80 setCenter(node.position.x + w / 2, node.position.y + h / 2, { zoom: 1, duration: 300, }) }) }, [getNodes, setCenter] ) const onConnect = useCallback( (connection: Connection) => { if (readOnly) return const next = addEdge({ ...connection, animated: true, id: nextNodeId('e') }, edges) setEdges(next) syncWorkflow(nodes, next) }, [edges, nodes, readOnly, setEdges, syncWorkflow] ) const onNodeDragStop = useCallback(() => { syncWorkflow(nodes, edges) }, [edges, nodes, syncWorkflow]) function addNode(type: WorkflowNodeType) { if (readOnly || type === 'start' || type === 'end') return const last = nodes[nodes.length - 1] const x = last ? last.position.x + 220 : 300 const y = last ? last.position.y : 200 const node = createFlowNode(type, { x, y }) const nextNodes = nodes.map((n) => ({ ...n, selected: false })).concat({ ...node, selected: true }) setNodes(nextNodes) syncWorkflow(nextNodes, edges) applySelection(node.id) setTimeout(() => focusNode(node.id), 50) } function updateNodeData(nodeId: string, data: Record) { const nextNodes = nodes.map((n) => (n.id === nodeId ? { ...n, data } : n)) setNodes(nextNodes) syncWorkflow(nextNodes, edges) } function deleteNode(nodeId: string) { const node = nodes.find((n) => n.id === nodeId) if (!node || node.type === 'start' || node.type === 'end') return const nextNodes = nodes.filter((n) => n.id !== nodeId) const nextEdges = edges.filter((e) => e.source !== nodeId && e.target !== nodeId) setNodes(nextNodes) setEdges(nextEdges) syncWorkflow(nextNodes, nextEdges) applySelection(null) } function updateVariables(variables: ExecVariable[]) { onChange({ ...state, workflow: { ...state.workflow, variables }, }) } return ( Nom onChange({ ...state, name: e.target.value })} /> Priorité onChange({ ...state, priority: Number(e.target.value) || 0 })} /> onChange({ ...state, is_active: v })} /> Active Type {state.rule_kind === 'rule' ? ( onChange({ ...state, workflow: { ...state.workflow, triggers } }) } /> ) : null} applySelection(n.id)} onPaneClick={() => applySelection(null)} fitView proOptions={{ hideAttribution: true }} > ) } function VariablesPanel({ variables, onChange, disabled, }: { variables: ExecVariable[] onChange: (v: ExecVariable[]) => void disabled?: boolean }) { return ( Variables d'exécution {variables.map((v, i) => ( { const next = [...variables] next[i] = { ...v, name: e.target.value } onChange(next) }} /> { const next = [...variables] next[i] = { ...v, default: e.target.value } onChange(next) }} /> ))} onChange([...variables, { name: '', type: 'string', default: '' }])} > Variable ) }