@evoke-platform/ui-components 1.4.0-testing.9 → 1.5.0-dev.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 (52) hide show
  1. package/dist/published/components/core/DatePicker/DatePicker.js +1 -1
  2. package/dist/published/components/core/List/ListItemIcon/ListItemIcon.d.ts +4 -0
  3. package/dist/published/components/core/List/ListItemIcon/ListItemIcon.js +6 -0
  4. package/dist/published/components/core/List/ListItemIcon/index.d.ts +3 -0
  5. package/dist/published/components/core/List/ListItemIcon/index.js +3 -0
  6. package/dist/published/components/core/List/index.d.ts +2 -1
  7. package/dist/published/components/core/List/index.js +2 -1
  8. package/dist/published/components/core/index.d.ts +4 -2
  9. package/dist/published/components/core/index.js +2 -1
  10. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +66 -51
  11. package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +1 -1
  12. package/dist/published/components/custom/CriteriaBuilder/ValueEditor.d.ts +2 -3
  13. package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +89 -71
  14. package/dist/published/components/custom/CriteriaBuilder/utils.js +2 -2
  15. package/dist/published/components/custom/Form/Common/FormComponentWrapper.js +18 -17
  16. package/dist/published/components/custom/Form/FormComponents/CriteriaComponent/Criteria.js +52 -1
  17. package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.d.ts +1 -1
  18. package/dist/published/components/custom/Form/FormComponents/FormFieldComponent.js +7 -10
  19. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.js +8 -4
  20. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.js +3 -3
  21. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/DocumentViewerCell.d.ts +13 -0
  22. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/DocumentViewerCell.js +115 -0
  23. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +25 -26
  24. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.js +1 -1
  25. package/dist/published/components/custom/Form/utils.js +2 -2
  26. package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +24 -21
  27. package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.test.js +50 -8
  28. package/dist/published/components/custom/FormField/FormField.d.ts +4 -1
  29. package/dist/published/components/custom/FormField/FormField.js +5 -2
  30. package/dist/published/components/custom/HistoryLog/DisplayedProperty.js +1 -1
  31. package/dist/published/components/custom/Menubar/Menubar.d.ts +14 -1
  32. package/dist/published/components/custom/Menubar/Menubar.js +9 -2
  33. package/dist/published/components/custom/Menubar/Menubar.test.js +5 -0
  34. package/dist/published/components/layout/Box/Box.d.ts +3 -3
  35. package/dist/published/components/layout/Box/Box.js +4 -4
  36. package/dist/published/icons/custom/NoNavigation.d.ts +3 -0
  37. package/dist/published/icons/custom/NoNavigation.js +10 -0
  38. package/dist/published/icons/custom/SideNavigation.d.ts +3 -0
  39. package/dist/published/icons/custom/SideNavigation.js +11 -0
  40. package/dist/published/icons/custom/TopNavigation.d.ts +3 -0
  41. package/dist/published/icons/custom/TopNavigation.js +11 -0
  42. package/dist/published/icons/custom/index.d.ts +3 -0
  43. package/dist/published/icons/custom/index.js +3 -0
  44. package/dist/published/index.d.ts +1 -1
  45. package/dist/published/index.js +1 -1
  46. package/dist/published/stories/Box.stories.d.ts +3 -12
  47. package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
  48. package/dist/published/stories/Form.stories.js +7 -2
  49. package/dist/published/stories/FormField.stories.js +2 -0
  50. package/dist/published/stories/MenuBar.stories.js +13 -18
  51. package/dist/published/stories/Palette.stories.d.ts +2 -12
  52. package/package.json +7 -4
@@ -18,7 +18,58 @@ export const Criteria = (props) => {
18
18
  setLoadingError(true);
19
19
  }
