@ram_28/kf-ai-sdk 2.0.12 → 2.0.13

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 (69) hide show
  1. package/dist/api/client.d.ts.map +1 -1
  2. package/dist/api.cjs +1 -1
  3. package/dist/api.mjs +2 -2
  4. package/dist/attachment-constants-B5jlqoKI.cjs +1 -0
  5. package/dist/attachment-constants-C2UHWxmp.js +63 -0
  6. package/dist/auth.cjs +1 -1
  7. package/dist/auth.mjs +1 -1
  8. package/dist/bdo/core/types.d.ts +4 -0
  9. package/dist/bdo/core/types.d.ts.map +1 -1
  10. package/dist/bdo/fields/NumberField.d.ts.map +1 -1
  11. package/dist/bdo/fields/ReferenceField.d.ts +3 -2
  12. package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
  13. package/dist/bdo/fields/SelectField.d.ts +1 -1
  14. package/dist/bdo/fields/SelectField.d.ts.map +1 -1
  15. package/dist/bdo/fields/UserField.d.ts +5 -0
  16. package/dist/bdo/fields/UserField.d.ts.map +1 -1
  17. package/dist/bdo.cjs +1 -1
  18. package/dist/bdo.mjs +107 -153
  19. package/dist/client-DnO2KKrw.cjs +1 -0
  20. package/dist/{client-CMERmrC-.js → client-iQTqFDNI.js} +34 -30
  21. package/dist/components/hooks/useForm/createItemProxy.d.ts +4 -0
  22. package/dist/components/hooks/useForm/createItemProxy.d.ts.map +1 -1
  23. package/dist/components/hooks/useForm/createResolver.d.ts.map +1 -1
  24. package/dist/components/hooks/useForm/useForm.d.ts +1 -0
  25. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
  26. package/dist/form.cjs +1 -1
  27. package/dist/form.mjs +368 -203
  28. package/dist/{metadata-BfJtHz84.cjs → metadata-DgLSJkF5.cjs} +1 -1
  29. package/dist/{metadata-CwAo6a8e.js → metadata-DpfI3zRN.js} +1 -1
  30. package/dist/table.cjs +1 -1
  31. package/dist/table.mjs +1 -1
  32. package/dist/workflow/types.d.ts +3 -2
  33. package/dist/workflow/types.d.ts.map +1 -1
  34. package/dist/workflow.cjs +1 -1
  35. package/dist/workflow.d.ts +0 -2
  36. package/dist/workflow.d.ts.map +1 -1
  37. package/dist/workflow.mjs +204 -274
  38. package/dist/workflow.types.d.ts +0 -1
  39. package/dist/workflow.types.d.ts.map +1 -1
  40. package/docs/api.md +45 -253
  41. package/docs/bdo.md +130 -711
  42. package/docs/useAuth.md +42 -104
  43. package/docs/useFilter.md +117 -1591
  44. package/docs/useForm.md +263 -861
  45. package/docs/useTable.md +255 -1096
  46. package/docs/workflow.md +10 -155
  47. package/package.json +1 -1
  48. package/sdk/api/client.ts +18 -4
  49. package/sdk/bdo/core/types.ts +1 -0
  50. package/sdk/bdo/fields/NumberField.ts +2 -1
  51. package/sdk/bdo/fields/ReferenceField.ts +4 -3
  52. package/sdk/bdo/fields/SelectField.ts +2 -2
  53. package/sdk/bdo/fields/UserField.ts +14 -0
  54. package/sdk/components/hooks/useForm/createItemProxy.ts +221 -4
  55. package/sdk/components/hooks/useForm/createResolver.ts +16 -1
  56. package/sdk/components/hooks/useForm/useForm.ts +151 -50
  57. package/sdk/workflow/types.ts +3 -2
  58. package/sdk/workflow.ts +0 -7
  59. package/sdk/workflow.types.ts +0 -7
  60. package/dist/client-BnVxSHAm.cjs +0 -1
  61. package/dist/workflow/components/useActivityTable/index.d.ts +0 -4
  62. package/dist/workflow/components/useActivityTable/index.d.ts.map +0 -1
  63. package/dist/workflow/components/useActivityTable/types.d.ts +0 -53
  64. package/dist/workflow/components/useActivityTable/types.d.ts.map +0 -1
  65. package/dist/workflow/components/useActivityTable/useActivityTable.d.ts +0 -4
  66. package/dist/workflow/components/useActivityTable/useActivityTable.d.ts.map +0 -1
  67. package/sdk/workflow/components/useActivityTable/index.ts +0 -8
  68. package/sdk/workflow/components/useActivityTable/types.ts +0 -67
  69. package/sdk/workflow/components/useActivityTable/useActivityTable.ts +0 -145
package/docs/useFilter.md CHANGED
@@ -1,1662 +1,188 @@
1
1
  # Filter SDK API
2
2
 
3
- This Filter SDK API provides necessary React-hooks and Components to build the
4
- Filter for using it when making api calls and building form component.
5
-
6
- Here is the example of Build and manage filter conditions with support for nested groups and complex logic.
7
-
8
- You SHOULD only use this API to building Table, Form, any other api call with that requires Filter.
3
+ React hook for building filter conditions for tables, forms, and API calls.
9
4
 
10
5
  ## Imports
11
6
 
