@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.
Files changed (93) hide show
  1. package/README.md +8 -16
  2. package/dist/api.cjs +1 -1
  3. package/dist/api.mjs +1 -1
  4. package/dist/auth/authConfig.d.ts +1 -1
  5. package/dist/auth/types.d.ts +1 -1
  6. package/dist/auth/types.d.ts.map +1 -1
  7. package/dist/auth.cjs +1 -1
  8. package/dist/auth.mjs +1 -1
  9. package/dist/bdo/core/Item.d.ts.map +1 -1
  10. package/dist/bdo.cjs +1 -1
  11. package/dist/bdo.mjs +32 -32
  12. package/dist/components/hooks/useActivityForm/types.d.ts +5 -4
  13. package/dist/components/hooks/useActivityForm/types.d.ts.map +1 -1
  14. package/dist/components/hooks/useActivityForm/useActivityForm.d.ts.map +1 -1
  15. package/dist/components/hooks/useActivityTable/types.d.ts +4 -5
  16. package/dist/components/hooks/useActivityTable/types.d.ts.map +1 -1
  17. package/dist/components/hooks/useActivityTable/useActivityTable.d.ts.map +1 -1
  18. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts +2 -2
  19. package/dist/components/hooks/useBDOForm/createItemProxy.d.ts.map +1 -1
  20. package/dist/components/hooks/useBDOTable/types.d.ts +12 -20
  21. package/dist/components/hooks/useBDOTable/types.d.ts.map +1 -1
  22. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts +2 -2
  23. package/dist/components/hooks/useBDOTable/useBDOTable.d.ts.map +1 -1
  24. package/dist/{constants-Cyi942Yr.js → constants-ConHc1oS.js} +5 -5
  25. package/dist/constants-QX2RX-wu.cjs +1 -0
  26. package/dist/filter.cjs +1 -1
  27. package/dist/filter.mjs +1 -1
  28. package/dist/form.cjs +1 -1
  29. package/dist/form.mjs +1 -1
  30. package/dist/table.cjs +1 -1
  31. package/dist/table.mjs +16 -15
  32. package/dist/table.types.d.ts +1 -1
  33. package/dist/table.types.d.ts.map +1 -1
  34. package/dist/types/constants.d.ts +1 -1
  35. package/dist/workflow/Activity.d.ts +5 -8
  36. package/dist/workflow/Activity.d.ts.map +1 -1
  37. package/dist/workflow.cjs +1 -1
  38. package/dist/workflow.mjs +476 -461
  39. package/docs/api.md +95 -0
  40. package/docs/bdo.md +224 -0
  41. package/docs/gaps.md +360 -0
  42. package/docs/useActivityForm.md +393 -0
  43. package/docs/useActivityTable.md +418 -0
  44. package/docs/useBDOForm.md +498 -0
  45. package/docs/useBDOTable.md +284 -0
  46. package/docs/useFilter.md +188 -0
  47. package/docs/workflow.md +560 -0
  48. package/package.json +14 -15
  49. package/sdk/auth/authConfig.ts +1 -1
  50. package/sdk/auth/types.ts +1 -1
  51. package/sdk/bdo/core/Item.ts +1 -2
  52. package/sdk/components/hooks/useActivityForm/types.ts +6 -4
  53. package/sdk/components/hooks/useActivityForm/useActivityForm.ts +73 -10
  54. package/sdk/components/hooks/useActivityTable/types.ts +5 -4
  55. package/sdk/components/hooks/useActivityTable/useActivityTable.ts +8 -10
  56. package/sdk/components/hooks/useBDOForm/createItemProxy.ts +5 -9
  57. package/sdk/components/hooks/useBDOTable/types.ts +10 -20
  58. package/sdk/components/hooks/useBDOTable/useBDOTable.ts +8 -12
  59. package/sdk/table.types.ts +0 -2
  60. package/sdk/types/constants.ts +1 -1
  61. package/sdk/workflow/Activity.ts +7 -39
  62. package/dist/constants-DEmYwKfC.cjs +0 -1
  63. package/docs/README.md +0 -57
  64. package/docs/bdo/README.md +0 -161
  65. package/docs/bdo/api_reference.md +0 -281
  66. package/docs/examples/bdo/create-product.md +0 -69
  67. package/docs/examples/bdo/edit-product-dialog.md +0 -95
  68. package/docs/examples/bdo/filtered-product-table.md +0 -100
  69. package/docs/examples/bdo/product-listing.md +0 -73
  70. package/docs/examples/bdo/supplier-dropdown.md +0 -60
  71. package/docs/examples/fields/complex-fields.md +0 -248
  72. package/docs/examples/fields/primitive-fields.md +0 -217
  73. package/docs/examples/workflow/approve-leave-request.md +0 -76
  74. package/docs/examples/workflow/filtered-activity-table.md +0 -101
  75. package/docs/examples/workflow/my-pending-requests.md +0 -90
  76. package/docs/examples/workflow/start-new-workflow.md +0 -47
  77. package/docs/examples/workflow/submit-leave-request.md +0 -72
  78. package/docs/examples/workflow/workflow-progress.md +0 -49
  79. package/docs/fields/README.md +0 -141
  80. package/docs/fields/api_reference.md +0 -134
  81. package/docs/useActivityForm/README.md +0 -244
  82. package/docs/useActivityForm/api_reference.md +0 -279
  83. package/docs/useActivityTable/README.md +0 -263
  84. package/docs/useActivityTable/api_reference.md +0 -294
  85. package/docs/useBDOForm/README.md +0 -175
  86. package/docs/useBDOForm/api_reference.md +0 -244
  87. package/docs/useBDOTable/README.md +0 -242
  88. package/docs/useBDOTable/api_reference.md +0 -253
  89. package/docs/useFilter/README.md +0 -323
  90. package/docs/useFilter/api_reference.md +0 -228
  91. package/docs/workflow/README.md +0 -158
  92. package/docs/workflow/api_reference.md +0 -161
  93. /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={} />` |