@evoke-platform/ui-components 1.6.0-dev.3 → 1.6.0-dev.30

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 (99) hide show
  1. package/dist/published/components/core/SwipeableDrawer/SwipeableDrawer.d.ts +4 -0
  2. package/dist/published/components/core/SwipeableDrawer/SwipeableDrawer.js +8 -0
  3. package/dist/published/components/core/SwipeableDrawer/index.d.ts +3 -0
  4. package/dist/published/components/core/SwipeableDrawer/index.js +3 -0
  5. package/dist/published/components/core/index.d.ts +1 -0
  6. package/dist/published/components/core/index.js +1 -0
  7. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +1 -1
  8. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.d.ts +1 -0
  9. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +45 -5
  10. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentComponent.d.ts +1 -0
  11. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentComponent.js +8 -1
  12. package/dist/published/components/custom/Form/FormComponents/ImageComponent/Image.js +2 -2
  13. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +87 -57
  14. package/dist/published/components/custom/Form/tests/Form.test.js +1 -1
  15. package/dist/published/components/custom/Form/utils.js +6 -0
  16. package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
  17. package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +3 -3
  18. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +7 -1
  19. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +7 -1
  20. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -3
  21. package/dist/published/components/custom/FormField/Select/Select.js +17 -5
  22. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +13 -3
  23. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +19 -0
  24. package/dist/published/components/custom/FormV2/FormRenderer.js +183 -0
  25. package/dist/published/components/custom/FormV2/components/AccordionSections.d.ts +4 -0
  26. package/dist/published/components/custom/FormV2/components/AccordionSections.js +131 -0
  27. package/dist/published/components/custom/FormV2/components/ActionButtons.d.ts +19 -0
  28. package/dist/published/components/custom/FormV2/components/ActionButtons.js +106 -0
  29. package/dist/published/components/custom/FormV2/components/FieldWrapper.d.ts +24 -0
  30. package/dist/published/components/custom/FormV2/components/FieldWrapper.js +100 -0
  31. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +12 -0
  32. package/dist/published/components/custom/FormV2/components/FormContext.js +8 -0
  33. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +17 -0
  34. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +50 -0
  35. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +14 -0
  36. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +88 -0
  37. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.d.ts +13 -0
  38. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.js +140 -0
  39. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.d.ts +17 -0
  40. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +233 -0
  41. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +40 -0
  42. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +95 -0
  43. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +12 -0
  44. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +526 -0
  45. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +12 -0
  46. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +93 -0
  47. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +16 -0
  48. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +113 -0
  49. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +13 -0
  50. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +179 -0
  51. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.d.ts +12 -0
  52. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +108 -0
  53. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.d.ts +16 -0
  54. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +129 -0
  55. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.d.ts +15 -0
  56. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +226 -0
  57. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.d.ts +4 -0
  58. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +439 -0
  59. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +29 -0
  60. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +74 -0
  61. package/dist/published/components/custom/FormV2/components/FormSections.d.ts +4 -0
  62. package/dist/published/components/custom/FormV2/components/FormSections.js +104 -0
  63. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -0
  64. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +209 -0
  65. package/dist/published/components/custom/FormV2/components/TabNav.d.ts +10 -0
  66. package/dist/published/components/custom/FormV2/components/TabNav.js +23 -0
  67. package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.d.ts +3 -0
  68. package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.js +176 -0
  69. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +10 -0
  70. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +45 -0
  71. package/dist/published/components/custom/FormV2/components/types.d.ts +122 -0
  72. package/dist/published/components/custom/FormV2/components/types.js +1 -0
  73. package/dist/published/components/custom/FormV2/components/utils.d.ts +47 -0
  74. package/dist/published/components/custom/FormV2/components/utils.js +434 -0
  75. package/dist/published/components/custom/FormV2/index.d.ts +1 -0
  76. package/dist/published/components/custom/FormV2/index.js +1 -0
  77. package/dist/published/components/custom/HistoryLog/HistoryData.d.ts +1 -0
  78. package/dist/published/components/custom/HistoryLog/HistoryData.js +14 -6
  79. package/dist/published/components/custom/HistoryLog/HistoryLoading.d.ts +4 -1
  80. package/dist/published/components/custom/HistoryLog/HistoryLoading.js +14 -8
  81. package/dist/published/components/custom/HistoryLog/index.d.ts +2 -0
  82. package/dist/published/components/custom/HistoryLog/index.js +4 -4
  83. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.d.ts +33 -0
  84. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.js +140 -0
  85. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.test.d.ts +1 -0
  86. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.test.js +100 -0
  87. package/dist/published/components/custom/ResponsiveOverflow/index.d.ts +4 -0
  88. package/dist/published/components/custom/ResponsiveOverflow/index.js +3 -0
  89. package/dist/published/components/custom/index.d.ts +2 -0
  90. package/dist/published/components/custom/index.js +2 -0
  91. package/dist/published/components/custom/util.d.ts +1 -0
  92. package/dist/published/components/custom/util.js +28 -0
  93. package/dist/published/index.d.ts +1 -1
  94. package/dist/published/index.js +1 -1
  95. package/dist/published/stories/ResponsiveOverflow.stories.d.ts +8 -0
  96. package/dist/published/stories/ResponsiveOverflow.stories.js +91 -0
  97. package/dist/published/theme/hooks.d.ts +7 -0
  98. package/dist/published/theme/hooks.js +9 -0
  99. package/package.json +6 -2
