@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,244 @@
1
+ # useBDOForm API Reference
2
+
3
+ ```tsx
4
+ import { useBDOForm } from "@ram_28/kf-ai-sdk/form";
5
+ import type {
6
+ UseBDOFormOptionsType,
7
+ UseBDOFormReturnType,
8
+ } from "@ram_28/kf-ai-sdk/form/types";
9
+ ```
10
+
11
+ ## Signature
12
+
13
+ ```tsx
14
+ const form: UseBDOFormReturnType<SellerProduct> = useBDOForm(options: UseBDOFormOptionsType<SellerProduct>);
15
+ ```
16
+
17
+ ## Options
18
+
19
+ `UseBDOFormOptionsType<B>`
20
+
21
+ - `bdo: B extends BaseBdo<any, any, any>`
22
+ - **Required**
23
+ - BDO instance. Must be memoized with `useMemo()`.
24
+ - `recordId: string | undefined`
25
+ - **Optional** · Defaults to `undefined`
26
+ - Record ID to edit. When present, the hook fetches the record and enters update mode. When absent, enters create mode.
27
+
28
+ ## Return Value
29
+
30
+ `UseBDOFormReturnType<B>`
31
+
32
+ ### Instance
33
+
34
+ - `item: FormItemType<TEditable, TReadonly>`
35
+ - The current record instance. Each field is an accessor with methods to read, write, and validate. See [Instance and Field Accessors](#instance-and-field-accessors).
36
+ - `bdo: B`
37
+ - The BDO instance passed in.
38
+
39
+ ### Form Methods
40
+
41
+ - `register: FormRegisterType<TEditable, TReadonly>`
42
+ - Registers an input field. Auto-disables readonly fields (`{ disabled: true }`).
43
+ - `handleSubmit: HandleSubmitType<CreateUpdateResponseType>`
44
+ - Validates, calls API, invokes callbacks. See [handleSubmit](#handlesubmit).
45
+ - `watch: UseFormWatch<AllFieldsType<B>>`
46
+ - Standard RHF `watch`. Typed to all fields (editable + readonly + system).
47
+ - `setValue: UseFormSetValue<ExtractEditableType<B>>`
48
+ - Standard RHF `setValue`. Typed to editable fields only.
49
+ - `getValues: UseFormGetValues<AllFieldsType<B>>`
50
+ - Returns all field values.
51
+ - `reset: UseFormReset<AllFieldsType<B>>`
52
+ - Resets form to given values or defaults.
53
+ - `trigger: UseFormTrigger<AllFieldsType<B>>`
54
+ - Manually triggers validation for specific or all fields.
55
+ - `control: Control<AllFieldsType<B>>`
56
+ - RHF control for `Controller` components.
57
+
58
+ ### Form State
59
+
60
+ - `formState: FormState<AllFieldsType<B>>`
61
+ - Full RHF form state object.
62
+ - `errors: FieldErrors<AllFieldsType<B>>`
63
+ - Validation errors by field name. Each entry has `type` and `message`.
64
+ - `isDirty: boolean`
65
+ - `true` if any field has been modified.
66
+ - `isValid: boolean`
67
+ - `true` if all fields pass validation.
68
+ - `isSubmitting: boolean`
69
+ - `true` during submission (from button click to API response).
70
+ - `isSubmitSuccessful: boolean`
71
+ - `true` after a successful submission.
72
+ - `dirtyFields: Partial<Record<keyof AllFieldsType<B>, boolean>>`
73
+ - Which fields have been modified.
74
+
75
+ ### Loading & Error
76
+
77
+ - `isLoading: boolean`
78
+ - `true` while fetching the record (update mode) or creating the draft (create mode). Guard form render on this.
79
+ - `isFetching: boolean`
80
+ - `true` during refetches in update mode (including background refetches).
81
+ - `loadError: Error | null`
82
+ - Error from record fetch or draft creation.
83
+ - `draftId: string | undefined`
84
+ - Draft record ID in create mode.
85
+ - `isCreatingDraft: boolean | undefined`
86
+ - `true` while the initial draft is being created.
87
+
88
+ ## handleSubmit
89
+
90
+ ```typescript
91
+ type HandleSubmitType<TRead> = (
92
+ onSuccess?: (data: TRead, e?: React.BaseSyntheticEvent) => void | Promise<void>,
93
+ onError?: (error: FieldErrors | Error, e?: React.BaseSyntheticEvent) => void | Promise<void>,
94
+ ) => (e?: React.BaseSyntheticEvent) => Promise<void>;
95
+ ```
96
+
97
+ Curried function. Pass to `<form onSubmit={handleSubmit(onSuccess, onError)}>`.
98
+
99
+ - `onSuccess(data, e?)`
100
+ - Called with `CreateUpdateResponseType` (`{ _id: string }`) after a successful API call.
101
+ - `onError(error, e?)`
102
+ - Called with `FieldErrors` if validation fails, or `Error` if the API call fails.
103
+
104
+ ## Instance and Field Accessors
105
+
106
+ `item` represents the current record. Each field on `item` is an accessor object.
107
+
108
+ ### Instance Members (`item`)
109
+
110
+ - `_id: string | undefined`
111
+ - Current record ID.
112
+ - `toJSON(): Partial<TEditable & TReadonly>`
113
+ - All form values as a plain object.
114
+ - `validate(): Promise<boolean>`
115
+ - Triggers validation for all fields. Returns `true` if valid.
116
+
117
+ ### Editable Field (`item.FieldName`)
118
+
119
+ - `get(): T | undefined`
120
+ - Current value from form state.
121
+ - `getOrDefault(fallback: T): T`
122
+ - Value or fallback if null/undefined.
123
+ - `set(value: T): void`
124
+ - Sets the field value.
125
+ - `validate(): ValidationResultType`
126
+ - Validates the current value. Returns `{ valid: boolean, errors: string[] }`.
127
+ - `label: string`
128
+ - Display label from field metadata.
129
+ - `required: boolean`
130
+ - Whether the field is required.
131
+ - `readOnly: boolean`
132
+ - Always `false` for editable fields.
133
+ - `defaultValue: unknown`
134
+ - Default value from metadata.
135
+ - `meta: BaseFieldMetaType`
136
+ - Raw field metadata.
137
+
138
+ ### Readonly Field (`item.FieldName`)
139
+
140
+ Same as editable except: no `set()` method, `readOnly` is always `true`.
141
+
142
+ ## Types
143
+
144
+ ```typescript
145
+ // Options
146
+ interface UseBDOFormOptionsType<B extends BaseBdo<any, any, any>> {
147
+ bdo: B;
148
+ recordId?: string;
149
+ }
150
+
151
+ // Return type
152
+ interface UseBDOFormReturnType<B extends BaseBdo<any, any, any>> {
153
+ item: FormItemType<ExtractEditableType<B>, ExtractReadonlyType<B>>;
154
+ bdo: B;
155
+
156
+ register: FormRegisterType<ExtractEditableType<B>, ExtractReadonlyType<B>>;
157
+ handleSubmit: HandleSubmitType<CreateUpdateResponseType>;
158
+ watch: UseFormWatch<AllFieldsType<B>>;
159
+ setValue: UseFormSetValue<ExtractEditableType<B>>;
160
+ getValues: UseFormGetValues<AllFieldsType<B>>;
161
+ reset: UseFormReset<AllFieldsType<B>>;
162
+ trigger: UseFormTrigger<AllFieldsType<B>>;
163
+ control: Control<AllFieldsType<B>>;
164
+
165
+ formState: FormState<AllFieldsType<B>>;
166
+ errors: FieldErrors<AllFieldsType<B>>;
167
+ isDirty: boolean;
168
+ isValid: boolean;
169
+ isSubmitting: boolean;
170
+ isSubmitSuccessful: boolean;
171
+ dirtyFields: Partial<Record<keyof AllFieldsType<B>, boolean>>;
172
+
173
+ isLoading: boolean;
174
+ isFetching: boolean;
175
+ loadError: Error | null;
176
+
177
+ draftId?: string;
178
+ isCreatingDraft?: boolean;
179
+ }
180
+
181
+ // handleSubmit signature
182
+ type HandleSubmitType<TRead = unknown> = (
183
+ onSuccess?: (data: TRead, e?: React.BaseSyntheticEvent) => void | Promise<void>,
184
+ onError?: (error: FieldErrors | Error, e?: React.BaseSyntheticEvent) => void | Promise<void>,
185
+ ) => (e?: React.BaseSyntheticEvent) => Promise<void>;
186
+
187
+ // Instance type
188
+ type FormItemType<TEditable, TReadonly> = {
189
+ [K in keyof TEditable]: EditableFormFieldAccessorType<TEditable[K]>;
190
+ } & {
191
+ [K in keyof TReadonly]: ReadonlyFormFieldAccessorType<TReadonly[K]>;
192
+ } & {
193
+ readonly _id: string | undefined;
194
+ toJSON(): Partial<TEditable & TReadonly>;
195
+ validate(): Promise<boolean>;
196
+ };
197
+
198
+ interface EditableFormFieldAccessorType<T> {
199
+ readonly label: string;
200
+ readonly required: boolean;
201
+ readonly readOnly: boolean;
202
+ readonly defaultValue: unknown;
203
+ readonly meta: BaseFieldMetaType;
204
+ get(): T | undefined;
205
+ getOrDefault(fallback: T): T;
206
+ set(value: T): void;
207
+ validate(): ValidationResultType;
208
+ }
209
+
210
+ interface ReadonlyFormFieldAccessorType<T> {
211
+ readonly label: string;
212
+ readonly required: boolean;
213
+ readonly readOnly: boolean;
214
+ readonly defaultValue: unknown;
215
+ readonly meta: BaseFieldMetaType;
216
+ get(): T | undefined;
217
+ getOrDefault(fallback: T): T;
218
+ validate(): ValidationResultType;
219
+ }
220
+
221
+ // Smart register
222
+ type FormRegisterType<TEditable, TReadonly> = <
223
+ K extends keyof TEditable | keyof TReadonly | string
224
+ >(
225
+ name: K & string,
226
+ options?: RegisterOptions,
227
+ ) => K extends keyof TReadonly
228
+ ? UseFormRegisterReturn & { disabled: true }
229
+ : UseFormRegisterReturn;
230
+
231
+ // Helpers
232
+ type ExtractEditableType<B> = B extends BaseBdo<any, infer E, any> ? E : never;
233
+ type ExtractReadonlyType<B> = B extends BaseBdo<any, any, infer R> ? R : never;
234
+ type AllFieldsType<B> = ExtractEditableType<B> & ExtractReadonlyType<B> & SystemFieldsType;
235
+
236
+ interface ValidationResultType {
237
+ valid: boolean;
238
+ errors: string[];
239
+ }
240
+
241
+ interface CreateUpdateResponseType {
242
+ _id: string;
243
+ }
244
+ ```
@@ -0,0 +1,242 @@
1
+ # useBDOTable
2
+
3
+ BDO-integrated table hook with data fetching, search, sorting, filtering, and pagination.
4
+
5
+ ## When to Use
6
+
7
+ **Use `useBDOTable` when:**
8
+
9
+ - Displaying a list of BDO records in a table
10
+ - You need search, sort, filter, and/or pagination
11
+
12
+ **Use something else when:**
13
+
14
+ - Displaying workflow/activity records — use [`useActivityTable`](../useActivityTable/README.md) instead
15
+ - Building a form — use [`useBDOForm`](../useBDOForm/README.md) instead
16
+
17
+ ## Imports
18
+
19
+ ```tsx
20
+ import { useBDOTable } from "@ram_28/kf-ai-sdk/table";
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```tsx
26
+ import { useMemo } from "react";
27
+ import { useBDOTable } from "@ram_28/kf-ai-sdk/table";
28
+ import { BuyerProduct } from "@/bdo/buyer/Product";
29
+
30
+ function ProductsPage() {
31
+ const product = useMemo(() => new BuyerProduct(), []);
32
+
33
+ const table = useBDOTable({
34
+ bdo: product,
35
+ initialState: {
36
+ sort: [{ Title: "ASC" }],
37
+ pagination: { pageNo: 1, pageSize: 10 },
38
+ },
39
+ });
40
+
41
+ if (table.isLoading) return <p>Loading...</p>;
42
+ if (table.error) return <p>Error: {table.error.message}</p>;
43
+
44
+ return (
45
+ <div>
46
+ <ul>
47
+ {table.rows.map((row) => (
48
+ <li key={row._id}>
49
+ {row.Title.get()} - ${row.Price.get()}
50
+ </li>
51
+ ))}
52
+ </ul>
53
+
54
+ <button
55
+ onClick={table.pagination.goToPrevious}
56
+ disabled={!table.pagination.canGoPrevious}
57
+ >
58
+ Previous
59
+ </button>
60
+ <span>
61
+ Page {table.pagination.pageNo} of {table.pagination.totalPages}
62
+ </span>
63
+ <button
64
+ onClick={table.pagination.goToNext}
65
+ disabled={!table.pagination.canGoNext}
66
+ >
67
+ Next
68
+ </button>
69
+ </div>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## Usage Guide
75
+
76
+ ### Accessing Row Data
77
+
78
+ Rows are `ItemType` instances, not plain objects. Access field values through the `.get()` method on each field accessor:
79
+
80
+ ```tsx
81
+ table.rows.map((row) => (
82
+ <tr key={row._id}>
83
+ <td>{row.Title.get()}</td>
84
+ <td>{row.Category.get()}</td>
85
+ <td>${row.Price.get()}</td>
86
+ </tr>
87
+ ));
88
+ ```
89
+
90
+ - `row._id` is a direct string (no `.get()` needed)
91
+ - `row.Title.get()` returns the field's current value
92
+ - `row.toJSON()` converts the entire row to a plain object
93
+ - Editable rows also have `.set()`, but this is typically not used in a table context
94
+
95
+ ### Search
96
+
97
+ Search filters results by matching a query string against a specific field. The input value updates immediately for a responsive UI, while the API call is debounced.
98
+
99
+ ```tsx
100
+ <input
101
+ type="text"
102
+ placeholder={`Search by ${product.Title.label}...`}
103
+ value={table.search.query}
104
+ onChange={(e) => table.search.set(product.Title.id, e.target.value)}
105
+ />
106
+ {table.search.query && (
107
+ <button onClick={table.search.clear}>Clear</button>
108
+ )}
109
+ {table.isFetching && <span>Searching...</span>}
110
+ ```
111
+
112
+ - **`search.set(field, query)`** -- set the field and query text. The API call is debounced by 300ms. Pagination resets to page 1.
113
+ - **`search.clear()`** -- clear the search field and query. Pagination resets to page 1.
114
+ - **`search.query`** -- current query string (updates immediately, API call debounced)
115
+ - **`search.field`** -- field currently being searched, or `null`
116
+ - Queries longer than 255 characters are silently ignored.
117
+
118
+ ### Sorting
119
+
120
+ Sorting is controlled through the `sort` object. Column headers typically use `toggle()` for click-to-sort behavior:
121
+
122
+ ```tsx
123
+ <th
124
+ onClick={() => table.sort.toggle(product.Title.id)}
125
+ style={{ cursor: "pointer" }}
126
+ >
127
+ {product.Title.label}
128
+ {table.sort.field === product.Title.id &&
129
+ (table.sort.direction === "ASC" ? " ↑" : " ↓")}
130
+ </th>
131
+ ```
132
+
133
+ - **`sort.toggle(field)`** -- cycles through ASC, DESC, then cleared. Toggling a different field starts at ASC.
134
+ - **`sort.set(field, direction)`** -- set the sort field and direction directly. Pass `null` for both to clear.
135
+ - **`sort.clear()`** -- remove sorting entirely.
136
+ - **`sort.field`** / **`sort.direction`** -- current sort state, or `null` when no sort is active.
137
+ - **`initialState.sort`** format is an array of single-key objects: `[{ fieldName: "ASC" }]`.
138
+
139
+ ### Filtering
140
+
141
+ 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:
142
+
143
+ ```tsx
144
+ import { ConditionOperator } from "@ram_28/kf-ai-sdk/table";
145
+
146
+ // Exact match
147
+ table.filter.addCondition({
148
+ Operator: ConditionOperator.EQ,
149
+ LHSField: product.Category.id,
150
+ RHSValue: "Electronics",
151
+ RHSType: "Constant",
152
+ });
153
+
154
+ // Range filter
155
+ table.filter.addCondition({
156
+ Operator: ConditionOperator.GTE,
157
+ LHSField: product.Price.id,
158
+ RHSValue: 100,
159
+ });
160
+
161
+ // Clear all filters
162
+ table.filter.clearAllConditions();
163
+
164
+ // Check if any filters are active
165
+ if (table.filter.hasConditions) {
166
+ // ...
167
+ }
168
+ ```
169
+
170
+ - **`table.filter.addCondition(condition)`** -- add a filter condition
171
+ - **`table.filter.clearAllConditions()`** -- remove all filter conditions
172
+ - **`table.filter.hasConditions`** -- `true` if any conditions are active
173
+ - Filter changes automatically reset pagination to page 1, preventing empty pages after narrowing results.
174
+ - See the [useFilter documentation](../useFilter/README.md) for the full filter API.
175
+
176
+ ### Pagination
177
+
178
+ Pages are 1-indexed (they start at 1, not 0). The default page is 1 and the default page size is 10.
179
+
180
+ ```tsx
181
+ <div>
182
+ <button
183
+ onClick={table.pagination.goToPrevious}
184
+ disabled={!table.pagination.canGoPrevious}
185
+ >
186
+ Previous
187
+ </button>
188
+ <span>
189
+ Page {table.pagination.pageNo} of {table.pagination.totalPages}
190
+ {" "}({table.pagination.totalItems} total)
191
+ </span>
192
+ <button
193
+ onClick={table.pagination.goToNext}
194
+ disabled={!table.pagination.canGoNext}
195
+ >
196
+ Next
197
+ </button>
198
+ </div>
199
+
200
+ <select
201
+ value={table.pagination.pageSize}
202
+ onChange={(e) => table.pagination.setPageSize(Number(e.target.value))}
203
+ >
204
+ <option value={10}>10</option>
205
+ <option value={25}>25</option>
206
+ <option value={50}>50</option>
207
+ </select>
208
+ ```
209
+
210
+ - **`pagination.goToNext()`** / **`pagination.goToPrevious()`** -- navigate forward/backward. No-op at boundaries.
211
+ - **`pagination.goToPage(n)`** -- jump to a specific page. Clamped to `[1, totalPages]`.
212
+ - **`pagination.canGoNext`** / **`pagination.canGoPrevious`** -- whether navigation is possible.
213
+ - **`pagination.setPageSize(n)`** -- change the page size. Resets to page 1.
214
+ - **`pagination.pageNo`** / **`pagination.pageSize`** / **`pagination.totalPages`** / **`pagination.totalItems`** -- current pagination state.
215
+
216
+ ### Refetching
217
+
218
+ Call `refetch()` to manually refresh the table data. This refetches both the list and the count:
219
+
220
+ ```tsx
221
+ const handleDelete = async (id: string) => {
222
+ await product.delete(id);
223
+ table.refetch();
224
+ };
225
+ ```
226
+
227
+ This is the standard pattern after any mutation (create, update, delete) that should be reflected in the table.
228
+
229
+ ## Further Reading
230
+
231
+ - [API Reference](./api_reference.md) -- All options, return values, and type definitions
232
+ - [Product Listing](../examples/bdo/product-listing.md) -- Table with search, sorting, pagination
233
+ - [Edit Product Dialog](../examples/bdo/edit-product-dialog.md) -- Table + edit dialog + refetch
234
+ - [Filtered Product Table](../examples/bdo/filtered-product-table.md) -- Category + price range filters
235
+
236
+ ## Common Mistakes
237
+
238
+ - **Don't forget `useMemo` on the BDO.** `new BdoClass()` must be wrapped in `useMemo(() => ..., [])`. Re-creating the instance on every render causes infinite refetching.
239
+ - **Don't access row fields directly.** `row.Title` is an accessor object, not the value. Use `row.Title.get()` to read the value.
240
+ - **Don't use 0-indexed pagination.** Pages start at 1. Passing `pageNo: 0` will produce incorrect results.
241
+ - **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.
242
+ - **Don't hardcode field names as strings.** Use the BDO field IDs (`product.Title.id`) instead of raw strings (`"Title"`). This keeps your code type-safe and resilient to field renames.