@ram_28/kf-ai-sdk 1.0.20 → 1.0.21
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/datetime.d.ts +59 -10
- package/dist/api/datetime.d.ts.map +1 -1
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.mjs +43 -21
- package/dist/api.types.d.ts +2 -1
- package/dist/api.types.d.ts.map +1 -1
- package/dist/auth/AuthProvider.d.ts.map +1 -1
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +34 -34
- package/dist/base-types.d.ts +1 -1
- package/dist/base-types.d.ts.map +1 -1
- package/dist/client-BIkaIr2y.js +217 -0
- package/dist/client-DxjRcEtN.cjs +1 -0
- package/dist/components/hooks/useForm/apiClient.d.ts +45 -4
- package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -1
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +736 -761
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/kanban.cjs +1 -1
- package/dist/kanban.mjs +1 -1
- package/dist/{metadata-0lZAfuTP.cjs → metadata-Bz8zJqC1.cjs} +1 -1
- package/dist/{metadata-B88D_pVS.js → metadata-VbQzyD2C.js} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +1 -1
- package/dist/types/base-fields.d.ts +71 -17
- package/dist/types/base-fields.d.ts.map +1 -1
- package/dist/types/common.d.ts +0 -12
- package/dist/types/common.d.ts.map +1 -1
- package/package.json +1 -1
- package/sdk/api/client.ts +3 -41
- package/sdk/api/datetime.ts +98 -14
- package/sdk/api/index.ts +12 -6
- package/sdk/api.ts +6 -3
- package/sdk/api.types.ts +3 -4
- package/sdk/auth/AuthProvider.tsx +22 -24
- package/sdk/base-types.ts +2 -0
- package/sdk/components/hooks/useForm/apiClient.ts +118 -4
- package/sdk/components/hooks/useForm/useForm.ts +70 -58
- package/sdk/index.ts +3 -0
- package/sdk/types/base-fields.ts +71 -17
- package/sdk/types/common.ts +2 -13
- package/dist/client-DgtkT50N.cjs +0 -1
- package/dist/client-V-WzUb8H.js +0 -237
package/sdk/base-types.ts
CHANGED
|
@@ -135,7 +135,62 @@ export async function submitFormData<T = any>(
|
|
|
135
135
|
// ============================================================
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
|
-
* Fetch reference field data
|
|
138
|
+
* Fetch reference field data using the fetchField API
|
|
139
|
+
* This correctly uses instance context for filter evaluation
|
|
140
|
+
*/
|
|
141
|
+
export async function fetchReferenceFieldData(
|
|
142
|
+
boId: string,
|
|
143
|
+
instanceId: string,
|
|
144
|
+
fieldId: string
|
|
145
|
+
): Promise<Array<{ Value: string; Label: string }>> {
|
|
146
|
+
try {
|
|
147
|
+
// Calls GET /{boId}/{instanceId}/field/{fieldId}/fetch
|
|
148
|
+
return await api(boId).fetchField(instanceId, fieldId);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(`Reference data fetch error for ${fieldId}:`, error);
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Fetch all reference data for a form
|
|
157
|
+
* @param boId - The Business Object ID
|
|
158
|
+
* @param instanceId - The instance ID (draftId for create, recordId for update)
|
|
159
|
+
* @param referenceFieldIds - Array of reference field IDs to fetch
|
|
160
|
+
*/
|
|
161
|
+
export async function fetchAllReferenceFieldData(
|
|
162
|
+
boId: string,
|
|
163
|
+
instanceId: string,
|
|
164
|
+
referenceFieldIds: string[]
|
|
165
|
+
): Promise<Record<string, Array<{ Value: string; Label: string }>>> {
|
|
166
|
+
const referenceData: Record<string, Array<{ Value: string; Label: string }>> = {};
|
|
167
|
+
|
|
168
|
+
// Fetch all reference data in parallel
|
|
169
|
+
const fetchPromises = referenceFieldIds.map(async (fieldId) => {
|
|
170
|
+
try {
|
|
171
|
+
const data = await fetchReferenceFieldData(boId, instanceId, fieldId);
|
|
172
|
+
return [fieldId, data] as const;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.warn(`Failed to fetch reference data for ${fieldId}:`, error);
|
|
175
|
+
return [fieldId, []] as const;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const results = await Promise.allSettled(fetchPromises);
|
|
180
|
+
|
|
181
|
+
results.forEach((result) => {
|
|
182
|
+
if (result.status === "fulfilled") {
|
|
183
|
+
const [fieldId, data] = result.value;
|
|
184
|
+
referenceData[fieldId] = [...data];
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return referenceData;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @deprecated Use fetchReferenceFieldData instead
|
|
193
|
+
* Legacy function that fetches reference data using list() API
|
|
139
194
|
*/
|
|
140
195
|
export async function fetchReferenceData(
|
|
141
196
|
businessObject: string,
|
|
@@ -171,7 +226,8 @@ export async function fetchReferenceData(
|
|
|
171
226
|
}
|
|
172
227
|
|
|
173
228
|
/**
|
|
174
|
-
*
|
|
229
|
+
* @deprecated Use fetchAllReferenceFieldData instead
|
|
230
|
+
* Legacy function that fetches all reference data using list() API
|
|
175
231
|
*/
|
|
176
232
|
export async function fetchAllReferenceData(
|
|
177
233
|
referenceFields: Record<string, any>
|
|
@@ -239,18 +295,26 @@ export function validateFormData<T>(
|
|
|
239
295
|
* Clean form data before submission
|
|
240
296
|
* - For create: returns all non-computed, non-undefined fields
|
|
241
297
|
* - For update: returns only fields that changed from originalData
|
|
298
|
+
* - Transforms Reference fields to JSON format expected by backend
|
|
299
|
+
*
|
|
300
|
+
* @param data - Form data to clean
|
|
301
|
+
* @param computedFields - Array of computed field names to exclude
|
|
302
|
+
* @param operation - "create" or "update"
|
|
303
|
+
* @param originalData - Original data for comparison (update only)
|
|
304
|
+
* @param fieldTypes - Map of field names to their BDO types (e.g., { "SupplierInfo": "Reference" })
|
|
242
305
|
*/
|
|
243
306
|
export function cleanFormData<T>(
|
|
244
307
|
data: Partial<T>,
|
|
245
308
|
computedFields: string[],
|
|
246
309
|
operation: FormOperationType = "create",
|
|
247
|
-
originalData?: Partial<T
|
|
310
|
+
originalData?: Partial<T>,
|
|
311
|
+
fieldTypes?: Record<string, string>
|
|
248
312
|
): Partial<T> {
|
|
249
313
|
const cleanedData: Partial<T> = {};
|
|
250
314
|
|
|
251
315
|
Object.keys(data).forEach((key) => {
|
|
252
316
|
const fieldKey = key as keyof T;
|
|
253
|
-
|
|
317
|
+
let value = data[fieldKey];
|
|
254
318
|
|
|
255
319
|
// Skip computed fields
|
|
256
320
|
if (computedFields.includes(key)) {
|
|
@@ -262,6 +326,12 @@ export function cleanFormData<T>(
|
|
|
262
326
|
return;
|
|
263
327
|
}
|
|
264
328
|
|
|
329
|
+
// Transform Reference fields to JSON format expected by backend
|
|
330
|
+
// Backend expects: {"_id": "...", "_name": "..."} or a JSON string that parses to this
|
|
331
|
+
if (fieldTypes?.[key] === "Reference" && value !== null) {
|
|
332
|
+
value = transformReferenceValue(value) as any;
|
|
333
|
+
}
|
|
334
|
+
|
|
265
335
|
// For create: include all non-computed, non-undefined fields
|
|
266
336
|
if (operation === "create") {
|
|
267
337
|
cleanedData[fieldKey] = value;
|
|
@@ -289,6 +359,50 @@ export function cleanFormData<T>(
|
|
|
289
359
|
return cleanedData;
|
|
290
360
|
}
|
|
291
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Transform a Reference/Lookup field value to the format expected by backend
|
|
364
|
+
*
|
|
365
|
+
* Backend expects Reference fields as either:
|
|
366
|
+
* - A dict with at minimum {"_id": "..."}
|
|
367
|
+
* - For Lookup fields, the full referenced record can be passed
|
|
368
|
+
* - A JSON string that parses to this format
|
|
369
|
+
*
|
|
370
|
+
* This function handles various input formats:
|
|
371
|
+
* - Plain string ID: "SUPP-001" → {"_id": "SUPP-001"}
|
|
372
|
+
* - Object with _id: preserves ALL properties (for lookup fields with full data)
|
|
373
|
+
* - JSON string: '{"_id": "..."}' → parsed and validated
|
|
374
|
+
*/
|
|
375
|
+
export function transformReferenceValue(value: any): Record<string, any> | null {
|
|
376
|
+
if (value === null || value === undefined || value === "") {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Already an object with _id - preserve ALL properties (for lookup fields with full data)
|
|
381
|
+
if (typeof value === "object" && value._id) {
|
|
382
|
+
return { ...value };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// String value - could be plain ID or JSON string
|
|
386
|
+
if (typeof value === "string") {
|
|
387
|
+
// Try to parse as JSON first
|
|
388
|
+
try {
|
|
389
|
+
const parsed = JSON.parse(value);
|
|
390
|
+
if (typeof parsed === "object" && parsed._id) {
|
|
391
|
+
return { ...parsed };
|
|
392
|
+
}
|
|
393
|
+
// Parsed but not a valid reference object - treat original as ID
|
|
394
|
+
} catch {
|
|
395
|
+
// Not valid JSON - treat as plain ID string
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Plain string ID
|
|
399
|
+
return { _id: value };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Unknown format - return as-is (let backend handle/reject it)
|
|
403
|
+
return value;
|
|
404
|
+
}
|
|
405
|
+
|
|
292
406
|
// ============================================================
|
|
293
407
|
// ERROR HANDLING
|
|
294
408
|
// ============================================================
|
|
@@ -16,14 +16,14 @@ import type {
|
|
|
16
16
|
FormFieldConfigType,
|
|
17
17
|
} from "./types";
|
|
18
18
|
|
|
19
|
-
import { processSchema
|
|
19
|
+
import { processSchema } from "./schemaParser.utils";
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
22
|
fetchFormSchemaWithCache,
|
|
23
23
|
fetchRecord,
|
|
24
24
|
submitFormData,
|
|
25
|
-
fetchAllReferenceData,
|
|
26
25
|
cleanFormData,
|
|
26
|
+
transformReferenceValue,
|
|
27
27
|
} from "./apiClient";
|
|
28
28
|
|
|
29
29
|
import { api } from "../../../api";
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
// ============================================================
|
|
41
41
|
|
|
42
42
|
export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
43
|
-
options: UseFormOptionsType<T
|
|
43
|
+
options: UseFormOptionsType<T>,
|
|
44
44
|
): UseFormReturnType<T> {
|
|
45
45
|
const {
|
|
46
46
|
source,
|
|
@@ -63,9 +63,13 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
63
63
|
// STATE MANAGEMENT
|
|
64
64
|
// ============================================================
|
|
65
65
|
|
|
66
|
-
const [schemaConfig, setSchemaConfig] =
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
const [schemaConfig, setSchemaConfig] = useState<FormSchemaConfigType | null>(
|
|
67
|
+
null,
|
|
68
|
+
);
|
|
69
|
+
// Reference data for cross-field validation - populated lazily by UI components if needed
|
|
70
|
+
const [referenceData, _setReferenceData] = useState<Record<string, any[]>>(
|
|
71
|
+
{},
|
|
72
|
+
);
|
|
69
73
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
70
74
|
const [lastFormValues] = useState<Partial<T>>({});
|
|
71
75
|
|
|
@@ -176,22 +180,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
176
180
|
const processed = processSchema(
|
|
177
181
|
schema as any,
|
|
178
182
|
{}, // Pass empty object - validation functions get live values from react-hook-form
|
|
179
|
-
userRole
|
|
183
|
+
userRole,
|
|
180
184
|
);
|
|
181
185
|
setSchemaConfig(processed);
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
const refFields = extractReferenceFields(processed);
|
|
185
|
-
if (Object.keys(refFields).length > 0) {
|
|
186
|
-
fetchAllReferenceData(refFields)
|
|
187
|
-
.then(setReferenceData)
|
|
188
|
-
.catch((err) => {
|
|
189
|
-
const error = toError(err);
|
|
190
|
-
console.warn("Failed to fetch reference data:", error);
|
|
191
|
-
// Notify via callback but don't block form - reference data is non-critical
|
|
192
|
-
onSchemaErrorRef.current?.(error);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
186
|
+
// Reference data is fetched lazily by UI components when dropdowns are opened
|
|
187
|
+
// using the fetchField API with proper instance context (draftId/recordId)
|
|
195
188
|
} catch (error) {
|
|
196
189
|
console.error("Schema processing failed:", error);
|
|
197
190
|
onSchemaErrorRef.current?.(toError(error));
|
|
@@ -219,7 +212,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
219
212
|
!schemaConfig ||
|
|
220
213
|
!enabled ||
|
|
221
214
|
draftId ||
|
|
222
|
-
draftCreationStartedRef.current
|
|
215
|
+
draftCreationStartedRef.current // Prevent duplicate calls in React strict mode
|
|
223
216
|
) {
|
|
224
217
|
return;
|
|
225
218
|
}
|
|
@@ -321,7 +314,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
321
314
|
const field = schemaConfig.fields[fieldName];
|
|
322
315
|
if (field._bdoField.Formula) {
|
|
323
316
|
const fieldDeps = getFieldDependencies(
|
|
324
|
-
field._bdoField.Formula.ExpressionTree
|
|
317
|
+
field._bdoField.Formula.ExpressionTree,
|
|
325
318
|
);
|
|
326
319
|
fieldDeps.forEach((dep) => {
|
|
327
320
|
// Only add non-computed fields as dependencies
|
|
@@ -357,10 +350,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
357
350
|
// For update mode, always behave as non-interactive (only trigger for computed deps)
|
|
358
351
|
// Interactive mode (create only): Always trigger draft API on blur
|
|
359
352
|
// Non-interactive mode: Only trigger for computed field dependencies
|
|
360
|
-
const shouldTrigger =
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
353
|
+
const shouldTrigger =
|
|
354
|
+
isInteractiveMode && operation !== "update"
|
|
355
|
+
? true // Interactive mode (create only): always trigger
|
|
356
|
+
: computedFieldDependencies.length > 0 &&
|
|
357
|
+
computedFieldDependencies.includes(fieldName as Path<T>);
|
|
364
358
|
|
|
365
359
|
if (!shouldTrigger) {
|
|
366
360
|
return;
|
|
@@ -422,7 +416,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
422
416
|
|
|
423
417
|
// Get computed field names to exclude from payload
|
|
424
418
|
const computedFieldNames = new Set(
|
|
425
|
-
schemaConfig.computedFields || []
|
|
419
|
+
schemaConfig.computedFields || [],
|
|
426
420
|
);
|
|
427
421
|
|
|
428
422
|
// Find fields that changed from baseline (excluding computed fields)
|
|
@@ -430,7 +424,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
430
424
|
// Skip _id and computed fields
|
|
431
425
|
if (key === "_id" || computedFieldNames.has(key)) return;
|
|
432
426
|
|
|
433
|
-
|
|
427
|
+
let currentValue = (currentValues as any)[key];
|
|
434
428
|
const baselineValue = (baseline as any)[key];
|
|
435
429
|
|
|
436
430
|
// Include if value has changed (using JSON.stringify for deep comparison)
|
|
@@ -443,6 +437,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
443
437
|
currentValue !== undefined;
|
|
444
438
|
|
|
445
439
|
if (hasChanged && isNonEmpty) {
|
|
440
|
+
// Transform Reference fields to format expected by backend
|
|
441
|
+
const fieldConfig = schemaConfig.fields[key];
|
|
442
|
+
if (fieldConfig?._bdoField?.Type === "Reference") {
|
|
443
|
+
currentValue = transformReferenceValue(currentValue);
|
|
444
|
+
}
|
|
446
445
|
(changedFields as any)[key] = currentValue;
|
|
447
446
|
}
|
|
448
447
|
});
|
|
@@ -470,7 +469,10 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
470
469
|
let computedFieldsResponse;
|
|
471
470
|
if (operation === "update" && recordId) {
|
|
472
471
|
// Update mode: use draftPatch (both interactive and non-interactive)
|
|
473
|
-
computedFieldsResponse = await client.draftPatch(
|
|
472
|
+
computedFieldsResponse = await client.draftPatch(
|
|
473
|
+
recordId,
|
|
474
|
+
payload,
|
|
475
|
+
);
|
|
474
476
|
} else if (isInteractiveMode && draftId) {
|
|
475
477
|
// Interactive create: use draftInteraction with _id
|
|
476
478
|
computedFieldsResponse = await client.draftInteraction(payload);
|
|
@@ -493,7 +495,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
493
495
|
shouldValidate: false,
|
|
494
496
|
});
|
|
495
497
|
}
|
|
496
|
-
}
|
|
498
|
+
},
|
|
497
499
|
);
|
|
498
500
|
|
|
499
501
|
// Update baseline with computed fields from successful API response
|
|
@@ -502,7 +504,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
502
504
|
if (computedFieldNames.has(fieldName)) {
|
|
503
505
|
(lastSyncedValuesRef.current as any)[fieldName] = value;
|
|
504
506
|
}
|
|
505
|
-
}
|
|
507
|
+
},
|
|
506
508
|
);
|
|
507
509
|
}
|
|
508
510
|
} catch (error) {
|
|
@@ -531,7 +533,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
531
533
|
computedFieldDependencies,
|
|
532
534
|
isInteractiveMode,
|
|
533
535
|
draftId,
|
|
534
|
-
]
|
|
536
|
+
],
|
|
535
537
|
);
|
|
536
538
|
|
|
537
539
|
// ============================================================
|
|
@@ -553,18 +555,16 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
553
555
|
|
|
554
556
|
// Cross-field validation
|
|
555
557
|
// Transform ValidationRule[] to the format expected by validateCrossField
|
|
556
|
-
const transformedRules = schemaConfig.crossFieldValidation.map(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
})
|
|
562
|
-
);
|
|
558
|
+
const transformedRules = schemaConfig.crossFieldValidation.map((rule) => ({
|
|
559
|
+
Id: rule.Id,
|
|
560
|
+
Condition: { ExpressionTree: rule.ExpressionTree },
|
|
561
|
+
Message: rule.Message || `Validation failed for ${rule.Name}`,
|
|
562
|
+
}));
|
|
563
563
|
|
|
564
564
|
const crossFieldErrors = validateCrossField(
|
|
565
565
|
transformedRules,
|
|
566
566
|
values as any,
|
|
567
|
-
referenceData
|
|
567
|
+
referenceData,
|
|
568
568
|
);
|
|
569
569
|
|
|
570
570
|
if (crossFieldErrors.length > 0) {
|
|
@@ -603,11 +603,14 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
603
603
|
*/
|
|
604
604
|
const handleSubmit = useCallback(
|
|
605
605
|
(
|
|
606
|
-
onSuccess?: (
|
|
606
|
+
onSuccess?: (
|
|
607
|
+
data: T,
|
|
608
|
+
e?: React.BaseSyntheticEvent,
|
|
609
|
+
) => void | Promise<void>,
|
|
607
610
|
onError?: (
|
|
608
611
|
error: import("react-hook-form").FieldErrors<T> | Error,
|
|
609
|
-
e?: React.BaseSyntheticEvent
|
|
610
|
-
) => void | Promise<void
|
|
612
|
+
e?: React.BaseSyntheticEvent,
|
|
613
|
+
) => void | Promise<void>,
|
|
611
614
|
) => {
|
|
612
615
|
return rhfForm.handleSubmit(
|
|
613
616
|
// RHF onValid handler - validation passed, now do cross-field + API
|
|
@@ -627,13 +630,13 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
627
630
|
Id: rule.Id,
|
|
628
631
|
Condition: { ExpressionTree: rule.ExpressionTree },
|
|
629
632
|
Message: rule.Message || `Validation failed for ${rule.Name}`,
|
|
630
|
-
})
|
|
633
|
+
}),
|
|
631
634
|
);
|
|
632
635
|
|
|
633
636
|
const crossFieldErrors = validateCrossField(
|
|
634
637
|
transformedRules,
|
|
635
638
|
data as any,
|
|
636
|
-
referenceData
|
|
639
|
+
referenceData,
|
|
637
640
|
);
|
|
638
641
|
|
|
639
642
|
if (crossFieldErrors.length > 0) {
|
|
@@ -649,12 +652,21 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
649
652
|
return;
|
|
650
653
|
}
|
|
651
654
|
|
|
655
|
+
// Extract field types from schema for Reference field transformation
|
|
656
|
+
const fieldTypes: Record<string, string> = {};
|
|
657
|
+
Object.entries(schemaConfig.fields).forEach(
|
|
658
|
+
([fieldName, field]) => {
|
|
659
|
+
fieldTypes[fieldName] = field._bdoField.Type;
|
|
660
|
+
},
|
|
661
|
+
);
|
|
662
|
+
|
|
652
663
|
// Clean data for submission
|
|
653
664
|
const cleanedData = cleanFormData(
|
|
654
665
|
data as any,
|
|
655
666
|
schemaConfig.computedFields,
|
|
656
667
|
operation,
|
|
657
|
-
recordData as Partial<T> | undefined
|
|
668
|
+
recordData as Partial<T> | undefined,
|
|
669
|
+
fieldTypes,
|
|
658
670
|
);
|
|
659
671
|
|
|
660
672
|
let result;
|
|
@@ -667,7 +679,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
667
679
|
// Interactive create: must have draftId
|
|
668
680
|
if (!draftId) {
|
|
669
681
|
throw new Error(
|
|
670
|
-
"Interactive create mode requires a draft ID. Draft creation may have failed."
|
|
682
|
+
"Interactive create mode requires a draft ID. Draft creation may have failed.",
|
|
671
683
|
);
|
|
672
684
|
}
|
|
673
685
|
// POST /{bdo_id}/draft with _id in payload
|
|
@@ -687,7 +699,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
687
699
|
source,
|
|
688
700
|
operation,
|
|
689
701
|
cleanedData,
|
|
690
|
-
recordId
|
|
702
|
+
recordId,
|
|
691
703
|
);
|
|
692
704
|
|
|
693
705
|
if (!result.success) {
|
|
@@ -716,7 +728,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
716
728
|
// RHF onInvalid handler - validation failed
|
|
717
729
|
(errors, event) => {
|
|
718
730
|
onError?.(errors, event);
|
|
719
|
-
}
|
|
731
|
+
},
|
|
720
732
|
);
|
|
721
733
|
},
|
|
722
734
|
[
|
|
@@ -729,7 +741,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
729
741
|
recordData,
|
|
730
742
|
isInteractiveMode,
|
|
731
743
|
draftId,
|
|
732
|
-
]
|
|
744
|
+
],
|
|
733
745
|
);
|
|
734
746
|
|
|
735
747
|
// ============================================================
|
|
@@ -740,7 +752,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
740
752
|
<K extends keyof T>(fieldName: K): FormFieldConfigType | null => {
|
|
741
753
|
return schemaConfig?.fields[fieldName as string] || null;
|
|
742
754
|
},
|
|
743
|
-
[schemaConfig]
|
|
755
|
+
[schemaConfig],
|
|
744
756
|
);
|
|
745
757
|
|
|
746
758
|
const getFields = useCallback((): Record<keyof T, FormFieldConfigType> => {
|
|
@@ -758,7 +770,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
758
770
|
<K extends keyof T>(fieldName: K): boolean => {
|
|
759
771
|
return !!schemaConfig?.fields[fieldName as string];
|
|
760
772
|
},
|
|
761
|
-
[schemaConfig]
|
|
773
|
+
[schemaConfig],
|
|
762
774
|
);
|
|
763
775
|
|
|
764
776
|
const isFieldRequired = useCallback(
|
|
@@ -767,7 +779,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
767
779
|
schemaConfig?.requiredFields.includes(fieldName as string) || false
|
|
768
780
|
);
|
|
769
781
|
},
|
|
770
|
-
[schemaConfig]
|
|
782
|
+
[schemaConfig],
|
|
771
783
|
);
|
|
772
784
|
|
|
773
785
|
const isFieldComputed = useCallback(
|
|
@@ -776,7 +788,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
776
788
|
schemaConfig?.computedFields.includes(fieldName as string) || false
|
|
777
789
|
);
|
|
778
790
|
},
|
|
779
|
-
[schemaConfig]
|
|
791
|
+
[schemaConfig],
|
|
780
792
|
);
|
|
781
793
|
|
|
782
794
|
// ============================================================
|
|
@@ -806,12 +818,12 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
806
818
|
|
|
807
819
|
const computedFields = useMemo<Array<keyof T>>(
|
|
808
820
|
() => (schemaConfig?.computedFields as Array<keyof T>) || [],
|
|
809
|
-
[schemaConfig]
|
|
821
|
+
[schemaConfig],
|
|
810
822
|
);
|
|
811
823
|
|
|
812
824
|
const requiredFields = useMemo<Array<keyof T>>(
|
|
813
825
|
() => (schemaConfig?.requiredFields as Array<keyof T>) || [],
|
|
814
|
-
[schemaConfig]
|
|
826
|
+
[schemaConfig],
|
|
815
827
|
);
|
|
816
828
|
|
|
817
829
|
// ============================================================
|
|
@@ -864,7 +876,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
864
876
|
value,
|
|
865
877
|
[rule],
|
|
866
878
|
currentValues,
|
|
867
|
-
lastFormValues as T | undefined
|
|
879
|
+
lastFormValues as T | undefined,
|
|
868
880
|
);
|
|
869
881
|
if (!result.isValid) {
|
|
870
882
|
return result.message || rule.Message || "Invalid value";
|
|
@@ -936,7 +948,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
936
948
|
onBlur: enhancedOnBlur,
|
|
937
949
|
});
|
|
938
950
|
},
|
|
939
|
-
[rhfForm, validationRules, triggerComputationAfterValidation, mode]
|
|
951
|
+
[rhfForm, validationRules, triggerComputationAfterValidation, mode],
|
|
940
952
|
);
|
|
941
953
|
|
|
942
954
|
return {
|
package/sdk/index.ts
CHANGED
package/sdk/types/base-fields.ts
CHANGED
|
@@ -67,27 +67,47 @@ export type LongFieldType = number;
|
|
|
67
67
|
*/
|
|
68
68
|
export type BooleanFieldType = boolean;
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Encoded date format from API response
|
|
72
|
+
* API returns: { "$__d__": "YYYY-MM-DD" }
|
|
73
|
+
*/
|
|
74
|
+
export interface DateEncodedType {
|
|
75
|
+
$__d__: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Encoded datetime format from API response
|
|
80
|
+
* API returns: { "$__dt__": unix_timestamp_seconds }
|
|
81
|
+
*/
|
|
82
|
+
export interface DateTimeEncodedType {
|
|
83
|
+
$__dt__: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
/**
|
|
71
87
|
* Date field (date only, no time)
|
|
72
|
-
*
|
|
88
|
+
* API Response Format: { "$__d__": "YYYY-MM-DD" }
|
|
89
|
+
* API Request Format: "YYYY-MM-DD"
|
|
73
90
|
* Storage: DATE in database
|
|
74
91
|
* Use this for birth dates, due dates, calendar events
|
|
75
92
|
*
|
|
76
93
|
* @example
|
|
77
|
-
*
|
|
94
|
+
* // Response from API:
|
|
95
|
+
* { "$__d__": "2025-03-15" }
|
|
78
96
|
*/
|
|
79
|
-
export type DateFieldType =
|
|
97
|
+
export type DateFieldType = DateEncodedType;
|
|
80
98
|
|
|
81
99
|
/**
|
|
82
100
|
* DateTime field (date and time)
|
|
83
|
-
*
|
|
101
|
+
* API Response Format: { "$__dt__": unix_timestamp_seconds }
|
|
102
|
+
* API Request Format: "YYYY-MM-DD HH:MM:SS"
|
|
84
103
|
* Storage: DATETIME/TIMESTAMP in database
|
|
85
104
|
* Use this for created_at, updated_at, event timestamps
|
|
86
105
|
*
|
|
87
106
|
* @example
|
|
88
|
-
*
|
|
107
|
+
* // Response from API:
|
|
108
|
+
* { "$__dt__": 1769110463 }
|
|
89
109
|
*/
|
|
90
|
-
export type DateTimeFieldType =
|
|
110
|
+
export type DateTimeFieldType = DateTimeEncodedType;
|
|
91
111
|
|
|
92
112
|
// ============================================================
|
|
93
113
|
// COMPLEX FIELD TYPES
|
|
@@ -124,7 +144,47 @@ export type CurrencyValueType =
|
|
|
124
144
|
*/
|
|
125
145
|
export type JSONFieldType<T = JSONValueType> = T;
|
|
126
146
|
|
|
127
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Reference/Lookup field for relationships to other Business Data Objects
|
|
149
|
+
*
|
|
150
|
+
* @template TReferencedType - The full type of the referenced BDO record
|
|
151
|
+
*
|
|
152
|
+
* Runtime behavior:
|
|
153
|
+
* - The field stores the full referenced record (e.g., full supplier object)
|
|
154
|
+
* - API returns the complete record from the referenced BDO
|
|
155
|
+
* - On save, the full object is sent in the payload
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* SupplierInfo: ReferenceFieldType<BaseSupplierType>;
|
|
159
|
+
* // At runtime: { _id: "...", SupplierId: "...", SupplierName: "...", ... }
|
|
160
|
+
*/
|
|
161
|
+
export type ReferenceFieldType<TReferencedType = unknown> = TReferencedType;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Extract the referenced type from a ReferenceFieldType
|
|
165
|
+
* Note: Since ReferenceFieldType<T> = T, this just returns T if it has _id
|
|
166
|
+
*/
|
|
167
|
+
export type ExtractReferenceType<T> = T extends { _id: string } ? T : never;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Extract the type that fetchField should return for a given field type
|
|
171
|
+
* - For Reference fields (objects with _id) → the full referenced record type
|
|
172
|
+
* - For other field types → { Value: string; Label: string } (static dropdown)
|
|
173
|
+
*
|
|
174
|
+
* Detection: All BDO types have _id: string, so we check for that property
|
|
175
|
+
* to distinguish reference fields from primitive fields like StringFieldType.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* type SupplierOptions = ExtractFetchFieldType<BaseProductType["SupplierInfo"]>;
|
|
179
|
+
* // Result: BaseSupplierType (has _id)
|
|
180
|
+
*
|
|
181
|
+
* type CategoryOptions = ExtractFetchFieldType<BaseProductType["Category"]>;
|
|
182
|
+
* // Result: { Value: string; Label: string } (string has no _id)
|
|
183
|
+
*/
|
|
184
|
+
export type ExtractFetchFieldType<T> =
|
|
185
|
+
T extends { _id: string }
|
|
186
|
+
? T
|
|
187
|
+
: { Value: string; Label: string };
|
|
128
188
|
|
|
129
189
|
/**
|
|
130
190
|
* Valid JSON value types
|
|
@@ -156,17 +216,11 @@ export interface JSONArrayType extends Array<JSONValueType> {}
|
|
|
156
216
|
export type SelectFieldType<T extends string> = T;
|
|
157
217
|
|
|
158
218
|
/**
|
|
159
|
-
* Lookup
|
|
160
|
-
* @template
|
|
161
|
-
*
|
|
162
|
-
* Storage: VARCHAR in database (stores the ID)
|
|
163
|
-
* Use this for foreign keys, relationships, references
|
|
164
|
-
*
|
|
165
|
-
* @example
|
|
166
|
-
* LookupFieldType // => string (referenced record ID)
|
|
167
|
-
* LookupFieldType<IdFieldType> // => string (typed as IdFieldType)
|
|
219
|
+
* Alias for ReferenceFieldType (Lookup = Reference in the backend)
|
|
220
|
+
* @template TReferencedType - The full type of the referenced BDO record
|
|
221
|
+
* @deprecated Use ReferenceFieldType instead
|
|
168
222
|
*/
|
|
169
|
-
export type LookupFieldType<
|
|
223
|
+
export type LookupFieldType<TReferencedType = unknown> = ReferenceFieldType<TReferencedType>;
|
|
170
224
|
|
|
171
225
|
// ============================================================
|
|
172
226
|
// CONTAINER AND UTILITY TYPES
|
package/sdk/types/common.ts
CHANGED
|
@@ -71,19 +71,8 @@ export interface ConditionGroupType<T = any> {
|
|
|
71
71
|
*/
|
|
72
72
|
export type FilterType<T = any> = ConditionGroupType<T>;
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*/
|
|
77
|
-
export interface DateTimeEncodedType {
|
|
78
|
-
$__dt__: number;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Date encoding format used by the API
|
|
83
|
-
*/
|
|
84
|
-
export interface DateEncodedType {
|
|
85
|
-
$__d__: string;
|
|
86
|
-
}
|
|
74
|
+
// Note: DateTimeEncodedType and DateEncodedType are now defined in base-fields.ts
|
|
75
|
+
// They are re-exported from api/index.ts for convenience
|
|
87
76
|
|
|
88
77
|
/**
|
|
89
78
|
* Standard paginated list response
|