@ram_28/kf-ai-sdk 1.0.18 → 1.0.20

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 (77) hide show
  1. package/README.md +45 -12
  2. package/dist/api/client.d.ts.map +1 -1
  3. package/dist/api.cjs +1 -1
  4. package/dist/api.mjs +2 -2
  5. package/dist/auth.cjs +1 -1
  6. package/dist/auth.mjs +1 -1
  7. package/dist/{client-C15j4O5B.cjs → client-DgtkT50N.cjs} +1 -1
  8. package/dist/{client-CfvLiGfP.js → client-V-WzUb8H.js} +9 -5
  9. package/dist/components/hooks/useFilter/types.d.ts +14 -11
  10. package/dist/components/hooks/useFilter/types.d.ts.map +1 -1
  11. package/dist/components/hooks/useFilter/useFilter.d.ts +1 -1
  12. package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
  13. package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -1
  14. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
  15. package/dist/components/hooks/useKanban/context.d.ts +1 -1
  16. package/dist/components/hooks/useKanban/context.d.ts.map +1 -1
  17. package/dist/components/hooks/useKanban/types.d.ts +5 -22
  18. package/dist/components/hooks/useKanban/types.d.ts.map +1 -1
  19. package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -1
  20. package/dist/components/hooks/useTable/types.d.ts +19 -31
  21. package/dist/components/hooks/useTable/types.d.ts.map +1 -1
  22. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
  23. package/dist/error-handling-CAoD0Kwb.cjs +1 -0
  24. package/dist/error-handling-CrhTtD88.js +14 -0
  25. package/dist/filter.cjs +1 -1
  26. package/dist/filter.mjs +1 -1
  27. package/dist/form.cjs +1 -1
  28. package/dist/form.mjs +825 -814
  29. package/dist/index.d.ts +18 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/kanban.cjs +2 -2
  32. package/dist/kanban.mjs +335 -323
  33. package/dist/{metadata-2FLBsFcf.cjs → metadata-0lZAfuTP.cjs} +1 -1
  34. package/dist/{metadata-DBcoDth-.js → metadata-B88D_pVS.js} +1 -1
  35. package/dist/table.cjs +1 -1
  36. package/dist/table.mjs +113 -96
  37. package/dist/table.types.d.ts +1 -1
  38. package/dist/table.types.d.ts.map +1 -1
  39. package/dist/types/common.d.ts +26 -6
  40. package/dist/types/common.d.ts.map +1 -1
  41. package/dist/useFilter-DzpP_ag0.cjs +1 -0
  42. package/dist/useFilter-H5bgAZQF.js +120 -0
  43. package/dist/utils/api/buildListOptions.d.ts +43 -0
  44. package/dist/utils/api/buildListOptions.d.ts.map +1 -0
  45. package/dist/utils/api/index.d.ts +2 -0
  46. package/dist/utils/api/index.d.ts.map +1 -0
  47. package/dist/utils/error-handling.d.ts +41 -0
  48. package/dist/utils/error-handling.d.ts.map +1 -0
  49. package/dist/utils/index.d.ts +2 -0
  50. package/dist/utils/index.d.ts.map +1 -1
  51. package/docs/QUICK_REFERENCE.md +142 -420
  52. package/docs/useAuth.md +52 -340
  53. package/docs/useFilter.md +858 -162
  54. package/docs/useForm.md +712 -501
  55. package/docs/useKanban.md +534 -279
  56. package/docs/useTable.md +725 -214
  57. package/package.json +1 -1
  58. package/sdk/api/client.ts +7 -1
  59. package/sdk/components/hooks/useFilter/types.ts +14 -11
  60. package/sdk/components/hooks/useFilter/useFilter.ts +20 -18
  61. package/sdk/components/hooks/useForm/apiClient.ts +2 -1
  62. package/sdk/components/hooks/useForm/useForm.ts +47 -13
  63. package/sdk/components/hooks/useKanban/context.ts +5 -3
  64. package/sdk/components/hooks/useKanban/types.ts +7 -23
  65. package/sdk/components/hooks/useKanban/useKanban.ts +54 -18
  66. package/sdk/components/hooks/useTable/types.ts +26 -32
  67. package/sdk/components/hooks/useTable/useTable.llm.txt +8 -22
  68. package/sdk/components/hooks/useTable/useTable.ts +70 -25
  69. package/sdk/index.ts +154 -10
  70. package/sdk/table.types.ts +3 -0
  71. package/sdk/types/common.ts +31 -6
  72. package/sdk/utils/api/buildListOptions.ts +120 -0
  73. package/sdk/utils/api/index.ts +2 -0
  74. package/sdk/utils/error-handling.ts +150 -0
  75. package/sdk/utils/index.ts +6 -0
  76. package/dist/useFilter-Dofowpr_.cjs +0 -1
  77. package/dist/useFilter-Dv-mr9QW.js +0 -117
package/docs/useFilter.md CHANGED
@@ -1,13 +1,8 @@
1
1
  # useFilter
2
2
 
3
- ## Brief Description
3
+ Build and manage filter conditions with support for nested groups and complex logic.
4
4
 
5
- - Manages filter conditions with support for nested filter groups (AND/OR/NOT operators)
6
- - Provides a clean API for building complex filter payloads that match the backend API format
7
- - Supports both flat conditions and nested condition groups for advanced filtering scenarios
8
- - Includes type guards (`isCondition`, `isConditionGroup`) for safely working with the filter tree structure
9
-
10
- ## Type Reference
5
+ ## Imports
11
6
 
