@ram_28/kf-ai-sdk 1.0.19 → 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/README.md +45 -12
- 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/useFilter/types.d.ts +14 -11
- package/dist/components/hooks/useFilter/types.d.ts.map +1 -1
- package/dist/components/hooks/useFilter/useFilter.d.ts +1 -1
- package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
- 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/components/hooks/useKanban/types.d.ts +5 -22
- package/dist/components/hooks/useKanban/types.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -1
- package/dist/components/hooks/useTable/types.d.ts +19 -31
- package/dist/components/hooks/useTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
- package/dist/error-handling-CAoD0Kwb.cjs +1 -0
- package/dist/error-handling-CrhTtD88.js +14 -0
- package/dist/filter.cjs +1 -1
- package/dist/filter.mjs +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +736 -750
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kanban.cjs +2 -2
- package/dist/kanban.mjs +333 -323
- 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 +113 -96
- package/dist/table.types.d.ts +1 -1
- package/dist/table.types.d.ts.map +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 +26 -18
- package/dist/types/common.d.ts.map +1 -1
- package/dist/useFilter-DzpP_ag0.cjs +1 -0
- package/dist/useFilter-H5bgAZQF.js +120 -0
- package/dist/utils/api/buildListOptions.d.ts +43 -0
- package/dist/utils/api/buildListOptions.d.ts.map +1 -0
- package/dist/utils/api/index.d.ts +2 -0
- package/dist/utils/api/index.d.ts.map +1 -0
- package/dist/utils/error-handling.d.ts +41 -0
- package/dist/utils/error-handling.d.ts.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/docs/QUICK_REFERENCE.md +142 -420
- package/docs/useAuth.md +52 -340
- package/docs/useFilter.md +858 -162
- package/docs/useForm.md +712 -501
- package/docs/useKanban.md +534 -279
- package/docs/useTable.md +725 -214
- 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/useFilter/types.ts +14 -11
- package/sdk/components/hooks/useFilter/useFilter.ts +20 -18
- package/sdk/components/hooks/useForm/apiClient.ts +120 -5
- package/sdk/components/hooks/useForm/useForm.ts +97 -61
- package/sdk/components/hooks/useKanban/types.ts +7 -23
- package/sdk/components/hooks/useKanban/useKanban.ts +54 -18
- package/sdk/components/hooks/useTable/types.ts +26 -32
- package/sdk/components/hooks/useTable/useTable.llm.txt +8 -22
- package/sdk/components/hooks/useTable/useTable.ts +70 -25
- package/sdk/index.ts +157 -10
- package/sdk/table.types.ts +3 -0
- package/sdk/types/base-fields.ts +71 -17
- package/sdk/types/common.ts +33 -19
- package/sdk/utils/api/buildListOptions.ts +120 -0
- package/sdk/utils/api/index.ts +2 -0
- package/sdk/utils/error-handling.ts +150 -0
- package/sdk/utils/index.ts +6 -0
- package/dist/client-DgtkT50N.cjs +0 -1
- package/dist/client-V-WzUb8H.js +0 -237
- package/dist/useFilter-Dofowpr_.cjs +0 -1
- package/dist/useFilter-Dv-mr9QW.js +0 -117
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { api, getBdoSchema } from "../../../api";
|
|
7
7
|
import type { BDOSchemaType, FormOperationType, SubmissionResultType } from "./types";
|
|
8
|
+
import { toError } from "../../../utils/error-handling";
|
|
8
9
|
|
|
9
10
|
// ============================================================
|
|
10
11
|
// SCHEMA FETCHING
|
|
@@ -47,7 +48,7 @@ export async function fetchFormSchemaWithRetry(
|
|
|
47
48
|
try {
|
|
48
49
|
return await fetchFormSchema(source);
|
|
49
50
|
} catch (error) {
|
|
50
|
-
lastError = error
|
|
51
|
+
lastError = toError(error);
|
|
51
52
|
|
|
52
53
|
if (attempt < maxRetries) {
|
|
53
54
|
// Wait before retrying (exponential backoff)
|
|
@@ -134,7 +135,62 @@ export async function submitFormData<T = any>(
|
|
|
134
135
|
// ============================================================
|
|
135
136
|
|
|
136
137
|
/**
|
|
137
|
-
* 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
|
|
138
194
|
*/
|
|
139
195
|
export async function fetchReferenceData(
|
|
140
196
|
businessObject: string,
|
|
@@ -170,7 +226,8 @@ export async function fetchReferenceData(
|
|
|
170
226
|
}
|
|
171
227
|
|
|
172
228
|
/**
|
|
173
|
-
*
|
|
229
|
+
* @deprecated Use fetchAllReferenceFieldData instead
|
|
230
|
+
* Legacy function that fetches all reference data using list() API
|
|
174
231
|
*/
|
|
175
232
|
export async function fetchAllReferenceData(
|
|
176
233
|
referenceFields: Record<string, any>
|
|
@@ -238,18 +295,26 @@ export function validateFormData<T>(
|
|
|
238
295
|
* Clean form data before submission
|
|
239
296
|
* - For create: returns all non-computed, non-undefined fields
|
|
240
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" })
|
|
241
305
|
*/
|
|
242
306
|
export function cleanFormData<T>(
|
|
243
307
|
data: Partial<T>,
|
|
244
308
|
computedFields: string[],
|
|
245
309
|
operation: FormOperationType = "create",
|
|
246
|
-
originalData?: Partial<T
|
|
310
|
+
originalData?: Partial<T>,
|
|
311
|
+
fieldTypes?: Record<string, string>
|
|
247
312
|
): Partial<T> {
|
|
248
313
|
const cleanedData: Partial<T> = {};
|
|
249
314
|
|
|
250
315
|
Object.keys(data).forEach((key) => {
|
|
251
316
|
const fieldKey = key as keyof T;
|
|
252
|
-
|
|
317
|
+
let value = data[fieldKey];
|
|
253
318
|
|
|
254
319
|
// Skip computed fields
|
|
255
320
|
if (computedFields.includes(key)) {
|
|
@@ -261,6 +326,12 @@ export function cleanFormData<T>(
|
|
|
261
326
|
return;
|
|
262
327
|
}
|
|
263
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
|
+
|
|
264
335
|
// For create: include all non-computed, non-undefined fields
|
|
265
336
|
if (operation === "create") {
|
|
266
337
|
cleanedData[fieldKey] = value;
|
|
@@ -288,6 +359,50 @@ export function cleanFormData<T>(
|
|
|
288
359
|
return cleanedData;
|
|
289
360
|
}
|
|
290
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
|
+
|
|
291
406
|
// ============================================================
|
|
292
407
|
// ERROR HANDLING
|
|
293
408
|
// ============================================================
|
|
@@ -16,19 +16,20 @@ 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";
|
|
30
30
|
|
|
31
31
|
import { validateCrossField } from "./expressionValidator.utils";
|
|
32
|
+
import { toError } from "../../../utils/error-handling";
|
|
32
33
|
import {
|
|
33
34
|
validateFieldOptimized,
|
|
34
35
|
getFieldDependencies,
|
|
@@ -39,7 +40,7 @@ import {
|
|
|
39
40
|
// ============================================================
|
|
40
41
|
|
|
41
42
|
export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
42
|
-
options: UseFormOptionsType<T
|
|
43
|
+
options: UseFormOptionsType<T>,
|
|
43
44
|
): UseFormReturnType<T> {
|
|
44
45
|
const {
|
|
45
46
|
source,
|
|
@@ -62,9 +63,13 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
62
63
|
// STATE MANAGEMENT
|
|
63
64
|
// ============================================================
|
|
64
65
|
|
|
65
|
-
const [schemaConfig, setSchemaConfig] =
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
);
|
|
68
73
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
69
74
|
const [lastFormValues] = useState<Partial<T>>({});
|
|
70
75
|
|
|
@@ -175,20 +180,14 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
175
180
|
const processed = processSchema(
|
|
176
181
|
schema as any,
|
|
177
182
|
{}, // Pass empty object - validation functions get live values from react-hook-form
|
|
178
|
-
userRole
|
|
183
|
+
userRole,
|
|
179
184
|
);
|
|
180
185
|
setSchemaConfig(processed);
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
const refFields = extractReferenceFields(processed);
|
|
184
|
-
if (Object.keys(refFields).length > 0) {
|
|
185
|
-
fetchAllReferenceData(refFields)
|
|
186
|
-
.then(setReferenceData)
|
|
187
|
-
.catch(console.warn);
|
|
188
|
-
}
|
|
186
|
+
// Reference data is fetched lazily by UI components when dropdowns are opened
|
|
187
|
+
// using the fetchField API with proper instance context (draftId/recordId)
|
|
189
188
|
} catch (error) {
|
|
190
189
|
console.error("Schema processing failed:", error);
|
|
191
|
-
onSchemaErrorRef.current?.(error
|
|
190
|
+
onSchemaErrorRef.current?.(toError(error));
|
|
192
191
|
}
|
|
193
192
|
}
|
|
194
193
|
}, [schema, userRole]); // Removed onSchemaError - using ref instead
|
|
@@ -213,7 +212,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
213
212
|
!schemaConfig ||
|
|
214
213
|
!enabled ||
|
|
215
214
|
draftId ||
|
|
216
|
-
draftCreationStartedRef.current
|
|
215
|
+
draftCreationStartedRef.current // Prevent duplicate calls in React strict mode
|
|
217
216
|
) {
|
|
218
217
|
return;
|
|
219
218
|
}
|
|
@@ -221,6 +220,9 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
221
220
|
// Mark as started immediately to prevent duplicate calls
|
|
222
221
|
draftCreationStartedRef.current = true;
|
|
223
222
|
|
|
223
|
+
// Track if effect is still active (for cleanup/race condition handling)
|
|
224
|
+
let isActive = true;
|
|
225
|
+
|
|
224
226
|
const createInitialDraft = async () => {
|
|
225
227
|
setIsCreatingDraft(true);
|
|
226
228
|
setDraftError(null);
|
|
@@ -230,6 +232,9 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
230
232
|
// Call PATCH /{bdo_id}/draft with empty payload to get draft ID
|
|
231
233
|
const response = await client.draftInteraction({});
|
|
232
234
|
|
|
235
|
+
// Check if effect is still active before setting state
|
|
236
|
+
if (!isActive) return;
|
|
237
|
+
|
|
233
238
|
// Store the draft ID
|
|
234
239
|
setDraftId(response._id);
|
|
235
240
|
|
|
@@ -249,16 +254,27 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
249
254
|
});
|
|
250
255
|
}
|
|
251
256
|
} catch (error) {
|
|
257
|
+
// Check if effect is still active before setting state
|
|
258
|
+
if (!isActive) return;
|
|
259
|
+
|
|
252
260
|
console.error("Failed to create initial draft:", error);
|
|
253
|
-
setDraftError(error
|
|
261
|
+
setDraftError(toError(error));
|
|
254
262
|
// Reset the ref on error so it can be retried
|
|
255
263
|
draftCreationStartedRef.current = false;
|
|
256
264
|
} finally {
|
|
257
|
-
|
|
265
|
+
// Check if effect is still active before setting state
|
|
266
|
+
if (isActive) {
|
|
267
|
+
setIsCreatingDraft(false);
|
|
268
|
+
}
|
|
258
269
|
}
|
|
259
270
|
};
|
|
260
271
|
|
|
261
272
|
createInitialDraft();
|
|
273
|
+
|
|
274
|
+
// Cleanup function to handle unmount during async operation
|
|
275
|
+
return () => {
|
|
276
|
+
isActive = false;
|
|
277
|
+
};
|
|
262
278
|
}, [isInteractiveMode, operation, schemaConfig, enabled, draftId, source]);
|
|
263
279
|
// Note: rhfForm removed from deps - we use ref pattern to avoid dependency loops
|
|
264
280
|
|
|
@@ -298,7 +314,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
298
314
|
const field = schemaConfig.fields[fieldName];
|
|
299
315
|
if (field._bdoField.Formula) {
|
|
300
316
|
const fieldDeps = getFieldDependencies(
|
|
301
|
-
field._bdoField.Formula.ExpressionTree
|
|
317
|
+
field._bdoField.Formula.ExpressionTree,
|
|
302
318
|
);
|
|
303
319
|
fieldDeps.forEach((dep) => {
|
|
304
320
|
// Only add non-computed fields as dependencies
|
|
@@ -331,12 +347,14 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
331
347
|
}
|
|
332
348
|
|
|
333
349
|
// Determine if draft should be triggered based on interaction mode
|
|
334
|
-
//
|
|
350
|
+
// For update mode, always behave as non-interactive (only trigger for computed deps)
|
|
351
|
+
// Interactive mode (create only): Always trigger draft API on blur
|
|
335
352
|
// Non-interactive mode: Only trigger for computed field dependencies
|
|
336
|
-
const shouldTrigger =
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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>);
|
|
340
358
|
|
|
341
359
|
if (!shouldTrigger) {
|
|
342
360
|
return;
|
|
@@ -398,7 +416,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
398
416
|
|
|
399
417
|
// Get computed field names to exclude from payload
|
|
400
418
|
const computedFieldNames = new Set(
|
|
401
|
-
schemaConfig.computedFields || []
|
|
419
|
+
schemaConfig.computedFields || [],
|
|
402
420
|
);
|
|
403
421
|
|
|
404
422
|
// Find fields that changed from baseline (excluding computed fields)
|
|
@@ -406,7 +424,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
406
424
|
// Skip _id and computed fields
|
|
407
425
|
if (key === "_id" || computedFieldNames.has(key)) return;
|
|
408
426
|
|
|
409
|
-
|
|
427
|
+
let currentValue = (currentValues as any)[key];
|
|
410
428
|
const baselineValue = (baseline as any)[key];
|
|
411
429
|
|
|
412
430
|
// Include if value has changed (using JSON.stringify for deep comparison)
|
|
@@ -419,6 +437,11 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
419
437
|
currentValue !== undefined;
|
|
420
438
|
|
|
421
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
|
+
}
|
|
422
445
|
(changedFields as any)[key] = currentValue;
|
|
423
446
|
}
|
|
424
447
|
});
|
|
@@ -446,7 +469,10 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
446
469
|
let computedFieldsResponse;
|
|
447
470
|
if (operation === "update" && recordId) {
|
|
448
471
|
// Update mode: use draftPatch (both interactive and non-interactive)
|
|
449
|
-
computedFieldsResponse = await client.draftPatch(
|
|
472
|
+
computedFieldsResponse = await client.draftPatch(
|
|
473
|
+
recordId,
|
|
474
|
+
payload,
|
|
475
|
+
);
|
|
450
476
|
} else if (isInteractiveMode && draftId) {
|
|
451
477
|
// Interactive create: use draftInteraction with _id
|
|
452
478
|
computedFieldsResponse = await client.draftInteraction(payload);
|
|
@@ -469,7 +495,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
469
495
|
shouldValidate: false,
|
|
470
496
|
});
|
|
471
497
|
}
|
|
472
|
-
}
|
|
498
|
+
},
|
|
473
499
|
);
|
|
474
500
|
|
|
475
501
|
// Update baseline with computed fields from successful API response
|
|
@@ -478,7 +504,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
478
504
|
if (computedFieldNames.has(fieldName)) {
|
|
479
505
|
(lastSyncedValuesRef.current as any)[fieldName] = value;
|
|
480
506
|
}
|
|
481
|
-
}
|
|
507
|
+
},
|
|
482
508
|
);
|
|
483
509
|
}
|
|
484
510
|
} catch (error) {
|
|
@@ -507,7 +533,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
507
533
|
computedFieldDependencies,
|
|
508
534
|
isInteractiveMode,
|
|
509
535
|
draftId,
|
|
510
|
-
]
|
|
536
|
+
],
|
|
511
537
|
);
|
|
512
538
|
|
|
513
539
|
// ============================================================
|
|
@@ -529,18 +555,16 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
529
555
|
|
|
530
556
|
// Cross-field validation
|
|
531
557
|
// Transform ValidationRule[] to the format expected by validateCrossField
|
|
532
|
-
const transformedRules = schemaConfig.crossFieldValidation.map(
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
})
|
|
538
|
-
);
|
|
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
|
+
}));
|
|
539
563
|
|
|
540
564
|
const crossFieldErrors = validateCrossField(
|
|
541
565
|
transformedRules,
|
|
542
566
|
values as any,
|
|
543
|
-
referenceData
|
|
567
|
+
referenceData,
|
|
544
568
|
);
|
|
545
569
|
|
|
546
570
|
if (crossFieldErrors.length > 0) {
|
|
@@ -579,11 +603,14 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
579
603
|
*/
|
|
580
604
|
const handleSubmit = useCallback(
|
|
581
605
|
(
|
|
582
|
-
onSuccess?: (
|
|
606
|
+
onSuccess?: (
|
|
607
|
+
data: T,
|
|
608
|
+
e?: React.BaseSyntheticEvent,
|
|
609
|
+
) => void | Promise<void>,
|
|
583
610
|
onError?: (
|
|
584
611
|
error: import("react-hook-form").FieldErrors<T> | Error,
|
|
585
|
-
e?: React.BaseSyntheticEvent
|
|
586
|
-
) => void | Promise<void
|
|
612
|
+
e?: React.BaseSyntheticEvent,
|
|
613
|
+
) => void | Promise<void>,
|
|
587
614
|
) => {
|
|
588
615
|
return rhfForm.handleSubmit(
|
|
589
616
|
// RHF onValid handler - validation passed, now do cross-field + API
|
|
@@ -603,13 +630,13 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
603
630
|
Id: rule.Id,
|
|
604
631
|
Condition: { ExpressionTree: rule.ExpressionTree },
|
|
605
632
|
Message: rule.Message || `Validation failed for ${rule.Name}`,
|
|
606
|
-
})
|
|
633
|
+
}),
|
|
607
634
|
);
|
|
608
635
|
|
|
609
636
|
const crossFieldErrors = validateCrossField(
|
|
610
637
|
transformedRules,
|
|
611
638
|
data as any,
|
|
612
|
-
referenceData
|
|
639
|
+
referenceData,
|
|
613
640
|
);
|
|
614
641
|
|
|
615
642
|
if (crossFieldErrors.length > 0) {
|
|
@@ -625,12 +652,21 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
625
652
|
return;
|
|
626
653
|
}
|
|
627
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
|
+
|
|
628
663
|
// Clean data for submission
|
|
629
664
|
const cleanedData = cleanFormData(
|
|
630
665
|
data as any,
|
|
631
666
|
schemaConfig.computedFields,
|
|
632
667
|
operation,
|
|
633
|
-
recordData as Partial<T> | undefined
|
|
668
|
+
recordData as Partial<T> | undefined,
|
|
669
|
+
fieldTypes,
|
|
634
670
|
);
|
|
635
671
|
|
|
636
672
|
let result;
|
|
@@ -643,7 +679,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
643
679
|
// Interactive create: must have draftId
|
|
644
680
|
if (!draftId) {
|
|
645
681
|
throw new Error(
|
|
646
|
-
"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.",
|
|
647
683
|
);
|
|
648
684
|
}
|
|
649
685
|
// POST /{bdo_id}/draft with _id in payload
|
|
@@ -653,8 +689,8 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
653
689
|
} as any);
|
|
654
690
|
result = { success: true, data: response };
|
|
655
691
|
} else {
|
|
656
|
-
//
|
|
657
|
-
const response = await client.
|
|
692
|
+
// Update operation - always use direct update API (non-interactive)
|
|
693
|
+
const response = await client.update(recordId!, cleanedData);
|
|
658
694
|
result = { success: true, data: response };
|
|
659
695
|
}
|
|
660
696
|
} else {
|
|
@@ -663,7 +699,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
663
699
|
source,
|
|
664
700
|
operation,
|
|
665
701
|
cleanedData,
|
|
666
|
-
recordId
|
|
702
|
+
recordId,
|
|
667
703
|
);
|
|
668
704
|
|
|
669
705
|
if (!result.success) {
|
|
@@ -684,7 +720,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
684
720
|
await onSuccess?.(result.data || data, event);
|
|
685
721
|
} catch (error) {
|
|
686
722
|
// API error - call onError with Error object
|
|
687
|
-
onError?.(error
|
|
723
|
+
onError?.(toError(error), event);
|
|
688
724
|
} finally {
|
|
689
725
|
setIsSubmitting(false);
|
|
690
726
|
}
|
|
@@ -692,7 +728,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
692
728
|
// RHF onInvalid handler - validation failed
|
|
693
729
|
(errors, event) => {
|
|
694
730
|
onError?.(errors, event);
|
|
695
|
-
}
|
|
731
|
+
},
|
|
696
732
|
);
|
|
697
733
|
},
|
|
698
734
|
[
|
|
@@ -705,7 +741,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
705
741
|
recordData,
|
|
706
742
|
isInteractiveMode,
|
|
707
743
|
draftId,
|
|
708
|
-
]
|
|
744
|
+
],
|
|
709
745
|
);
|
|
710
746
|
|
|
711
747
|
// ============================================================
|
|
@@ -716,7 +752,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
716
752
|
<K extends keyof T>(fieldName: K): FormFieldConfigType | null => {
|
|
717
753
|
return schemaConfig?.fields[fieldName as string] || null;
|
|
718
754
|
},
|
|
719
|
-
[schemaConfig]
|
|
755
|
+
[schemaConfig],
|
|
720
756
|
);
|
|
721
757
|
|
|
722
758
|
const getFields = useCallback((): Record<keyof T, FormFieldConfigType> => {
|
|
@@ -734,7 +770,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
734
770
|
<K extends keyof T>(fieldName: K): boolean => {
|
|
735
771
|
return !!schemaConfig?.fields[fieldName as string];
|
|
736
772
|
},
|
|
737
|
-
[schemaConfig]
|
|
773
|
+
[schemaConfig],
|
|
738
774
|
);
|
|
739
775
|
|
|
740
776
|
const isFieldRequired = useCallback(
|
|
@@ -743,7 +779,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
743
779
|
schemaConfig?.requiredFields.includes(fieldName as string) || false
|
|
744
780
|
);
|
|
745
781
|
},
|
|
746
|
-
[schemaConfig]
|
|
782
|
+
[schemaConfig],
|
|
747
783
|
);
|
|
748
784
|
|
|
749
785
|
const isFieldComputed = useCallback(
|
|
@@ -752,7 +788,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
752
788
|
schemaConfig?.computedFields.includes(fieldName as string) || false
|
|
753
789
|
);
|
|
754
790
|
},
|
|
755
|
-
[schemaConfig]
|
|
791
|
+
[schemaConfig],
|
|
756
792
|
);
|
|
757
793
|
|
|
758
794
|
// ============================================================
|
|
@@ -782,12 +818,12 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
782
818
|
|
|
783
819
|
const computedFields = useMemo<Array<keyof T>>(
|
|
784
820
|
() => (schemaConfig?.computedFields as Array<keyof T>) || [],
|
|
785
|
-
[schemaConfig]
|
|
821
|
+
[schemaConfig],
|
|
786
822
|
);
|
|
787
823
|
|
|
788
824
|
const requiredFields = useMemo<Array<keyof T>>(
|
|
789
825
|
() => (schemaConfig?.requiredFields as Array<keyof T>) || [],
|
|
790
|
-
[schemaConfig]
|
|
826
|
+
[schemaConfig],
|
|
791
827
|
);
|
|
792
828
|
|
|
793
829
|
// ============================================================
|
|
@@ -840,7 +876,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
840
876
|
value,
|
|
841
877
|
[rule],
|
|
842
878
|
currentValues,
|
|
843
|
-
lastFormValues as T | undefined
|
|
879
|
+
lastFormValues as T | undefined,
|
|
844
880
|
);
|
|
845
881
|
if (!result.isValid) {
|
|
846
882
|
return result.message || rule.Message || "Invalid value";
|
|
@@ -912,7 +948,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
912
948
|
onBlur: enhancedOnBlur,
|
|
913
949
|
});
|
|
914
950
|
},
|
|
915
|
-
[rhfForm, validationRules, triggerComputationAfterValidation, mode]
|
|
951
|
+
[rhfForm, validationRules, triggerComputationAfterValidation, mode],
|
|
916
952
|
);
|
|
917
953
|
|
|
918
954
|
return {
|
|
@@ -940,7 +976,7 @@ export function useForm<T extends Record<string, any> = Record<string, any>>(
|
|
|
940
976
|
isCreatingDraft,
|
|
941
977
|
|
|
942
978
|
// Error handling
|
|
943
|
-
loadError: loadError
|
|
979
|
+
loadError: loadError ? toError(loadError) : null,
|
|
944
980
|
hasError,
|
|
945
981
|
|
|
946
982
|
// Schema information
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
// Core TypeScript interfaces for the kanban board functionality
|
|
5
5
|
// Following patterns from useTable and useForm
|
|
6
6
|
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
7
|
+
import type { UseFilterReturnType, UseFilterOptionsType } from "../useFilter";
|
|
8
|
+
import type { ColumnDefinitionType } from "../../../types/common";
|
|
9
|
+
|
|
10
|
+
// Re-export ColumnDefinitionType for backwards compatibility
|
|
11
|
+
export type { ColumnDefinitionType };
|
|
9
12
|
|
|
10
13
|
// ============================================================
|
|
11
14
|
// CORE DATA STRUCTURES
|
|
@@ -95,23 +98,6 @@ export interface KanbanColumnType<T = Record<string, any>> {
|
|
|
95
98
|
_modified_at?: Date;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
/**
|
|
99
|
-
* Column definition for display and behavior
|
|
100
|
-
* Similar to ColumnDefinition in useTable
|
|
101
|
-
*/
|
|
102
|
-
export interface ColumnDefinitionType<T> {
|
|
103
|
-
/** Field name from the card type */
|
|
104
|
-
fieldId: keyof T;
|
|
105
|
-
/** Display label (optional, defaults to fieldId) */
|
|
106
|
-
label?: string;
|
|
107
|
-
/** Enable sorting for this field */
|
|
108
|
-
enableSorting?: boolean;
|
|
109
|
-
/** Enable filtering for this field */
|
|
110
|
-
enableFiltering?: boolean;
|
|
111
|
-
/** Custom transform function (overrides auto-formatting) */
|
|
112
|
-
transform?: (value: any, card: T) => React.ReactNode;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
101
|
// ============================================================
|
|
116
102
|
// DRAG & DROP TYPES
|
|
117
103
|
// ============================================================
|
|
@@ -195,10 +181,8 @@ export interface UseKanbanOptionsType<T> {
|
|
|
195
181
|
|
|
196
182
|
/** Initial state */
|
|
197
183
|
initialState?: {
|
|
198
|
-
/** Initial filter conditions */
|
|
199
|
-
|
|
200
|
-
/** Initial filter operator for combining filter conditions */
|
|
201
|
-
filterOperator?: ConditionGroupOperatorType;
|
|
184
|
+
/** Initial filter configuration: { conditions, operator } */
|
|
185
|
+
filter?: UseFilterOptionsType;
|
|
202
186
|
/** Initial search query */
|
|
203
187
|
search?: string;
|
|
204
188
|
/** Initial column order */
|