@ram_28/kf-ai-sdk 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/api/index.d.ts +1 -1
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/components/hooks/useFilter/index.d.ts +3 -3
  4. package/dist/components/hooks/useFilter/index.d.ts.map +1 -1
  5. package/dist/components/hooks/useFilter/types.d.ts +83 -109
  6. package/dist/components/hooks/useFilter/types.d.ts.map +1 -1
  7. package/dist/components/hooks/useFilter/useFilter.d.ts +1 -1
  8. package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
  9. package/dist/components/hooks/useKanban/index.d.ts +1 -1
  10. package/dist/components/hooks/useKanban/index.d.ts.map +1 -1
  11. package/dist/components/hooks/useKanban/types.d.ts +6 -46
  12. package/dist/components/hooks/useKanban/types.d.ts.map +1 -1
  13. package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -1
  14. package/dist/components/hooks/useTable/types.d.ts +5 -33
  15. package/dist/components/hooks/useTable/types.d.ts.map +1 -1
  16. package/dist/components/hooks/useTable/useTable.d.ts +0 -5
  17. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
  18. package/dist/index.cjs +12 -12
  19. package/dist/index.mjs +2130 -2368
  20. package/dist/types/common.d.ts +35 -26
  21. package/dist/types/common.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/sdk/api/index.ts +7 -3
  24. package/sdk/components/hooks/useFilter/index.ts +19 -18
  25. package/sdk/components/hooks/useFilter/types.ts +157 -123
  26. package/sdk/components/hooks/useFilter/useFilter.ts +259 -393
  27. package/sdk/components/hooks/useKanban/index.ts +0 -1
  28. package/sdk/components/hooks/useKanban/types.ts +8 -66
  29. package/sdk/components/hooks/useKanban/useKanban.ts +14 -75
  30. package/sdk/components/hooks/useTable/types.ts +7 -60
  31. package/sdk/components/hooks/useTable/useTable.ts +13 -121
  32. package/sdk/types/common.ts +42 -26
  33. package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts +0 -33
  34. package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts.map +0 -1
  35. package/sdk/components/hooks/useFilter/payloadBuilder.utils.ts +0 -298
@@ -18,44 +18,11 @@ interface SortingState<T> {
18
18
  direction: "asc" | "desc" | null;
19
19
  }
20
20
 
21
- interface FilteringState {
22
- global: string;
23
- }
24
-
25
21
  interface PaginationState {
26
22
  pageNo: number; // 1-indexed page number
27
23
  pageSize: number;
28
24
  }
29
25
 
30
- // ============================================================
31
- // FIELD TYPE DETECTION
32
- // ============================================================
33
-
34
- /**
35
- * Detect field type for auto-formatting
36
- * This will be enhanced with actual type introspection in the future
37
- * Currently unused but kept for future auto-formatting implementation
38
- */
39
- // function _detectFieldType<T>(fieldId: keyof T): string {
40
- // const fieldName = String(fieldId);
41
- //
42
- // // Basic pattern matching - will be replaced with actual type introspection
43
- // if (fieldName.includes('price') || fieldName.includes('cost') || fieldName.includes('amount')) {
44
- // return 'currency';
45
- // }
46
- // if (fieldName.includes('date') || fieldName.includes('At') || fieldName === 'created' || fieldName === 'updated') {
47
- // return 'datetime';
48
- // }
49
- // if (fieldName.includes('stock') || fieldName.includes('quantity') || fieldName.includes('count')) {
50
- // return 'number';
51
- // }
52
- // if (fieldName.includes('status') || fieldName.includes('type') || fieldName.includes('category')) {
53
- // return 'string';
54
- // }
55
- //
56
- // return 'string'; // default
57
- // }
58
-
59
26
  // ============================================================
60
27
  // MAIN HOOK
61
28
  // ============================================================
@@ -76,12 +43,8 @@ export function useTable<T = any>(
76
43
  direction: options.initialState?.sorting?.direction || null,
77
44
  });
78
45
 
