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