@ram_28/kf-ai-sdk 2.0.11 → 2.0.13

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 (69) hide show
  1. package/dist/api/client.d.ts.map +1 -1
  2. package/dist/api.cjs +1 -1
  3. package/dist/api.mjs +2 -2
  4. package/dist/attachment-constants-B5jlqoKI.cjs +1 -0
  5. package/dist/attachment-constants-C2UHWxmp.js +63 -0
  6. package/dist/auth.cjs +1 -1
  7. package/dist/auth.mjs +1 -1
  8. package/dist/bdo/core/types.d.ts +4 -0
  9. package/dist/bdo/core/types.d.ts.map +1 -1
  10. package/dist/bdo/fields/NumberField.d.ts.map +1 -1
  11. package/dist/bdo/fields/ReferenceField.d.ts +3 -2
  12. package/dist/bdo/fields/ReferenceField.d.ts.map +1 -1
  13. package/dist/bdo/fields/SelectField.d.ts +1 -1
  14. package/dist/bdo/fields/SelectField.d.ts.map +1 -1
  15. package/dist/bdo/fields/UserField.d.ts +5 -0
  16. package/dist/bdo/fields/UserField.d.ts.map +1 -1
  17. package/dist/bdo.cjs +1 -1
  18. package/dist/bdo.mjs +107 -153
  19. package/dist/client-DnO2KKrw.cjs +1 -0
  20. package/dist/{client-CMERmrC-.js → client-iQTqFDNI.js} +34 -30
  21. package/dist/components/hooks/useForm/createItemProxy.d.ts +4 -0
  22. package/dist/components/hooks/useForm/createItemProxy.d.ts.map +1 -1
  23. package/dist/components/hooks/useForm/createResolver.d.ts.map +1 -1
  24. package/dist/components/hooks/useForm/useForm.d.ts +1 -0
  25. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
  26. package/dist/form.cjs +1 -1
  27. package/dist/form.mjs +368 -203
  28. package/dist/{metadata-BfJtHz84.cjs → metadata-DgLSJkF5.cjs} +1 -1
  29. package/dist/{metadata-CwAo6a8e.js → metadata-DpfI3zRN.js} +1 -1
  30. package/dist/table.cjs +1 -1
  31. package/dist/table.mjs +1 -1
  32. package/dist/workflow/types.d.ts +3 -2
  33. package/dist/workflow/types.d.ts.map +1 -1
  34. package/dist/workflow.cjs +1 -1
  35. package/dist/workflow.d.ts +0 -2
  36. package/dist/workflow.d.ts.map +1 -1
  37. package/dist/workflow.mjs +204 -274
  38. package/dist/workflow.types.d.ts +0 -1
  39. package/dist/workflow.types.d.ts.map +1 -1
  40. package/docs/api.md +45 -253
  41. package/docs/bdo.md +130 -711
  42. package/docs/useAuth.md +42 -104
  43. package/docs/useFilter.md +117 -1591
  44. package/docs/useForm.md +263 -861
  45. package/docs/useTable.md +255 -1096
  46. package/docs/workflow.md +10 -155
  47. package/package.json +1 -1
  48. package/sdk/api/client.ts +18 -4
  49. package/sdk/bdo/core/types.ts +1 -0
  50. package/sdk/bdo/fields/NumberField.ts +2 -1
  51. package/sdk/bdo/fields/ReferenceField.ts +4 -3
  52. package/sdk/bdo/fields/SelectField.ts +2 -2
  53. package/sdk/bdo/fields/UserField.ts +14 -0
  54. package/sdk/components/hooks/useForm/createItemProxy.ts +221 -4
  55. package/sdk/components/hooks/useForm/createResolver.ts +16 -1
  56. package/sdk/components/hooks/useForm/useForm.ts +153 -50
  57. package/sdk/workflow/types.ts +3 -2
  58. package/sdk/workflow.ts +0 -7
  59. package/sdk/workflow.types.ts +0 -7
  60. package/dist/client-BnVxSHAm.cjs +0 -1
  61. package/dist/workflow/components/useActivityTable/index.d.ts +0 -4
  62. package/dist/workflow/components/useActivityTable/index.d.ts.map +0 -1
  63. package/dist/workflow/components/useActivityTable/types.d.ts +0 -53
  64. package/dist/workflow/components/useActivityTable/types.d.ts.map +0 -1
  65. package/dist/workflow/components/useActivityTable/useActivityTable.d.ts +0 -4
  66. package/dist/workflow/components/useActivityTable/useActivityTable.d.ts.map +0 -1
  67. package/sdk/workflow/components/useActivityTable/index.ts +0 -8
  68. package/sdk/workflow/components/useActivityTable/types.ts +0 -67
  69. package/sdk/workflow/components/useActivityTable/useActivityTable.ts +0 -145
package/docs/useForm.md CHANGED
@@ -1,971 +1,373 @@
1
1
  # Form SDK API
2
2
 
3
- This Form SDK API provides React hooks for building forms with automatic validation,
4
- API integration, and type-safe field handling.
3
+ React hook for forms with validation, API integration, and typed field handling.
5
4
 
6
5
  ## Imports
7
6
 
8
7
  ```typescript
9
8
  import { useForm } from "@ram_28/kf-ai-sdk/form";
10
9
  import { ValidationMode, FormOperation } from "@ram_28/kf-ai-sdk/form";
11
- import type {
12
- UseFormOptionsType,
13
- UseFormReturnType,
14
- FormItemType,
15
- FormRegisterType,
16
- HandleSubmitType,
17
- ValidationModeType,
18
- EditableFormFieldAccessorType,
19
- ReadonlyFormFieldAccessorType,
20
- } from "@ram_28/kf-ai-sdk/form/types";
21
-
22
- // For filter conditions in forms
23
- import { ConditionOperator, GroupOperator, RHSType } from "@ram_28/kf-ai-sdk/filter";
24
- ```
25
-
26
- ## Type Definitions
27
-
28
- ### UseFormOptionsType
29
-
30
- ```typescript
31
- // Create mode — no recordId
32
- interface UseFormCreateOptionsType<B extends BaseBdo<any, any, any>> {
33
- bdo: B;
34
- defaultValues?: Partial<EditableFieldType>;
35
- mode?: ValidationModeType;
36
- enableDraft?: boolean;
37
- enableConstraintValidation?: boolean; // default: true
38
- enableExpressionValidation?: boolean; // default: true
39
- }
40
-
41
- // Edit mode — recordId required
42
- interface UseFormEditOptionsType<B extends BaseBdo<any, any, any>> {
43
- bdo: B;
44
- recordId: string;
45
- mode?: ValidationModeType;
46
- enableDraft?: boolean;
47
- enableConstraintValidation?: boolean; // default: true
48
- enableExpressionValidation?: boolean; // default: true
49
- }
50
-
51
- // Explicit operation mode
52
- interface UseFormExplicitOptionsType<B extends BaseBdo<any, any, any>> {
53
- bdo: B;
54
- operation: "create" | "update";
55
- recordId?: string;
56
- defaultValues?: Partial<EditableFieldType>;
57
- mode?: ValidationModeType;
58
- enableDraft?: boolean;
59
- enableConstraintValidation?: boolean; // default: true
60
- enableExpressionValidation?: boolean; // default: true
61
- }
10
+ import type { UseFormOptionsType, UseFormReturnType, FormItemType, FormRegisterType, HandleSubmitType } from "@ram_28/kf-ai-sdk/form/types";
11
+ import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
62
12
 
63
- type UseFormOptionsType<B extends BaseBdo<any, any, any>> =
64
- | UseFormCreateOptionsType<B>
65
- | UseFormEditOptionsType<B>
66
- | UseFormExplicitOptionsType<B>;
13
+ // Pre-built components for special field types
14
+ import { ReferenceSelect } from "@/components/ui/reference-select";
15
+ import { ImageUpload } from "@/components/ui/image-upload";
16
+ import { FileUpload } from "@/components/ui/file-upload";
67
17
  ```
