'use client'
import { memo, type ReactNode } from 'react'
import { Handle, Position, type NodeProps } from '@xyflow/react'
import { cn } from '@/lib/utils'
import {
NODE_COLORS,
CONDITION_OPERATORS,
LABEL_CONDITION_OPERATORS,
} from '@/lib/mail-automation/node-definitions'
import { actionTypeLabel, conditionFieldLabel } from '@/lib/mail-automation/domains'
import { formatConditionSummary } from '@/lib/mail-automation/condition-helpers'
import type {
ActionsNodeData,
ConditionNodeData,
LabelCheckNodeData,
LLMCheckNodeData,
SetVarNodeData,
SwitchNodeData,
CallRuleNodeData,
WorkflowNodeType,
} from '@/lib/mail-automation/types'
function fieldLabel(field: string) {
return conditionFieldLabel(field)
}
function opLabel(op: string) {
const fromString = CONDITION_OPERATORS.find((o) => o.value === op)?.label
if (fromString) return fromString
return LABEL_CONDITION_OPERATORS.find((o) => o.value === op)?.label ?? op
}
function actionLabel(type: string) {
return actionTypeLabel(type)
}
function nodeShell(
type: WorkflowNodeType,
selected: boolean | undefined,
className: string,
children: ReactNode
) {
return (
{children}
)
}
function BranchOutputRow({
id,
label,
labelClassName,
handleClassName,
top,
}: {
id: string
label: string
labelClassName?: string
handleClassName?: string
top: string
}) {
return (
<>
{label}
>
)
}
function BranchOutputs({
branches,
}: {
branches: {
id: string
label: string
labelClassName?: string
handleClassName?: string
}[]
}) {
const rowHeight = 24
const blockHeight = branches.length * rowHeight
return (
{branches.map((b, i) => {
const topPx = rowHeight * i + rowHeight / 2
return
})}
)
}
export const StartNode = memo(function StartNode({ selected }: NodeProps) {
return nodeShell('start', selected, 'min-w-[120px]', (
<>
Début
>
))
})
export const EndNode = memo(function EndNode({ selected }: NodeProps) {
return nodeShell('end', selected, 'min-w-[100px]', (
<>
Fin
>
))
})
export const ConditionNode = memo(function ConditionNode({ data, selected }: NodeProps) {
const d = data as unknown as ConditionNodeData
return nodeShell('condition', selected, 'min-w-[200px]', (
<>
Si…
{formatConditionSummary(d, fieldLabel, opLabel)}
>
))
})
export const LabelCheckNode = memo(function LabelCheckNode({ data, selected }: NodeProps) {
const d = data as unknown as LabelCheckNodeData
return nodeShell('label_check', selected, 'min-w-[200px]', (
<>
Libellé
{d.operator === 'not_has' ? 'Sans' : 'A'} « {d.label || '…'} »
>
))
})
export const SwitchNode = memo(function SwitchNode({ data, selected }: NodeProps) {
const d = data as unknown as SwitchNodeData
const cases = d.cases ?? []
const caseBranches = cases.map((c, i) => ({
id: `case-${i}`,
label: c.label || c.value || `Cas ${i + 1}`,
labelClassName: 'text-violet-600',
handleClassName: '!bg-violet-500',
}))
return nodeShell('switch', selected, 'min-w-[220px]', (
<>
Switch · {fieldLabel(d.field)}
>
))
})
export const LLMCheckNode = memo(function LLMCheckNode({ data, selected }: NodeProps) {
const d = data as unknown as LLMCheckNodeData
return nodeShell('llm_check', selected, 'min-w-[220px]', (
<>
LLM
{d.prompt || 'Prompt…'}
>
))
})
export const ActionsNode = memo(function ActionsNode({ data, selected }: NodeProps) {
const d = data as unknown as ActionsNodeData
const actions = d.actions ?? []
return nodeShell('actions', selected, 'min-w-[200px]', (
<>
Actions ({actions.length})
{actions.slice(0, 4).map((a, i) => (
-
{actionLabel(a.type)}
{a.value ? `: ${a.value}` : ''}
))}
>
))
})
export const SetVarNode = memo(function SetVarNode({ data, selected }: NodeProps) {
const d = data as unknown as SetVarNodeData
return nodeShell('set_var', selected, 'min-w-[180px]', (
<>
Variable
{d.name || 'var'} = {d.value || '…'}
>
))
})
export const CallFunctionNode = memo(function CallFunctionNode({ data, selected }: NodeProps) {
const d = data as unknown as CallRuleNodeData
return nodeShell('call_function', selected, 'min-w-[180px]', (
<>
Fonction
{d.rule_id || 'Choisir…'}
>
))
})
export const CallRuleNode = memo(function CallRuleNode({ data, selected }: NodeProps) {
const d = data as unknown as CallRuleNodeData
return nodeShell('call_rule', selected, 'min-w-[180px]', (
<>
Règle cascade
{d.rule_id || 'Choisir…'}
>
))
})
export const workflowNodeTypes = {
start: StartNode,
end: EndNode,
condition: ConditionNode,
label_check: LabelCheckNode,
switch: SwitchNode,
llm_check: LLMCheckNode,
actions: ActionsNode,
set_var: SetVarNode,
call_function: CallFunctionNode,
call_rule: CallRuleNode,
}