12
7
  ```typescript
13
- import {
14
- useFilter,
15
- isCondition,
16
- isConditionGroup,
17
- ConditionOperator,
18
- GroupOperator,
19
- RHSType,
20
- } from "@ram_28/kf-ai-sdk/filter";
21
- import type {
22
- UseFilterOptionsType,
23
- UseFilterReturnType,
24
- ConditionType,
25
- ConditionGroupType,
26
- ConditionOperatorType,
27
- ConditionGroupOperatorType,
28
- FilterType,
29
- } from "@ram_28/kf-ai-sdk/filter/types";
30
- ```
31
-
32
- ## Constants
33
-
34
- Use the provided constants instead of hardcoded strings for type-safety:
35
-
36
- ```typescript
37
- // Condition operators
38
- ConditionOperator.EQ // "EQ"
39
- ConditionOperator.NE // "NE"
40
- ConditionOperator.GT // "GT"
41
- ConditionOperator.GTE // "GTE"
42
- ConditionOperator.LT // "LT"
43
- ConditionOperator.LTE // "LTE"
44
- ConditionOperator.Between // "Between"
45
- ConditionOperator.NotBetween // "NotBetween"
46
- ConditionOperator.IN // "IN"
47
- ConditionOperator.NIN // "NIN"
48
- ConditionOperator.Empty // "Empty"
49
- ConditionOperator.NotEmpty // "NotEmpty"
50
- ConditionOperator.Contains // "Contains"
51
- ConditionOperator.NotContains // "NotContains"
52
- ConditionOperator.MinLength // "MinLength"
53
- ConditionOperator.MaxLength // "MaxLength"
54
-
55
- // Group operators
56
- GroupOperator.And // "And"
57
- GroupOperator.Or // "Or"
58
- GroupOperator.Not // "Not"
59
-
60
- // RHS types
61
- RHSType.Constant // "Constant"
62
- RHSType.BDOField // "BDOField"
63
- RHSType.AppVariable // "AppVariable"
64
- ```
65
-
66
- ## Type Definitions
67
-
68
- ```typescript
69
- // Condition operators
70
- type ConditionOperatorType =
71
- | "EQ"
72
- | "NE" // Equal, Not Equal
73
- | "GT"
74
- | "GTE" // Greater Than, Greater Than or Equal
75
- | "LT"
76
- | "LTE" // Less Than, Less Than or Equal
77
- | "Between"
78
- | "NotBetween"
79
- | "IN"
80
- | "NIN" // In List, Not In List
81
- | "Empty"
82
- | "NotEmpty"
83
- | "Contains"
84
- | "NotContains"
85
- | "MinLength"
86
- | "MaxLength";
87
-
88
- // Group operators
89
- type ConditionGroupOperatorType = "And" | "Or" | "Not";
90
-
91
- // Single condition (generic for type-safe LHSField)
92
- interface ConditionType<T = any> {
93
- // Auto-generated unique identifier
94
- id?: string;
95
-
96
- // Comparison operator (EQ, GT, Contains, etc.)
97
- Operator: ConditionOperatorType;
98
-
99
- // Field name to compare
100
- LHSField: keyof T | string;
101
-
102
- // Value to compare against
103
- RHSValue: any;
104
-
105
- // Value type (default: "Constant")
106
- RHSType?: "Constant" | "BDOField" | "AppVariable";
107
- }
108
-
109
- // Condition group (can contain conditions or nested groups)
110
- interface ConditionGroupType<T = any> {
111
- // Auto-generated unique identifier
112
- id?: string;
113
-
114
- // Group operator (And, Or, Not)
115
- Operator: ConditionGroupOperatorType;
116
-
117
- // Nested conditions or groups
118
- Condition: Array<ConditionType<T> | ConditionGroupType<T>>;
119
- }
120
-
121
- // Hook options (also used for initialState in useTable/useKanban)
122
- interface UseFilterOptionsType<T = any> {
123
- // Initial conditions to populate the filter
124
- conditions?: Array<ConditionType<T> | ConditionGroupType<T>>;
125
-
126
- // Root operator for combining conditions (default: "And")
127
- operator?: ConditionGroupOperatorType;
128
- }
129
-
130
- // Hook return type (generic for type-safe field names)
131
- interface UseFilterReturnType<T = any> {
132
- // ============================================================
133
- // STATE
134
- // ============================================================
135
-
136
- // Current root operator (And, Or, Not)
137
- operator: ConditionGroupOperatorType;
138
-
139
- // All conditions and groups at root level
140
- items: Array<ConditionType<T> | ConditionGroupType<T>>;
141
-
142
- // API-ready filter payload (undefined if no conditions)
143
- payload: FilterType<T> | undefined;
144
-
145
- // True when at least one condition exists
146
- hasConditions: boolean;
147
-
148
- // ============================================================
149
- // METHODS
150
- // ============================================================
151
-
152
- // Add a condition, optionally to a parent group. Returns the new condition's ID
153
- addCondition: (
154
- condition: Omit<ConditionType<T>, "id">,
155
- parentId?: string,
156
- ) => string;
157
-
158
- // Add a condition group, optionally to a parent group. Returns the new group's ID
159
- addConditionGroup: (
160
- operator: ConditionGroupOperatorType,
161
- parentId?: string,
162
- ) => string;
163
-
164
- // Update a condition's properties (Operator, LHSField, RHSValue)
165
- updateCondition: (
166
- id: string,
167
- updates: Partial<Omit<ConditionType<T>, "id">>,
168
- ) => void;
169
-
170
- // Change a group's operator (And, Or, Not)
171
- updateGroupOperator: (
172
- id: string,
173
- operator: ConditionGroupOperatorType,
174
- ) => void;
175
-
176
- // Remove a condition or group by ID
177
- removeCondition: (id: string) => void;
178
-
179
- // Get a condition or group by ID
180
- getCondition: (
181
- id: string,
182
- ) => ConditionType<T> | ConditionGroupType<T> | undefined;
183
-
184
- // Remove all conditions and groups
185
- clearAllConditions: () => void;
186
-
187
- // Change the root operator
188
- setRootOperator: (operator: ConditionGroupOperatorType) => void;
189
- }
190
- ```
191
-
192
- ## Operator Applicability
193
-
194
- | Operator | Applicable Field Types |
195
- | --------------------- | ---------------------- |
196
- | EQ, NE | All types |
197
- | GT, GTE, LT, LTE | number, date, currency |
198
- | Between, NotBetween | number, date, currency |
199
- | IN, NIN | All types |
200
- | Empty, NotEmpty | All types |
201
- | Contains, NotContains | string, reference/user (with dot notation) |
202
- | MinLength, MaxLength | string, array |
203
- | Length | array |
204
-
205
- ## Basic Example
206
-
207
- Create a simple filter with one condition.
208
-
209
- ```tsx
210
- import { useMemo } from "react";
211
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
212
- import { BuyerProduct } from "../bdo/buyer/Product";
213
-
214
- function SimpleFilter() {
215
- const product = useMemo(() => new BuyerProduct(), []);
216
- const filter = useFilter();
217
-
218
- const addCategoryFilter = () => {
219
- filter.addCondition({
220
- Operator: ConditionOperator.EQ,
221
- LHSField: product.Category.id,
222
- RHSValue: "Electronics",
223
- RHSType: RHSType.Constant,
224
- });
225
- };
226
-
227
- return (
228
- <div>
229
- <button onClick={addCategoryFilter}>Filter by Electronics</button>
230
- <button
231
- onClick={filter.clearAllConditions}
232
- disabled={!filter.hasConditions}
233
- >
234
- Clear
235
- </button>
236
- <p>Active filters: {filter.items.length}</p>
237
- </div>
238
- );
239
- }
240
- ```
241
-
242
- ---
243
-
244
- ## Type-Safe Filtering
245
-
246
- Use the generic type parameter to get TypeScript validation on field names.
247
-
248
- ### With Generic Type
249
-
250
- ```tsx
251
- import { useMemo } from "react";
252
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
253
- import { BuyerProduct } from "../bdo/buyer/Product";
254
- import type { BuyerProductFieldType } from "../bdo/buyer/Product";
255
-
256
- function TypeSafeFilter() {
257
- const product = useMemo(() => new BuyerProduct(), []);
258
- // Pass the type parameter for type-safe LHSField
259
- const filter = useFilter<BuyerProductFieldType>();
260
-
261
- const addCategoryFilter = () => {
262
- filter.addCondition({
263
- Operator: ConditionOperator.EQ,
264
- LHSField: product.Category.id, // Type-safe via BDO field
265
- RHSValue: "Electronics",
266
- RHSType: RHSType.Constant,
267
- });
268
- };
269
-
270
- const addInvalidFilter = () => {
271
- filter.addCondition({
272
- Operator: ConditionOperator.EQ,
273
- LHSField: "InvalidField", // TypeScript error: not a key of BuyerProductFieldType
274
- RHSValue: "test",
275
- RHSType: RHSType.Constant,
276
- });
277
- };
278
-
279
- return (
280
- <div>
281
- <button onClick={addCategoryFilter}>Filter by Category</button>
282
- </div>
283
- );
284
- }
285
- ```
286
-
287
- ### UseFilterOptionsType for Initial State
288
-
289
- `UseFilterOptionsType<T>` is used for:
290
-
291
- - Initializing `useFilter` directly
292
- - Setting `initialState.filter` in `useTable`
293
- - Setting `initialState.filter` in `useKanban`
294
-
295
- ```tsx
296
- import { useMemo } from "react";
297
- import { useFilter, ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
298
- import type { UseFilterOptionsType } from "@ram_28/kf-ai-sdk/filter/types";
299
- import { BuyerProduct } from "../bdo/buyer/Product";
300
- import type { BuyerProductFieldType } from "../bdo/buyer/Product";
301
-
302
- function FilterWithInitialState() {
303
- const product = useMemo(() => new BuyerProduct(), []);
304
-
305
- // Type-safe filter options
306
- const initialFilter: UseFilterOptionsType<BuyerProductFieldType> = {
307
- conditions: [
308
- {
309
- Operator: ConditionOperator.EQ,
310
- LHSField: product.Category.id,
311
- RHSValue: "Electronics",
312
- RHSType: RHSType.Constant,
313
- },
314
- {
315
- Operator: ConditionOperator.GT,
316
- LHSField: product.Price.id,
317
- RHSValue: 100,
318
- RHSType: RHSType.Constant,
319
- },
320
- ],
321
- operator: GroupOperator.And,
322
- };
323
-
324
- // Use with useFilter
325
- const filter = useFilter<BuyerProductFieldType>(initialFilter);
326
- }
327
- ```
328
-
329
- ---
330
-
331
- ## Condition Types
332
-
333
- ### Equality (EQ, NE)
334
-
335
- Match or exclude exact values.
336
-
337
- ```tsx
338
- import { useMemo } from "react";
339
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
340
- import { BuyerProduct } from "../bdo/buyer/Product";
341
-
342
- function EqualityFilters() {
343
- const product = useMemo(() => new BuyerProduct(), []);
344
- const filter = useFilter();
345
-
346
- // Exact match
347
- const filterByStatus = (status: string) => {
348
- filter.addCondition({
349
- Operator: ConditionOperator.EQ,
350
- LHSField: product.Status.id,
351
- RHSValue: status,
352
- RHSType: RHSType.Constant,
353
- });
354
- };
355
-
356
- // Exclude a value
357
- const excludeStatus = (status: string) => {
358
- filter.addCondition({
359
- Operator: ConditionOperator.NE,
360
- LHSField: product.Status.id,
361
- RHSValue: status,
362
- RHSType: RHSType.Constant,
363
- });
364
- };
365
-
366
- return (
367
- <div>
368
- <button onClick={() => filterByStatus("Active")}>Active Only</button>
369
- <button onClick={() => excludeStatus("Archived")}>
370
- Exclude Archived
371
- </button>
372
- </div>
373
- );
374
- }
375
- ```
376
-
377
- ### Comparison (GT, GTE, LT, LTE)
378
-
379
- Filter by numeric or date comparisons.
380
-
381
- ```tsx
382
- import { useMemo } from "react";
383
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
384
- import { BuyerProduct } from "../bdo/buyer/Product";
385
-
386
- function ComparisonFilters() {
387
- const product = useMemo(() => new BuyerProduct(), []);
388
- const filter = useFilter();
389
-
390
- // Price greater than
391
- const filterByMinPrice = (minPrice: number) => {
392
- filter.addCondition({
393
- Operator: ConditionOperator.GT,
394
- LHSField: product.Price.id,
395
- RHSValue: minPrice,
396
- RHSType: RHSType.Constant,
397
- });
398
- };
399
-
400
- // Stock less than or equal
401
- const filterLowStock = (threshold: number) => {
402
- filter.addCondition({
403
- Operator: ConditionOperator.LTE,
404
- LHSField: product.Stock.id,
405
- RHSValue: threshold,
406
- RHSType: RHSType.Constant,
407
- });
408
- };
409
-
410
- // Date comparison (using system field)
411
- const filterRecent = () => {
412
- const lastWeek = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
413
- filter.addCondition({
414
- Operator: ConditionOperator.GTE,
415
- LHSField: "_created_at", // System field
416
- RHSValue: lastWeek,
417
- RHSType: RHSType.Constant,
418
- });
419
- };
420
-
421
- return (
422
- <div>
423
- <button onClick={() => filterByMinPrice(100)}>Price > $100</button>
424
- <button onClick={() => filterLowStock(10)}>Low Stock</button>
425
- <button onClick={filterRecent}>Created This Week</button>
426
- </div>
427
- );
428
- }
429
- ```
430
-
431
- ### Range (Between, NotBetween)
432
-
433
- Filter values within or outside a range.
434
-
435
- ```tsx
436
- import { useMemo } from "react";
437
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
438
- import { BuyerProduct } from "../bdo/buyer/Product";
439
- import { BuyerOrder } from "../bdo/buyer/Order";
440
-
441
- function RangeFilters() {
442
- const product = useMemo(() => new BuyerProduct(), []);
443
- const order = useMemo(() => new BuyerOrder(), []);
444
- const filter = useFilter();
445
-
446
- // Price between range
447
- const filterPriceRange = (min: number, max: number) => {
448
- filter.addCondition({
449
- Operator: ConditionOperator.Between,
450
- LHSField: product.Price.id,
451
- RHSValue: [min, max],
452
- RHSType: RHSType.Constant,
453
- });
454
- };
455
-
456
- // Date range
457
- const filterDateRange = (startDate: string, endDate: string) => {
458
- filter.addCondition({
459
- Operator: ConditionOperator.Between,
460
- LHSField: order.OrderDate.id,
461
- RHSValue: [startDate, endDate],
462
- RHSType: RHSType.Constant,
463
- });
464
- };
465
-
466
- // Exclude range
467
- const excludePriceRange = (min: number, max: number) => {
468
- filter.addCondition({
469
- Operator: ConditionOperator.NotBetween,
470
- LHSField: product.Price.id,
471
- RHSValue: [min, max],
472
- RHSType: RHSType.Constant,
473
- });
474
- };
475
-
476
- return (
477
- <div>
478
- <button onClick={() => filterPriceRange(50, 200)}>$50 - $200</button>
479
- <button onClick={() => excludePriceRange(0, 10)}>
480
- Exclude Under $10
481
- </button>
482
- </div>
483
- );
484
- }
485
- ```
486
-
487
- ### List (IN, NIN)
488
-
489
- Match against a list of values.
490
-
491
- ```tsx
492
- import { useMemo } from "react";
493
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
494
- import { BuyerProduct } from "../bdo/buyer/Product";
495
-
496
- function ListFilters() {
497
- const product = useMemo(() => new BuyerProduct(), []);
498
- const filter = useFilter();
499
-
500
- // Match any in list
501
- const filterByCategories = (categories: string[]) => {
502
- filter.addCondition({
503
- Operator: ConditionOperator.IN,
504
- LHSField: product.Category.id,
505
- RHSValue: categories,
506
- RHSType: RHSType.Constant,
507
- });
508
- };
509
-
510
- // Exclude list
511
- const excludeCategories = (categories: string[]) => {
512
- filter.addCondition({
513
- Operator: ConditionOperator.NIN,
514
- LHSField: product.Category.id,
515
- RHSValue: categories,
516
- RHSType: RHSType.Constant,
517
- });
518
- };
519
-
520
- return (
521
- <div>
522
- <button onClick={() => filterByCategories(["Electronics", "Computers"])}>
523
- Electronics & Computers
524
- </button>
525
- <button onClick={() => excludeCategories(["Clearance", "Discontinued"])}>
526
- Exclude Clearance
527
- </button>
528
- </div>
529
- );
530
- }
531
- ```
532
-
533
- ### Text (Contains, NotContains)
534
-
535
- Search within text fields.
536
-
537
- ```tsx
538
- import { useMemo } from "react";
539
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
540
- import { BuyerProduct } from "../bdo/buyer/Product";
541
-
542
- function TextFilters() {
543
- const product = useMemo(() => new BuyerProduct(), []);
544
- const filter = useFilter();
545
-
546
- // Contains text
547
- const filterByKeyword = (keyword: string) => {
548
- filter.addCondition({
549
- Operator: ConditionOperator.Contains,
550
- LHSField: product.Title.id,
551
- RHSValue: keyword,
552
- RHSType: RHSType.Constant,
553
- });
554
- };
555
-
556
- // Does not contain
557
- const excludeKeyword = (keyword: string) => {
558
- filter.addCondition({
559
- Operator: ConditionOperator.NotContains,
560
- LHSField: product.Description.id,
561
- RHSValue: keyword,
562
- RHSType: RHSType.Constant,
563
- });
564
- };
565
-
566
- return (
567
- <div>
568
- <input
569
- placeholder="Search..."
570
- onKeyDown={(e) => {
571
- if (e.key === "Enter") {
572
- filterByKeyword((e.target as HTMLInputElement).value);
573
- }
574
- }}
575
- />
576
- </div>
577
- );
578
- }
579
- ```
580
-
581
- ### Empty Checks (Empty, NotEmpty)
582
-
583
- Filter by presence or absence of values.
584
-
585
- ```tsx
586
- import { useMemo } from "react";
587
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
588
- import { Task } from "../bdo/Task";
589
-
590
- function EmptyFilters() {
591
- const task = useMemo(() => new Task(), []);
592
- const filter = useFilter();
593
-
594
- // Has no value
595
- const filterUnassigned = () => {
596
- filter.addCondition({
597
- Operator: ConditionOperator.Empty,
598
- LHSField: task.AssignedTo.id,
599
- RHSValue: null,
600
- RHSType: RHSType.Constant,
601
- });
602
- };
603
-
604
- // Has a value
605
- const filterAssigned = () => {
606
- filter.addCondition({
607
- Operator: ConditionOperator.NotEmpty,
608
- LHSField: task.AssignedTo.id,
609
- RHSValue: null,
610
- RHSType: RHSType.Constant,
611
- });
612
- };
613
-
614
- return (
615
- <div>
616
- <button onClick={filterUnassigned}>Unassigned Tasks</button>
617
- <button onClick={filterAssigned}>Assigned Tasks</button>
618
- </div>
619
- );
620
- }
8
+ import { useFilter, isCondition, isConditionGroup, ConditionOperator, GroupOperator, FilterValueSource } from "@ram_28/kf-ai-sdk/filter";
9
+ import type { UseFilterOptionsType, UseFilterReturnType, ConditionType, ConditionGroupType, FilterType } from "@ram_28/kf-ai-sdk/filter/types";
621
10
  ```