79
- const [filtering, setFiltering] = useState<FilteringState>({
80
- global: options.initialState?.globalFilter || "",
81
- });
82
-
83
46
  const [pagination, setPagination] = useState<PaginationState>({
84
- pageNo: options.initialState?.pagination?.pageNo || 1, // Start at page 1
47
+ pageNo: options.initialState?.pagination?.pageNo || 1,
85
48
  pageSize: options.initialState?.pagination?.pageSize || 10,
86
49
  });
87
50
 
@@ -89,23 +52,9 @@ export function useTable<T = any>(
89
52
  // FILTER HOOK INTEGRATION
90
53
  // ============================================================
91
54
 
92
- const filterHook = useFilter<T>({
55
+ const filter = useFilter({
93
56
  initialConditions: options.initialState?.filters,
94
- initialLogicalOperator: options.initialState?.filterOperator || "And",
95
- validateOnChange: true,
96
- onValidationError: options.onFilterError,
97
- onConditionAdd: () => {
98
- // Reset to first page when adding filters
99
- setPagination((prev) => ({ ...prev, pageNo: 1 }));
100
- },
101
- onConditionUpdate: () => {
102
- // Reset to first page when updating filters
103
- setPagination((prev) => ({ ...prev, pageNo: 1 }));
104
- },
105
- onConditionRemove: () => {
106
- // Reset to first page when removing filters
107
- setPagination((prev) => ({ ...prev, pageNo: 1 }));
108
- },
57
+ initialOperator: options.initialState?.filterOperator || "And",
109
58
  });
110
59
 
111
60
  // ============================================================
@@ -122,12 +71,12 @@ export function useTable<T = any>(
122
71
  }
123
72
 
124
73
  // Add filter conditions (affects count)
125
- if (filterHook.filterPayload) {
126
- opts.Filter = filterHook.filterPayload;
74
+ if (filter.payload) {
75
+ opts.Filter = filter.payload;
127
76
  }
128
77
 
129
78
  return opts;
130
- }, [search.query, filterHook.filterPayload]);
79
+ }, [search.query, filter.payload]);
131
80
 
132
81
  // Options for list query - includes all options
