@evoke-platform/ui-components 1.6.0-testing.9 → 1.6.0
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 +1 -1
- package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
- package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +3 -3
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +7 -1
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +7 -1
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -3
- package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +13 -3
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +19 -0
- package/dist/published/components/custom/FormV2/FormRenderer.js +183 -0
- package/dist/published/components/custom/FormV2/components/AccordionSections.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/AccordionSections.js +131 -0
- package/dist/published/components/custom/FormV2/components/ActionButtons.d.ts +19 -0
- package/dist/published/components/custom/FormV2/components/ActionButtons.js +106 -0
- package/dist/published/components/custom/FormV2/components/FieldWrapper.d.ts +24 -0
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +100 -0
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormContext.js +8 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +17 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +50 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +14 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +88 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.d.ts +13 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.js +140 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.d.ts +17 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +233 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +40 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +95 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +526 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +93 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +73 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +13 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +179 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +108 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +129 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.d.ts +15 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +226 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +439 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +29 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +74 -0
- package/dist/published/components/custom/FormV2/components/FormSections.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/FormSections.js +104 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +209 -0
- package/dist/published/components/custom/FormV2/components/TabNav.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/TabNav.js +23 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.d.ts +3 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.js +176 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +45 -0
- package/dist/published/components/custom/FormV2/components/types.d.ts +122 -0
- package/dist/published/components/custom/FormV2/components/types.js +1 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +47 -0
- package/dist/published/components/custom/FormV2/components/utils.js +434 -0
- 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/ResponsiveOverflow/ResponsiveOverflow.js +5 -8
- package/dist/published/components/custom/index.d.ts +1 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +2 -2
- package/dist/published/index.js +2 -2
- package/dist/published/theme/hooks.d.ts +7 -0
- package/dist/published/theme/hooks.js +9 -0
- package/package.json +4 -2
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { useApiServices, useNotification, } from '@evoke-platform/context';
|
|
2
|
+
import { LocalDateTime } from '@js-joda/core';
|
|
3
|
+
import { get, isEqual, isObject, pick, startCase } from 'lodash';
|
|
4
|
+
import { DateTime } from 'luxon';
|
|
5
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
import sift from 'sift';
|
|
7
|
+
import { Edit, ExpandMoreOutlined, TrashCan } from '../../../../../../icons';
|
|
8
|
+
import { useResponsive } from '../../../../../../theme';
|
|
9
|
+
import { useFormContext } from '../../../../../../theme/hooks';
|
|
10
|
+
import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, Skeleton, Snackbar, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography, } from '../../../../../core';
|
|
11
|
+
import { Box } from '../../../../../layout';
|
|
12
|
+
import { getReadableQuery } from '../../../../CriteriaBuilder';
|
|
13
|
+
import { retrieveCustomErrorMessage } from '../../../../Form/utils';
|
|
14
|
+
import { getPrefixedUrl, normalizeDateTime, transformToWhere } from '../../utils';
|
|
15
|
+
import { ActionDialog } from './ActionDialog';
|
|
16
|
+
import { DocumentViewerCell } from './DocumentViewerCell';
|
|
17
|
+
const styles = {
|
|
18
|
+
addButton: {
|
|
19
|
+
backgroundColor: '#ebf4f8',
|
|
20
|
+
boxShadow: 'none',
|
|
21
|
+
color: '#0075a7',
|
|
22
|
+
marginTop: '15px',
|
|
23
|
+
'&:hover': {
|
|
24
|
+
backgroundColor: '#ebf4f8',
|
|
25
|
+
color: '#0075a7',
|
|
26
|
+
boxShadow: 'none',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
tableCell: {
|
|
30
|
+
color: '#637381',
|
|
31
|
+
backgroundColor: '#F4F6F8',
|
|
32
|
+
fontSize: '14px',
|
|
33
|
+
fontWeight: '700',
|
|
34
|
+
padding: '8px 20px',
|
|
35
|
+
whiteSpace: 'nowrap',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const RepeatableField = (props) => {
|
|
39
|
+
const { fieldDefinition, instance, canUpdateProperty, criteria, viewLayout } = props;
|
|
40
|
+
const { fetchedOptions, setFetchedOptions } = useFormContext();
|
|
41
|
+
const { instanceChanges } = useNotification();
|
|
42
|
+
const apiServices = useApiServices();
|
|
43
|
+
const { smallerThan } = useResponsive();
|
|
44
|
+
const smallerThanMd = smallerThan('md');
|
|
45
|
+
const [reloadOnErrorTrigger, setReloadOnErrorTrigger] = useState(true);
|
|
46
|
+
const [criteriaObjects, setCriteriaObjects] = useState([]);
|
|
47
|
+
const [selectedRow, setSelectedRow] = useState();
|
|
48
|
+
const [dialogType, setDialogType] = useState();
|
|
49
|
+
const [openDialog, setOpenDialog] = useState(false);
|
|
50
|
+
const [users, setUsers] = useState(fetchedOptions[`${fieldDefinition.id}Users`] || []);
|
|
51
|
+
const [error, setError] = useState(false);
|
|
52
|
+
const [relatedInstances, setRelatedInstances] = useState(fetchedOptions[`${fieldDefinition.id}Options`] || []);
|
|
53
|
+
const [relatedObject, setRelatedObject] = useState(fetchedOptions[`${fieldDefinition.id}RelatedObject`]);
|
|
54
|
+
const [hasCreateAction, setHasCreateAction] = useState(fetchedOptions[`${fieldDefinition.id}HasCreateAction`] || false);
|
|
55
|
+
const [loading, setLoading] = useState((relatedObject && relatedInstances) || !fieldDefinition ? false : true);
|
|
56
|
+
const [tableViewLayout, setTableViewLayout] = useState(fetchedOptions[`${fieldDefinition.id}TableViewLayout`]);
|
|
57
|
+
const [snackbarError, setSnackbarError] = useState({
|
|
58
|
+
showAlert: false,
|
|
59
|
+
isError: false,
|
|
60
|
+
});
|
|
61
|
+
const DEFAULT_CREATE_ACTION = '_create';
|
|
62
|
+
const fetchRelatedInstances = useCallback(async (refetch = false) => {
|
|
63
|
+
let relatedObject;
|
|
64
|
+
if (fieldDefinition.objectId) {
|
|
65
|
+
if (!fetchedOptions[`${fieldDefinition.id}RelatedObject`]) {
|
|
66
|
+
try {
|
|
67
|
+
relatedObject = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective`));
|
|
68
|
+
let defaultTableViewLayout;
|
|
69
|
+
if (relatedObject.viewLayout?.table) {
|
|
70
|
+
defaultTableViewLayout = {
|
|
71
|
+
id: 'default',
|
|
72
|
+
name: 'Default',
|
|
73
|
+
objectId: relatedObject.id,
|
|
74
|
+
...relatedObject?.viewLayout.table,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (viewLayout) {
|
|
78
|
+
apiServices
|
|
79
|
+
.get(getPrefixedUrl(`/objects/${viewLayout.objectId}/tableLayouts/${viewLayout.id}`))
|
|
80
|
+
.then(setTableViewLayout)
|
|
81
|
+
.catch((err) => setTableViewLayout(defaultTableViewLayout));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
setTableViewLayout(defaultTableViewLayout);
|
|
85
|
+
}
|
|
86
|
+
setRelatedObject(relatedObject);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error(err);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (fieldDefinition.relatedPropertyId &&
|
|
93
|
+
fieldDefinition.objectId &&
|
|
94
|
+
instance?.id &&
|
|
95
|
+
(!fetchedOptions[`${fieldDefinition.id}Options`] || refetch)) {
|
|
96
|
+
const filterProperty = `${fieldDefinition.relatedPropertyId}.id`;
|
|
97
|
+
const transformedCriteria = criteria ? transformToWhere(criteria) : {};
|
|
98
|
+
const filter = {
|
|
99
|
+
where: { [filterProperty]: instance?.id, ...transformedCriteria },
|
|
100
|
+
limit: 100,
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
const timeout = setTimeout(() => {
|
|
104
|
+
setLoading(false);
|
|
105
|
+
}, 300);
|
|
106
|
+
setLoading(true);
|
|
107
|
+
const instances = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances`), {
|
|
108
|
+
params: { filter: JSON.stringify(filter) },
|
|
109
|
+
});
|
|
110
|
+
clearTimeout(timeout);
|
|
111
|
+
if (instances) {
|
|
112
|
+
setRelatedInstances(instances);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
setError(true);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
setLoading(false);
|
|
120
|
+
}
|
|
121
|
+
relatedObject && checkCreateAccess(relatedObject);
|
|
122
|
+
}, [fieldDefinition]);
|
|
123
|
+
const fetchCriteriaObjects = useCallback(async () => {
|
|
124
|
+
let objectIds = [];
|
|
125
|
+
const criteriaProperties = relatedObject?.properties?.filter((property) => property.type === 'criteria' && property.objectId) ?? [];
|
|
126
|
+
if (tableViewLayout) {
|
|
127
|
+
objectIds = criteriaProperties
|
|
128
|
+
.filter((p) => tableViewLayout.properties.some((column) => column.id === p.id))
|
|
129
|
+
.map((property) => property.objectId);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
objectIds = criteriaProperties.map((p) => p.objectId);
|
|
133
|
+
}
|
|
134
|
+
const objects = [];
|
|
135
|
+
for (const objectId of new Set(objectIds)) {
|
|
136
|
+
try {
|
|
137
|
+
const criteriaObject = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/effective`), {
|
|
138
|
+
params: { fields: ['id', 'name', 'properties'] },
|
|
139
|
+
});
|
|
140
|
+
objects.push(criteriaObject);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(`Error fetching criteria object with ID ${objectId}:`, error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
setCriteriaObjects(objects);
|
|
147
|
+
}, [apiServices, relatedObject, tableViewLayout]);
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!fetchedOptions[`${fieldDefinition.id}Users`]) {
|
|
150
|
+
(async () => {
|
|
151
|
+
try {
|
|
152
|
+
const users = await apiServices.get(getPrefixedUrl(`/users`));
|
|
153
|
+
setFetchedOptions({
|
|
154
|
+
[`${fieldDefinition.id}Users`]: users,
|
|
155
|
+
});
|
|
156
|
+
setUsers(users);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.error(error);
|
|
160
|
+
}
|
|
161
|
+
})();
|
|
162
|
+
}
|
|
163
|
+
}, [apiServices]);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
fetchRelatedInstances();
|
|
166
|
+
}, [fetchRelatedInstances, reloadOnErrorTrigger, instance]);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (relatedObject)
|
|
169
|
+
fetchCriteriaObjects();
|
|
170
|
+
}, [fetchCriteriaObjects, relatedObject]);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (relatedObject?.rootObjectId) {
|
|
173
|
+
// pass true here so while it doesn't refetch on every tab change it does refetch on changes made
|
|
174
|
+
const callback = () => fetchRelatedInstances(true);
|
|
175
|
+
instanceChanges?.subscribe(relatedObject?.rootObjectId, callback);
|
|
176
|
+
return () => instanceChanges?.unsubscribe(relatedObject?.rootObjectId, callback);
|
|
177
|
+
}
|
|
178
|
+
}, [instanceChanges, relatedObject]);
|
|
179
|
+
const retrieveCriteria = (relatedObjProperty, action, object) => {
|
|
180
|
+
let property;
|
|
181
|
+
if (action.parameters) {
|
|
182
|
+
property = action.parameters.find((param) => param.id === relatedObjProperty);
|
|
183
|
+
return {
|
|
184
|
+
relatedObjectProperty: property,
|
|
185
|
+
criteria: property?.validation?.criteria,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
else if (action.inputProperties) {
|
|
189
|
+
const flattenInputProperties = (entries) => {
|
|
190
|
+
return entries.reduce((acc, entry) => {
|
|
191
|
+
if (entry.components) {
|
|
192
|
+
const components = entry.components.flatMap((s) => s.components ?? []);
|
|
193
|
+
return acc.concat(flattenInputProperties(components ?? []));
|
|
194
|
+
}
|
|
195
|
+
else if (entry.columns) {
|
|
196
|
+
const components = entry.columns.flatMap((c) => c.components ?? []);
|
|
197
|
+
return acc.concat(flattenInputProperties(components ?? []));
|
|
198
|
+
}
|
|
199
|
+
else if (entry.html) {
|
|
200
|
+
return acc;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
return acc.concat([entry]);
|
|
204
|
+
}
|
|
205
|
+
}, []);
|
|
206
|
+
};
|
|
207
|
+
property = flattenInputProperties(action.inputProperties).find((param) => param.key === relatedObjProperty);
|
|
208
|
+
return {
|
|
209
|
+
relatedObjectProperty: property,
|
|
210
|
+
criteria: (property?.validate).criteria,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
property = object.properties?.find((prop) => prop.id === relatedObjProperty);
|
|
215
|
+
return {
|
|
216
|
+
relatedObjectProperty: property,
|
|
217
|
+
criteria: property?.validation?.criteria,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const checkCreateAccess = (relatedObject) => {
|
|
222
|
+
if (fieldDefinition.objectId && canUpdateProperty && !fetchedOptions[`${fieldDefinition.id}HasCreateAction`]) {
|
|
223
|
+
apiServices
|
|
224
|
+
.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/checkAccess`), {
|
|
225
|
+
params: { action: 'execute', field: '_create', scope: 'data' },
|
|
226
|
+
})
|
|
227
|
+
.then((checkAccess) => {
|
|
228
|
+
const action = relatedObject.actions?.find((item) => item.id === '_create');
|
|
229
|
+
if (action &&
|
|
230
|
+
fieldDefinition.relatedPropertyId &&
|
|
231
|
+
// TODO: replace with the entries create form or defaultFormId of the
|
|
232
|
+
// default create action, keeping it like this to get minimum changes out so other can use it
|
|
233
|
+
!!fieldDefinition.createForm) {
|
|
234
|
+
const { relatedObjectProperty, criteria } = retrieveCriteria(fieldDefinition.relatedPropertyId, action, relatedObject);
|
|
235
|
+
if (!criteria || JSON.stringify(criteria).includes('{{{input.') || !relatedObjectProperty) {
|
|
236
|
+
setHasCreateAction(checkAccess.result);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const validate = sift(criteria);
|
|
240
|
+
setHasCreateAction(validate(instance) && checkAccess.result);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
setHasCreateAction(false);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
const updatedOptions = {};
|
|
251
|
+
if ((relatedInstances && !fetchedOptions[`${fieldDefinition.id}Options`]) ||
|
|
252
|
+
fetchedOptions[`${fieldDefinition.id}Options`].length === 0 ||
|
|
253
|
+
!isEqual(relatedInstances, fetchedOptions[`${fieldDefinition.id}Options`])) {
|
|
254
|
+
updatedOptions[`${fieldDefinition.id}Options`] = relatedInstances;
|
|
255
|
+
}
|
|
256
|
+
if (relatedObject && !fetchedOptions[`${fieldDefinition.id}RelatedObject`]) {
|
|
257
|
+
updatedOptions[`${fieldDefinition.id}RelatedObject`] = relatedObject;
|
|
258
|
+
}
|
|
259
|
+
if (tableViewLayout && !fetchedOptions[`${fieldDefinition.id}TableViewLayout`]) {
|
|
260
|
+
updatedOptions[`${fieldDefinition.id}TableViewLayout`] = tableViewLayout;
|
|
261
|
+
}
|
|
262
|
+
if ((hasCreateAction || hasCreateAction === false) && !fetchedOptions[`${fieldDefinition.id}HasCreateAction`]) {
|
|
263
|
+
updatedOptions[`${fieldDefinition.id}HasCreateAction`] = hasCreateAction;
|
|
264
|
+
}
|
|
265
|
+
else if (!hasCreateAction && relatedObject) {
|
|
266
|
+
checkCreateAccess(relatedObject);
|
|
267
|
+
}
|
|
268
|
+
if (Object.keys(updatedOptions).length > 0) {
|
|
269
|
+
setFetchedOptions(updatedOptions);
|
|
270
|
+
}
|
|
271
|
+
}, [relatedObject, relatedInstances, hasCreateAction, tableViewLayout]);
|
|
272
|
+
const deleteRow = (id) => {
|
|
273
|
+
setDialogType('delete');
|
|
274
|
+
setSelectedRow(id);
|
|
275
|
+
setOpenDialog(true);
|
|
276
|
+
};
|
|
277
|
+
const addRow = () => {
|
|
278
|
+
setDialogType('create');
|
|
279
|
+
setSelectedRow(undefined);
|
|
280
|
+
setOpenDialog(true);
|
|
281
|
+
};
|
|
282
|
+
const editRow = (id) => {
|
|
283
|
+
setDialogType('update');
|
|
284
|
+
setSelectedRow(id);
|
|
285
|
+
setOpenDialog(true);
|
|
286
|
+
};
|
|
287
|
+
const ErrorComponent = () => loading ? (React.createElement("div", null,
|
|
288
|
+
React.createElement(Typography, { sx: {
|
|
289
|
+
fontSize: '14px',
|
|
290
|
+
color: '#727c84',
|
|
291
|
+
} }, "Loading..."))) : (React.createElement(Typography, { sx: {
|
|
292
|
+
color: 'rgb(114 124 132)',
|
|
293
|
+
fontSize: '14px',
|
|
294
|
+
} },
|
|
295
|
+
"An error occurred when retrieving this data.",
|
|
296
|
+
' ',
|
|
297
|
+
React.createElement(Button, { sx: {
|
|
298
|
+
padding: 0,
|
|
299
|
+
'&:hover': {
|
|
300
|
+
backgroundColor: 'transparent',
|
|
301
|
+
},
|
|
302
|
+
'min-width': '44px',
|
|
303
|
+
}, variant: "text", onClick: () => setReloadOnErrorTrigger((prevState) => !prevState) }, "Retry")));
|
|
304
|
+
const save = async (actionType, input, instanceId) => {
|
|
305
|
+
// date-time fields are stored in the database in ISO format so convert all
|
|
306
|
+
// LocalDateTime objects to ISO format.
|
|
307
|
+
if (isObject(input)) {
|
|
308
|
+
input = Object.entries(input).reduce((agg, [key, value]) => Object.assign(agg, {
|
|
309
|
+
[key]: value instanceof LocalDateTime ? normalizeDateTime(value) : value,
|
|
310
|
+
}), {});
|
|
311
|
+
}
|
|
312
|
+
if (actionType === 'create') {
|
|
313
|
+
const updatedInput = {
|
|
314
|
+
...input,
|
|
315
|
+
[fieldDefinition?.relatedPropertyId]: { id: instance?.id },
|
|
316
|
+
};
|
|
317
|
+
try {
|
|
318
|
+
const instance = await apiServices.post(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances/actions`), {
|
|
319
|
+
actionId: DEFAULT_CREATE_ACTION,
|
|
320
|
+
input: updatedInput,
|
|
321
|
+
});
|
|
322
|
+
const hasAccess = fieldDefinition?.relatedPropertyId && fieldDefinition.relatedPropertyId in instance;
|
|
323
|
+
hasAccess && setRelatedInstances([...relatedInstances, instance]);
|
|
324
|
+
setOpenDialog(false);
|
|
325
|
+
setDialogType(undefined);
|
|
326
|
+
setSelectedRow(undefined);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
setSnackbarError({
|
|
330
|
+
showAlert: true,
|
|
331
|
+
message: retrieveCustomErrorMessage(err) ??
|
|
332
|
+
`An error occurred while creating an instance`,
|
|
333
|
+
isError: true,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
const relatedObjectId = relatedObject?.id;
|
|
339
|
+
try {
|
|
340
|
+
await apiServices.post(getPrefixedUrl(`/objects/${relatedObjectId}/instances/${instanceId}/actions`), {
|
|
341
|
+
actionId: `_${actionType}`,
|
|
342
|
+
input: pick(input, relatedObject?.properties
|
|
343
|
+
?.filter((property) => !property.formula && property.type !== 'collection')
|
|
344
|
+
.map((property) => property.id) ?? []),
|
|
345
|
+
});
|
|
346
|
+
if (actionType === 'delete') {
|
|
347
|
+
setRelatedInstances((prevInstances) => prevInstances.filter((instance) => instance.id !== instanceId));
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
setRelatedInstances((prevInstances) => prevInstances.map((i) => (i.id === instance?.id ? instance : i)));
|
|
351
|
+
}
|
|
352
|
+
setOpenDialog(false);
|
|
353
|
+
setDialogType(undefined);
|
|
354
|
+
setSelectedRow(undefined);
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
setSnackbarError({
|
|
358
|
+
showAlert: true,
|
|
359
|
+
message: retrieveCustomErrorMessage(err) ??
|
|
360
|
+
`An error occurred while ${actionType === 'delete' ? ' deleting' : ' updating'} an instance`,
|
|
361
|
+
isError: true,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const retrieveViewLayout = () => {
|
|
367
|
+
let properties = [];
|
|
368
|
+
if (tableViewLayout?.properties?.length) {
|
|
369
|
+
for (const prop of tableViewLayout.properties) {
|
|
370
|
+
const propertyId = prop.id.split('.')[0];
|
|
371
|
+
const property = relatedObject?.properties?.find((p) => p.id === propertyId);
|
|
372
|
+
if (property) {
|
|
373
|
+
if ((property.type === 'object' && property.id !== property.relatedPropertyId) ||
|
|
374
|
+
property.type === 'address' ||
|
|
375
|
+
property.type === 'user') {
|
|
376
|
+
properties.push({
|
|
377
|
+
...property,
|
|
378
|
+
id: ['user', 'object'].includes(property.type) && !prop.id.endsWith('.name')
|
|
379
|
+
? `${prop.id}.name`
|
|
380
|
+
: prop.id,
|
|
381
|
+
name: property.type === 'address'
|
|
382
|
+
? `${property.name} - ${startCase(prop.id.split('.')[1])}`
|
|
383
|
+
: property.name,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
properties.push(property);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
properties =
|
|
394
|
+
relatedObject?.properties
|
|
395
|
+
?.filter((prop) => !['address', 'image', 'collection'].includes(prop.type))
|
|
396
|
+
.map((prop) => ({
|
|
397
|
+
...prop,
|
|
398
|
+
id: prop.type === 'object' || prop.type === 'user' ? `${prop.id}.name` : prop.id,
|
|
399
|
+
})) ?? [];
|
|
400
|
+
}
|
|
401
|
+
return properties;
|
|
402
|
+
};
|
|
403
|
+
const columns = retrieveViewLayout();
|
|
404
|
+
const getValue = (relatedInstance, propertyId, propertyType) => {
|
|
405
|
+
const value = get(relatedInstance, propertyId);
|
|
406
|
+
// If the date-like value is empty then there is no need to format.
|
|
407
|
+
if (!value) {
|
|
408
|
+
return value;
|
|
409
|
+
}
|
|
410
|
+
if (propertyType === 'date') {
|
|
411
|
+
return DateTime.fromISO(value).toLocaleString(DateTime.DATE_SHORT);
|
|
412
|
+
}
|
|
413
|
+
if (propertyType === 'date-time') {
|
|
414
|
+
return DateTime.fromISO(value).toLocaleString(DateTime.DATETIME_SHORT);
|
|
415
|
+
}
|
|
416
|
+
if (propertyType === 'time') {
|
|
417
|
+
return DateTime.fromISO(value).toLocaleString(DateTime.TIME_SIMPLE);
|
|
418
|
+
}
|
|
419
|
+
if (propertyType === 'criteria' && typeof value === 'object') {
|
|
420
|
+
const property = relatedObject?.properties?.find((p) => p.id === propertyId);
|
|
421
|
+
return getReadableQuery(value, criteriaObjects.find((o) => o.id === property?.objectId)?.properties ?? []);
|
|
422
|
+
}
|
|
423
|
+
return value;
|
|
424
|
+
};
|
|
425
|
+
return loading ? (React.createElement(React.Fragment, null,
|
|
426
|
+
React.createElement(Skeleton, null),
|
|
427
|
+
React.createElement(Skeleton, null),
|
|
428
|
+
React.createElement(Skeleton, null))) : (React.createElement(React.Fragment, null,
|
|
429
|
+
React.createElement(Box, { sx: { padding: '10px 0' } },
|
|
430
|
+
!relatedInstances?.length ? (!error ? (React.createElement(Typography, { sx: { margin: '-10px 0', color: 'rgb(114 124 132)', fontSize: '14px' } }, "No items added")) : (React.createElement(ErrorComponent, null))) : smallerThanMd ? (React.createElement(React.Fragment, null, relatedInstances?.map((relatedInstance, index) => (React.createElement(Accordion, { key: relatedInstance.id, sx: {
|
|
431
|
+
border: '1px solid #dbe0e4',
|
|
432
|
+
borderTop: index === 0 ? undefined : 'none',
|
|
433
|
+
boxShadow: 'none',
|
|
434
|
+
'&:before': {
|
|
435
|
+
display: 'none',
|
|
436
|
+
},
|
|
437
|
+
marginBottom: 0,
|
|
438
|
+
'&.Mui-expanded': {
|
|
439
|
+
margin: '0px',
|
|
440
|
+
},
|
|
441
|
+
} },
|
|
442
|
+
React.createElement(AccordionSummary, { sx: {
|
|
443
|
+
'&.Mui-expanded': {
|
|
444
|
+
borderBottom: '1px solid #dbe0e4',
|
|
445
|
+
minHeight: '44px',
|
|
446
|
+
borderBottomLeftRadius: '0px',
|
|
447
|
+
borderBottomRightRadius: '0px',
|
|
448
|
+
margin: '0px',
|
|
449
|
+
},
|
|
450
|
+
minHeight: '48px',
|
|
451
|
+
maxHeight: '48px',
|
|
452
|
+
backgroundColor: '#f9fafb',
|
|
453
|
+
// MUI accordion summaries have different border radius for the first and last item
|
|
454
|
+
borderTopLeftRadius: index === 0 ? '3px' : undefined,
|
|
455
|
+
borderTopRightRadius: index === 0 ? '3px' : undefined,
|
|
456
|
+
borderBottomRightRadius: index === relatedInstances.length - 1 ? '3px' : undefined,
|
|
457
|
+
borderBottomLeftRadius: index === relatedInstances.length - 1 ? '3px' : undefined,
|
|
458
|
+
}, expandIcon: React.createElement(ExpandMoreOutlined, { fontSize: "medium" }) },
|
|
459
|
+
React.createElement(Box, { sx: {
|
|
460
|
+
display: 'flex',
|
|
461
|
+
alignItems: 'center',
|
|
462
|
+
width: '100%',
|
|
463
|
+
justifyContent: 'space-between',
|
|
464
|
+
} }, React.createElement(Box, { sx: { display: 'flex', alignItems: 'center', marginY: '10px' } },
|
|
465
|
+
React.createElement(Typography, { sx: { fontSize: '16px', fontWeight: '600', lineHight: '24px' } }, getValue(relatedInstance, 'name', 'string'))))),
|
|
466
|
+
React.createElement(AccordionDetails, null,
|
|
467
|
+
React.createElement(Box, null, columns
|
|
468
|
+
?.filter((prop) => prop.id !== 'name')
|
|
469
|
+
?.map((prop) => (React.createElement(Box, { key: prop.id, sx: { mb: 1, display: 'flex', alignItems: 'center' } },
|
|
470
|
+
React.createElement(Typography, { component: "span", sx: { color: '#637381', marginRight: '3px' } },
|
|
471
|
+
prop.name,
|
|
472
|
+
":"),
|
|
473
|
+
prop.type === 'document' ? (React.createElement(DocumentViewerCell, { instance: relatedInstance, propertyId: prop.id, setSnackbarError: setSnackbarError })) : (React.createElement(Typography, null,
|
|
474
|
+
getValue(relatedInstance, prop.id, prop.type),
|
|
475
|
+
prop.type === 'user' &&
|
|
476
|
+
users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' &&
|
|
477
|
+
' (Inactive)')))))),
|
|
478
|
+
canUpdateProperty && (React.createElement(Box, { sx: { mt: 2, display: 'flex', gap: 1 } },
|
|
479
|
+
React.createElement(IconButton, { onClick: () => editRow(relatedInstance.id) },
|
|
480
|
+
React.createElement(Tooltip, { title: "Edit" },
|
|
481
|
+
React.createElement(Edit, null))),
|
|
482
|
+
React.createElement(IconButton, { onClick: () => deleteRow(relatedInstance.id) },
|
|
483
|
+
React.createElement(Tooltip, { title: "Delete" },
|
|
484
|
+
React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } }))))))))))) : (React.createElement(TableContainer, { sx: {
|
|
485
|
+
borderRadius: '6px',
|
|
486
|
+
border: '1px solid #919EAB3D',
|
|
487
|
+
boxShadow: 'none',
|
|
488
|
+
maxHeight: '70vh',
|
|
489
|
+
} },
|
|
490
|
+
React.createElement(Table, { stickyHeader: true, sx: { minWidth: 650 } },
|
|
491
|
+
React.createElement(TableHead, { sx: { backgroundColor: '#F4F6F8' } },
|
|
492
|
+
React.createElement(TableRow, null,
|
|
493
|
+
columns?.map((prop) => React.createElement(TableCell, { sx: styles.tableCell }, prop.name)),
|
|
494
|
+
canUpdateProperty && React.createElement(TableCell, { sx: { ...styles.tableCell, width: '80px' } }))),
|
|
495
|
+
React.createElement(TableBody, null, relatedInstances?.map((relatedInstance, index) => (React.createElement(TableRow, { key: relatedInstance.id },
|
|
496
|
+
columns?.map((prop) => {
|
|
497
|
+
return (React.createElement(TableCell, { sx: { fontSize: '16px' } }, prop.type === 'document' ? (React.createElement(DocumentViewerCell, { instance: relatedInstance, propertyId: prop.id, setSnackbarError: setSnackbarError })) : (React.createElement(Typography, { key: prop.id, sx: prop.id === 'name'
|
|
498
|
+
? {
|
|
499
|
+
'&:hover': {
|
|
500
|
+
textDecoration: 'underline',
|
|
501
|
+
cursor: 'pointer',
|
|
502
|
+
},
|
|
503
|
+
}
|
|
504
|
+
: {}, onClick: !!fieldDefinition.updatedForm && // TODO: replace with the entries update form
|
|
505
|
+
canUpdateProperty &&
|
|
506
|
+
prop.id === 'name'
|
|
507
|
+
? () => editRow(relatedInstance.id)
|
|
508
|
+
: undefined },
|
|
509
|
+
getValue(relatedInstance, prop.id, prop.type),
|
|
510
|
+
prop.type === 'user' &&
|
|
511
|
+
users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' && (React.createElement("span", null, ' (Inactive)'))))));
|
|
512
|
+
}),
|
|
513
|
+
canUpdateProperty && (React.createElement(TableCell, { sx: { width: '80px' } },
|
|
514
|
+
!!fieldDefinition.updateForm && ( // TODO: replace with the entries update form
|
|
515
|
+
React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
|
|
516
|
+
React.createElement(Tooltip, { title: "Edit" },
|
|
517
|
+
React.createElement(Edit, null)))),
|
|
518
|
+
React.createElement(IconButton, { "aria-label": `delete-collection-instance-${index}`, onClick: () => deleteRow(relatedInstance.id) },
|
|
519
|
+
React.createElement(Tooltip, { title: "Delete" },
|
|
520
|
+
React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } })))))))))))),
|
|
521
|
+
hasCreateAction && (React.createElement(Button, { variant: "contained", sx: styles.addButton, onClick: addRow, "aria-label": 'Add' }, "Add"))),
|
|
522
|
+
relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, onClose: () => setOpenDialog(false), instanceInput: relatedInstances.find((i) => i.id === selectedRow) ?? {}, handleSubmit: save, action: relatedObject?.actions?.find((a) => a.id ===
|
|
523
|
+
(dialogType === 'create' ? '_create' : dialogType === 'update' ? '_update' : '_delete')), instanceId: selectedRow, relatedParameter: fieldDefinition })),
|
|
524
|
+
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({ isError: snackbarError.isError, showAlert: false }), message: snackbarError.message, error: snackbarError.isError })));
|
|
525
|
+
};
|
|
526
|
+
export default RepeatableField;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { InputParameter, Property } from '@evoke-platform/context';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
type CriteriaProps = {
|
|
4
|
+
fieldDefinition: InputParameter | Property;
|
|
5
|
+
value?: CriteriaValue | null;
|
|
6
|
+
handleChange: (propertyId: string, value: CriteriaValue | null) => void;
|
|
7
|
+
canUpdateProperty: boolean;
|
|
8
|
+
error?: boolean;
|
|
9
|
+
};
|
|
10
|
+
type CriteriaValue = Record<string, unknown>;
|
|
11
|
+
export default function Criteria(props: CriteriaProps): React.JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useApiServices } from '@evoke-platform/context';
|
|
2
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { useFormContext } from '../../../../../theme/hooks';
|
|
4
|
+
import { Button, CircularProgress, Typography } from '../../../../core';
|
|
5
|
+
import { Box } from '../../../../layout';
|
|
6
|
+
import CriteriaBuilder from '../../../CriteriaBuilder';
|
|
7
|
+
import { addressProperties, getPrefixedUrl } from '../utils';
|
|
8
|
+
export default function Criteria(props) {
|
|
9
|
+
const { handleChange, value, canUpdateProperty, fieldDefinition, error } = props;
|
|
10
|
+
const apiServices = useApiServices();
|
|
11
|
+
const { fetchedOptions, setFetchedOptions } = useFormContext();
|
|
12
|
+
const [loadingError, setLoadingError] = useState(false);
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
const [properties, setProperties] = useState(fetchedOptions[`${fieldDefinition.id}Options`] || []);
|
|
15
|
+
const fetchProperties = useCallback(async () => {
|
|
16
|
+
if (fieldDefinition.objectId && !fetchedOptions[`${fieldDefinition.id}Options`]) {
|
|
17
|
+
setLoading(true);
|
|
18
|
+
apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective/properties`), { params: { fields: ['properties'] } }, (error, properties) => {
|
|
19
|
+
if (error) {
|
|
20
|
+
console.error('Error fetching object properties', error);
|
|
21
|
+
setLoadingError(true);
|
|
22
|
+
}
|
|
23
|
+
if (properties) {
|
|
24
|
+
const flattenProperties = properties.flatMap((prop) => {
|
|
25
|
+
if (prop.type === 'object' || prop.type === 'user') {
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
id: `${prop.id}.id`,
|
|
29
|
+
name: `${prop.name} Id`,
|
|
30
|
+
type: 'string',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: `${prop.id}.name`,
|
|
34
|
+
name: `${prop.name} Name`,
|
|
35
|
+
type: 'string',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
else if (prop.type === 'address') {
|
|
40
|
+
return addressProperties(prop);
|
|
41
|
+
}
|
|
42
|
+
return prop;
|
|
43
|
+
});
|
|
44
|
+
setProperties(flattenProperties);
|
|
45
|
+
setFetchedOptions({
|
|
46
|
+
[`${fieldDefinition.id}Options`]: flattenProperties.map((prop) => ({
|
|
47
|
+
id: prop.id,
|
|
48
|
+
name: prop.name,
|
|
49
|
+
})),
|
|
50
|
+
});
|
|
51
|
+
setLoadingError(false);
|
|
52
|
+
}
|
|
53
|
+
setLoading(false);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}, [fieldDefinition.objectId, apiServices]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
fetchProperties();
|
|
59
|
+
}, [fetchProperties]);
|
|
60
|
+
const handleUpdate = (criteria) => {
|
|
61
|
+
if (criteria || value) {
|
|
62
|
+
handleChange(fieldDefinition.id, criteria ?? null);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
if (loadingError) {
|
|
66
|
+
return (React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
67
|
+
React.createElement(Typography, { sx: { color: 'rgb(114 124 132)', fontSize: '14px', paddingLeft: '10px' } }, "An error occurred when retrieving data needed for this criteria."),
|
|
68
|
+
React.createElement(Button, { sx: {
|
|
69
|
+
padding: 0,
|
|
70
|
+
'&:hover': { backgroundColor: 'transparent' },
|
|
71
|
+
minWidth: '44px',
|
|
72
|
+
}, variant: "text", onClick: fetchProperties, disabled: loading }, "Retry"),
|
|
73
|
+
loading && React.createElement(CircularProgress, { size: 20, sx: { paddingLeft: '10px' } })));
|
|
74
|
+
}
|
|
75
|
+
return !!value || canUpdateProperty ? (React.createElement(Box, { sx: { borderRadius: '8px', border: error ? '1px solid #FF0000' : '1px solid #ddd' } },
|
|
76
|
+
React.createElement(CriteriaBuilder, { criteria: value ?? undefined, properties: properties, setCriteria: handleUpdate, disabled: !canUpdateProperty, hideBorder: true, presetValues: [
|
|
77
|
+
{
|
|
78
|
+
label: 'Current Date',
|
|
79
|
+
value: { name: '{{{currentDate}}}', label: 'Current Date' },
|
|
80
|
+
type: 'date',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
label: 'Current Time',
|
|
84
|
+
value: { name: '{{{currentTime}}}', label: 'Current Time' },
|
|
85
|
+
type: 'time',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
label: 'Current Date Time',
|
|
89
|
+
value: { name: '{{{currentDateTime}}}', label: 'Current Date Time' },
|
|
90
|
+
type: 'date-time',
|
|
91
|
+
},
|
|
92
|
+
], enablePresetValues: true }))) : (React.createElement(Typography, { variant: "body2", sx: { color: '#637381' } }, "No criteria"));
|
|
93
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DocumentValidation } from '@evoke-platform/context';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { FieldValues } from 'react-hook-form';
|
|
4
|
+
import { SavedDocumentReference } from '../../types';
|
|
5
|
+
type DocumentProps = {
|
|
6
|
+
id: string;
|
|
7
|
+
handleChange: (propertyId: string, value: (File | SavedDocumentReference)[] | undefined) => void;
|
|
8
|
+
instance?: FieldValues;
|
|
9
|
+
canUpdateProperty: boolean;
|
|
10
|
+
error: boolean;
|
|
11
|
+
validate?: DocumentValidation;
|
|
12
|
+
value: (File | SavedDocumentReference)[] | undefined;
|
|
13
|
+
hasDescription?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare const Document: (props: DocumentProps) => React.JSX.Element;
|
|
16
|
+
export {};
|