12
7
  ```typescript
13
8
  import { useFilter, isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
@@ -19,115 +14,523 @@ import type {
19
14
  ConditionOperatorType,
20
15
  ConditionGroupOperatorType,
21
16
  FilterType,
22
- FilterRHSTypeType,
23
17
  } from "@ram_28/kf-ai-sdk/filter/types";
18
+ ```
24
19
 
25
- // Condition operators for comparing field values
20
+ ## Type Definitions
21
+
22
+ ```typescript
23
+ // Condition operators
26
24
  type ConditionOperatorType =
27
- | "EQ" | "NE" | "GT" | "GTE" | "LT" | "LTE"
25
+ | "EQ" | "NE" // Equal, Not Equal
26
+ | "GT" | "GTE" // Greater Than, Greater Than or Equal
27
+ | "LT" | "LTE" // Less Than, Less Than or Equal
28
28
  | "Between" | "NotBetween"
29
- | "IN" | "NIN"
29
+ | "IN" | "NIN" // In List, Not In List
30
30
  | "Empty" | "NotEmpty"
31
31
  | "Contains" | "NotContains"
32
32
  | "MinLength" | "MaxLength";
33
33
 
34
- // Group operators for combining conditions
34
+ // Group operators
35
35
  type ConditionGroupOperatorType = "And" | "Or" | "Not";
36
36
 
37
- // RHS type for condition values
38
- type FilterRHSTypeType = "Constant" | "BOField" | "AppVariable";
39
-
40
- // Leaf condition (matches API format)
41
- interface ConditionType {
37
+ // Single condition (generic for type-safe LHSField)
38
+ interface ConditionType<T = any> {
42
39
  id?: string;
43
40
  Operator: ConditionOperatorType;
44
- LHSField: string;
41
+ LHSField: keyof T | string; // Type-safe when T is provided
45
42
  RHSValue: any;
46
- RHSType?: FilterRHSTypeType;
43
+ RHSType?: "Constant" | "BOField" | "AppVariable";
47
44
  }
48
45
 
49
- // Condition group (recursive structure)
50
- interface ConditionGroupType {
46
+ // Condition group (can contain conditions or nested groups)
47
+ interface ConditionGroupType<T = any> {
51
48
  id?: string;
52
49
  Operator: ConditionGroupOperatorType;
53
- Condition: Array<ConditionType | ConditionGroupType>;
50
+ Condition: Array<ConditionType<T> | ConditionGroupType<T>>;
54
51
  }
55
52
 
56
- // Filter payload (alias for ConditionGroupType)
57
- type FilterType = ConditionGroupType;
58
-
59
- // Hook options
60
- interface UseFilterOptionsType {
61
- initialConditions?: Array<ConditionType | ConditionGroupType>;
62
- initialOperator?: ConditionGroupOperatorType;
53
+ // Hook options (also used for initialState in useTable/useKanban)
54
+ interface UseFilterOptionsType<T = any> {
55
+ conditions?: Array<ConditionType<T> | ConditionGroupType<T>>;
56
+ operator?: ConditionGroupOperatorType;
63
57
  }
64
58
 
65
- // Hook return type
66
- interface UseFilterReturnType {
67
- // State (read-only)
59
+ // Hook return type (generic for type-safe field names)
60
+ interface UseFilterReturnType<T = any> {
68
61
  operator: ConditionGroupOperatorType;
69
- items: Array<ConditionType | ConditionGroupType>;
70
- payload: FilterType | undefined;
62
+ items: Array<ConditionType<T> | ConditionGroupType<T>>;
63
+ payload: FilterType<T> | undefined;
71
64
  hasConditions: boolean;
72
65
 
73
- // Add operations (return id of created item)
74
- addCondition: (condition: Omit<ConditionType, "id">, parentId?: string) => string;
66
+ addCondition: (condition: Omit<ConditionType<T>, "id">, parentId?: string) => string;
75
67
  addConditionGroup: (operator: ConditionGroupOperatorType, parentId?: string) => string;
76
-
77
- // Update operations
78
- updateCondition: (id: string, updates: Partial<Omit<ConditionType, "id">>) => void;
68
+ updateCondition: (id: string, updates: Partial<Omit<ConditionType<T>, "id">>) => void;
79
69
  updateGroupOperator: (id: string, operator: ConditionGroupOperatorType) => void;
80
-
81
- // Remove & access
82
70
  removeCondition: (id: string) => void;
83
- getCondition: (id: string) => ConditionType | ConditionGroupType | undefined;
84
-
85
- // Utility
71
+ getCondition: (id: string) => ConditionType<T> | ConditionGroupType<T> | undefined;
86
72
  clearAllConditions: () => void;
87
- setRootOperator: (op: ConditionGroupOperatorType) => void;
73
+ setRootOperator: (operator: ConditionGroupOperatorType) => void;
88
74
  }
75
+ ```
76
+
77
+ ## Operator Applicability
78
+
79
+ | Operator | Applicable Field Types |
80
+ |----------|----------------------|
81
+ | EQ, NE | All types |
82
+ | GT, GTE, LT, LTE | number, date, currency |
83
+ | Between, NotBetween | number, date, currency |
84
+ | IN, NIN | All types |
85
+ | Empty, NotEmpty | All types |
86
+ | Contains, NotContains | string only |
87
+ | MinLength, MaxLength | string only |
88
+
89
+ ## Basic Example
89
90
 