20
20
  if (properties) {
21
- setProperties(properties);
21
+ const flattenProperties = properties.flatMap((prop) => {
22
+ if (prop.type === 'object' || prop.type === 'user') {
23
+ return [
24
+ {
25
+ id: `${prop.id}.id`,
26
+ name: `${prop.name} Id`,
27
+ type: 'string',
28
+ },
29
+ {
30
+ id: `${prop.id}.name`,
31
+ name: `${prop.name} Name`,
32
+ type: 'string',
33
+ },
34
+ ];
35
+ }
36
+ else if (prop.type === 'address') {
37
+ return [
38
+ {
39
+ id: `${prop.id}.line1`,
40
+ name: `${prop.name} Line 1`,
41
+ type: 'string',
42
+ },
43
+ {
44
+ id: `${prop.id}.line2`,
45
+ name: `${prop.name} Line 2`,
46
+ type: 'string',
47
+ },
48
+ {
49
+ id: `${prop.id}.city`,
50
+ name: `${prop.name} City`,
51
+ type: 'string',
52
+ },
53
+ {
54
+ id: `${prop.id}.county`,
55
+ name: `${prop.name} County`,
56
+ type: 'string',
57
+ },
58
+ {
59
+ id: `${prop.id}.state`,
60
+ name: `${prop.name} State`,
61
+ type: 'string',
62
+ },
63
+ {
64
+ id: `${prop.id}.zipCode`,
65
+ name: `${prop.name} Zip Code`,
66
+ type: 'string',
67
+ },
68
+ ];
69
+ }
70
+ return prop;
71
+ });
72
+ setProperties(flattenProperties);
22
73
  setLoadingError(false);
23
74
  }
24
75
  setLoading(false);
@@ -35,7 +35,7 @@ export declare class FormFieldComponent extends ReactComponent {
35
35
  */
36
36
  manageFormErrors(): void;
37
37
  beforeSubmit(): void;
38
- handleComponentChange: (components: any, value: any) => void;
38
+ handleAddressChange: (components: any, value: any) => void;
39
39
  handleChange: (key: string, value: any) => void;
40
40
  attachReact(element: Element): void;
41
41
  }
@@ -61,13 +61,13 @@ export class FormFieldComponent extends ReactComponent {
61
61
  selectOptions,
62
62
  inputMaskPlaceholderChar: component.inputMaskPlaceholderChar || '_',
63
63
  }, options, data);
64
- this.handleComponentChange = (components, value) => {
64
+ this.handleAddressChange = (components, value) => {
65
65
  if (isArray(components)) {
66
66
  if (components.filter((component) => Object.hasOwnProperty.call(component, 'components'))) {
67
67
  components
68
68
  .filter((component) => Object.hasOwnProperty.call(component, 'components'))
69
69
  .forEach((comp) => {
70
- this.handleComponentChange(comp.components, value);
70
+ this.handleAddressChange(comp.components, value);
71
71
  });
72
72
  }
73
73
  components
@@ -93,12 +93,7 @@ export class FormFieldComponent extends ReactComponent {
93
93
  else {
94
94
  selectedValue = value.line1;
95
95
  label = value.line1;
96
- this.handleComponentChange(this.root.components, value);
97
- this.root.components
98
- .filter((component) => Object.prototype.hasOwnProperty.call(component, 'components'))
99
- .forEach((section) => {
100
- this.handleComponentChange(section.components, value);
101
- });
96
+ this.handleAddressChange(this.root.components, value);
102
97
  }
103
98
  }
104
99
  else if (this.component.property.type === 'choices' || this.component.property.type === 'array') {
@@ -152,7 +147,9 @@ export class FormFieldComponent extends ReactComponent {
152
147
  this.on('changed-' + this.component.key, (value) => {
153
148
  this.setValue(value ?? '');
154
149
  this.updateValue(value, { modified: true });
155
- this.attach(this.element);
150
+ if (this.element) {
151
+ this.attach(this.element);
152
+ }
156
153
  });
157
154
  }
158
155
  if (this.component.type === 'Date') {
@@ -465,6 +462,6 @@ export class FormFieldComponent extends ReactComponent {
465
462
  falsePositiveMaskError &&
466
463
  isEmpty(this.errorDetails) &&
467
464
  this.emit('changed-' + this.component.key, e.target.value);
468
- }, ...this.component, id: inputId, defaultValue: this.dataValue, mask: this.component.inputMask, error: this.hasErrors(), size: this.component.fieldHeight ?? 'medium' }))), root);
465
+ }, ...this.component, id: inputId, defaultValue: this.dataValue, mask: this.component.inputMask, error: this.hasErrors(), size: this.component.fieldHeight ?? 'medium', required: this.component.validate?.required }))), root);
469
466
  }
470
467
  }
@@ -71,7 +71,7 @@ export class ObjectComponent extends ReactComponent {
71
71
  }
72
72
  this.updatedCriteria = updateCriteriaInputs(this.criteria ?? {}, data, this.component.user);
73
73
  if (this.visible) {
74
- this.attachReact(this.element);
74
+ this.attach(this.element);
75
75
  }
76
76
  });
77
77
  }
@@ -115,7 +115,7 @@ export class ObjectComponent extends ReactComponent {
115
115
  }