@@ -0,0 +1,4 @@
1
+ import { SwipeableDrawerProps } from '@mui/material';
2
+ import * as React from 'react';
3
+ declare const SwipeableDrawer: (props: SwipeableDrawerProps) => React.JSX.Element;
4
+ export default SwipeableDrawer;
@@ -0,0 +1,8 @@
1
+ import { SwipeableDrawer as MuiSwipeableDrawer } from '@mui/material';
2
+ import * as React from 'react';
3
+ import UIThemeProvider from '../../../theme';
4
+ const SwipeableDrawer = (props) => {
5
+ return (React.createElement(UIThemeProvider, null,
6
+ React.createElement(MuiSwipeableDrawer, { ...props })));
7
+ };
8
+ export default SwipeableDrawer;
@@ -0,0 +1,3 @@
1
+ import SwipeableDrawer from './SwipeableDrawer';
2
+ export default SwipeableDrawer;
3
+ export { SwipeableDrawer };
@@ -0,0 +1,3 @@
1
+ import SwipeableDrawer from './SwipeableDrawer';
2
+ export default SwipeableDrawer;
3
+ export { SwipeableDrawer };
@@ -37,6 +37,7 @@ export { Skeleton } from './Skeleton';
37
37
  export { Snackbar } from './Snackbar';
38
38
  export * from './StaticDatePicker';
39
39
  export { Step, StepButton, StepConnector, StepContent, StepIcon, StepLabel, Stepper } from './Stepper';
40
+ export { SwipeableDrawer } from './SwipeableDrawer';
40
41
  export { Switch } from './Switch';
41
42
  export { Table } from './Table';
42
43
  export { Tab, Tabs } from './Tabs';
@@ -37,6 +37,7 @@ export { Skeleton } from './Skeleton';
37
37
  export { Snackbar } from './Snackbar';
38
38
  export * from './StaticDatePicker';
39
39
  export { Step, StepButton, StepConnector, StepContent, StepIcon, StepLabel, Stepper } from './Stepper';
40
+ export { SwipeableDrawer } from './SwipeableDrawer';
40
41
  export { Switch } from './Switch';
41
42
  export { Table } from './Table';
42
43
  export { Tab, Tabs } from './Tabs';
@@ -186,7 +186,7 @@ const customSelector = (props) => {
186
186
  return opts.find((o) => option === o.name)?.label || option;
187
187
  }
188
188
  return option.label;
