@ram_28/kf-ai-sdk 1.0.16 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +2 -2
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/{client-C15j4O5B.cjs → client-DgtkT50N.cjs} +1 -1
- package/dist/{client-CfvLiGfP.js → client-V-WzUb8H.js} +9 -5
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/context.d.ts +1 -1
- package/dist/components/hooks/useKanban/context.d.ts.map +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +740 -740
- package/dist/kanban.cjs +1 -1
- package/dist/kanban.mjs +4 -2
- package/dist/{metadata-2FLBsFcf.cjs → metadata-0lZAfuTP.cjs} +1 -1
- package/dist/{metadata-DBcoDth-.js → metadata-B88D_pVS.js} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +1 -1
- package/docs/QUICK_REFERENCE.md +528 -0
- package/docs/useAuth.md +402 -0
- package/docs/useFilter.md +273 -0
- package/docs/useForm.md +629 -0
- package/docs/useKanban.md +421 -0
- package/docs/useTable.md +372 -0
- package/package.json +4 -2
- package/sdk/api/client.ts +7 -1
- package/sdk/components/hooks/useForm/useForm.ts +12 -2
- package/sdk/components/hooks/useKanban/context.ts +5 -3
package/docs/useForm.md
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
# useForm
|
|
2
|
+
|
|
3
|
+
## Brief Description
|
|
4
|
+
|
|
5
|
+
- Integrates react-hook-form with backend Business Data Object (BDO) schemas for automatic validation and computed field handling
|
|
6
|
+
- Fetches schema metadata from the API and generates validation rules, default values, and field permissions automatically
|
|
7
|
+
- Supports both create and update operations with automatic draft API calls for server-side computed fields
|
|
8
|
+
- Provides flattened state accessors (`errors`, `isValid`, `isDirty`) alongside react-hook-form's standard methods
|
|
9
|
+
|
|
10
|
+
## Type Reference
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { useForm } from "@ram_28/kf-ai-sdk/form";
|
|
14
|
+
import type {
|
|
15
|
+
// Core types
|
|
16
|
+
UseFormOptionsType,
|
|
17
|
+
UseFormReturnType,
|
|
18
|
+
FormOperationType,
|
|
19
|
+
FormModeType,
|
|
20
|
+
|
|
21
|
+
// Form field configuration
|
|
22
|
+
FormFieldConfigType,
|
|
23
|
+
FormSchemaConfigType,
|
|
24
|
+
FormFieldTypeType,
|
|
25
|
+
SelectOptionType,
|
|
26
|
+
FieldPermissionType,
|
|
27
|
+
|
|
28
|
+
// Result types
|
|
29
|
+
FieldValidationResultType,
|
|
30
|
+
SubmissionResultType,
|
|
31
|
+
|
|
32
|
+
// BDO Schema types (advanced)
|
|
33
|
+
BDOSchemaType,
|
|
34
|
+
BDOFieldDefinitionType,
|
|
35
|
+
SchemaValidationRuleType,
|
|
36
|
+
} from "@ram_28/kf-ai-sdk/form/types";
|
|
37
|
+
|
|
38
|
+
// Error utilities
|
|
39
|
+
import {
|
|
40
|
+
parseApiError,
|
|
41
|
+
isNetworkError,
|
|
42
|
+
isValidationError,
|
|
43
|
+
clearFormCache,
|
|
44
|
+
} from "@ram_28/kf-ai-sdk/form";
|
|
45
|
+
|
|
46
|
+
// Re-exported from react-hook-form (available from SDK)
|
|
47
|
+
import type {
|
|
48
|
+
UseFormRegister,
|
|
49
|
+
FieldErrors,
|
|
50
|
+
FormState,
|
|
51
|
+
Path,
|
|
52
|
+
PathValue,
|
|
53
|
+
} from "@ram_28/kf-ai-sdk/form.types";
|
|
54
|
+
|
|
55
|
+
// FieldErrors type from react-hook-form
|
|
56
|
+
// Maps field names to their error objects (message, type, etc.)
|
|
57
|
+
// Used in handleSubmit's onError callback for validation errors
|
|
58
|
+
type FieldErrors<T> = {
|
|
59
|
+
[K in keyof T]?: {
|
|
60
|
+
type: string;
|
|
61
|
+
message?: string;
|
|
62
|
+
ref?: React.RefObject<any>;
|
|
63
|
+
};
|
|
64
|
+
} & {
|
|
65
|
+
root?: Record<string, { type: string; message?: string }>; // Cross-field errors
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// FormState type from react-hook-form
|
|
69
|
+
// Contains all form state information
|
|
70
|
+
interface FormState<T> {
|
|
71
|
+
isDirty: boolean; // Form has been modified
|
|
72
|
+
isValid: boolean; // All validations pass
|
|
73
|
+
isSubmitting: boolean; // Form is being submitted
|
|
74
|
+
isSubmitted: boolean; // Form has been submitted at least once
|
|
75
|
+
isSubmitSuccessful: boolean; // Last submission was successful
|
|
76
|
+
isLoading: boolean; // Form is loading async default values
|
|
77
|
+
isValidating: boolean; // Form is validating
|
|
78
|
+
submitCount: number; // Number of times form was submitted
|
|
79
|
+
errors: FieldErrors<T>; // Current validation errors
|
|
80
|
+
touchedFields: Partial<Record<keyof T, boolean>>; // Fields that have been touched
|
|
81
|
+
dirtyFields: Partial<Record<keyof T, boolean>>; // Fields that have been modified
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Form operation type
|
|
85
|
+
type FormOperationType = "create" | "update";
|
|
86
|
+
|
|
87
|
+
// Form field input types
|
|
88
|
+
type FormFieldTypeType =
|
|
89
|
+
| "text"
|
|
90
|
+
| "number"
|
|
91
|
+
| "email"
|
|
92
|
+
| "password"
|
|
93
|
+
| "date"
|
|
94
|
+
| "datetime-local"
|
|
95
|
+
| "checkbox"
|
|
96
|
+
| "select"
|
|
97
|
+
| "textarea"
|
|
98
|
+
| "reference";
|
|
99
|
+
|
|
100
|
+
// Select option for dropdown fields
|
|
101
|
+
interface SelectOptionType {
|
|
102
|
+
value: any;
|
|
103
|
+
label: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Field permission for current user
|
|
107
|
+
interface FieldPermissionType {
|
|
108
|
+
editable: boolean;
|
|
109
|
+
readable: boolean;
|
|
110
|
+
hidden: boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Field rule IDs by category
|
|
114
|
+
interface FieldRuleIdsType {
|
|
115
|
+
validation: string[];
|
|
116
|
+
computation: string[];
|
|
117
|
+
businessLogic: string[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Form field configuration for rendering
|
|
121
|
+
interface FormFieldConfigType {
|
|
122
|
+
name: string;
|
|
123
|
+
type: FormFieldTypeType;
|
|
124
|
+
label: string;
|
|
125
|
+
required: boolean;
|
|
126
|
+
computed: boolean;
|
|
127
|
+
defaultValue?: any;
|
|
128
|
+
options?: SelectOptionType[];
|
|
129
|
+
validation: any;
|
|
130
|
+
description?: string;
|
|
131
|
+
_bdoField: BDOFieldDefinitionType;
|
|
132
|
+
permission: FieldPermissionType;
|
|
133
|
+
rules: FieldRuleIdsType;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Form schema configuration after processing
|
|
137
|
+
interface FormSchemaConfigType {
|
|
138
|
+
fields: Record<string, FormFieldConfigType>;
|
|
139
|
+
fieldOrder: string[];
|
|
140
|
+
computedFields: string[];
|
|
141
|
+
requiredFields: string[];
|
|
142
|
+
crossFieldValidation: SchemaValidationRuleType[];
|
|
143
|
+
rules: {
|
|
144
|
+
validation: Record<string, SchemaValidationRuleType>;
|
|
145
|
+
computation: Record<string, SchemaValidationRuleType>;
|
|
146
|
+
businessLogic: Record<string, SchemaValidationRuleType>;
|
|
147
|
+
};
|
|
148
|
+
fieldRules: Record<string, FieldRuleIdsType>;
|
|
149
|
+
rolePermissions?: Record<string, RolePermissionType>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// BDO field definition structure
|
|
153
|
+
interface BDOFieldDefinitionType {
|
|
154
|
+
Id: string;
|
|
155
|
+
Name: string;
|
|
156
|
+
Type:
|
|
157
|
+
| "String"
|
|
158
|
+
| "Number"
|
|
159
|
+
| "Boolean"
|
|
160
|
+
| "Date"
|
|
161
|
+
| "DateTime"
|
|
162
|
+
| "Reference"
|
|
163
|
+
| "Array"
|
|
164
|
+
| "Object";
|
|
165
|
+
Required?: boolean;
|
|
166
|
+
Unique?: boolean;
|
|
167
|
+
DefaultValue?: DefaultValueExpressionType;
|
|
168
|
+
Formula?: ComputedFieldFormulaType;
|
|
169
|
+
Computed?: boolean;
|
|
170
|
+
Validation?: string[] | SchemaValidationRuleType[];
|
|
171
|
+
Values?: FieldOptionsConfigType;
|
|
172
|
+
Description?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// BDO schema structure
|
|
176
|
+
interface BDOSchemaType {
|
|
177
|
+
Id: string;
|
|
178
|
+
Name: string;
|
|
179
|
+
Kind: "BusinessObject";
|
|
180
|
+
Description: string;
|
|
181
|
+
Rules: {
|
|
182
|
+
Computation?: Record<string, SchemaValidationRuleType>;
|
|
183
|
+
Validation?: Record<string, SchemaValidationRuleType>;
|
|
184
|
+
BusinessLogic?: Record<string, SchemaValidationRuleType>;
|
|
185
|
+
};
|
|
186
|
+
Fields: Record<string, BDOFieldDefinitionType>;
|
|
187
|
+
RolePermission: Record<string, RolePermissionType>;
|
|
188
|
+
Roles: Record<string, { Name: string; Description: string }>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Field validation result
|
|
192
|
+
interface FieldValidationResultType<T = Record<string, any>> {
|
|
193
|
+
isValid: boolean;
|
|
194
|
+
message?: string;
|
|
195
|
+
fieldName?: keyof T;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Form submission result
|
|
199
|
+
interface SubmissionResultType {
|
|
200
|
+
success: boolean;
|
|
201
|
+
data?: any;
|
|
202
|
+
error?: Error;
|
|
203
|
+
recordId?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Hook options
|
|
207
|
+
interface UseFormOptionsType<T> {
|
|
208
|
+
source: string;
|
|
209
|
+
operation: FormOperationType;
|
|
210
|
+
recordId?: string;
|
|
211
|
+
defaultValues?: Partial<T>;
|
|
212
|
+
mode?: "onBlur" | "onChange" | "onSubmit" | "onTouched" | "all";
|
|
213
|
+
enabled?: boolean;
|
|
214
|
+
userRole?: string;
|
|
215
|
+
schema?: BDOSchemaType;
|
|
216
|
+
onSchemaError?: (error: Error) => void; // Schema loading errors only
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Hook return type
|
|
220
|
+
interface UseFormReturnType<T> {
|
|
221
|
+
// React Hook Form methods
|
|
222
|
+
register: UseFormRegister<T>;
|
|
223
|
+
/**
|
|
224
|
+
* handleSubmit follows RHF pattern with optional callbacks:
|
|
225
|
+
* - onSuccess: Called with API response data on successful submission
|
|
226
|
+
* - onError: Called with FieldErrors (validation) or Error (API failure)
|
|
227
|
+
*
|
|
228
|
+
* Usage:
|
|
229
|
+
* form.handleSubmit() // No callbacks
|
|
230
|
+
* form.handleSubmit(onSuccess) // Success only
|
|
231
|
+
* form.handleSubmit(onSuccess, onError) // Both callbacks
|
|
232
|
+
* form.handleSubmit(undefined, onError) // Error only
|
|
233
|
+
*/
|
|
234
|
+
handleSubmit: (
|
|
235
|
+
onSuccess?: (data: T, e?: React.FormEvent) => void | Promise<void>,
|
|
236
|
+
onError?: (
|
|
237
|
+
error: FieldErrors<T> | Error,
|
|
238
|
+
e?: React.FormEvent,
|
|
239
|
+
) => void | Promise<void>,
|
|
240
|
+
) => (e?: React.FormEvent) => Promise<void>;
|
|
241
|
+
watch: <K extends Path<T>>(
|
|
242
|
+
name?: K,
|
|
243
|
+
) => K extends Path<T> ? PathValue<T, K> : T;
|
|
244
|
+
setValue: <K extends Path<T>>(name: K, value: PathValue<T, K>) => void;
|
|
245
|
+
reset: (values?: T) => void;
|
|
246
|
+
|
|
247
|
+
// Flattened state (recommended)
|
|
248
|
+
errors: FieldErrors<T>;
|
|
249
|
+
isValid: boolean;
|
|
250
|
+
isDirty: boolean;
|
|
251
|
+
isSubmitting: boolean;
|
|
252
|
+
isSubmitSuccessful: boolean;
|
|
253
|
+
|
|
254
|
+
// Legacy (backward compatible)
|
|
255
|
+
formState: FormState<T>;
|
|
256
|
+
|
|
257
|
+
// Loading states
|
|
258
|
+
isLoadingInitialData: boolean;
|
|
259
|
+
isLoadingRecord: boolean;
|
|
260
|
+
isLoading: boolean;
|
|
261
|
+
|
|
262
|
+
// Draft state
|
|
263
|
+
draftId: string | null;
|
|
264
|
+
isCreatingDraft: boolean;
|
|
265
|
+
|
|
266
|
+
// Error handling
|
|
267
|
+
loadError: Error | null;
|
|
268
|
+
hasError: boolean;
|
|
269
|
+
|
|
270
|
+
// Schema info
|
|
271
|
+
schema: BDOSchemaType | null;
|
|
272
|
+
schemaConfig: FormSchemaConfigType | null;
|
|
273
|
+
computedFields: Array<keyof T>;
|
|
274
|
+
requiredFields: Array<keyof T>;
|
|
275
|
+
|
|
276
|
+
// Field helpers
|
|
277
|
+
getField: <K extends keyof T>(fieldName: K) => FormFieldConfigType | null;
|
|
278
|
+
getFields: () => Record<keyof T, FormFieldConfigType>;
|
|
279
|
+
hasField: <K extends keyof T>(fieldName: K) => boolean;
|
|
280
|
+
isFieldRequired: <K extends keyof T>(fieldName: K) => boolean;
|
|
281
|
+
isFieldComputed: <K extends keyof T>(fieldName: K) => boolean;
|
|
282
|
+
|
|
283
|
+
// Operations
|
|
284
|
+
refreshSchema: () => Promise<void>;
|
|
285
|
+
validateForm: () => Promise<boolean>;
|
|
286
|
+
clearErrors: () => void;
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Usage Example
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import { useState } from "react";
|
|
294
|
+
import { useTable } from "@ram_28/kf-ai-sdk/table";
|
|
295
|
+
import { useForm } from "@ram_28/kf-ai-sdk/form";
|
|
296
|
+
import type {
|
|
297
|
+
UseFormOptionsType,
|
|
298
|
+
UseFormReturnType,
|
|
299
|
+
FormOperationType,
|
|
300
|
+
FormFieldConfigType,
|
|
301
|
+
FormSchemaConfigType,
|
|
302
|
+
BDOFieldDefinitionType,
|
|
303
|
+
BDOSchemaType,
|
|
304
|
+
} from "@ram_28/kf-ai-sdk/form/types";
|
|
305
|
+
import type {
|
|
306
|
+
UseTableOptionsType,
|
|
307
|
+
UseTableReturnType,
|
|
308
|
+
ColumnDefinitionType,
|
|
309
|
+
} from "@ram_28/kf-ai-sdk/table/types";
|
|
310
|
+
import type { FieldErrors } from "@ram_28/kf-ai-sdk/form.types";
|
|
311
|
+
import { Product, ProductType } from "../sources";
|
|
312
|
+
import { Roles } from "../sources/roles";
|
|
313
|
+
|
|
314
|
+
// Get the typed product for the Seller role (who can create/update products)
|
|
315
|
+
type SellerProduct = ProductType<typeof Roles.Seller>;
|
|
316
|
+
|
|
317
|
+
function ProductManagementPage() {
|
|
318
|
+
// Instantiate the Product source with role
|
|
319
|
+
const product = new Product(Roles.Seller);
|
|
320
|
+
|
|
321
|
+
const [showForm, setShowForm] = useState(false);
|
|
322
|
+
const [selectedProduct, setSelectedProduct] = useState<SellerProduct | null>(
|
|
323
|
+
null,
|
|
324
|
+
);
|
|
325
|
+
const [formMode, setFormMode] = useState<FormOperationType>("create");
|
|
326
|
+
|
|
327
|
+
// Table for listing products
|
|
328
|
+
const table = useTable<SellerProduct>({
|
|
329
|
+
source: product._id,
|
|
330
|
+
columns: [
|
|
331
|
+
{ fieldId: "Title", label: "Name" },
|
|
332
|
+
{ fieldId: "Price", label: "Price" },
|
|
333
|
+
{ fieldId: "Discount", label: "Discount %" },
|
|
334
|
+
],
|
|
335
|
+
enablePagination: true,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Form configuration with all options
|
|
339
|
+
const formOptions: UseFormOptionsType<SellerProduct> = {
|
|
340
|
+
source: product._id,
|
|
341
|
+
operation: formMode,
|
|
342
|
+
recordId: selectedProduct?._id,
|
|
343
|
+
enabled: showForm,
|
|
344
|
+
mode: "onBlur",
|
|
345
|
+
defaultValues: {
|
|
346
|
+
Title: "",
|
|
347
|
+
Description: "",
|
|
348
|
+
Price: 0,
|
|
349
|
+
MRP: 0,
|
|
350
|
+
Category: "Electronics",
|
|
351
|
+
Stock: 0,
|
|
352
|
+
},
|
|
353
|
+
onSchemaError: (error: Error) =>
|
|
354
|
+
console.error("Schema error:", error.message),
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const form: UseFormReturnType<SellerProduct> =
|
|
358
|
+
useForm<SellerProduct>(formOptions);
|
|
359
|
+
|
|
360
|
+
// Handle form submission with new RHF-style callbacks
|
|
361
|
+
const onFormSuccess = (data: SellerProduct) => {
|
|
362
|
+
console.log("Product saved:", data);
|
|
363
|
+
setShowForm(false);
|
|
364
|
+
table.refetch();
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const onFormError = (error: FieldErrors<SellerProduct> | Error) => {
|
|
368
|
+
if (error instanceof Error) {
|
|
369
|
+
// API error
|
|
370
|
+
console.error("Submit error:", error.message);
|
|
371
|
+
} else {
|
|
372
|
+
// Validation errors (FieldErrors)
|
|
373
|
+
console.error("Validation errors:", error);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// Access processed schema
|
|
378
|
+
const displaySchemaInfo = () => {
|
|
379
|
+
const schema: FormSchemaConfigType | null = form.schemaConfig;
|
|
380
|
+
if (!schema) return null;
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
<div className="schema-info">
|
|
384
|
+
<p>Required: {schema.requiredFields.join(", ")}</p>
|
|
385
|
+
<p>Computed: {schema.computedFields.join(", ")}</p>
|
|
386
|
+
<p>Field Order: {schema.fieldOrder.join(", ")}</p>
|
|
387
|
+
</div>
|
|
388
|
+
);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Render a field using FormFieldConfigType metadata
|
|
392
|
+
const renderField = (fieldName: keyof SellerProduct) => {
|
|
393
|
+
const field: FormFieldConfigType | null = form.getField(fieldName);
|
|
394
|
+
if (!field || field.permission.hidden) return null;
|
|
395
|
+
|
|
396
|
+
const isDisabled = !field.permission.editable || field.computed;
|
|
397
|
+
const error = form.errors[fieldName];
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<div key={field.name} className="form-field">
|
|
401
|
+
<label htmlFor={field.name}>
|
|
402
|
+
{field.label}
|
|
403
|
+
{field.required && <span className="required">*</span>}
|
|
404
|
+
{field.computed && <span className="computed">(auto)</span>}
|
|
405
|
+
</label>
|
|
406
|
+
|
|
407
|
+
{field.type === "select" && field.options ? (
|
|
408
|
+
<select
|
|
409
|
+
id={field.name}
|
|
410
|
+
disabled={isDisabled}
|
|
411
|
+
{...form.register(fieldName as any)}
|
|
412
|
+
>
|
|
413
|
+
<option value="">Select {field.label}</option>
|
|
414
|
+
{field.options.map((opt) => (
|
|
415
|
+
<option key={opt.value} value={opt.value}>
|
|
416
|
+
{opt.label}
|
|
417
|
+
</option>
|
|
418
|
+
))}
|
|
419
|
+
</select>
|
|
420
|
+
) : field.computed ? (
|
|
421
|
+
<input
|
|
422
|
+
id={field.name}
|
|
423
|
+
type={field.type === "number" ? "number" : "text"}
|
|
424
|
+
value={form.watch(fieldName as any) ?? field.defaultValue ?? ""}
|
|
425
|
+
readOnly
|
|
426
|
+
disabled
|
|
427
|
+
/>
|
|
428
|
+
) : (
|
|
429
|
+
<input
|
|
430
|
+
id={field.name}
|
|
431
|
+
type={field.type === "number" ? "number" : "text"}
|
|
432
|
+
disabled={isDisabled}
|
|
433
|
+
{...form.register(fieldName as any)}
|
|
434
|
+
/>
|
|
435
|
+
)}
|
|
436
|
+
|
|
437
|
+
{error && <span className="error">{error.message as string}</span>}
|
|
438
|
+
</div>
|
|
439
|
+
);
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Access raw BDO schema
|
|
443
|
+
const displayRawSchema = () => {
|
|
444
|
+
const schema: BDOSchemaType | null = form.schema;
|
|
445
|
+
if (!schema) return null;
|
|
446
|
+
|
|
447
|
+
return (
|
|
448
|
+
<details>
|
|
449
|
+
<summary>Raw Schema: {schema.Name}</summary>
|
|
450
|
+
<ul>
|
|
451
|
+
{Object.entries(schema.Fields).map(
|
|
452
|
+
([fieldName, field]: [string, BDOFieldDefinitionType]) => (
|
|
453
|
+
<li key={fieldName}>
|
|
454
|
+
{field.Name || fieldName} ({field.Type})
|
|
455
|
+
{field.Required && " - Required"}
|
|
456
|
+
{field.Computed && " - Computed"}
|
|
457
|
+
</li>
|
|
458
|
+
),
|
|
459
|
+
)}
|
|
460
|
+
</ul>
|
|
461
|
+
</details>
|
|
462
|
+
);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Check field properties using helpers
|
|
466
|
+
const checkFieldStatus = () => {
|
|
467
|
+
console.log("Is Title required?", form.isFieldRequired("Title"));
|
|
468
|
+
console.log("Is Discount computed?", form.isFieldComputed("Discount"));
|
|
469
|
+
console.log("Has Category field?", form.hasField("Category"));
|
|
470
|
+
console.log("All computed fields:", form.computedFields);
|
|
471
|
+
console.log("All required fields:", form.requiredFields);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// Handle form submission - use the new RHF-style API
|
|
475
|
+
// Note: form.handleSubmit returns a function that handles preventDefault internally
|
|
476
|
+
|
|
477
|
+
// Open create form
|
|
478
|
+
const handleCreate = () => {
|
|
479
|
+
setFormMode("create");
|
|
480
|
+
setSelectedProduct(null);
|
|
481
|
+
setShowForm(true);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// Open edit form
|
|
485
|
+
const handleEdit = (productItem: SellerProduct) => {
|
|
486
|
+
setFormMode("update");
|
|
487
|
+
setSelectedProduct(productItem);
|
|
488
|
+
setShowForm(true);
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
return (
|
|
492
|
+
<div className="product-management">
|
|
493
|
+
<h1>Products</h1>
|
|
494
|
+
<button onClick={handleCreate}>Add Product</button>
|
|
495
|
+
|
|
496
|
+
{/* Product Table */}
|
|
497
|
+
<table>
|
|
498
|
+
<thead>
|
|
499
|
+
<tr>
|
|
500
|
+
<th>Title</th>
|
|
501
|
+
<th>Price</th>
|
|
502
|
+
<th>Discount</th>
|
|
503
|
+
<th>Actions</th>
|
|
504
|
+
</tr>
|
|
505
|
+
</thead>
|
|
506
|
+
<tbody>
|
|
507
|
+
{table.rows.map((row: SellerProduct) => (
|
|
508
|
+
<tr key={row._id}>
|
|
509
|
+
<td>{row.Title}</td>
|
|
510
|
+
<td>${row.Price.toFixed(2)}</td>
|
|
511
|
+
<td>{row.Discount}%</td>
|
|
512
|
+
<td>
|
|
513
|
+
<button onClick={() => handleEdit(row)}>Edit</button>
|
|
514
|
+
</td>
|
|
515
|
+
</tr>
|
|
516
|
+
))}
|
|
517
|
+
</tbody>
|
|
518
|
+
</table>
|
|
519
|
+
|
|
520
|
+
{/* Form Dialog */}
|
|
521
|
+
{showForm && (
|
|
522
|
+
<dialog open>
|
|
523
|
+
{form.isLoadingInitialData ? (
|
|
524
|
+
<div>Loading form...</div>
|
|
525
|
+
) : form.loadError ? (
|
|
526
|
+
<div>
|
|
527
|
+
<p>Error: {form.loadError.message}</p>
|
|
528
|
+
<button onClick={() => form.refreshSchema()}>Retry</button>
|
|
529
|
+
</div>
|
|
530
|
+
) : (
|
|
531
|
+
<form onSubmit={form.handleSubmit(onFormSuccess, onFormError)}>
|
|
532
|
+
<h2>
|
|
533
|
+
{formMode === "create" ? "Create Product" : "Edit Product"}
|
|
534
|
+
</h2>
|
|
535
|
+
|
|
536
|
+
{/* Schema Info */}
|
|
537
|
+
{displaySchemaInfo()}
|
|
538
|
+
|
|
539
|
+
{/* Form Fields */}
|
|
540
|
+
{renderField("Title")}
|
|
541
|
+
{renderField("Description")}
|
|
542
|
+
{renderField("MRP")}
|
|
543
|
+
{renderField("Price")}
|
|
544
|
+
{renderField("Discount")}
|
|
545
|
+
{renderField("Category")}
|
|
546
|
+
{renderField("Stock")}
|
|
547
|
+
|
|
548
|
+
{/* Form Status */}
|
|
549
|
+
<div className="form-status">
|
|
550
|
+
{form.isDirty && <span>Unsaved changes</span>}
|
|
551
|
+
{!form.isValid && <span>Please fix validation errors</span>}
|
|
552
|
+
</div>
|
|
553
|
+
|
|
554
|
+
{/* Cross-field validation errors */}
|
|
555
|
+
{form.errors.root && (
|
|
556
|
+
<div className="cross-field-errors">
|
|
557
|
+
{Object.values(form.errors.root).map((err, i) => (
|
|
558
|
+
<p key={i} className="error">
|
|
559
|
+
{(err as any).message}
|
|
560
|
+
</p>
|
|
561
|
+
))}
|
|
562
|
+
</div>
|
|
563
|
+
)}
|
|
564
|
+
|
|
565
|
+
{/* Note: Submit errors are now handled via onError callback */}
|
|
566
|
+
|
|
567
|
+
{/* Raw Schema Debug */}
|
|
568
|
+
{displayRawSchema()}
|
|
569
|
+
|
|
570
|
+
{/* Form Actions */}
|
|
571
|
+
<div className="form-actions">
|
|
572
|
+
<button type="button" onClick={() => setShowForm(false)}>
|
|
573
|
+
Cancel
|
|
574
|
+
</button>
|
|
575
|
+
<button type="button" onClick={() => form.reset()}>
|
|
576
|
+
Reset
|
|
577
|
+
</button>
|
|
578
|
+
<button type="button" onClick={() => form.validateForm()}>
|
|
579
|
+
Validate
|
|
580
|
+
</button>
|
|
581
|
+
<button
|
|
582
|
+
type="submit"
|
|
583
|
+
disabled={form.isSubmitting || form.isLoading}
|
|
584
|
+
>
|
|
585
|
+
{form.isSubmitting
|
|
586
|
+
? "Saving..."
|
|
587
|
+
: formMode === "create"
|
|
588
|
+
? "Create"
|
|
589
|
+
: "Update"}
|
|
590
|
+
</button>
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
{/* Watch specific fields */}
|
|
594
|
+
<div className="field-preview">
|
|
595
|
+
<p>Current Price: ${form.watch("Price")}</p>
|
|
596
|
+
<p>Current Discount: {form.watch("Discount")}%</p>
|
|
597
|
+
</div>
|
|
598
|
+
</form>
|
|
599
|
+
)}
|
|
600
|
+
</dialog>
|
|
601
|
+
)}
|
|
602
|
+
</div>
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
## Error Utilities
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
import {
|
|
611
|
+
parseApiError,
|
|
612
|
+
isNetworkError,
|
|
613
|
+
isValidationError,
|
|
614
|
+
clearFormCache,
|
|
615
|
+
} from "@ram_28/kf-ai-sdk/form";
|
|
616
|
+
|
|
617
|
+
// Parse API error to user-friendly message
|
|
618
|
+
const message = parseApiError(error); // "Failed to save: Invalid data"
|
|
619
|
+
|
|
620
|
+
// Check error type for specific handling
|
|
621
|
+
if (isNetworkError(error)) {
|
|
622
|
+
showOfflineMessage();
|
|
623
|
+
} else if (isValidationError(error)) {
|
|
624
|
+
showValidationErrors();
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Clear form schema cache (useful after schema changes)
|
|
628
|
+
clearFormCache();
|
|
629
|
+
```
|