@ram_28/kf-ai-sdk 2.0.16 → 2.0.18

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 (105) hide show
  1. package/README.md +16 -8
  2. package/dist/{FileField-BWrSHNRq.js → FileField-CZjS2uLh.js} +3 -3
  3. package/dist/{FileField-eDeuzln8.cjs → FileField-DU4UWo_t.cjs} +1 -1
  4. package/dist/api.cjs +1 -1
  5. package/dist/api.mjs +1 -1
  6. package/dist/auth/authConfig.d.ts +1 -1
  7. package/dist/auth/types.d.ts +1 -1
  8. package/dist/auth/types.d.ts.map +1 -1
  9. package/dist/auth.cjs +1 -1
  10. package/dist/auth.mjs +1 -1
  11. package/dist/bdo/core/Item.d.ts +0 -4
  12. package/dist/bdo/core/Item.d.ts.map +1 -1
  13. package/dist/bdo/fields/ReferenceField.d.ts +1 -1
  14. package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
  15. package/dist/bdo/fields/SelectField.d.ts +1 -1
  16. package/dist/bdo/fields/SelectField.d.ts.map +1 -1
  17. package/dist/bdo/fields/UserField.d.ts +1 -1
  18. package/dist/bdo/fields/UserField.d.ts.map +1 -1
  19. package/dist/bdo.cjs +1 -1
  20. package/dist/bdo.mjs +53 -62
  21. package/dist/components/hooks/useActivityForm/types.d.ts +4 -5
  22. package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -1
  23. package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -1
  24. package/dist/components/hooks/useActivityTable/types.d.ts +5 -4
  25. package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -1
  26. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -1
  27. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts +2 -3
  28. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts.map +1 -1
  29. package/dist/components/hooks/useBDOTable/types.d.ts +20 -12
  30. package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -1
  31. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +2 -2
  32. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -1
  33. package/dist/{constants-ConHc1oS.js → constants-Cyi942Yr.js} +5 -5
  34. package/dist/constants-DEmYwKfC.cjs +1 -0
  35. package/dist/filter.cjs +1 -1
  36. package/dist/filter.mjs +1 -1
  37. package/dist/form.cjs +1 -1
  38. package/dist/form.mjs +226 -243
  39. package/dist/table.cjs +1 -1
  40. package/dist/table.mjs +15 -16
  41. package/dist/table.types.d.ts +1 -1
  42. package/dist/table.types.d.ts.map +1 -1
  43. package/dist/types/constants.d.ts +1 -1
  44. package/dist/workflow/Activity.d.ts +8 -5
  45. package/dist/workflow/Activity.d.ts.map +1 -1
  46. package/dist/workflow.cjs +1 -1
  47. package/dist/workflow.mjs +461 -476
  48. package/docs/README.md +57 -0
  49. package/docs/bdo/README.md +161 -0
  50. package/docs/bdo/api_reference.md +281 -0
  51. package/docs/examples/bdo/create-product.md +69 -0
  52. package/docs/examples/bdo/edit-product-dialog.md +95 -0
  53. package/docs/examples/bdo/filtered-product-table.md +100 -0
  54. package/docs/examples/bdo/product-listing.md +73 -0
  55. package/docs/examples/bdo/supplier-dropdown.md +60 -0
  56. package/docs/examples/fields/complex-fields.md +248 -0
  57. package/docs/examples/fields/primitive-fields.md +217 -0
  58. package/docs/examples/workflow/approve-leave-request.md +76 -0
  59. package/docs/examples/workflow/filtered-activity-table.md +101 -0
  60. package/docs/examples/workflow/my-pending-requests.md +90 -0
  61. package/docs/examples/workflow/start-new-workflow.md +47 -0
  62. package/docs/examples/workflow/submit-leave-request.md +72 -0
  63. package/docs/examples/workflow/workflow-progress.md +49 -0
  64. package/docs/fields/README.md +141 -0
  65. package/docs/fields/api_reference.md +134 -0
  66. package/docs/useActivityForm/README.md +244 -0
  67. package/docs/useActivityForm/api_reference.md +279 -0
  68. package/docs/useActivityTable/README.md +263 -0
  69. package/docs/useActivityTable/api_reference.md +294 -0
  70. package/docs/useBDOForm/README.md +175 -0
  71. package/docs/useBDOForm/api_reference.md +244 -0
  72. package/docs/useBDOTable/README.md +242 -0
  73. package/docs/useBDOTable/api_reference.md +253 -0
  74. package/docs/useFilter/README.md +323 -0
  75. package/docs/useFilter/api_reference.md +228 -0
  76. package/docs/workflow/README.md +158 -0
  77. package/docs/workflow/api_reference.md +161 -0
  78. package/package.json +1 -1
  79. package/sdk/auth/authConfig.ts +1 -1
  80. package/sdk/auth/types.ts +1 -1
  81. package/sdk/bdo/core/Item.ts +1 -10
  82. package/sdk/bdo/fields/ReferenceField.ts +1 -1
  83. package/sdk/bdo/fields/SelectField.ts +1 -1
  84. package/sdk/bdo/fields/UserField.ts +1 -1
  85. package/sdk/components/hooks/useActivityForm/types.ts +4 -6
  86. package/sdk/components/hooks/useActivityForm/useActivityForm.ts +10 -73
  87. package/sdk/components/hooks/useActivityTable/types.ts +4 -5
  88. package/sdk/components/hooks/useActivityTable/useActivityTable.ts +10 -8
  89. package/sdk/components/hooks/useBDOForm/createItemProxy.ts +17 -58
  90. package/sdk/components/hooks/useBDOTable/types.ts +20 -10
  91. package/sdk/components/hooks/useBDOTable/useBDOTable.ts +12 -8
  92. package/sdk/table.types.ts +2 -0
  93. package/sdk/types/constants.ts +1 -1
  94. package/sdk/workflow/Activity.ts +39 -7
  95. package/dist/constants-QX2RX-wu.cjs +0 -1
  96. package/docs/api.md +0 -95
  97. package/docs/bdo.md +0 -224
  98. package/docs/gaps.md +0 -360
  99. package/docs/useActivityForm.md +0 -393
  100. package/docs/useActivityTable.md +0 -418
  101. package/docs/useBDOForm.md +0 -376
  102. package/docs/useBDOTable.md +0 -284
  103. package/docs/useFilter.md +0 -188
  104. package/docs/workflow.md +0 -560
  105. /package/docs/{useAuth.md → useAuth/README.md} +0 -0