622
11
 
623
12
  ---
624
13
 
625
- ## Filtering Reference & User Fields
626
-
627
- Reference and User fields are stored as JSON objects (`{ _id, _name, ... }`). The backend automatically targets the `_id` sub-field when filtering these fields. User and Reference fields behave identically for filtering.
628
-
629
- ### Supported Operators
630
-
631
- | Operator | Supported | Notes |
632
- | --- | --- | --- |
633
- | EQ, NE | Yes | Compares against `_id` by default |
634
- | IN, NIN | Yes | List of IDs |
635
- | Empty, NotEmpty | Yes | Checks if field is null/unset |
636
- | Contains, NotContains | Only with dot notation | e.g., `"Vendor._name"` |
637
- | GT, GTE, LT, LTE, Between | No | Raises backend error |
638
-
639
- ### Filter by ID (Default)
640
-
641
- When filtering a Reference or User field, pass the `_id` string directly as `RHSValue`. The backend automatically compares against the `_id` sub-field.
642
-
643
- ```tsx
644
- import { useMemo } from "react";
645
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
646
- import { AdminCartItem } from "../bdo/admin/CartItem";
647
-
648
- function ReferenceFilter() {
649
- const cartItem = useMemo(() => new AdminCartItem(), []);
650
- const filter = useFilter();
651
-
652
- // Filter cart items by a specific product ID
653
- const filterByProduct = (productId: string) => {
654
- filter.addCondition({
655
- Operator: ConditionOperator.EQ,
656
- LHSField: cartItem.ProductInfo.id,
657
- RHSValue: productId,
658
- RHSType: RHSType.Constant,
659
- });
660
- };
661
-
662
- // Filter by multiple product IDs
663
- const filterByProducts = (productIds: string[]) => {
664
- filter.addCondition({
665
- Operator: ConditionOperator.IN,
666
- LHSField: cartItem.ProductInfo.id,
667
- RHSValue: productIds,
668
- RHSType: RHSType.Constant,
669
- });
670
- };
14
+ ## Common Mistakes (READ FIRST)
671
15
 
672
- // Filter unassigned items (reference is empty)
673
- const filterUnlinked = () => {
674
- filter.addCondition({
675
- Operator: ConditionOperator.Empty,
676
- LHSField: cartItem.ProductInfo.id,
677
- RHSValue: null,
678
- RHSType: RHSType.Constant,
679
- });
680
- };
681
-
682
- return (
683
- <div>
684
- <button onClick={() => filterByProduct("PROD_001")}>
685
- Filter by Product
686
- </button>
687
- <button onClick={() => filterByProducts(["PROD_001", "PROD_002"])}>
688
- Filter by Multiple
689
- </button>
690
- <button onClick={filterUnlinked}>Unlinked Items</button>
691
- </div>
692
- );
693
- }
694
- ```
16
+ ### 1. Importing `RHSType` instead of `FilterValueSource`
695
17
 