189
- }, isOptionEqualToValue: (option, value) => {
189
+ }, getOptionKey: (option) => option.name, isOptionEqualToValue: (option, value) => {
190
190
  if (typeof option === 'string') {
191
191
  return option === value;
192
192
  }
@@ -12,6 +12,7 @@ type DocumentProps = {
12
12
  objectId?: string;
13
13
  validate?: DocumentValidation;
14
14
  value: (File | SavedDocumentReference)[] | undefined;
15
+ onFileRejections: (fileRejectionsErrors: string[]) => void;
15
16
  };
16
17
  export declare const Document: (props: DocumentProps) => React.JSX.Element;
17
18
  export {};
@@ -1,16 +1,32 @@
1
1
  import { isNil } from 'lodash';
2
+ import prettyBytes from 'pretty-bytes';
2
3
  import React, { useEffect, useState } from 'react';
3
4
  import { useDropzone } from 'react-dropzone';
4
5
  import { UploadCloud } from '../../../../../icons';
5
6
  import { Skeleton, Snackbar, Typography } from '../../../../core';
6
7
  import { Box, Grid } from '../../../../layout';
8
+ import { createAcceptObject } from '../../../util';
7
9
  import { getPrefixedUrl } from '../../utils';
8
10
  import { DocumentList } from './DocumentList';
9
11
  export const Document = (props) => {
10
- const { id, handleChange, property, instance, canUpdateProperty, apiServices, error, objectId, value, validate } = props;
12
+ const { id, handleChange, property, instance, canUpdateProperty, apiServices, error, objectId, value, validate, onFileRejections, } = props;
11
13
  const [documents, setDocuments] = useState();
12
14
  const [hasUpdatePermission, setHasUpdatePermission] = useState();
13
15
  const [snackbarError, setSnackbarError] = useState();
16
+ let allowedTypesMessage = '';
17
+ if (validate?.allowedFileExtensions?.length) {
18
+ if (validate.allowedFileExtensions.length === 1) {
19
+ allowedTypesMessage = validate.allowedFileExtensions[0];
20
+ }
21
+ else {
22
+ allowedTypesMessage = `${validate.allowedFileExtensions.slice(0, -1).join(', ')} or ${validate.allowedFileExtensions.slice(-1)[0]}`;
23
+ }
24
+ }
25
+ const maxSizeInBytes = Number.isFinite(validate?.maxSizeInKB)
26
+ ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
27
+ validate.maxSizeInKB * 1000 // convert to bytes
28
+ : undefined;
29
+ const formattedMaxSize = maxSizeInBytes !== undefined ? prettyBytes(maxSizeInBytes) : '';
14
30
  useEffect(() => {
15
31
  setDocuments(value);
16
32
  }, [value]);
@@ -36,15 +52,27 @@ export const Document = (props) => {
36
52
  handleChange(property.id, newDocuments);
37
53
  };
38
54
  const uploadDisabled = !!validate?.maxDocuments && (documents?.length ?? 0) >= validate.maxDocuments;
39
- const { getRootProps, getInputProps, open } = useDropzone({
55
+ const { getRootProps, getInputProps, open, fileRejections } = useDropzone({
40
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
57
  onDrop: (files) => handleUpload(files),
42
58
  disabled: uploadDisabled,
59
+ accept: validate?.allowedFileExtensions ? createAcceptObject(validate.allowedFileExtensions) : undefined,
60
+ maxSize: maxSizeInBytes,
43
61
  });
62
+ useEffect(() => {
63
+ const errors = [];
64
+ if (fileRejections.some((fileRejection) => fileRejection.errors.some((error) => error.code === 'file-invalid-type'))) {
65
+ errors.push(`Invalid file extension. Allowed extensions are: ${allowedTypesMessage}`);
66
+ }
67
+ if (fileRejections.some((fileRejection) => fileRejection.errors.some((error) => error.code === 'file-too-large'))) {
68
+ errors.push(`File size exceeds the max limit of ${formattedMaxSize}`);
69
+ }
70
+ onFileRejections(errors);
71
+ }, [fileRejections, onFileRejections]);
44
72
  return (React.createElement(React.Fragment, null,
45
73
  canUpdateProperty && hasUpdatePermission && (React.createElement(Box, { sx: {
46
74
  margin: '5px 0',
47
- height: '115px',
75
+ height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px',
48
76
  borderRadius: '8px',
49
77
  display: 'flex',
50
78
  justifyContent: 'center',
@@ -63,8 +91,20 @@ export const Document = (props) => {
63
91
  ' ',
64
92
  React.createElement(Typography, { component: 'span', sx: { color: uploadDisabled ? '#919EAB' : '#0075A7', fontSize: '14px' } }, "select file"),
65
93
  ' ',
66
- "to upload"))))),
67
- canUpdateProperty && isNil(hasUpdatePermission) && (React.createElement(Skeleton, { variant: "rectangular", height: "115px", sx: { margin: '5px 0', borderRadius: '8px' } })),
94
+ "to upload"),
95
+ allowedTypesMessage && (React.createElement(Typography, { sx: {
96
+ color: '#637381',
97
+ textAlign: 'center',
98
+ fontSize: '12px',
99
+ } }, `${allowedTypesMessage}.`)),
100
+ formattedMaxSize && (React.createElement(Typography, { sx: {
101
+ color: '#637381',
102
+ textAlign: 'center',
103
+ fontSize: '12px',
104
+ } }, validate?.maxDocuments === 1
105
+ ? `Max size of ${formattedMaxSize}.`
106
+ : `The max size of each document is ${formattedMaxSize}.`)))))),
107
+ canUpdateProperty && isNil(hasUpdatePermission) && (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })),
68
108
  React.createElement(DocumentList, { property: property, instance: instance, objectId: objectId, handleChange: handleChange, value: value, apiServices: apiServices, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission }),
69
109
  React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' })));
70
110
  };
@@ -16,6 +16,7 @@ export declare class DocumentComponent extends ReactComponent {
16
16
  */
17
17
  manageFormErrors(): void;
18
18
  handleChange: (key: string, value?: (File | SavedDocumentReference)[] | null) => void;
19
+ handleFileRejections(fileRejectionsErrors: string[]): void;
19
20
  handleValidation(value?: (File | SavedDocumentReference)[] | null): void;
20
21
  beforeSubmit(): Promise<void>;
21
22
  attachReact(element: Element): void;
@@ -26,6 +26,7 @@ export class DocumentComponent extends ReactComponent {
26
26
  };
27
27
  this.errorDetails = {};
28
28
  this.handleChange = this.handleChange.bind(this);
29
+ this.handleFileRejections = this.handleFileRejections.bind(this);
29
30
  }