116
116
  this.updatedDefaultValueCriteria = updateCriteriaInputs(this.defaultValueCriteria ?? {}, data, this.component.user);
117
117
  if (this.visible) {
118
- this.attachReact(this.element);
118
+ this.attach(this.element);
119
119
  }
120
120
  });
121
121
  }
@@ -158,7 +158,6 @@ export class ObjectComponent extends ReactComponent {
158
158
  delete this.errorDetails['api-error'];
159
159
  }
160
160
  this.attach(this.element);
161
- this.attachReact(this.element);
162
161
  });
163
162
  if (this.component.defaultValue) {
164
163
  this.expandInstance();
@@ -216,11 +215,16 @@ export class ObjectComponent extends ReactComponent {
216
215
  if (!root) {
217
216
  root = element;
218
217
  }
218
+ let updatedValue;
219
+ if (this.shouldSetValue)
220
+ updatedValue = this.dataForSetting;
221
+ else
222
+ updatedValue = this.dataValue;
219
223
  const updatedComponent = {
220
224
  ...this.component,
221
225
  instance: {
222
226
  ...this.component.instance,
223
- [this.component.key]: isEmpty(this.dataValue) ? null : this.dataValue,
227
+ [this.component.key]: isEmpty(updatedValue) || isNil(updatedValue) || updatedValue.length === 0 ? null : updatedValue,
224
228
  },
225
229
  defaultValueCriteria: this.updatedDefaultValueCriteria,
226
230
  };
@@ -41,7 +41,7 @@ export const RelatedObjectInstance = (props) => {
41
41
  handleClose();
42
42
  setErrors([]);
43
43
  };
44
- const createNewInstance = async (submission) => {
44
+ const createNewInstance = async (submission, setSubmitting) => {
45
45
  if (!relatedObject) {
46
46
  // Handle the case where relatedObject is undefined
47
47
  return { isSuccessful: false };
@@ -74,12 +74,12 @@ export const RelatedObjectInstance = (props) => {
74
74
  setSnackbarError({
75
75
  showAlert: true,
76
76
  message: err.response?.data?.error?.details?.[0]?.message ??
77
- err.data?.error?.message ??
77
+ err.response?.data?.error?.message ??
78
78
  `An error occurred. The new instance was not created.`,
79
79
  isError: true,
80
80
  });
81
81
  error = err.response?.data?.error;
82
- onClose();
82
+ setSubmitting && setSubmitting(false);
83
83
  }
84
84
  return { isSuccessful, error };
85
85
  };
@@ -0,0 +1,13 @@
1
+ import { ApiServices, ObjectInstance } from '@evoke-platform/context';
2
+ import React from 'react';
3
+ export type DocumentViewerCellProps = {
4
+ instance: ObjectInstance;
5
+ propertyId: string;
6
+ apiServices: ApiServices;
7
+ setSnackbarError: (error: {
8
+ showAlert: boolean;
9
+ message?: string;
10
+ isError?: boolean;
11
+ }) => void;
12
+ };
13
+ export declare const DocumentViewerCell: (props: DocumentViewerCellProps) => React.JSX.Element;
@@ -0,0 +1,115 @@
1
+ import React, { useState } from 'react';
2
+ import { AutorenewRounded, FileWithExtension, LaunchRounded } from '../../../../../icons';
3
+ import { Button, Menu, MenuItem, Typography } from '../../../../core';
4
+ import { Grid } from '../../../../layout';
5
+ import { getPrefixedUrl } from '../../utils';
6
+ export const DocumentViewerCell = (props) => {
7
+ const { instance, propertyId, apiServices, setSnackbarError } = props;
8
+ const [anchorEl, setAnchorEl] = useState(null);
9
+ const [isLoading, setIsLoading] = useState(false);
10
+ const downloadDocument = async (doc, instance) => {
11
+ setIsLoading(true);
12
+ try {
13
+ const documentResponse = await apiServices.get(getPrefixedUrl(`/objects/${instance.objectId}/instances/${instance.id}/documents/${doc.id}/content`), { responseType: 'blob' });
14
+ const contentType = documentResponse.type;
15
+ const blob = new Blob([documentResponse], { type: contentType });
16
+ const url = URL.createObjectURL(blob);
17
+ // Let the browser handle whether to open the document to view in a new tab or download it.
18
+ window.open(url, '_blank');
19
+ setIsLoading(false);
20
+ URL.revokeObjectURL(url);
21
+ }
22
+ catch (error) {
23
+ const status = error.status;
24
+ let message = 'An error occurred while downloading the document.';
25
+ if (status === 403) {
26
+ message = 'You do not have permission to download this document.';
27
+ }
28
+ else if (status === 404) {
29
+ message = 'Document not found.';
30
+ }
31
+ setIsLoading(false);
32
+ setSnackbarError({
33
+ showAlert: true,
34
+ message,
35
+ isError: true,
36
+ });
37
+ }
38
+ };
39
+ return (React.createElement(React.Fragment, null, instance[propertyId]?.length ? (React.createElement(React.Fragment, null,
40
+ React.createElement(Button, { sx: {
41
+ display: 'flex',
42
+ alignItems: 'center',
43
+ justifyContent: 'flex-start',
44
+ padding: '6px 10px',
45
+ }, color: 'inherit', onClick: async (event) => {
46
+ event.stopPropagation();
47
+ const documents = instance[propertyId];
48
+ if (documents.length === 1) {
49
+ await downloadDocument(documents[0], instance);
50
+ }
51
+ else {
52
+ setAnchorEl(event.currentTarget);
53
+ }
54
+ }, variant: "text", "aria-haspopup": "menu", "aria-controls": `document-menu-${instance.id}-${propertyId}`, "aria-expanded": anchorEl ? 'true' : 'false' },
55
+ isLoading ? (React.createElement(AutorenewRounded, { sx: {
56
+ color: '#637381',
57
+ width: '20px',
58
+ height: '20px',
59
+ } })) : (React.createElement(LaunchRounded, { sx: {
60
+ color: '#637381',
61
+ width: '20px',
62
+ height: '20px',
63
+ } })),
64
+ React.createElement(Typography, { sx: {
65
+ marginLeft: '8px',
66
+ fontWeight: 400,
67
+ fontSize: '14px',
68
+ } }, isLoading ? 'Preparing document...' : 'View Document')),
69
+ React.createElement("section", { role: "region", "aria-label": "Document Menu" },
70
+ React.createElement(Menu, { id: `document-menu-${instance.id}-${propertyId}`, anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
71
+ setAnchorEl(null);
72
+ }, sx: {
73
+ '& .MuiPaper-root': {
74
+ borderRadius: '12px',
75
+ boxShadow: 'rgba(145, 158, 171, 0.2)',
76
+ },
77
+ }, variant: 'menu', slotProps: {
78
+ paper: {
79
+ tabIndex: 0,
80
+ style: {
81
+ maxHeight: 200,
82
+ maxWidth: 300,
83
+ minWidth: 300,
84
+ },
85
+ },
86
+ } }, instance[propertyId].map((document) => (React.createElement(MenuItem, { key: document.id, onClick: async (e) => {
87
+ setAnchorEl(null);
88
+ await downloadDocument(document, instance);
89
+ }, "aria-label": document.name },
90
+ React.createElement(Grid, { item: true, sx: {
91
+ display: 'flex',
92
+ justifyContent: 'center',
93
+ padding: '7px',
94
+ } },
95
+ React.createElement(FileWithExtension, { fontFamily: "Arial", fileExtension: document.name?.split('.')?.pop() ?? '', sx: {
96
+ height: '1rem',
97
+ width: '1rem',
98
+ } })),
99
+ React.createElement(Grid, { item: true, xs: 12, sx: {
100
+ width: '100%',
101
+ overflow: 'hidden',
102
+ textOverflow: 'ellipsis',
103
+ } },
104
+ React.createElement(Typography, { noWrap: true, sx: {
105
+ fontSize: '14px',
106
+ fontWeight: 700,
107
+ color: '#212B36',
108
+ lineHeight: '15px',
109
+ width: '100%',
110
+ } }, document.name))))))))) : (React.createElement(Typography, { sx: {
111
+ fontStyle: 'italic',
112
+ marginLeft: '12px',
113
+ fontSize: '14px',
114
+ } }, "No documents"))));
115
+ };
@@ -9,6 +9,7 @@ import { Button, IconButton, Skeleton, Snackbar, Table, TableBody, TableCell, Ta
9
9
  import { Box } from '../../../../layout';
10
10
  import { getPrefixedUrl, normalizeDateTime } from '../../utils';
11
11
  import { ActionDialog } from './ActionDialog';
12
+ import { DocumentViewerCell } from './DocumentViewerCell';
12
13
  const styles = {
13
14
  addButton: {
14
15
  backgroundColor: 'rgba(0, 117, 167, 0.08)',
@@ -250,13 +251,13 @@ const RepeatableField = (props) => {
250
251
  isSuccessful = true;
251
252
  }
252
253
  catch (err) {
254
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
255
+ error = err.response?.data?.error;
253
256
  setSnackbarError({
254
257
  showAlert: true,
255
- message: `An error occurred while creating an instance`,
258
+ message: error?.message ?? `An error occurred while creating an instance`,
256
259
  isError: true,
257
260
  });
258
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
- error = err.response?.data?.error;
260
261
  setSubmitting && setSubmitting(false);
261
262
  }
262
263
  }
@@ -284,13 +285,15 @@ const RepeatableField = (props) => {
284
285
  isSuccessful = true;
285
286
  }
286
287
  catch (err) {
288
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
289
+ error = err.response?.data?.error;
287
290
  setSnackbarError({
288
291
  showAlert: true,
289
- message: `An error occurred while ${actionType === 'delete' ? ' deleting' : ' updating'} an instance`,
292
+ message: error?.message ??
293
+ `An error occurred while ${actionType === 'delete' ? ' deleting' : ' updating'} an instance`,
290
294
  isError: true,
291
295
  });
292
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
293
- error = err.response?.data?.error;
296
+ setSubmitting && setSubmitting(false);
294
297
  }
295
298
  }
296
299
  return { isSuccessful, error };
@@ -334,9 +337,9 @@ const RepeatableField = (props) => {
334
337
  };
335
338
  const getValue = (relatedInstance, propertyId, propertyType) => {
336
339
  const value = get(relatedInstance, propertyId);
337
- // If the property is not date-like or document then just return the
340
+ // If the property is not date-like then just return the
338
341
  // value found at the given path.
339
- if (!['date', 'date-time', 'time', 'document'].includes(propertyType)) {
342
+ if (!['date', 'date-time', 'time'].includes(propertyType)) {
340
343
  return value;
341
344
  }
342
345
  // If the date-like value is empty then there is no need to format.
@@ -356,9 +359,6 @@ const RepeatableField = (props) => {
356
359
  if (propertyType === 'time') {
357
360
  return DateTime.fromISO(stringValue).toLocaleString(DateTime.TIME_SIMPLE);
358
361
  }
359
- if (property.type === 'document') {
360
- return Array.isArray(value) ? value.map((v) => v.name).join(', ') : value;
361
- }
362
362
  };
363
363
  const columns = retrieveViewLayout();
364
364
  return loading ? (React.createElement(React.Fragment, null,
@@ -376,23 +376,22 @@ const RepeatableField = (props) => {
376
376
  React.createElement(TableHead, { sx: { backgroundColor: '#F4F6F8' } },
377
377
  React.createElement(TableRow, null,
378
378
  columns?.map((prop) => React.createElement(TableCell, { sx: styles.tableCell }, prop.name)),
379
- React.createElement(TableCell, { sx: { ...styles.tableCell, width: '80px' } }))),
379
+ canUpdateProperty && React.createElement(TableCell, { sx: { ...styles.tableCell, width: '80px' } }))),
380
380
  React.createElement(TableBody, null, relatedInstances?.map((relatedInstance, index) => (React.createElement(TableRow, { key: relatedInstance.id },
381
381
  columns?.map((prop) => {
382
- return (React.createElement(TableCell, { sx: { color: '#212B36', fontSize: '16px' } },
383
- React.createElement(Typography, { key: prop.id, sx: prop.id === 'name'
384
- ? {
385
- '&:hover': {
386
- textDecoration: 'underline',
387
- cursor: 'pointer',
388
- },
389
- }
390
- : {}, onClick: canUpdateProperty && prop.id === 'name'
391
- ? () => editRow(relatedInstance.id)
392
- : undefined },
393
- getValue(relatedInstance, prop.id, prop.type),
394
- prop.type === 'user' &&
395
- users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' && React.createElement("span", null, ' (Inactive)'))));
382
+ return (React.createElement(TableCell, { sx: { color: '#212B36', fontSize: '16px' } }, prop.type === 'document' ? (React.createElement(DocumentViewerCell, { instance: relatedInstance, propertyId: prop.id, apiServices: apiServices, setSnackbarError: setSnackbarError })) : (React.createElement(Typography, { key: prop.id, sx: prop.id === 'name'
383
+ ? {
384
+ '&:hover': {
385
+ textDecoration: 'underline',
386
+ cursor: 'pointer',
387
+ },
388
+ }
389
+ : {}, onClick: canUpdateProperty && prop.id === 'name'
390
+ ? () => editRow(relatedInstance.id)
391
+ : undefined },
392
+ getValue(relatedInstance, prop.id, prop.type),
393
+ prop.type === 'user' &&
394
+ users?.find((user) => get(relatedInstance, `${prop.id.split('.')[0]}.id`) === user.id)?.status === 'Inactive' && (React.createElement("span", null, ' (Inactive)'))))));
396
395
  }),
397
396
  canUpdateProperty && (React.createElement(TableCell, { sx: { width: '80px' } },
398
397
  React.createElement(IconButton, { "aria-label": `edit-collection-instance-${index}`, onClick: () => editRow(relatedInstance.id) },
@@ -59,7 +59,7 @@ export class RepeatableFieldComponent extends ReactComponent {
59
59
  }
60
60
  }
61
61
  this.updatedCriteria = updateCriteriaInputs(this.criteria ?? {}, data, this.component.user);
62
- this.attachReact(this.element);
62
+ this.attach(this.element);
63
63
  });
64
64
  }
65
65
  }
@@ -127,7 +127,7 @@ export function convertFormToComponents(entries, parameters, object) {
127
127
  conditional: convertVisibilityToConditional(entry.visibility),
128
128
  };
129
129
  }
130
- else {
130
+ else if (entry.type === 'input') {
131
131
  const displayOptions = entry.display;
132
132
  const parameter = parameters.find((parameter) => parameter.id === entry.parameterId);
133
133
  if (!parameter) {
@@ -719,7 +719,7 @@ formComponents, allCriteriaInputs, instance, objectPropertyInputProps, associate
719
719
  // Set the associated instance as a default value and hide the field.
720
720
  if (associatedObject?.instanceId &&
721
721
  associatedObject?.propertyId &&
722
- item.property.id === associatedObject.propertyId) {
722
+ item.property?.id === associatedObject.propertyId) {
723
723
  item.defaultValue = { id: associatedObject.instanceId };
724
724
  item.hidden = true;
725
725
  // If "conditional" is defined, the "hidden" property isn't respected.
@@ -1,39 +1,42 @@
1
+ import parse from 'html-react-parser';
1
2
  import React, { useEffect, useState } from 'react';
2
- import { Autocomplete, TextField } from '../../../core';
3
+ import { Help } from '../../../../icons';
4
+ import { Autocomplete, Checkbox, FormControl, FormControlLabel, FormHelperText, IconButton, Switch, TextField, Tooltip, Typography, } from '../../../core';
3
5
  import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
6
+ const descriptionStyles = {
7
+ color: '#999 !important',
8
+ whiteSpace: 'normal',
9
+ paddingBottom: '4px',
10
+ marginX: 0,
11
+ };
4
12
  const BooleanSelect = (props) => {
5
- const { id, property, defaultValue, error, errorMessage, readOnly, size, placeholder, onBlur, additionalProps } = props;
13
+ const { id, property, defaultValue, error, errorMessage, readOnly, size, displayOption, label, required, tooltip, description, placeholder, onBlur, additionalProps, } = props;
6
14
  const [value, setValue] = useState(defaultValue);
7
15
  useEffect(() => {
8
16
  setValue(defaultValue);
9
17
  }, [defaultValue]);
10
- const handleChange = (event, selected) => {
11
- setValue(selected.value);
12
- props.onChange(property.id, selected.value, property);
18
+ const handleChange = (value) => {
19
+ setValue(value);
20
+ props.onChange(property.id, value, property);
13
21
  };
14
22
  const booleanOptions = [
15
23
  {
16
- label: 'Yes',
24
+ label: 'True',
17
25
  value: true,
18
26
  },
19
27
  {
20
- label: 'No',
28
+ label: 'False',
21
29
  value: false,
22
30
  },
23
31
  ];
24
- return readOnly ? (React.createElement(InputFieldComponent, { ...props })) : (React.createElement(Autocomplete, { id: id, renderInput: (params) => (React.createElement(TextField, { ...params, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, sx: { background: 'white' }, size: size ?? 'medium', placeholder: placeholder })), value: value, onChange: handleChange, isOptionEqualToValue: (option, val) => {
25
- if (typeof val === 'boolean') {
26
- return option.value === val;
27
- }
28
- return option.value === val?.value;
29
- }, getOptionLabel: (option) => {
30
- if (typeof option === 'boolean') {
31
- const opt = booleanOptions.find((o) => o.value === option);
32
- return opt ? opt.label : option;
33
- }
34
- if (typeof option === 'string')
35
- return option;
36
- return option.label;
37
- }, options: booleanOptions, disableClearable: true, sx: { background: 'white', borderRadius: '8px' }, ...(additionalProps ?? {}) }));
32
+ return displayOption === 'dropdown' ? (readOnly ? (React.createElement(InputFieldComponent, { ...props })) : (React.createElement(Autocomplete, { renderInput: (params) => (React.createElement(TextField, { ...params, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, sx: { background: 'white' }, placeholder: placeholder, size: size ?? 'medium' })), value: booleanOptions.find((opt) => opt.value === value) ?? '', onChange: (e, selectedValue) => handleChange(selectedValue.value), isOptionEqualToValue: (option, val) => option?.value === val?.value, options: booleanOptions, disableClearable: true, sx: { background: 'white', borderRadius: '8px' }, ...(additionalProps ?? {}), sortBy: "NONE" }))) : (React.createElement(FormControl, { error: error, fullWidth: true },
33
+ React.createElement(FormControlLabel, { labelPlacement: "end", label: React.createElement(Typography, { variant: "body2", sx: { wordWrap: 'break-word', fontFamily: 'Public Sans' } },
34
+ label,
35
+ tooltip && (React.createElement(Tooltip, { placement: "right", title: tooltip },
36
+ React.createElement(IconButton, null,
37
+ React.createElement(Help, { sx: { fontSize: '14px' } })))),
38
+ required && (React.createElement(Typography, { variant: "body2", component: "span", color: "error", sx: { marginLeft: '4px', fontSize: '18px' } }, "*"))), control: displayOption === 'switch' ? (React.createElement(Switch, { id: id, "aria-checked": value, "aria-required": required, "aria-invalid": error, size: size ?? 'medium', name: property.id, checked: value, onChange: (e) => handleChange(e.target.checked), disabled: readOnly, sx: { alignSelf: 'start' } })) : (React.createElement(Checkbox, { id: id, "aria-checked": value, "aria-required": required, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), disabled: readOnly, sx: { alignSelf: 'start', padding: '4px 9px 9px 9px' } })) }),
39
+ error && React.createElement(FormHelperText, { sx: { marginX: 0 } }, errorMessage),
40
+ description && (React.createElement(FormHelperText, { sx: descriptionStyles, component: Typography }, parse(description)))));
38
41
  };
39
42
  export default BooleanSelect;
@@ -10,13 +10,55 @@ const booleanProperty = {
10
10
  name: 'Question',
11
11
  type: 'boolean',
12
12
  };
13
- it('returns selected option', async () => {
13
+ describe('BooleanSelect', () => {
14
14
  const user = userEvent.setup();
15
- const onChangeMock = vi.fn((name, value, property) => { });
16
- render(React.createElement(BooleanSelect, { id: "chooseYesOrNo", property: booleanProperty, onChange: onChangeMock }));
17
- const inputField = screen.getByRole('combobox');
18
- await user.click(inputField);
19
- const yesOption = await screen.findByRole('option', { name: 'Yes' });
20
- await user.click(yesOption);
21
- expect(onChangeMock).toBeCalledWith('theQuestion', true, booleanProperty);
15
+ it('returns selected option', async () => {
16
+ const user = userEvent.setup();
17
+ const onChangeMock = vi.fn((name, value, property) => { });
18
+ render(React.createElement(BooleanSelect, { id: "chooseTrueOrFalse", property: booleanProperty, onChange: onChangeMock, displayOption: "dropdown" }));
19
+ const inputField = screen.getByRole('combobox');
20
+ await user.click(inputField);
21
+ const trueOption = await screen.findByRole('option', { name: 'True' });
22
+ await user.click(trueOption);
23
+ expect(onChangeMock).toBeCalledWith('theQuestion', true, booleanProperty);
24
+ });
25
+ it('renders checkbox', () => {
26
+ const onChangeMock = vi.fn((name, value, property) => { });
27
+ render(React.createElement(BooleanSelect, { id: "checkbox", property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
28
+ const checkbox = screen.queryByRole('checkbox', { name: /Field label/i });
29
+ expect(checkbox).to.be.not.null;
30
+ });
31
+ it('renders switch', () => {
32
+ const onChangeMock = vi.fn((name, value, property) => { });
33
+ render(React.createElement(BooleanSelect, { id: "switch", property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "switch" }));
34
+ const switchComp = screen.queryByRole('checkbox', { name: /Field label/i });
35
+ expect(switchComp).to.be.not.null;
36
+ });
37
+ it('renders label', () => {
38
+ const onChangeMock = vi.fn((name, value, property) => { });
39
+ render(React.createElement(BooleanSelect, { id: "checkbox", property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
40
+ const label = screen.queryByText(/Field label/i);
41
+ expect(label).to.be.not.null;
42
+ });
43
+ it('allows defaulting', () => {
44
+ const onChangeMock = vi.fn((name, value, property) => { });
45
+ render(React.createElement(BooleanSelect, { id: "checkbox", defaultValue: true, property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
46
+ const checkbox = screen.getByRole('checkbox', { name: /Field label/i });
47
+ expect(checkbox.checked).to.be.true;
48
+ });
49
+ it('sets checkbox', async () => {
50
+ const onChangeMock = vi.fn((name, value, property) => { });
51
+ render(React.createElement(BooleanSelect, { id: "checkbox", property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
52
+ const checkbox = screen.getByRole('checkbox', { name: /Field label/i });
53
+ await user.click(checkbox);
54
+ expect(checkbox.checked).to.be.true;
55
+ expect(onChangeMock).toBeCalledWith('theQuestion', true, booleanProperty);
56
+ });
57
+ it('unsets checkbox', async () => {
58
+ const onChangeMock = vi.fn((name, value, property) => { });
59
+ render(React.createElement(BooleanSelect, { id: "checkbox", defaultValue: true, property: booleanProperty, onChange: onChangeMock, label: 'Field label', displayOption: "checkbox" }));
60
+ const checkbox = screen.getByRole('checkbox', { name: /Field label/i });
61
+ await user.click(checkbox);
62
+ expect(checkbox.checked).to.be.false;
63
+ });
22
64
  });
@@ -29,8 +29,11 @@ export type FormFieldProps = {
29
29
  getOptionLabel?: (option: AutocompleteOption) => string;
30
30
  disableCloseOnSelect?: boolean;
31
31
  additionalProps?: Record<string, unknown>;
32
- displayOption?: 'dropdown' | 'radioButton';
32
+ displayOption?: 'dropdown' | 'radioButton' | 'switch' | 'checkbox';
33
33
  sortBy?: 'ASC' | 'DESC' | 'NONE';
34
+ label?: string;
35
+ description?: string;
36
+ tooltip?: string;
34
37
  };
35
38
  declare const FormField: (props: FormFieldProps) => React.JSX.Element;
36
39
  export default FormField;
@@ -8,7 +8,7 @@ import InputFieldComponent from './InputFieldComponent/InputFieldComponent';
8
8
  import Select from './Select/Select';
9
9
  import TimePickerSelect from './TimePickerSelect/TimePickerSelect';
10
10
  const FormField = (props) => {
11
- const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, } = props;
11
+ const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, label, description, tooltip, } = props;
12
12
  let control;
13
13
  const commonProps = {
14
14
  id: id ?? property.id,
@@ -32,6 +32,9 @@ const FormField = (props) => {
32
32
  additionalProps,
33
33
  displayOption,
34
34
  sortBy,
35
+ label,
36
+ description,
37
+ tooltip,
35
38
  };
36
39
  if (queryAddresses) {
37
40
  control = (React.createElement(AddressFieldComponent, { ...commonProps, mask: mask, inputMaskPlaceholderChar: inputMaskPlaceholderChar, isMultiLineText: isMultiLineText, rows: rows, queryAddresses: queryAddresses }));
@@ -39,7 +42,7 @@ const FormField = (props) => {
39
42
  }
40
43
  switch (property.type) {
41
44
  case 'boolean':
42
- control = React.createElement(BooleanSelect, { ...commonProps, defaultValue: defaultValue });
45
+ control = React.createElement(BooleanSelect, { ...commonProps });
43
46
  break;
44
47
  case 'date':
45
48
  control = React.createElement(DatePickerSelect, { ...commonProps });
@@ -23,7 +23,7 @@ const DisplayedProperty = (props) => {
23
23
  return stringAddress;
24
24
  };
25
25
  const formatData = (property, value) => {
26
- if (property?.objectId && property?.type === 'object') {
26
+ if (property?.objectId && (property?.type === 'object' || property?.type === 'collection')) {
27
27
  return value?.name ?? value?.id;
28
28
  }
29
29
  switch (property?.type) {