696
- ### Filter by Name (Dot Notation)
18
+ There is NO named export `RHSType`. Import `FilterValueSource`. The JSON key IS called `RHSType`, but the value comes from `FilterValueSource`.
697
19
 
698
- Use dot notation in `LHSField` to filter on a nested sub-field like `_name`. This skips the default `_id` targeting and compares against the specified path.
699
-
700
- ```tsx
701
- import { useMemo } from "react";
702
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
703
- import { AdminCartItem } from "../bdo/admin/CartItem";
704
-
705
- function ReferenceNameFilter() {
706
- const cartItem = useMemo(() => new AdminCartItem(), []);
707
- const filter = useFilter();
708
-
709
- // Search product name within the reference field
710
- const searchProductName = (name: string) => {
711
- filter.addCondition({
712
- Operator: ConditionOperator.Contains,
713
- LHSField: `${cartItem.ProductInfo.id}._name`,
714
- RHSValue: name,
715
- RHSType: RHSType.Constant,
716
- });
717
- };
20
+ ```typescript
21
+ // ❌ WRONG — RHSType is not an importable enum
22
+ import { RHSType } from "@ram_28/kf-ai-sdk/filter";
718
23
 
719
- return (
720
- <input
721
- placeholder="Search by product name..."
722
- onKeyDown={(e) => {
723
- if (e.key === "Enter") {
724
- searchProductName((e.target as HTMLInputElement).value);
725
- }
726
- }}
727
- />
728
- );
729
- }
24
+ // ✅ CORRECT
25
+ import { FilterValueSource } from "@ram_28/kf-ai-sdk/filter";
26
+ { RHSType: FilterValueSource.Constant }
730
27
  ```
731
28
 
732
- ### Filter System User Fields
29
+ ### 2. Wrong field name casing in ConditionType
733
30
 
734
- System fields `_created_by` and `_modified_by` are User fields. Filter them the same way.
31
+ `ConditionType` uses **PascalCase**: `Operator`, `LHSField`, `RHSValue`, `RHSType`. NOT camelCase.
735
32
 
736
- ```tsx
737
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
738
-
739
- function UserFieldFilter() {
740
- const filter = useFilter();
741
-
742
- // Filter by creator (using user's _id)
743
- const filterByCreator = (userId: string) => {
744
- filter.addCondition({
745
- Operator: ConditionOperator.EQ,
746
- LHSField: "_created_by",
747
- RHSValue: userId,
748
- RHSType: RHSType.Constant,
749
- });
750
- };
751
-
752
- // Search by creator name
753
- const searchByCreatorName = (name: string) => {
754
- filter.addCondition({
755
- Operator: ConditionOperator.Contains,
756
- LHSField: "_created_by._name",
757
- RHSValue: name,
758
- RHSType: RHSType.Constant,
759
- });
760
- };
33
+ ```typescript
34
+ // WRONG camelCase
35
+ { operator: "EQ", lhsField: "status", rhsValue: "Active" }
761
36
 
762
- return (
763
- <div>
764
- <button onClick={() => filterByCreator("USR_001")}>My Items</button>
765
- <input
766
- placeholder="Search by creator..."
767
- onKeyDown={(e) => {
768
- if (e.key === "Enter") {
769
- searchByCreatorName((e.target as HTMLInputElement).value);
770
- }
771
- }}
772
- />
773
- </div>
774
- );
775
- }
37
+ // ✅ CORRECT — PascalCase
38
+ { Operator: ConditionOperator.EQ, LHSField: bdo.status.id, RHSValue: "Active", RHSType: FilterValueSource.Constant }
776
39
  ```
777
40
 
778
- ### RHSValue Formats
779
-
780
- The backend accepts multiple `RHSValue` formats for Reference/User fields:
781
-
782
- | Format | Example | When to use |
783
- | --- | --- | --- |
784
- | String ID | `"PROD_001"` | Simplest — use when you have the ID |
785
- | Object with `_id` | `{ _id: "PROD_001", _name: "Widget" }` | Backend extracts `_id` automatically |
786
- | Array of IDs | `["PROD_001", "PROD_002"]` | With `IN` / `NIN` operators |
787
- | Array of objects | `[{ _id: "PROD_001" }, { _id: "PROD_002" }]` | With `IN` / `NIN` — backend extracts each `_id` |
788
-
789
- > **Tip:** Prefer passing the string ID directly. Passing the full object works but adds unnecessary payload size.
790
-
791
- ---
792
-
793
- ## Condition Groups
794
-
795
- ### AND Logic
796
-
797
- All conditions must match.
798
-
799
- ```tsx
800
- import { useMemo } from "react";
801
- import { useFilter, ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
802
- import { BuyerProduct } from "../bdo/buyer/Product";
803
-
804
- function AndFilter() {
805
- const product = useMemo(() => new BuyerProduct(), []);
806
- const filter = useFilter({
807
- operator: GroupOperator.And,
808
- });
41
+ ### 3. Mixing hook init format with API format
809
42
 
810
- const applyFilters = () => {
811
- filter.clearAllConditions();
43
+ Hook init (`UseFilterOptionsType`) uses lowercase `conditions`/`operator`. API format (`FilterType`/`ConditionType`) uses uppercase `Condition`/`Operator`.
812
44
 
813
- // Category AND Price AND InStock - all must be true
814
- filter.addCondition({
815
- Operator: ConditionOperator.EQ,
816
- LHSField: product.Category.id,
817
- RHSValue: "Electronics",
818
- RHSType: RHSType.Constant,
819
- });
820
- filter.addCondition({
821
- Operator: ConditionOperator.LTE,
822
- LHSField: product.Price.id,
823
- RHSValue: 500,
824
- RHSType: RHSType.Constant,
825
- });
826
- filter.addCondition({
827
- Operator: ConditionOperator.GT,
828
- LHSField: product.Stock.id,
829
- RHSValue: 0,
830
- RHSType: RHSType.Constant,
831
- });
832
- };
45
+ ```typescript
46
+ // Hook initialization — lowercase
47
+ useTable({ initialState: { filter: { conditions: [...], operator: "And" } } });
833
48
 
834
- return (
835
- <div>
836
- <button onClick={applyFilters}>Electronics under $500, In Stock</button>
837
- <p>Root operator: {filter.operator}</p>
838
- </div>
839
- );
840
- }
49
+ // API calls — uppercase
50
+ bdo.list({ Filter: { Operator: "And", Condition: [...] } });
841
51
  ```
842
52
 
843
- ### OR Logic
53
+ ### 4. Hardcoded operator strings
844
54
 
845
- Any condition can match.
55
+ ALWAYS use `ConditionOperator` constants. NEVER write `"Equals"`, `"equals"`, or `"eq"`.
846
56
 
847
- ```tsx
848
- import { useMemo } from "react";
849
- import { useFilter, ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
850
- import { Task } from "../bdo/Task";
851
-
852
- function OrFilter() {
853
- const task = useMemo(() => new Task(), []);
854
- const filter = useFilter({
855
- operator: GroupOperator.Or,
856
- });
857
-
858
- const applyFilters = () => {
859
- filter.clearAllConditions();
860
-
861
- // High priority OR Overdue - either can match
862
- filter.addCondition({
863
- Operator: ConditionOperator.EQ,
864
- LHSField: task.Priority.id,
865
- RHSValue: "High",
866
- RHSType: RHSType.Constant,
867
- });
868
- filter.addCondition({
869
- Operator: ConditionOperator.LT,
870
- LHSField: task.DueDate.id,
871
- RHSValue: new Date().toISOString(),
872
- RHSType: RHSType.Constant,
873
- });
874
- };
57
+ ```typescript
58
+ // WRONG
59
+ { Operator: "Equals" } { Operator: "equals" } { Operator: "eq" }
875
60
 
876
- return <button onClick={applyFilters}>Urgent Tasks</button>;
877
- }
61
+ // CORRECT
62
+ { Operator: ConditionOperator.EQ } // "EQ"
63
+ { Operator: ConditionOperator.Contains } // "Contains"
878
64
  ```
879
65
 
880
- ### Nested Groups
881
-
882
- Combine AND and OR logic.
883
-
884
- ```tsx
885
- import { useMemo } from "react";
886
- import { useFilter, ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
887
- import { BuyerProduct } from "../bdo/buyer/Product";
888
-
889
- function NestedFilter() {
890
- const product = useMemo(() => new BuyerProduct(), []);
891
- const filter = useFilter({
892
- operator: GroupOperator.And,
893
- });
894
-
895
- // Build: Category = "Electronics" AND (Price < 100 OR OnSale = true)
896
- const applyComplexFilter = () => {
897
- filter.clearAllConditions();
898
-
899
- // Root level condition
900
- filter.addCondition({
901
- Operator: ConditionOperator.EQ,
902
- LHSField: product.Category.id,
903
- RHSValue: "Electronics",
904
- RHSType: RHSType.Constant,
905
- });
906
-
907
- // Create nested OR group
908
- const orGroupId = filter.addConditionGroup(GroupOperator.Or);
66
+ ### 5. Accessing `filter.conditions` instead of `filter.items`
909
67
 
910
- // Add conditions to the OR group
911
- filter.addCondition(
912
- {
913
- Operator: ConditionOperator.LT,
914
- LHSField: product.Price.id,
915
- RHSValue: 100,
916
- RHSType: RHSType.Constant,
917
- },
918
- orGroupId,
919
- );
920
-
921
- filter.addCondition(
922
- {
923
- Operator: ConditionOperator.EQ,
924
- LHSField: product.OnSale.id,
925
- RHSValue: true,
926
- RHSType: RHSType.Constant,
927
- },
928
- orGroupId,
929
- );
930
- };
68
+ ```typescript
69
+ // ❌ WRONG — conditions does NOT exist
70
+ filter.conditions.length
931
71
 
932
- return <button onClick={applyComplexFilter}>Affordable Electronics</button>;
933
- }
72
+ // CORRECT
73
+ filter.items.length
74
+ filter.hasConditions // boolean shortcut
934
75
  ```
935
76
 
936
- ### Deep Nesting
937
-
938
- Create multiple levels of nested groups.
77
+ ### 6. Using `as const` on Operator values
939
78
 
940
- ```tsx
941
- import { useFilter, ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
942
-
943
- function DeepNestedFilter() {
944
- const filter = useFilter();
945
-
946
- // Build: (A AND B) OR (C AND D)
947
- // Generic example - for real usage, use bdo.field.id for LHSField
948
- const buildFilter = () => {
949
- filter.clearAllConditions();
950
- filter.setRootOperator(GroupOperator.Or);
951
-
952
- // First AND group
953
- const group1 = filter.addConditionGroup(GroupOperator.And);
954
- filter.addCondition(
955
- { Operator: ConditionOperator.EQ, LHSField: "Type", RHSValue: "A", RHSType: RHSType.Constant },
956
- group1,
957
- );
958
- filter.addCondition(
959
- { Operator: ConditionOperator.GT, LHSField: "Value", RHSValue: 10, RHSType: RHSType.Constant },
960
- group1,
961
- );
962
-
963
- // Second AND group
964
- const group2 = filter.addConditionGroup(GroupOperator.And);
965
- filter.addCondition(
966
- { Operator: ConditionOperator.EQ, LHSField: "Type", RHSValue: "B", RHSType: RHSType.Constant },
967
- group2,
968
- );
969
- filter.addCondition(
970
- { Operator: ConditionOperator.LT, LHSField: "Value", RHSValue: 5, RHSType: RHSType.Constant },
971
- group2,
972
- );
973
- };
79
+ ```typescript
80
+ // WRONG causes narrowing errors in arrays
81
+ [{ Operator: ConditionOperator.EQ as const, ... }]
974
82
 
975
- return <button onClick={buildFilter}>Apply Complex Filter</button>;
976
- }
83
+ // CORRECT type the array
84
+ const conditions: Omit<ConditionType, "id">[] = [{ Operator: ConditionOperator.EQ, ... }];
977
85
  ```
978
86
 
979
87
  ---
980
88
 
981
- ## Displaying Filters
982
-
983
- ### Render Active Filters
984
-
985
- Show current filter conditions with remove buttons.
986
-
987
- ```tsx
988
- import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
989
-
990
- function ActiveFilters() {
991
- const filter = useFilter();
992
-
993
- const renderItem = (item: ConditionType | ConditionGroupType) => {
994
- if (isCondition(item)) {
995
- return (
996
- <span key={item.id} className="filter-tag">
997
- {item.LHSField} {item.Operator} {String(item.RHSValue)}
998
- <button onClick={() => filter.removeCondition(item.id!)}>x</button>
999
- </span>
1000
- );
1001
- }
1002
-
1003
- if (isConditionGroup(item)) {
1004
- return (
1005
- <span key={item.id} className="filter-group">
1006
- {item.Operator} ({item.Condition.length} conditions)
1007
- <button onClick={() => filter.removeCondition(item.id!)}>x</button>
1008
- </span>
1009
- );
1010
- }
1011
-
1012
- return null;
1013
- };
1014
-
1015
- return (
1016
- <div className="active-filters">
1017
- {filter.items.map(renderItem)}
1018
- {filter.hasConditions && (
1019
- <button onClick={filter.clearAllConditions}>Clear All</button>
1020
- )}
1021
- </div>
1022
- );
1023
- }
1024
- ```
1025
-
1026
- ### Recursive Tree Display
1027
-
1028
- Display nested filter structure as a tree.
1029
-
1030
- ```tsx
1031
- function FilterTree() {
1032
- const filter = useFilter();
1033
-
1034
- const renderNode = (item: ConditionType | ConditionGroupType, depth = 0) => {
1035
- const indent = { marginLeft: depth * 20 };
1036
-
1037
- if (isCondition(item)) {
1038
- return (
1039
- <div key={item.id} style={indent} className="filter-condition">
1040
- {item.LHSField} {item.Operator} {JSON.stringify(item.RHSValue)}
1041
- </div>
1042
- );
1043
- }
89
+ ## Constants
1044
90
 
1045
- if (isConditionGroup(item)) {
1046
- return (
1047
- <div key={item.id} style={indent} className="filter-group">
1048
- <strong>{item.Operator}</strong>
1049
- {item.Condition.map((child) => renderNode(child, depth + 1))}
1050
- </div>
1051
- );
1052
- }
91
+ ```typescript
92
+ // Condition operators
93
+ ConditionOperator.EQ ConditionOperator.NE // Equal, Not Equal
94
+ ConditionOperator.GT ConditionOperator.GTE // Greater Than (or Equal)
95
+ ConditionOperator.LT ConditionOperator.LTE // Less Than (or Equal)
96
+ ConditionOperator.Contains ConditionOperator.NotContains // String contains
97
+ ConditionOperator.IN ConditionOperator.NIN // Value in/not-in list
98
+ ConditionOperator.Empty ConditionOperator.NotEmpty // Null/empty check
99
+ ConditionOperator.Between ConditionOperator.NotBetween // Range
1053
100
 
1054
- return null;
1055
- };
101
+ // Group operators
102
+ GroupOperator.And GroupOperator.Or GroupOperator.Not
1056
103
 
1057
- return (
1058
- <div className="filter-tree">
1059
- <div className="root">
1060
- <strong>Root: {filter.operator}</strong>
1061
- </div>
1062
- {filter.items.map((item) => renderNode(item, 1))}
1063
- </div>
1064
- );
1065
- }
104
+ // RHS value source (KEY is "RHSType", VALUE from FilterValueSource)
105
+ FilterValueSource.Constant // "Constant" — literal value
106
+ FilterValueSource.BDOField // "BDOField" — compare against another field
107
+ FilterValueSource.AppVariable // "AppVariable" — app variable
1066
108
  ```
1067
109
 
1068
110
  ---
1069
111
 
1070
- ## Modifying Filters
1071
-
1072
- ### Update a Condition
1073
-
1074
- Change an existing condition's values.
1075
-
1076
- ```tsx
1077
- import { useMemo, useState } from "react";
1078
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1079
- import type { ConditionOperatorType } from "@ram_28/kf-ai-sdk/filter/types";
1080
- import { BuyerProduct } from "../bdo/buyer/Product";
1081
-
1082
- function EditableFilter() {
1083
- const product = useMemo(() => new BuyerProduct(), []);
1084
- const filter = useFilter();
1085
- const [conditionId, setConditionId] = useState<string | null>(null);
1086
-
1087
- const addFilter = () => {
1088
- const id = filter.addCondition({
1089
- Operator: ConditionOperator.GT,
1090
- LHSField: product.Price.id,
1091
- RHSValue: 50,
1092
- RHSType: RHSType.Constant,
1093
- });
1094
- setConditionId(id);
1095
- };
1096
-
1097
- const updateValue = (newValue: number) => {
1098
- if (conditionId) {
1099
- filter.updateCondition(conditionId, { RHSValue: newValue });
1100
- }
1101
- };
1102
-
1103
- const updateOperator = (operator: ConditionOperatorType) => {
1104
- if (conditionId) {
1105
- filter.updateCondition(conditionId, { Operator: operator });
1106
- }
1107
- };
112
+ ## Type Definitions
1108
113
 
1109
- return (
1110
- <div>
1111
- <button onClick={addFilter}>Add Price Filter</button>
1112
- {conditionId && (
1113
- <div>
1114
- <select
1115
- onChange={(e) =>
1116
- updateOperator(e.target.value as ConditionOperatorType)
1117
- }
1118
- >
1119
- <option value={ConditionOperator.GT}>Greater Than</option>
1120
- <option value={ConditionOperator.LT}>Less Than</option>
1121
- <option value={ConditionOperator.EQ}>Equals</option>
1122
- </select>
1123
- <input
1124
- type="number"
1125
- defaultValue={50}
1126
- onChange={(e) => updateValue(Number(e.target.value))}
1127
- />
1128
- </div>
1129
- )}
1130
- </div>
1131
- );
114
+ ```typescript
115
+ interface ConditionType<T = any> {
116
+ id?: string; // Auto-generated
117
+ Operator: ConditionOperatorType; // PascalCase
118
+ LHSField: keyof T | string;
119
+ RHSValue: any;
120
+ RHSType?: "Constant" | "BDOField" | "AppVariable";
1132
121
  }
1133
- ```
1134
122
 
1135
- ### Change Group Operator
1136
-
1137
- Toggle between AND/OR for a group.
1138
-
1139
- ```tsx
1140
- import { useState } from "react";
1141
- import { useFilter, ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1142
- import type { ConditionGroupType } from "@ram_28/kf-ai-sdk/filter/types";
1143
-
1144
- function ToggleableGroupOperator() {
1145
- const filter = useFilter();
1146
- const [groupId, setGroupId] = useState<string | null>(null);
1147
-
1148
- // Generic example - for real usage, use bdo.field.id for LHSField
1149
- const createGroup = () => {
1150
- const id = filter.addConditionGroup(GroupOperator.And);
1151
- filter.addCondition(
1152
- { Operator: ConditionOperator.EQ, LHSField: "FieldA", RHSValue: 1, RHSType: RHSType.Constant },
1153
- id,
1154
- );
1155
- filter.addCondition(
1156
- { Operator: ConditionOperator.EQ, LHSField: "FieldB", RHSValue: 2, RHSType: RHSType.Constant },
1157
- id,
1158
- );
1159
- setGroupId(id);
1160
- };
1161
-
1162
- const toggleOperator = () => {
1163
- if (groupId) {
1164
- const group = filter.getCondition(groupId) as ConditionGroupType;
1165
- const newOp = group.Operator === GroupOperator.And ? GroupOperator.Or : GroupOperator.And;
1166
- filter.updateGroupOperator(groupId, newOp);
1167
- }
1168
- };
1169
-
1170
- return (
1171
- <div>
1172
- <button onClick={createGroup}>Create Group</button>
1173
- {groupId && <button onClick={toggleOperator}>Toggle AND/OR</button>}
1174
- </div>
1175
- );
123
+ interface ConditionGroupType<T = any> {
124
+ id?: string;
125
+ Operator: "And" | "Or" | "Not";
126
+ Condition: Array<ConditionType<T> | ConditionGroupType<T>>;
1176
127
  }
1177
- ```
1178
-
1179
- ---
1180
-
1181
- ## Using Filter Payload
1182
128
 
1183
- ### Get API-Ready Payload
1184
-
1185
- Access the filter structure for API calls.
1186
-
1187
- ```tsx
1188
- import { useMemo } from "react";
1189
- import { useFilter, ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1190
- import { BuyerProduct } from "../bdo/buyer/Product";
1191
-
1192
- function FilterWithApi() {
1193
- const product = useMemo(() => new BuyerProduct(), []);
1194
- const filter = useFilter();
1195
-
1196
- const fetchFiltered = async () => {
1197
- const payload = filter.payload;
1198
-
1199
- if (!payload) {
1200
- console.log("No filters applied");
1201
- return;
1202
- }
1203
-
1204
- // Payload is ready for API - no internal IDs included
1205
- const response = await fetch("/api/products", {
1206
- method: "POST",
1207
- headers: { "Content-Type": "application/json" },
1208
- body: JSON.stringify({ Filter: payload }),
1209
- });
1210
-
1211
- return response.json();
1212
- };
1213
-
1214
- return (
1215
- <div>
1216
- <button
1217
- onClick={() =>
1218
- filter.addCondition({
1219
- Operator: ConditionOperator.EQ,
1220
- LHSField: product.Status.id,
1221
- RHSValue: "Active",
1222
- RHSType: RHSType.Constant,
1223
- })
1224
- }
1225
- >
1226
- Add Filter
1227
- </button>
1228
- <button onClick={fetchFiltered}>Fetch Data</button>
1229
-
1230
- <pre>{JSON.stringify(filter.payload, null, 2)}</pre>
1231
- </div>
1232
- );
129
+ interface UseFilterOptionsType<T = any> { // lowercase — hook init
130
+ conditions?: Array<ConditionType<T> | ConditionGroupType<T>>;
131
+ operator?: "And" | "Or" | "Not";
1233
132
  }
1234
- ```
1235
-
1236
- ### Initialize with Existing Filters
1237
-
1238
- Load filters from saved state.
1239
-
1240
- ```tsx
1241
- import { useMemo } from "react";
1242
- import { useFilter, ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1243
- import type { ConditionType, ConditionGroupType } from "@ram_28/kf-ai-sdk/filter/types";
1244
- import { BuyerProduct } from "../bdo/buyer/Product";
1245
133
 
1246
- function FilterWithInitialState() {
1247
- const product = useMemo(() => new BuyerProduct(), []);
1248
-
1249
- const savedFilters: Array<ConditionType | ConditionGroupType> = [
1250
- {
1251
- Operator: ConditionOperator.EQ,
1252
- LHSField: product.Status.id,
1253
- RHSValue: "Active",
1254
- RHSType: RHSType.Constant,
1255
- },
1256
- {
1257
- Operator: ConditionOperator.GT,
1258
- LHSField: product.Price.id,
1259
- RHSValue: 100,
1260
- RHSType: RHSType.Constant,
1261
- },
1262
- ];
1263
-
1264
- const filter = useFilter({
1265
- conditions: savedFilters,
1266
- operator: GroupOperator.And,
1267
- });
1268
-
1269
- return (
1270
- <div>
1271
- <p>Loaded {filter.items.length} filters</p>
1272
- {/* filter UI */}
1273
- </div>
1274
- );
134
+ interface UseFilterReturnType<T = any> {
135
+ operator: "And" | "Or" | "Not";
136
+ items: Array<ConditionType<T> | ConditionGroupType<T>>;
137
+ payload: FilterType<T> | undefined; // API-ready, undefined if empty
138
+ hasConditions: boolean;
139
+ addCondition: (condition: Omit<ConditionType<T>, "id">, parentId?: string) => string;
140
+ addConditionGroup: (operator: "And" | "Or" | "Not", parentId?: string) => string;
141
+ updateCondition: (id: string, updates: Partial<Omit<ConditionType<T>, "id">>) => void;
142
+ removeCondition: (id: string) => void;
143
+ clearAllConditions: () => void;
144
+ setRootOperator: (op: "And" | "Or" | "Not") => void;
1275
145
  }
1276
146
  ```
1277
147
 
1278
148
  ---
1279
149
 
1280
- ## Payload Structure
1281
-
1282
- The `filter.payload` property returns the filter structure ready for API consumption. Here are examples of what the JSON looks like for different scenarios.
1283
-
1284
- ### Single Condition
1285
-
1286
- ```tsx
1287
- import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1288
-
1289
- filter.addCondition({
1290
- Operator: ConditionOperator.EQ,
1291
- LHSField: product.Status.id,
1292
- RHSValue: "Active",
1293
- RHSType: RHSType.Constant,
1294
- });
1295
- ```
1296
-
1297
- Produces:
1298
-
1299
- ```json
1300
- {
1301
- "Operator": "And",
1302
- "Condition": [
1303
- {
1304
- "Operator": "EQ",
1305
- "LHSField": "Status",
1306
- "RHSValue": "Active",
1307
- "RHSType": "Constant"
1308
- }
1309
- ]
1310
- }
1311
- ```
1312
-
1313
- ### Multiple Conditions (AND)
150
+ ## Usage Example
1314
151
 
1315
152
  ```tsx
1316
- import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1317
-
1318
- filter.addCondition({
1319
- Operator: ConditionOperator.EQ,
1320
- LHSField: product.Category.id,
1321
- RHSValue: "Electronics",
1322
- RHSType: RHSType.Constant,
1323
- });
1324
- filter.addCondition({
1325
- Operator: ConditionOperator.GT,
1326
- LHSField: product.Price.id,
1327
- RHSValue: 100,
1328
- RHSType: RHSType.Constant,
1329
- });
1330
- filter.addCondition({
1331
- Operator: ConditionOperator.LTE,
1332
- LHSField: product.Stock.id,
1333
- RHSValue: 50,
1334
- RHSType: RHSType.Constant,
1335
- });
1336
- ```
153
+ import { useFilter, ConditionOperator, GroupOperator, FilterValueSource } from "@ram_28/kf-ai-sdk/filter";
154
+ import type { UseFilterOptionsType } from "@ram_28/kf-ai-sdk/filter/types";
1337
155
 
1338
- Produces:
156
+ // Standalone filter
157
+ const filter = useFilter<AdminProductFieldType>();
158
+ filter.addCondition({ Operator: ConditionOperator.EQ, LHSField: bdo.status.id, RHSValue: "Active", RHSType: FilterValueSource.Constant });
159
+ filter.addCondition({ Operator: ConditionOperator.GT, LHSField: bdo.unit_price.id, RHSValue: 100, RHSType: FilterValueSource.Constant });
1339
160
 
1340
- ```json
1341
- {
1342
- "Operator": "And",
1343
- "Condition": [
1344
- {
1345
- "Operator": "EQ",
1346
- "LHSField": "Category",
1347
- "RHSValue": "Electronics",
1348
- "RHSType": "Constant"
1349
- },
1350
- {
1351
- "Operator": "GT",
1352
- "LHSField": "Price",
1353
- "RHSValue": 100,
1354
- "RHSType": "Constant"
161
+ // With useTable initialState
162
+ const table = useTable<AdminProductFieldType>({
163
+ source: bdo.meta._id,
164
+ columns,
165
+ initialState: {
166
+ filter: {
167
+ conditions: [
168
+ { Operator: ConditionOperator.EQ, LHSField: "_created_by", RHSValue: user._id, RHSType: FilterValueSource.Constant },
169
+ ],
170
+ operator: GroupOperator.And,
1355
171
  },
1356
- {
1357
- "Operator": "LTE",
1358
- "LHSField": "Stock",
1359
- "RHSValue": 50,
1360
- "RHSType": "Constant"
1361
- }
1362
- ]
1363
- }
1364
- ```
1365
-
1366
- ### Nested Groups
1367
-
1368
- ```tsx
1369
- import { ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1370
-
1371
- // Root: AND
1372
- // Category = "Electronics" AND (Price < 100 OR OnSale = true)
1373
- filter.addCondition({
1374
- Operator: ConditionOperator.EQ,
1375
- LHSField: product.Category.id,
1376
- RHSValue: "Electronics",
1377
- RHSType: RHSType.Constant,
1378
- });
1379
- const orGroupId = filter.addConditionGroup(GroupOperator.Or);
1380
- filter.addCondition(
1381
- {
1382
- Operator: ConditionOperator.LT,
1383
- LHSField: product.Price.id,
1384
- RHSValue: 100,
1385
- RHSType: RHSType.Constant,
1386
- },
1387
- orGroupId,
1388
- );
1389
- filter.addCondition(
1390
- {
1391
- Operator: ConditionOperator.EQ,
1392
- LHSField: product.OnSale.id,
1393
- RHSValue: true,
1394
- RHSType: RHSType.Constant,
1395
172
  },
1396
- orGroupId,
1397
- );
1398
- ```
1399
-
1400
- Produces:
1401
-
1402
- ```json
1403
- {
1404
- "Operator": "And",
1405
- "Condition": [
1406
- {
1407
- "Operator": "EQ",
1408
- "LHSField": "Category",
1409
- "RHSValue": "Electronics",
1410
- "RHSType": "Constant"
1411
- },
1412
- {
1413
- "Operator": "Or",
1414
- "Condition": [
1415
- {
1416
- "Operator": "LT",
1417
- "LHSField": "Price",
1418
- "RHSValue": 100,
1419
- "RHSType": "Constant"
1420
- },
1421
- {
1422
- "Operator": "EQ",
1423
- "LHSField": "OnSale",
1424
- "RHSValue": true,
1425
- "RHSType": "Constant"
1426
- }
1427
- ]
1428
- }
1429
- ]
1430
- }
1431
- ```
1432
-
1433
- ### Range Values (Between)
1434
-
1435
- ```tsx
1436
- import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1437
-
1438
- filter.addCondition({
1439
- Operator: ConditionOperator.Between,
1440
- LHSField: product.Price.id,
1441
- RHSValue: [50, 200],
1442
- RHSType: RHSType.Constant,
1443
- });
1444
- ```
1445
-
1446
- Produces:
1447
-
1448
- ```json
1449
- {
1450
- "Operator": "And",
1451
- "Condition": [
1452
- {
1453
- "Operator": "Between",
1454
- "LHSField": "Price",
1455
- "RHSValue": [50, 200],
1456
- "RHSType": "Constant"
1457
- }
1458
- ]
1459
- }
1460
- ```
1461
-
1462
- ### List Values (IN)
1463
-
1464
- ```tsx
1465
- import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1466
-
1467
- filter.addCondition({
1468
- Operator: ConditionOperator.IN,
1469
- LHSField: product.Category.id,
1470
- RHSValue: ["Electronics", "Computers", "Accessories"],
1471
- RHSType: RHSType.Constant,
1472
173
  });
1473
- ```
1474
-
1475
- Produces:
1476
-
1477
- ```json
1478
- {
1479
- "Operator": "And",
1480
- "Condition": [
1481
- {
1482
- "Operator": "IN",
1483
- "LHSField": "Category",
1484
- "RHSValue": ["Electronics", "Computers", "Accessories"],
1485
- "RHSType": "Constant"
1486
- }
1487
- ]
1488
- }
1489
- ```
1490
-
1491
- > **Note:** The `RHSType` field defaults to `"Constant"` and is automatically added to the payload. Other possible values are `"BDOField"` (reference another BDO field) and `"AppVariable"` (reference an application variable).
1492
-
1493
- ### Reference Field (EQ)
1494
-
1495
- ```tsx
1496
- import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
1497
-
1498
- filter.addCondition({
1499
- Operator: ConditionOperator.EQ,
1500
- LHSField: cartItem.ProductInfo.id,
1501
- RHSValue: "PROD_001",
1502
- RHSType: RHSType.Constant,
1503
- });
1504
- ```
1505
-
1506
- Produces:
1507
-
1508
- ```json
1509
- {
1510
- "Operator": "And",
1511
- "Condition": [
1512
- {
1513
- "Operator": "EQ",
1514
- "LHSField": "ProductInfo",
1515
- "RHSValue": "PROD_001",
1516
- "RHSType": "Constant"
1517
- }
1518
- ]
1519
- }
1520
- ```
1521
-
1522
- ### Reference Field Nested Path (Contains)
1523
-
1524
- ```tsx
1525
- filter.addCondition({
1526
- Operator: ConditionOperator.Contains,
1527
- LHSField: `${cartItem.ProductInfo.id}._name`,
1528
- RHSValue: "Widget",
1529
- RHSType: RHSType.Constant,
1530
- });
1531
- ```
1532
174
 
1533
- Produces:
1534
-
1535
- ```json
1536
- {
1537
- "Operator": "And",
1538
- "Condition": [
1539
- {
1540
- "Operator": "Contains",
1541
- "LHSField": "ProductInfo._name",
1542
- "RHSValue": "Widget",
1543
- "RHSType": "Constant"
1544
- }
1545
- ]
1546
- }
1547
- ```
1548
-
1549
- ### Field-to-Field Comparison (BDOField)
1550
-
1551
- Use `RHSType.BDOField` to compare one field against another field instead of a constant value.
1552
-
1553
- ```tsx
1554
- // Filter where Quantity exceeds Stock (field vs field)
1555
- filter.addCondition({
1556
- Operator: ConditionOperator.GT,
1557
- LHSField: cartItem.Quantity.id,
1558
- RHSValue: cartItem.Stock.id,
1559
- RHSType: RHSType.BDOField,
1560
- });
1561
- ```
1562
-
1563
- Produces:
1564
-
1565
- ```json
1566
- {
1567
- "Operator": "And",
1568
- "Condition": [
1569
- {
1570
- "Operator": "GT",
1571
- "LHSField": "Quantity",
1572
- "RHSValue": "Stock",
1573
- "RHSType": "BDOField"
1574
- }
1575
- ]
1576
- }
1577
- ```
1578
-
1579
- ---
1580
-
1581
- ## Common Mistakes
1582
-
1583
- ### 1. Inventing RHSType values
1584
-
1585
- Only three RHSType values exist. Any other string causes TS2322.
1586
-
1587
- ```typescript
1588
- // ❌ WRONG — these values don't exist
1589
- { RHSType: "Value" }
1590
- { RHSType: "Literal" }
1591
- { RHSType: "Field" }
1592
-
1593
- // ✅ CORRECT — only these three values
1594
- { RHSType: RHSType.Constant } // "Constant"
1595
- { RHSType: RHSType.BDOField } // "BDOField"
1596
- { RHSType: RHSType.AppVariable } // "AppVariable"
1597
- ```
1598
-
1599
- ### 2. Accessing `filter.conditions` instead of `filter.items`
1600
-
1601
- `UseFilterReturnType` does NOT have a `conditions` property. The conditions array is called `items`.
1602
-
1603
- ```typescript
1604
- // ❌ WRONG — conditions does NOT exist on UseFilterReturnType
1605
- filter.conditions
1606
- filter.conditions.length
1607
-
1608
- // ✅ CORRECT — use items
1609
- filter.items
1610
- filter.items.length
1611
- filter.hasConditions // boolean shortcut
1612
- ```
1613
-
1614
- ### 3. Mixing hook init format with API format
1615
-
1616
- `UseFilterOptionsType` (for hook initialization) uses lowercase. `FilterType` (for API calls) uses uppercase. NEVER mix them.
1617
-
1618
- ```typescript
1619
- // Hook initialization (UseFilterOptionsType) — lowercase, plural
1620
- useTable({
1621
- initialState: {
1622
- filter: { conditions: [...], operator: "And" }
175
+ // Dynamic filter with useEffect
176
+ useEffect(() => {
177
+ table.filter.clearAllConditions();
178
+ if (status !== "all") {
179
+ table.filter.addCondition({ Operator: ConditionOperator.EQ, LHSField: bdo.status.id, RHSValue: status, RHSType: FilterValueSource.Constant });
1623
180
  }
1624
- });
1625
-
1626
- // API calls (FilterType) — uppercase, singular
1627
- bdo.list({
1628
- Filter: { Operator: "And", Condition: [...] }
1629
- });
1630
- ```
1631
-
1632
- ### 4. Using `as const` on Operator values
1633
-
1634
- ```typescript
1635
- // ❌ WRONG — as const causes TypeScript narrowing errors in arrays
1636
- const conditions = [
1637
- { Operator: ConditionOperator.EQ as const, ... }
1638
- ];
1639
-
1640
- // ✅ CORRECT — type the array instead
1641
- const conditions: Omit<ConditionType, "id">[] = [
1642
- { Operator: ConditionOperator.EQ, ... }
1643
- ];
1644
- ```
1645
-
1646
- ### 5. Filtering Reference/User fields without _id
181
+ }, [status]);
1647
182
 
