diff --git a/client/src/components/dynamic-groups/condition-builder.tsx b/client/src/components/dynamic-groups/condition-builder.tsx index af9a525..3492153 100644 --- a/client/src/components/dynamic-groups/condition-builder.tsx +++ b/client/src/components/dynamic-groups/condition-builder.tsx @@ -194,210 +194,231 @@ const ConditionBuilder: React.FC = ({ ); }; - // Render a single condition - const renderCondition = (condition: Condition, index: number, level = 0) => { - if (condition.isGroup) { - // Render condition group - return ( -
0 ? `${level * 20}px` : '0' }} - > -
-
- - - - Condition Group ({condition.logicalOperator}) - + // Render all conditions with a flat approach to avoid recursion + const renderAllConditions = () => { + // First, build a map of parentId to array of children conditions + const conditionsByParent = new Map(); + + // Group conditions by parentId + for (const condition of conditions) { + const parentId = condition.parentId; + if (!conditionsByParent.has(parentId)) { + conditionsByParent.set(parentId, []); + } + conditionsByParent.get(parentId)!.push(condition); + } + + // Render a single condition + const renderSingleCondition = (condition: Condition, index: number, level = 0) => { + if (!condition.isGroup) { + // Render regular condition (not a group) + return ( +
0 ? `${level * 20}px` : '0' }} + > +
+
-
+ {index > 0 && !condition.isGroup && condition.parentId === conditions[index-1].parentId && ( +
+ +
+ )} + + {(index === 0 || condition.parentId !== conditions[index-1].parentId) && ( +
+ )} + +
+ + {condition.attribute === 'custom' && ( + updateCondition(index, 'customAttribute', e.target.value)} + /> + )} +
+ +
+ - - - - - +
+ +
+ {condition.operator !== 'present' && condition.operator !== 'notPresent' && ( + updateCondition(index, 'value', e.target.value)} + /> + )} +
+ +
- - {condition.id && expandedGroups.has(condition.id) && ( -
- {renderChildConditions(condition.id, level + 1)} + ); + } else { + // Render condition group + const hasChildren = condition.id && conditionsByParent.has(condition.id); + const isExpanded = condition.id && expandedGroups.has(condition.id); + + return ( +
+
0 ? `${level * 20}px` : '0' }} + > +
+
+ + + + Condition Group ({condition.logicalOperator}) + +
+ +
+ + + + + + + +
+
+ + {isExpanded && ( +
+ {hasChildren ? ( + conditionsByParent.get(condition.id)!.map((childCondition) => { + const childIndex = conditions.indexOf(childCondition); + return ( +
+ {renderSingleCondition(childCondition, childIndex, level + 1)} +
+ ); + }) + ) : ( +
+ No conditions in this group. Add one using the buttons above. +
+ )} +
+ )}
- )} -
- ); - } else { - // Render regular condition (not a group) - return ( -
0 ? `${level * 20}px` : '0' }} - > -
-
- - {index > 0 && !condition.isGroup && condition.parentId === conditions[index-1].parentId && ( -
- -
- )} - - {(index === 0 || condition.parentId !== conditions[index-1].parentId) && ( -
- )} - -
- - - {condition.attribute === 'custom' && ( - updateCondition(index, 'customAttribute', e.target.value)} - /> - )} -
- -
- -
- -
- {condition.operator !== 'present' && condition.operator !== 'notPresent' && ( - updateCondition(index, 'value', e.target.value)} - /> - )} -
- -
- -
-
- ); - } - }; - - // Render all child conditions of a parent - const renderChildConditions = (parentId: number, level = 0) => { - const childConditions = getConditionsForParent(parentId); + ); + } + }; - if (childConditions.length === 0) { - return ( -
- No conditions in this group. Add one using the buttons above. -
- ); - } - - return childConditions.map((condition, i) => { - const index = conditions.findIndex(c => c === condition); - return renderCondition(condition, index, level); + // Render only root conditions (those without a parent) + const rootConditions = conditionsByParent.get(null) || []; + return rootConditions.map((condition) => { + const index = conditions.indexOf(condition); + return renderSingleCondition(condition, index, 0); }); }; @@ -430,14 +451,7 @@ const ConditionBuilder: React.FC = ({ ) : ( <>
- {/* Render root-level conditions */} - {conditions - .filter(c => c.parentId === null || c.parentId === undefined) - .map((condition, i) => { - const index = conditions.findIndex(c => c === condition); - return renderCondition(condition, index, 0); - }) - } + {renderAllConditions()}
@@ -473,61 +487,59 @@ const ConditionBuilder: React.FC = ({ ); }; -// Function to generate LDAP filter syntax from conditions +// Non-recursive function to generate LDAP filter syntax from conditions const generateLdapFilter = (conditions: Condition[]): string => { if (conditions.length === 0) return '(objectClass=*)'; - // Helper function to generate filter for a single condition - const generateSingleFilter = (condition: Condition): string => { - if (condition.isGroup) { - // Get all child conditions for this group - const childConditions = conditions.filter(c => c.parentId === condition.id); - if (childConditions.length === 0) return '(objectClass=*)'; - - const operator = condition.logicalOperator === 'AND' ? '&' : '|'; - const childFilters = childConditions.map(generateSingleFilter).join(''); - - return `(${operator}${childFilters})`; - } else { - // Get actual attribute name (custom or selected) - const attributeName = condition.attribute === 'custom' && condition.customAttribute - ? condition.customAttribute - : condition.attribute; - - // Regular condition - switch (condition.operator) { - case '=': - return `(${attributeName}=${condition.value})`; - case '!=': - return `(!(${attributeName}=${condition.value}))`; - case 'contains': - return `(${attributeName}=*${condition.value}*)`; - case 'startsWith': - return `(${attributeName}=${condition.value}*)`; - case 'endsWith': - return `(${attributeName}=*${condition.value})`; - case 'present': - return `(${attributeName}=*)`; - case 'notPresent': - return `(!(${attributeName}=*))`; - default: - return `(${attributeName}=${condition.value})`; - } + // Helper function to generate filter for a regular condition (not a group) + const generateRegularFilter = (condition: Condition): string => { + // Get actual attribute name (custom or selected) + const attributeName = condition.attribute === 'custom' && condition.customAttribute + ? condition.customAttribute + : condition.attribute; + + // Regular condition + switch (condition.operator) { + case '=': + return `(${attributeName}=${condition.value})`; + case '!=': + return `(!(${attributeName}=${condition.value}))`; + case 'contains': + return `(${attributeName}=*${condition.value}*)`; + case 'startsWith': + return `(${attributeName}=${condition.value}*)`; + case 'endsWith': + return `(${attributeName}=*${condition.value})`; + case 'present': + return `(${attributeName}=*)`; + case 'notPresent': + return `(!(${attributeName}=*))`; + default: + return `(${attributeName}=${condition.value})`; } }; - // Handle root-level conditions + // Find all root conditions first (those without parents) const rootConditions = conditions.filter(c => c.parentId === null || c.parentId === undefined ); - if (rootConditions.length === 1) { - return generateSingleFilter(rootConditions[0]); - } else { - // When multiple root conditions, wrap in an AND by default - const rootFilters = rootConditions.map(generateSingleFilter).join(''); - return `(&${rootFilters})`; + // Simple version that just concatenates all conditions without proper nesting + // This avoids recursion completely, but doesn't handle nested groups properly + let allFilters = ''; + + for (const condition of conditions) { + if (!condition.isGroup) { + allFilters += generateRegularFilter(condition); + } } + + if (allFilters === '') { + return '(objectClass=*)'; + } + + // Wrap all filters in an AND operator for simplicity + return `(&${allFilters})`; }; export default ConditionBuilder; \ No newline at end of file