@evoke-platform/ui-components 1.7.0-testing.3 → 1.7.0-testing.4
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/FormV2/FormRenderer.js +1 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +21 -0
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +489 -0
- package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/DefaultValues.js +208 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +7 -5
- package/dist/published/components/custom/FormV2/components/types.d.ts +11 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +18 -2
- package/dist/published/components/custom/FormV2/components/utils.js +138 -8
- package/dist/published/components/custom/FormV2/index.d.ts +1 -0
- package/dist/published/components/custom/FormV2/index.js +1 -0
- package/dist/published/components/custom/index.d.ts +1 -1
- package/dist/published/components/custom/index.js +1 -1
- package/dist/published/index.d.ts +1 -1
- package/dist/published/index.js +1 -1
- package/package.json +1 -1
|
@@ -153,7 +153,7 @@ function FormRenderer(props) {
|
|
|
153
153
|
paddingBottom: '0px',
|
|
154
154
|
paddingTop: !hasSections ? undefined : '0px',
|
|
155
155
|
} },
|
|
156
|
-
entries.map((entry, index) => (React.createElement(RecursiveEntryRenderer, { fieldHeight: fieldHeight, key: index, entry: entry, handleChange: onChange, errors: errors, showSubmitError: !!(isSubmitted && errors), instance: instance, richTextEditor: richTextEditor, expandedSections: expandedSections, setExpandedSections: setExpandedSections, expandAll: expandAll, setExpandAll: setExpandAll, triggerFieldReset: triggerFieldReset, parameters: parameters }))),
|
|
156
|
+
entries.map((entry, index) => (React.createElement(RecursiveEntryRenderer, { fieldHeight: fieldHeight, key: index, entry: entry, handleChange: onChange, errors: errors, showSubmitError: !!(isSubmitted && errors), instance: instance, richTextEditor: richTextEditor, expandedSections: expandedSections, setExpandedSections: setExpandedSections, expandAll: expandAll, setExpandAll: setExpandAll, triggerFieldReset: triggerFieldReset, parameters: parameters, isDocument: !!(form.id === 'documentForm') }))),
|
|
157
157
|
!hideButtons && (actionId || form.id === 'documentForm') && onSubmit && (React.createElement(Box, { sx: {
|
|
158
158
|
...(stickyFooter === false ? { position: 'static' } : { position: 'sticky' }),
|
|
159
159
|
bottom: isModal ? -5 : isSmallerThanMd ? 0 : 24,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { ComponentType } from 'react';
|
|
2
|
+
import { BaseProps, SimpleEditorProps } from './components/types';
|
|
3
|
+
export type FormProps = BaseProps & {
|
|
4
|
+
formId?: string;
|
|
5
|
+
instanceId?: string;
|
|
6
|
+
defaultPages?: Record<string, string>;
|
|
7
|
+
pageNavigation?: string;
|
|
8
|
+
documentId?: string;
|
|
9
|
+
dataType?: 'documents' | 'objectInstances';
|
|
10
|
+
display?: {
|
|
11
|
+
fieldHeight?: 'small' | 'medium';
|
|
12
|
+
};
|
|
13
|
+
actionId?: string;
|
|
14
|
+
stickyFooter?: boolean;
|
|
15
|
+
objectId: string;
|
|
16
|
+
richTextEditor?: ComponentType<SimpleEditorProps>;
|
|
17
|
+
onClose?: () => void;
|
|
18
|
+
onSubmit?: (submission: Record<string, unknown>) => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
declare function FormRendererContainer(props: FormProps): React.JSX.Element;
|
|
21
|
+
export default FormRendererContainer;
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { useApiServices, useApp, useAuthenticationContext, useNavigate, useObject, } from '@evoke-platform/context';
|
|
2
|
+
import { LocalDateTime } from '@js-joda/core';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { get, isArray, isEmpty, isEqual, merge, omit, pick, set, uniq } from 'lodash';
|
|
5
|
+
import React, { useEffect, useState } from 'react';
|
|
6
|
+
import { Skeleton, Snackbar } from '../../core';
|
|
7
|
+
import { Box } from '../../layout';
|
|
8
|
+
import ErrorComponent from '../ErrorComponent';
|
|
9
|
+
import { evalDefaultVals, processValueUpdate } from './components/DefaultValues';
|
|
10
|
+
import { convertDocToEntries, encodePageSlug, formatDataToDoc, getEntryId, getPrefixedUrl, getUnnestedEntries, isAddressProperty, isEmptyWithDefault, normalizeDateTime, } from './components/utils';
|
|
11
|
+
import FormRenderer from './FormRenderer';
|
|
12
|
+
function FormRendererContainer(props) {
|
|
13
|
+
const { instanceId, pageNavigation, documentId, dataType, display, formId, stickyFooter, objectId, actionId, richTextEditor, } = props;
|
|
14
|
+
const apiServices = useApiServices();
|
|
15
|
+
const navigateTo = useNavigate();
|
|
16
|
+
const { id: appId, defaultPages } = useApp();
|
|
17
|
+
const [hasDocumentUpdateAccess, setHasDocumentUpdateAccess] = useState();
|
|
18
|
+
const [defaultPagesWithSlugs, setDefaultPagesWithSlugs] = useState({});
|
|
19
|
+
const [sanitizedObject, setSanitizedObject] = useState();
|
|
20
|
+
const [navigationSlug, setNavigationSlug] = useState();
|
|
21
|
+
const [parameters, setParameters] = useState();
|
|
22
|
+
const [document, setDocument] = useState();
|
|
23
|
+
const [instance, setInstance] = useState();
|
|
24
|
+
const [formData, setFormData] = useState();
|
|
25
|
+
const [action, setAction] = useState();
|
|
26
|
+
const [error, setError] = useState();
|
|
27
|
+
const [form, setForm] = useState();
|
|
28
|
+
const [snackbarError, setSnackbarError] = useState({
|
|
29
|
+
showAlert: false,
|
|
30
|
+
isError: true,
|
|
31
|
+
});
|
|
32
|
+
const userAccount = useAuthenticationContext()?.account;
|
|
33
|
+
const objectStore = useObject(form?.objectId ?? objectId);
|
|
34
|
+
const onError = (err) => {
|
|
35
|
+
const code = axios.isAxiosError(err) ? err.response?.status : undefined;
|
|
36
|
+
setSnackbarError({ ...snackbarError, isError: true });
|
|
37
|
+
setError(code ?? true);
|
|
38
|
+
};
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
(async () => {
|
|
41
|
+
try {
|
|
42
|
+
if (dataType === 'documents') {
|
|
43
|
+
const object = await objectStore.get({ sanitized: true });
|
|
44
|
+
const document = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instanceId}/documents/${documentId}`));
|
|
45
|
+
if (document) {
|
|
46
|
+
setDocument(document);
|
|
47
|
+
const accessCheck = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instanceId}/documents/${documentId}/checkAccess?action=update`));
|
|
48
|
+
setHasDocumentUpdateAccess(accessCheck.result);
|
|
49
|
+
}
|
|
50
|
+
setSanitizedObject(omit(object, 'properties'));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
if (instanceId) {
|
|
54
|
+
objectStore.getInstance(instanceId).then((instance) => {
|
|
55
|
+
setInstance(instance);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const object = await apiServices.get(getPrefixedUrl(`/objects/${form?.objectId || objectId}${instanceId ? `/instances/${instanceId}/object` : '/effective'}`), { params: { sanitizedVersion: true } });
|
|
59
|
+
setSanitizedObject(object);
|
|
60
|
+
const action = object?.actions?.find((a) => a.id === (form?.actionId || actionId));
|
|
61
|
+
if (action && (instanceId || action.type === 'create')) {
|
|
62
|
+
setAction(action);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
setError(true);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
onError(error);
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
}, [dataType, form, instanceId, documentId]);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (pageNavigation) {
|
|
76
|
+
apiServices
|
|
77
|
+
.get(getPrefixedUrl(`/apps/${appId}/pages/${encodePageSlug(pageNavigation)}`))
|
|
78
|
+
.then((page) => {
|
|
79
|
+
setNavigationSlug(page?.slug);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (defaultPages) {
|
|
83
|
+
for (const [objectId, defaultPage] of Object.entries(defaultPages)) {
|
|
84
|
+
const pageId = defaultPage.includes('/')
|
|
85
|
+
? encodePageSlug(defaultPage.split('/').slice(2).join('/'))
|
|
86
|
+
: defaultPage;
|
|
87
|
+
apiServices.get(getPrefixedUrl(`/apps/${appId}/pages/${pageId}`)).then((page) => {
|
|
88
|
+
setDefaultPagesWithSlugs({
|
|
89
|
+
...defaultPagesWithSlugs,
|
|
90
|
+
[objectId]: '/' + page.appId + '/' + page.slug,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (dataType === 'documents' || form)
|
|
98
|
+
return;
|
|
99
|
+
if (formId || action?.defaultFormId) {
|
|
100
|
+
apiServices
|
|
101
|
+
.get(getPrefixedUrl(`data/forms/${formId || action?.defaultFormId}`))
|
|
102
|
+
.then((evokeForm) => {
|
|
103
|
+
if (evokeForm?.actionId === actionId) {
|
|
104
|
+
setForm(evokeForm);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
setError(true);
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
.catch((error) => {
|
|
111
|
+
onError(error);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
else if (action && !action?.defaultFormId) {
|
|
115
|
+
apiServices
|
|
116
|
+
.get(getPrefixedUrl(`data/forms?filter[where][actionId]=${action.id}`))
|
|
117
|
+
.then((matchingForms) => {
|
|
118
|
+
if (matchingForms.length === 1) {
|
|
119
|
+
if (matchingForms[0]?.actionId === actionId) {
|
|
120
|
+
setForm(matchingForms[0]);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
setError(true);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
.catch((error) => {
|
|
128
|
+
onError(error);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}, [action]);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (form?.id === 'documentForm') {
|
|
134
|
+
setParameters([
|
|
135
|
+
{
|
|
136
|
+
id: 'type',
|
|
137
|
+
name: 'Type',
|
|
138
|
+
type: 'string',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'view_permission',
|
|
142
|
+
name: 'View Permission',
|
|
143
|
+
type: 'string',
|
|
144
|
+
enum: ['Public', 'Private', 'Portal'],
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
else if (form) {
|
|
149
|
+
const unnestedEntries = getUnnestedEntries(form.entries);
|
|
150
|
+
const inputFieldParams = unnestedEntries
|
|
151
|
+
.filter((entry) => entry.type === 'inputField')
|
|
152
|
+
.map((entry) => entry.input);
|
|
153
|
+
setParameters(uniq([...(action?.parameters ?? []), ...inputFieldParams]));
|
|
154
|
+
}
|
|
155
|
+
}, [form, action]);
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
const getInitialValues = async () => {
|
|
158
|
+
if (document && objectId) {
|
|
159
|
+
const defaultValues = await getDefaultValues(convertDocToEntries(document), document);
|
|
160
|
+
setFormData(defaultValues);
|
|
161
|
+
if (!form) {
|
|
162
|
+
setForm({
|
|
163
|
+
id: 'documentForm',
|
|
164
|
+
name: 'Document Form',
|
|
165
|
+
entries: convertDocToEntries(document),
|
|
166
|
+
objectId: objectId,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (form && (instance || !instanceId)) {
|
|
171
|
+
const defaultValues = await getDefaultValues(form.entries, instance || {});
|
|
172
|
+
setFormData(defaultValues);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
getInitialValues();
|
|
176
|
+
}, [form, instance, sanitizedObject]);
|
|
177
|
+
const uploadDocuments = async (files, metadata) => {
|
|
178
|
+
const allDocuments = [];
|
|
179
|
+
const formData = new FormData();
|
|
180
|
+
for (const [index, file] of files.entries()) {
|
|
181
|
+
if ('size' in file) {
|
|
182
|
+
formData.append(`files[${index}]`, file);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
allDocuments.push(file);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (metadata) {
|
|
189
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
190
|
+
formData.append(key, value);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const docs = await apiServices?.post(getPrefixedUrl(`/objects/${form?.objectId}/instances/${instanceId}/documents`), formData);
|
|
194
|
+
return allDocuments.concat(docs?.map((doc) => ({
|
|
195
|
+
id: doc.id,
|
|
196
|
+
name: doc.name,
|
|
197
|
+
})) ?? []);
|
|
198
|
+
};
|
|
199
|
+
const deleteDocuments = async (submittedFields, requestSuccess, action) => {
|
|
200
|
+
const documentProperties = action?.parameters
|
|
201
|
+
? action.parameters.filter((param) => param.type === 'document')
|
|
202
|
+
: sanitizedObject?.properties?.filter((prop) => prop.type === 'document');
|
|
203
|
+
for (const docProperty of documentProperties ?? []) {
|
|
204
|
+
const savedValue = submittedFields[docProperty.id];
|
|
205
|
+
const originalValue = instance?.[docProperty.id];
|
|
206
|
+
const documentsToRemove = requestSuccess
|
|
207
|
+
? (originalValue?.filter((file) => !savedValue?.some((f) => f.id === file.id)) ?? [])
|
|
208
|
+
: (savedValue?.filter((file) => !originalValue?.some((f) => f.id === file.id)) ?? []);
|
|
209
|
+
for (const doc of documentsToRemove) {
|
|
210
|
+
try {
|
|
211
|
+
await apiServices?.delete(getPrefixedUrl(`/objects/${form?.objectId}/instances/${instanceId}/documents/${doc.id}`));
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (error) {
|
|
215
|
+
setSnackbarError({
|
|
216
|
+
showAlert: true,
|
|
217
|
+
message: `An error occurred while removing document '${doc.name}'`,
|
|
218
|
+
isError: true,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const onSubmissionSuccess = (updatedInstance) => {
|
|
226
|
+
setSnackbarError({
|
|
227
|
+
showAlert: true,
|
|
228
|
+
message: 'Your changes have been submitted',
|
|
229
|
+
isError: false,
|
|
230
|
+
});
|
|
231
|
+
if (navigationSlug) {
|
|
232
|
+
if (navigationSlug.includes(':instanceId') && navigationSlug.includes(':documentId')) {
|
|
233
|
+
navigateTo(`/${appId}/${navigationSlug
|
|
234
|
+
.replace(':instanceId', instanceId ?? ':instanceId')
|
|
235
|
+
.replace(':documentId', documentId ?? ':documentId')}`);
|
|
236
|
+
}
|
|
237
|
+
else if (navigationSlug.includes(':instanceId')) {
|
|
238
|
+
const navigateInstanceId = action?.type === 'create' ? updatedInstance?.id : instanceId;
|
|
239
|
+
navigateTo(`/${appId}/${navigationSlug.replace(':instanceId', navigateInstanceId ?? ':instanceId')}`);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
navigateTo(`/${appId}/${navigationSlug}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
setInstance(updatedInstance);
|
|
246
|
+
};
|
|
247
|
+
const saveHandler = async (submission) => {
|
|
248
|
+
if (!form)
|
|
249
|
+
return;
|
|
250
|
+
// checks for a file upload and handles getting the new format to upload
|
|
251
|
+
for (const [key, value] of Object.entries(submission)) {
|
|
252
|
+
if (isArray(value)) {
|
|
253
|
+
const fileInArray = value.some((item) => item instanceof File);
|
|
254
|
+
if (fileInArray) {
|
|
255
|
+
try {
|
|
256
|
+
const uploadedDocuments = await uploadDocuments(value, {
|
|
257
|
+
type: '',
|
|
258
|
+
view_permission: '',
|
|
259
|
+
});
|
|
260
|
+
submission[key] = uploadedDocuments;
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
if (err) {
|
|
264
|
+
setSnackbarError({
|
|
265
|
+
showAlert: true,
|
|
266
|
+
message: `An error occurred while uploading associated documents`,
|
|
267
|
+
isError: true,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// if there are address fields with no value address needs to be set to undefined to be able to submit
|
|
274
|
+
}
|
|
275
|
+
else if (typeof value === 'object' && value !== null) {
|
|
276
|
+
if (Object.values(value).every((v) => v === undefined)) {
|
|
277
|
+
submission[key] = undefined;
|
|
278
|
+
// only submit the name and id of a related object
|
|
279
|
+
}
|
|
280
|
+
else if ('id' in value && 'name' in value) {
|
|
281
|
+
submission[key] = pick(value, 'id', 'name');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else if ((value === '' && !document) || value === undefined) {
|
|
285
|
+
submission[key] = null;
|
|
286
|
+
}
|
|
287
|
+
else if (value instanceof LocalDateTime) {
|
|
288
|
+
submission[key] = normalizeDateTime(value);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (document) {
|
|
292
|
+
submission = formatDataToDoc(submission);
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
if (dataType === 'documents' && !!document) {
|
|
296
|
+
try {
|
|
297
|
+
await apiServices.patch(getPrefixedUrl(`/objects/${form.objectId}/instances/${instanceId}/documents/${documentId}`), pick(submission, ['metadata']).metadata ?? submission);
|
|
298
|
+
setDocument((prev) => ({
|
|
299
|
+
...prev,
|
|
300
|
+
metadata: submission.metadata,
|
|
301
|
+
}));
|
|
302
|
+
setSnackbarError({
|
|
303
|
+
showAlert: true,
|
|
304
|
+
message: 'Your changes have been submitted',
|
|
305
|
+
isError: false,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
setSnackbarError({
|
|
310
|
+
isError: true,
|
|
311
|
+
showAlert: true,
|
|
312
|
+
message: error.response?.data?.error?.message ?? 'An error occurred',
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (action?.type === 'create') {
|
|
317
|
+
const response = await apiServices.post(getPrefixedUrl(`/objects/${form.objectId}/instances/actions`), {
|
|
318
|
+
actionId: form.actionId,
|
|
319
|
+
input: pick(submission, sanitizedObject?.properties
|
|
320
|
+
?.filter((property) => !property.formula && property.type !== 'collection')
|
|
321
|
+
.map((property) => property.id) ?? []),
|
|
322
|
+
});
|
|
323
|
+
if (response) {
|
|
324
|
+
onSubmissionSuccess(response);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else if (instanceId && action) {
|
|
328
|
+
const response = await objectStore.instanceAction(instanceId, {
|
|
329
|
+
actionId: action.id,
|
|
330
|
+
input: pick(submission, sanitizedObject?.properties
|
|
331
|
+
?.filter((property) => !property.formula && property.type !== 'collection')
|
|
332
|
+
.map((property) => property.id) ?? []),
|
|
333
|
+
});
|
|
334
|
+
if (response) {
|
|
335
|
+
onSubmissionSuccess(response);
|
|
336
|
+
deleteDocuments(submission, !!response, action ?? undefined);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
setSnackbarError({
|
|
342
|
+
isError: true,
|
|
343
|
+
showAlert: true,
|
|
344
|
+
message: error.response?.data?.error?.message ?? 'An error occurred',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const getDefaultValues = async (entries, instanceData) => {
|
|
349
|
+
const result = {};
|
|
350
|
+
const processEntries = async (entries) => {
|
|
351
|
+
if (!entries)
|
|
352
|
+
return;
|
|
353
|
+
for (const entry of entries) {
|
|
354
|
+
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
355
|
+
const subEntries = entry.type === 'sections' ? entry.sections : entry.columns;
|
|
356
|
+
for (const subEntry of subEntries) {
|
|
357
|
+
if (subEntry.entries) {
|
|
358
|
+
const nested = await getDefaultValues(subEntry.entries, instanceData);
|
|
359
|
+
merge(result, nested);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if ((entry.type === 'input' || entry.type === 'inputField') &&
|
|
364
|
+
isAddressProperty(entry.parameterId || entry.input?.id)) {
|
|
365
|
+
const fieldId = getEntryId(entry);
|
|
366
|
+
if (!fieldId)
|
|
367
|
+
return;
|
|
368
|
+
const fieldValue = get(instanceData, fieldId);
|
|
369
|
+
if ((isEmpty(instanceData) ||
|
|
370
|
+
fieldValue === undefined ||
|
|
371
|
+
fieldValue === null ||
|
|
372
|
+
fieldValue === '') &&
|
|
373
|
+
entry?.display?.defaultValue &&
|
|
374
|
+
parameters) {
|
|
375
|
+
const defaultValuesArray = await evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
376
|
+
if (isArray(defaultValuesArray)) {
|
|
377
|
+
defaultValuesArray.forEach(({ fieldId, fieldValue }) => {
|
|
378
|
+
set(result, fieldId, fieldValue);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
383
|
+
set(result, fieldId, fieldValue);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else if (entry.type !== 'sections' && entry.type !== 'columns' && entry.type !== 'content') {
|
|
387
|
+
const fieldId = entry.type === 'input'
|
|
388
|
+
? entry.parameterId
|
|
389
|
+
: entry.type === 'inputField'
|
|
390
|
+
? entry.input?.id
|
|
391
|
+
: undefined;
|
|
392
|
+
if (fieldId) {
|
|
393
|
+
const fieldValue = instanceData?.[fieldId] ??
|
|
394
|
+
instanceData?.metadata?.[fieldId];
|
|
395
|
+
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
396
|
+
if (entry.type !== 'readonlyField' && isEmptyWithDefault(fieldValue, entry, instanceData)) {
|
|
397
|
+
if (fieldId && parameters && parameters.length > 0) {
|
|
398
|
+
const defaultValuesArray = await evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, instanceData);
|
|
399
|
+
for (const { fieldId, fieldValue } of defaultValuesArray) {
|
|
400
|
+
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
401
|
+
if (parameter?.type === 'object') {
|
|
402
|
+
const dependentFields = await processValueUpdate(form?.entries, parameters, fieldValue, apiServices, fieldId, formData, userAccount);
|
|
403
|
+
for (const field of dependentFields) {
|
|
404
|
+
set(result, field.fieldId, field.fieldValue);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
set(result, fieldId, fieldValue);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if (parameter?.type === 'boolean' && (fieldValue === undefined || fieldValue === null)) {
|
|
412
|
+
result[fieldId] = false;
|
|
413
|
+
}
|
|
414
|
+
else if (fieldValue !== undefined && fieldValue !== null) {
|
|
415
|
+
result[fieldId] = fieldValue;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
await processEntries(entries);
|
|
422
|
+
return result;
|
|
423
|
+
};
|
|
424
|
+
async function onChange(id, value) {
|
|
425
|
+
const parameter = parameters?.find((param) => param.id === id);
|
|
426
|
+
const entries = getUnnestedEntries(form.entries);
|
|
427
|
+
const isReadOnlyField = entries.some((e) => e.type === 'readonlyField' && e.propertyId === id) &&
|
|
428
|
+
!entries.some((e) => (e.type === 'input' && e.parameterId === id) || (e.type === 'inputField' && e.input.id === id));
|
|
429
|
+
if (isReadOnlyField)
|
|
430
|
+
return;
|
|
431
|
+
if (parameter) {
|
|
432
|
+
if (parameter.type === 'object' && parameters && parameters.length > 0) {
|
|
433
|
+
// On change of a related object, update default values dependent on that object
|
|
434
|
+
const dependentFields = await processValueUpdate(form?.entries, parameters, value, apiServices, id, formData, userAccount);
|
|
435
|
+
for (const field of dependentFields) {
|
|
436
|
+
onChange(field.fieldId, field.fieldValue);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
else if (parameter.type === 'string' && parameter.enum && value) {
|
|
440
|
+
// If a single select property has a sortBy option that isn't NONE the value gets spread and doesn't save properly,
|
|
441
|
+
// this will make it correctly save the value
|
|
442
|
+
value = value.value ? value.value : value;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (!isEqual(value, get(formData, id))) {
|
|
446
|
+
setFormData((prev) => {
|
|
447
|
+
const newData = { ...prev };
|
|
448
|
+
set(newData, id, value);
|
|
449
|
+
return newData;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function onCancel() {
|
|
454
|
+
(async () => {
|
|
455
|
+
if (document) {
|
|
456
|
+
const defaultValues = await getDefaultValues(convertDocToEntries(document), document);
|
|
457
|
+
setFormData(defaultValues);
|
|
458
|
+
}
|
|
459
|
+
else if (form) {
|
|
460
|
+
const defaultValues = await getDefaultValues(form.entries, instance || {});
|
|
461
|
+
setFormData(defaultValues);
|
|
462
|
+
}
|
|
463
|
+
})();
|
|
464
|
+
}
|
|
465
|
+
const isLoading = (instanceId && !formData && !document) || !form || !sanitizedObject;
|
|
466
|
+
return !error ? (React.createElement(Box, { sx: {
|
|
467
|
+
backgroundColor: '#ffffff',
|
|
468
|
+
borderRadius: '6px',
|
|
469
|
+
padding: '0px',
|
|
470
|
+
border: !isLoading ? '1px solid #dbe0e4' : undefined,
|
|
471
|
+
} },
|
|
472
|
+
!isLoading ? (React.createElement(React.Fragment, null,
|
|
473
|
+
React.createElement(FormRenderer, { onSubmit: saveHandler, hideButtons: document && !hasDocumentUpdateAccess, richTextEditor: richTextEditor, fieldHeight: display?.fieldHeight ?? 'medium', value: formData, stickyFooter: stickyFooter, form: form, instance: dataType !== 'documents' ? instance : document, onChange: onChange, onCancel: onCancel }))) : (React.createElement(Box, { sx: { padding: '20px' } },
|
|
474
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
475
|
+
React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
476
|
+
React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
477
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
478
|
+
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
479
|
+
React.createElement(Skeleton, { width: '33%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
480
|
+
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
481
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
482
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
483
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))),
|
|
484
|
+
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({
|
|
485
|
+
isError: snackbarError.isError,
|
|
486
|
+
showAlert: false,
|
|
487
|
+
}), message: snackbarError.message, error: snackbarError.isError }))) : (React.createElement(ErrorComponent, { colspan: props.colspan, code: error === 403 ? 'AccessDenied' : error === 404 ? 'NotFound' : 'Misconfigured' }));
|
|
488
|
+
}
|
|
489
|
+
export default FormRendererContainer;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ApiServices, FormEntry, InputField, InputParameter, InputParameterReference, ObjectInstance, Reference, UserAccount } from '@evoke-platform/context';
|
|
2
|
+
import { FieldValues } from 'react-hook-form';
|
|
3
|
+
export declare function evalDefaultVals(parameters: InputParameter[], entry: InputParameterReference | InputField, fieldValue: unknown, fieldId: string, apiServices: ApiServices, userAccount?: UserAccount, formValues?: FieldValues, updatedRelatedObjectValue?: ObjectInstance | null | Reference): Promise<{
|
|
4
|
+
fieldId: string;
|
|
5
|
+
fieldValue: unknown;
|
|
6
|
+
}[]>;
|
|
7
|
+
export declare function processValueUpdate(entries: FormEntry[] | undefined, parameters: InputParameter[], updatedRelatedObjectValue: ObjectInstance | null | Reference, apiServices: ApiServices, changedEntryId?: string, formValues?: FieldValues, userAccount?: UserAccount): Promise<{
|
|
8
|
+
fieldId: string;
|
|
9
|
+
fieldValue: unknown;
|
|
10
|
+
}[]>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { isArray, isEmpty, uniq } from 'lodash';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
3
|
+
import { getEntryId, getPrefixedUrl, isAddressProperty } from './utils';
|
|
4
|
+
export async function evalDefaultVals(parameters, entry, fieldValue, fieldId, apiServices, userAccount, formValues, updatedRelatedObjectValue) {
|
|
5
|
+
const updates = [];
|
|
6
|
+
const parameter = parameters.find((param) => param.id === fieldId);
|
|
7
|
+
const defaultValue = entry.display?.defaultValue;
|
|
8
|
+
// Handles dynamic default values
|
|
9
|
+
// Identifies a combination of static and dynamic values mixed in the same array,
|
|
10
|
+
// Example of mixed values: ["{{input.relatedObject.nestedProperty}}", "{{input.secondRelatedObject.nestedProperty}}", "option1"]
|
|
11
|
+
if (isArray(defaultValue) && defaultValue.some((item) => /^{{.*}}$/.test(item))) {
|
|
12
|
+
const staticValues = defaultValue.filter((item) => !/^{{.*}}$/.test(item));
|
|
13
|
+
for (const item of defaultValue.filter((item) => /^{{.*}}$/.test(item))) {
|
|
14
|
+
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
|
|
15
|
+
const groups = regex.exec(item)?.groups;
|
|
16
|
+
if (groups?.relatedObjectProperty && groups?.nestedProperty) {
|
|
17
|
+
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
18
|
+
let relatedObject = updatedRelatedObjectValue;
|
|
19
|
+
if (!relatedObject && !isEmpty(formValues)) {
|
|
20
|
+
relatedObject = formValues[groups.relatedObjectProperty];
|
|
21
|
+
}
|
|
22
|
+
if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
|
|
23
|
+
fieldValue = uniq([
|
|
24
|
+
...staticValues,
|
|
25
|
+
...(isArray(updatedRelatedObjectValue[groups.nestedProperty])
|
|
26
|
+
? updatedRelatedObjectValue[groups.nestedProperty]
|
|
27
|
+
: []),
|
|
28
|
+
]);
|
|
29
|
+
updates.push({ fieldId, fieldValue });
|
|
30
|
+
}
|
|
31
|
+
else if (relatedObject && relatedObject.id && relatedObjectParameter) {
|
|
32
|
+
const instance = await new Promise((resolve) => {
|
|
33
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectParameter.objectId}/instances/${relatedObject?.id}`), (error, instance) => {
|
|
34
|
+
if (error) {
|
|
35
|
+
console.error(error);
|
|
36
|
+
return resolve(undefined);
|
|
37
|
+
}
|
|
38
|
+
resolve(instance);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
if (instance) {
|
|
42
|
+
fieldValue = uniq([
|
|
43
|
+
...staticValues,
|
|
44
|
+
...(isArray(instance[groups.nestedProperty])
|
|
45
|
+
? instance[groups.nestedProperty]
|
|
46
|
+
: []),
|
|
47
|
+
]);
|
|
48
|
+
updates.push({ fieldId, fieldValue });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
updates.push({ fieldId, fieldValue: staticValues });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
staticValues.length > 0 && updates.push({ fieldId, fieldValue: staticValues });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Identifies dynamic values using Handlebars syntax: {{input.relatedObject.nestedProperty}} or {{input.relatedObject.address.nestedAddressProperty}}
|
|
60
|
+
}
|
|
61
|
+
else if (typeof defaultValue === 'string' && /^{{.*}}$/.test(defaultValue)) {
|
|
62
|
+
if (isAddressProperty(fieldId)) {
|
|
63
|
+
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)}}$/;
|
|
64
|
+
const groups = regex.exec(defaultValue)?.groups;
|
|
65
|
+
if (groups?.relatedObjectProperty && groups?.addressProperty && groups?.nestedAddressProperty) {
|
|
66
|
+
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
67
|
+
let relatedObject = updatedRelatedObjectValue;
|
|
68
|
+
if (!relatedObject && !isEmpty(formValues)) {
|
|
69
|
+
relatedObject = formValues[groups.relatedObjectProperty];
|
|
70
|
+
}
|
|
71
|
+
if (updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty]) {
|
|
72
|
+
fieldValue = updatedRelatedObjectValue?.[groups.addressProperty]?.[groups.nestedAddressProperty];
|
|
73
|
+
updates.push({ fieldId, fieldValue });
|
|
74
|
+
}
|
|
75
|
+
else if (relatedObject && relatedObject.id && relatedObjectParameter) {
|
|
76
|
+
const instance = await new Promise((resolve) => {
|
|
77
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectParameter.objectId}/instances/${relatedObject?.id}`), (error, instance) => {
|
|
78
|
+
if (error) {
|
|
79
|
+
console.error(error);
|
|
80
|
+
return resolve(undefined);
|
|
81
|
+
}
|
|
82
|
+
resolve(instance);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
// Clear dependent fields only if value is explicitly null (user cleared it).
|
|
86
|
+
// If updatedRelatedObjectValue is undefined (not triggered by onChange), use the value from the instance.
|
|
87
|
+
if (updatedRelatedObjectValue === null) {
|
|
88
|
+
updates.push({ fieldId, fieldValue: '' });
|
|
89
|
+
}
|
|
90
|
+
else if (instance) {
|
|
91
|
+
fieldValue = instance?.[groups.addressProperty]?.[groups.nestedAddressProperty];
|
|
92
|
+
updates.push({ fieldId, fieldValue });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
|
|
99
|
+
const groups = regex.exec(defaultValue)?.groups;
|
|
100
|
+
if (groups?.relatedObjectProperty && groups?.nestedProperty) {
|
|
101
|
+
const relatedObjectParameter = parameters.find((param) => param.id === groups?.relatedObjectProperty);
|
|
102
|
+
let relatedObject = updatedRelatedObjectValue;
|
|
103
|
+
if (!relatedObject && !isEmpty(formValues)) {
|
|
104
|
+
relatedObject = formValues[groups.relatedObjectProperty];
|
|
105
|
+
}
|
|
106
|
+
if (updatedRelatedObjectValue?.[groups.nestedProperty]) {
|
|
107
|
+
fieldValue = updatedRelatedObjectValue[groups.nestedProperty];
|
|
108
|
+
updates.push({ fieldId, fieldValue });
|
|
109
|
+
}
|
|
110
|
+
else if (relatedObject?.id && relatedObjectParameter) {
|
|
111
|
+
const instance = await new Promise((resolve) => {
|
|
112
|
+
apiServices.get(getPrefixedUrl(`/objects/${relatedObjectParameter.objectId}/instances/${relatedObject?.id}`), (error, instance) => {
|
|
113
|
+
if (error) {
|
|
114
|
+
console.error(error);
|
|
115
|
+
return resolve(undefined);
|
|
116
|
+
}
|
|
117
|
+
resolve(instance);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
// Clear dependent fields only if value is explicitly null (user cleared it).
|
|
121
|
+
// If updatedRelatedObjectValue is undefined (not triggered by onChange), use the value from the instance.
|
|
122
|
+
if (updatedRelatedObjectValue === null) {
|
|
123
|
+
updates.push({ fieldId, fieldValue: null });
|
|
124
|
+
}
|
|
125
|
+
else if (instance) {
|
|
126
|
+
fieldValue = instance?.[groups.nestedProperty] || null;
|
|
127
|
+
updates.push({ fieldId, fieldValue });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// all other default values set here
|
|
133
|
+
}
|
|
134
|
+
else if (parameter?.type !== 'object') {
|
|
135
|
+
let updatedValue = defaultValue;
|
|
136
|
+
// handles current default values ie: "Current logged in user", "Today" etc.
|
|
137
|
+
if (updatedValue === 'currentDate') {
|
|
138
|
+
updatedValue = DateTime.now().toISODate();
|
|
139
|
+
}
|
|
140
|
+
else if (updatedValue === 'currentTime') {
|
|
141
|
+
updatedValue = DateTime.now().toISOTime({
|
|
142
|
+
includeOffset: false,
|
|
143
|
+
suppressMilliseconds: true,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
else if (updatedValue === 'currentDateTime') {
|
|
147
|
+
updatedValue = DateTime.now().toISO();
|
|
148
|
+
}
|
|
149
|
+
else if (updatedValue === '$_CURRENT') {
|
|
150
|
+
updatedValue = { name: userAccount?.name, id: userAccount?.id };
|
|
151
|
+
}
|
|
152
|
+
updates.push({ fieldId, fieldValue: updatedValue });
|
|
153
|
+
}
|
|
154
|
+
return updates;
|
|
155
|
+
}
|
|
156
|
+
export async function processValueUpdate(entries, parameters, updatedRelatedObjectValue, apiServices, changedEntryId, formValues, userAccount) {
|
|
157
|
+
const updates = [];
|
|
158
|
+
for (const entry of entries || []) {
|
|
159
|
+
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
160
|
+
const subEntries = entry.type === 'sections' ? entry.sections : entry.columns;
|
|
161
|
+
for (const subEntry of subEntries) {
|
|
162
|
+
const subUpdates = await processValueUpdate(subEntry.entries, parameters, updatedRelatedObjectValue, apiServices, changedEntryId, formValues, userAccount);
|
|
163
|
+
updates.push(...subUpdates);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if ((entry.type === 'input' || entry.type === 'inputField') && entry?.display?.defaultValue) {
|
|
167
|
+
const parameterId = getEntryId(entry);
|
|
168
|
+
if (!parameterId)
|
|
169
|
+
return [];
|
|
170
|
+
if (isAddressProperty(parameterId)) {
|
|
171
|
+
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)}}$/;
|
|
172
|
+
const groups = regex.exec(entry.display.defaultValue)?.groups;
|
|
173
|
+
const [addressObject, addressField] = parameterId.split('.');
|
|
174
|
+
if (groups?.relatedObjectProperty &&
|
|
175
|
+
groups?.addressProperty &&
|
|
176
|
+
groups?.nestedAddressProperty &&
|
|
177
|
+
changedEntryId === groups.relatedObjectProperty) {
|
|
178
|
+
const result = await evalDefaultVals(parameters, entry, formValues?.[addressObject]?.[addressField], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
179
|
+
updates.push(...result);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (isArray(entry.display.defaultValue) &&
|
|
183
|
+
entry.display.defaultValue.some((item) => /^{{.*}}$/.test(item))) {
|
|
184
|
+
for (const item of entry.display.defaultValue.filter((item) => /^{{.*}}$/.test(item))) {
|
|
185
|
+
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
|
|
186
|
+
const groups = regex.exec(item)?.groups;
|
|
187
|
+
if (groups?.relatedObjectProperty &&
|
|
188
|
+
groups?.nestedProperty &&
|
|
189
|
+
changedEntryId === groups.relatedObjectProperty) {
|
|
190
|
+
const result = await evalDefaultVals(parameters, entry, entry.display.defaultValue, parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
191
|
+
updates.push(...result);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const regex = /^{{input\.(?<relatedObjectProperty>[a-zA-Z][a-zA-Z0-9_]*)\.(?<nestedProperty>[a-zA-Z][a-zA-Z0-9_]*)}}$/;
|
|
197
|
+
const groups = regex.exec(entry.display.defaultValue)?.groups;
|
|
198
|
+
if (groups?.relatedObjectProperty &&
|
|
199
|
+
groups?.nestedProperty &&
|
|
200
|
+
changedEntryId === groups.relatedObjectProperty) {
|
|
201
|
+
const result = await evalDefaultVals(parameters, entry, formValues?.[parameterId], parameterId, apiServices, userAccount, formValues, updatedRelatedObjectValue);
|
|
202
|
+
updates.push(...result);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return updates;
|
|
208
|
+
}
|
|
@@ -18,7 +18,7 @@ import { Image } from './FormFieldTypes/Image';
|
|
|
18
18
|
import ObjectPropertyInput from './FormFieldTypes/relatedObjectFiles/ObjectPropertyInput';
|
|
19
19
|
import UserProperty from './FormFieldTypes/UserProperty';
|
|
20
20
|
import FormSections from './FormSections';
|
|
21
|
-
import { entryIsVisible, fetchCollectionData, getEntryId, isAddressProperty, isOptionEqualToValue, updateCriteriaInputs, } from './utils';
|
|
21
|
+
import { docProperties, entryIsVisible, fetchCollectionData, getEntryId, isAddressProperty, isOptionEqualToValue, updateCriteriaInputs, } from './utils';
|
|
22
22
|
function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, fieldHeight, errors, validation) {
|
|
23
23
|
return {
|
|
24
24
|
inputId: entryId,
|
|
@@ -38,7 +38,7 @@ function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, displ
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
export function RecursiveEntryRenderer(props) {
|
|
41
|
-
const { handleChange, errors, showSubmitError, fieldHeight, instance, richTextEditor, expandedSections, setExpandedSections, expandAll, setExpandAll, parameters, entry, triggerFieldReset, } = props;
|
|
41
|
+
const { handleChange, errors, showSubmitError, fieldHeight, instance, richTextEditor, expandedSections, setExpandedSections, expandAll, setExpandAll, parameters, entry, triggerFieldReset, isDocument, } = props;
|
|
42
42
|
const { fetchedOptions, setFetchedOptions, object, getValues } = useFormContext();
|
|
43
43
|
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
44
44
|
if (!entryIsVisible(entry, getValues(), instance)) {
|
|
@@ -58,9 +58,11 @@ export function RecursiveEntryRenderer(props) {
|
|
|
58
58
|
def = parameters?.find((param) => param.id === entry.parameterId);
|
|
59
59
|
}
|
|
60
60
|
else if (entry.type === 'readonlyField') {
|
|
61
|
-
def =
|
|
62
|
-
?
|
|
63
|
-
:
|
|
61
|
+
def = isDocument
|
|
62
|
+
? docProperties.find((prop) => prop.id === entry.propertyId)
|
|
63
|
+
: isAddressProperty(entry.propertyId)
|
|
64
|
+
? object?.properties?.find((prop) => prop.id === entry.propertyId.split('.')[0])
|
|
65
|
+
: object?.properties?.find((prop) => prop.id === entry.propertyId);
|
|
64
66
|
}
|
|
65
67
|
else if (entry.type === 'inputField') {
|
|
66
68
|
def = entry.input;
|
|
@@ -104,6 +104,7 @@ export type EntryRendererProps = BaseProps & {
|
|
|
104
104
|
parameters?: InputParameter[];
|
|
105
105
|
readOnly?: boolean;
|
|
106
106
|
triggerFieldReset?: boolean;
|
|
107
|
+
isDocument?: boolean;
|
|
107
108
|
};
|
|
108
109
|
export type SectionsProps = {
|
|
109
110
|
entry: Sections;
|
|
@@ -120,3 +121,13 @@ export type SectionsProps = {
|
|
|
120
121
|
triggerFieldReset?: boolean;
|
|
121
122
|
showSubmitError?: boolean;
|
|
122
123
|
};
|
|
124
|
+
export type DocumentData = {
|
|
125
|
+
id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
contentType: string;
|
|
128
|
+
size: number;
|
|
129
|
+
uploadedDate: string;
|
|
130
|
+
versionId: string;
|
|
131
|
+
type: string;
|
|
132
|
+
view_permission: string;
|
|
133
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ApiServices, Column, Columns, FormEntry, InputParameter, Obj, ObjectInstance, Property, Section, Sections, UserAccount } from '@evoke-platform/context';
|
|
1
|
+
import { ApiServices, Column, Columns, FormEntry, InputField, InputParameter, InputParameterReference, Obj, ObjectInstance, Property, Section, Sections, UserAccount } from '@evoke-platform/context';
|
|
2
2
|
import { LocalDateTime } from '@js-joda/core';
|
|
3
3
|
import { FieldErrors, FieldValues } from 'react-hook-form';
|
|
4
4
|
import { AutocompleteOption } from '../../../core';
|
|
5
|
-
import { Document } from './types';
|
|
5
|
+
import { Document, DocumentData } from './types';
|
|
6
6
|
export declare const scrollIntoViewWithOffset: (el: HTMLElement, offset: number, container?: HTMLElement) => void;
|
|
7
7
|
export declare const normalizeDateTime: (dateTime: LocalDateTime) => string;
|
|
8
8
|
export declare function isAddressProperty(key: string): boolean;
|
|
@@ -45,3 +45,19 @@ export declare const convertDocToParameters: (obj: Document) => InputParameter[]
|
|
|
45
45
|
export declare const propertyToParameter: (property: Property) => InputParameter;
|
|
46
46
|
export declare const propertyValidationToParameterValidation: (property: Property) => InputParameter['validation'];
|
|
47
47
|
export declare const convertPropertiesToParams: (object: Obj) => InputParameter[] | undefined;
|
|
48
|
+
export declare function convertDocToEntries(document: Document): FormEntry[];
|
|
49
|
+
export declare function formatDataToDoc(data: DocumentData): {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
contentType: string;
|
|
53
|
+
size: number;
|
|
54
|
+
uploadedDate: string;
|
|
55
|
+
versionId: string;
|
|
56
|
+
metadata: {
|
|
57
|
+
type: string;
|
|
58
|
+
view_permission: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
export declare function getUnnestedEntries(entries: FormEntry[]): FormEntry[];
|
|
62
|
+
export declare const isEmptyWithDefault: (fieldValue: unknown, entry: InputParameterReference | InputField, instance: Record<string, unknown> | object) => boolean | "" | 0 | undefined;
|
|
63
|
+
export declare const docProperties: Property[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LocalDateTime } from '@js-joda/core';
|
|
2
2
|
import jsonLogic from 'json-logic-js';
|
|
3
|
-
import { get, isArray, isObject, omit, startCase, transform } from 'lodash';
|
|
3
|
+
import { get, isArray, isEmpty, isObject, omit, startCase, transform } from 'lodash';
|
|
4
4
|
import { DateTime } from 'luxon';
|
|
5
5
|
import Handlebars from 'no-eval-handlebars';
|
|
6
6
|
import { defaultRuleProcessorMongoDB, formatQuery, parseMongoDB } from 'react-querybuilder';
|
|
@@ -389,13 +389,7 @@ export const convertDocToParameters = (obj) => {
|
|
|
389
389
|
},
|
|
390
390
|
];
|
|
391
391
|
}
|
|
392
|
-
return [
|
|
393
|
-
{
|
|
394
|
-
id: key,
|
|
395
|
-
name: startCase(key),
|
|
396
|
-
type: 'string',
|
|
397
|
-
},
|
|
398
|
-
];
|
|
392
|
+
return [];
|
|
399
393
|
});
|
|
400
394
|
};
|
|
401
395
|
export const propertyToParameter = (property) => {
|
|
@@ -432,3 +426,139 @@ export const convertPropertiesToParams = (object) => {
|
|
|
432
426
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
433
427
|
.map(propertyToParameter);
|
|
434
428
|
};
|
|
429
|
+
export function convertDocToEntries(document) {
|
|
430
|
+
const entries = [
|
|
431
|
+
{
|
|
432
|
+
type: 'readonlyField',
|
|
433
|
+
propertyId: 'id',
|
|
434
|
+
display: {
|
|
435
|
+
label: 'Id',
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
type: 'readonlyField',
|
|
440
|
+
propertyId: 'name',
|
|
441
|
+
display: {
|
|
442
|
+
label: 'Name',
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
type: 'readonlyField',
|
|
447
|
+
propertyId: 'contentType',
|
|
448
|
+
display: {
|
|
449
|
+
label: 'Content Type',
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
type: 'readonlyField',
|
|
454
|
+
propertyId: 'size',
|
|
455
|
+
display: {
|
|
456
|
+
label: 'Size',
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
type: 'readonlyField',
|
|
461
|
+
propertyId: 'uploadedDate',
|
|
462
|
+
display: {
|
|
463
|
+
label: 'Uploaded Date',
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
];
|
|
467
|
+
if (!isEmpty(document.metadata)) {
|
|
468
|
+
entries.push({
|
|
469
|
+
type: 'input',
|
|
470
|
+
parameterId: 'type',
|
|
471
|
+
display: {
|
|
472
|
+
label: 'Type',
|
|
473
|
+
},
|
|
474
|
+
}, {
|
|
475
|
+
type: 'input',
|
|
476
|
+
parameterId: 'view_permission',
|
|
477
|
+
display: {
|
|
478
|
+
label: 'View Permission',
|
|
479
|
+
choicesDisplay: {
|
|
480
|
+
type: 'dropdown',
|
|
481
|
+
sortBy: 'ASC',
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
enumWithLabels: [
|
|
485
|
+
{ label: 'Public', value: 'Public' },
|
|
486
|
+
{ label: 'Private', value: 'Private' },
|
|
487
|
+
{ label: 'Portal', value: 'Portal' },
|
|
488
|
+
],
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
entries.push({
|
|
492
|
+
type: 'readonlyField',
|
|
493
|
+
propertyId: 'versionId',
|
|
494
|
+
display: {
|
|
495
|
+
label: 'Version Id',
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
return entries;
|
|
499
|
+
}
|
|
500
|
+
export function formatDataToDoc(data) {
|
|
501
|
+
return {
|
|
502
|
+
id: data.id,
|
|
503
|
+
name: data.name,
|
|
504
|
+
contentType: data.contentType,
|
|
505
|
+
size: data.size,
|
|
506
|
+
uploadedDate: data.uploadedDate,
|
|
507
|
+
versionId: data.versionId,
|
|
508
|
+
metadata: {
|
|
509
|
+
type: data.type,
|
|
510
|
+
view_permission: data.view_permission,
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
export function getUnnestedEntries(entries) {
|
|
515
|
+
return entries?.flatMap((entry) => {
|
|
516
|
+
if (entry.type === 'columns' && isArray(entry.columns)) {
|
|
517
|
+
return entry.columns.flatMap((column) => isArray(column.entries) ? getUnnestedEntries(column.entries) : []);
|
|
518
|
+
}
|
|
519
|
+
if (entry.type === 'sections' && isArray(entry.sections)) {
|
|
520
|
+
return entry.sections.flatMap((section) => isArray(section.entries) ? getUnnestedEntries(section.entries) : []);
|
|
521
|
+
}
|
|
522
|
+
return [entry];
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
export const isEmptyWithDefault = (fieldValue, entry, instance) => {
|
|
526
|
+
return ((isEmpty(instance) ||
|
|
527
|
+
fieldValue === undefined ||
|
|
528
|
+
fieldValue === null ||
|
|
529
|
+
(isArray(fieldValue) && fieldValue.length === 0)) &&
|
|
530
|
+
entry.display?.defaultValue &&
|
|
531
|
+
(!isObject(entry.display?.defaultValue) || !isEmpty(entry.display?.defaultValue)));
|
|
532
|
+
};
|
|
533
|
+
export const docProperties = [
|
|
534
|
+
{
|
|
535
|
+
id: 'id',
|
|
536
|
+
name: 'id',
|
|
537
|
+
type: 'string',
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
id: 'name',
|
|
541
|
+
name: 'Name',
|
|
542
|
+
type: 'string',
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
id: 'contentType',
|
|
546
|
+
name: 'contentType',
|
|
547
|
+
type: 'string',
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
id: 'size',
|
|
551
|
+
name: 'size',
|
|
552
|
+
type: 'string',
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
id: 'uploadedDate',
|
|
556
|
+
name: 'uploadedDate',
|
|
557
|
+
type: 'string',
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
id: 'versionId',
|
|
561
|
+
name: 'versionId',
|
|
562
|
+
type: 'string',
|
|
563
|
+
},
|
|
564
|
+
];
|
|
@@ -5,7 +5,7 @@ export { ErrorComponent } from './ErrorComponent';
|
|
|
5
5
|
export { Form } from './Form';
|
|
6
6
|
export type { FormRef } from './Form';
|
|
7
7
|
export { FormField } from './FormField';
|
|
8
|
-
export { FormRenderer } from './FormV2';
|
|
8
|
+
export { FormRenderer, FormRendererContainer } from './FormV2';
|
|
9
9
|
export { HistoryLog } from './HistoryLog';
|
|
10
10
|
export { MenuBar } from './Menubar';
|
|
11
11
|
export { MultiSelect } from './MultiSelect';
|
|
@@ -4,7 +4,7 @@ export { DataGrid } from './DataGrid';
|
|
|
4
4
|
export { ErrorComponent } from './ErrorComponent';
|
|
5
5
|
export { Form } from './Form';
|
|
6
6
|
export { FormField } from './FormField';
|
|
7
|
-
export { FormRenderer } from './FormV2';
|
|
7
|
+
export { FormRenderer, FormRendererContainer } from './FormV2';
|
|
8
8
|
export { HistoryLog } from './HistoryLog';
|
|
9
9
|
export { MenuBar } from './Menubar';
|
|
10
10
|
export { MultiSelect } from './MultiSelect';
|
|
@@ -2,7 +2,7 @@ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar, useMe
|
|
|
2
2
|
export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
|
|
3
3
|
export * from './colors';
|
|
4
4
|
export * from './components/core';
|
|
5
|
-
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, FormRenderer, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, } from './components/custom';
|
|
5
|
+
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, FormRenderer, FormRendererContainer, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, } from './components/custom';
|
|
6
6
|
export type { FormRef } from './components/custom';
|
|
7
7
|
export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
|
|
8
8
|
export { Box, Container, Grid, Stack } from './components/layout';
|
package/dist/published/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar, useMe
|
|
|
2
2
|
export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
|
|
3
3
|
export * from './colors';
|
|
4
4
|
export * from './components/core';
|
|
5
|
-
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, FormRenderer, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, } from './components/custom';
|
|
5
|
+
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, FormRenderer, FormRendererContainer, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, } from './components/custom';
|
|
6
6
|
export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
|
|
7
7
|
export { Box, Container, Grid, Stack } from './components/layout';
|
|
8
8
|
export * from './theme';
|