@@ -0,0 +1,253 @@
1
+ # useBDOTable API Reference
2
+
3
+ ```tsx
4
+ import { useBDOTable } from '@ram_28/kf-ai-sdk/table';
5
+ import type {
6
+ UseBDOTableOptionsType,
7
+ UseBDOTableReturnType,
8
+ PaginationStateType,
9
+ } from '@ram_28/kf-ai-sdk/table/types';
10
+ ```
11
+
12
+ ## Signature
13
+
14
+ ```tsx
15
+ const table: UseBDOTableReturnType<ProductBdo> = useBDOTable(options: UseBDOTableOptionsType<ProductBdo>);
16
+ ```
17
+
18
+ ## Options
19
+
20
+ `UseBDOTableOptionsType<B>`
21
+
22
+ - `bdo: B`
23
+ - **Required**
24
+ - BDO instance with `list()` and `count()` methods. Must be memoized with `useMemo()`.
25
+ - `initialState`
26
+ - **Optional**
27
+ - Object with:
28
+ - `sort?: SortType` — Initial sort configuration. Format: `[{ "fieldName": "ASC" }]`
29
+ - `pagination?: PaginationStateType` — Initial page and page size. Defaults to `{ pageNo: 1, pageSize: 10 }`.
30
+ - `filter?: UseFilterOptionsType<T>` — Initial filter conditions and operator.
31
+ - `onError?: (error: Error) => void`
32
+ - **Optional**
33
+ - Called when a list or count fetch fails.
34
+ - `onSuccess?: (data: T[]) => void`
35
+ - **Optional**
36
+ - Called with the current page rows after a successful list fetch.
37
+
38
+ ## Return Value
39
+
40
+ `UseBDOTableReturnType<B>`
41
+
42
+ Alias for `UseTableReturnType<BDORowType<B>>`. `BDORowType<B>` resolves to `ItemType<TEditable, TReadonly>` — inferred from the BDO's `list()` return type. Rows are proxy objects with `.get()` accessors, not plain objects.
43
+
44
+ ### Data
45
+
46
+ - `rows: ItemType<TEditable, TReadonly>[]`
47
+ - Array of `ItemType` instances for the current page. Access field values via `.get()` (e.g. `row.Title.get()`), not direct property access. Empty array during initial loading.
48
+ - `totalItems: number`
49
+ - Total number of records matching the current filters and search. `0` while count is loading.
50
+
51
+ ### Loading & Error
52
+
53
+ - `isLoading: boolean`
54
+ - `true` during the initial load of both list and count queries.
55
+ - `isFetching: boolean`
56
+ - `true` during any fetch including background refetches for list or count.
57
+ - `error: Error | null`
58
+ - Most recent fetch error from either list or count query. `null` when no error.
59
+
60
+ ### Search
61
+
62
+ - `search.query: string`
63
+ - Current search input value (updated immediately on `set()`).
64
+ - `search.field: keyof T | null`
65
+ - The field currently being searched. `null` when no search is active.
66
+ - `search.set(field: keyof T, query: string): void`
67
+ - Set the search field and query. The input value updates immediately; the API query is debounced by 300 ms. Pagination resets to page 1 after the debounce. Query is capped at 255 characters.
68
+ - `search.clear(): void`
69
+ - Clear the search field and query. Pagination resets to page 1.
70
+
71
+ ### Sort
72
+
73
+ - `sort.field: keyof T | null`
74
+ - Currently sorted field. `null` when no sort is active.
75
+ - `sort.direction: "ASC" | "DESC" | null`
76
+ - Current sort direction. `null` when no sort is active.
77
+ - `sort.toggle(field: keyof T): void`
78
+ - Cycle the sort for a field: ASC -> DESC -> cleared. If a different field was sorted, starts at ASC.
79
+ - `sort.clear(): void`
80
+ - Remove all sorting.
81
+ - `sort.set(field: keyof T | null, direction: "ASC" | "DESC" | null): void`
82
+ - Explicitly set the sort field and direction.
83
+
84
+ ### Filter
85
+
86
+ - `filter: UseFilterReturnType<T>`
87
+ - Full filter state and operations. See [useFilter reference](../useFilter/api_reference.md) for the complete API.
88
+
89
+ ### Pagination
90
+
91
+ - `pagination.pageNo: number`
92
+ - Current page number (1-indexed).
93
+ - `pagination.pageSize: number`
94
+ - Current number of items per page.
95
+ - `pagination.totalPages: number`
96
+ - Total number of pages based on `totalItems` and `pageSize`.
97
+ - `pagination.totalItems: number`
98
+ - Same as the top-level `totalItems`.
99
+ - `pagination.canGoNext: boolean`
100
+ - `true` when a next page exists.
101
+ - `pagination.canGoPrevious: boolean`
102
+ - `true` when a previous page exists (i.e. `pageNo > 1`).
103
+ - `pagination.goToNext(): void`
104
+ - Navigate to the next page. No-op if already on the last page.
105
+ - `pagination.goToPrevious(): void`
106
+ - Navigate to the previous page. No-op if already on page 1.
107
+ - `pagination.goToPage(page: number): void`
108
+ - Navigate to a specific page. Clamped to `[1, totalPages]`.
109
+ - `pagination.setPageSize(size: number): void`
110
+ - Change the page size. Resets to page 1.
111
+
112
+ ### Operations
113
+
114
+ - `refetch(): Promise<ListResponseType<T>>`
115
+ - Manually refetch both the list and count queries. Returns the list response.
116
+
117
+ ## Exported Constants
118
+
119
+ Available from `@ram_28/kf-ai-sdk/table`:
120
+
121
+ ```typescript
122
+ import { SortDirection, TableDefaults } from '@ram_28/kf-ai-sdk/table';
123
+ ```
124
+
125
+ **SortDirection**
126
+
127
+ | Key | Value |
128
+ |-----|-------|
129
+ | `ASC` | `"ASC"` |
130
+ | `DESC` | `"DESC"` |
131
+
132
+ **TableDefaults**
133
+
134
+ | Key | Value | Description |
135
+ |-----|-------|-------------|
136
+ | `SEARCH_DEBOUNCE_MS` | `300` | Debounce delay for search (ms) |
137
+ | `PAGE_SIZE` | `10` | Default items per page |
138
+ | `PAGE` | `1` | Default page number (1-indexed) |
139
+ | `SEARCH_MAX_LENGTH` | `255` | Maximum search query length |
140
+
141
+ For filter constants (`ConditionOperator`, `GroupOperator`, `RHSType`), see [useFilter reference](../useFilter/api_reference.md).
142
+
143
+ ## Types
144
+
145
+ ```typescript
146
+ import type { ItemType } from '@ram_28/kf-ai-sdk/bdo/types';
147
+
148
+ // ============================================================
149
+ // Options
150
+ // ============================================================
151
+
152
+ interface UseBDOTableOptionsType<B extends BDOTableSourceType> {
153
+ bdo: B;
154
+ initialState?: {
155
+ sort?: SortType;
156
+ pagination?: PaginationStateType;
157
+ filter?: UseFilterOptionsType<BDORowType<B>>;
158
+ };
159
+ onError?: (error: Error) => void;
160
+ onSuccess?: (data: BDORowType<B>[]) => void;
161
+ }
162
+
163
+ // ============================================================
164
+ // Return type
165
+ // ============================================================
166
+
167
+ type UseBDOTableReturnType<B extends BDOTableSourceType> =
168
+ UseTableReturnType<BDORowType<B>>;
169
+
170
+ // When used via UseBDOTableReturnType<B>, T = ItemType<TEditable, TReadonly>
171
+ // ItemType<TEditable, TReadonly> provides:
172
+ // row._id: string (direct access, no .get())
173
+ // row.Field.get(): value (read field value)
174
+ // row.Field.set(value): void (editable fields only)
175
+ // row.Field.validate(): ValidationResultType (single field)
176
+ // row.Field.label / .required / .readOnly / .defaultValue / .meta
177
+ // row.toJSON(): plain object
178
+ // row.validate(): ValidationResultType (all fields)
179
+ interface UseTableReturnType<T> {
180
+ // Data
181
+ rows: T[];
182
+ totalItems: number;
183
+
184
+ // Loading States
185
+ isLoading: boolean;
186
+ isFetching: boolean;
187
+
188
+ // Error Handling
189
+ error: Error | null;
190
+
191
+ // Search
192
+ search: {
193
+ query: string;
194
+ field: keyof T | null;
195
+ set: (field: keyof T, query: string) => void;
196
+ clear: () => void;
197
+ };
198
+
199
+ // Sorting
200
+ sort: {
201
+ field: keyof T | null;
202
+ direction: 'ASC' | 'DESC' | null;
203
+ toggle: (field: keyof T) => void;
204
+ clear: () => void;
205
+ set: (field: keyof T | null, direction: 'ASC' | 'DESC' | null) => void;
206
+ };
207
+
208
+ // Filter
209
+ filter: UseFilterReturnType<T>;
210
+
211
+ // Pagination
212
+ pagination: {
213
+ pageNo: number;
214
+ pageSize: number;
215
+ totalPages: number;
216
+ totalItems: number;
217
+ canGoNext: boolean;
218
+ canGoPrevious: boolean;
219
+ goToNext: () => void;
220
+ goToPrevious: () => void;
221
+ goToPage: (page: number) => void;
222
+ setPageSize: (size: number) => void;
223
+ };
224
+
225
+ // Operations
226
+ refetch: () => Promise<ListResponseType<T>>;
227
+ }
228
+
229
+ // ============================================================
230
+ // State types
231
+ // ============================================================
232
+
233
+ interface PaginationStateType {
234
+ pageNo: number;
235
+ pageSize: number;
236
+ }
237
+
238
+ // ============================================================
239
+ // Sort
240
+ // ============================================================
241
+
242
+ type SortOptionType = Record<string, 'ASC' | 'DESC'>;
243
+ type SortType = SortOptionType[];
244
+
245
+ // Filter types — see useFilter reference (../useFilter/api_reference.md)
246
+
247
+ // ============================================================
248
+ // Row type inference
249
+ // ============================================================
250
+
251
+ type BDORowType<B extends BDOTableSourceType> =
252
+ B extends { list(opts?: any): Promise<(infer R)[]> } ? R : never;
253
+ ```
@@ -0,0 +1,323 @@
1
+ # useFilter
2
+
3
+ Standalone filter state manager for building composable filter conditions with nested groups.
4
+
5
+ ## When to Use
6
+
7
+ **Use `useFilter` when:**
8
+
9
+ - Building a standalone filter panel or sidebar outside of a table
10
+ - You need a composable condition tree with nested AND/OR/NOT groups
11
+ - Managing filter state that feeds into a custom data fetch
12
+
13
+ **Use something else when:**
14
+
15
+ - Filtering a BDO table — use [`useBDOTable`](../useBDOTable/README.md) (includes useFilter at `table.filter`)
16
+ - Filtering an activity table — use [`useActivityTable`](../useActivityTable/README.md) (includes useFilter at `table.filter`)
17
+
18
+ ## Imports
19
+
20
+ ```tsx
21
+ import { useFilter, ConditionOperator, GroupOperator, FilterValueSource } from "@ram_28/kf-ai-sdk/filter";
22
+ import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```tsx
28
+ import { useMemo } from "react";
29
+ import { useFilter, ConditionOperator, FilterValueSource } from "@ram_28/kf-ai-sdk/filter";
30
+ import { AdminProduct } from "@/bdo/admin/Product";
31
+ import type { AdminProductFieldType } from "@/bdo/admin/Product";
32
+
33
+ function ProductFilter() {
34
+ const bdo = useMemo(() => new AdminProduct(), []);
35
+ const filter = useFilter<AdminProductFieldType>();
36
+
37
+ const applyStatusFilter = (status: string) => {
38
+ filter.clearAllConditions();
39
+ if (status !== "all") {
40
+ filter.addCondition({
41
+ Operator: ConditionOperator.EQ,
42
+ LHSField: bdo.status.id,
43
+ RHSValue: status,
44
+ RHSType: FilterValueSource.Constant,
45
+ });
46
+ }
47
+ };
48
+
49
+ return (
50
+ <div>
51
+ <button onClick={() => applyStatusFilter("Active")}>Active</button>
52
+ <button onClick={() => applyStatusFilter("all")}>All</button>
53
+ <p>{filter.hasConditions ? "Filtering active" : "No filters"}</p>
54
+ {filter.payload && <pre>{JSON.stringify(filter.payload, null, 2)}</pre>}
55
+ </div>
56
+ );
57
+ }
58
+ ```
59
+
60
+ ## Usage Guide
61
+
62
+ ### Adding Conditions
63
+
64
+ Add leaf conditions with `addCondition()`. Each condition uses PascalCase properties: `Operator`, `LHSField`, `RHSValue`, and optionally `RHSType`. Always use constants instead of string literals.
65
+
66
+ ```tsx
67
+ import { ConditionOperator, FilterValueSource } from "@ram_28/kf-ai-sdk/filter";
68
+
69
+ // Exact match
70
+ filter.addCondition({
71
+ Operator: ConditionOperator.EQ,
72
+ LHSField: bdo.category.id,
73
+ RHSValue: "Electronics",
74
+ RHSType: FilterValueSource.Constant,
75
+ });
76
+
77
+ // Range filter
78
+ filter.addCondition({
79
+ Operator: ConditionOperator.Between,
80
+ LHSField: bdo.unit_price.id,
81
+ RHSValue: [100, 500],
82
+ RHSType: FilterValueSource.Constant,
83
+ });
84
+
85
+ // String search
86
+ filter.addCondition({
87
+ Operator: ConditionOperator.Contains,
88
+ LHSField: bdo.description.id,
89
+ RHSValue: "wireless",
90
+ RHSType: FilterValueSource.Constant,
91
+ });
92
+ ```
93
+
94
+ ### Tracking and Removing by ID
95
+
96
+ `addCondition()` returns an auto-generated ID string. Store the ID to later remove or look up the condition. IDs are internal state-management artifacts -- they are stripped from `payload` automatically.
97
+
98
+ ```tsx
99
+ const [statusId, setStatusId] = useState<string | null>(null);
100
+
101
+ const applyStatusFilter = (status: string) => {
102
+ // Remove existing status condition if present
103
+ if (statusId) {
104
+ filter.removeCondition(statusId);
105
+ setStatusId(null);
106
+ }
107
+
108
+ // Add new condition and store its ID
109
+ if (status !== "all") {
110
+ const id = filter.addCondition({
111
+ Operator: ConditionOperator.EQ,
112
+ LHSField: bdo.status.id,
113
+ RHSValue: status,
114
+ RHSType: FilterValueSource.Constant,
115
+ });
116
+ setStatusId(id);
117
+ }
118
+ };
119
+ ```
120
+
121
+ ### Updating Conditions
122
+
123
+ Update a leaf condition's properties without removing and re-adding it. Use `updateCondition()` for leaf conditions and `updateGroupOperator()` for groups:
124
+
125
+ ```tsx
126
+ // Change the comparison value on an existing condition
127
+ filter.updateCondition(conditionId, {
128
+ RHSValue: "Books",
129
+ });
130
+
131
+ // Change a group's operator from And to Or
132
+ filter.updateGroupOperator(groupId, GroupOperator.Or);
133
+ ```
134
+
135
+ ### Nested Groups
136
+
137
+ Build arbitrarily deep condition trees using `addConditionGroup()`. Pass a `parentId` to nest groups inside other groups, and pass a `parentId` to `addCondition()` to add leaf conditions inside a group.
138
+
139
+ This example constructs: `Status = "Active" AND (Category = "Electronics" AND Price < 500) OR (Category = "Books" AND Price < 50)`:
140
+
141
+ ```tsx
142
+ import { useFilter, ConditionOperator, GroupOperator, FilterValueSource } from "@ram_28/kf-ai-sdk/filter";
143
+
144
+ const filter = useFilter<AdminProductFieldType>();
145
+
146
+ // Root-level condition
147
+ filter.addCondition({
148
+ Operator: ConditionOperator.EQ,
149
+ LHSField: bdo.status.id,
150
+ RHSValue: "Active",
151
+ RHSType: FilterValueSource.Constant,
152
+ });
153
+
154
+ // Create an OR group at root level -- capture its ID
155
+ const orGroupId = filter.addConditionGroup(GroupOperator.Or);
156
+
157
+ // Nest an AND group inside the OR group for Electronics
158
+ const electronicsGroupId = filter.addConditionGroup(GroupOperator.And, orGroupId);
159
+ filter.addCondition(
160
+ {
161
+ Operator: ConditionOperator.EQ,
162
+ LHSField: bdo.category.id,
163
+ RHSValue: "Electronics",
164
+ RHSType: FilterValueSource.Constant,
165
+ },
166
+ electronicsGroupId
167
+ );
168
+ filter.addCondition(
169
+ {
170
+ Operator: ConditionOperator.LT,
171
+ LHSField: bdo.unit_price.id,
172
+ RHSValue: 500,
173
+ RHSType: FilterValueSource.Constant,
174
+ },
175
+ electronicsGroupId
176
+ );
177
+
178
+ // Nest another AND group inside the OR group for Books
179
+ const booksGroupId = filter.addConditionGroup(GroupOperator.And, orGroupId);
180
+ filter.addCondition(
181
+ {
182
+ Operator: ConditionOperator.EQ,
183
+ LHSField: bdo.category.id,
184
+ RHSValue: "Books",
185
+ RHSType: FilterValueSource.Constant,
186
+ },
187
+ booksGroupId
188
+ );
189
+ filter.addCondition(
190
+ {
191
+ Operator: ConditionOperator.LT,
192
+ LHSField: bdo.unit_price.id,
193
+ RHSValue: 50,
194
+ RHSType: FilterValueSource.Constant,
195
+ },
196
+ booksGroupId
197
+ );
198
+
199
+ // Resulting payload:
200
+ // {
201
+ // Operator: "And",
202
+ // Condition: [
203
+ // { Operator: "EQ", LHSField: "status", RHSValue: "Active", RHSType: "Constant" },
204
+ // {
205
+ // Operator: "Or",
206
+ // Condition: [
207
+ // {
208
+ // Operator: "And",
209
+ // Condition: [
210
+ // { Operator: "EQ", LHSField: "category", RHSValue: "Electronics", RHSType: "Constant" },
211
+ // { Operator: "LT", LHSField: "unit_price", RHSValue: 500, RHSType: "Constant" },
212
+ // ],
213
+ // },
214
+ // {
215
+ // Operator: "And",
216
+ // Condition: [
217
+ // { Operator: "EQ", LHSField: "category", RHSValue: "Books", RHSType: "Constant" },
218
+ // { Operator: "LT", LHSField: "unit_price", RHSValue: 50, RHSType: "Constant" },
219
+ // ],
220
+ // },
221
+ // ],
222
+ // },
223
+ // ],
224
+ // }
225
+ ```
226
+
227
+ ### Root Operator
228
+
229
+ The root operator combines all top-level conditions. It defaults to `"And"` and can be set via the `operator` option or changed later with `setRootOperator()`:
230
+
231
+ ```tsx
232
+ // Set at initialization
233
+ const filter = useFilter<AdminProductFieldType>({
234
+ operator: "Or",
235
+ });
236
+
237
+ // Toggle between And and Or
238
+ const toggleOperator = () => {
239
+ const next = filter.operator === GroupOperator.And ? GroupOperator.Or : GroupOperator.And;
240
+ filter.setRootOperator(next);
241
+ };
242
+ ```
243
+
244
+ ### Reading State
245
+
246
+ - **`items`** -- current conditions with IDs populated (use for UI rendering)
247
+ - **`payload`** -- API-ready `FilterType` with IDs stripped. `undefined` when no conditions exist.
248
+ - **`hasConditions`** -- convenience boolean (`items.length > 0`)
249
+ - **`operator`** -- current root operator (`"And"`, `"Or"`, or `"Not"`)
250
+
251
+ Guard with a falsy check before sending the payload:
252
+
253
+ ```tsx
254
+ if (filter.payload) {
255
+ const results = await bdo.list({ Filter: filter.payload });
256
+ }
257
+ ```
258
+
259
+ ### Integration with Table Hooks
260
+
261
+ `useBDOTable` and `useActivityTable` embed a `useFilter` instance at `table.filter`. The API is identical -- `table.filter.addCondition()`, `table.filter.clearAllConditions()`, etc.
262
+
263
+ ```tsx
264
+ const table = useBDOTable({
265
+ bdo: product,
266
+ initialState: {
267
+ filter: {
268
+ conditions: [
269
+ { Operator: ConditionOperator.EQ, LHSField: "status", RHSValue: "Active" },
270
+ ],
271
+ operator: "And",
272
+ },
273
+ },
274
+ });
275
+
276
+ // Same API as standalone useFilter
277
+ table.filter.addCondition({ ... });
278
+ table.filter.clearAllConditions();
279
+ ```
280
+
281
+ - Filter changes automatically reset table pagination to page 1.
282
+ - See [`useBDOTable`](../useBDOTable/README.md) and [`useActivityTable`](../useActivityTable/README.md) for full table documentation.
283
+
284
+ ### Iterating with Type Guards
285
+
286
+ Use `isCondition()` and `isConditionGroup()` to distinguish between leaf conditions and groups when rendering the filter tree:
287
+
288
+ ```tsx
289
+ import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
290
+
291
+ filter.items.map((item) => {
292
+ if (isCondition(item)) {
293
+ return (
294
+ <li key={item.id}>
295
+ {String(item.LHSField)} {item.Operator} {String(item.RHSValue)}
296
+ <button onClick={() => filter.removeCondition(item.id!)}>Remove</button>
297
+ </li>
298
+ );
299
+ }
300
+ if (isConditionGroup(item)) {
301
+ return (
302
+ <li key={item.id}>
303
+ {item.Operator} group ({item.Condition.length} children)
304
+ </li>
305
+ );
306
+ }
307
+ return null;
308
+ });
309
+ ```
310
+
311
+ ## Further Reading
312
+
313
+ - [API Reference](./api_reference.md) -- All options, return values, constants, and type definitions
314
+ - [Filtered Product Table](../examples/bdo/filtered-product-table.md) -- BDO table with category + price range filters
315
+ - [Filtered Activity Table](../examples/workflow/filtered-activity-table.md) -- Activity table with date range filter
316
+
317
+ ## Common Mistakes
318
+
319
+ - **Don't read `filter.conditions`** -- the property is `filter.items`.
320
+ - **Don't expect `payload` to be an empty object when empty** -- it's `undefined`. Use a simple falsy check: `if (filter.payload) { ... }`.
321
+ - **Don't use string literals** -- use `ConditionOperator.EQ`, `GroupOperator.And`, `FilterValueSource.Constant` instead.
322
+ - **Don't expect IDs in `payload`** -- they're stripped automatically before the payload is built.
323
+ - **Don't mix PascalCase and camelCase** -- condition properties are PascalCase (`Operator`, `LHSField`, `RHSValue`, `RHSType`), while hook options are camelCase (`conditions`, `operator`).