@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,279 @@
1
+ # useActivityForm API Reference
2
+
3
+ ```tsx
4
+ import { useActivityForm } from "@ram_28/kf-ai-sdk/workflow";
5
+ import type {
6
+ UseActivityFormOptions,
7
+ UseActivityFormReturn,
8
+ } from "@ram_28/kf-ai-sdk/workflow";
9
+ ```
10
+
11
+ ## Signature
12
+
13
+ ```tsx
14
+ const form: UseActivityFormReturn<EmployeeInputActivity> = useActivityForm(
15
+ activity: EmployeeInputActivity,
16
+ options: UseActivityFormOptions<EmployeeInputActivity>,
17
+ );
18
+ ```
19
+
20
+ First argument is the Activity instance (positional). Second argument is the options object.
21
+
22
+ ## Options
23
+
24
+ `UseActivityFormOptions<A>`
25
+
26
+ - `activity_instance_id: string`
27
+ - **Required**
28
+ - Activity instance ID. From `workflow.start()` response or `getInProgressList()` rows.
29
+ - `defaultValues?: Partial<ExtractActivityEditable<A>>`
30
+ - **Optional** · Defaults to `{}`
31
+ - Initial form values before server data loads. Server data wins on merge.
32
+ - `mode?: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all"`
33
+ - **Optional** · Defaults to `"onBlur"`
34
+ - RHF validation mode. Also controls when per-field server sync fires.
35
+ - `enabled?: boolean`
36
+ - **Optional** · Defaults to `true`
37
+ - Set `false` to defer `activity.read()` until the instance ID is available.
38
+
39
+ ## Return Value
40
+
41
+ `UseActivityFormReturn<A>`
42
+
43
+ ### Instance
44
+
45
+ - `item: FormItemType<TEditable, TReadonly>`
46
+ - The current activity instance. Each field is an accessor with methods to read, write, and validate. See [Instance and Field Accessors](#instance-and-field-accessors).
47
+ - `activity: A`
48
+ - The Activity instance passed to the hook.
49
+
50
+ ### Form Methods
51
+
52
+ - `register: FormRegisterType<TEditable, TReadonly>`
53
+ - Registers an input field. Auto-disables readonly fields (`{ disabled: true }`).
54
+ - `handleSubmit: HandleSubmitType<CreateUpdateResponseType>`
55
+ - Validates, updates any remaining dirty fields, then completes the activity. See [handleSubmit](#handlesubmit).
56
+ - `watch: UseFormWatch<AllActivityFields<A>>`
57
+ - Standard RHF `watch`. Typed to all fields (editable + readonly).
58
+ - `setValue: UseFormSetValue<ExtractActivityEditable<A>>`
59
+ - Standard RHF `setValue`. Typed to editable fields only.
60
+ - `getValues: UseFormGetValues<AllActivityFields<A>>`
61
+ - Returns all field values.
62
+ - `reset: UseFormReset<AllActivityFields<A>>`
63
+ - Resets form to given values or defaults.
64
+ - `trigger: UseFormTrigger<AllActivityFields<A>>`
65
+ - Manually triggers validation for specific or all fields.
66
+ - `control: Control<AllActivityFields<A>>`
67
+ - RHF control for `Controller` components.
68
+
69
+ ### Form State
70
+
71
+ - `errors: FieldErrors<AllActivityFields<A>>`
72
+ - Validation errors by field name. Each entry has `type` and `message`.
73
+ - `isValid: boolean`
74
+ - `true` if all fields pass validation.
75
+ - `isDirty: boolean`
76
+ - `true` if any field has been modified.
77
+ - `isSubmitting: boolean`
78
+ - `true` during `handleSubmit` (from button click to API response).
79
+ - `isSubmitSuccessful: boolean`
80
+ - `true` after the last submission succeeded.
81
+
82
+ ### Loading & Error
83
+
84
+ - `isLoading: boolean`
85
+ - `true` during initial data + metadata loading. Guard form render on this.
86
+ - `isMetadataLoading: boolean`
87
+ - `true` while BP metadata is being fetched.
88
+ - `loadError: Error | null`
89
+ - Error from `activity.read()`.
90
+ - `hasError: boolean`
91
+ - `true` when `loadError` is non-null.
92
+
93
+ ### Other
94
+
95
+ - `bpMetadata: Record<string, unknown> | null`
96
+ - Raw BP metadata blob. `null` while loading.
97
+ - `clearErrors: () => void`
98
+ - Clear all form validation errors.
99
+
100
+ ## handleSubmit
101
+
102
+ ```typescript
103
+ type HandleSubmitType<TRead> = (
104
+ onSuccess?: (data: TRead, e?: React.BaseSyntheticEvent) => void | Promise<void>,
105
+ onError?: (error: FieldErrors | Error, e?: React.BaseSyntheticEvent) => void | Promise<void>,
106
+ ) => (e?: React.BaseSyntheticEvent) => Promise<void>;
107
+ ```
108
+
109
+ Curried function. Pass to an `onClick` handler: `onClick={handleSubmit(onSuccess, onError)}`.
110
+
111
+ **Behavior:**
112
+ 1. Validates all fields via the resolver (type + constraint).
113
+ 2. Collects dirty, non-readonly fields and sends them via `activity.update()`.
114
+ 3. Calls `activity.complete()` to advance the workflow.
115
+ 4. Invokes `onSuccess` with the complete response.
116
+
117
+ - `onSuccess(result, e?)`
118
+ - Called with `CreateUpdateResponseType` (`{ _id: string }`) after the activity is completed and the workflow advances.
119
+ - `onError(error, e?)`
120
+ - Called with `FieldErrors` if validation fails, or `Error` if the API call fails.
121
+
122
+ If no dirty fields exist, the update call is skipped but `complete()` is still called.
123
+
124
+ > Per-field sync handles auto-saving on blur/change. `handleSubmit` is for completing the activity, not saving drafts.
125
+
126
+ ## Instance and Field Accessors
127
+
128
+ `item` represents the current activity instance. Each field on `item` is an accessor object.
129
+
130
+ ### Instance Members (`item`)
131
+
132
+ - `_id: string | undefined`
133
+ - Current activity instance ID.
134
+ - `toJSON(): Partial<TEditable & TReadonly>`
135
+ - All form values as a plain object.
136
+ - `validate(): Promise<boolean>`
137
+ - Triggers validation for all fields. Returns `true` if valid.
138
+
139
+ ### Editable Field (`item.FieldName`)
140
+
141
+ - `get(): T | undefined`
142
+ - Current value from form state.
143
+ - `getOrDefault(fallback: T): T`
144
+ - Value or fallback if null/undefined.
145
+ - `set(value: T): void`
146
+ - Sets the field value.
147
+ - `validate(): ValidationResultType`
148
+ - Validates the current value. Returns `{ valid: boolean, errors: string[] }`.
149
+ - `label: string`
150
+ - Display label from field metadata.
151
+ - `required: boolean`
152
+ - Whether the field is required.
153
+ - `readOnly: boolean`
154
+ - Always `false` for editable fields.
155
+ - `defaultValue: unknown`
156
+ - Default value from metadata.
157
+ - `meta: BaseFieldMetaType`
158
+ - Raw field metadata.
159
+
160
+ ### Readonly Field (`item.FieldName`)
161
+
162
+ Same as editable except: no `set()` method, `readOnly` is always `true`.
163
+
164
+ ## Types
165
+
166
+ ```typescript
167
+ // ---- Generic extraction helpers ----
168
+
169
+ type ExtractActivityEntity<A> =
170
+ A extends Activity<infer E, any, any> ? E : never;
171
+
172
+ type ExtractActivityEditable<A> =
173
+ A extends Activity<any, infer E, any> ? E : never;
174
+
175
+ type ExtractActivityReadonly<A> =
176
+ A extends Activity<any, any, infer R> ? R : never;
177
+
178
+ type AllActivityFields<A> =
179
+ ExtractActivityEditable<A> & ExtractActivityReadonly<A>;
180
+
181
+ // ---- Hook options ----
182
+
183
+ interface UseActivityFormOptions<A extends Activity<any, any, any>> {
184
+ activity_instance_id: string;
185
+ defaultValues?: Partial<ExtractActivityEditable<A>>;
186
+ mode?: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all";
187
+ enabled?: boolean;
188
+ }
189
+
190
+ // ---- Hook return type ----
191
+
192
+ interface UseActivityFormReturn<A extends Activity<any, any, any>> {
193
+ item: FormItemType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>>;
194
+ activity: A;
195
+
196
+ register: FormRegisterType<ExtractActivityEditable<A>, ExtractActivityReadonly<A>>;
197
+ handleSubmit: HandleSubmitType<CreateUpdateResponseType>;
198
+ watch: UseFormWatch<AllActivityFields<A>>;
199
+ setValue: UseFormSetValue<ExtractActivityEditable<A>>;
200
+ getValues: UseFormGetValues<AllActivityFields<A>>;
201
+ reset: UseFormReset<AllActivityFields<A>>;
202
+ trigger: UseFormTrigger<AllActivityFields<A>>;
203
+ control: Control<AllActivityFields<A>>;
204
+
205
+ errors: FieldErrors<AllActivityFields<A>>;
206
+ isValid: boolean;
207
+ isDirty: boolean;
208
+ isSubmitting: boolean;
209
+ isSubmitSuccessful: boolean;
210
+
211
+ isLoading: boolean;
212
+ isMetadataLoading: boolean;
213
+ loadError: Error | null;
214
+ hasError: boolean;
215
+
216
+ bpMetadata: Record<string, unknown> | null;
217
+ clearErrors: () => void;
218
+ }
219
+
220
+ // ---- Shared types ----
221
+
222
+ type HandleSubmitType<TRead = unknown> = (
223
+ onSuccess?: (
224
+ data: TRead,
225
+ e?: React.BaseSyntheticEvent,
226
+ ) => void | Promise<void>,
227
+ onError?: (
228
+ error: FieldErrors | Error,
229
+ e?: React.BaseSyntheticEvent,
230
+ ) => void | Promise<void>,
231
+ ) => (e?: React.BaseSyntheticEvent) => Promise<void>;
232
+
233
+ type FormRegisterType<TEditable, TReadonly> = <
234
+ K extends keyof TEditable | keyof TReadonly | string
235
+ >(
236
+ name: K & string,
237
+ options?: RegisterOptions,
238
+ ) => K extends keyof TReadonly
239
+ ? UseFormRegisterReturn & { disabled: true }
240
+ : UseFormRegisterReturn;
241
+
242
+ type FormItemType<TEditable, TReadonly> = {
243
+ [K in keyof TEditable]: EditableFormFieldAccessorType<TEditable[K]>;
244
+ } & {
245
+ [K in keyof TReadonly]: ReadonlyFormFieldAccessorType<TReadonly[K]>;
246
+ } & {
247
+ readonly _id: string | undefined;
248
+ toJSON(): Partial<TEditable & TReadonly>;
249
+ validate(): Promise<boolean>;
250
+ };
251
+
252
+ interface EditableFormFieldAccessorType<T> {
253
+ readonly label: string;
254
+ readonly required: boolean;
255
+ readonly readOnly: false;
256
+ readonly defaultValue: unknown;
257
+ readonly meta: BaseFieldMetaType;
258
+ get(): T | undefined;
259
+ getOrDefault(fallback: T): T;
260
+ set(value: T): void;
261
+ validate(): ValidationResultType;
262
+ }
263
+
264
+ interface ReadonlyFormFieldAccessorType<T> {
265
+ readonly label: string;
266
+ readonly required: boolean;
267
+ readonly readOnly: true;
268
+ readonly defaultValue: unknown;
269
+ readonly meta: BaseFieldMetaType;
270
+ get(): T | undefined;
271
+ getOrDefault(fallback: T): T;
272
+ validate(): ValidationResultType;
273
+ }
274
+
275
+ interface ValidationResultType {
276
+ valid: boolean;
277
+ errors: string[];
278
+ }
279
+ ```
@@ -0,0 +1,263 @@
1
+ # useActivityTable
2
+
3
+ Activity-integrated table hook for listing workflow instances with search, sorting, filtering, and pagination.
4
+
5
+ ## When to Use
6
+
7
+ **Use `useActivityTable` when:**
8
+
9
+ - Displaying in-progress or completed workflow activity instances
10
+ - You need search, sort, filter, and/or pagination on activity data
11
+
12
+ **Use something else when:**
13
+
14
+ - Displaying BDO records (not workflow) — use [`useBDOTable`](../useBDOTable/README.md) instead
15
+ - Building a form for an activity instance — use [`useActivityForm`](../useActivityForm/README.md) instead
16
+
17
+ ## Imports
18
+
19
+ ```tsx
20
+ import { useActivityTable, ActivityTableStatus } from "@ram_28/kf-ai-sdk/workflow";
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```tsx
26
+ import { useMemo } from "react";
27
+ import { useActivityTable, ActivityTableStatus } from "@ram_28/kf-ai-sdk/workflow";
28
+ import { EmployeeInputActivity } from "@/bdo/workflows/SimpleLeaveProcess";
29
+
30
+ function PendingLeaveRequests() {
31
+ const activity = useMemo(() => new EmployeeInputActivity(), []);
32
+
33
+ const table = useActivityTable({
34
+ activity,
35
+ status: ActivityTableStatus.InProgress,
36
+ });
37
+
38
+ if (table.isLoading) return <p>Loading...</p>;
39
+ if (table.error) return <p>Error: {table.error.message}</p>;
40
+
41
+ return (
42
+ <div>
43
+ <ul>
44
+ {table.rows.map((row) => (
45
+ <li key={row._id}>
46
+ {row.LeaveType.get()} — {row.StartDate.get()} to {row.EndDate.get()}
47
+ </li>
48
+ ))}
49
+ </ul>
50
+
51
+ <button
52
+ onClick={table.pagination.goToPrevious}
53
+ disabled={!table.pagination.canGoPrevious}
54
+ >
55
+ Previous
56
+ </button>
57
+ <span>
58
+ Page {table.pagination.pageNo} of {table.pagination.totalPages}
59
+ </span>
60
+ <button
61
+ onClick={table.pagination.goToNext}
62
+ disabled={!table.pagination.canGoNext}
63
+ >
64
+ Next
65
+ </button>
66
+ </div>
67
+ );
68
+ }
69
+ ```
70
+
71
+ ## Usage Guide
72
+
73
+ ### Status Filter
74
+
75
+ Every `useActivityTable` call requires a `status` option that determines which items are fetched:
76
+
77
+ ```tsx
78
+ const [status, setStatus] = useState(ActivityTableStatus.InProgress);
79
+
80
+ const table = useActivityTable({
81
+ activity,
82
+ status,
83
+ });
84
+ ```
85
+
86
+ - **`ActivityTableStatus.InProgress`** — fetches the in-progress items of the current activity
87
+ - **`ActivityTableStatus.Completed`** — fetches the completed items of the current activity
88
+ - Raw values are `'inprogress'` (all lowercase, no underscore) and `'completed'`
89
+ - Always use the `ActivityTableStatus` constants — never raw strings
90
+ - Changing status re-fetches automatically (status is part of the query key)
91
+
92
+ ### Accessing Row Data
93
+
94
+ Rows are `ActivityInstanceType` proxies, not plain objects. Access field values through the `.get()` method on each field accessor:
95
+
96
+ ```tsx
97
+ table.rows.map((row) => (
98
+ <tr key={row._id}>
99
+ <td>{row.LeaveType.get()}</td>
100
+ <td>{row.StartDate.get()}</td>
101
+ <td>{row.EndDate.get()}</td>
102
+ <td>{row.LeaveDays.get()}</td>
103
+ <td>{row.Status.get()}</td>
104
+ </tr>
105
+ ));
106
+ ```
107
+
108
+ - `row._id` is a direct string (no `.get()` needed)
109
+ - `row.LeaveType.get()` returns the field's current value
110
+ - Activity system fields: `row.BPInstanceId.get()`, `row.Status.get()`, `row.AssignedTo.get()`, `row.CompletedAt.get()`
111
+ - `row.toJSON()` converts the entire row to a plain object
112
+ - Editable fields also have `.set()`, but this is typically not used in a table context
113
+ - Persistence methods available on each row: `row.update()`, `row.save()`, `row.complete()`, `row.progress()`
114
+
115
+ ### Search
116
+
117
+ Search filters results by matching a query string against a specific field. The input value updates immediately for a responsive UI, while the data fetch is debounced.
118
+
119
+ ```tsx
120
+ <input
121
+ type="text"
122
+ placeholder={`Search by ${activity.EmployeeName.label}...`}
123
+ value={table.search.query}
124
+ onChange={(e) => table.search.set(activity.EmployeeName.id, e.target.value)}
125
+ />
126
+ {table.search.query && (
127
+ <button onClick={table.search.clear}>Clear</button>
128
+ )}
129
+ {table.isFetching && <span>Searching...</span>}
130
+ ```
131
+
132
+ - **`search.set(field, query)`** — set the field and query text. The data fetch is debounced by 300ms. Pagination resets to page 1.
133
+ - **`search.clear()`** — clear the search field and query. Pagination resets to page 1.
134
+ - **`search.query`** — current query string (updates immediately, API call debounced)
135
+ - **`search.field`** — field currently being searched, or `null`
136
+ - Queries longer than 255 characters are silently ignored.
137
+
138
+ ### Sorting
139
+
140
+ Sorting is controlled through the `sort` object. Column headers typically use `toggle()` for click-to-sort behavior:
141
+
142
+ ```tsx
143
+ <th
144
+ onClick={() => table.sort.toggle(activity.StartDate.id)}
145
+ style={{ cursor: "pointer" }}
146
+ >
147
+ {activity.StartDate.label}
148
+ {table.sort.field === activity.StartDate.id &&
149
+ (table.sort.direction === "ASC" ? " ↑" : " ↓")}
150
+ </th>
151
+ ```
152
+
153
+ - **`sort.toggle(field)`** — cycles through ASC, DESC, then cleared. Toggling a different field starts at ASC.
154
+ - **`sort.set(field, direction)`** — set the sort field and direction directly. Pass `null` for both to clear.
155
+ - **`sort.clear()`** — remove sorting entirely.
156
+ - **`sort.field`** / **`sort.direction`** — current sort state, or `null` when no sort is active.
157
+ - **`initialState.sort`** format is an array of single-key objects: `[{ fieldName: "ASC" }]`.
158
+
159
+ ### Filtering
160
+
161
+ The table includes an integrated [`useFilter`](../useFilter/README.md) instance at `table.filter`. Use it to build filter conditions that narrow down the table results:
162
+
163
+ ```tsx
164
+ import { ConditionOperator } from "@ram_28/kf-ai-sdk/table";
165
+
166
+ // Filter by leave type
167
+ table.filter.addCondition({
168
+ Operator: ConditionOperator.EQ,
169
+ LHSField: activity.LeaveType.id,
170
+ RHSValue: "PTO",
171
+ RHSType: "Constant",
172
+ });
173
+
174
+ // Date range filter
175
+ table.filter.addCondition({
176
+ Operator: ConditionOperator.GTE,
177
+ LHSField: activity.StartDate.id,
178
+ RHSValue: "2026-01-01",
179
+ RHSType: "Constant",
180
+ });
181
+
182
+ // Clear all filters
183
+ table.filter.clearAllConditions();
184
+
185
+ // Check if any filters are active
186
+ if (table.filter.hasConditions) {
187
+ // ...
188
+ }
189
+ ```
190
+
191
+ - **`table.filter.addCondition(condition)`** — add a filter condition
192
+ - **`table.filter.clearAllConditions()`** — remove all filter conditions
193
+ - **`table.filter.hasConditions`** — `true` if any conditions are active
194
+ - Filter changes automatically reset pagination to page 1, preventing empty pages after narrowing results.
195
+ - See the [useFilter documentation](../useFilter/README.md) for the full filter API.
196
+
197
+ ### Pagination
198
+
199
+ Pages are 1-indexed (they start at 1, not 0). The default page is 1 and the default page size is 10.
200
+
201
+ ```tsx
202
+ <div>
203
+ <button
204
+ onClick={table.pagination.goToPrevious}
205
+ disabled={!table.pagination.canGoPrevious}
206
+ >
207
+ Previous
208
+ </button>
209
+ <span>
210
+ Page {table.pagination.pageNo} of {table.pagination.totalPages}
211
+ {" "}({table.pagination.totalItems} total)
212
+ </span>
213
+ <button
214
+ onClick={table.pagination.goToNext}
215
+ disabled={!table.pagination.canGoNext}
216
+ >
217
+ Next
218
+ </button>
219
+ </div>
220
+
221
+ <select
222
+ value={table.pagination.pageSize}
223
+ onChange={(e) => table.pagination.setPageSize(Number(e.target.value))}
224
+ >
225
+ <option value={10}>10</option>
226
+ <option value={25}>25</option>
227
+ <option value={50}>50</option>
228
+ </select>
229
+ ```
230
+
231
+ - **`pagination.goToNext()`** / **`pagination.goToPrevious()`** — navigate forward/backward. No-op at boundaries.
232
+ - **`pagination.goToPage(n)`** — jump to a specific page. Clamped to `[1, totalPages]`.
233
+ - **`pagination.canGoNext`** / **`pagination.canGoPrevious`** — whether navigation is possible.
234
+ - **`pagination.setPageSize(n)`** — change the page size. Resets to page 1.
235
+ - **`pagination.pageNo`** / **`pagination.pageSize`** / **`pagination.totalPages`** / **`pagination.totalItems`** — current pagination state.
236
+
237
+ ### Refetching
238
+
239
+ Call `refetch()` to manually refresh the table data. This refetches both the list and the count:
240
+
241
+ ```tsx
242
+ const handleComplete = async (row) => {
243
+ await row.complete();
244
+ table.refetch();
245
+ };
246
+ ```
247
+
248
+ This is the standard pattern after any mutation (complete, update, save) that should be reflected in the table. Also use `table.refetch()` after closing a form dialog that modifies an activity instance.
249
+
250
+ ## Further Reading
251
+
252
+ - [API Reference](./api_reference.md) — All options, return values, and type definitions
253
+ - [My Pending Requests](../examples/workflow/my-pending-requests.md) — In Progress / Completed tabs
254
+ - [Filtered Activity Table](../examples/workflow/filtered-activity-table.md) — Date range filter on activities
255
+
256
+ ## Common Mistakes
257
+
258
+ - **Don't forget `useMemo` on the Activity instance.** `new ActivityClass()` must be wrapped in `useMemo(() => ..., [])`. Re-creating the instance on every render causes infinite refetching.
259
+ - **Don't access row fields directly.** `row.LeaveType` is an accessor object, not the value. Use `row.LeaveType.get()` to read the value.
260
+ - **Don't use raw strings for status.** Use `ActivityTableStatus.InProgress` and `ActivityTableStatus.Completed`, not `'inProgress'` or `'in_progress'`.
261
+ - **Don't use 0-indexed pagination.** Pages start at 1. Passing `pageNo: 0` will produce incorrect results.
262
+ - **Don't forget to guard on `isLoading` before rendering rows.** While loading, `rows` is an empty array. Guard with `if (table.isLoading) return <Loading />` to avoid rendering an empty table.
263
+ - **Don't hardcode field names as strings.** Use the activity field IDs (`activity.StartDate.id`) instead of raw strings (`"StartDate"`). This keeps your code type-safe and resilient to field renames.