import { Plus, Trash2, GripVertical } from 'lucide-react' export interface Condition { field: string operator: string value: string } export interface AvailableField { path: string type: string nodeName: string displayName?: string } interface ConditionBuilderProps { conditions: Condition[] combineWith: 'and' | 'or' onChange: (conditions: Condition[], combineWith: 'and' | 'or') => void availableFields?: AvailableField[] } const OPERATORS = [ { value: 'equals', label: 'Equals', description: '==' }, { value: 'not_equals', label: 'Not Equals', description: '!=' }, { value: 'greater_than', label: 'Greater Than', description: '>' }, { value: 'less_than', label: 'Less Than', description: '<' }, { value: 'greater_than_or_equal', label: 'Greater Than or Equal', description: '>=' }, { value: 'less_than_or_equal', label: 'Less Than or Equal', description: '<=' }, { value: 'contains', label: 'Contains', description: 'includes' }, { value: 'not_contains', label: 'Not Contains', description: '!includes' }, { value: 'starts_with', label: 'Starts With', description: 'startsWith' }, { value: 'ends_with', label: 'Ends With', description: 'endsWith' }, { value: 'is_empty', label: 'Is Empty', description: 'null/empty' }, { value: 'is_not_empty', label: 'Is Not Empty', description: '!empty' }, { value: 'is_true', label: 'Is True', description: '=== true' }, { value: 'is_false', label: 'Is False', description: '=== false' }, { value: 'is_null', label: 'Is Null', description: '=== null' }, { value: 'is_not_null', label: 'Is Not Null', description: '!== null' }, { value: 'exists', label: 'Exists', description: '!== undefined' }, { value: 'regex', label: 'Matches Regex', description: '/pattern/' }, ] // Operators that don't need a value input const VALUE_LESS_OPERATORS = [ 'is_empty', 'is_not_empty', 'is_true', 'is_false', 'is_null', 'is_not_null', 'exists', ] export function ConditionBuilder({ conditions, combineWith, onChange, availableFields = [], }: ConditionBuilderProps) { const addCondition = () => { const newCondition: Condition = { field: '', operator: 'equals', value: '', } onChange([...conditions, newCondition], combineWith) } const updateCondition = (index: number, updates: Partial) => { // Strip {{...}} template syntax from field paths - IF condition uses raw paths if (updates.field) { const match = updates.field.match(/^\{\{(.+)\}\}$/) if (match) { updates.field = match[1] } } const updated = conditions.map((c, i) => i === index ? { ...c, ...updates } : c ) onChange(updated, combineWith) } const removeCondition = (index: number) => { onChange( conditions.filter((_, i) => i !== index), combineWith ) } const isValueLessOperator = (operator: string) => VALUE_LESS_OPERATORS.includes(operator) return (
{/* Combine selector */}
When
{/* Conditions list */}
{conditions.length === 0 ? (
No conditions defined. Add a condition to enable branching.
Without conditions, the IF node will always output to the TRUE branch.
) : ( conditions.map((condition, index) => (
{/* Drag handle (placeholder for future drag-drop) */}
{/* Field input with autocomplete */}
updateCondition(index, { field: e.target.value }) } placeholder="e.g., data.status or data.user.email" className="w-full px-3 py-2 border border-gray-200 dark:border-slate-600 rounded-lg text-sm bg-white dark:bg-slate-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-primary-500 focus:border-primary-500" list={`fields-${index}`} /> {availableFields.length > 0 && ( {availableFields.map((field) => ( )}
{/* Operator and Value row */}
{!isValueLessOperator(condition.operator) && (
updateCondition(index, { value: e.target.value }) } placeholder="Value to compare" className="w-full px-3 py-2 border border-gray-200 dark:border-slate-600 rounded-lg text-sm bg-white dark:bg-slate-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-primary-500 focus:border-primary-500" />
)}
{/* Remove button */}
)) )}
{/* Add condition button */} {/* Preview section */} {conditions.length > 0 && (
Condition Preview
{conditions .map((c) => { const op = OPERATORS.find((o) => o.value === c.operator) if (isValueLessOperator(c.operator)) { return `${c.field} ${op?.description || c.operator}` } // Don't quote numeric values for numeric operators const numericOperators = ['greater_than', 'less_than', 'greater_than_or_equal', 'less_than_or_equal'] const isNumeric = numericOperators.includes(c.operator) && !isNaN(Number(c.value)) return `${c.field} ${op?.description || c.operator} ${isNumeric ? c.value : `"${c.value}"`}` }) .join(combineWith === 'and' ? ' && ' : ' || ')}
)}
) }