68
18
 
69
- ### UseFormReturnType
19
+ ---
70
20
 
71
- ```typescript
72
- interface UseFormReturnType<B extends BaseBdo<any, any, any>> {
73
- // ============================================================
74
- // CORE
75
- // ============================================================
76
-
77
- // Item proxy with typed field accessors
78
- item: FormItemType<EditableFieldType, ReadonlyFieldType>;
79
-
80
- // BDO reference and operation info
81
- bdo: B;
82
- operation: "create" | "update";
83
- recordId?: string;
84
-
85
- // Smart register (auto-disables readonly fields)
86
- register: FormRegisterType<EditableFieldType, ReadonlyFieldType>;
87
-
88
- // Custom handleSubmit (handles API call + payload filtering)
89
- handleSubmit: HandleSubmitType;
90
-
91
- // ============================================================
92
- // REACT HOOK FORM METHODS
93
- // ============================================================
94
-
95
- watch: UseFormWatch<AllFieldsType>;
96
- setValue: UseFormSetValue<EditableFieldType>;
97
- getValues: UseFormGetValues<AllFieldsType>;
98
- reset: UseFormReset<AllFieldsType>;
99
- trigger: UseFormTrigger<AllFieldsType>;
100
- control: Control<AllFieldsType>;
101
-
102
- // ============================================================
103
- // FORM STATE
104
- // ============================================================
105
-
106
- formState: FormState<AllFieldsType>;
107
- errors: FieldErrors<AllFieldsType>;
108
- isDirty: boolean;
109
- isValid: boolean;
110
- isSubmitting: boolean;
111
- isSubmitSuccessful: boolean;
112
- dirtyFields: Partial<Record<keyof AllFieldsType, boolean>>;
21
+ ## Common Mistakes (READ FIRST)
113
22
 
114
- // ============================================================
115
- // LOADING & ERROR
116
- // ============================================================
23
+ ### 1. Missing `operation` in useForm options (TS2345)
117
24
 
118
- isLoading: boolean; // Fetching record data
119
- isFetching: boolean; // True during any background refetch
120
- loadError: Error | null; // Schema/record fetch error
25
+ ALWAYS include `operation`. Without it, TypeScript cannot resolve the union type.
121
26
 
122
- // ============================================================
123
- // DRAFT (OPTIONAL)
124
- // ============================================================
27
+ ```typescript
28
+ // WRONG — missing operation (TS2345)
29
+ useForm({ bdo: product, mode: ValidationMode.OnBlur });
125
30
 
126
- draftId?: string;
127
- isCreatingDraft?: boolean;
128
- }
31
+ // ✅ CORRECT — always include operation
32
+ useForm({ bdo: product, operation: FormOperation.Create, mode: ValidationMode.OnBlur });
33
+ useForm({ bdo: product, operation: FormOperation.Update, recordId: id, mode: ValidationMode.OnBlur });
129
34
  ```
130
35
 
131
- ### FormItemType (Item Proxy)
132
-
133
- ```typescript
134
- type FormItemType<TEditable, TReadonly> = {
135
- // Editable field accessors
136
- [K in keyof TEditable]: EditableFormFieldAccessorType<TEditable[K]>;
137
- } & {
138
- // Readonly field accessors
139
- [K in keyof TReadonly]: ReadonlyFormFieldAccessorType<TReadonly[K]>;
140
- } & {
141
- // Direct access
142
- readonly _id: string | undefined;
143
-
144
- // Methods
145
- toJSON(): Partial<TEditable & TReadonly>;
146
- validate(): Promise<boolean>;
147
- };
148
- ```
36
+ ### 2. Annotating ternary with UseFormOptionsType (TS2322)
149
37
 
150
- ### Field Accessor Types
38
+ `UseFormOptionsType` is a discriminated union. Type-annotating a variable prevents TS narrowing. NEVER annotate — call useForm inline in each branch.
151
39
 
152
40
  ```typescript
153
- // For editable fields
154
- interface EditableFormFieldAccessorType<T> {
155
- readonly label: string;
156
- readonly required: boolean;
157
- readonly readOnly: false;
158
- readonly defaultValue: unknown;
159
- readonly meta: BaseFieldMetaType;
160
- get(): T | undefined;
161
- set(value: T): void;
162
- validate(): ValidationResultType;
163
- }
164
-
165
- // For readonly fields (no set method)
166
- interface ReadonlyFormFieldAccessorType<T> {
167
- readonly label: string;
168
- readonly required: boolean;
169
- readonly readOnly: true;
170
- readonly defaultValue: unknown;
171
- readonly meta: BaseFieldMetaType;
172
- get(): T | undefined;
173
- validate(): ValidationResultType;
174
- }
41
+ // WRONG — type annotation prevents union narrowing (TS2322)
42
+ const options: UseFormOptionsType<typeof bdo> = id
43
+ ? { bdo, operation: FormOperation.Update, recordId: id, mode: ValidationMode.OnBlur }
44
+ : { bdo, operation: FormOperation.Create, mode: ValidationMode.OnBlur };
45
+ const formResult = useForm(options);
46
+
47
+ // CORRECT — call useForm inline, no type annotation
48
+ const formResult = id
49
+ ? useForm({ bdo, operation: FormOperation.Update, recordId: id, mode: ValidationMode.OnBlur })
50
+ : useForm({ bdo, operation: FormOperation.Create, mode: ValidationMode.OnBlur });
51
+ const { register, handleSubmit, watch, setValue, item, formState: { errors, isSubmitting }, isLoading } = formResult;
175
52
  ```
176
53
 
177
- ### File & Image Value Types
54
+ ### 3. Using `.options` on StringField (TS2339)
178
55
 
179
- ```typescript
180
- // Metadata for an uploaded file or image
181
- interface FileType {
182
- _id: string;
183
- _name: string;
184
- FileName: string;
185
- FileExtension: string;
186
- Size: number;
187
- ContentType: string;
188
- }
56
+ ONLY `SelectField` has `.options` getter. `StringField` with `Constraint.Enum` does NOT — use hardcoded `<option>` values from the BDO file.
189
57
 
