@ram_28/kf-ai-sdk 2.0.19 → 2.0.20-beta.0
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/{FileField-CZjS2uLh.js → FileField-BWrSHNRq.js} +3 -3
- package/dist/{FileField-DU4UWo_t.cjs → FileField-eDeuzln8.cjs} +1 -1
- 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 +4 -0
- package/dist/bdo/core/Item.d.ts.map +1 -1
- package/dist/bdo/fields/ReferenceField.d.ts +1 -1
- package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
- package/dist/bdo/fields/SelectField.d.ts +1 -1
- package/dist/bdo/fields/SelectField.d.ts.map +1 -1
- package/dist/bdo/fields/UserField.d.ts +1 -1
- package/dist/bdo/fields/UserField.d.ts.map +1 -1
- package/dist/bdo.cjs +1 -1
- package/dist/bdo.mjs +62 -53
- 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 +3 -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 +243 -226
- 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 +10 -1
- package/sdk/bdo/fields/ReferenceField.ts +1 -1
- package/sdk/bdo/fields/SelectField.ts +1 -1
- package/sdk/bdo/fields/UserField.ts +1 -1
- 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 +58 -17
- 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
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
# useBDOForm
|
|
2
|
+
|
|
3
|
+
React hook for BDO forms with validation, API integration, and typed field handling.
|
|
4
|
+
|
|
5
|
+
## Imports
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useBDOForm } from "@ram_28/kf-ai-sdk/form";
|
|
9
|
+
import { ValidationMode, FormOperation } from "@ram_28/kf-ai-sdk/form";
|
|
10
|
+
import type { UseBDOFormOptionsType, UseBDOFormReturnType, FormItemType, FormRegisterType, HandleSubmitType } from "@ram_28/kf-ai-sdk/form/types";
|
|
11
|
+
import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
|
|
12
|
+
|
|
13
|
+
// Pre-built components for special field types
|
|
14
|
+
import { ReferenceSelect } from "@/components/system/reference-select";
|
|
15
|
+
import { ImageUpload } from "@/components/system/image-upload";
|
|
16
|
+
import { FileUpload } from "@/components/system/file-upload";
|
|
17
|
+
// Display components (for detail/view pages, NOT forms)
|
|
18
|
+
import { ImageThumbnail } from "@/components/system/image-thumbnail";
|
|
19
|
+
import { FilePreview } from "@/components/system/file-preview";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Common Mistakes (READ FIRST)
|
|
25
|
+
|
|
26
|
+
### 1. Missing `operation` in useBDOForm options (TS2345)
|
|
27
|
+
|
|
28
|
+
ALWAYS include `operation`. Without it, TypeScript cannot resolve the union type.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// ❌ WRONG — missing operation (TS2345)
|
|
32
|
+
useBDOForm({ bdo: product, mode: ValidationMode.OnBlur });
|
|
33
|
+
|
|
34
|
+
// ✅ CORRECT — always include operation
|
|
35
|
+
useBDOForm({ bdo: product, operation: FormOperation.Create, mode: ValidationMode.OnBlur });
|
|
36
|
+
useBDOForm({ bdo: product, operation: FormOperation.Update, recordId: id, mode: ValidationMode.OnBlur });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Annotating ternary with UseBDOFormOptionsType (TS2322)
|
|
40
|
+
|
|
41
|
+
`UseBDOFormOptionsType` is a discriminated union. Type-annotating a variable prevents TS narrowing. NEVER annotate — call useBDOForm inline in each branch.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// ❌ WRONG — type annotation prevents union narrowing (TS2322)
|
|
45
|
+
const options: UseBDOFormOptionsType<typeof bdo> = id
|
|
46
|
+
? { bdo, operation: FormOperation.Update, recordId: id, mode: ValidationMode.OnBlur }
|
|
47
|
+
: { bdo, operation: FormOperation.Create, mode: ValidationMode.OnBlur };
|
|
48
|
+
const formResult = useBDOForm(options);
|
|
49
|
+
|
|
50
|
+
// ✅ CORRECT — call useBDOForm inline, no type annotation
|
|
51
|
+
const formResult = id
|
|
52
|
+
? useBDOForm({ bdo, operation: FormOperation.Update, recordId: id, mode: ValidationMode.OnBlur })
|
|
53
|
+
: useBDOForm({ bdo, operation: FormOperation.Create, mode: ValidationMode.OnBlur });
|
|
54
|
+
const { register, handleSubmit, watch, setValue, item, formState: { errors, isSubmitting }, isLoading } = formResult;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3. Using `.options` on StringField (TS2339)
|
|
58
|
+
|
|
59
|
+
ONLY `SelectField` has `.options` getter. `StringField` with `Constraint.Enum` does NOT — use hardcoded `<option>` values from the BDO file.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Check the BDO class to determine field type:
|
|
63
|
+
// new SelectField({...}) → has .options → use bdo.field.options.map()
|
|
64
|
+
// new StringField({..."Constraint": {"Enum": [...]}}) → NO .options → hardcode from Enum array
|
|
65
|
+
|
|
66
|
+
// ❌ WRONG — StringField has no .options (TS2339)
|
|
67
|
+
// Given: readonly status = new StringField({... "Constraint": { "Enum": ["Active", "Discontinued"] }})
|
|
68
|
+
<select {...register(bdo.status.id)}>
|
|
69
|
+
{bdo.status.options.map((opt) => ( // TS2339: Property 'options' does not exist on StringField
|
|
70
|
+
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
71
|
+
))}
|
|
72
|
+
</select>
|
|
73
|
+
|
|
74
|
+
// ✅ CORRECT for StringField with Enum — hardcode options from BDO Constraint.Enum array
|
|
75
|
+
<select {...register(bdo.status.id)}>
|
|
76
|
+
<option value="">Select {bdo.status.label}</option>
|
|
77
|
+
<option value="Active">Active</option>
|
|
78
|
+
<option value="Discontinued">Discontinued</option>
|
|
79
|
+
</select>
|
|
80
|
+
|
|
81
|
+
// ✅ CORRECT for SelectField — use .options getter
|
|
82
|
+
// Given: readonly status = new SelectField({...})
|
|
83
|
+
<select {...register(bdo.status.id)}>
|
|
84
|
+
<option value="">Select {bdo.status.label}</option>
|
|
85
|
+
{bdo.status.options.map((opt) => (
|
|
86
|
+
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
87
|
+
))}
|
|
88
|
+
</select>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 4. Using register() for BooleanField
|
|
92
|
+
|
|
93
|
+
HTML checkboxes don't work with register(). Use watch/setValue.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// ❌ WRONG
|
|
97
|
+
<input type="checkbox" {...register(bdo.is_active.id)} />
|
|
98
|
+
|
|
99
|
+
// ✅ CORRECT
|
|
100
|
+
<Checkbox
|
|
101
|
+
checked={Boolean(watch(bdo.is_active.id))}
|
|
102
|
+
onCheckedChange={(v) => setValue(bdo.is_active.id, v as boolean, { shouldDirty: true })}
|
|
103
|
+
/>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 5. Using `<input>` for ReferenceField (should use `<ReferenceSelect>`)
|
|
107
|
+
|
|
108
|
+
ReferenceField stores an object `{ _id, _name, ... }`, not a string. Use the pre-built component.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// ❌ WRONG — text input for reference field
|
|
112
|
+
<input {...register(bdo.category.id)} />
|
|
113
|
+
|
|
114
|
+
// ✅ CORRECT
|
|
115
|
+
<ReferenceSelect
|
|
116
|
+
bdoField={bdo.category}
|
|
117
|
+
instanceId={id || String(watch("_id") ?? "")}
|
|
118
|
+
value={watch(bdo.category.id)}
|
|
119
|
+
onChange={(val) => setValue(bdo.category.id, val, { shouldDirty: true })}
|
|
120
|
+
/>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 6. Using custom Image/File upload (should use template components)
|
|
124
|
+
|
|
125
|
+
Use `<ImageUpload>` and `<FileUpload>`. CRITICAL: `instanceId` must work for BOTH edit and create mode.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
// ❌ WRONG — instanceId={id} is undefined in create mode
|
|
129
|
+
<ImageUpload field={item.icon} value={watch(bdo.icon.id)} boId={bdo.meta._id} instanceId={id} fieldId={bdo.icon.id} />
|
|
130
|
+
|
|
131
|
+
// ✅ CORRECT — ImageUpload (single image)
|
|
132
|
+
<ImageUpload
|
|
133
|
+
field={item.product_image}
|
|
134
|
+
value={watch(bdo.product_image.id)}
|
|
135
|
+
boId={bdo.meta._id}
|
|
136
|
+
instanceId={id || String(watch("_id") ?? "")}
|
|
137
|
+
fieldId={bdo.product_image.id}
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
// ✅ CORRECT — FileUpload (multi-file)
|
|
141
|
+
<FileUpload
|
|
142
|
+
field={item.specification_document}
|
|
143
|
+
value={watch(bdo.specification_document.id)}
|
|
144
|
+
boId={bdo.meta._id}
|
|
145
|
+
instanceId={id || String(watch("_id") ?? "")}
|
|
146
|
+
fieldId={bdo.specification_document.id}
|
|
147
|
+
/>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Props: `field` = item accessor (`item.fieldName`), `value` = `watch(bdo.field.id)`, `boId` = `bdo.meta._id`, `instanceId` = `id || String(watch("_id") ?? "")`, `fieldId` = `bdo.field.id`. Parent component MUST have `"use no memo"` directive.
|
|
151
|
+
|
|
152
|
+
### 7. Wrong handleSubmit onSuccess type
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// ❌ WRONG
|
|
156
|
+
const onSuccess = (data: unknown) => { ... };
|
|
157
|
+
|
|
158
|
+
// ✅ CORRECT — CreateUpdateResponseType = { _id: string }
|
|
159
|
+
import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
|
|
160
|
+
const onSuccess = (data: CreateUpdateResponseType) => { toast.success("Saved"); navigate("/list"); };
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 8. Passing FieldType instead of BDO instance
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// ❌ WRONG
|
|
167
|
+
useBDOForm<ProductFieldType>({ ... });
|
|
168
|
+
|
|
169
|
+
// ✅ CORRECT — pass BDO class instance
|
|
170
|
+
const product = useMemo(() => new SellerProduct(), []);
|
|
171
|
+
useBDOForm({ bdo: product, operation: FormOperation.Create, mode: ValidationMode.OnBlur });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 9. Wrong default values for date fields
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// ❌ WRONG — empty string causes type errors
|
|
178
|
+
defaultValues: { start_date: "" }
|
|
179
|
+
|
|
180
|
+
// ✅ CORRECT
|
|
181
|
+
defaultValues: { start_date: undefined }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
### 10. Comparing `item.field` to a string literal (TS2367)
|
|
186
|
+
|
|
187
|
+
`item.field` returns an accessor proxy (`EditableFieldAccessorType`), NOT a raw string. Use `.get()` to extract the value.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
// ❌ WRONG — item.status is a proxy, not a string (TS2367: no overlap)
|
|
191
|
+
if (item.status === "Active") { ... }
|
|
192
|
+
if (item.order_status === "Shipped") { ... }
|
|
193
|
+
|
|
194
|
+
// ✅ CORRECT — use .get() to extract the raw value
|
|
195
|
+
if (item.status.get() === "Active") { ... }
|
|
196
|
+
if (item.order_status.getOrDefault("Pending") === "Shipped") { ... }
|
|
197
|
+
|
|
198
|
+
// ✅ ALSO CORRECT — use watch() for the raw value
|
|
199
|
+
const statusVal = watch(bdo.status.id);
|
|
200
|
+
if (statusVal === "Active") { ... }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Applies to ALL conditional rendering based on field values:**
|
|
204
|
+
```tsx
|
|
205
|
+
// ❌ WRONG — TS2367
|
|
206
|
+
{item.category === "Electronics" && <ElectronicsFields />}
|
|
207
|
+
|
|
208
|
+
// ✅ CORRECT
|
|
209
|
+
{watch(bdo.category.id) === "Electronics" && <ElectronicsFields />}
|
|
210
|
+
// or
|
|
211
|
+
{item.category.get() === "Electronics" && <ElectronicsFields />}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 11. Passing `form.watch()` to useEffect dependency array
|
|
215
|
+
|
|
216
|
+
`watch()` creates a new reference each render — putting it directly in deps causes infinite re-renders.
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// ❌ WRONG — watch() in deps triggers infinite loop
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
// side effect using field value
|
|
222
|
+
}, [form.watch(bdo.status.id)]);
|
|
223
|
+
|
|
224
|
+
// ✅ CORRECT — store watch result in a variable first
|
|
225
|
+
const statusVal = form.watch(bdo.status.id);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
// side effect using statusVal
|
|
228
|
+
}, [statusVal]);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 12. Using string literals instead of `field.id` in watch/setValue (TS2322)
|
|
232
|
+
|
|
233
|
+
`watch()` and `setValue()` expect the field name union type from the BDO class. Plain strings don't match.
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
// ❌ WRONG — plain string doesn't match field name union type (TS2322)
|
|
237
|
+
watch("tier");
|
|
238
|
+
setValue("tier", value);
|
|
239
|
+
|
|
240
|
+
// ✅ CORRECT — use bdo.field.id
|
|
241
|
+
watch(bdo.tier.id);
|
|
242
|
+
setValue(bdo.tier.id, value, { shouldDirty: true });
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
### 13. Using `field` prop on display components (TS2322)
|
|
247
|
+
|
|
248
|
+
Display components (`ImageThumbnail`, `FilePreview`) take individual string props. Form components (`ImageUpload`, `FileUpload`) take a `field` accessor. NEVER mix them up.
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
// ❌ WRONG — ImageThumbnail does NOT accept field prop (TS2322)
|
|
252
|
+
<ImageThumbnail field={item.product_image} value={watch(bdo.product_image.id)} />
|
|
253
|
+
|
|
254
|
+
// ✅ CORRECT — ImageThumbnail uses individual props (for detail/view/table pages)
|
|
255
|
+
<ImageThumbnail
|
|
256
|
+
boId={bdo.meta._id}
|
|
257
|
+
instanceId={id}
|
|
258
|
+
fieldId={bdo.product_image.id}
|
|
259
|
+
value={watch(bdo.product_image.id)}
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
// ❌ WRONG — FilePreview does NOT accept field prop (TS2322)
|
|
263
|
+
<FilePreview field={item.specification_document} value={watch(bdo.specification_document.id)} />
|
|
264
|
+
|
|
265
|
+
// ✅ CORRECT — FilePreview uses individual props (for detail/view/table pages)
|
|
266
|
+
<FilePreview
|
|
267
|
+
boId={bdo.meta._id}
|
|
268
|
+
instanceId={id}
|
|
269
|
+
fieldId={bdo.specification_document.id}
|
|
270
|
+
value={watch(bdo.specification_document.id)}
|
|
271
|
+
/>
|
|
272
|
+
|
|
273
|
+
// ✅ CORRECT — ImageUpload uses field accessor (for form/edit pages)
|
|
274
|
+
<ImageUpload
|
|
275
|
+
field={item.product_image}
|
|
276
|
+
value={watch(bdo.product_image.id)}
|
|
277
|
+
boId={bdo.meta._id}
|
|
278
|
+
instanceId={id || String(watch("_id") ?? "")}
|
|
279
|
+
fieldId={bdo.product_image.id}
|
|
280
|
+
/>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Rule of thumb:**
|
|
284
|
+
- **Form page (create/edit)**: `ImageUpload` / `FileUpload` with `field={item.fieldname}`
|
|
285
|
+
- **Display page (detail/view/table)**: `ImageThumbnail` / `FilePreview` with `boId, instanceId, fieldId, value`
|
|
286
|
+
|
|
287
|
+
### 14. Wrong handleSubmit onError type (TS2345)
|
|
288
|
+
|
|
289
|
+
`handleSubmit(onSuccess, onError)` — the `onError` callback receives `FieldErrors | Error`, NOT just `Error`.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// ❌ WRONG — Error alone is not assignable (TS2345)
|
|
293
|
+
const onError = (error: Error) => { toast.error(error.message); };
|
|
294
|
+
|
|
295
|
+
// ✅ CORRECT — import FieldErrors from react-hook-form
|
|
296
|
+
import type { FieldErrors } from "react-hook-form";
|
|
297
|
+
const onError = (error: FieldErrors | Error) => {
|
|
298
|
+
toast.error(error instanceof Error ? error.message : "Please fix errors above");
|
|
299
|
+
};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Complete Form Example (Create + Edit)
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
"use no memo";
|
|
308
|
+
|
|
309
|
+
import { useMemo } from "react";
|
|
310
|
+
import { useNavigate, useParams } from "react-router-dom";
|
|
311
|
+
import { useBDOForm, ValidationMode, FormOperation } from "@ram_28/kf-ai-sdk/form";
|
|
312
|
+
import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
|
|
313
|
+
import type { FieldErrors } from "react-hook-form";
|
|
314
|
+
import { AdminProduct } from "@/bdo/admin/Product";
|
|
315
|
+
import { ReferenceSelect } from "@/components/system/reference-select";
|
|
316
|
+
import { ImageUpload } from "@/components/system/image-upload";
|
|
317
|
+
import { FileUpload } from "@/components/system/file-upload";
|
|
318
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
319
|
+
import { toast } from "sonner";
|
|
320
|
+
|
|
321
|
+
export default function ProductForm() {
|
|
322
|
+
const { id } = useParams<{ id: string }>();
|
|
323
|
+
const navigate = useNavigate();
|
|
324
|
+
const bdo = useMemo(() => new AdminProduct(), []);
|
|
325
|
+
|
|
326
|
+
// Create/Edit ternary — NO type annotation on result
|
|
327
|
+
const formResult = id
|
|
328
|
+
? useBDOForm({ bdo, operation: FormOperation.Update, recordId: id, mode: ValidationMode.OnBlur })
|
|
329
|
+
: useBDOForm({ bdo, operation: FormOperation.Create, mode: ValidationMode.OnBlur });
|
|
330
|
+
|
|
331
|
+
const { register, handleSubmit, watch, setValue, item, formState: { errors, isSubmitting }, isLoading } = formResult;
|
|
332
|
+
|
|
333
|
+
// Loading guard AFTER all hooks
|
|
334
|
+
if (isLoading) return <div className="flex items-center justify-center h-full"><div className="animate-spin w-8 h-8 border-4 border-primary border-t-transparent rounded-full" /></div>;
|
|
335
|
+
|
|
336
|
+
const onSuccess = (data: CreateUpdateResponseType) => {
|
|
337
|
+
toast.success(id ? "Updated" : "Created");
|
|
338
|
+
navigate("/products");
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const onError = (error: FieldErrors | Error) => {
|
|
342
|
+
toast.error(error instanceof Error ? error.message : "Please fix errors above");
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<form onSubmit={handleSubmit(onSuccess, onError)} className="space-y-6">
|
|
347
|
+
{/* StringField — register */}
|
|
348
|
+
<div>
|
|
349
|
+
<label>{bdo.product_name.label}{bdo.product_name.required && <span className="text-red-500"> *</span>}</label>
|
|
350
|
+
<input {...register(bdo.product_name.id)} className="w-full border rounded px-3 py-2" />
|
|
351
|
+
{errors.product_name && <p className="text-red-600 text-sm">{errors.product_name.message}</p>}
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
{/* NumberField — register */}
|
|
355
|
+
<div>
|
|
356
|
+
<label>{bdo.unit_price.label}</label>
|
|
357
|
+
<input type="number" step="0.01" {...register(bdo.unit_price.id)} className="w-full border rounded px-3 py-2" />
|
|
358
|
+
{errors.unit_price && <p className="text-red-600 text-sm">{errors.unit_price.message}</p>}
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
{/* StringField with Constraint.Enum — hardcoded options (NO .options getter) */}
|
|
362
|
+
<div>
|
|
363
|
+
<label>{bdo.status.label}</label>
|
|
364
|
+
<select {...register(bdo.status.id)} className="w-full border rounded px-3 py-2">
|
|
365
|
+
<option value="">Select {bdo.status.label}</option>
|
|
366
|
+
<option value="Active">Active</option>
|
|
367
|
+
<option value="Discontinued">Discontinued</option>
|
|
368
|
+
</select>
|
|
369
|
+
{errors.status && <p className="text-red-600 text-sm">{errors.status.message}</p>}
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
{/* ReferenceField — ReferenceSelect component */}
|
|
373
|
+
<div>
|
|
374
|
+
<label>{bdo.category.label}</label>
|
|
375
|
+
<ReferenceSelect
|
|
376
|
+
bdoField={bdo.category}
|
|
377
|
+
instanceId={id || String(watch("_id") ?? "")}
|
|
378
|
+
value={watch(bdo.category.id)}
|
|
379
|
+
onChange={(val) => setValue(bdo.category.id, val, { shouldDirty: true })}
|
|
380
|
+
/>
|
|
381
|
+
{errors.category && <p className="text-red-600 text-sm">{String(errors.category.message ?? "")}</p>}
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
{/* BooleanField — watch + setValue */}
|
|
385
|
+
<div className="flex items-center gap-2">
|
|
386
|
+
<Checkbox
|
|
387
|
+
checked={Boolean(watch(bdo.is_active.id))}
|
|
388
|
+
onCheckedChange={(v) => setValue(bdo.is_active.id, v as boolean, { shouldDirty: true })}
|
|
389
|
+
/>
|
|
390
|
+
<label>{bdo.is_active.label}</label>
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
{/* ImageField — ImageUpload component */}
|
|
394
|
+
<div>
|
|
395
|
+
<label>{bdo.product_image.label}</label>
|
|
396
|
+
<ImageUpload
|
|
397
|
+
field={item.product_image}
|
|
398
|
+
value={watch(bdo.product_image.id)}
|
|
399
|
+
boId={bdo.meta._id}
|
|
400
|
+
instanceId={id || String(watch("_id") ?? "")}
|
|
401
|
+
fieldId={bdo.product_image.id}
|
|
402
|
+
/>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
{/* FileField — FileUpload component */}
|
|
406
|
+
<div>
|
|
407
|
+
<label>{bdo.specification_document.label}</label>
|
|
408
|
+
<FileUpload
|
|
409
|
+
field={item.specification_document}
|
|
410
|
+
value={watch(bdo.specification_document.id)}
|
|
411
|
+
boId={bdo.meta._id}
|
|
412
|
+
instanceId={id || String(watch("_id") ?? "")}
|
|
413
|
+
fieldId={bdo.specification_document.id}
|
|
414
|
+
/>
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
{/* TextField — textarea with register */}
|
|
418
|
+
<div>
|
|
419
|
+
<label>{bdo.description.label}</label>
|
|
420
|
+
<textarea {...register(bdo.description.id)} rows={4} className="w-full border rounded px-3 py-2" />
|
|
421
|
+
</div>
|
|
422
|
+
|
|
423
|
+
<button type="submit" disabled={isSubmitting} className="px-4 py-2 bg-primary text-white rounded">
|
|
424
|
+
{isSubmitting ? "Saving..." : id ? "Update" : "Create"}
|
|
425
|
+
</button>
|
|
426
|
+
</form>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Type Definitions
|
|
434
|
+
|
|
435
|
+
### UseBDOFormOptionsType (Discriminated Union)
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
type UseBDOFormOptionsType<B extends BaseBdo<any, any, any>> =
|
|
439
|
+
| { bdo: B; operation: "create"; defaultValues?: Partial<EditableFieldType>; mode?: ValidationModeType; }
|
|
440
|
+
| { bdo: B; operation: "update"; recordId: string; mode?: ValidationModeType; }
|
|
441
|
+
| { bdo: B; recordId?: string; defaultValues?: Partial<EditableFieldType>; mode?: ValidationModeType; };
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### UseBDOFormReturnType
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
interface UseBDOFormReturnType<B> {
|
|
448
|
+
item: FormItemType<EditableFieldType, ReadonlyFieldType>; // Field accessors (.get(), .set(), .upload())
|
|
449
|
+
register: FormRegisterType; // Auto-disables readonly fields
|
|
450
|
+
handleSubmit: HandleSubmitType; // Auto-calls bdo.create() or bdo.update()
|
|
451
|
+
watch: UseFormWatch; // Watch field values by bdo.field.id
|
|
452
|
+
setValue: UseFormSetValue; // Set field values by bdo.field.id
|
|
453
|
+
getValues: UseFormGetValues;
|
|
454
|
+
control: Control;
|
|
455
|
+
formState: FormState;
|
|
456
|
+
errors: FieldErrors;
|
|
457
|
+
isLoading: boolean; // Fetching record data (edit mode)
|
|
458
|
+
isSubmitting: boolean;
|
|
459
|
+
isDirty: boolean;
|
|
460
|
+
loadError: Error | null;
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### File & Image Types
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
interface FileType { _id: string; _name: string; FileName: string; FileExtension: string; Size: number; ContentType: string; }
|
|
468
|
+
type ImageFieldType = FileType | null; // Single image, nullable
|
|
469
|
+
type FileFieldType = FileType[]; // Array of files
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Image accessor: `item.field.get()` returns `FileType | null`. Has `upload(file: File)`, `deleteAttachment()`, `getDownloadUrl()`.
|
|
473
|
+
File accessor: `item.field.get()` returns `FileType[]`. Has `upload(files: File[])`, `deleteAttachment(id)`, `getDownloadUrl(id)`.
|
|
474
|
+
|
|
475
|
+
### Constants
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
FormOperation.Create // "create"
|
|
479
|
+
FormOperation.Update // "update"
|
|
480
|
+
ValidationMode.OnBlur / .OnChange / .OnSubmit / .OnTouched / .All
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Field-Type to UI Component Mapping
|
|
484
|
+
|
|
485
|
+
| BDO Field Class | UI Pattern |
|
|
486
|
+
|---|---|
|
|
487
|
+
| `StringField` | `<input {...register(bdo.field.id)} />` |
|
|
488
|
+
| `StringField` with `Constraint.Enum` | `<select {...register(bdo.field.id)}>` with hardcoded `<option>` from Enum array |
|
|
489
|
+
| `SelectField` | `<select {...register(bdo.field.id)}>` with `bdo.field.options.map()` |
|
|
490
|
+
| `TextField` | `<textarea {...register(bdo.field.id)} />` |
|
|
491
|
+
| `NumberField` | `<input type="number" {...register(bdo.field.id)} />` |
|
|
492
|
+
| `BooleanField` | `<Checkbox checked={watch()} onCheckedChange={v => setValue()} />` |
|
|
493
|
+
| `DateField` | `<input type="date" {...register(bdo.field.id)} />` |
|
|
494
|
+
| `DateTimeField` | `<input type="datetime-local" {...register(bdo.field.id)} />` |
|
|
495
|
+
| `ReferenceField` | `<ReferenceSelect bdoField={bdo.field} instanceId={id \|\| String(watch("_id") ?? "")} value={watch()} onChange={...} />` |
|
|
496
|
+
| `UserField` | `<ReferenceSelect bdoField={bdo.field} instanceId={id \|\| String(watch("_id") ?? "")} value={watch()} onChange={...} />` |
|
|
497
|
+
| `ImageField` | `<ImageUpload field={item.field} value={watch()} boId={} instanceId={} fieldId={} />` |
|
|
498
|
+
| `FileField` | `<FileUpload field={item.field} value={watch()} boId={} instanceId={} fieldId={} />` |
|