1648
- The backend automatically targets `_id` when you filter a Reference or User field by name. Do NOT manually append `._id` unless you specifically want to bypass the backend's auto-extraction.
1649
-
1650
- ```typescript
1651
- // WRONG unnecessary, and disables auto-extraction of _id from objects
1652
- { LHSField: "ProductInfo._id", Operator: "EQ", RHSValue: { _id: "P1", _name: "Widget" } }
1653
-
1654
- // ✅ CORRECT — backend auto-targets _id
1655
- { LHSField: "ProductInfo", Operator: "EQ", RHSValue: "P1" }
1656
-
1657
- // ✅ CORRECT — backend extracts _id from the object
1658
- { LHSField: "ProductInfo", Operator: "EQ", RHSValue: { _id: "P1", _name: "Widget" } }
1659
-
1660
- // ✅ CORRECT — dot notation for filtering by _name
1661
- { LHSField: "ProductInfo._name", Operator: "Contains", RHSValue: "Widget" }
183
+ // Nested: Category = "Electronics" AND (Price < 100 OR OnSale = true)
184
+ filter.addCondition({ Operator: ConditionOperator.EQ, LHSField: bdo.category.id, RHSValue: "Electronics", RHSType: FilterValueSource.Constant });
185
+ const orGroupId = filter.addConditionGroup(GroupOperator.Or);
186
+ filter.addCondition({ Operator: ConditionOperator.LT, LHSField: bdo.price.id, RHSValue: 100, RHSType: FilterValueSource.Constant }, orGroupId);
187
+ filter.addCondition({ Operator: ConditionOperator.EQ, LHSField: bdo.on_sale.id, RHSValue: true, RHSType: FilterValueSource.Constant }, orGroupId);
1662
188
  ```