30
31
  init() {
31
32
  this.on(`api-error`, (details) => {
@@ -87,6 +88,12 @@ export class DocumentComponent extends ReactComponent {
87
88
  }
88
89
  });
89
90
  }
91
+ handleFileRejections(fileRejectionsErrors) {
92
+ delete this.errorDetails['file-rejections'];
93
+ if (fileRejectionsErrors.length > 0) {
94
+ this.errorDetails['file-rejections'] = `${fileRejectionsErrors.join('; ')}.`;
95
+ }
96
+ }
90
97
  handleValidation(value) {
91
98
  const validate = this.component.validate;
92
99
  const amountOfDocuments = value?.length ?? 0;
@@ -131,6 +138,6 @@ export class DocumentComponent extends ReactComponent {
131
138
  const inputId = `${this.component.id}-input`;
132
139
  return ReactDOM.render(React.createElement("div", null,
133
140
  React.createElement(FormComponentWrapper, { ...this.component, inputId: inputId, viewOnly: !this.component.canUpdateProperty, errorMessage: this.errorMessages() },
134
- React.createElement(Document, { ...this.component, id: inputId, handleChange: this.handleChange, error: this.hasErrors(), value: this.dataValue }))), root);
141
+ React.createElement(Document, { ...this.component, id: inputId, handleChange: this.handleChange, error: this.hasErrors(), value: this.dataValue, onFileRejections: this.handleFileRejections }))), root);
135
142
  }
136
143
  }
@@ -1,6 +1,6 @@
1
- import { BackupOutlined, ClearRounded } from '@mui/icons-material';
2
1
  import React, { useEffect, useState } from 'react';
3
2
  import { useDropzone } from 'react-dropzone';
3
+ import { ClearRounded, UploadCloud } from '../../../../../icons';
4
4
  import { CardMedia, IconButton, Typography } from '../../../../core';
5
5
  import { Box, Grid } from '../../../../layout';