190
- type ImageFieldType = FileType | null; // Single image, nullable
191
- type FileFieldType = FileType[]; // Array of files
192
-
193
- // Request a thumbnail or preview variant from the backend
194
- type AttachmentViewType = "thumbnail" | "preview";
58
+ ```tsx
59
+ // Check the BDO class to determine field type:
60
+ // new SelectField({...}) → has .options → use bdo.field.options.map()
61
+ // new StringField({..."Constraint": {"Enum": [...]}}) NO .options → hardcode from Enum array
62
+
63
+ // ❌ WRONG — StringField has no .options (TS2339)
64
+ // Given: readonly status = new StringField({... "Constraint": { "Enum": ["Active", "Discontinued"] }})
65
+ <select {...register(bdo.status.id)}>
66
+ {bdo.status.options.map((opt) => ( // TS2339: Property 'options' does not exist on StringField
67
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
68
+ ))}
69
+ </select>
70
+
71
+ // ✅ CORRECT for StringField with Enum — hardcode options from BDO Constraint.Enum array
72
+ <select {...register(bdo.status.id)}>
73
+ <option value="">Select {bdo.status.label}</option>
74
+ <option value="Active">Active</option>
75
+ <option value="Discontinued">Discontinued</option>
76
+ </select>
77
+
78
+ // ✅ CORRECT for SelectField — use .options getter
79
+ // Given: readonly status = new SelectField({...})
80
+ <select {...register(bdo.status.id)}>
81
+ <option value="">Select {bdo.status.label}</option>
82
+ {bdo.status.options.map((opt) => (
83
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
84
+ ))}
85
+ </select>
195
86
  ```
196
87
 
197
- ### File & Image Accessor Types
88
+ ### 4. Using register() for BooleanField
198
89
 
199
- Image and File fields extend the base accessor with attachment operations.
200
- Editable accessors get `upload` and `deleteAttachment`; readonly accessors only get download.
201
-
202
- ```typescript
203
- // Editable Image — single file
204
- interface EditableImageFieldAccessorType
205
- extends EditableFormFieldAccessorType<ImageFieldType> {
206
- upload(file: File): Promise<FileType>;
207
- getDownloadUrl(viewType?: AttachmentViewType): Promise<FileDownloadResponseType>;
208
- deleteAttachment(): Promise<void>;
209
- }
210
-
211
- // Readonly Image — download only
212
- interface ReadonlyImageFieldAccessorType
213
- extends ReadonlyFormFieldAccessorType<ImageFieldType> {
214
- getDownloadUrl(viewType?: AttachmentViewType): Promise<FileDownloadResponseType>;
215
- }
90
+ HTML checkboxes don't work with register(). Use watch/setValue.
216
91
 
217
- // Editable File — multi-file
218
- interface EditableFileFieldAccessorType
219
- extends EditableFormFieldAccessorType<FileFieldType> {
220
- upload(files: File[]): Promise<FileType[]>;
221
- getDownloadUrl(attachmentId: string, viewType?: AttachmentViewType): Promise<FileDownloadResponseType>;
222
- getDownloadUrls(viewType?: AttachmentViewType): Promise<FileDownloadResponseType[]>;
223
- deleteAttachment(attachmentId: string): Promise<void>;
224
- }
92
+ ```tsx
93
+ // ❌ WRONG
94
+ <input type="checkbox" {...register(bdo.is_active.id)} />
225
95
 
226
- // Readonly File — download only
227
- interface ReadonlyFileFieldAccessorType
228
- extends ReadonlyFormFieldAccessorType<FileFieldType> {
229
- getDownloadUrl(attachmentId: string, viewType?: AttachmentViewType): Promise<FileDownloadResponseType>;
230
- getDownloadUrls(viewType?: AttachmentViewType): Promise<FileDownloadResponseType[]>;
231
- }
96
+ // CORRECT
97
+ <Checkbox
98
+ checked={Boolean(watch(bdo.is_active.id))}
99
+ onCheckedChange={(v) => setValue(bdo.is_active.id, v as boolean, { shouldDirty: true })}
100
+ />
232
101
  ```
233
102
 
234
- ---
235
-
236
- ## Basic Example
103
+ ### 5. Using `<input>` for ReferenceField (should use `<ReferenceSelect>`)
237
104
 
238
- ### Create Mode
105
+ ReferenceField stores an object `{ _id, _name, ... }`, not a string. Use the pre-built component.
239
106
 
240
107
  ```tsx