90
- // Type guards
91
- const isCondition: (item: ConditionType | ConditionGroupType) => item is ConditionType;
92
- const isConditionGroup: (item: ConditionType | ConditionGroupType) => item is ConditionGroupType;
91
+ Create a simple filter with one condition.
92
+
93
+ ```tsx
94
+ import { useFilter } from "@ram_28/kf-ai-sdk/filter";
95
+
96
+ function SimpleFilter() {
97
+ const filter = useFilter();
98
+
99
+ const addCategoryFilter = () => {
100
+ filter.addCondition({
101
+ Operator: "EQ",
102
+ LHSField: "Category",
103
+ RHSValue: "Electronics",
104
+ });
105
+ };
106
+
107
+ return (
108
+ <div>
109
+ <button onClick={addCategoryFilter}>Filter by Electronics</button>
110
+ <button onClick={filter.clearAllConditions} disabled={!filter.hasConditions}>
111
+ Clear
112
+ </button>
113
+ <p>Active filters: {filter.items.length}</p>
114
+ </div>
115
+ );
116
+ }
93
117
  ```
94
118
 
95
- ## Usage Example
119
+ ---
120
+
121
+ ## Type-Safe Filtering
122
+
123
+ Use the generic type parameter to get TypeScript validation on field names.
124
+
125
+ ### With Generic Type
96
126
 