6
6
  export function blobToDataUrl(blob) {
@@ -84,7 +84,7 @@ export const Image = (props) => {
84
84
  React.createElement("input", { ...getInputProps({ id }), multiple: false }),
85
85
  React.createElement(Grid, { container: true, sx: { width: '100%' } },
86
86
  React.createElement(Grid, { item: true, xs: 12, sx: { display: 'flex', justifyContent: 'center', paddingBottom: '5px' } },
87
- React.createElement(BackupOutlined, { sx: { color: '#919EAB', height: '1.5em', width: '1.5em' } })),
87
+ React.createElement(UploadCloud, { sx: { color: '#919EAB', width: '50px', height: '30px' } })),
88
88
  React.createElement(Grid, { item: true, xs: 12 },
89
89
  React.createElement(Typography, { variant: "body2", sx: { color: '#212B36', textAlign: 'center' } },
90
90
  "Drag and drop or",
@@ -7,6 +7,7 @@ import sift from 'sift';
7
7
  import { Edit, TrashCan } from '../../../../../icons';
8
8
  import { Button, IconButton, Skeleton, Snackbar, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography, } from '../../../../core';
9
9
  import { Box } from '../../../../layout';
10
+ import { getReadableQuery } from '../../../CriteriaBuilder';
10
11
  import { getPrefixedUrl, normalizeDateTime, retrieveCustomErrorMessage } from '../../utils';
11
12
  import { ActionDialog } from './ActionDialog';
12
13
  import { DocumentViewerCell } from './DocumentViewerCell';
@@ -37,6 +38,7 @@ const RepeatableField = (props) => {
37
38
  const [relatedObject, setRelatedObject] = useState();
38
39
  const [hasCreateAction, setHasCreateAction] = useState(false);
39
40
  const [users, setUsers] = useState();
41
+ const [criteriaObjects, setCriteriaObjects] = useState([]);
40
42
  const [openDialog, setOpenDialog] = useState(false);
41
43
  const [dialogType, setDialogType] = useState();
42
44
  const [selectedRow, setSelectedRow] = useState();
@@ -50,9 +52,7 @@ const RepeatableField = (props) => {
50
52
  const [error, setError] = useState(false);
51
53
  const DEFAULT_CREATE_ACTION = '_create';
52
54
  const { instanceChanges } = useNotification();
53
- const fetchRelatedInstances = useCallback(async () => {
54
- if (openDialog)
55
- return;
55
+ const fetchRelatedObject = useCallback(async () => {
56
56
  let relatedObject;
57
57
  if (property.objectId) {
58
58
  try {
@@ -80,31 +80,86 @@ const RepeatableField = (props) => {
80
80
  catch (err) {
81
81
  console.error(error);
82
82
  }
83
- if (property.relatedPropertyId && instance?.id) {
84
- const filterProperty = `${property.relatedPropertyId}.id`;
85
- const filter = { where: { [filterProperty]: instance?.id }, limit: 100 };
86
- const objectId = property.objectId;
87
- try {
88
- const timeout = setTimeout(() => {
89
- setLoading(false);
90
- }, 300);
91
- setLoading(true);
92
- const instances = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances`), {
93
- params: { filter: JSON.stringify(filter) },
94
- });
95
- clearTimeout(timeout);
83
+ }
84
+ relatedObject && checkCreateAccess(relatedObject);
85
+ }, [apiServices, property]);
86
+ const fetchRelatedInstances = useCallback(async () => {
87
+ if (openDialog)
88
+ return;
89
+ if (property.objectId && property.relatedPropertyId && instance?.id) {
90
+ const filterProperty = `${property.relatedPropertyId}.id`;
91
+ const filter = { where: { [filterProperty]: instance?.id }, limit: 100 };
92
+ const objectId = property.objectId;
93
+ try {
94
+ const timeout = setTimeout(() => {
96
95
  setLoading(false);
97
- if (instances) {
98
- setRelatedInstances(instances);
99
- }
100
- }
101
- catch (error) {
102
- setError(true);
96
+ }, 300);
97
+ setLoading(true);
98
+ const instances = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances`), {
99
+ params: { filter: JSON.stringify(filter) },
100
+ });
101
+ clearTimeout(timeout);
102
+ setLoading(false);
103
+ if (instances) {
104
+ setRelatedInstances(instances);
103
105
  }
104
106
  }
107
+ catch (error) {
108
+ setError(true);
109
+ }
105
110
  }
106
- relatedObject && checkCreateAccess(relatedObject);
107
- }, [apiServices, property, viewLayout]);
111
+ }, [apiServices, property]);
112
+ const fetchCriteriaObjects = useCallback(async () => {
113
+ let objectIds = [];
114
+ const criteriaProperties = relatedObject?.properties?.filter((property) => property.type === 'criteria' && property.objectId) ?? [];
115
+ if (tableViewLayout) {
116
+ objectIds = criteriaProperties
117
+ .filter((p) => tableViewLayout.properties.some((column) => column.id === p.id))
118
+ .map((property) => property.objectId);
119
+ }
120
+ else {
121
+ objectIds = criteriaProperties.map((p) => p.objectId);
122
+ }
123
+ const objects = [];
124
+ for (const objectId of new Set(objectIds)) {
125
+ try {
126
+ const criteriaObject = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/effective`), {
127
+ params: { fields: ['id', 'name', 'properties'] },
128
+ });
129
+ objects.push(criteriaObject);
130
+ }
131
+ catch (error) {
132
+ console.error(`Error fetching criteria object with ID ${objectId}:`, error);
133
+ }
134
+ }
135
+ setCriteriaObjects(objects);
136
+ }, [apiServices, relatedObject, tableViewLayout]);
137
+ useEffect(() => {
138
+ (async () => {
139
+ try {
140
+ const users = await apiServices.get(getPrefixedUrl(`/users`));
141
+ setUsers(users);
142
+ }
143
+ catch (error) {
144
+ console.error(error);
145
+ }
146
+ })();
147
+ }, [apiServices]);
148
+ useEffect(() => {
149
+ fetchRelatedObject();
150
+ fetchRelatedInstances();
151
+ }, [fetchRelatedInstances, fetchRelatedObject, reloadOnErrorTrigger, instance]);
152
+ useEffect(() => {
153
+ if (relatedObject)
154
+ fetchCriteriaObjects();
155
+ }, [fetchCriteriaObjects, relatedObject]);
156
+ useEffect(() => {
157
+ if (relatedObject?.rootObjectId) {
158
+ const callback = () => fetchRelatedInstances();
159
+ instanceChanges?.subscribe(relatedObject?.rootObjectId, callback);
160
+ return () => instanceChanges?.unsubscribe(relatedObject?.rootObjectId, callback);
161
+ }
162
+ }, [instanceChanges, relatedObject]);
108
163
  const retrieveCriteria = (relatedObjProperty, action, object) => {
109
164
  let property;
110
165
  if (action.parameters) {
@@ -171,27 +226,6 @@ const RepeatableField = (props) => {
171
226
  });
172
227
  }
173
228
  };
174
- useEffect(() => {
175
- (async () => {
176
- try {
177
- const users = await apiServices.get(getPrefixedUrl(`/users`));
178
- setUsers(users);
179
- }
180
- catch (error) {
181
- console.error(error);
182
- }
183
- })();
184
- }, [apiServices]);
185
- useEffect(() => {
186
- fetchRelatedInstances();
187
- }, [fetchRelatedInstances, reloadOnErrorTrigger, instance]);
188
- useEffect(() => {
189
- if (relatedObject?.rootObjectId) {
190
- const callback = () => fetchRelatedInstances();
191
- instanceChanges?.subscribe(relatedObject?.rootObjectId, callback);
192
- return () => instanceChanges?.unsubscribe(relatedObject?.rootObjectId, callback);
193
- }
194
- }, [instanceChanges, relatedObject]);
195
229
  const deleteRow = (id) => {
196
230
  setDialogType('delete');
197
231
  setSelectedRow(id);
@@ -334,28 +368,24 @@ const RepeatableField = (props) => {
334
368
  };
335
369
  const getValue = (relatedInstance, propertyId, propertyType) => {
336
370
  const value = get(relatedInstance, propertyId);
337
- // If the property is not date-like then just return the
338
- // value found at the given path.
339
- if (!['date', 'date-time', 'time'].includes(propertyType)) {
340
- return value;
341
- }
342
371
  // If the date-like value is empty then there is no need to format.
343
372
  if (!value) {
344
373
  return value;
345
374
  }
346
- // At this point it has been asserted that there is a value
347
- // and since the property is date-like the value must be
348
- // a string.
349
- const stringValue = value;
350
375
  if (propertyType === 'date') {
351
- return DateTime.fromISO(stringValue).toLocaleString(DateTime.DATE_SHORT);
376
+ return DateTime.fromISO(value).toLocaleString(DateTime.DATE_SHORT);
352
377
  }
353
378
  if (propertyType === 'date-time') {
354
- return DateTime.fromISO(stringValue).toLocaleString(DateTime.DATETIME_SHORT);
379
+ return DateTime.fromISO(value).toLocaleString(DateTime.DATETIME_SHORT);
355
380
  }
356
381
  if (propertyType === 'time') {
357
- return DateTime.fromISO(stringValue).toLocaleString(DateTime.TIME_SIMPLE);
382
+ return DateTime.fromISO(value).toLocaleString(DateTime.TIME_SIMPLE);
383
+ }
384
+ if (propertyType === 'criteria' && typeof value === 'object') {
385
+ const property = relatedObject?.properties?.find((p) => p.id === propertyId);
386
+ return getReadableQuery(value, criteriaObjects.find((o) => o.id === property?.objectId)?.properties ?? []);
358
387
  }
388
+ return value;
359
389
  };
360
390
  const columns = retrieveViewLayout();
361
391
  return loading ? (React.createElement(React.Fragment, null,
@@ -6,7 +6,7 @@ import { isEqual } from 'lodash';
6
6
  import { http, HttpResponse } from 'msw';
7
7
  import { setupServer } from 'msw/node';
8
8
  import React from 'react';
9
- import { it } from 'vitest';
9
+ import { expect, it } from 'vitest';
10
10
  import Form from '../Common/Form';
11
11
  import { licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, } from './test-data';
12
12
  const removePoppers = () => {
@@ -233,6 +233,12 @@ export function convertFormToComponents(entries, parameters, object) {
233
233
  maxDocuments: parameter.type === 'document'
234
234
  ? parameter.validation?.maxDocuments
235
235
  : undefined,
236
+ allowedFileExtensions: parameter.type === 'document'
237
+ ? parameter.validation?.allowedFileExtensions
238
+ : undefined,
239
+ maxSizeInKB: parameter.type === 'document'
240
+ ? parameter.validation?.maxSizeInKB
241
+ : undefined,
236
242
  customMessage: ['integer', 'number', 'date', 'time', 'document'].includes(parameter.type ?? '')
237
243
  ? parameter.validation
238
244
  ?.errorMessage
@@ -45,7 +45,7 @@ const AddressFieldComponent = (props) => {
45
45
  setAnchorEl(null);
46
46
  };
47
47
  return (React.createElement(Box, null,
48
- !mask ? (React.createElement(TextField, { id: id, inputRef: textFieldRef, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: placeholder, InputProps: {
48
+ !mask ? (React.createElement(TextField, { id: id, inputRef: textFieldRef, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: readOnly ? undefined : placeholder, InputProps: {
49
49
  type: 'search',
50
50
  autoComplete: 'off',
51
51
  }, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, inputRef: textFieldRef, sx: readOnly
@@ -56,12 +56,12 @@ const BooleanSelect = (props) => {
56
56
  return readOnlyComponent();
57
57
  }
58
58
  return displayOption === 'dropdown' ? (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", required: strictlyTrue })) : (React.createElement(FormControl, { required: strictlyTrue, error: error, fullWidth: true },
59
- React.createElement(FormControlLabel, { labelPlacement: "end", label: labelComponent(), control: displayOption === 'switch' ? (React.createElement(Switch, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', name: property.id, checked: value, onChange: (e) => handleChange(e.target.checked), sx: {
59
+ React.createElement(FormControlLabel, { labelPlacement: "end", label: labelComponent(), sx: { marginLeft: '-8px' }, control: displayOption === 'switch' ? (React.createElement(Switch, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', name: property.id, checked: value, onChange: (e) => handleChange(e.target.checked), sx: {
60
60
  alignSelf: 'start',
61
- } })) : (React.createElement(Checkbox, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), sx: {
61
+ }, ...(additionalProps ?? {}) })) : (React.createElement(Checkbox, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), sx: {
62
62
  alignSelf: 'start',
63
63
  padding: '4px 9px 9px 9px',
64
- } })) }),
64
+ }, ...(additionalProps ?? {}) })) }),
65
65
  error && errorMessage?.length && React.createElement(FormHelperText, { sx: { marginX: 0 } }, errorMessage),
66
66
  descriptionComponent()));
67
67
  };
@@ -1,4 +1,5 @@
1
1
  import { DateTimeFormatter } from '@js-joda/core';
2
+ import { omit } from 'lodash';
2
3
  import React, { useEffect, useState } from 'react';
3
4
  import { InvalidDate, LocalDate, nativeJs } from '../../../../util';
4
5
  import { DatePicker, LocalizationProvider, TextField } from '../../../core';
@@ -36,6 +37,11 @@ const DatePickerSelect = (props) => {
36
37
  onChange && onChange(property.id, date, property);
37
38
  };
38
39
  return readOnly ? (React.createElement(InputFieldComponent, { ...{ ...props, defaultValue: asMonthDayYearFormat(value) } })) : (React.createElement(LocalizationProvider, null,
39
- React.createElement(DatePicker, { value: value, onChange: handleChange, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', ...(additionalProps ?? {}) })) })));
40
+ React.createElement(DatePicker, { value: value, onChange: handleChange, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
41
+ // merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
42
+ inputProps: {
43
+ ...params.inputProps,
44
+ ...(additionalProps?.inputProps ?? {}),
45
+ }, ...omit(additionalProps, ['inputProps']) })) })));
40
46
  };
41
47
  export default DatePickerSelect;
@@ -1,4 +1,5 @@
1
1
  import { LocalDate, LocalDateTime, LocalTime, nativeJs } from '@js-joda/core';
2
+ import { omit } from 'lodash';
2
3
  import React, { useEffect, useState } from 'react';
3
4
  import { InvalidDate } from '../../../../util';
4
5
  import { DateTimePicker, LocalizationProvider, TextField } from '../../../core';
@@ -43,6 +44,11 @@ const DateTimePickerSelect = (props) => {
43
44
  props.onChange && props.onChange(property.id, date, property);
44
45
  };
45
46
  return readOnly ? (React.createElement(InputFieldComponent, { ...{ ...props, defaultValue: formatDateTime(value) } })) : (React.createElement(LocalizationProvider, null,
46
- React.createElement(DateTimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', ...(additionalProps ?? {}) })) })));
47
+ React.createElement(DateTimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
48
+ // merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
49
+ inputProps: {
50
+ ...params.inputProps,
51
+ ...(additionalProps?.inputProps ?? {}),
52
+ }, ...omit(additionalProps, ['inputProps']) })) })));
47
53
  };
48
54
  export default DateTimePickerSelect;
@@ -36,9 +36,12 @@ const InputFieldComponent = (props) => {
36
36
  setInputValue(selectValue);
37
37
  };
38
38
  const InputProps = property.type === 'number'
39
- ? { inputComponent: NumericFormat, inputProps: { min, max, readOnly } }
39
+ ? {
40
+ inputComponent: NumericFormat,
41
+ inputProps: { min, max, readOnly, ...(additionalProps?.inputProps ?? {}) },
42
+ }
40
43
  : property.type === 'integer'
41
- ? { inputProps: { min, max } }
44
+ ? { inputProps: { min, max, ...(additionalProps?.inputProps ?? {}) } }
42
45
  : null;
43
46
  return property.enum && !readOnly ? (React.createElement(Autocomplete, { id: id,
44
47
  // note: this is different between widgets and builder
@@ -62,7 +65,7 @@ const InputFieldComponent = (props) => {
62
65
  backgroundColor: '#f4f6f8',
63
66
  },
64
67
  }),
65
- }, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, readOnly: readOnly }, required: required, fullWidth: true, onBlur: onBlur, placeholder: placeholder, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, sx: readOnly
68
+ }, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, readOnly: readOnly }, required: required, fullWidth: true, onBlur: onBlur, placeholder: readOnly ? undefined : placeholder, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, sx: readOnly
66
69
  ? {
67
70
  '& .MuiOutlinedInput-notchedOutline': {
68
71
  border: 'none',
@@ -1,4 +1,5 @@
1
1
  import { createFilterOptions, List, ListSubheader } from '@mui/material';
2
+ import { uniq } from 'lodash';
2
3
  import React, { forwardRef, useEffect, useRef, useState } from 'react';
3
4
  import { Clear } from '../../../../icons';
4
5
  import { Autocomplete, FormControl, FormControlLabel, IconButton, Radio, RadioGroup, TextField, Typography, } from '../../../core';
@@ -15,7 +16,7 @@ const Select = (props) => {
15
16
  const [isOtherFocused, setIsOtherFocused] = useState(false);
16
17
  const [value, setValue] = useState(defaultValue);
17
18
  const [inputValue, setInputValue] = useState(!selectOptions?.some((option) => (typeof option === 'string' && option === defaultValue) ||
18
- option.value === defaultValue)
19
+ option.value === defaultValue) && property.type !== 'array'
19
20
  ? defaultValue
20
21
  : '');
21
22
  const [errorState, setErrorState] = useState();
@@ -27,11 +28,14 @@ const Select = (props) => {
27
28
  otherInputRef.current.focus();
28
29
  }
29
30
  }, [isOther, value]);
31
+ useEffect(() => {
32
+ setValue(defaultValue);
33
+ }, [defaultValue]);
30
34
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
35
  const handleChange = (event, selected) => {
32
36
  if (Array.isArray(selected)) {
33
37
  const newValues = selected.map((option) => option.value ?? option);
34
- setValue(newValues);
38
+ setValue(uniq(newValues));
35
39
  onChange && onChange(property.id, newValues, property);
36
40
  }
37
41
  else {
@@ -144,6 +148,7 @@ const Select = (props) => {
144
148
  filtered.push({
145
149
  value: inputValue,
146
150
  label: `Add "${inputValue}"`,
151
+ isCustomValue: true,
147
152
  });
148
153
  }
149
154
  return filtered;
@@ -151,12 +156,19 @@ const Select = (props) => {
151
156
  ? (option, value) => isOptionEqualToValue(option, value)
152
157
  : undefined, getOptionLabel: getOptionLabel && !isCombobox
153
158
  ? (option) => getOptionLabel(option)
154
- : (option) => (typeof option === 'string' ? option : option.label), renderOption: renderOption
159
+ : (option) => {
160
+ if (typeof option === 'string')
161
+ return option;
162
+ // If the option is a custom value, return the value without the prepended "Add" text.
163
+ if (option.isCustomValue)
164
+ return option.value;
165
+ return option.label ?? '';
166
+ }, renderOption: renderOption
155
167
  ? (props, option, state) => renderOption(props, option, state)
156
- : undefined, ListboxComponent: ListboxComponent, disableCloseOnSelect: disableCloseOnSelect, sx: {
168
+ : (props, option) => React.createElement("li", { ...props }, option.label ?? option), ListboxComponent: ListboxComponent, disableCloseOnSelect: disableCloseOnSelect, sx: {
157
169
  '& button.MuiButtonBase-root': {
158
170
  visibility: 'visible',
159
171
  },
160
- }, ...(isCombobox ? { selectOnFocus: true, handleHomeEndKeys: true, freeSolo: true } : {}), ...(additionalProps ?? {}) }));
172
+ }, forcePopupIcon: true, ...(isCombobox ? { selectOnFocus: true, handleHomeEndKeys: true, freeSolo: true } : {}), ...(additionalProps ?? {}) }));
161
173
  };
162
174
  export default Select;
@@ -1,9 +1,11 @@
1
1
  import { LocalDateTime } from '@js-joda/core';
2
2
  import { TimePicker } from '@mui/x-date-pickers';
3
- import { isUndefined, padStart } from 'lodash';
3
+ import { isUndefined, omit, padStart } from 'lodash';
4
+ import { DateTime } from 'luxon';
4
5
  import React, { useEffect, useState } from 'react';
5
6
  import { InvalidDate } from '../../../../util';
6
7
  import { LocalizationProvider, TextField } from '../../../core';
8
+ import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
7
9
  const TimePickerSelect = (props) => {
8
10
  const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, placeholder, additionalProps, } = props;
9
11
  const values = defaultValue ? defaultValue.split(':') : undefined;
@@ -39,7 +41,15 @@ const TimePickerSelect = (props) => {
39
41
  props.onChange && props.onChange(property.id, date, property);
40
42
  }
41
43
  };
42
- return (React.createElement(LocalizationProvider, null,
43
- React.createElement(TimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', placeholder: placeholder, readOnly: readOnly, ...(additionalProps ?? {}) })), readOnly: readOnly })));
44
+ return readOnly ? (React.createElement(InputFieldComponent, { ...{
45
+ ...props,
46
+ defaultValue: value instanceof LocalDateTime ? DateTime.fromISO(value.toString()).toFormat('hh:mm a') : '',
47
+ } })) : (React.createElement(LocalizationProvider, null,
48
+ React.createElement(TimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', placeholder: placeholder,
49
+ // merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
50
+ inputProps: {
51
+ ...params.inputProps,
52
+ ...(additionalProps?.inputProps ?? {}),
53
+ }, ...omit(additionalProps, ['inputProps']) })), readOnly: readOnly })));
44
54
  };
45
55
  export default TimePickerSelect;