@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.
Files changed (69) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +1 -1
  2. package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
  3. package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +3 -3
  4. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +7 -1
  5. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +7 -1
  6. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -3
  7. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +13 -3
  8. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +19 -0
  9. package/dist/published/components/custom/FormV2/FormRenderer.js +183 -0
  10. package/dist/published/components/custom/FormV2/components/AccordionSections.d.ts +4 -0
  11. package/dist/published/components/custom/FormV2/components/AccordionSections.js +131 -0
  12. package/dist/published/components/custom/FormV2/components/ActionButtons.d.ts +19 -0
  13. package/dist/published/components/custom/FormV2/components/ActionButtons.js +106 -0
  14. package/dist/published/components/custom/FormV2/components/FieldWrapper.d.ts +24 -0
  15. package/dist/published/components/custom/FormV2/components/FieldWrapper.js +100 -0
  16. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +12 -0
  17. package/dist/published/components/custom/FormV2/components/FormContext.js +8 -0
  18. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +17 -0
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +50 -0
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +14 -0
  21. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +88 -0
  22. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.d.ts +13 -0
  23. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.js +140 -0
  24. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.d.ts +17 -0
  25. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +233 -0
  26. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +40 -0
  27. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +95 -0
  28. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +12 -0
  29. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +526 -0
  30. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +12 -0
  31. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +93 -0
  32. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +16 -0
  33. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +73 -0
  34. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +13 -0
  35. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +179 -0
  36. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.d.ts +12 -0
  37. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +108 -0
  38. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.d.ts +16 -0
  39. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +129 -0
  40. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.d.ts +15 -0
  41. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +226 -0
  42. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.d.ts +4 -0
  43. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +439 -0
  44. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +29 -0
  45. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +74 -0
  46. package/dist/published/components/custom/FormV2/components/FormSections.d.ts +4 -0
  47. package/dist/published/components/custom/FormV2/components/FormSections.js +104 -0
  48. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -0
  49. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +209 -0
  50. package/dist/published/components/custom/FormV2/components/TabNav.d.ts +10 -0
  51. package/dist/published/components/custom/FormV2/components/TabNav.js +23 -0
  52. package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.d.ts +3 -0
  53. package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.js +176 -0
  54. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +10 -0
  55. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +45 -0
  56. package/dist/published/components/custom/FormV2/components/types.d.ts +122 -0
  57. package/dist/published/components/custom/FormV2/components/types.js +1 -0
  58. package/dist/published/components/custom/FormV2/components/utils.d.ts +47 -0
  59. package/dist/published/components/custom/FormV2/components/utils.js +434 -0
  60. package/dist/published/components/custom/FormV2/index.d.ts +1 -0
  61. package/dist/published/components/custom/FormV2/index.js +1 -0
  62. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.js +5 -8
  63. package/dist/published/components/custom/index.d.ts +1 -0
  64. package/dist/published/components/custom/index.js +1 -0
  65. package/dist/published/index.d.ts +2 -2
  66. package/dist/published/index.js +2 -2
  67. package/dist/published/theme/hooks.d.ts +7 -0
  68. package/dist/published/theme/hooks.js +9 -0
  69. 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 {};