@ram_28/kf-ai-sdk 2.0.19 → 2.0.20-beta.1
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/README.md +8 -16
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +1 -1
- package/dist/auth/authConfig.d.ts +1 -1
- package/dist/auth/types.d.ts +1 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/bdo/core/Item.d.ts.map +1 -1
- package/dist/bdo.cjs +1 -1
- package/dist/bdo.mjs +32 -32
- package/dist/components/hooks/useActivityForm/types.d.ts +5 -4
- package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -1
- package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -1
- package/dist/components/hooks/useActivityTable/types.d.ts +4 -5
- package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -1
- package/dist/components/hooks/useBDOForm/createItemProxy.d.ts +2 -2
- package/dist/components/hooks/useBDOForm/createItemProxy.d.ts.map +1 -1
- package/dist/components/hooks/useBDOTable/types.d.ts +12 -20
- package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +2 -2
- package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -1
- package/dist/{constants-Cyi942Yr.js → constants-ConHc1oS.js} +5 -5
- package/dist/constants-QX2RX-wu.cjs +1 -0
- package/dist/filter.cjs +1 -1
- package/dist/filter.mjs +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +16 -15
- package/dist/table.types.d.ts +1 -1
- package/dist/table.types.d.ts.map +1 -1
- package/dist/types/constants.d.ts +1 -1
- package/dist/workflow/Activity.d.ts +5 -8
- package/dist/workflow/Activity.d.ts.map +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.mjs +476 -461
- package/docs/api.md +95 -0
- package/docs/bdo.md +224 -0
- package/docs/gaps.md +360 -0
- package/docs/useActivityForm.md +393 -0
- package/docs/useActivityTable.md +418 -0
- package/docs/useBDOForm.md +498 -0
- package/docs/useBDOTable.md +284 -0
- package/docs/useFilter.md +188 -0
- package/docs/workflow.md +560 -0
- package/package.json +14 -15
- package/sdk/auth/authConfig.ts +1 -1
- package/sdk/auth/types.ts +1 -1
- package/sdk/bdo/core/Item.ts +1 -2
- package/sdk/components/hooks/useActivityForm/types.ts +6 -4
- package/sdk/components/hooks/useActivityForm/useActivityForm.ts +73 -10
- package/sdk/components/hooks/useActivityTable/types.ts +5 -4
- package/sdk/components/hooks/useActivityTable/useActivityTable.ts +8 -10
- package/sdk/components/hooks/useBDOForm/createItemProxy.ts +5 -9
- package/sdk/components/hooks/useBDOTable/types.ts +10 -20
- package/sdk/components/hooks/useBDOTable/useBDOTable.ts +8 -12
- package/sdk/table.types.ts +0 -2
- package/sdk/types/constants.ts +1 -1
- package/sdk/workflow/Activity.ts +7 -39
- package/dist/constants-DEmYwKfC.cjs +0 -1
- package/docs/README.md +0 -57
- package/docs/bdo/README.md +0 -161
- package/docs/bdo/api_reference.md +0 -281
- package/docs/examples/bdo/create-product.md +0 -69
- package/docs/examples/bdo/edit-product-dialog.md +0 -95
- package/docs/examples/bdo/filtered-product-table.md +0 -100
- package/docs/examples/bdo/product-listing.md +0 -73
- package/docs/examples/bdo/supplier-dropdown.md +0 -60
- package/docs/examples/fields/complex-fields.md +0 -248
- package/docs/examples/fields/primitive-fields.md +0 -217
- package/docs/examples/workflow/approve-leave-request.md +0 -76
- package/docs/examples/workflow/filtered-activity-table.md +0 -101
- package/docs/examples/workflow/my-pending-requests.md +0 -90
- package/docs/examples/workflow/start-new-workflow.md +0 -47
- package/docs/examples/workflow/submit-leave-request.md +0 -72
- package/docs/examples/workflow/workflow-progress.md +0 -49
- package/docs/fields/README.md +0 -141
- package/docs/fields/api_reference.md +0 -134
- package/docs/useActivityForm/README.md +0 -244
- package/docs/useActivityForm/api_reference.md +0 -279
- package/docs/useActivityTable/README.md +0 -263
- package/docs/useActivityTable/api_reference.md +0 -294
- package/docs/useBDOForm/README.md +0 -175
- package/docs/useBDOForm/api_reference.md +0 -244
- package/docs/useBDOTable/README.md +0 -242
- package/docs/useBDOTable/api_reference.md +0 -253
- package/docs/useFilter/README.md +0 -323
- package/docs/useFilter/api_reference.md +0 -228
- package/docs/workflow/README.md +0 -158
- package/docs/workflow/api_reference.md +0 -161
- /package/docs/{useAuth/README.md → useAuth.md} +0 -0
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
```typescript
|
|
2
|
-
import { BaseBdo, StringField, NumberField, BooleanField, DateField, SelectField, ReferenceField } from "@ram_28/kf-ai-sdk/bdo";
|
|
3
|
-
import type {
|
|
4
|
-
ItemType,
|
|
5
|
-
EditableFieldAccessorType,
|
|
6
|
-
ReadonlyFieldAccessorType,
|
|
7
|
-
ValidationResultType,
|
|
8
|
-
BdoMetaType,
|
|
9
|
-
} from "@ram_28/kf-ai-sdk/bdo/types";
|
|
10
|
-
import type {
|
|
11
|
-
ListOptionsType,
|
|
12
|
-
FilterType,
|
|
13
|
-
ConditionGroupType,
|
|
14
|
-
ConditionType,
|
|
15
|
-
SortType,
|
|
16
|
-
CreateUpdateResponseType,
|
|
17
|
-
DeleteResponseType,
|
|
18
|
-
MetricOptionsType,
|
|
19
|
-
MetricResponseType,
|
|
20
|
-
PivotOptionsType,
|
|
21
|
-
PivotResponseType,
|
|
22
|
-
} from "@ram_28/kf-ai-sdk/api/types";
|
|
23
|
-
import type {
|
|
24
|
-
StringFieldType,
|
|
25
|
-
NumberFieldType,
|
|
26
|
-
BooleanFieldType,
|
|
27
|
-
DateFieldType,
|
|
28
|
-
DateTimeFieldType,
|
|
29
|
-
TextFieldType,
|
|
30
|
-
SelectFieldType,
|
|
31
|
-
ReferenceFieldType,
|
|
32
|
-
UserFieldType,
|
|
33
|
-
FileFieldType,
|
|
34
|
-
ImageFieldType,
|
|
35
|
-
ArrayFieldType,
|
|
36
|
-
ObjectFieldType,
|
|
37
|
-
SystemFieldsType,
|
|
38
|
-
} from "@ram_28/kf-ai-sdk/types";
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Methods
|
|
42
|
-
|
|
43
|
-
All methods are `protected` on `BaseBdo`. Subclasses expose them as `public` per role.
|
|
44
|
-
|
|
45
|
-
| Method | Params | Returns |
|
|
46
|
-
|--------|--------|---------|
|
|
47
|
-
| `get(id)` | `id: string` | `Promise<ItemType<TEditable, TReadonly>>` |
|
|
48
|
-
| `list(options?)` | `options?: ListOptionsType` | `Promise<ItemType<TEditable, TReadonly>[]>` |
|
|
49
|
-
| `count(options?)` | `options?: ListOptionsType` | `Promise<number>` |
|
|
50
|
-
| `create(data)` | `data: Partial<TEditable>` | `Promise<ItemType<TEditable, TReadonly>>` |
|
|
51
|
-
| `update(id, data)` | `id: string, data: Partial<TEditable>` | `Promise<CreateUpdateResponseType>` |
|
|
52
|
-
| `delete(id)` | `id: string` | `Promise<DeleteResponseType>` |
|
|
53
|
-
| `metric(options)` | `options: Omit<MetricOptionsType, "Type">` | `Promise<MetricResponseType>` |
|
|
54
|
-
| `pivot(options)` | `options: Omit<PivotOptionsType, "Type">` | `Promise<PivotResponseType>` |
|
|
55
|
-
|
|
56
|
-
## Types
|
|
57
|
-
|
|
58
|
-
### ItemType\<TEditable, TReadonly\>
|
|
59
|
-
|
|
60
|
-
Proxy-wrapped record returned by `get()`, `list()`, and `create()`. Provides typed field accessors for each field.
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
type ItemType<TEditable, TReadonly> = {
|
|
64
|
-
readonly _id: string; // direct string access
|
|
65
|
-
|
|
66
|
-
// Editable fields → EditableFieldAccessorType<T>
|
|
67
|
-
// Readonly fields → ReadonlyFieldAccessorType<T>
|
|
68
|
-
|
|
69
|
-
validate(): ValidationResultType; // validates all fields
|
|
70
|
-
toJSON(): Partial<TEditable & TReadonly>;
|
|
71
|
-
};
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### EditableFieldAccessorType\<T\>
|
|
75
|
-
|
|
76
|
-
Accessor for fields the current role can write.
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
interface EditableFieldAccessorType<T> {
|
|
80
|
-
get(): T | undefined;
|
|
81
|
-
getOrDefault(fallback: T): T;
|
|
82
|
-
set(value: T): void;
|
|
83
|
-
validate(): ValidationResultType;
|
|
84
|
-
readonly label: string;
|
|
85
|
-
readonly required: boolean;
|
|
86
|
-
readonly readOnly: false;
|
|
87
|
-
readonly defaultValue: unknown;
|
|
88
|
-
readonly meta: BaseFieldMetaType;
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### ReadonlyFieldAccessorType\<T\>
|
|
93
|
-
|
|
94
|
-
Accessor for fields the current role can only read. Same as editable but no `set()`.
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
interface ReadonlyFieldAccessorType<T> {
|
|
98
|
-
get(): T | undefined;
|
|
99
|
-
getOrDefault(fallback: T): T;
|
|
100
|
-
validate(): ValidationResultType;
|
|
101
|
-
readonly label: string;
|
|
102
|
-
readonly required: boolean;
|
|
103
|
-
readonly readOnly: true;
|
|
104
|
-
readonly defaultValue: unknown;
|
|
105
|
-
readonly meta: BaseFieldMetaType;
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### ValidationResultType
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
interface ValidationResultType {
|
|
113
|
-
valid: boolean;
|
|
114
|
-
errors: string[];
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### ListOptionsType
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
interface ListOptionsType {
|
|
122
|
-
Filter?: FilterType;
|
|
123
|
-
Sort?: SortType;
|
|
124
|
-
Page?: number; // 1-indexed
|
|
125
|
-
PageSize?: number;
|
|
126
|
-
Search?: string;
|
|
127
|
-
Field?: string[]; // specific fields to return
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### FilterType / ConditionGroupType / ConditionType
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
// Root filter — alias for ConditionGroupType
|
|
135
|
-
type FilterType = ConditionGroupType;
|
|
136
|
-
|
|
137
|
-
interface ConditionGroupType {
|
|
138
|
-
Operator: "And" | "Or" | "Not";
|
|
139
|
-
Condition: Array<ConditionType | ConditionGroupType>;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
interface ConditionType {
|
|
143
|
-
Operator: string; // "eq", "neq", "gt", "gte", "lt", "lte", "contains", "startswith", etc.
|
|
144
|
-
LHSField: string; // field name
|
|
145
|
-
RHSValue: any; // comparison value
|
|
146
|
-
RHSType?: string; // defaults to "Constant"
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### SortType
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
type SortType = Record<string, "ASC" | "DESC">[];
|
|
154
|
-
// Example: [{ "Price": "DESC" }, { "Title": "ASC" }]
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### CreateUpdateResponseType
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
interface CreateUpdateResponseType {
|
|
161
|
-
_id: string;
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### DeleteResponseType
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
interface DeleteResponseType {
|
|
169
|
-
status: string;
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### MetricOptionsType
|
|
174
|
-
|
|
175
|
-
Omit `Type` when calling `bdo.metric()` — it is added automatically.
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
interface MetricOptionsType {
|
|
179
|
-
Type: "Metric"; // omit when calling bdo.metric()
|
|
180
|
-
GroupBy: string[];
|
|
181
|
-
Metric: MetricFieldType[];
|
|
182
|
-
Filter?: FilterType;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
interface MetricFieldType {
|
|
186
|
-
Field: string;
|
|
187
|
-
Type: string; // "Count" | "Sum" | "Avg" | "Max" | "Min"
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### MetricResponseType
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
interface MetricResponseType {
|
|
195
|
-
Data: Record<string, any>[];
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### PivotOptionsType
|
|
200
|
-
|
|
201
|
-
Omit `Type` when calling `bdo.pivot()` — it is added automatically.
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
interface PivotOptionsType {
|
|
205
|
-
Type: "Pivot"; // omit when calling bdo.pivot()
|
|
206
|
-
Row: string[];
|
|
207
|
-
Column: string[];
|
|
208
|
-
Metric: MetricFieldType[];
|
|
209
|
-
Filter?: FilterType;
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### PivotResponseType
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
interface PivotResponseType {
|
|
217
|
-
Data: {
|
|
218
|
-
RowHeader: PivotHeaderItemType[];
|
|
219
|
-
ColumnHeader: PivotHeaderItemType[];
|
|
220
|
-
Value: (number | string | null)[][];
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
interface PivotHeaderItemType {
|
|
225
|
-
Key: string;
|
|
226
|
-
Children?: PivotHeaderItemType[] | null;
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### SystemFieldsType
|
|
231
|
-
|
|
232
|
-
Seven system-managed fields inherited from `BaseBdo`. Never redeclare in subclasses.
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
type SystemFieldsType = {
|
|
236
|
-
_id: StringFieldType;
|
|
237
|
-
_created_at: DateTimeFieldType;
|
|
238
|
-
_modified_at: DateTimeFieldType;
|
|
239
|
-
_created_by: UserFieldType;
|
|
240
|
-
_modified_by: UserFieldType;
|
|
241
|
-
_version: StringFieldType;
|
|
242
|
-
_m_version: StringFieldType;
|
|
243
|
-
};
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Field Value Types
|
|
247
|
-
|
|
248
|
-
| Type | Resolves To |
|
|
249
|
-
|------|-------------|
|
|
250
|
-
| `StringFieldType` | `string` |
|
|
251
|
-
| `TextFieldType` | `string` |
|
|
252
|
-
| `NumberFieldType` | `number` |
|
|
253
|
-
| `BooleanFieldType` | `boolean` |
|
|
254
|
-
| `DateFieldType` | `"YYYY-MM-DD"` |
|
|
255
|
-
| `DateTimeFieldType` | `"YYYY-MM-DDThh:mm:ssZ"` |
|
|
256
|
-
| `SelectFieldType<T>` | `T` |
|
|
257
|
-
| `ReferenceFieldType<T>` | `T` |
|
|
258
|
-
| `UserFieldType` | `{ _id: string; _name: string }` |
|
|
259
|
-
| `ImageFieldType` | `FileType \| null` |
|
|
260
|
-
| `FileFieldType` | `FileType[]` |
|
|
261
|
-
| `ArrayFieldType<T>` | `T[]` |
|
|
262
|
-
| `ObjectFieldType<T>` | `T` |
|
|
263
|
-
|
|
264
|
-
### Generated Type Patterns
|
|
265
|
-
|
|
266
|
-
The js_sdk generator creates these types per entity:
|
|
267
|
-
|
|
268
|
-
```typescript
|
|
269
|
-
// entities/Product.ts
|
|
270
|
-
export type ProductType = {
|
|
271
|
-
ProductId: StringFieldType;
|
|
272
|
-
Title: StringFieldType;
|
|
273
|
-
Price: NumberFieldType;
|
|
274
|
-
// ... business fields
|
|
275
|
-
} & SystemFieldsType;
|
|
276
|
-
|
|
277
|
-
// admin/Product.ts
|
|
278
|
-
type AdminProductEditableFieldType = Omit<ProductType, keyof SystemFieldsType>;
|
|
279
|
-
type AdminProductReadonlyFieldType = Record<string, never>;
|
|
280
|
-
// or Pick<ProductType, "Field1" | "Field2"> for limited access
|
|
281
|
-
```
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# Create Product
|
|
2
|
-
|
|
3
|
-
> Create a new product using `useBDOForm` with validation, select fields, and submit handling.
|
|
4
|
-
|
|
5
|
-
```tsx
|
|
6
|
-
import { useMemo } from "react";
|
|
7
|
-
import { useBDOForm } from "@ram_28/kf-ai-sdk/form";
|
|
8
|
-
import type { UseBDOFormReturnType } from "@ram_28/kf-ai-sdk/form/types";
|
|
9
|
-
import { BuyerProduct } from "@/bdo/buyer/Product";
|
|
10
|
-
import type { BuyerProductEntityType } from "@/bdo/buyer/Product";
|
|
11
|
-
import type { FieldErrors } from "react-hook-form";
|
|
12
|
-
|
|
13
|
-
export default function CreateProductForm() {
|
|
14
|
-
const product = useMemo(() => new BuyerProduct(), []);
|
|
15
|
-
|
|
16
|
-
const { register, handleSubmit, errors, isLoading, isSubmitting, watch, setValue }: UseBDOFormReturnType<BuyerProduct> =
|
|
17
|
-
useBDOForm({ bdo: product, defaultValues: { Title: "", Price: 0 } });
|
|
18
|
-
|
|
19
|
-
if (isLoading) return <p>Loading...</p>;
|
|
20
|
-
|
|
21
|
-
const onSuccess = (data: { _id: string }) => console.log("Created:", data._id);
|
|
22
|
-
const onError = (err: FieldErrors<BuyerProductEntityType> | Error) => {
|
|
23
|
-
if (err instanceof Error) console.error(err.message);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<form onSubmit={handleSubmit(onSuccess, onError)}>
|
|
28
|
-
{/* Text input with register() */}
|
|
29
|
-
<label>{product.Title.label} {product.Title.required && <span>*</span>}</label>
|
|
30
|
-
<input {...register(product.Title.id)} />
|
|
31
|
-
{errors.Title && <p>{errors.Title.message}</p>}
|
|
32
|
-
|
|
33
|
-
<label>{product.Description.label}</label>
|
|
34
|
-
<textarea {...register(product.Description.id)} rows={3} />
|
|
35
|
-
{errors.Description && <p>{errors.Description.message}</p>}
|
|
36
|
-
|
|
37
|
-
{/* Number input with register() */}
|
|
38
|
-
<label>{product.Price.label} {product.Price.required && <span>*</span>}</label>
|
|
39
|
-
<input type="number" step="0.01" {...register(product.Price.id)} />
|
|
40
|
-
{errors.Price && <p>{errors.Price.message}</p>}
|
|
41
|
-
|
|
42
|
-
{/* Select field — watch/setValue, not register */}
|
|
43
|
-
<label>{product.Category.label} {product.Category.required && <span>*</span>}</label>
|
|
44
|
-
<select
|
|
45
|
-
value={watch(product.Category.id) ?? ""}
|
|
46
|
-
onChange={(e) => setValue(product.Category.id, e.target.value)}
|
|
47
|
-
>
|
|
48
|
-
<option value="">Select category</option>
|
|
49
|
-
{product.Category.options.map((opt) => (
|
|
50
|
-
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
51
|
-
))}
|
|
52
|
-
</select>
|
|
53
|
-
{errors.Category && <p>{errors.Category.message}</p>}
|
|
54
|
-
|
|
55
|
-
<button type="submit" disabled={isSubmitting}>
|
|
56
|
-
{isSubmitting ? "Saving..." : "Add Product"}
|
|
57
|
-
</button>
|
|
58
|
-
</form>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Key Patterns
|
|
64
|
-
|
|
65
|
-
- **Create mode** -- no `recordId` passed to `useBDOForm`; a draft is allocated on mount
|
|
66
|
-
- **`register()`** -- for text and number inputs that fire native change events
|
|
67
|
-
- **`watch()` + `setValue()`** -- for select fields and other custom components
|
|
68
|
-
- **`handleSubmit(onSuccess, onError)`** -- validates, filters to editable fields, calls the API; never call `bdo.create()` manually
|
|
69
|
-
- **Validation errors** -- `errors.FieldName.message` shows constraint violations automatically
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Edit Product Dialog
|
|
2
|
-
|
|
3
|
-
> Click a table row to open an edit form in a dialog, save, and refetch the table.
|
|
4
|
-
|
|
5
|
-
```tsx
|
|
6
|
-
import { useState, useMemo } from "react";
|
|
7
|
-
import { useBDOTable } from "@ram_28/kf-ai-sdk/table";
|
|
8
|
-
import type { UseBDOTableReturnType } from "@ram_28/kf-ai-sdk/table/types";
|
|
9
|
-
import { useBDOForm } from "@ram_28/kf-ai-sdk/form";
|
|
10
|
-
import type { UseBDOFormReturnType } from "@ram_28/kf-ai-sdk/form/types";
|
|
11
|
-
import { BuyerProduct } from "@/bdo/buyer/Product";
|
|
12
|
-
import type { BuyerProductEntityType } from "@/bdo/buyer/Product";
|
|
13
|
-
import type { FieldErrors } from "react-hook-form";
|
|
14
|
-
|
|
15
|
-
export default function ProductPage() {
|
|
16
|
-
const [showForm, setShowForm] = useState(false);
|
|
17
|
-
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
18
|
-
|
|
19
|
-
const product = useMemo(() => new BuyerProduct(), []);
|
|
20
|
-
|
|
21
|
-
// ── Table ────────────────────────────────────────────────────
|
|
22
|
-
const table: UseBDOTableReturnType<BuyerProduct> = useBDOTable({
|
|
23
|
-
bdo: product,
|
|
24
|
-
initialState: { sort: [{ Title: "ASC" }], pagination: { pageNo: 1, pageSize: 10 } },
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// ── Form (create when selectedId is null, edit when string) ──
|
|
28
|
-
const { register, handleSubmit, errors, isLoading: formLoading, isSubmitting, watch, setValue, isDirty }: UseBDOFormReturnType<BuyerProduct> =
|
|
29
|
-
useBDOForm({ bdo: product, recordId: selectedId ?? undefined });
|
|
30
|
-
|
|
31
|
-
const handleCreate = () => { setSelectedId(null); setShowForm(true); };
|
|
32
|
-
const handleEdit = (id: string) => { setSelectedId(id); setShowForm(true); };
|
|
33
|
-
const onSuccess = () => { setShowForm(false); setSelectedId(null); table.refetch(); };
|
|
34
|
-
const onError = (err: FieldErrors<BuyerProductEntityType> | Error) => {
|
|
35
|
-
if (err instanceof Error) console.error(err.message);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
if (table.isLoading) return <p>Loading...</p>;
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<div>
|
|
42
|
-
<button onClick={handleCreate}>Add Product</button>
|
|
43
|
-
|
|
44
|
-
<table>
|
|
45
|
-
<thead>
|
|
46
|
-
<tr>
|
|
47
|
-
<th>{product.Title.label}</th>
|
|
48
|
-
<th>{product.Price.label}</th>
|
|
49
|
-
</tr>
|
|
50
|
-
</thead>
|
|
51
|
-
<tbody>
|
|
52
|
-
{table.rows.map((row) => (
|
|
53
|
-
<tr key={row._id} onClick={() => handleEdit(row._id)} style={{ cursor: "pointer" }}>
|
|
54
|
-
<td>{row.Title.get()}</td>
|
|
55
|
-
<td>${row.Price.get()?.toFixed(2)}</td>
|
|
56
|
-
</tr>
|
|
57
|
-
))}
|
|
58
|
-
</tbody>
|
|
59
|
-
</table>
|
|
60
|
-
|
|
61
|
-
{/* Dialog form */}
|
|
62
|
-
{showForm && (
|
|
63
|
-
<dialog open>
|
|
64
|
-
<h2>{selectedId ? "Edit Product" : "Add Product"}</h2>
|
|
65
|
-
{formLoading ? (
|
|
66
|
-
<p>Loading form...</p>
|
|
67
|
-
) : (
|
|
68
|
-
<form onSubmit={handleSubmit(onSuccess, onError)}>
|
|
69
|
-
<label>{product.Title.label}</label>
|
|
70
|
-
<input {...register(product.Title.id)} />
|
|
71
|
-
{errors.Title && <p>{errors.Title.message}</p>}
|
|
72
|
-
|
|
73
|
-
<label>{product.Price.label}</label>
|
|
74
|
-
<input type="number" step="0.01" {...register(product.Price.id)} />
|
|
75
|
-
{errors.Price && <p>{errors.Price.message}</p>}
|
|
76
|
-
|
|
77
|
-
<button type="button" onClick={() => setShowForm(false)}>Cancel</button>
|
|
78
|
-
<button type="submit" disabled={isSubmitting || (!!selectedId && !isDirty)}>
|
|
79
|
-
{isSubmitting ? "Saving..." : selectedId ? "Save Changes" : "Add Product"}
|
|
80
|
-
</button>
|
|
81
|
-
</form>
|
|
82
|
-
)}
|
|
83
|
-
</dialog>
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Key Patterns
|
|
91
|
-
|
|
92
|
-
- **Shared BDO instance** -- `useMemo(() => new BuyerProduct(), [])` is passed to both `useBDOTable` and `useBDOForm`
|
|
93
|
-
- **Create vs edit mode** -- `selectedId` is `null` for create (no `recordId`), or a string for edit
|
|
94
|
-
- **`table.refetch()` after save** -- called in `onSuccess` to refresh the table data
|
|
95
|
-
- **`isDirty` guard** -- disables the submit button in edit mode when no changes have been made
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Filtered Product Table
|
|
2
|
-
|
|
3
|
-
> Filter products by category and price range using the integrated `table.filter` API.
|
|
4
|
-
|
|
5
|
-
```tsx
|
|
6
|
-
import { useState, useMemo } from "react";
|
|
7
|
-
import { useBDOTable } from "@ram_28/kf-ai-sdk/table";
|
|
8
|
-
import type { UseBDOTableReturnType } from "@ram_28/kf-ai-sdk/table/types";
|
|
9
|
-
import { ConditionOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
|
|
10
|
-
import { BuyerProduct } from "@/bdo/buyer/Product";
|
|
11
|
-
|
|
12
|
-
export default function FilteredProductTable() {
|
|
13
|
-
const product = useMemo(() => new BuyerProduct(), []);
|
|
14
|
-
const table: UseBDOTableReturnType<BuyerProduct> = useBDOTable({ bdo: product });
|
|
15
|
-
|
|
16
|
-
// ── Category filter (single EQ condition) ─────────────────────
|
|
17
|
-
const [categoryFilter, setCategoryFilter] = useState("");
|
|
18
|
-
const [categoryConditionId, setCategoryConditionId] = useState<string | null>(null);
|
|
19
|
-
|
|
20
|
-
const handleCategoryFilter = (value: string) => {
|
|
21
|
-
if (categoryConditionId) {
|
|
22
|
-
table.filter.removeCondition(categoryConditionId);
|
|
23
|
-
setCategoryConditionId(null);
|
|
24
|
-
}
|
|
25
|
-
setCategoryFilter(value);
|
|
26
|
-
if (value && value !== "all") {
|
|
27
|
-
const id = table.filter.addCondition({
|
|
28
|
-
LHSField: "Category",
|
|
29
|
-
Operator: ConditionOperator.EQ,
|
|
30
|
-
RHSType: RHSType.Constant,
|
|
31
|
-
RHSValue: value,
|
|
32
|
-
});
|
|
33
|
-
setCategoryConditionId(id);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// ── Price range filter (GTE + LTE conditions) ─────────────────
|
|
38
|
-
const [minPrice, setMinPrice] = useState("");
|
|
39
|
-
const [maxPrice, setMaxPrice] = useState("");
|
|
40
|
-
const [minPriceConditionId, setMinPriceConditionId] = useState<string | null>(null);
|
|
41
|
-
const [maxPriceConditionId, setMaxPriceConditionId] = useState<string | null>(null);
|
|
42
|
-
|
|
43
|
-
const applyPriceFilter = () => {
|
|
44
|
-
if (minPriceConditionId) { table.filter.removeCondition(minPriceConditionId); setMinPriceConditionId(null); }
|
|
45
|
-
if (maxPriceConditionId) { table.filter.removeCondition(maxPriceConditionId); setMaxPriceConditionId(null); }
|
|
46
|
-
|
|
47
|
-
if (minPrice !== "") {
|
|
48
|
-
const id = table.filter.addCondition({
|
|
49
|
-
LHSField: "Price", Operator: ConditionOperator.GTE, RHSType: RHSType.Constant, RHSValue: Number(minPrice),
|
|
50
|
-
});
|
|
51
|
-
setMinPriceConditionId(id);
|
|
52
|
-
}
|
|
53
|
-
if (maxPrice !== "") {
|
|
54
|
-
const id = table.filter.addCondition({
|
|
55
|
-
LHSField: "Price", Operator: ConditionOperator.LTE, RHSType: RHSType.Constant, RHSValue: Number(maxPrice),
|
|
56
|
-
});
|
|
57
|
-
setMaxPriceConditionId(id);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
if (table.isLoading) return <p>Loading...</p>;
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div>
|
|
65
|
-
{/* Category dropdown */}
|
|
66
|
-
<select value={categoryFilter} onChange={(e) => handleCategoryFilter(e.target.value)}>
|
|
67
|
-
<option value="all">All categories</option>
|
|
68
|
-
{product.Category.options.map((opt) => (
|
|
69
|
-
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
70
|
-
))}
|
|
71
|
-
</select>
|
|
72
|
-
|
|
73
|
-
{/* Price range */}
|
|
74
|
-
<input type="number" placeholder="Min" value={minPrice} onChange={(e) => setMinPrice(e.target.value)} />
|
|
75
|
-
<input type="number" placeholder="Max" value={maxPrice} onChange={(e) => setMaxPrice(e.target.value)} />
|
|
76
|
-
<button onClick={applyPriceFilter}>Apply</button>
|
|
77
|
-
|
|
78
|
-
{/* Table */}
|
|
79
|
-
<table>
|
|
80
|
-
<tbody>
|
|
81
|
-
{table.rows.map((row) => (
|
|
82
|
-
<tr key={row._id}>
|
|
83
|
-
<td>{row.Title.get()}</td>
|
|
84
|
-
<td>${row.Price.get()?.toFixed(2)}</td>
|
|
85
|
-
<td>{row.Category.get()}</td>
|
|
86
|
-
</tr>
|
|
87
|
-
))}
|
|
88
|
-
</tbody>
|
|
89
|
-
</table>
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Key Patterns
|
|
96
|
-
|
|
97
|
-
- **`addCondition()` returns an ID** -- store it in state to remove the condition later with `removeCondition(id)`
|
|
98
|
-
- **Category filter** -- single `ConditionOperator.EQ` condition; remove-then-add on every change
|
|
99
|
-
- **Price range** -- two separate conditions (`GTE` and `LTE`), each tracked by its own ID
|
|
100
|
-
- **Filter changes auto-reset pagination** -- when conditions change, the table resets to page 1
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# Product Listing
|
|
2
|
-
|
|
3
|
-
> Browse products with search, sortable columns, and pagination using `useBDOTable`.
|
|
4
|
-
|
|
5
|
-
```tsx
|
|
6
|
-
import { useMemo } from "react";
|
|
7
|
-
import { useBDOTable } from "@ram_28/kf-ai-sdk/table";
|
|
8
|
-
import type { UseBDOTableReturnType } from "@ram_28/kf-ai-sdk/table/types";
|
|
9
|
-
import { BuyerProduct } from "@/bdo/buyer/Product";
|
|
10
|
-
|
|
11
|
-
export default function ProductListingPage() {
|
|
12
|
-
const product = useMemo(() => new BuyerProduct(), []);
|
|
13
|
-
const table: UseBDOTableReturnType<BuyerProduct> = useBDOTable({
|
|
14
|
-
bdo: product,
|
|
15
|
-
initialState: { sort: [{ _created_at: "DESC" }], pagination: { pageNo: 1, pageSize: 10 } },
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
if (table.isLoading) return <p>Loading...</p>;
|
|
19
|
-
if (table.error) return <p>Error: {table.error.message}</p>;
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<div>
|
|
23
|
-
{/* Search */}
|
|
24
|
-
<input
|
|
25
|
-
placeholder="Search by title..."
|
|
26
|
-
value={table.search.query}
|
|
27
|
-
onChange={(e) => table.search.set("Title", e.target.value)}
|
|
28
|
-
/>
|
|
29
|
-
{table.search.query && <button onClick={table.search.clear}>Clear</button>}
|
|
30
|
-
|
|
31
|
-
{/* Table with sortable headers */}
|
|
32
|
-
<table>
|
|
33
|
-
<thead>
|
|
34
|
-
<tr>
|
|
35
|
-
<th onClick={() => table.sort.toggle("Title")} style={{ cursor: "pointer" }}>
|
|
36
|
-
{product.Title.label}
|
|
37
|
-
{table.sort.field === "Title" && (table.sort.direction === "ASC" ? " ↑" : " ↓")}
|
|
38
|
-
</th>
|
|
39
|
-
<th onClick={() => table.sort.toggle("Price")} style={{ cursor: "pointer" }}>
|
|
40
|
-
{product.Price.label}
|
|
41
|
-
{table.sort.field === "Price" && (table.sort.direction === "ASC" ? " ↑" : " ↓")}
|
|
42
|
-
</th>
|
|
43
|
-
<th>{product.Category.label}</th>
|
|
44
|
-
</tr>
|
|
45
|
-
</thead>
|
|
46
|
-
<tbody>
|
|
47
|
-
{table.rows.map((row) => (
|
|
48
|
-
<tr key={row._id}>
|
|
49
|
-
<td>{row.Title.get()}</td>
|
|
50
|
-
<td>${row.Price.get()?.toFixed(2)}</td>
|
|
51
|
-
<td>{row.Category.get()}</td>
|
|
52
|
-
</tr>
|
|
53
|
-
))}
|
|
54
|
-
</tbody>
|
|
55
|
-
</table>
|
|
56
|
-
|
|
57
|
-
{/* Pagination */}
|
|
58
|
-
<div>
|
|
59
|
-
<button onClick={table.pagination.goToPrevious} disabled={!table.pagination.canGoPrevious}>Previous</button>
|
|
60
|
-
<span>Page {table.pagination.pageNo} of {table.pagination.totalPages}</span>
|
|
61
|
-
<button onClick={table.pagination.goToNext} disabled={!table.pagination.canGoNext}>Next</button>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Key Patterns
|
|
69
|
-
|
|
70
|
-
- **`row.Field.get()`** -- rows are `ItemType` proxies; always use `.get()` to read values
|
|
71
|
-
- **`search.set(field, query)`** -- updates the input instantly, debounces the API call (300ms)
|
|
72
|
-
- **`sort.toggle(field)`** -- cycles ASC → DESC → cleared on each click
|
|
73
|
-
- **Pagination** -- `canGoNext`/`canGoPrevious` disable buttons at boundaries; `pageNo`, `totalPages` for display
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Supplier Dropdown
|
|
2
|
-
|
|
3
|
-
> Lazy-load ReferenceField options when the dropdown opens using `useQuery`.
|
|
4
|
-
|
|
5
|
-
```tsx
|
|
6
|
-
import { useState, useMemo } from "react";
|
|
7
|
-
import { useQuery } from "@tanstack/react-query";
|
|
8
|
-
import { useBDOForm } from "@ram_28/kf-ai-sdk/form";
|
|
9
|
-
import type { UseBDOFormReturnType } from "@ram_28/kf-ai-sdk/form/types";
|
|
10
|
-
import { BuyerProduct } from "@/bdo/buyer/Product";
|
|
11
|
-
import type { ProductSupplierRefType } from "@/bdo/buyer/Product";
|
|
12
|
-
|
|
13
|
-
export default function SupplierDropdown({ recordId }: { recordId?: string }) {
|
|
14
|
-
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
15
|
-
const product = useMemo(() => new BuyerProduct(), []);
|
|
16
|
-
|
|
17
|
-
const { watch, setValue, item }: UseBDOFormReturnType<BuyerProduct> = useBDOForm({ bdo: product, recordId });
|
|
18
|
-
|
|
19
|
-
// Lazy-load options only when dropdown opens and item has an _id
|
|
20
|
-
const { data: suppliers = [], isFetching } = useQuery<ProductSupplierRefType[]>({
|
|
21
|
-
queryKey: ["supplier-options", item._id],
|
|
22
|
-
queryFn: () => product.SupplierInfo.fetchOptions(item._id!),
|
|
23
|
-
enabled: dropdownOpen && !!item._id,
|
|
24
|
-
staleTime: Infinity,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const currentSupplier = watch(product.SupplierInfo.id);
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<div>
|
|
31
|
-
<label>{product.SupplierInfo.label}</label>
|
|
32
|
-
<select
|
|
33
|
-
value={currentSupplier?._id ?? ""}
|
|
34
|
-
onFocus={() => setDropdownOpen(true)}
|
|
35
|
-
onChange={(e) => {
|
|
36
|
-
const supplier = suppliers.find((s) => s._id === e.target.value);
|
|
37
|
-
if (supplier) setValue(product.SupplierInfo.id, supplier);
|
|
38
|
-
}}
|
|
39
|
-
>
|
|
40
|
-
<option value="">{currentSupplier?.SupplierName ?? "Select supplier"}</option>
|
|
41
|
-
{isFetching ? (
|
|
42
|
-
<option disabled>Loading...</option>
|
|
43
|
-
) : (
|
|
44
|
-
suppliers.map((s) => (
|
|
45
|
-
<option key={s._id} value={s._id}>{s.SupplierName}</option>
|
|
46
|
-
))
|
|
47
|
-
)}
|
|
48
|
-
</select>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Key Patterns
|
|
55
|
-
|
|
56
|
-
- **`enabled: dropdownOpen && !!item._id`** -- options aren't fetched until the dropdown opens and the draft/record has an ID
|
|
57
|
-
- **`fetchOptions(instanceId)`** -- loads reference options for the current record context
|
|
58
|
-
- **`watch()` + `setValue()`** -- reads/writes the entire reference object (`{ _id, SupplierName }`)
|
|
59
|
-
- **`referenceFields`** -- `product.SupplierInfo.referenceFields` lists the fields fetched from the referenced BDO
|
|
60
|
-
- **`staleTime: Infinity`** -- options are cached and not refetched on re-focus
|