133
82
  const apiOptions = useMemo((): ListOptions => {
@@ -144,7 +93,7 @@ export function useTable<T = any>(
144
93
 
145
94
  // Add pagination
146
95
  if (options.enablePagination) {
147
- opts.Page = pagination.pageNo; // Already 1-indexed
96
+ opts.Page = pagination.pageNo;
148
97
  opts.PageSize = pagination.pageSize;
149
98
  }
150
99
 
@@ -181,7 +130,7 @@ export function useTable<T = any>(
181
130
  gcTime: 0,
182
131
  });
183
132
 
184
- // Count query for accurate total items (only depends on filters/search, not sorting/pagination)
133
+ // Count query for accurate total items
185
134
  const {
186
135
  data: countData,
187
136
  isLoading: isCountLoading,
@@ -222,14 +171,12 @@ export function useTable<T = any>(
222
171
  const toggleSort = useCallback((field: keyof T) => {
223
172
  setSorting((prev) => {
224
173
  if (prev.field === field) {
225
- // Same field - toggle direction or clear
226
174
  if (prev.direction === "asc") {
227
175
  return { field, direction: "desc" };
228
176
  } else if (prev.direction === "desc") {
229
177
  return { field: null, direction: null };
230
178
  }
231
179
  }
232
- // New field or no current sort
233
180
  return { field, direction: "asc" };
234
181
  });
235
182
  }, []);
@@ -251,7 +198,6 @@ export function useTable<T = any>(
251
198
 
252
199
  const setSearchQuery = useCallback((value: string) => {
253
200
  setSearch({ query: value });
254
- // Reset to first page when searching
255
201
  setPagination((prev) => ({ ...prev, pageNo: 1 }));
256
202
  }, []);
257
203
 
@@ -259,20 +205,6 @@ export function useTable<T = any>(
259
205
  setSearch({ query: "" });
260
206
  }, []);
261
207
 
262
- // ============================================================
263
- // FILTERING OPERATIONS
264
- // ============================================================
265
-
266
- const setGlobalFilter = useCallback((value: string) => {
267
- setFiltering((prev) => ({ ...prev, global: value }));
268
- // Reset to first page when filtering
269
- setPagination((prev) => ({ ...prev, pageNo: 1 }));
270
- }, []);
271
-
272
- const clearFilter = useCallback(() => {
273
- setFiltering({ global: "" });
274
- }, []);
275
-
276
208
  // ============================================================
277
209
  // PAGINATION OPERATIONS
278
210
  // ============================================================
@@ -294,7 +226,7 @@ export function useTable<T = any>(
294
226
 
295
227
  const goToPage = useCallback(
296
228
  (page: number) => {
297
- const pageNo = Math.max(1, Math.min(page, totalPages)); // Clamp between 1 and totalPages
229
+ const pageNo = Math.max(1, Math.min(page, totalPages));
298
230
  setPagination((prev) => ({ ...prev, pageNo }));
299
231
  },
300
232
  [totalPages]
@@ -304,7 +236,7 @@ export function useTable<T = any>(
304
236
  setPagination((prev) => ({
305
237
  ...prev,
306
238
  pageSize: size,
307
- pageNo: 1, // Reset to first page
239
+ pageNo: 1,
308
240
  }));
309
241
  }, []);
310
242
 
@@ -349,52 +281,12 @@ export function useTable<T = any>(
349
281
  set: setSort,
350
282
  },
351
283
 
352
- // Legacy Global Filtering (Flat Access)
353
- globalFilter: {
354
- value: filtering.global,
355
- setValue: setGlobalFilter,
356
- clear: clearFilter,
357
- },
358
-
359
- // Advanced Filtering (Filter Conditions)
360
- filter: {
361
- // State
362
- conditions: filterHook.conditions,
363
- logicalOperator: filterHook.logicalOperator,
364
- isValid: filterHook.isValid,
365
- validationErrors: filterHook.validationErrors,
366
- hasConditions: filterHook.hasConditions,
367
-
368
- // Condition Management
369
- addCondition: filterHook.addCondition,
370
- updateCondition: filterHook.updateCondition,
371
- removeCondition: filterHook.removeCondition,
372
- clearConditions: filterHook.clearConditions,
373
- getCondition: filterHook.getCondition,
374
-
375
- // Logical Operator
376
- setLogicalOperator: filterHook.setLogicalOperator,
377
-
378
- // Bulk Operations
379
- setConditions: filterHook.setConditions,
380
- replaceCondition: filterHook.replaceCondition,
381
-
382
- // Validation
383
- validateCondition: filterHook.validateCondition,
384
- validateAllConditions: filterHook.validateAllConditions,
385
-
386
- // State Management
387
- exportState: filterHook.exportState,
388
- importState: filterHook.importState,
389
- resetToInitial: filterHook.resetToInitial,
390
-
391
- // Utilities
392
- getConditionCount: filterHook.getConditionCount,
393
- },
284
+ // Filter (Simplified chainable API)
285
+ filter,
394
286
 
395
287
  // Pagination (Flat Access)
396
288
  pagination: {
397
- currentPage: pagination.pageNo, // Already 1-indexed
289
+ currentPage: pagination.pageNo,
398
290
  pageSize: pagination.pageSize,
399
291
  totalPages,
400
292
  totalItems,
@@ -15,18 +15,20 @@ export type SortOption = Record<string, SortDirection>;
15
15
  export type Sort = SortOption[];
16
16
 
17
17
  /**
18
- * Filter operators for individual conditions (leaf nodes)
18
+ * Condition operators for individual conditions (leaf nodes)
19
+ * Used in Condition.Operator
19
20
  */
20
- export type FilterOperator =
21
+ export type ConditionOperator =
21
22
  | "EQ" | "NE" | "GT" | "GTE" | "LT" | "LTE"
22
23
  | "Between" | "NotBetween" | "IN" | "NIN"
23
24
  | "Empty" | "NotEmpty" | "Contains" | "NotContains"
24
25
  | "MinLength" | "MaxLength";
25
26
 
26
27
  /**
27
- * Logical operators for combining filter conditions (tree nodes)
28
+ * Operators for combining conditions in a group (tree nodes)
29
+ * Used in ConditionGroup.Operator
28
30
  */
29
- export type LogicalOperator = "And" | "Or" | "Not";
31
+ export type ConditionGroupOperator = "And" | "Or" | "Not";
30
32
 
31
33
  /**
32
34
  * RHS value type for filter conditions
@@ -34,19 +36,13 @@ export type LogicalOperator = "And" | "Or" | "Not";
34
36
  export type FilterRHSType = "Constant" | "BOField" | "AppVariable";
35
37
 
36
38
  /**
37
- * Base interface for all filter nodes
39
+ * Leaf condition (actual field comparison)
38
40
  */
39
- interface FilterNodeBase {
40
- /** Operator type */
41
- Operator: FilterOperator | LogicalOperator;
42
- }
43
-
44
- /**
45
- * Leaf filter condition (actual field comparison)
46
- */
47
- export interface FilterCondition extends FilterNodeBase {
41
+ export interface Condition {
42
+ /** Optional ID for hook state management (omitted in API payload) */
43
+ id?: string;
48
44
  /** Condition operator */
49
- Operator: FilterOperator;
45
+ Operator: ConditionOperator;
50
46
  /** Left-hand side field name */
51
47
  LHSField: string;
52
48
  /** Right-hand side value */
@@ -56,25 +52,45 @@ export interface FilterCondition extends FilterNodeBase {
56
52
  }
57
53
 
58
54
  /**
59
- * Logical filter node (combines multiple conditions)
55
+ * Group combining conditions (recursive structure)
60
56
  */
61
- export interface FilterLogical extends FilterNodeBase {
62
- /** Logical operator */
63
- Operator: LogicalOperator;
64
- /** Nested conditions (can be FilterCondition or FilterLogical) */
65
- Condition: Array<FilterCondition | FilterLogical>;
57
+ export interface ConditionGroup {
58
+ /** Optional ID for hook state management (omitted in API payload) */
59
+ id?: string;
60
+ /** Group operator (And, Or, Not) */
61
+ Operator: ConditionGroupOperator;
62
+ /** Nested conditions (can be Condition or ConditionGroup) */
63
+ Condition: Array<Condition | ConditionGroup>;
66
64
  }
67
65
 
68
66
  /**
69
- * Filter structure matching API specification (root level)
70
- * This is a discriminated union - a filter is either a logical node or a condition
67
+ * Root filter type (alias for ConditionGroup)
68
+ */
69
+ export type Filter = ConditionGroup;
70
+
71
+ // ============================================================
72
+ // LEGACY TYPE ALIASES (for backwards compatibility)
73
+ // ============================================================
74
+
75
+ /**
76
+ * @deprecated Use `Condition` instead
77
+ */
78
+ export type FilterCondition = Condition;
79
+
80
+ /**
81
+ * @deprecated Use `ConditionGroup` instead
82
+ */
83
+ export type FilterLogical = ConditionGroup;
84
+
85
+ /**
86
+ * @deprecated Use `ConditionGroupOperator` instead
71
87
  */
72
- export type Filter = FilterLogical;
88
+ export type FilterOperator = ConditionGroupOperator;
73
89
 
74
90
  /**
75
- * Convenience type for any filter node (leaf or logical)
91
+ * @deprecated Use `Condition | ConditionGroup` instead
76
92
  */
77
- export type FilterNode = FilterCondition | FilterLogical;
93
+ export type FilterNode = Condition | ConditionGroup;
78
94
 
79
95
  /**
80
96
  * DateTime encoding format used by the API
@@ -1,33 +0,0 @@
1
- import type { Filter, LogicalOperator } from "../../../types/common";
2
- import type { FilterConditionWithId, FilterState } from "../useFilter";
3
- /**
4
- * Build SDK Filter payload from filter state
5
- * Returns undefined if no valid conditions exist
6
- * Supports both flat and nested filter structures
7
- */
8
- export declare const buildFilterPayload: (conditions: FilterConditionWithId[], logicalOperator: LogicalOperator) => Filter | undefined;
9
- /**
10
- * Build filter payload from complete filter state
11
- */
12
- export declare const buildFilterPayloadFromState: (state: FilterState) => Filter | undefined;
13
- /**
14
- * Validate that a filter payload is well-formed (supports nested filters)
15
- */
16
- export declare const validateFilterPayload: (filter: Filter | undefined) => boolean;
17
- /**
18
- * Deep clone a filter payload (supports nested filters)
19
- */
20
- export declare const cloneFilterPayload: (filter: Filter | undefined) => Filter | undefined;
21
- /**
22
- * Merge multiple filter payloads with a logical operator
23
- */
24
- export declare const mergeFilterPayloads: (filters: (Filter | undefined)[], operator: LogicalOperator) => Filter | undefined;
25
- /**
26
- * Convert filter payload to a human-readable string for debugging (supports nested filters)
27
- */
28
- export declare const filterPayloadToString: (filter: Filter | undefined) => string;
29
- /**
30
- * Check if two filter payloads are equivalent (supports nested filters)
31
- */
32
- export declare const areFilterPayloadsEqual: (filter1: Filter | undefined, filter2: Filter | undefined) => boolean;
33
- //# sourceMappingURL=payloadBuilder.utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"payloadBuilder.utils.d.ts","sourceRoot":"","sources":["../../../../sdk/components/hooks/useFilter/payloadBuilder.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAkC,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACrG,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAqCvE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,qBAAqB,EAAE,EACnC,iBAAiB,eAAe,KAC/B,MAAM,GAAG,SAmBX,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,WAAW,KAAG,MAAM,GAAG,SAEzE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,MAAM,GAAG,SAAS,KAAG,OAwClE,CAAC;AAwBF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SASxE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAC9B,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,EAC/B,UAAU,eAAe,KACxB,MAAM,GAAG,SAoBX,CAAC;AA6BF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,MAAM,GAAG,SAAS,KAAG,MAYlE,CAAC;AAyCF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,SAAS,MAAM,GAAG,SAAS,EAC3B,SAAS,MAAM,GAAG,SAAS,KAC1B,OAyBF,CAAC"}
@@ -1,298 +0,0 @@
1
- import type { Filter, FilterCondition, FilterLogical, LogicalOperator } from "../../../types/common";
2
- import type { FilterConditionWithId, FilterState } from "../useFilter";
3
-
4
- // ============================================================
5
- // PAYLOAD BUILDING UTILITIES
6
- // ============================================================
7
-
8
- /**
9
- * Helper to check if operator is a logical operator
10
- */
11
- const isLogicalOperator = (operator: string): operator is LogicalOperator => {
12
- return operator === 'And' || operator === 'Or' || operator === 'Not';
13
- };
14
-
15
- /**
16
- * Convert a single FilterConditionWithId to API format (FilterCondition or FilterLogical)
17
- */
18
- const buildCondition = (condition: FilterConditionWithId): FilterCondition | FilterLogical => {
19
- // Check if this is a logical operator (nested group)
20
- if (isLogicalOperator(condition.operator)) {
21
- // Build nested logical filter
22
- return {
23
- Operator: condition.operator,
24
- Condition: (condition.children || [])
25
- .filter(child => child.isValid)
26
- .map(child => buildCondition(child))
27
- };
28
- } else {
29
- // Build simple condition
30
- return {
31
- Operator: condition.operator,
32
- LHSField: condition.lhsField!,
33
- RHSValue: condition.rhsValue,
34
- RHSType: condition.rhsType || "Constant"
35
- };
36
- }
37
- };
38
-
39
- /**
40
- * Build SDK Filter payload from filter state
41
- * Returns undefined if no valid conditions exist
42
- * Supports both flat and nested filter structures
43
- */
44
- export const buildFilterPayload = (
45
- conditions: FilterConditionWithId[],
46
- logicalOperator: LogicalOperator
47
- ): Filter | undefined => {
48
- // Return undefined if no conditions
49
- if (conditions.length === 0) {
50
- return undefined;
51
- }
52
-
53
- // Filter out invalid conditions
54
- const validConditions = conditions.filter(condition => condition.isValid);
55
-
56
- // Return undefined if no valid conditions
57
- if (validConditions.length === 0) {
58
- return undefined;
59
- }
60
-
61
- // Build the filter payload
62
- return {
63
- Operator: logicalOperator,
64
- Condition: validConditions.map(buildCondition)
65
- };
66
- };
67
-
68
- /**
69
- * Build filter payload from complete filter state
70
- */
71
- export const buildFilterPayloadFromState = (state: FilterState): Filter | undefined => {
72
- return buildFilterPayload(state.conditions, state.logicalOperator);
73
- };
74
-
75
- /**
76
- * Validate that a filter payload is well-formed (supports nested filters)
77
- */
78
- export const validateFilterPayload = (filter: Filter | undefined): boolean => {
79
- if (!filter) {
80
- return true; // undefined filter is valid (no filtering)
81
- }
82
-
83
- // Check operator
84
- if (!filter.Operator || !['And', 'Or', 'Not'].includes(filter.Operator)) {
85
- return false;
86
- }
87
-
88
- // Check conditions array
89
- if (!Array.isArray(filter.Condition) || filter.Condition.length === 0) {
90
- return false;
91
- }
92
-
93
- // Not operator can only have one child
94
- if (filter.Operator === 'Not' && filter.Condition.length !== 1) {
95
- return false;
96
- }
97
-
98
- // Validate each condition recursively
99
- return filter.Condition.every(condition => {
100
- if (!condition.Operator) {
101
- return false;
102
- }
103
-
104
- // Check if this is a logical operator (nested)
105
- if (isLogicalOperator(condition.Operator)) {
106
- // Recursively validate nested filter
107
- return validateFilterPayload(condition as FilterLogical);
108
- } else {
109
- // Validate simple condition
110
- const simpleCondition = condition as FilterCondition;
111
- return (
112
- simpleCondition.LHSField !== undefined &&
113
- simpleCondition.RHSValue !== undefined &&
114
- (simpleCondition.RHSType === undefined || ['Constant', 'BOField', 'AppVariable'].includes(simpleCondition.RHSType))
115
- );
116
- }
117
- });
118
- };
119
-
120
- /**
121
- * Deep clone a filter condition (supports nested filters)
122
- */
123
- const cloneCondition = (condition: FilterCondition | FilterLogical): FilterCondition | FilterLogical => {
124
- if (isLogicalOperator(condition.Operator)) {
125
- // Clone logical filter recursively
126
- return {
127
- Operator: condition.Operator,
128
- Condition: (condition as FilterLogical).Condition.map(cloneCondition)
129
- };
130
- } else {
131
- // Clone simple condition
132
- const simpleCondition = condition as FilterCondition;
133
- return {
134
- Operator: simpleCondition.Operator,
135
- LHSField: simpleCondition.LHSField,
136
- RHSValue: simpleCondition.RHSValue,
137
- RHSType: simpleCondition.RHSType
138
- };
139
- }
140
- };
141
-
142
- /**
143
- * Deep clone a filter payload (supports nested filters)
144
- */
145
- export const cloneFilterPayload = (filter: Filter | undefined): Filter | undefined => {
146
- if (!filter) {
147
- return undefined;
148
- }
149
-
150
- return {
151
- Operator: filter.Operator,
152
- Condition: filter.Condition.map(cloneCondition)
153
- };
154
- };
155
-
156
- /**
157
- * Merge multiple filter payloads with a logical operator
158
- */
159
- export const mergeFilterPayloads = (
160
- filters: (Filter | undefined)[],
161
- operator: LogicalOperator
162
- ): Filter | undefined => {
163
- const validFilters = filters.filter((filter): filter is Filter =>
164
- filter !== undefined && validateFilterPayload(filter)
165
- );
166
-
167
- if (validFilters.length === 0) {
168
- return undefined;
169
- }
170
-
171
- if (validFilters.length === 1) {
172
- return cloneFilterPayload(validFilters[0]);
173
- }
174
-
175
- // Flatten all conditions
176
- const allConditions = validFilters.flatMap(filter => filter.Condition);
177
-
178
- return {
179
- Operator: operator,
180
- Condition: allConditions
181
- };
182
- };
183
-
184
- /**
185
- * Convert filter condition to a human-readable string (supports nested filters)
186
- */
187
- const conditionToString = (condition: FilterCondition | FilterLogical): string => {
188
- if (isLogicalOperator(condition.Operator)) {
189
- // Handle logical filter
190
- const logicalFilter = condition as FilterLogical;
191
- const childStrings = logicalFilter.Condition.map(conditionToString);
192
-
193
- if (childStrings.length === 1) {
194
- return condition.Operator === 'Not'
195
- ? `NOT (${childStrings[0]})`
196
- : childStrings[0];
197
- }
198
-
199
- return `(${childStrings.join(` ${condition.Operator} `)})`;
200
- } else {
201
- // Handle simple condition
202
- const simpleCondition = condition as FilterCondition;
203
- const rhsDisplay = Array.isArray(simpleCondition.RHSValue)
204
- ? `[${simpleCondition.RHSValue.join(', ')}]`
205
- : String(simpleCondition.RHSValue);
206
-
207
- return `${simpleCondition.LHSField} ${simpleCondition.Operator} ${rhsDisplay}`;
208
- }
209
- };
210
-
211
- /**
212
- * Convert filter payload to a human-readable string for debugging (supports nested filters)
213
- */
214
- export const filterPayloadToString = (filter: Filter | undefined): string => {
215
- if (!filter) {
216
- return "No filters";
217
- }
218
-
219
- const conditionStrings = filter.Condition.map(conditionToString);
220
-
221
- if (conditionStrings.length === 1) {
222
- return conditionStrings[0];
223
- }
224
-
225
- return `(${conditionStrings.join(` ${filter.Operator} `)})`;
226
- };
227
-
228
- /**
229
- * Check if two conditions are equivalent (supports nested filters)
230
- */
231
- const areConditionsEqual = (
232
- condition1: FilterCondition | FilterLogical,
233
- condition2: FilterCondition | FilterLogical
234
- ): boolean => {
235
- // Different operators
236
- if (condition1.Operator !== condition2.Operator) {
237
- return false;
238
- }
239
-
240
- // Check if logical operators
241
- if (isLogicalOperator(condition1.Operator)) {
242
- const logical1 = condition1 as FilterLogical;
243
- const logical2 = condition2 as FilterLogical;
244
-
245
- // Different number of children
246
- if (logical1.Condition.length !== logical2.Condition.length) {
247
- return false;
248
- }
249
-
250
- // Recursively compare all children
251
- return logical1.Condition.every((child1, index) =>
252
- areConditionsEqual(child1, logical2.Condition[index])
253
- );
254
- } else {
255
- // Compare simple conditions
256
- const simple1 = condition1 as FilterCondition;
257
- const simple2 = condition2 as FilterCondition;
258
-
259
- return (
260
- simple1.LHSField === simple2.LHSField &&
261
- JSON.stringify(simple1.RHSValue) === JSON.stringify(simple2.RHSValue) &&
262
- simple1.RHSType === simple2.RHSType
263
- );
264
- }
265
- };
266
-
267
- /**
268
- * Check if two filter payloads are equivalent (supports nested filters)
269
- */
270
- export const areFilterPayloadsEqual = (
271
- filter1: Filter | undefined,
272
- filter2: Filter | undefined
273
- ): boolean => {
274
- // Both undefined
275
- if (!filter1 && !filter2) {
276
- return true;
277
- }
278
-
279
- // One undefined, one defined
280
- if (!filter1 || !filter2) {
281
- return false;
282
- }
283
-
284
- // Different operators
285
- if (filter1.Operator !== filter2.Operator) {
286
- return false;
287
- }
288
-
289
- // Different number of conditions
290
- if (filter1.Condition.length !== filter2.Condition.length) {
291
- return false;
292
- }
293
-
294
- // Compare each condition recursively
295
- return filter1.Condition.every((condition1, index) =>
296
- areConditionsEqual(condition1, filter2.Condition[index])
297
- );
298
- };