@ram_28/kf-ai-sdk 1.0.5 → 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.
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/components/hooks/useFilter/index.d.ts +3 -4
- package/dist/components/hooks/useFilter/index.d.ts.map +1 -1
- package/dist/components/hooks/useFilter/types.d.ts +84 -127
- package/dist/components/hooks/useFilter/types.d.ts.map +1 -1
- package/dist/components/hooks/useFilter/useFilter.d.ts +1 -1
- package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/index.d.ts +1 -1
- package/dist/components/hooks/useKanban/index.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/types.d.ts +6 -49
- package/dist/components/hooks/useKanban/types.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -1
- package/dist/components/hooks/useTable/types.d.ts +5 -35
- package/dist/components/hooks/useTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useTable/useTable.d.ts +0 -5
- package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
- package/dist/index.cjs +13 -13
- package/dist/index.mjs +2395 -2865
- package/dist/types/common.d.ts +35 -26
- package/dist/types/common.d.ts.map +1 -1
- package/package.json +1 -1
- package/sdk/api/index.ts +7 -3
- package/sdk/components/hooks/useFilter/index.ts +19 -31
- package/sdk/components/hooks/useFilter/types.ts +157 -138
- package/sdk/components/hooks/useFilter/useFilter.ts +259 -414
- package/sdk/components/hooks/useKanban/index.ts +0 -1
- package/sdk/components/hooks/useKanban/types.ts +8 -71
- package/sdk/components/hooks/useKanban/useKanban.ts +14 -77
- package/sdk/components/hooks/useTable/types.ts +7 -63
- package/sdk/components/hooks/useTable/useTable.ts +13 -122
- package/sdk/types/common.ts +42 -26
- package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts +0 -33
- package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts.map +0 -1
- package/dist/components/hooks/useFilter/validation.utils.d.ts +0 -38
- package/dist/components/hooks/useFilter/validation.utils.d.ts.map +0 -1
- package/sdk/components/hooks/useFilter/payloadBuilder.utils.ts +0 -298
- package/sdk/components/hooks/useFilter/validation.utils.ts +0 -401
|
@@ -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
|
-
};
|
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
import type { FilterOperator } from "../../../types/common";
|
|
2
|
-
import type { FieldDefinition, ValidationResult } from "../useFilter";
|
|
3
|
-
|
|
4
|
-
// ============================================================
|
|
5
|
-
// FIELD TYPE VALIDATION HELPERS
|
|
6
|
-
// ============================================================
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Validate number values based on operator
|
|
10
|
-
*/
|
|
11
|
-
export const validateNumberValue = (value: any, operator: FilterOperator): ValidationResult => {
|
|
12
|
-
const errors: string[] = [];
|
|
13
|
-
|
|
14
|
-
switch (operator) {
|
|
15
|
-
case 'Between':
|
|
16
|
-
case 'NotBetween':
|
|
17
|
-
if (!Array.isArray(value) || value.length !== 2) {
|
|
18
|
-
errors.push('Between operators require exactly two numeric values');
|
|
19
|
-
} else {
|
|
20
|
-
const [min, max] = value;
|
|
21
|
-
if (typeof min !== 'number' || typeof max !== 'number') {
|
|
22
|
-
errors.push('Between values must be numbers');
|
|
23
|
-
} else if (min >= max) {
|
|
24
|
-
errors.push('First value must be less than second value');
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
break;
|
|
28
|
-
case 'IN':
|
|
29
|
-
case 'NIN':
|
|
30
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
31
|
-
errors.push('IN/NIN operators require a non-empty array of numbers');
|
|
32
|
-
} else if (!value.every((v: any) => typeof v === 'number')) {
|
|
33
|
-
errors.push('All values in array must be numbers');
|
|
34
|
-
}
|
|
35
|
-
break;
|
|
36
|
-
case 'Empty':
|
|
37
|
-
case 'NotEmpty':
|
|
38
|
-
// No validation needed for these operators
|
|
39
|
-
break;
|
|
40
|
-
default:
|
|
41
|
-
if (typeof value !== 'number' || isNaN(value)) {
|
|
42
|
-
errors.push('Value must be a valid number');
|
|
43
|
-
}
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
isValid: errors.length === 0,
|
|
49
|
-
errors
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Validate date values based on operator
|
|
55
|
-
*/
|
|
56
|
-
export const validateDateValue = (value: any, operator: FilterOperator): ValidationResult => {
|
|
57
|
-
const errors: string[] = [];
|
|
58
|
-
|
|
59
|
-
const isValidDate = (date: any): boolean => {
|
|
60
|
-
return date instanceof Date && !isNaN(date.getTime());
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const parseDate = (val: any): Date | null => {
|
|
64
|
-
if (val instanceof Date) return val;
|
|
65
|
-
if (typeof val === 'string' || typeof val === 'number') {
|
|
66
|
-
const parsed = new Date(val);
|
|
67
|
-
return isValidDate(parsed) ? parsed : null;
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
switch (operator) {
|
|
73
|
-
case 'Between':
|
|
74
|
-
case 'NotBetween':
|
|
75
|
-
if (!Array.isArray(value) || value.length !== 2) {
|
|
76
|
-
errors.push('Between operators require exactly two date values');
|
|
77
|
-
} else {
|
|
78
|
-
const [start, end] = value.map(parseDate);
|
|
79
|
-
if (!start || !end) {
|
|
80
|
-
errors.push('Between values must be valid dates');
|
|
81
|
-
} else if (start >= end) {
|
|
82
|
-
errors.push('Start date must be before end date');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
break;
|
|
86
|
-
case 'IN':
|
|
87
|
-
case 'NIN':
|
|
88
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
89
|
-
errors.push('IN/NIN operators require a non-empty array of dates');
|
|
90
|
-
} else if (!value.every((v: any) => parseDate(v))) {
|
|
91
|
-
errors.push('All values in array must be valid dates');
|
|
92
|
-
}
|
|
93
|
-
break;
|
|
94
|
-
case 'Empty':
|
|
95
|
-
case 'NotEmpty':
|
|
96
|
-
// No validation needed for these operators
|
|
97
|
-
break;
|
|
98
|
-
default:
|
|
99
|
-
if (!parseDate(value)) {
|
|
100
|
-
errors.push('Value must be a valid date');
|
|
101
|
-
}
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
isValid: errors.length === 0,
|
|
107
|
-
errors
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Validate currency values based on operator
|
|
113
|
-
*/
|
|
114
|
-
export const validateCurrencyValue = (value: any, operator: FilterOperator): ValidationResult => {
|
|
115
|
-
const errors: string[] = [];
|
|
116
|
-
|
|
117
|
-
const isValidCurrencyObject = (val: any): boolean => {
|
|
118
|
-
return val &&
|
|
119
|
-
typeof val === 'object' &&
|
|
120
|
-
typeof val.value === 'number' &&
|
|
121
|
-
typeof val.currency === 'string' &&
|
|
122
|
-
val.currency.length === 3; // Standard currency codes are 3 letters
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const isValidCurrencyString = (val: any): boolean => {
|
|
126
|
-
return typeof val === 'string' && /^\d+(\.\d{2})?\s[A-Z]{3}$/.test(val);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const isValidCurrency = (val: any): boolean => {
|
|
130
|
-
return isValidCurrencyObject(val) || isValidCurrencyString(val) || typeof val === 'number';
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
switch (operator) {
|
|
134
|
-
case 'Between':
|
|
135
|
-
case 'NotBetween':
|
|
136
|
-
if (!Array.isArray(value) || value.length !== 2) {
|
|
137
|
-
errors.push('Between operators require exactly two currency values');
|
|
138
|
-
} else if (!value.every(isValidCurrency)) {
|
|
139
|
-
errors.push('Between values must be valid currency amounts');
|
|
140
|
-
}
|
|
141
|
-
break;
|
|
142
|
-
case 'IN':
|
|
143
|
-
case 'NIN':
|
|
144
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
145
|
-
errors.push('IN/NIN operators require a non-empty array of currency values');
|
|
146
|
-
} else if (!value.every(isValidCurrency)) {
|
|
147
|
-
errors.push('All values in array must be valid currency amounts');
|
|
148
|
-
}
|
|
149
|
-
break;
|
|
150
|
-
case 'Empty':
|
|
151
|
-
case 'NotEmpty':
|
|
152
|
-
// No validation needed for these operators
|
|
153
|
-
break;
|
|
154
|
-
default:
|
|
155
|
-
if (!isValidCurrency(value)) {
|
|
156
|
-
errors.push('Value must be a valid currency amount');
|
|
157
|
-
}
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
isValid: errors.length === 0,
|
|
163
|
-
errors
|
|
164
|
-
};
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Validate string values based on operator
|
|
169
|
-
*/
|
|
170
|
-
export const validateStringValue = (value: any, operator: FilterOperator): ValidationResult => {
|
|
171
|
-
const errors: string[] = [];
|
|
172
|
-
|
|
173
|
-
switch (operator) {
|
|
174
|
-
case 'Between':
|
|
175
|
-
case 'NotBetween':
|
|
176
|
-
errors.push('Between operators are not supported for string fields');
|
|
177
|
-
break;
|
|
178
|
-
case 'GT':
|
|
179
|
-
case 'GTE':
|
|
180
|
-
case 'LT':
|
|
181
|
-
case 'LTE':
|
|
182
|
-
errors.push('Comparison operators are not supported for string fields');
|
|
183
|
-
break;
|
|
184
|
-
case 'IN':
|
|
185
|
-
case 'NIN':
|
|
186
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
187
|
-
errors.push('IN/NIN operators require a non-empty array of strings');
|
|
188
|
-
} else if (!value.every((v: any) => typeof v === 'string')) {
|
|
189
|
-
errors.push('All values in array must be strings');
|
|
190
|
-
}
|
|
191
|
-
break;
|
|
192
|
-
case 'MinLength':
|
|
193
|
-
if (typeof value !== 'number' || value < 0) {
|
|
194
|
-
errors.push('MinLength value must be a non-negative number');
|
|
195
|
-
}
|
|
196
|
-
break;
|
|
197
|
-
case 'MaxLength':
|
|
198
|
-
if (typeof value !== 'number' || value < 0) {
|
|
199
|
-
errors.push('MaxLength value must be a non-negative number');
|
|
200
|
-
}
|
|
201
|
-
break;
|
|
202
|
-
case 'Empty':
|
|
203
|
-
case 'NotEmpty':
|
|
204
|
-
// No validation needed for these operators
|
|
205
|
-
break;
|
|
206
|
-
default:
|
|
207
|
-
if (typeof value !== 'string') {
|
|
208
|
-
errors.push('Value must be a string');
|
|
209
|
-
}
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
isValid: errors.length === 0,
|
|
215
|
-
errors
|
|
216
|
-
};
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Validate boolean values based on operator
|
|
221
|
-
*/
|
|
222
|
-
export const validateBooleanValue = (value: any, operator: FilterOperator): ValidationResult => {
|
|
223
|
-
const errors: string[] = [];
|
|
224
|
-
|
|
225
|
-
const supportedOperators: FilterOperator[] = ['EQ', 'NE', 'IN', 'NIN', 'Empty', 'NotEmpty'];
|
|
226
|
-
|
|
227
|
-
if (!supportedOperators.includes(operator)) {
|
|
228
|
-
errors.push(`Operator ${operator} is not supported for boolean fields`);
|
|
229
|
-
return { isValid: false, errors };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
switch (operator) {
|
|
233
|
-
case 'IN':
|
|
234
|
-
case 'NIN':
|
|
235
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
236
|
-
errors.push('IN/NIN operators require a non-empty array of boolean values');
|
|
237
|
-
} else if (!value.every((v: any) => typeof v === 'boolean')) {
|
|
238
|
-
errors.push('All values in array must be boolean');
|
|
239
|
-
}
|
|
240
|
-
break;
|
|
241
|
-
case 'Empty':
|
|
242
|
-
case 'NotEmpty':
|
|
243
|
-
// No validation needed for these operators
|
|
244
|
-
break;
|
|
245
|
-
default:
|
|
246
|
-
if (typeof value !== 'boolean') {
|
|
247
|
-
errors.push('Value must be a boolean (true or false)');
|
|
248
|
-
}
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
isValid: errors.length === 0,
|
|
254
|
-
errors
|
|
255
|
-
};
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Validate select field values based on operator and available options
|
|
260
|
-
*/
|
|
261
|
-
export const validateSelectValue = (
|
|
262
|
-
value: any,
|
|
263
|
-
operator: FilterOperator,
|
|
264
|
-
selectOptions?: Array<{ label: string; value: any }>
|
|
265
|
-
): ValidationResult => {
|
|
266
|
-
const errors: string[] = [];
|
|
267
|
-
|
|
268
|
-
if (!selectOptions || selectOptions.length === 0) {
|
|
269
|
-
errors.push('No select options defined for this field');
|
|
270
|
-
return { isValid: false, errors };
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const validValues = selectOptions.map(option => option.value);
|
|
274
|
-
const isValidOption = (val: any) => validValues.includes(val);
|
|
275
|
-
|
|
276
|
-
switch (operator) {
|
|
277
|
-
case 'Between':
|
|
278
|
-
case 'NotBetween':
|
|
279
|
-
case 'GT':
|
|
280
|
-
case 'GTE':
|
|
281
|
-
case 'LT':
|
|
282
|
-
case 'LTE':
|
|
283
|
-
errors.push(`Operator ${operator} is not supported for select fields`);
|
|
284
|
-
break;
|
|
285
|
-
case 'IN':
|
|
286
|
-
case 'NIN':
|
|
287
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
288
|
-
errors.push('IN/NIN operators require a non-empty array of values');
|
|
289
|
-
} else if (!value.every(isValidOption)) {
|
|
290
|
-
errors.push('All values must be from the available options');
|
|
291
|
-
}
|
|
292
|
-
break;
|
|
293
|
-
case 'Empty':
|
|
294
|
-
case 'NotEmpty':
|
|
295
|
-
// No validation needed for these operators
|
|
296
|
-
break;
|
|
297
|
-
default:
|
|
298
|
-
if (!isValidOption(value)) {
|
|
299
|
-
errors.push('Value must be one of the available options');
|
|
300
|
-
}
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
isValid: errors.length === 0,
|
|
306
|
-
errors
|
|
307
|
-
};
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
// ============================================================
|
|
311
|
-
// FIELD DEFINITION HELPERS
|
|
312
|
-
// ============================================================
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Get default field definition based on field type
|
|
316
|
-
*/
|
|
317
|
-
export const getDefaultFieldDefinition = (fieldType: FieldDefinition['type']): FieldDefinition => {
|
|
318
|
-
switch (fieldType) {
|
|
319
|
-
case 'string':
|
|
320
|
-
return {
|
|
321
|
-
type: 'string',
|
|
322
|
-
allowedOperators: ['EQ', 'NE', 'Contains', 'NotContains', 'IN', 'NIN', 'Empty', 'NotEmpty', 'MinLength', 'MaxLength'],
|
|
323
|
-
validateValue: validateStringValue
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
case 'number':
|
|
327
|
-
return {
|
|
328
|
-
type: 'number',
|
|
329
|
-
allowedOperators: ['EQ', 'NE', 'GT', 'GTE', 'LT', 'LTE', 'Between', 'NotBetween', 'IN', 'NIN', 'Empty', 'NotEmpty'],
|
|
330
|
-
validateValue: validateNumberValue
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
case 'date':
|
|
334
|
-
return {
|
|
335
|
-
type: 'date',
|
|
336
|
-
allowedOperators: ['EQ', 'NE', 'GT', 'GTE', 'LT', 'LTE', 'Between', 'NotBetween', 'IN', 'NIN', 'Empty', 'NotEmpty'],
|
|
337
|
-
validateValue: validateDateValue
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
case 'boolean':
|
|
341
|
-
return {
|
|
342
|
-
type: 'boolean',
|
|
343
|
-
allowedOperators: ['EQ', 'NE', 'IN', 'NIN', 'Empty', 'NotEmpty'],
|
|
344
|
-
validateValue: validateBooleanValue
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
case 'currency':
|
|
348
|
-
return {
|
|
349
|
-
type: 'currency',
|
|
350
|
-
allowedOperators: ['EQ', 'NE', 'GT', 'GTE', 'LT', 'LTE', 'Between', 'NotBetween', 'IN', 'NIN', 'Empty', 'NotEmpty'],
|
|
351
|
-
validateValue: validateCurrencyValue
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
case 'select':
|
|
355
|
-
const selectFieldDef: FieldDefinition = {
|
|
356
|
-
type: 'select',
|
|
357
|
-
allowedOperators: ['EQ', 'NE', 'IN', 'NIN', 'Empty', 'NotEmpty'],
|
|
358
|
-
selectOptions: []
|
|
359
|
-
};
|
|
360
|
-
selectFieldDef.validateValue = (value: any, operator: FilterOperator) =>
|
|
361
|
-
validateSelectValue(value, operator, selectFieldDef.selectOptions);
|
|
362
|
-
return selectFieldDef;
|
|
363
|
-
|
|
364
|
-
default:
|
|
365
|
-
// Default to string type
|
|
366
|
-
return getDefaultFieldDefinition('string');
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Create field definitions from sample data
|
|
372
|
-
*/
|
|
373
|
-
export const createFieldDefinitionsFromSample = <T>(sampleData: T): Record<keyof T, FieldDefinition> => {
|
|
374
|
-
const fieldDefinitions = {} as Record<keyof T, FieldDefinition>;
|
|
375
|
-
|
|
376
|
-
if (!sampleData || typeof sampleData !== 'object') {
|
|
377
|
-
return fieldDefinitions;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
Object.keys(sampleData).forEach(key => {
|
|
381
|
-
const value = (sampleData as any)[key];
|
|
382
|
-
const fieldKey = key as keyof T;
|
|
383
|
-
|
|
384
|
-
if (typeof value === 'string') {
|
|
385
|
-
fieldDefinitions[fieldKey] = getDefaultFieldDefinition('string');
|
|
386
|
-
} else if (typeof value === 'number') {
|
|
387
|
-
fieldDefinitions[fieldKey] = getDefaultFieldDefinition('number');
|
|
388
|
-
} else if (typeof value === 'boolean') {
|
|
389
|
-
fieldDefinitions[fieldKey] = getDefaultFieldDefinition('boolean');
|
|
390
|
-
} else if (value instanceof Date) {
|
|
391
|
-
fieldDefinitions[fieldKey] = getDefaultFieldDefinition('date');
|
|
392
|
-
} else if (value && typeof value === 'object' && 'value' in value && 'currency' in value) {
|
|
393
|
-
fieldDefinitions[fieldKey] = getDefaultFieldDefinition('currency');
|
|
394
|
-
} else {
|
|
395
|
-
// Default to string for complex types
|
|
396
|
-
fieldDefinitions[fieldKey] = getDefaultFieldDefinition('string');
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
return fieldDefinitions;
|
|
401
|
-
};
|