@evoke-platform/ui-components 1.15.0 → 1.15.1
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/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +4 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +0 -6
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +149 -98
- package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/DefaultValues.js +60 -89
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +0 -1
- package/dist/published/components/custom/FormV2/components/FormContext.js +0 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +3 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +2 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +6 -2
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +7 -8
- package/dist/published/components/custom/FormV2/components/utils.js +3 -3
- package/dist/published/theme/hooks.d.ts +2 -3
- package/package.json +2 -1
|
@@ -390,7 +390,10 @@ const CriteriaBuilder = (props) => {
|
|
|
390
390
|
return defaultRuleProcessorMongoDB(newRule, options);
|
|
391
391
|
},
|
|
392
392
|
}));
|
|
393
|
-
if (
|
|
393
|
+
if (isEmpty(difference(newCriteria, criteria))) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
else if (!isEmpty(difference(newCriteria, { $and: [{ $expr: true }] }))) {
|
|
394
397
|
setCriteria(newCriteria);
|
|
395
398
|
}
|
|
396
399
|
else {
|
|
@@ -30,7 +30,6 @@ const FormRendererInternal = (props) => {
|
|
|
30
30
|
const [fetchedOptions, setFetchedOptions] = useState({});
|
|
31
31
|
const [expandAll, setExpandAll] = useState();
|
|
32
32
|
const [action, setAction] = useState();
|
|
33
|
-
const [triggerFieldReset, setTriggerFieldReset] = useState(false);
|
|
34
33
|
const [isInitializing, setIsInitializing] = useState(true);
|
|
35
34
|
const [parameters, setParameters] = useState();
|
|
36
35
|
const validationContainerRef = useRef(null);
|
|
@@ -83,9 +82,6 @@ const FormRendererInternal = (props) => {
|
|
|
83
82
|
setValue(key, value[key], { shouldValidate: true });
|
|
84
83
|
}
|
|
85
84
|
}
|
|
86
|
-
if (triggerFieldReset === true) {
|
|
87
|
-
setTriggerFieldReset(false);
|
|
88
|
-
}
|
|
89
85
|
}
|
|
90
86
|
}, [value]);
|
|
91
87
|
const handleReset = () => {
|
|
@@ -95,7 +91,6 @@ const FormRendererInternal = (props) => {
|
|
|
95
91
|
else {
|
|
96
92
|
reset(instance); // clears react-hook-form state back to default values
|
|
97
93
|
}
|
|
98
|
-
setTriggerFieldReset(true);
|
|
99
94
|
};
|
|
100
95
|
useEffect(() => {
|
|
101
96
|
handleValidation(entries, register, getValues(), action?.parameters, instance);
|
|
@@ -209,7 +204,6 @@ const FormRendererInternal = (props) => {
|
|
|
209
204
|
fieldHeight,
|
|
210
205
|
handleChange: onChange,
|
|
211
206
|
onAutosave,
|
|
212
|
-
triggerFieldReset,
|
|
213
207
|
showSubmitError: isSubmitted,
|
|
214
208
|
associatedObject,
|
|
215
209
|
form,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useApiServices, useApp, useAuthenticationContext, useNavigate, useObject, } from '@evoke-platform/context';
|
|
2
2
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
3
3
|
import axios from 'axios';
|
|
4
|
-
import { cloneDeep, get, isArray, isEmpty, isEqual,
|
|
5
|
-
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { cloneDeep, get, isArray, isEmpty, isEqual, isObject, pick, set } from 'lodash';
|
|
5
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { Skeleton, Snackbar } from '../../core';
|
|
7
7
|
import { Box } from '../../layout';
|
|
8
8
|
import ErrorComponent from '../ErrorComponent';
|
|
@@ -11,6 +11,7 @@ import { evalDefaultVals, processValueUpdate } from './components/DefaultValues'
|
|
|
11
11
|
import Header from './components/Header';
|
|
12
12
|
import { convertPropertiesToParams, createFileLinks, deleteDocuments, encodePageSlug, extractAllCriteria, extractPresetValuesFromCriteria, extractPresetValuesFromDynamicDefaultValues, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault, plainTextToRtf, useFormById, } from './components/utils';
|
|
13
13
|
import FormRenderer from './FormRenderer';
|
|
14
|
+
import { DepGraph } from 'dependency-graph';
|
|
14
15
|
// Wrapper to provide QueryClient context for FormRendererContainer if this is not a nested form
|
|
15
16
|
function FormRendererContainer(props) {
|
|
16
17
|
return (React.createElement(ConditionalQueryClientProvider, null,
|
|
@@ -23,7 +24,7 @@ function FormRendererContainerInner(props) {
|
|
|
23
24
|
const navigateTo = useNavigate();
|
|
24
25
|
const queryClient = useQueryClient();
|
|
25
26
|
const { id: appId } = useApp();
|
|
26
|
-
const [parameters, setParameters] = useState();
|
|
27
|
+
const [parameters, setParameters] = useState([]);
|
|
27
28
|
const formDataRef = useRef();
|
|
28
29
|
// We only need the setter to force a re-render when form data updates; the value itself
|
|
29
30
|
// is intentionally not referenced elsewhere to avoid stale reads (we use formDataRef).
|
|
@@ -53,7 +54,54 @@ function FormRendererContainerInner(props) {
|
|
|
53
54
|
const [isSaving, setIsSaving] = useState(false);
|
|
54
55
|
const [lastSavedData, setLastSavedData] = useState({});
|
|
55
56
|
const [uniquePresetValues, setUniquePresetValues] = useState([]);
|
|
56
|
-
const flattenFormEntries = useMemo(() =>
|
|
57
|
+
const flattenFormEntries = useMemo(() => {
|
|
58
|
+
const graph = new DepGraph({ circular: true });
|
|
59
|
+
const unnestedEntries = getUnnestedEntries(form?.entries || []);
|
|
60
|
+
const nonInputEntries = [];
|
|
61
|
+
for (const entry of unnestedEntries) {
|
|
62
|
+
const entryId = getEntryId(entry);
|
|
63
|
+
if (entryId && entry.type !== 'readonlyField') {
|
|
64
|
+
graph.addNode(entryId, entry);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
nonInputEntries.push(entry);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const entry of unnestedEntries) {
|
|
71
|
+
const entryId = getEntryId(entry);
|
|
72
|
+
if (entryId && (entry.type === 'input' || entry.type === 'inputField')) {
|
|
73
|
+
const { defaultValue } = entry.display || {};
|
|
74
|
+
let presetValues = [];
|
|
75
|
+
if (typeof defaultValue === 'string') {
|
|
76
|
+
presetValues = extractPresetValuesFromDynamicDefaultValues([entry]);
|
|
77
|
+
}
|
|
78
|
+
else if (isObject(defaultValue) && 'criteria' in defaultValue && !isEmpty(defaultValue.criteria)) {
|
|
79
|
+
presetValues = extractPresetValuesFromCriteria(defaultValue.criteria);
|
|
80
|
+
}
|
|
81
|
+
presetValues.forEach((presetValue) => {
|
|
82
|
+
const fragments = presetValue.replace(/{{{input.|}}}|{{input.|}}/g, '').split('.');
|
|
83
|
+
// preset value references top level fields i.e name or dateOfBirth
|
|
84
|
+
if (fragments.length === 1 && graph.hasNode(fragments[0])) {
|
|
85
|
+
graph.addDependency(entryId, fragments[0]);
|
|
86
|
+
}
|
|
87
|
+
else if (fragments.length > 1) {
|
|
88
|
+
// preset value references nested fields i.e address.line1 or person.address.line1
|
|
89
|
+
const addressKeys = ['line1', 'line2', 'city', 'county', 'state', 'zipCode', 'country'];
|
|
90
|
+
if (addressKeys.includes(fragments[1]) && graph.hasNode(`${fragments[0]}.${fragments[1]}`)) {
|
|
91
|
+
graph.addDependency(entryId, `${fragments[0]}.${fragments[1]}`);
|
|
92
|
+
}
|
|
93
|
+
else if (graph.hasNode(fragments[0])) {
|
|
94
|
+
graph.addDependency(entryId, fragments[0]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return graph
|
|
101
|
+
.overallOrder()
|
|
102
|
+
.map((id) => graph.getNodeData(id))
|
|
103
|
+
.concat(nonInputEntries);
|
|
104
|
+
}, [form?.entries]);
|
|
57
105
|
const userAccount = useAuthenticationContext()?.account;
|
|
58
106
|
const objectStore = useObject(form?.objectId ?? objectId);
|
|
59
107
|
const onError = (err) => {
|
|
@@ -71,7 +119,7 @@ function FormRendererContainerInner(props) {
|
|
|
71
119
|
});
|
|
72
120
|
// trigger refetch on success
|
|
73
121
|
const { data: instance, error: instanceError } = useQuery({
|
|
74
|
-
queryKey: [objectId, instanceId, 'instance'],
|
|
122
|
+
queryKey: [objectId, instanceId, 'instance', uniquePresetValues],
|
|
75
123
|
queryFn: async () => {
|
|
76
124
|
const instance = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instanceId}`), {
|
|
77
125
|
params: {
|
|
@@ -93,6 +141,87 @@ function FormRendererContainerInner(props) {
|
|
|
93
141
|
staleTime: Infinity,
|
|
94
142
|
enabled: !!instanceId && !!sanitizedObject,
|
|
95
143
|
});
|
|
144
|
+
const getDefaultValues = useCallback(async (unnestedEntries, instanceData) => {
|
|
145
|
+
const result = cloneDeep(instanceData);
|
|
146
|
+
for (const entry of unnestedEntries) {
|
|
147
|
+
const fieldId = getEntryId(entry);
|
|
148
|
+
if (!fieldId)
|
|
149
|
+
continue;
|
|
150
|
+
const fieldValue = get(result, fieldId);
|
|
151
|
+
if ((entry.type === 'input' || entry.type === 'inputField') &&
|
|
152
|
+
isAddressProperty(entry.parameterId || entry.input?.id)) {
|
|
153
|
+
if ((isEmpty(result) || fieldValue === undefined || fieldValue === null || fieldValue === '') &&
|
|
154
|
+
entry?.display?.defaultValue &&
|
|
155
|
+
parameters) {
|
|
156
|
+
const defaultValuesArray = await evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, result);
|
|
157
|
+
if (isArray(defaultValuesArray)) {
|
|
158
|
+
defaultValuesArray.forEach(({ fieldId, fieldValue }) => {
|
|
159
|
+
set(result, fieldId, fieldValue);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
164
|
+
set(result, fieldId, fieldValue);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (entry.type !== 'sections' && entry.type !== 'columns' && entry.type !== 'content') {
|
|
168
|
+
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
169
|
+
if (associatedObject?.propertyId === fieldId &&
|
|
170
|
+
associatedObject?.instanceId &&
|
|
171
|
+
parameter &&
|
|
172
|
+
action?.type === 'create') {
|
|
173
|
+
try {
|
|
174
|
+
const instance = await apiServices.get(getPrefixedUrl(`/objects/${parameter.objectId}/instances/${associatedObject.instanceId}`), {
|
|
175
|
+
params: {
|
|
176
|
+
expand: uniquePresetValues.filter((value) => value.startsWith(`{{{input.${fieldId}.`) ||
|
|
177
|
+
value.startsWith(`{{input.${fieldId}.`)),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
result[associatedObject.propertyId] = instance;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error(error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (entry.type === 'formlet') {
|
|
187
|
+
// TODO: this should eventually fetch the formletId then get the fields and default values of those fields
|
|
188
|
+
}
|
|
189
|
+
else if (entry.type !== 'readonlyField') {
|
|
190
|
+
if (isEmptyWithDefault(fieldValue, entry, result)) {
|
|
191
|
+
if (fieldId && parameters && parameters.length > 0) {
|
|
192
|
+
const defaultValuesArray = await evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, result);
|
|
193
|
+
for (const { fieldId, fieldValue } of defaultValuesArray) {
|
|
194
|
+
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
195
|
+
if (parameter?.type === 'object') {
|
|
196
|
+
const dependentFields = await processValueUpdate(unnestedEntries, parameters, fieldValue, apiServices, fieldId, formDataRef.current, userAccount);
|
|
197
|
+
for (const field of dependentFields) {
|
|
198
|
+
set(result, field.fieldId, field.fieldValue);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
set(result, fieldId, fieldValue);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (parameter?.type === 'boolean' && (fieldValue === undefined || fieldValue === null)) {
|
|
206
|
+
result[fieldId] = false;
|
|
207
|
+
}
|
|
208
|
+
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
209
|
+
if (parameter?.type === 'richText' && typeof fieldValue === 'string') {
|
|
210
|
+
let RTFFieldValue = fieldValue;
|
|
211
|
+
if (!fieldValue.trim().startsWith('{\\rtf')) {
|
|
212
|
+
RTFFieldValue = plainTextToRtf(fieldValue);
|
|
213
|
+
}
|
|
214
|
+
result[fieldId] = RTFFieldValue;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
result[fieldId] = fieldValue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}, [action, parameters, associatedObject, uniquePresetValues, formDataRef, apiServices, userAccount]);
|
|
96
225
|
useEffect(() => {
|
|
97
226
|
if (!sanitizedObject)
|
|
98
227
|
return;
|
|
@@ -113,7 +242,7 @@ function FormRendererContainerInner(props) {
|
|
|
113
242
|
else {
|
|
114
243
|
setError('Action could not be found');
|
|
115
244
|
}
|
|
116
|
-
}, [sanitizedObject, actionId, form?.actionId, instanceId]);
|
|
245
|
+
}, [sanitizedObject, actionId, form?.actionId, instanceId, flattenFormEntries, parameters]);
|
|
117
246
|
const { data: navigationSlug } = useQuery({
|
|
118
247
|
queryKey: [appId, 'navigationSlug'],
|
|
119
248
|
queryFn: () => apiServices.get(getPrefixedUrl(`/apps/${appId}/pages/${encodePageSlug(pageNavigation)}`)),
|
|
@@ -159,11 +288,12 @@ function FormRendererContainerInner(props) {
|
|
|
159
288
|
onError(error);
|
|
160
289
|
}, [sanitizedObjectError, fetchedFormError, instanceError]);
|
|
161
290
|
useEffect(() => {
|
|
162
|
-
if (!form)
|
|
291
|
+
if (!form || !action)
|
|
163
292
|
return;
|
|
164
293
|
// If no parameters are defined, then the action is synced with object properties
|
|
165
|
-
const getParamsFromObject = sanitizedObject && !action
|
|
166
|
-
|
|
294
|
+
const getParamsFromObject = sanitizedObject && !action.parameters;
|
|
295
|
+
const parameters = (getParamsFromObject ? convertPropertiesToParams(sanitizedObject) : action.parameters) ?? [];
|
|
296
|
+
setParameters(parameters.filter((param) => param.type !== 'collection' && !param.formula));
|
|
167
297
|
}, [form, action?.parameters, sanitizedObject]);
|
|
168
298
|
useEffect(() => {
|
|
169
299
|
const getInitialValues = async () => {
|
|
@@ -175,7 +305,7 @@ function FormRendererContainerInner(props) {
|
|
|
175
305
|
}
|
|
176
306
|
};
|
|
177
307
|
getInitialValues();
|
|
178
|
-
}, [instanceId, instance, flattenFormEntries]);
|
|
308
|
+
}, [instanceId, instance, flattenFormEntries, getDefaultValues]);
|
|
179
309
|
const onSubmissionSuccess = (updatedInstance) => {
|
|
180
310
|
setSnackbarError({
|
|
181
311
|
showAlert: true,
|
|
@@ -222,9 +352,7 @@ function FormRendererContainerInner(props) {
|
|
|
222
352
|
if (action?.type === 'create') {
|
|
223
353
|
const response = await apiServices.post(getPrefixedUrl(`/objects/${form.objectId}/instances/actions`), {
|
|
224
354
|
actionId: form.actionId,
|
|
225
|
-
input:
|
|
226
|
-
?.filter((property) => property.formula || property.type === 'collection')
|
|
227
|
-
.map((property) => property.id) ?? []),
|
|
355
|
+
input: pick(submission, parameters.map((parameter) => parameter.id)),
|
|
228
356
|
});
|
|
229
357
|
if (response) {
|
|
230
358
|
// Manually link files to created instance.
|
|
@@ -235,9 +363,7 @@ function FormRendererContainerInner(props) {
|
|
|
235
363
|
else if (instanceId && action) {
|
|
236
364
|
const response = await objectStore.instanceAction(instanceId, {
|
|
237
365
|
actionId: action.id,
|
|
238
|
-
input:
|
|
239
|
-
?.filter((property) => property.formula || property.type === 'collection')
|
|
240
|
-
.map((property) => property.id) ?? []),
|
|
366
|
+
input: pick(submission, parameters.map((parameter) => parameter.id)),
|
|
241
367
|
});
|
|
242
368
|
if (sanitizedObject && instance) {
|
|
243
369
|
onSubmissionSuccess(response);
|
|
@@ -258,79 +384,6 @@ function FormRendererContainerInner(props) {
|
|
|
258
384
|
throw error; // Throw error so caller knows submission failed
|
|
259
385
|
}
|
|
260
386
|
};
|
|
261
|
-
const getDefaultValues = async (unnestedEntries, instanceData) => {
|
|
262
|
-
const result = {};
|
|
263
|
-
for (const entry of unnestedEntries) {
|
|
264
|
-
const fieldId = getEntryId(entry);
|
|
265
|
-
if (!fieldId)
|
|
266
|
-
continue;
|
|
267
|
-
const fieldValue = get(instanceData, fieldId);
|
|
268
|
-
if ((entry.type === 'input' || entry.type === 'inputField') &&
|
|
269
|
-
isAddressProperty(entry.parameterId || entry.input?.id)) {
|
|
270
|
-
if ((isEmpty(instanceData) || fieldValue === undefined || fieldValue === null || fieldValue === '') &&
|
|
271
|
-
entry?.display?.defaultValue &&
|
|
272
|
-
parameters) {
|
|
273
|
-
const defaultValuesArray = await evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
274
|
-
if (isArray(defaultValuesArray)) {
|
|
275
|
-
defaultValuesArray.forEach(({ fieldId, fieldValue }) => {
|
|
276
|
-
set(result, fieldId, fieldValue);
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
281
|
-
set(result, fieldId, fieldValue);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
else if (entry.type !== 'sections' && entry.type !== 'columns' && entry.type !== 'content') {
|
|
285
|
-
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
286
|
-
if (associatedObject?.propertyId === fieldId &&
|
|
287
|
-
associatedObject?.instanceId &&
|
|
288
|
-
parameter &&
|
|
289
|
-
action?.type === 'create') {
|
|
290
|
-
try {
|
|
291
|
-
const instance = await apiServices.get(getPrefixedUrl(`/objects/${parameter.objectId}/instances/${associatedObject.instanceId}`));
|
|
292
|
-
result[associatedObject.propertyId] = instance;
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
console.error(error);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
else if (entry.type !== 'readonlyField') {
|
|
299
|
-
if (isEmptyWithDefault(fieldValue, entry, instanceData)) {
|
|
300
|
-
if (fieldId && parameters && parameters.length > 0) {
|
|
301
|
-
const defaultValuesArray = await evalDefaultVals(parameters, unnestedEntries, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
302
|
-
for (const { fieldId, fieldValue } of defaultValuesArray) {
|
|
303
|
-
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
304
|
-
if (parameter?.type === 'object') {
|
|
305
|
-
const dependentFields = await processValueUpdate(unnestedEntries, parameters, fieldValue, apiServices, fieldId, formDataRef.current, userAccount);
|
|
306
|
-
for (const field of dependentFields) {
|
|
307
|
-
set(result, field.fieldId, field.fieldValue);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
set(result, fieldId, fieldValue);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
else if (parameter?.type === 'boolean' && (fieldValue === undefined || fieldValue === null)) {
|
|
315
|
-
result[fieldId] = false;
|
|
316
|
-
}
|
|
317
|
-
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
318
|
-
if (parameter?.type === 'richText' && typeof fieldValue === 'string') {
|
|
319
|
-
let RTFFieldValue = fieldValue;
|
|
320
|
-
if (!fieldValue.trim().startsWith('{\\rtf')) {
|
|
321
|
-
RTFFieldValue = plainTextToRtf(fieldValue);
|
|
322
|
-
}
|
|
323
|
-
result[fieldId] = RTFFieldValue;
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
result[fieldId] = fieldValue;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return result;
|
|
333
|
-
};
|
|
334
387
|
const removeUneditedProtectedValues = (data) => {
|
|
335
388
|
const protectedProperties = sanitizedObject?.properties?.filter((prop) => prop.protection?.maskChar);
|
|
336
389
|
if (!protectedProperties || protectedProperties.length === 0) {
|
|
@@ -385,21 +438,19 @@ function FormRendererContainerInner(props) {
|
|
|
385
438
|
!flattenFormEntries.some((e) => (e.type === 'input' && e.parameterId === id) || (e.type === 'inputField' && e.input.id === id));
|
|
386
439
|
if (isReadOnlyField)
|
|
387
440
|
return;
|
|
388
|
-
if (parameter) {
|
|
389
|
-
|
|
441
|
+
if (parameter?.type === 'string' && parameter.enum && value) {
|
|
442
|
+
// If a single select property has a sortBy option that isn't NONE the value gets spread and doesn't save properly,
|
|
443
|
+
// this will make it correctly save the value
|
|
444
|
+
value = value.value ? value.value : value;
|
|
445
|
+
}
|
|
446
|
+
if (!isEqual(value, get(formDataRef.current, id))) {
|
|
447
|
+
if (parameter?.type === 'object' && parameters && parameters.length > 0) {
|
|
390
448
|
// On change of a related object, update default values dependent on that object
|
|
391
449
|
const dependentFields = await processValueUpdate(flattenFormEntries, parameters, value, apiServices, id, formDataRef.current, userAccount);
|
|
392
450
|
for (const field of dependentFields) {
|
|
393
451
|
onChange(field.fieldId, field.fieldValue);
|
|
394
452
|
}
|
|
395
453
|
}
|
|
396
|
-
else if (parameter.type === 'string' && parameter.enum && value) {
|
|
397
|
-
// If a single select property has a sortBy option that isn't NONE the value gets spread and doesn't save properly,
|
|
398
|
-
// this will make it correctly save the value
|
|
399
|
-
value = value.value ? value.value : value;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (!isEqual(value, get(formDataRef.current, id))) {
|
|
403
454
|
const newData = { ...formDataRef.current };
|
|
404
455
|
set(newData, id, value);
|
|
405
456
|
setFormData(newData);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiServices, FormEntry, InputField, InputParameter, InputParameterReference, ObjectInstance, Reference, UserAccount } from '@evoke-platform/context';
|
|
2
2
|
import { FieldValues } from 'react-hook-form';
|
|
3
|
-
export declare function evalDefaultVals(parameters: InputParameter[],
|
|
3
|
+
export declare function evalDefaultVals(parameters: InputParameter[], entry: InputParameterReference | InputField, fieldValue: unknown, fieldId: string, apiServices: ApiServices, userAccount?: UserAccount, formValues?: FieldValues, updatedRelatedObjectValue?: ObjectInstance | null | Reference, updatedRelatedObjectParamId?: string): Promise<{
|
|
4
4
|
fieldId: string;
|
|
5
5
|
fieldValue: unknown;
|
|
6
6
|
}[]>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { isArray, isEmpty, uniq } from 'lodash';
|
|
1
|
+
import { isArray, isEmpty, isObject, uniq } from 'lodash';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
|
-
import { getEntryId, getPrefixedUrl, isAddressProperty } from './utils';
|
|
4
|
-
export async function evalDefaultVals(parameters,
|
|
3
|
+
import { getEntryId, getPrefixedUrl, isAddressProperty, transformToWhere, updateCriteriaInputs } from './utils';
|
|
4
|
+
export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, formValues, updatedRelatedObjectValue, updatedRelatedObjectParamId) {
|
|
5
5
|
const updates = [];
|
|
6
6
|
const parameter = parameters.find((param) => param.id === fieldId);
|
|
7
7
|
const defaultValue = entry.display?.defaultValue;
|
|
@@ -14,7 +14,6 @@ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldV
|
|
|
14
14
|
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
|
|
15
15
|
const groups = regex.exec(item)?.groups;
|
|
16
16
|
if (groups?.relatedObjectProperty && groups?.nestedProperty) {
|
|
17
|
-
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
18
17
|
let relatedObjectInstance = updatedRelatedObjectValue;
|
|
19
18
|
if (!relatedObjectInstance && !isEmpty(formValues)) {
|
|
20
19
|
relatedObjectInstance = formValues[groups.relatedObjectProperty];
|
|
@@ -28,30 +27,14 @@ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldV
|
|
|
28
27
|
]);
|
|
29
28
|
updates.push({ fieldId, fieldValue });
|
|
30
29
|
}
|
|
31
|
-
else if (relatedObjectInstance
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (error) {
|
|
40
|
-
console.error(error);
|
|
41
|
-
return resolve(undefined);
|
|
42
|
-
}
|
|
43
|
-
resolve(instance);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
if (instance) {
|
|
47
|
-
fieldValue = uniq([
|
|
48
|
-
...staticValues,
|
|
49
|
-
...(isArray(instance[groups.nestedProperty])
|
|
50
|
-
? instance[groups.nestedProperty]
|
|
51
|
-
: []),
|
|
52
|
-
]);
|
|
53
|
-
updates.push({ fieldId, fieldValue });
|
|
54
|
-
}
|
|
30
|
+
else if (relatedObjectInstance) {
|
|
31
|
+
fieldValue = uniq([
|
|
32
|
+
...staticValues,
|
|
33
|
+
...(isArray(relatedObjectInstance[groups.nestedProperty])
|
|
34
|
+
? relatedObjectInstance[groups.nestedProperty]
|
|
35
|
+
: []),
|
|
36
|
+
]);
|
|
37
|
+
updates.push({ fieldId, fieldValue });
|
|
55
38
|
}
|
|
56
39
|
else {
|
|
57
40
|
updates.push({ fieldId, fieldValue: staticValues });
|
|
@@ -65,42 +48,23 @@ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldV
|
|
|
65
48
|
}
|
|
66
49
|
else if (typeof defaultValue === 'string' && /^{{.*}}$/.test(defaultValue)) {
|
|
67
50
|
if (isAddressProperty(fieldId)) {
|
|
68
|
-
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<addressProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedAddressProperty>line1|line2|city|county|state|zipCode)}}$/;
|
|
51
|
+
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<addressProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedAddressProperty>line1|line2|city|county|state|zipCode|country)}}$/;
|
|
69
52
|
const groups = regex.exec(defaultValue)?.groups;
|
|
70
53
|
if (groups?.relatedObjectProperty && groups?.addressProperty && groups?.nestedAddressProperty) {
|
|
71
|
-
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
72
54
|
let relatedObjectInstance = updatedRelatedObjectValue;
|
|
73
55
|
if (!relatedObjectInstance && !isEmpty(formValues)) {
|
|
74
56
|
relatedObjectInstance = formValues[groups.relatedObjectProperty];
|
|
75
57
|
}
|
|
76
|
-
if (updatedRelatedObjectValue
|
|
58
|
+
if (updatedRelatedObjectValue === null) {
|
|
59
|
+
updates.push({ fieldId, fieldValue: '' });
|
|
60
|
+
}
|
|
61
|
+
else if (updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty]) {
|
|
77
62
|
fieldValue = updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty];
|
|
78
63
|
updates.push({ fieldId, fieldValue });
|
|
79
64
|
}
|
|
80
|
-
else if (relatedObjectInstance
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
|
|
84
|
-
relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
|
|
85
|
-
}
|
|
86
|
-
const instance = await new Promise((resolve) => {
|
|
87
|
-
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
|
|
88
|
-
if (error) {
|
|
89
|
-
console.error(error);
|
|
90
|
-
return resolve(undefined);
|
|
91
|
-
}
|
|
92
|
-
resolve(instance);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
// Clear dependent fields only if value is explicitly null (user cleared it).
|
|
96
|
-
// If updatedRelatedObjectValue is undefined (not triggered by onChange), use the value from the instance.
|
|
97
|
-
if (updatedRelatedObjectValue === null) {
|
|
98
|
-
updates.push({ fieldId, fieldValue: '' });
|
|
99
|
-
}
|
|
100
|
-
else if (instance) {
|
|
101
|
-
fieldValue = instance?.[groups.addressProperty]?.[groups.nestedAddressProperty];
|
|
102
|
-
updates.push({ fieldId, fieldValue });
|
|
103
|
-
}
|
|
65
|
+
else if (relatedObjectInstance) {
|
|
66
|
+
fieldValue = relatedObjectInstance?.[groups.addressProperty]?.[groups.nestedAddressProperty];
|
|
67
|
+
updates.push({ fieldId, fieldValue });
|
|
104
68
|
}
|
|
105
69
|
}
|
|
106
70
|
}
|
|
@@ -108,44 +72,44 @@ export async function evalDefaultVals(parameters, unnestedEntries, entry, fieldV
|
|
|
108
72
|
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
|
|
109
73
|
const groups = regex.exec(defaultValue)?.groups;
|
|
110
74
|
if (groups?.relatedObjectProperty && groups?.nestedProperty) {
|
|
111
|
-
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
112
75
|
let relatedObjectInstance = updatedRelatedObjectValue;
|
|
113
76
|
if (!relatedObjectInstance && !isEmpty(formValues)) {
|
|
114
77
|
relatedObjectInstance = formValues[groups.relatedObjectProperty];
|
|
115
78
|
}
|
|
116
|
-
if (updatedRelatedObjectValue
|
|
79
|
+
if (updatedRelatedObjectValue === null) {
|
|
80
|
+
updates.push({ fieldId, fieldValue: null });
|
|
81
|
+
}
|
|
82
|
+
else if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
|
|
117
83
|
fieldValue = updatedRelatedObjectValue[groups.nestedProperty];
|
|
118
84
|
updates.push({ fieldId, fieldValue });
|
|
119
85
|
}
|
|
120
|
-
else if (relatedObjectInstance
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const relatedObjectParamEntry = unnestedEntries.find((e) => getEntryId(e) === relatedObjectParameter.id);
|
|
124
|
-
relatedObjectId = relatedObjectParamEntry?.display?.relatedObjectId;
|
|
125
|
-
}
|
|
126
|
-
const instance = await new Promise((resolve) => {
|
|
127
|
-
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${relatedObjectInstance?.id}`), (error, instance) => {
|
|
128
|
-
if (error) {
|
|
129
|
-
console.error(error);
|
|
130
|
-
return resolve(undefined);
|
|
131
|
-
}
|
|
132
|
-
resolve(instance);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
// Clear dependent fields only if value is explicitly null (user cleared it).
|
|
136
|
-
// If updatedRelatedObjectValue is undefined (not triggered by onChange), use the value from the instance.
|
|
137
|
-
if (updatedRelatedObjectValue === null) {
|
|
138
|
-
updates.push({ fieldId, fieldValue: null });
|
|
139
|
-
}
|
|
140
|
-
else if (instance) {
|
|
141
|
-
fieldValue = instance?.[groups.nestedProperty] || null;
|
|
142
|
-
updates.push({ fieldId, fieldValue });
|
|
143
|
-
}
|
|
86
|
+
else if (relatedObjectInstance) {
|
|
87
|
+
fieldValue = relatedObjectInstance[groups.nestedProperty] || null;
|
|
88
|
+
updates.push({ fieldId, fieldValue });
|
|
144
89
|
}
|
|
145
90
|
}
|
|
146
91
|
}
|
|
147
92
|
// all other default values set here
|
|
148
93
|
}
|
|
94
|
+
else if (isObject(defaultValue) && 'criteria' in defaultValue && !isEmpty(defaultValue.criteria)) {
|
|
95
|
+
const criteria = defaultValue.criteria;
|
|
96
|
+
const evaluatedCriteria = updateCriteriaInputs(criteria, { ...formValues, [updatedRelatedObjectParamId || '']: updatedRelatedObjectValue }, userAccount);
|
|
97
|
+
try {
|
|
98
|
+
const instances = await apiServices.get(getPrefixedUrl(`/objects/${parameter?.objectId || entry.display?.relatedObjectId}/instances`), {
|
|
99
|
+
params: {
|
|
100
|
+
filter: {
|
|
101
|
+
where: transformToWhere(evaluatedCriteria),
|
|
102
|
+
limit: 1,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
updates.push({ fieldId, fieldValue: instances[0] ?? null });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
updates.push({ fieldId, fieldValue: null });
|
|
110
|
+
console.error(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
149
113
|
else if (parameter?.type !== 'object') {
|
|
150
114
|
let updatedValue = defaultValue;
|
|
151
115
|
// handles current default values ie: "Current logged in user", "Today" etc.
|
|
@@ -175,38 +139,45 @@ export async function processValueUpdate(unnestedEntries, parameters, updatedRel
|
|
|
175
139
|
const parameterId = getEntryId(entry);
|
|
176
140
|
if (!parameterId)
|
|
177
141
|
return [];
|
|
142
|
+
if (parameterId === changedEntryId) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const defaultValue = entry.display.defaultValue;
|
|
178
146
|
if (isAddressProperty(parameterId)) {
|
|
179
|
-
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<addressProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedAddressProperty>line1|line2|city|county|state|zipCode)}}$/;
|
|
147
|
+
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<addressProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedAddressProperty>line1|line2|city|county|state|zipCode|country)}}$/;
|
|
180
148
|
const groups = regex.exec(entry.display.defaultValue)?.groups;
|
|
181
149
|
const [addressObject, addressField] = parameterId.split('.');
|
|
182
150
|
if (groups?.relatedObjectProperty &&
|
|
183
151
|
groups?.addressProperty &&
|
|
184
152
|
groups?.nestedAddressProperty &&
|
|
185
153
|
changedEntryId === groups.relatedObjectProperty) {
|
|
186
|
-
const result = await evalDefaultVals(parameters,
|
|
154
|
+
const result = await evalDefaultVals(parameters, entry, formValues?.[addressObject]?.[addressField], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue, changedEntryId);
|
|
187
155
|
updates.push(...result);
|
|
188
156
|
}
|
|
189
157
|
}
|
|
190
|
-
else if (isArray(
|
|
191
|
-
|
|
192
|
-
for (const item of entry.display.defaultValue.filter((item) => /^{{.*}}$/.test(item))) {
|
|
158
|
+
else if (isArray(defaultValue) && defaultValue.some((item) => /^{{.*}}$/.test(item))) {
|
|
159
|
+
for (const item of defaultValue.filter((item) => /^{{.*}}$/.test(item))) {
|
|
193
160
|
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
|
|
194
161
|
const groups = regex.exec(item)?.groups;
|
|
195
162
|
if (groups?.relatedObjectProperty &&
|
|
196
163
|
groups?.nestedProperty &&
|
|
197
164
|
changedEntryId === groups.relatedObjectProperty) {
|
|
198
|
-
const result = await evalDefaultVals(parameters,
|
|
165
|
+
const result = await evalDefaultVals(parameters, entry, defaultValue, parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue, changedEntryId);
|
|
199
166
|
updates.push(...result);
|
|
200
167
|
}
|
|
201
168
|
}
|
|
202
169
|
}
|
|
170
|
+
else if (isObject(defaultValue) && 'criteria' in defaultValue && !isEmpty(defaultValue.criteria)) {
|
|
171
|
+
const result = await evalDefaultVals(parameters, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue, changedEntryId);
|
|
172
|
+
updates.push(...result);
|
|
173
|
+
}
|
|
203
174
|
else {
|
|
204
175
|
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
|
|
205
|
-
const groups = regex.exec(
|
|
176
|
+
const groups = regex.exec(defaultValue)?.groups;
|
|
206
177
|
if (groups?.relatedObjectProperty &&
|
|
207
178
|
groups?.nestedProperty &&
|
|
208
179
|
changedEntryId === groups.relatedObjectProperty) {
|
|
209
|
-
const result = await evalDefaultVals(parameters,
|
|
180
|
+
const result = await evalDefaultVals(parameters, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue, changedEntryId);
|
|
210
181
|
updates.push(...result);
|
|
211
182
|
}
|
|
212
183
|
}
|
|
@@ -18,7 +18,6 @@ type FormContextType = {
|
|
|
18
18
|
handleChange?: (name: string, value: unknown) => void | Promise<void>;
|
|
19
19
|
onAutosave?: (fieldId: string) => void | Promise<void>;
|
|
20
20
|
fieldHeight?: 'small' | 'medium';
|
|
21
|
-
triggerFieldReset?: boolean;
|
|
22
21
|
showSubmitError?: boolean;
|
|
23
22
|
associatedObject?: {
|
|
24
23
|
instanceId: string;
|
|
@@ -7,5 +7,5 @@ type CriteriaProps = {
|
|
|
7
7
|
error?: boolean;
|
|
8
8
|
};
|
|
9
9
|
type CriteriaValue = Record<string, unknown>;
|
|
10
|
-
|
|
11
|
-
export
|
|
10
|
+
declare const Criteria: (props: CriteriaProps) => React.JSX.Element;
|
|
11
|
+
export default Criteria;
|
|
@@ -6,7 +6,7 @@ import { Button, CircularProgress, Skeleton, Typography } from '../../../../core
|
|
|
6
6
|
import { Box } from '../../../../layout';
|
|
7
7
|
import CriteriaBuilder from '../../../CriteriaBuilder';
|
|
8
8
|
import { addressProperties, getPrefixedUrl } from '../utils';
|
|
9
|
-
|
|
9
|
+
const Criteria = (props) => {
|
|
10
10
|
const { value, canUpdateProperty, fieldDefinition, error } = props;
|
|
11
11
|
const apiServices = useApiServices();
|
|
12
12
|
const { handleChange, onAutosave } = useFormContext();
|
|
@@ -90,4 +90,5 @@ export default function Criteria(props) {
|
|
|
90
90
|
type: 'date-time',
|
|
91
91
|
},
|
|
92
92
|
], enablePresetValues: true }))) : (React.createElement(Typography, { variant: "body2", sx: { color: '#637381' } }, "No criteria"));
|
|
93
|
-
}
|
|
93
|
+
};
|
|
94
|
+
export default Criteria;
|
|
@@ -56,11 +56,9 @@ const styles = {
|
|
|
56
56
|
export const Image = (props) => {
|
|
57
57
|
const { id, canUpdateProperty, error, value, hasDescription } = props;
|
|
58
58
|
const { handleChange, onAutosave: onAutosave } = useFormContext();
|
|
59
|
-
const [image, setImage] = useState();
|
|
59
|
+
const [image, setImage] = useState(value ?? null);
|
|
60
60
|
useEffect(() => {
|
|
61
|
-
|
|
62
|
-
setImage(value);
|
|
63
|
-
}
|
|
61
|
+
setImage(value ?? null);
|
|
64
62
|
}, [value]);
|
|
65
63
|
const handleUpload = async (file) => {
|
|
66
64
|
// max file size 300KB
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useApiServices } from '@evoke-platform/context';
|
|
2
2
|
import { DialogActions } from '@mui/material';
|
|
3
3
|
import { useQueryClient } from '@tanstack/react-query';
|
|
4
|
-
import { isEmpty } from 'lodash';
|
|
4
|
+
import { isEmpty, pick } from 'lodash';
|
|
5
5
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { Close } from '../../../../../../icons';
|
|
7
7
|
import useWidgetSize, { useFormContext } from '../../../../../../theme/hooks';
|
|
@@ -95,11 +95,15 @@ const RelatedObjectInstance = (props) => {
|
|
|
95
95
|
// Handle the case where relatedObject is undefined
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
|
+
const action = relatedObject.actions?.find((act) => act.id === actionId);
|
|
99
|
+
const parameters = action?.parameters || [];
|
|
98
100
|
submission = await formatSubmission(submission, apiServices, relatedObject.id);
|
|
99
101
|
try {
|
|
100
102
|
const response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObject.id}/instances/actions`), {
|
|
101
103
|
actionId: actionId,
|
|
102
|
-
input: submission,
|
|
104
|
+
input: pick(submission, parameters
|
|
105
|
+
.filter((param) => param.type !== 'collection' && !param.formula)
|
|
106
|
+
.map((param) => param.id)),
|
|
103
107
|
});
|
|
104
108
|
const expandedInstance = await apiServices.get(getPrefixedUrl(`/objects/${relatedObject.id}/instances/${response.id}`), {
|
|
105
109
|
params: {
|
|
@@ -39,7 +39,7 @@ function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, displ
|
|
|
39
39
|
}
|
|
40
40
|
export function RecursiveEntryRenderer(props) {
|
|
41
41
|
const { entry } = props;
|
|
42
|
-
const { object, getValues, errors, instance, richTextEditor: RichTextEditor, parameters, handleChange, onAutosave, fieldHeight,
|
|
42
|
+
const { object, getValues, errors, instance, richTextEditor: RichTextEditor, parameters, handleChange, onAutosave, fieldHeight, associatedObject, width, } = useFormContext();
|
|
43
43
|
const { isBelow, breakpoints } = useWidgetSize({
|
|
44
44
|
scroll: false,
|
|
45
45
|
defaultWidth: width,
|
|
@@ -154,7 +154,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
else if (fieldDefinition.type === 'richText') {
|
|
157
|
-
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) }, RichTextEditor ? (React.createElement(RichTextEditor
|
|
157
|
+
return (React.createElement(FieldWrapper, { key: `${entryId}-${fieldValue}`, ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) }, RichTextEditor ? (React.createElement(RichTextEditor
|
|
158
158
|
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
159
159
|
, {
|
|
160
160
|
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
@@ -170,7 +170,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
170
170
|
}
|
|
171
171
|
else if (fieldDefinition.type === 'criteria') {
|
|
172
172
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
173
|
-
React.createElement(Criteria, { key:
|
|
173
|
+
React.createElement(Criteria, { key: `${entryId}-${fieldValue}`, fieldDefinition: fieldDefinition, value: fieldValue, canUpdateProperty: !(entry.type === 'readonlyField'), error: !!errors?.[entryId] })));
|
|
174
174
|
}
|
|
175
175
|
else {
|
|
176
176
|
// Add `aria-describedby` to ensure screen readers read the description
|
|
@@ -198,12 +198,11 @@ export function RecursiveEntryRenderer(props) {
|
|
|
198
198
|
* Key remounts the field if a value is changed.
|
|
199
199
|
* Needed to clear certain fields on discard changes.
|
|
200
200
|
*/
|
|
201
|
-
key:
|
|
201
|
+
key: fieldDefinition.type === 'choices' ||
|
|
202
202
|
fieldDefinition?.type === 'time' ||
|
|
203
|
-
fieldDefinition?.type === 'boolean'
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
: `${entryId}-reset-false`, ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors), errorMessage: undefined },
|
|
203
|
+
fieldDefinition?.type === 'boolean'
|
|
204
|
+
? `${entryId}-${fieldValue}`
|
|
205
|
+
: undefined, ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors), errorMessage: undefined },
|
|
207
206
|
React.createElement(FormField, { id: entryId,
|
|
208
207
|
// TODO: Ideally the FormField prop should be called parameter but can't change the name for backwards compatibility reasons
|
|
209
208
|
property: fieldDefinition, defaultValue: fieldValue, onChange: handleChange, onBlur: () => {
|
|
@@ -403,7 +403,7 @@ export const convertPropertiesToParams = (object) => {
|
|
|
403
403
|
]
|
|
404
404
|
: [...acc, property];
|
|
405
405
|
}, [])
|
|
406
|
-
.filter((
|
|
406
|
+
.filter((prop) => prop.type !== 'collection' && !prop.formula)
|
|
407
407
|
.flat()
|
|
408
408
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
409
409
|
.map(propertyToParameter);
|
|
@@ -630,8 +630,8 @@ export async function formatSubmission(submission, apiServices, objectId, instan
|
|
|
630
630
|
// if there are address fields with no value address needs to be set to undefined to be able to submit
|
|
631
631
|
}
|
|
632
632
|
else if (typeof value === 'object' && value !== null) {
|
|
633
|
-
if (Object.values(value).every((v) => v === undefined)) {
|
|
634
|
-
submission[key] =
|
|
633
|
+
if (Object.values(value).every((v) => v === undefined || v === '')) {
|
|
634
|
+
submission[key] = null;
|
|
635
635
|
// only submit the name and id of a regular related object
|
|
636
636
|
// and include objectId if it is a dynamic related object
|
|
637
637
|
}
|
|
@@ -150,12 +150,11 @@ export declare function useFormContext(): {
|
|
|
150
150
|
handleChange?: ((name: string, value: unknown) => void | Promise<void>) | undefined;
|
|
151
151
|
onAutosave?: ((fieldId: string) => void | Promise<void>) | undefined;
|
|
152
152
|
fieldHeight?: "medium" | "small" | undefined;
|
|
153
|
-
triggerFieldReset?: boolean | undefined;
|
|
154
153
|
showSubmitError?: boolean | undefined;
|
|
155
154
|
associatedObject?: {
|
|
156
155
|
instanceId: string;
|
|
157
|
-
propertyId: string;
|
|
158
|
-
} | undefined;
|
|
156
|
+
propertyId: string;
|
|
157
|
+
} | undefined;
|
|
159
158
|
form?: import("@evoke-platform/context").EvokeForm | undefined;
|
|
160
159
|
width: number;
|
|
161
160
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evoke-platform/ui-components",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/published/index.js",
|
|
6
6
|
"module": "dist/published/index.js",
|
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
"@react-querybuilder/material": "^6.5.0",
|
|
113
113
|
"@tanstack/react-query": "^5.90.20",
|
|
114
114
|
"clean-deep": "^3.4.0",
|
|
115
|
+
"dependency-graph": "^1.0.0",
|
|
115
116
|
"devexpress-richedit": "^23.1.5",
|
|
116
117
|
"devextreme": "^23.1.5",
|
|
117
118
|
"devextreme-dist": "^23.1.5",
|