@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.
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +2 -2
- package/dist/attachment-constants-B5jlqoKI.cjs +1 -0
- package/dist/attachment-constants-C2UHWxmp.js +63 -0
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/bdo/core/types.d.ts +4 -0
- package/dist/bdo/core/types.d.ts.map +1 -1
- package/dist/bdo/fields/NumberField.d.ts.map +1 -1
- package/dist/bdo/fields/ReferenceField.d.ts +3 -2
- package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
- package/dist/bdo/fields/SelectField.d.ts +1 -1
- package/dist/bdo/fields/SelectField.d.ts.map +1 -1
- package/dist/bdo/fields/UserField.d.ts +5 -0
- package/dist/bdo/fields/UserField.d.ts.map +1 -1
- package/dist/bdo.cjs +1 -1
- package/dist/bdo.mjs +107 -153
- package/dist/client-DnO2KKrw.cjs +1 -0
- package/dist/{client-CMERmrC-.js → client-iQTqFDNI.js} +34 -30
- package/dist/components/hooks/useForm/createItemProxy.d.ts +4 -0
- package/dist/components/hooks/useForm/createItemProxy.d.ts.map +1 -1
- package/dist/components/hooks/useForm/createResolver.d.ts.map +1 -1
- package/dist/components/hooks/useForm/useForm.d.ts +1 -0
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +368 -203
- package/dist/{metadata-BfJtHz84.cjs → metadata-DgLSJkF5.cjs} +1 -1
- package/dist/{metadata-CwAo6a8e.js → metadata-DpfI3zRN.js} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +1 -1
- package/dist/workflow/types.d.ts +3 -2
- package/dist/workflow/types.d.ts.map +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.d.ts +0 -2
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.mjs +204 -274
- package/dist/workflow.types.d.ts +0 -1
- package/dist/workflow.types.d.ts.map +1 -1
- package/docs/api.md +45 -253
- package/docs/bdo.md +130 -711
- package/docs/useAuth.md +42 -104
- package/docs/useFilter.md +117 -1591
- package/docs/useForm.md +263 -861
- package/docs/useTable.md +255 -1096
- package/docs/workflow.md +10 -155
- package/package.json +1 -1
- package/sdk/api/client.ts +18 -4
- package/sdk/bdo/core/types.ts +1 -0
- package/sdk/bdo/fields/NumberField.ts +2 -1
- package/sdk/bdo/fields/ReferenceField.ts +4 -3
- package/sdk/bdo/fields/SelectField.ts +2 -2
- package/sdk/bdo/fields/UserField.ts +14 -0
- package/sdk/components/hooks/useForm/createItemProxy.ts +221 -4
- package/sdk/components/hooks/useForm/createResolver.ts +16 -1
- package/sdk/components/hooks/useForm/useForm.ts +151 -50
- package/sdk/workflow/types.ts +3 -2
- package/sdk/workflow.ts +0 -7
- package/sdk/workflow.types.ts +0 -7
- package/dist/client-BnVxSHAm.cjs +0 -1
- package/dist/workflow/components/useActivityTable/index.d.ts +0 -4
- package/dist/workflow/components/useActivityTable/index.d.ts.map +0 -1
- package/dist/workflow/components/useActivityTable/types.d.ts +0 -53
- package/dist/workflow/components/useActivityTable/types.d.ts.map +0 -1
- package/dist/workflow/components/useActivityTable/useActivityTable.d.ts +0 -4
- package/dist/workflow/components/useActivityTable/useActivityTable.d.ts.map +0 -1
- package/sdk/workflow/components/useActivityTable/index.ts +0 -8
- package/sdk/workflow/components/useActivityTable/types.ts +0 -67
- 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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
18
|
+
There is NO named export `RHSType`. Import `FilterValueSource`. The JSON key IS called `RHSType`, but the value comes from `FilterValueSource`.
|
|
697
19
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
###
|
|
29
|
+
### 2. Wrong field name casing in ConditionType
|
|
733
30
|
|
|
734
|
-
|
|
31
|
+
`ConditionType` uses **PascalCase**: `Operator`, `LHSField`, `RHSValue`, `RHSType`. NOT camelCase.
|
|
735
32
|
|
|
736
|
-
```
|
|
737
|
-
|
|
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
|
-
|
|
763
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
811
|
-
filter.clearAllConditions();
|
|
43
|
+
Hook init (`UseFilterOptionsType`) uses lowercase `conditions`/`operator`. API format (`FilterType`/`ConditionType`) uses uppercase `Condition`/`Operator`.
|
|
812
44
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
|
|
835
|
-
|
|
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
|
-
###
|
|
53
|
+
### 4. Hardcoded operator strings
|
|
844
54
|
|
|
845
|
-
|
|
55
|
+
ALWAYS use `ConditionOperator` constants. NEVER write `"Equals"`, `"equals"`, or `"eq"`.
|
|
846
56
|
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
|
|
877
|
-
}
|
|
61
|
+
// ✅ CORRECT
|
|
62
|
+
{ Operator: ConditionOperator.EQ } // "EQ"
|
|
63
|
+
{ Operator: ConditionOperator.Contains } // "Contains"
|
|
878
64
|
```
|
|
879
65
|
|
|
880
|
-
###
|
|
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
|
-
|
|
911
|
-
|
|
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
|
-
|
|
933
|
-
|
|
72
|
+
// ✅ CORRECT
|
|
73
|
+
filter.items.length
|
|
74
|
+
filter.hasConditions // boolean shortcut
|
|
934
75
|
```
|
|
935
76
|
|
|
936
|
-
###
|
|
937
|
-
|
|
938
|
-
Create multiple levels of nested groups.
|
|
77
|
+
### 6. Using `as const` on Operator values
|
|
939
78
|
|
|
940
|
-
```
|
|
941
|
-
|
|
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
|
-
|
|
976
|
-
}
|
|
83
|
+
// ✅ CORRECT — type the array
|
|
84
|
+
const conditions: Omit<ConditionType, "id">[] = [{ Operator: ConditionOperator.EQ, ... }];
|
|
977
85
|
```
|
|
978
86
|
|
|
979
87
|
---
|
|
980
88
|
|
|
981
|
-
##
|
|
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
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
-
|
|
1055
|
-
|
|
101
|
+
// Group operators
|
|
102
|
+
GroupOperator.And GroupOperator.Or GroupOperator.Not
|
|
1056
103
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
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
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
-
##
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1341
|
-
{
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
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
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
{
|
|
1537
|
-
|
|
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
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
{
|
|
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
|
```
|