241
- import { useMemo } from "react";
242
- import { useForm } from "@ram_28/kf-ai-sdk/form";
243
- import { ValidationMode } from "@ram_28/kf-ai-sdk/form";
244
- import type {
245
- UseFormOptionsType,
246
- UseFormReturnType,
247
- FormItemType,
248
- FormRegisterType,
249
- HandleSubmitType,
250
- } from "@ram_28/kf-ai-sdk/form/types";
251
- import type { FieldErrors } from "react-hook-form";
252
- import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
253
- import { SellerProduct } from "../bdo/seller/Product";
254
- import type {
255
- SellerProductEditableFieldType,
256
- SellerProductReadonlyFieldType,
257
- } from "../bdo/seller/Product";
258
-
259
- function CreateProductForm() {
260
- // 1. Instantiate BDO (memoized)
261
- const product: SellerProduct = useMemo(() => new SellerProduct(), []);
262
-
263
- // 2. Form options with explicit type
264
- const formOptions: UseFormOptionsType<SellerProduct> = {
265
- bdo: product,
266
- defaultValues: {
267
- Title: "",
268
- Price: 0,
269
- Stock: 0,
270
- } satisfies Partial<SellerProductEditableFieldType>,
271
- mode: ValidationMode.OnBlur,
272
- };
273
-
274
- // 3. Initialize form with typed destructuring
275
- const {
276
- register,
277
- handleSubmit,
278
- item,
279
- errors,
280
- isSubmitting,
281
- }: {
282
- register: FormRegisterType<SellerProductEditableFieldType, SellerProductReadonlyFieldType>;
283
- handleSubmit: HandleSubmitType<CreateUpdateResponseType>;
284
- item: FormItemType<SellerProductEditableFieldType, SellerProductReadonlyFieldType>;
285
- errors: FieldErrors<SellerProductEditableFieldType & SellerProductReadonlyFieldType>;
286
- isSubmitting: boolean;
287
- } = useForm(formOptions);
288
-
289
- // 4. Typed handlers
290
- const onSuccess = (data: CreateUpdateResponseType): void => {
291
- console.log("Created with ID:", data._id);
292
- };
293
-
294
- const onError = (error: FieldErrors | Error): void => {
295
- if (error instanceof Error) {
296
- console.error("API Error:", error.message);
297
- } else {
298
- console.error("Validation errors:", error);
299
- }
300
- };
301
-
302
- // Type for item proxy
303
- type ProductItem = FormItemType<
304
- SellerProductEditableFieldType,
305
- SellerProductReadonlyFieldType
306
- >;
307
-
308
- return (
309
- <form onSubmit={handleSubmit(onSuccess, onError)}>
310
- {/* Use field.id for register */}
311
- <div>
312
- <label>{product.Title.label}</label>
313
- <input {...register(product.Title.id)} />
314
- {errors.Title && <span>{errors.Title.message}</span>}
315
- </div>
316
-
317
- <div>
318
- <label>{product.Price.label}</label>
319
- <input type="number" {...register(product.Price.id)} />
320
- {errors.Price && <span>{errors.Price.message}</span>}
321
- </div>
322
-
323
- <button type="submit" disabled={isSubmitting}>
324
- {isSubmitting ? "Creating..." : "Create Product"}
325
- </button>
326
- </form>
327
- );
328
- }
108
+ // WRONG text input for reference field
109
+ <input {...register(bdo.category.id)} />
110
+
111
+ // CORRECT
112
+ <ReferenceSelect
113
+ bdoField={bdo.category}
114
+ value={watch(bdo.category.id)}
115
+ onChange={(val) => setValue(bdo.category.id, val, { shouldDirty: true })}
116
+ />
329
117
  ```
330
118
 
331
- ### Edit Mode
119
+ ### 6. Using custom Image/File upload (should use template components)
332
120
 
333
- ```tsx
334
- import { useMemo } from "react";
335
- import { useForm } from "@ram_28/kf-ai-sdk/form";
336
- import { ValidationMode } from "@ram_28/kf-ai-sdk/form";
337
- import type {
338
- UseFormOptionsType,
339
- UseFormReturnType,
340
- } from "@ram_28/kf-ai-sdk/form/types";
341
- import type { FieldErrors } from "react-hook-form";
342
- import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
343
- import { SellerProduct } from "../bdo/seller/Product";
344
- import type {
345
- SellerProductEditableFieldType,
346
- SellerProductReadonlyFieldType,
347
- } from "../bdo/seller/Product";
348
-
349
- interface EditProductFormProps {
350
- productId: string;
351
- onClose: () => void;
352
- }
353
-
354
- function EditProductForm({ productId, onClose }: EditProductFormProps) {
355
- const product: SellerProduct = useMemo(() => new SellerProduct(), []);
121
+ Use `<ImageUpload>` and `<FileUpload>`. CRITICAL: `instanceId` must work for BOTH edit and create mode.
356
122
 
357
- // Form options for edit mode
358
- const formOptions: UseFormOptionsType<SellerProduct> = {
359
- bdo: product,
360
- recordId: productId, // Triggers edit mode - fetches existing record
361
- mode: ValidationMode.OnBlur,
362
- };
363
-
364
- // Typed destructuring
365
- const {
366
- register,
367
- handleSubmit,
368
- item,
369
- errors,
370
- isLoading,
371
- isSubmitting,
372
- loadError,
373
- }: {
374
- register: FormRegisterType<SellerProductEditableFieldType, SellerProductReadonlyFieldType>;
375
- handleSubmit: HandleSubmitType<CreateUpdateResponseType>;
376
- item: FormItemType<SellerProductEditableFieldType, SellerProductReadonlyFieldType>;
377
- errors: FieldErrors<SellerProductEditableFieldType & SellerProductReadonlyFieldType>;
378
- isLoading: boolean;
379
- isSubmitting: boolean;
380
- loadError: Error | null;
381
- } = useForm(formOptions);
382
-
383
- if (isLoading) return <div>Loading...</div>;
384
- if (loadError) return <div>Error: {loadError.message}</div>;
385
-
386
- const onSuccess = (data: CreateUpdateResponseType): void => {
387
- console.log("Updated:", data._id);
388
- onClose();
389
- };
390
-
391
- const onError = (error: FieldErrors | Error): void => {
392
- console.error("Error:", error);
393
- };
394
-
395
- return (
396
- <form onSubmit={handleSubmit(onSuccess, onError)}>
397
- {/* Readonly fields are auto-disabled */}
398
- <div>
399
- <label>{product.ASIN.label}</label>
400
- <input {...register(product.ASIN.id)} />
401
- {/* This input is automatically disabled: true for readonly fields */}
402
- </div>
403
-
404
- {/* Editable fields */}
405
- <div>
406
- <label>{product.Title.label}</label>
407
- <input {...register(product.Title.id)} />
408
- {errors.Title && <span>{errors.Title.message}</span>}
409
- </div>
123
+ ```tsx
124
+ // WRONG — instanceId={id} is undefined in create mode
125
+ <ImageUpload field={item.icon} value={watch(bdo.icon.id)} boId={bdo.meta._id} instanceId={id} fieldId={bdo.icon.id} />
126
+
127
+ // ✅ CORRECT — ImageUpload (single image)
128
+ <ImageUpload
129
+ field={item.product_image}
130
+ value={watch(bdo.product_image.id)}
131
+ boId={bdo.meta._id}
132
+ instanceId={id || String(watch("_id") ?? "")}
133
+ fieldId={bdo.product_image.id}
134
+ />
410
135
 
411
- <button type="submit" disabled={isSubmitting}>
412
- {isSubmitting ? "Saving..." : "Save Changes"}
413
- </button>
414
- </form>
415
- );
416
- }
136
+ // CORRECT — FileUpload (multi-file)
137
+ <FileUpload
138
+ field={item.specification_document}
139
+ value={watch(bdo.specification_document.id)}
140
+ boId={bdo.meta._id}
141
+ instanceId={id || String(watch("_id") ?? "")}
142
+ fieldId={bdo.specification_document.id}
143
+ />
417
144
  ```
418
145
 
419
- ---
420
-
421
- ## register() Function
422
-
423
- The `register` function extends React Hook Form's standard register with automatic readonly field handling.
146
+ 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.
424
147
 
425
- ### Behavior
148
+ ### 7. Wrong handleSubmit onSuccess type
426
149
 
427
150
  ```typescript
428
- // For editable fields - standard result
429
- register(product.Title.id)
430
- // => UseFormRegisterReturn
151
+ // WRONG
152
+ const onSuccess = (data: unknown) => { ... };
431
153
 
432
- // For readonly fields - includes disabled: true
433
- register(product.ASIN.id)
434
- // => UseFormRegisterReturn & { disabled: true }
154
+ // CORRECT CreateUpdateResponseType = { _id: string }
155
+ import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
156
+ const onSuccess = (data: CreateUpdateResponseType) => { toast.success("Saved"); navigate("/list"); };
435
157
  ```
436
158
 
437
- ### Usage
159
+ ### 8. Passing FieldType instead of BDO instance
438
160
 
439
- ```tsx
440
- // Always use field.id (not hardcoded strings)
441
- <input {...register(product.Title.id)} />
442
- <input {...register(product.Price.id)} />
443
-
444
- // Readonly fields are auto-disabled
445
- <input {...register(product.ASIN.id)} /> // disabled: true added automatically
161
+ ```typescript
162
+ // WRONG
163
+ useForm<ProductFieldType>({ ... });
446
164
 
447
- // With additional options
448
- <input {...register(product.Email.id, { required: true })} />
165
+ // CORRECT — pass BDO class instance
166
+ const product = useMemo(() => new SellerProduct(), []);
167
+ useForm({ bdo: product, operation: FormOperation.Create, mode: ValidationMode.OnBlur });
449
168
  ```
450
169
 
451
- ---
452
-
453
- ## handleSubmit() Function
454
-
455
- Custom handleSubmit that handles API calls automatically and filters payload to editable fields only.
456
-
457
- ### Type
170
+ ### 9. Wrong default values for date fields
458
171
 
459
172
  ```typescript
460
- type HandleSubmitType<TRead = unknown> = (
461
- onSuccess?: (data: TRead, e?: React.BaseSyntheticEvent) => void | Promise<void>,
462
- onError?: (error: FieldErrors | Error, e?: React.BaseSyntheticEvent) => void | Promise<void>,
463
- ) => (e?: React.BaseSyntheticEvent) => Promise<void>;
464
- ```
465
-
466
- ### Execution Flow
467
-
468
- 1. Validation runs (type + expression validation)
469
- 2. If **valid**: Filters payload to editable fields -> Calls `bdo.create()` or `bdo.update()` -> Calls `onSuccess(result)`
470
- 3. If **invalid**: Calls `onError(FieldErrors)`
471
- 4. API errors: Calls `onError(Error)`
472
-
473
- ### Usage
173
+ // WRONG empty string causes type errors
174
+ defaultValues: { start_date: "" }
474
175
 
475
- ```tsx
476
- const onSuccess = (data: CreateUpdateResponseType) => {
477
- console.log("Saved with ID:", data._id);
478
- router.push("/products");
479
- };
480
-
481
- const onError = (error: FieldErrors | Error) => {
482
- if (error instanceof Error) {
483
- // API error
484
- toast.error(`Failed: ${error.message}`);
485
- } else {
486
- // Validation errors (already shown by field error messages)
487
- toast.error("Please fix the errors above");
488
- }
489
- };
490
-
491
- <form onSubmit={handleSubmit(onSuccess, onError)}>
492
- {/* form fields */}
493
- </form>
176
+ // ✅ CORRECT
177
+ defaultValues: { start_date: undefined }
494
178
  ```
495
179
 
496
180
  ---
497
181
 
498
- ## item Proxy
499
-
500
- The `item` object provides field-level access with typed accessors.
501
-
502
- ### Field Access
182
+ ## Complete Form Example (Create + Edit)
503
183
 
504
184
  ```tsx
505
- // Get field value
506
- const title = item.Title.get();
507
-
508
- // Set field value (editable fields only)
509
- item.Title.set("New Title");
185
+ "use no memo";
510
186
 
511
- // Access field properties
512
- item.Title.id // "Title"
513
- item.Title.label // "Product Title"
514
- item.Title.readOnly // false
515
-
516
- // Validate single field
517
- const result = item.Title.validate();
518
- // => { valid: boolean, errors: string[] }
519
-
520
- // Direct _id access
521
- const recordId = item._id;
522
-
523
- // Get all values as JSON
524
- const data = item.toJSON();
525
-
526
- // Validate all fields
527
- const isValid = await item.validate();
528
- ```
529
-
530
- ### Readonly Field Behavior
531
-
532
- ```tsx
533
- // Readonly fields don't have set() method
534
- item.ASIN.get() // Works
535
- item.ASIN.set("...") // TypeScript error - method doesn't exist
536
- item.ASIN.readOnly // true
537
- ```
538
-
539
- ---
540
-
541
- ## Validation
542
-
543
- useForm provides three-phase validation:
544
-
545
- ### Phase 1: Type Validation
546
- From field classes (StringField, NumberField, etc.)
547
-
548
- ### Phase 2: Constraint Validation
549
- From field meta constraints (required, string length, number integerPart/fractionPart).
550
- Controlled by the `enableConstraintValidation` option (default: `true`).
551
-
552
- ### Phase 3: Expression Validation
553
- From backend schema rules (automatically fetched)
554
-
555
- ### Validation Timing
556
-
557
- Controlled by the `mode` option:
558
-
559
- ```typescript
560
- import { ValidationMode } from "@ram_28/kf-ai-sdk/form";
561
-
562
- useForm({
563
- bdo: product,
564
- mode: ValidationMode.OnBlur, // Validate on blur (default)
565
- // mode: ValidationMode.OnChange, // Validate on every keystroke
566
- // mode: ValidationMode.OnSubmit, // Validate only on submit
567
- });
568
- ```
569
-
570
- ### Readonly Field Handling
571
-
572
- - Readonly fields are **skipped** during validation
573
- - They never generate validation errors
574
- - `register()` auto-adds `disabled: true`
575
-
576
- ---
577
-
578
- ## Complete Example
579
-
580
- ```tsx
581
- import { useMemo, useState } from "react";
582
- import { useForm } from "@ram_28/kf-ai-sdk/form";
583
- import { ValidationMode, FormOperation } from "@ram_28/kf-ai-sdk/form";
584
- import type {
585
- UseFormOptionsType,
586
- UseFormReturnType,
587
- FormItemType,
588
- FormRegisterType,
589
- HandleSubmitType,
590
- } from "@ram_28/kf-ai-sdk/form/types";
591
- import type { FieldErrors, UseFormWatch, UseFormSetValue } from "react-hook-form";
187
+ import { useMemo } from "react";
188
+ import { useNavigate, useParams } from "react-router-dom";
189
+ import { useForm, ValidationMode, FormOperation } from "@ram_28/kf-ai-sdk/form";
592
190
  import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
593
- import { SellerProduct } from "../bdo/seller/Product";
594
- import type {
595
- SellerProductEditableFieldType,
596
- SellerProductReadonlyFieldType,
597
- } from "../bdo/seller/Product";
598
-
599
- // Combine editable + readonly for errors type
600
- type AllFieldsType = SellerProductEditableFieldType & SellerProductReadonlyFieldType;
601
-
602
- interface ProductFormProps {
603
- productId?: string; // Undefined for create, defined for edit
604
- onSuccess: () => void;
605
- }
606
-
607
- function ProductForm({ productId, onSuccess }: ProductFormProps) {
608
- const [generalError, setGeneralError] = useState<string | null>(null);
609
- const product: SellerProduct = useMemo(() => new SellerProduct(), []);
610
-
611
- // Form options with explicit types
612
- const formOptions: UseFormOptionsType<SellerProduct> = {
613
- bdo: product,
614
- recordId: productId,
615
- defaultValues: {
616
- Title: "",
617
- Description: "",
618
- Price: 0,
619
- Stock: 0,
620
- } satisfies Partial<SellerProductEditableFieldType>,
621
- mode: ValidationMode.OnBlur,
191
+ import { AdminProduct } from "@/bdo/admin/Product";
192
+ import { ReferenceSelect } from "@/components/ui/reference-select";
193
+ import { ImageUpload } from "@/components/ui/image-upload";
194
+ import { FileUpload } from "@/components/ui/file-upload";
195
+ import { Checkbox } from "@/components/ui/checkbox";
196
+ import { toast } from "sonner";
197
+
198
+ export default function ProductForm() {
199
+ const { id } = useParams<{ id: string }>();
200
+ const navigate = useNavigate();
201
+ const bdo = useMemo(() => new AdminProduct(), []);
202
+
203
+ // Create/Edit ternary — NO type annotation on result
204
+ const formResult = id
205
+ ? useForm({ bdo, operation: FormOperation.Update, recordId: id, mode: ValidationMode.OnBlur })
206
+ : useForm({ bdo, operation: FormOperation.Create, mode: ValidationMode.OnBlur });
207
+
208
+ const { register, handleSubmit, watch, setValue, item, formState: { errors, isSubmitting }, isLoading } = formResult;
209
+
210
+ // Loading guard AFTER all hooks
211
+ 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>;
212
+
213
+ const onSuccess = (data: CreateUpdateResponseType) => {
214
+ toast.success(id ? "Updated" : "Created");
215
+ navigate("/products");
622
216
  };
623
217
 
624
- // Typed destructuring
625
- const {
626
- register,
627
- handleSubmit,
628
- item,
629
- errors,
630
- isLoading,
631
- isSubmitting,
632
- loadError,
633
- watch,
634
- setValue,
635
- operation,
636
- }: {
637
- register: FormRegisterType<SellerProductEditableFieldType, SellerProductReadonlyFieldType>;
638
- handleSubmit: HandleSubmitType<CreateUpdateResponseType>;
639
- item: FormItemType<SellerProductEditableFieldType, SellerProductReadonlyFieldType>;
640
- errors: FieldErrors<AllFieldsType>;
641
- isLoading: boolean;
642
- isSubmitting: boolean;
643
- loadError: Error | null;
644
- watch: UseFormWatch<AllFieldsType>;
645
- setValue: UseFormSetValue<SellerProductEditableFieldType>;
646
- operation: "create" | "update";
647
- } = useForm(formOptions);
648
-
649
- // Loading state
650
- if (isLoading) {
651
- return <div>Loading product...</div>;
652
- }
653
-
654
- if (loadError) {
655
- return <div>Error loading product: {loadError.message}</div>;
656
- }
657
-
658
- // Typed handlers
659
- const onFormSuccess = (data: CreateUpdateResponseType): void => {
660
- setGeneralError(null);
661
- console.log("Saved with ID:", data._id);
662
- onSuccess();
218
+ const onError = (error: any) => {
219
+ toast.error(error instanceof Error ? error.message : "Please fix errors above");
663
220
  };
664
221
 
665
- const onFormError = (error: FieldErrors | Error): void => {
666
- if (error instanceof Error) {
667
- setGeneralError(error.message);
668
- } else {
669
- setGeneralError("Please fix the validation errors");
670
- }
671
- };
672
-
673
- // Watch for dynamic behavior
674
- const currentPrice = watch(product.Price.id);
675
-
676
222
  return (
677
- <form onSubmit={handleSubmit(onFormSuccess, onFormError)}>
678
- <h2>{operation === FormOperation.Create ? "Create Product" : "Edit Product"}</h2>
679
-
680
- {generalError && (
681
- <div className="error-banner">{generalError}</div>
682
- )}
683
-
684
- {/* Readonly field (auto-disabled) */}
685
- {operation === FormOperation.Update && (
686
- <div className="field">
687
- <label>{product.ASIN.label}</label>
688
- <input {...register(product.ASIN.id)} />
689
- </div>
690
- )}
691
-
692
- {/* Title */}
693
- <div className="field">
694
- <label>{product.Title.label}</label>
695
- <input
696
- {...register(product.Title.id)}
697
- placeholder="Enter product title"
698
- />
699
- {errors.Title && (
700
- <span className="error">{errors.Title.message}</span>
701
- )}
223
+ <form onSubmit={handleSubmit(onSuccess, onError)} className="space-y-6">
224
+ {/* StringField register */}
225
+ <div>
226
+ <label>{bdo.product_name.label}{bdo.product_name.required && <span className="text-red-500"> *</span>}</label>
227
+ <input {...register(bdo.product_name.id)} className="w-full border rounded px-3 py-2" />
228
+ {errors.product_name && <p className="text-red-600 text-sm">{errors.product_name.message}</p>}
702
229
  </div>
703
230
 
704
- {/* Description */}
705
- <div className="field">
706
- <label>{product.Description.label}</label>
707
- <textarea {...register(product.Description.id)} rows={4} />
708
- {errors.Description && (
709
- <span className="error">{errors.Description.message}</span>
710
- )}
231
+ {/* NumberField — register */}
232
+ <div>
233
+ <label>{bdo.unit_price.label}</label>
234
+ <input type="number" step="0.01" {...register(bdo.unit_price.id)} className="w-full border rounded px-3 py-2" />
235
+ {errors.unit_price && <p className="text-red-600 text-sm">{errors.unit_price.message}</p>}
711
236
  </div>
712
237
 
713
- {/* Price */}
714
- <div className="field">
715
- <label>{product.Price.label}</label>
716
- <input
717
- type="number"
718
- step="0.01"
719
- {...register(product.Price.id)}
720
- />
721
- {errors.Price && (
722
- <span className="error">{errors.Price.message}</span>
723
- )}
724
- {currentPrice > 1000 && (
725
- <span className="warning">High price - verify before saving</span>
726
- )}
238
+ {/* StringField with Constraint.Enum — hardcoded options (NO .options getter) */}
239
+ <div>
240
+ <label>{bdo.status.label}</label>
241
+ <select {...register(bdo.status.id)} className="w-full border rounded px-3 py-2">
242
+ <option value="">Select {bdo.status.label}</option>
243
+ <option value="Active">Active</option>
244
+ <option value="Discontinued">Discontinued</option>
245
+ </select>
246
+ {errors.status && <p className="text-red-600 text-sm">{errors.status.message}</p>}
727
247
  </div>
728
248
 
729
- {/* Stock */}
730
- <div className="field">
731
- <label>{product.Stock.label}</label>
732
- <input
733
- type="number"
734
- {...register(product.Stock.id)}
249
+ {/* ReferenceField — ReferenceSelect component */}
250
+ <div>
251
+ <label>{bdo.category.label}</label>
252
+ <ReferenceSelect
253
+ bdoField={bdo.category}
254
+ value={watch(bdo.category.id)}
255
+ onChange={(val) => setValue(bdo.category.id, val, { shouldDirty: true })}
735
256
  />
736
- {errors.Stock && (
737
- <span className="error">{errors.Stock.message}</span>
738
- )}
257
+ {errors.category && <p className="text-red-600 text-sm">{String(errors.category.message ?? "")}</p>}
739
258
  </div>
740
259
 
741
- {/* Category (using setValue for select) */}
742
- <div className="field">
743
- <label>{product.Category.label}</label>
744
- <select
745
- value={watch(product.Category.id) || ""}
746
- onChange={(e) => setValue(product.Category.id, e.target.value)}
747
- >
748
- <option value="">Select category</option>
749
- <option value="Electronics">Electronics</option>
750
- <option value="Clothing">Clothing</option>
751
- <option value="Books">Books</option>
752
- </select>
753
- {errors.Category && (
754
- <span className="error">{errors.Category.message}</span>
755
- )}
260
+ {/* BooleanField watch + setValue */}
261
+ <div className="flex items-center gap-2">
262
+ <Checkbox
263
+ checked={Boolean(watch(bdo.is_active.id))}
264
+ onCheckedChange={(v) => setValue(bdo.is_active.id, v as boolean, { shouldDirty: true })}
265
+ />
266
+ <label>{bdo.is_active.label}</label>
756
267
  </div>
757
268
 
758
- {/* Actions */}
759
- <div className="actions">
760
- <button type="submit" disabled={isSubmitting}>
761
- {isSubmitting
762
- ? (operation === FormOperation.Create ? "Creating..." : "Saving...")
763
- : (operation === FormOperation.Create ? "Create Product" : "Save Changes")
764
- }
765
- </button>
269
+ {/* ImageField — ImageUpload component */}
270
+ <div>
271
+ <label>{bdo.product_image.label}</label>
272
+ <ImageUpload
273
+ field={item.product_image}
274
+ value={watch(bdo.product_image.id)}
275
+ boId={bdo.meta._id}
276
+ instanceId={id || String(watch("_id") ?? "")}
277
+ fieldId={bdo.product_image.id}
278
+ />
766
279
  </div>
767
280
 
768
- {/* Debug: Show current values */}
769
- <details>
770
- <summary>Current Values</summary>
771
- <pre>{JSON.stringify(item.toJSON(), null, 2)}</pre>
772
- </details>
773
- </form>
774
- );
775
- }
776
- ```
777
-
778
- ---
779
-
780
- ## File & Image Attachments in Forms
781
-
782
- Image and File fields get attachment methods on the `item` accessor automatically.
783
- `upload()` updates local field state only — the form's `handleSubmit` persists everything to the backend.
784
- `deleteAttachment()` is atomic — it removes the file from storage and the backend record immediately.
785
-
786
- ```tsx
787
- // Assuming SellerProduct BDO has:
788
- // readonly ProductImage = new ImageField({ _id: "ProductImage", Name: "Product Image", Type: "Image" });
789
- // readonly Attachments = new FileField({ _id: "Attachments", Name: "Attachments", Type: "File" });
790
-
791
- function ProductFormWithAttachments({ productId }: { productId?: string }) {
792
- const product = useMemo(() => new SellerProduct(), []);
793
-
794
- const { register, handleSubmit, item, errors, isSubmitting, watch } = useForm({
795
- bdo: product,
796
- recordId: productId,
797
- mode: ValidationMode.OnBlur,
798
- });
799
-
800
- // --- Image field (single file) ---
801
-
802
- const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
803
- const file = e.target.files?.[0];
804
- if (!file) return;
805
-
806
- // Uploads to storage, sets local value (FileType)
807
- // Validates extension client-side before uploading
808
- const metadata = await item.ProductImage.upload(file);
809
- console.log("Uploaded image:", metadata.FileName);
810
- };
811
-
812
- const handleImageDelete = async () => {
813
- // Atomic — deletes from storage + backend immediately
814
- await item.ProductImage.deleteAttachment();
815
- };
816
-
817
- const handleImagePreview = async () => {
818
- // Get a thumbnail URL (backend-generated variant)
819
- const { DownloadUrl } = await item.ProductImage.getDownloadUrl("thumbnail");
820
- window.open(DownloadUrl);
821
- };
822
-
823
- // --- File field (multi-file) ---
824
-
825
- const handleFilesUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
826
- const files = Array.from(e.target.files ?? []);
827
- if (!files.length) return;
828
-
829
- // Uploads all files in parallel, appends to existing array
830
- const uploaded = await item.Attachments.upload(files);
831
- console.log(`Uploaded ${uploaded.length} files`);
832
- };
833
-
834
- const handleFileDelete = async (attachmentId: string) => {
835
- // Removes single file from storage + backend + local array
836
- await item.Attachments.deleteAttachment(attachmentId);
837
- };
838
-
839
- const handleFileDownload = async (attachmentId: string) => {
840
- const { DownloadUrl } = await item.Attachments.getDownloadUrl(attachmentId);
841
- window.open(DownloadUrl);
842
- };
843
-
844
- // Current field values
845
- const currentImage = item.ProductImage.get(); // FileType | null
846
- const currentFiles = item.Attachments.get() ?? []; // FileType[]
847
-
848
- return (
849
- <form onSubmit={handleSubmit((data) => console.log("Saved:", data._id))}>
850
- {/* ... other fields with register() ... */}
851
-
852
- {/* Image field */}
853
- <div className="field">
854
- <label>{product.ProductImage.label}</label>
855
- {currentImage ? (
856
- <div>
857
- <span>{currentImage.FileName}</span>
858
- <button type="button" onClick={handleImagePreview}>Preview</button>
859
- <button type="button" onClick={handleImageDelete}>Remove</button>
860
- </div>
861
- ) : (
862
- <input type="file" accept="image/*" onChange={handleImageUpload} />
863
- )}
281
+ {/* FileField FileUpload component */}
282
+ <div>
283
+ <label>{bdo.specification_document.label}</label>
284
+ <FileUpload
285
+ field={item.specification_document}
286
+ value={watch(bdo.specification_document.id)}
287
+ boId={bdo.meta._id}
288
+ instanceId={id || String(watch("_id") ?? "")}
289
+ fieldId={bdo.specification_document.id}
290
+ />
864
291
  </div>
865
292
 
866
- {/* File field */}
867
- <div className="field">
868
- <label>{product.Attachments.label}</label>
869
- <input type="file" multiple onChange={handleFilesUpload} />
870
- <ul>
871
- {currentFiles.map((file) => (
872
- <li key={file._id}>
873
- {file.FileName} ({file.Size} bytes)
874
- <button type="button" onClick={() => handleFileDownload(file._id)}>Download</button>
875
- <button type="button" onClick={() => handleFileDelete(file._id)}>Delete</button>
876
- </li>
877
- ))}
878
- </ul>
293
+ {/* TextField textarea with register */}
294
+ <div>
295
+ <label>{bdo.description.label}</label>
296
+ <textarea {...register(bdo.description.id)} rows={4} className="w-full border rounded px-3 py-2" />
879
297
  </div>
880
298
 
881
- <button type="submit" disabled={isSubmitting}>
882
- {isSubmitting ? "Saving..." : "Save"}
299
+ <button type="submit" disabled={isSubmitting} className="px-4 py-2 bg-primary text-white rounded">
300
+ {isSubmitting ? "Saving..." : id ? "Update" : "Create"}
883
301
  </button>
884
302
  </form>
885
303
  );
886
304
  }
887
305
  ```
888
306
 
889
- **Key points:**
890
- - `upload()` validates file extensions client-side (throws on unsupported types)
891
- - Image upload replaces the current value; File upload appends to the array
892
- - `getDownloadUrl("thumbnail")` / `getDownloadUrl("preview")` request backend-generated variants
893
- - Readonly Image/File fields only have `getDownloadUrl` — no `upload` or `deleteAttachment`
894
-
895
307
  ---
896
308
 
897
- ## Constants Reference
898
-
899
- ```typescript
900
- import { ValidationMode, FormOperation } from "@ram_28/kf-ai-sdk/form";
309
+ ## Type Definitions
901
310
 
902
- // Validation timing
903
- ValidationMode.OnBlur // "onBlur"
904
- ValidationMode.OnChange // "onChange"
905
- ValidationMode.OnSubmit // "onSubmit"
906
- ValidationMode.OnTouched // "onTouched"
907
- ValidationMode.All // "all"
311
+ ### UseFormOptionsType (Discriminated Union)
908
312
 
909
- // Form operation
910
- FormOperation.Create // "create"
911
- FormOperation.Update // "update"
313
+ ```typescript
314
+ type UseFormOptionsType<B extends BaseBdo<any, any, any>> =
315
+ | { bdo: B; operation: "create"; defaultValues?: Partial<EditableFieldType>; mode?: ValidationModeType; }
316
+ | { bdo: B; operation: "update"; recordId: string; mode?: ValidationModeType; }
317
+ | { bdo: B; recordId?: string; defaultValues?: Partial<EditableFieldType>; mode?: ValidationModeType; };
912
318
  ```
913
319
 
914
- ---
915
-
916
- ## Common Mistakes
917
-
918
- ### 1. Passing FieldType instead of BDO instance
919
-
920
- `useForm` takes a BDO class **instance**, NOT a FieldType. The hook infers all types from the BDO class.
320
+ ### UseFormReturnType
921
321
 
922
322
  ```typescript
923
- // WRONG — FieldType as generic causes register/watch to return `never`
924
- useForm<ProductFieldType>({ ... });
925
-
926
- // WRONG passing FieldType as bdo
927
- useForm({ bdo: ProductFieldType });
928
-
929
- // ✅ CORRECT — pass a BDO class instance
930
- const product = useMemo(() => new SellerProduct(), []);
931
- useForm({ bdo: product, mode: ValidationMode.OnBlur });
323
+ interface UseFormReturnType<B> {
324
+ item: FormItemType<EditableFieldType, ReadonlyFieldType>; // Field accessors (.get(), .set(), .upload())
325
+ register: FormRegisterType; // Auto-disables readonly fields
326
+ handleSubmit: HandleSubmitType; // Auto-calls bdo.create() or bdo.update()
327
+ watch: UseFormWatch; // Watch field values by bdo.field.id
328
+ setValue: UseFormSetValue; // Set field values by bdo.field.id
329
+ getValues: UseFormGetValues;
330
+ control: Control;
331
+ formState: FormState;
332
+ errors: FieldErrors;
333
+ isLoading: boolean; // Fetching record data (edit mode)
334
+ isSubmitting: boolean;
335
+ isDirty: boolean;
336
+ loadError: Error | null;
337
+ }
932
338
  ```
933
339
 
934
- ### 2. Wrong handleSubmit onSuccess type
340
+ ### File & Image Types
935
341
 
936
342
  ```typescript
937
- // WRONG these types cause errors
938
- const onSuccess = (data: unknown) => { ... };
939
- const onSuccess = (data: Partial<EditableFieldType>) => { ... };
940
-
941
- // ✅ CORRECT — CreateUpdateResponseType = { _id: string }
942
- import type { CreateUpdateResponseType } from "@ram_28/kf-ai-sdk/api/types";
943
- const onSuccess = (data: CreateUpdateResponseType): void => {
944
- console.log("Saved:", data._id);
945
- };
343
+ interface FileType { _id: string; _name: string; FileName: string; FileExtension: string; Size: number; ContentType: string; }
344
+ type ImageFieldType = FileType | null; // Single image, nullable
345
+ type FileFieldType = FileType[]; // Array of files
946
346
  ```
947
347
 
948
- ### 3. Using register() for boolean fields
348
+ Image accessor: `item.field.get()` returns `FileType | null`. Has `upload(file: File)`, `deleteAttachment()`, `getDownloadUrl()`.
349
+ File accessor: `item.field.get()` returns `FileType[]`. Has `upload(files: File[])`, `deleteAttachment(id)`, `getDownloadUrl(id)`.
949
350
 
950
- Boolean fields need watch/setValue pattern because HTML checkboxes don't work with register().
351
+ ### Constants
951
352
 
952
353
  ```typescript
953
- // ❌ WRONG — register doesn't work correctly with checkboxes
954
- <input type="checkbox" {...register(bdo.IsActive.id)} />
955
-
956
- // ✅ CORRECT — use watch + setValue
957
- <Checkbox
958
- checked={Boolean(watch(bdo.IsActive.id))}
959
- onCheckedChange={(v) => setValue(bdo.IsActive.id, v as boolean)}
960
- />
354
+ FormOperation.Create // "create"
355
+ FormOperation.Update // "update"
356
+ ValidationMode.OnBlur / .OnChange / .OnSubmit / .OnTouched / .All
961
357
  ```
962
358
 
963
- ### 4. Wrong default values for date fields
964
-
965
- ```typescript
966
- // ❌ WRONG — empty string causes type errors
967
- defaultValues: { StartDate: "" }
968
-
969
- // CORRECT use undefined for date fields
970
- defaultValues: { StartDate: undefined }
971
- ```
359
+ ### Field-Type to UI Component Mapping
360
+
361
+ | BDO Field Class | UI Pattern |
362
+ |---|---|
363
+ | `StringField` | `<input {...register(bdo.field.id)} />` |
364
+ | `StringField` with `Constraint.Enum` | `<select {...register(bdo.field.id)}>` with hardcoded `<option>` from Enum array |
365
+ | `SelectField` | `<select {...register(bdo.field.id)}>` with `bdo.field.options.map()` |
366
+ | `TextField` | `<textarea {...register(bdo.field.id)} />` |
367
+ | `NumberField` | `<input type="number" {...register(bdo.field.id)} />` |
368
+ | `BooleanField` | `<Checkbox checked={watch()} onCheckedChange={v => setValue()} />` |
369
+ | `DateField` | `<input type="date" {...register(bdo.field.id)} />` |
370
+ | `DateTimeField` | `<input type="datetime-local" {...register(bdo.field.id)} />` |
371
+ | `ReferenceField` | `<ReferenceSelect bdoField={bdo.field} value={watch()} onChange={...} />` |
372
+ | `ImageField` | `<ImageUpload field={item.field} value={watch()} boId={} instanceId={} fieldId={} />` |
373
+ | `FileField` | `<FileUpload field={item.field} value={watch()} boId={} instanceId={} fieldId={} />` |