97
127
  ```tsx
98
- import { useFilter, isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
99
- import type {
100
- ConditionType,
101
- ConditionGroupType,
102
- ConditionGroupOperatorType,
103
- FilterType,
104
- UseFilterOptionsType,
105
- UseFilterReturnType,
106
- } from "@ram_28/kf-ai-sdk/filter/types";
128
+ import { useFilter } from "@ram_28/kf-ai-sdk/filter";
129
+
130
+ interface Product {
131
+ _id: string;
132
+ Title: string;
133
+ Price: number;
134
+ Category: string;
135
+ Stock: number;
136
+ }
137
+
138
+ function TypeSafeFilter() {
139
+ // Pass the type parameter for type-safe LHSField
140
+ const filter = useFilter<Product>();
107
141
 
108
- function ProductFilterBuilder() {
109
- // Initialize hook with options
110
- const options: UseFilterOptionsType = {
111
- initialOperator: "And",
142
+ const addCategoryFilter = () => {
143
+ filter.addCondition({
144
+ Operator: "EQ",
145
+ LHSField: "Category", // TypeScript validates this field exists
146
+ RHSValue: "Electronics",
147
+ });
112
148
  };
113
- const filter: UseFilterReturnType = useFilter(options);
114
149
 
115
- // Add a simple condition at root level
116
- const handleAddCondition = () => {
117
- const id = filter.addCondition({
150
+ const addInvalidFilter = () => {
151
+ filter.addCondition({
152
+ Operator: "EQ",
153
+ LHSField: "InvalidField", // TypeScript error: not a key of Product
154
+ RHSValue: "test",
155
+ });
156
+ };
157
+
158
+ return (
159
+ <div>
160
+ <button onClick={addCategoryFilter}>Filter by Category</button>
161
+ </div>
162
+ );
163
+ }
164
+ ```
165
+
166
+ ### UseFilterOptionsType for Initial State
167
+
168
+ `UseFilterOptionsType<T>` is used for:
169
+ - Initializing `useFilter` directly
170
+ - Setting `initialState.filter` in `useTable`
171
+ - Setting `initialState.filter` in `useKanban`
172
+
173
+ ```tsx
174
+ import { useFilter } from "@ram_28/kf-ai-sdk/filter";
175
+ import type { UseFilterOptionsType } from "@ram_28/kf-ai-sdk/filter/types";
176
+
177
+ interface Product {
178
+ _id: string;
179
+ Title: string;
180
+ Price: number;
181
+ Category: string;
182
+ }
183
+
184
+ // Type-safe filter options
185
+ const initialFilter: UseFilterOptionsType<Product> = {
186
+ conditions: [
187
+ { Operator: "EQ", LHSField: "Category", RHSValue: "Electronics" },
188
+ { Operator: "GT", LHSField: "Price", RHSValue: 100 },
189
+ ],
190
+ operator: "And",
191
+ };
192
+
193
+ // Use with useFilter
194
+ const filter = useFilter<Product>(initialFilter);
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Condition Types
200
+
201
+ ### Equality (EQ, NE)
202
+
203
+ Match or exclude exact values.
204
+
205
+ ```tsx
206
+ function EqualityFilters() {
207
+ const filter = useFilter();
208
+
209
+ // Exact match
210
+ const filterByStatus = (status: string) => {
211
+ filter.addCondition({
212
+ Operator: "EQ",
213
+ LHSField: "Status",
214
+ RHSValue: status,
215
+ });
216
+ };
217
+
218
+ // Exclude a value
219
+ const excludeStatus = (status: string) => {
220
+ filter.addCondition({
221
+ Operator: "NE",
222
+ LHSField: "Status",
223
+ RHSValue: status,
224
+ });
225
+ };
226
+
227
+ return (
228
+ <div>
229
+ <button onClick={() => filterByStatus("Active")}>Active Only</button>
230
+ <button onClick={() => excludeStatus("Archived")}>Exclude Archived</button>
231
+ </div>
232
+ );
233
+ }
234
+ ```
235
+
236
+ ### Comparison (GT, GTE, LT, LTE)
237
+
238
+ Filter by numeric or date comparisons.
239
+
240
+ ```tsx
241
+ function ComparisonFilters() {
242
+ const filter = useFilter();
243
+
244
+ // Price greater than
245
+ const filterByMinPrice = (minPrice: number) => {
246
+ filter.addCondition({
247
+ Operator: "GT",
248
+ LHSField: "Price",
249
+ RHSValue: minPrice,
250
+ });
251
+ };
252
+
253
+ // Stock less than or equal
254
+ const filterLowStock = (threshold: number) => {
255
+ filter.addCondition({
256
+ Operator: "LTE",
257
+ LHSField: "Stock",
258
+ RHSValue: threshold,
259
+ });
260
+ };
261
+
262
+ // Date comparison
263
+ const filterRecent = () => {
264
+ const lastWeek = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
265
+ filter.addCondition({
266
+ Operator: "GTE",
267
+ LHSField: "CreatedAt",
268
+ RHSValue: lastWeek,
269
+ });
270
+ };
271
+
272
+ return (
273
+ <div>
274
+ <button onClick={() => filterByMinPrice(100)}>Price > $100</button>
275
+ <button onClick={() => filterLowStock(10)}>Low Stock</button>
276
+ <button onClick={filterRecent}>Created This Week</button>
277
+ </div>
278
+ );
279
+ }
280
+ ```
281
+
282
+ ### Range (Between, NotBetween)
283
+
284
+ Filter values within or outside a range.
285
+
286
+ ```tsx
287
+ function RangeFilters() {
288
+ const filter = useFilter();
289
+
290
+ // Price between range
291
+ const filterPriceRange = (min: number, max: number) => {
292
+ filter.addCondition({
293
+ Operator: "Between",
294
+ LHSField: "Price",
295
+ RHSValue: [min, max],
296
+ });
297
+ };
298
+
299
+ // Date range
300
+ const filterDateRange = (startDate: string, endDate: string) => {
301
+ filter.addCondition({
302
+ Operator: "Between",
303
+ LHSField: "OrderDate",
304
+ RHSValue: [startDate, endDate],
305
+ });
306
+ };
307
+
308
+ // Exclude range
309
+ const excludePriceRange = (min: number, max: number) => {
310
+ filter.addCondition({
311
+ Operator: "NotBetween",
312
+ LHSField: "Price",
313
+ RHSValue: [min, max],
314
+ });
315
+ };
316
+
317
+ return (
318
+ <div>
319
+ <button onClick={() => filterPriceRange(50, 200)}>$50 - $200</button>
320
+ <button onClick={() => excludePriceRange(0, 10)}>Exclude Under $10</button>
321
+ </div>
322
+ );
323
+ }
324
+ ```
325
+
326
+ ### List (IN, NIN)
327
+
328
+ Match against a list of values.
329
+
330
+ ```tsx
331
+ function ListFilters() {
332
+ const filter = useFilter();
333
+
334
+ // Match any in list
335
+ const filterByCategories = (categories: string[]) => {
336
+ filter.addCondition({
337
+ Operator: "IN",
338
+ LHSField: "Category",
339
+ RHSValue: categories,
340
+ });
341
+ };
342
+
343
+ // Exclude list
344
+ const excludeCategories = (categories: string[]) => {
345
+ filter.addCondition({
346
+ Operator: "NIN",
347
+ LHSField: "Category",
348
+ RHSValue: categories,
349
+ });
350
+ };
351
+
352
+ return (
353
+ <div>
354
+ <button onClick={() => filterByCategories(["Electronics", "Computers"])}>
355
+ Electronics & Computers
356
+ </button>
357
+ <button onClick={() => excludeCategories(["Clearance", "Discontinued"])}>
358
+ Exclude Clearance
359
+ </button>
360
+ </div>
361
+ );
362
+ }
363
+ ```
364
+
365
+ ### Text (Contains, NotContains)
366
+
367
+ Search within text fields.
368
+
369
+ ```tsx
370
+ function TextFilters() {
371
+ const filter = useFilter();
372
+
373
+ // Contains text
374
+ const filterByKeyword = (keyword: string) => {
375
+ filter.addCondition({
376
+ Operator: "Contains",
377
+ LHSField: "Title",
378
+ RHSValue: keyword,
379
+ });
380
+ };
381
+
382
+ // Does not contain
383
+ const excludeKeyword = (keyword: string) => {
384
+ filter.addCondition({
385
+ Operator: "NotContains",
386
+ LHSField: "Description",
387
+ RHSValue: keyword,
388
+ });
389
+ };
390
+
391
+ return (
392
+ <div>
393
+ <input
394
+ placeholder="Search..."
395
+ onKeyDown={(e) => {
396
+ if (e.key === "Enter") {
397
+ filterByKeyword((e.target as HTMLInputElement).value);
398
+ }
399
+ }}
400
+ />
401
+ </div>
402
+ );
403
+ }
404
+ ```
405
+
406
+ ### Empty Checks (Empty, NotEmpty)
407
+
408
+ Filter by presence or absence of values.
409
+
410
+ ```tsx
411
+ function EmptyFilters() {
412
+ const filter = useFilter();
413
+
414
+ // Has no value
415
+ const filterUnassigned = () => {
416
+ filter.addCondition({
417
+ Operator: "Empty",
418
+ LHSField: "AssignedTo",
419
+ RHSValue: null,
420
+ });
421
+ };
422
+
423
+ // Has a value
424
+ const filterAssigned = () => {
425
+ filter.addCondition({
426
+ Operator: "NotEmpty",
427
+ LHSField: "AssignedTo",
428
+ RHSValue: null,
429
+ });
430
+ };
431
+
432
+ return (
433
+ <div>
434
+ <button onClick={filterUnassigned}>Unassigned Tasks</button>
435
+ <button onClick={filterAssigned}>Assigned Tasks</button>
436
+ </div>
437
+ );
438
+ }
439
+ ```
440
+
441
+ ---
442
+
443
+ ## Condition Groups
444
+
445
+ ### AND Logic
446
+
447
+ All conditions must match.
448
+
449
+ ```tsx
450
+ function AndFilter() {
451
+ const filter = useFilter({
452
+ operator: "And",
453
+ });
454
+
455
+ const applyFilters = () => {
456
+ filter.clearAllConditions();
457
+
458
+ // Category AND Price AND InStock - all must be true
459
+ filter.addCondition({
118
460
  Operator: "EQ",
119
461
  LHSField: "Category",
120
462
  RHSValue: "Electronics",
121
- RHSType: "Constant",
122
463
  });
123
- console.log("Created condition with id:", id);
464
+ filter.addCondition({
465
+ Operator: "LTE",
466
+ LHSField: "Price",
467
+ RHSValue: 500,
468
+ });
469
+ filter.addCondition({
470
+ Operator: "GT",
471
+ LHSField: "Stock",
472
+ RHSValue: 0,
473
+ });
124
474
  };
125
475
 
126
- // Build nested filter: (Category = "Electronics") AND (Price > 100 OR OnSale = true)
127
- const handleBuildComplexFilter = () => {
476
+ return (
477
+ <div>
478
+ <button onClick={applyFilters}>
479
+ Electronics under $500, In Stock
480
+ </button>
481
+ <p>Root operator: {filter.operator}</p>
482
+ </div>
483
+ );
484
+ }
485
+ ```
486
+
487
+ ### OR Logic
488
+
489
+ Any condition can match.
490
+
491
+ ```tsx
492
+ function OrFilter() {
493
+ const filter = useFilter({
494
+ operator: "Or",
495
+ });
496
+
497
+ const applyFilters = () => {
128
498
  filter.clearAllConditions();
129
499
 
130
- // Add root condition
500
+ // High priority OR Overdue - either can match
501
+ filter.addCondition({
502
+ Operator: "EQ",
503
+ LHSField: "Priority",
504
+ RHSValue: "High",
505
+ });
506
+ filter.addCondition({
507
+ Operator: "LT",
508
+ LHSField: "DueDate",
509
+ RHSValue: new Date().toISOString(),
510
+ });
511
+ };
512
+
513
+ return (
514
+ <button onClick={applyFilters}>Urgent Tasks</button>
515
+ );
516
+ }
517
+ ```
518
+
519
+ ### Nested Groups
520
+
521
+ Combine AND and OR logic.
522
+
523
+ ```tsx
524
+ function NestedFilter() {
525
+ const filter = useFilter({
526
+ operator: "And",
527
+ });
528
+
529
+ // Build: Category = "Electronics" AND (Price < 100 OR OnSale = true)
530
+ const applyComplexFilter = () => {
531
+ filter.clearAllConditions();
532
+
533
+ // Root level condition
131
534
  filter.addCondition({
132
535
  Operator: "EQ",
133
536
  LHSField: "Category",
@@ -135,42 +538,120 @@ function ProductFilterBuilder() {
135
538
  });
136
539
 
137
540
  // Create nested OR group
138
- const groupId = filter.addConditionGroup("Or");
541
+ const orGroupId = filter.addConditionGroup("Or");
139
542
 
140
- // Add conditions to the group
543
+ // Add conditions to the OR group
141
544
  filter.addCondition({
142
- Operator: "GT",
545
+ Operator: "LT",
143
546
  LHSField: "Price",
144
547
  RHSValue: 100,
145
- }, groupId);
548
+ }, orGroupId);
146
549
 
147
- const saleConditionId = filter.addCondition({
550
+ filter.addCondition({
148
551
  Operator: "EQ",
149
552
  LHSField: "OnSale",
150
553
  RHSValue: true,
151
- }, groupId);
554
+ }, orGroupId);
555
+ };
556
+
557
+ return (
558
+ <button onClick={applyComplexFilter}>
559
+ Affordable Electronics
560
+ </button>
561
+ );
562
+ }
563
+ ```
564
+
565
+ ### Deep Nesting
152
566
 
153
- // Update a condition
154
- filter.updateCondition(saleConditionId, { RHSValue: false });
567
+ Create multiple levels of nested groups.
155
568
 
156
- // Toggle group operator
157
- filter.updateGroupOperator(groupId, "And");
569
+ ```tsx
570
+ function DeepNestedFilter() {
571
+ const filter = useFilter();
572
+
573
+ // Build: (A AND B) OR (C AND D)
574
+ const buildFilter = () => {
575
+ filter.clearAllConditions();
576
+ filter.setRootOperator("Or");
577
+
578
+ // First AND group
579
+ const group1 = filter.addConditionGroup("And");
580
+ filter.addCondition({ Operator: "EQ", LHSField: "Type", RHSValue: "A" }, group1);
581
+ filter.addCondition({ Operator: "GT", LHSField: "Value", RHSValue: 10 }, group1);
582
+
583
+ // Second AND group
584
+ const group2 = filter.addConditionGroup("And");
585
+ filter.addCondition({ Operator: "EQ", LHSField: "Type", RHSValue: "B" }, group2);
586
+ filter.addCondition({ Operator: "LT", LHSField: "Value", RHSValue: 5 }, group2);
587
+ };
588
+
589
+ return <button onClick={buildFilter}>Apply Complex Filter</button>;
590
+ }
591
+ ```
592
+
593
+ ---
594
+
595
+ ## Displaying Filters
596
+
597
+ ### Render Active Filters
598
+
599
+ Show current filter conditions with remove buttons.
600
+
601
+ ```tsx
602
+ import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
603
+
604
+ function ActiveFilters() {
605
+ const filter = useFilter();
606
+
607
+ const renderItem = (item: ConditionType | ConditionGroupType) => {
608
+ if (isCondition(item)) {
609
+ return (
610
+ <span key={item.id} className="filter-tag">
611
+ {item.LHSField} {item.Operator} {String(item.RHSValue)}
612
+ <button onClick={() => filter.removeCondition(item.id!)}>x</button>
613
+ </span>
614
+ );
615
+ }
616
+
617
+ if (isConditionGroup(item)) {
618
+ return (
619
+ <span key={item.id} className="filter-group">
620
+ {item.Operator} ({item.Condition.length} conditions)
621
+ <button onClick={() => filter.removeCondition(item.id!)}>x</button>
622
+ </span>
623
+ );
624
+ }
625
+
626
+ return null;
158
627
  };
159
628
 
160
- // Render filter tree recursively
161
- const renderFilterItem = (item: ConditionType | ConditionGroupType, depth = 0): JSX.Element => {
629
+ return (
630
+ <div className="active-filters">
631
+ {filter.items.map(renderItem)}
632
+ {filter.hasConditions && (
633
+ <button onClick={filter.clearAllConditions}>Clear All</button>
634
+ )}
635
+ </div>
636
+ );
637
+ }
638
+ ```
639
+
640
+ ### Recursive Tree Display
641
+
642
+ Display nested filter structure as a tree.
643
+
644
+ ```tsx
645
+ function FilterTree() {
646
+ const filter = useFilter();
647
+
648
+ const renderNode = (item: ConditionType | ConditionGroupType, depth = 0) => {
162
649
  const indent = { marginLeft: depth * 20 };
163
650
 
164
651
  if (isCondition(item)) {
165
652
  return (
166
653
  <div key={item.id} style={indent} className="filter-condition">
167
- <span>
168
- {item.LHSField} {item.Operator} {String(item.RHSValue)}
169
- </span>
170
- <button onClick={() => filter.removeCondition(item.id!)}>Remove</button>
171
- <button onClick={() => filter.updateCondition(item.id!, { RHSValue: "Updated" })}>
172
- Update
173
- </button>
654
+ {item.LHSField} {item.Operator} {JSON.stringify(item.RHSValue)}
174
655
  </div>
175
656
  );
176
657
  }
@@ -178,96 +659,311 @@ function ProductFilterBuilder() {
178
659
  if (isConditionGroup(item)) {
179
660
  return (
180
661
  <div key={item.id} style={indent} className="filter-group">
181
- <div className="group-header">
182
- <select
183
- value={item.Operator}
184
- onChange={(e) =>
185
- filter.updateGroupOperator(item.id!, e.target.value as ConditionGroupOperatorType)
186
- }
187
- >
188
- <option value="And">AND</option>
189
- <option value="Or">OR</option>
190
- <option value="Not">NOT</option>
191
- </select>
192
- <button onClick={() => filter.addCondition({ Operator: "EQ", LHSField: "Field", RHSValue: "" }, item.id!)}>
193
- + Condition
194
- </button>
195
- <button onClick={() => filter.addConditionGroup("And", item.id!)}>+ Group</button>
196
- <button onClick={() => filter.removeCondition(item.id!)}>Remove Group</button>
197
- </div>
198
- <div className="group-conditions">
199
- {item.Condition.map((child) => renderFilterItem(child, depth + 1))}
200
- </div>
662
+ <strong>{item.Operator}</strong>
663
+ {item.Condition.map((child) => renderNode(child, depth + 1))}
201
664
  </div>
202
665
  );
203
666
  }
204
667
 
205
- return <></>;
668
+ return null;
206
669
  };
207
670
 
208
- // Use filter payload in API call
209
- const handleApplyFilter = async () => {
210
- const payload: FilterType | undefined = filter.payload;
211
- if (payload) {
212
- console.log("API Payload (no ids):", JSON.stringify(payload, null, 2));
213
- const response = await fetch("/api/products", {
214
- method: "POST",
215
- headers: { "Content-Type": "application/json" },
216
- body: JSON.stringify({ Filter: payload }),
217
- });
218
- return response.json();
671
+ return (
672
+ <div className="filter-tree">
673
+ <div className="root">
674
+ <strong>Root: {filter.operator}</strong>
675
+ </div>
676
+ {filter.items.map((item) => renderNode(item, 1))}
677
+ </div>
678
+ );
679
+ }
680
+ ```
681
+
682
+ ---
683
+
684
+ ## Modifying Filters
685
+
686
+ ### Update a Condition
687
+
688
+ Change an existing condition's values.
689
+
690
+ ```tsx
691
+ function EditableFilter() {
692
+ const filter = useFilter();
693
+ const [conditionId, setConditionId] = useState<string | null>(null);
694
+
695
+ const addFilter = () => {
696
+ const id = filter.addCondition({
697
+ Operator: "GT",
698
+ LHSField: "Price",
699
+ RHSValue: 50,
700
+ });
701
+ setConditionId(id);
702
+ };
703
+
704
+ const updateValue = (newValue: number) => {
705
+ if (conditionId) {
706
+ filter.updateCondition(conditionId, { RHSValue: newValue });
219
707
  }
220
708
  };
221
709
 
222
- // Access individual item by id
223
- const handleGetItem = (id: string) => {
224
- const item = filter.getCondition(id);
225
- if (item) {
226
- console.log("Found item:", item);
710
+ const updateOperator = (operator: ConditionOperatorType) => {
711
+ if (conditionId) {
712
+ filter.updateCondition(conditionId, { Operator: operator });
227
713
  }
228
714
  };
229
715
 
230
716
  return (
231
- <div className="filter-builder">
232
- {/* Root operator control */}
233
- <div className="root-controls">
234
- <label>
235
- Root Operator:
236
- <select
237
- value={filter.operator}
238
- onChange={(e) => filter.setRootOperator(e.target.value as ConditionGroupOperator)}
239
- >
240
- <option value="And">AND</option>
241
- <option value="Or">OR</option>
717
+ <div>
718
+ <button onClick={addFilter}>Add Price Filter</button>
719
+ {conditionId && (
720
+ <div>
721
+ <select onChange={(e) => updateOperator(e.target.value as ConditionOperatorType)}>
722
+ <option value="GT">Greater Than</option>
723
+ <option value="LT">Less Than</option>
724
+ <option value="EQ">Equals</option>
242
725
  </select>
243
- </label>
244
- <button onClick={handleAddCondition}>Add Condition</button>
245
- <button onClick={() => filter.addConditionGroup("Or")}>Add Group</button>
246
- <button onClick={handleBuildComplexFilter}>Build Complex Filter</button>
247
- <button onClick={filter.clearAllConditions} disabled={!filter.hasConditions}>
248
- Clear All
249
- </button>
250
- </div>
726
+ <input
727
+ type="number"
728
+ defaultValue={50}
729
+ onChange={(e) => updateValue(Number(e.target.value))}
730
+ />
731
+ </div>
732
+ )}
733
+ </div>
734
+ );
735
+ }
736
+ ```
251
737
 
252
- {/* Filter tree display */}
253
- <div className="filter-tree">
254
- <h3>Filter Tree ({filter.items.length} root items)</h3>
255
- {filter.items.map((item) => renderFilterItem(item))}
256
- </div>
738
+ ### Change Group Operator
257
739
 
258
- {/* Apply filter */}
259
- <div className="filter-actions">
260
- <button onClick={handleApplyFilter} disabled={!filter.hasConditions}>
261
- Apply Filter
262
- </button>
263
- <span>Has conditions: {filter.hasConditions ? "Yes" : "No"}</span>
264
- </div>
740
+ Toggle between AND/OR for a group.
741
+
742
+ ```tsx
743
+ function ToggleableGroupOperator() {
744
+ const filter = useFilter();
745
+ const [groupId, setGroupId] = useState<string | null>(null);
746
+
747
+ const createGroup = () => {
748
+ const id = filter.addConditionGroup("And");
749
+ filter.addCondition({ Operator: "EQ", LHSField: "A", RHSValue: 1 }, id);
750
+ filter.addCondition({ Operator: "EQ", LHSField: "B", RHSValue: 2 }, id);
751
+ setGroupId(id);
752
+ };
753
+
754
+ const toggleOperator = () => {
755
+ if (groupId) {
756
+ const group = filter.getCondition(groupId) as ConditionGroupType;
757
+ const newOp = group.Operator === "And" ? "Or" : "And";
758
+ filter.updateGroupOperator(groupId, newOp);
759
+ }
760
+ };
761
+
762
+ return (
763
+ <div>
764
+ <button onClick={createGroup}>Create Group</button>
765
+ {groupId && (
766
+ <button onClick={toggleOperator}>Toggle AND/OR</button>
767
+ )}
768
+ </div>
769
+ );
770
+ }
771
+ ```
772
+
773
+ ---
774
+
775
+ ## Using Filter Payload
776
+
777
+ ### Get API-Ready Payload
778
+
779
+ Access the filter structure for API calls.
780
+
781
+ ```tsx
782
+ function FilterWithApi() {
783
+ const filter = useFilter();
784
+
785
+ const fetchFiltered = async () => {
786
+ const payload = filter.payload;
787
+
788
+ if (!payload) {
789
+ console.log("No filters applied");
790
+ return;
791
+ }
792
+
793
+ // Payload is ready for API - no internal IDs included
794
+ const response = await fetch("/api/products", {
795
+ method: "POST",
796
+ headers: { "Content-Type": "application/json" },
797
+ body: JSON.stringify({ Filter: payload }),
798
+ });
799
+
800
+ return response.json();
801
+ };
802
+
803
+ return (
804
+ <div>
805
+ <button onClick={() => filter.addCondition({
806
+ Operator: "EQ",
807
+ LHSField: "Status",
808
+ RHSValue: "Active",
809
+ })}>
810
+ Add Filter
811
+ </button>
812
+ <button onClick={fetchFiltered}>Fetch Data</button>
813
+
814
+ <pre>{JSON.stringify(filter.payload, null, 2)}</pre>
815
+ </div>
816
+ );
817
+ }
818
+ ```
819
+
820
+ ### Initialize with Existing Filters
821
+
822
+ Load filters from saved state.
265
823
 
266
- {/* Debug payload (id fields are stripped) */}
267
- <pre className="filter-payload">
268
- {JSON.stringify(filter.payload, null, 2)}
269
- </pre>
824
+ ```tsx
825
+ function FilterWithInitialState() {
826
+ const savedFilters: Array<ConditionType | ConditionGroupType> = [
827
+ { Operator: "EQ", LHSField: "Status", RHSValue: "Active" },
828
+ { Operator: "GT", LHSField: "Price", RHSValue: 100 },
829
+ ];
830
+
831
+ const filter = useFilter({
832
+ conditions: savedFilters,
833
+ operator: "And",
834
+ });
835
+
836
+ return (
837
+ <div>
838
+ <p>Loaded {filter.items.length} filters</p>
839
+ {/* filter UI */}
270
840
  </div>
271
841
  );
272
842
  }
273
843
  ```
844
+
845
+ ---
846
+
847
+ ## Payload Structure
848
+
849
+ The `filter.payload` property returns the filter structure ready for API consumption. Here are examples of what the JSON looks like for different scenarios.
850
+
851
+ ### Single Condition
852
+
853
+ ```tsx
854
+ filter.addCondition({
855
+ Operator: "EQ",
856
+ LHSField: "Status",
857
+ RHSValue: "Active",
858
+ });
859
+ ```
860
+
861
+ Produces:
862
+
863
+ ```json
864
+ {
865
+ "Operator": "And",
866
+ "Condition": [
867
+ {
868
+ "Operator": "EQ",
869
+ "LHSField": "Status",
870
+ "RHSValue": "Active",
871
+ "RHSType": "Constant"
872
+ }
873
+ ]
874
+ }
875
+ ```
876
+
877
+ ### Multiple Conditions (AND)
878
+
879
+ ```tsx
880
+ filter.addCondition({ Operator: "EQ", LHSField: "Category", RHSValue: "Electronics" });
881
+ filter.addCondition({ Operator: "GT", LHSField: "Price", RHSValue: 100 });
882
+ filter.addCondition({ Operator: "LTE", LHSField: "Stock", RHSValue: 50 });
883
+ ```
884
+
885
+ Produces:
886
+
887
+ ```json
888
+ {
889
+ "Operator": "And",
890
+ "Condition": [
891
+ { "Operator": "EQ", "LHSField": "Category", "RHSValue": "Electronics", "RHSType": "Constant" },
892
+ { "Operator": "GT", "LHSField": "Price", "RHSValue": 100, "RHSType": "Constant" },
893
+ { "Operator": "LTE", "LHSField": "Stock", "RHSValue": 50, "RHSType": "Constant" }
894
+ ]
895
+ }
896
+ ```
897
+
898
+ ### Nested Groups
899
+
900
+ ```tsx
901
+ // Root: AND
902
+ // Category = "Electronics" AND (Price < 100 OR OnSale = true)
903
+ filter.addCondition({ Operator: "EQ", LHSField: "Category", RHSValue: "Electronics" });
904
+ const orGroupId = filter.addConditionGroup("Or");
905
+ filter.addCondition({ Operator: "LT", LHSField: "Price", RHSValue: 100 }, orGroupId);
906
+ filter.addCondition({ Operator: "EQ", LHSField: "OnSale", RHSValue: true }, orGroupId);
907
+ ```
908
+
909
+ Produces:
910
+
911
+ ```json
912
+ {
913
+ "Operator": "And",
914
+ "Condition": [
915
+ { "Operator": "EQ", "LHSField": "Category", "RHSValue": "Electronics", "RHSType": "Constant" },
916
+ {
917
+ "Operator": "Or",
918
+ "Condition": [
919
+ { "Operator": "LT", "LHSField": "Price", "RHSValue": 100, "RHSType": "Constant" },
920
+ { "Operator": "EQ", "LHSField": "OnSale", "RHSValue": true, "RHSType": "Constant" }
921
+ ]
922
+ }
923
+ ]
924
+ }
925
+ ```
926
+
927
+ ### Range Values (Between)
928
+
929
+ ```tsx
930
+ filter.addCondition({
931
+ Operator: "Between",
932
+ LHSField: "Price",
933
+ RHSValue: [50, 200],
934
+ });
935
+ ```
936
+
937
+ Produces:
938
+
939
+ ```json
940
+ {
941
+ "Operator": "And",
942
+ "Condition": [
943
+ { "Operator": "Between", "LHSField": "Price", "RHSValue": [50, 200], "RHSType": "Constant" }
944
+ ]
945
+ }
946
+ ```
947
+
948
+ ### List Values (IN)
949
+
950
+ ```tsx
951
+ filter.addCondition({
952
+ Operator: "IN",
953
+ LHSField: "Category",
954
+ RHSValue: ["Electronics", "Computers", "Accessories"],
955
+ });
956
+ ```
957
+
958
+ Produces:
959
+
960
+ ```json
961
+ {
962
+ "Operator": "And",
963
+ "Condition": [
964
+ { "Operator": "IN", "LHSField": "Category", "RHSValue": ["Electronics", "Computers", "Accessories"], "RHSType": "Constant" }
965
+ ]
966
+ }
967
+ ```
968
+
969
+ > **Note:** The `RHSType` field defaults to `"Constant"` and is automatically added to the payload. Other possible values are `"BOField"` (reference another field) and `"AppVariable"` (reference an application variable).