@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,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 {};
@@ -0,0 +1,113 @@
1
+ import { useApiServices } from '@evoke-platform/context';
2
+ import { isNil } from 'lodash';
3
+ import prettyBytes from 'pretty-bytes';
4
+ import React, { useEffect, useState } from 'react';
5
+ import { useDropzone } from 'react-dropzone';
6
+ import { InfoRounded, UploadCloud } from '../../../../../../icons';
7
+ import { useFormContext } from '../../../../../../theme/hooks';
8
+ import { Skeleton, Snackbar, Typography } from '../../../../../core';
9
+ import { Box, Grid } from '../../../../../layout';
10
+ import { createAcceptObject } from '../../../../util';
11
+ import { getPrefixedUrl } from '../../utils';
12
+ import { DocumentList } from './DocumentList';
13
+ export const Document = (props) => {
14
+ const { id, handleChange, canUpdateProperty, error, instance, value, validate, hasDescription } = props;
15
+ const apiServices = useApiServices();
16
+ const { fetchedOptions, setFetchedOptions, object } = useFormContext();
17
+ const [snackbarError, setSnackbarError] = useState();
18
+ const [documents, setDocuments] = useState();
19
+ const [hasUpdatePermission, setHasUpdatePermission] = useState(fetchedOptions[`${id}UpdatePermission`]);
20
+ let allowedTypesMessage = '';
21
+ if (validate?.allowedFileExtensions?.length) {
22
+ if (validate.allowedFileExtensions.length === 1) {
23
+ allowedTypesMessage = validate.allowedFileExtensions[0];
24
+ }
25
+ else {
26
+ allowedTypesMessage = `${validate.allowedFileExtensions.slice(0, -1).join(', ')} or ${validate.allowedFileExtensions.slice(-1)[0]}`;
27
+ }
28
+ }
29
+ const maxSizeInBytes = Number.isFinite(validate?.maxSizeInKB)
30
+ ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
31
+ validate.maxSizeInKB * 1000 // convert to bytes
32
+ : undefined;
33
+ const formattedMaxSize = maxSizeInBytes !== undefined ? prettyBytes(maxSizeInBytes) : '';
34
+ useEffect(() => {
35
+ setDocuments(value);
36
+ }, [value]);
37
+ useEffect(() => {
38
+ checkPermissions();
39
+ }, [object]);
40
+ const checkPermissions = () => {
41
+ if (canUpdateProperty && !fetchedOptions[`${id}UpdatePermission`]) {
42
+ apiServices
43
+ .get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=update`))
44
+ .then((accessCheck) => {
45
+ setFetchedOptions({
46
+ [`${id}UpdatePermission`]: accessCheck.result,
47
+ });
48
+ setHasUpdatePermission(accessCheck.result);
49
+ });
50
+ }
51
+ };
52
+ const handleUpload = async (files) => {
53
+ const newDocuments = [...(documents ?? []), ...(files ?? [])];
54
+ setDocuments(newDocuments);
55
+ handleChange(id, newDocuments);
56
+ };
57
+ const uploadDisabled = !!validate?.maxDocuments && (documents?.length ?? 0) >= validate.maxDocuments;
58
+ const { getRootProps, getInputProps, open, fileRejections } = useDropzone({
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ onDrop: (files) => handleUpload(files),
61
+ disabled: uploadDisabled,
62
+ accept: validate?.allowedFileExtensions ? createAcceptObject(validate.allowedFileExtensions) : undefined,
63
+ maxSize: maxSizeInBytes,
64
+ });
65
+ const errors = [];
66
+ if (fileRejections.some((fileRejection) => fileRejection.errors.some((error) => error.code === 'file-invalid-type'))) {
67
+ errors.push(`Invalid file extension. Allowed extensions are: ${allowedTypesMessage}`);
68
+ }
69
+ if (fileRejections.some((fileRejection) => fileRejection.errors.some((error) => error.code === 'file-too-large'))) {
70
+ errors.push(`File size exceeds the max limit of ${formattedMaxSize}`);
71
+ }
72
+ return (React.createElement(React.Fragment, null,
73
+ canUpdateProperty && hasUpdatePermission && (React.createElement(Box, { sx: {
74
+ margin: '5px 0',
75
+ height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px',
76
+ borderRadius: '8px',
77
+ display: 'flex',
78
+ justifyContent: 'center',
79
+ alignItems: 'center',
80
+ border: `1px dashed ${error ? 'red' : uploadDisabled ? '#DFE3E8' : '#858585'}`,
81
+ position: 'relative',
82
+ cursor: uploadDisabled ? 'cursor' : 'pointer',
83
+ }, ...getRootProps(), onClick: open },
84
+ React.createElement("input", { ...getInputProps({ id }), disabled: uploadDisabled, ...(hasDescription ? { 'aria-describedby': `${id}-description` } : undefined) }),
85
+ React.createElement(Grid, { container: true, sx: { width: '100%' } },
86
+ React.createElement(Grid, { item: true, xs: 12, sx: { display: 'flex', justifyContent: 'center', paddingBottom: '7px' } },
87
+ React.createElement(UploadCloud, { sx: { color: '#919EAB', width: '50px', height: '30px' } })),
88
+ React.createElement(Grid, { item: true, xs: 12 },
89
+ React.createElement(Typography, { variant: "body2", sx: { color: uploadDisabled ? '#919EAB' : '#212B36', textAlign: 'center' } },
90
+ "Drag and drop or",
91
+ ' ',
92
+ React.createElement(Typography, { component: 'span', color: uploadDisabled ? '#919EAB' : 'primary', sx: { fontSize: '14px' } }, "select file"),
93
+ ' ',
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' } })),
108
+ React.createElement(DocumentList, { id: id, instance: instance, handleChange: handleChange, value: value, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission }),
109
+ React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' }),
110
+ errors.length > 0 && (React.createElement(Box, { display: 'flex', alignItems: 'center' },
111
+ React.createElement(InfoRounded, { color: 'error', sx: { fontSize: '.75rem', marginRight: '3px' } }),
112
+ React.createElement(Typography, { fontSize: '12px', color: 'error', sx: { lineHeight: '18px' } }, errors.join('; ') + '.')))));
113
+ };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { FieldValues } from 'react-hook-form';
3
+ import { SavedDocumentReference } from '../../types';
4
+ type DocumentListProps = {
5
+ handleChange: (propertyId: string, value: (File | SavedDocumentReference)[] | undefined) => void;
6
+ id: string;
7
+ instance?: FieldValues;
8
+ canUpdateProperty: boolean;
9
+ value: (File | SavedDocumentReference)[] | undefined;
10
+ setSnackbarError: (type: 'error' | 'success', message: string) => void;
11
+ };
12
+ export declare const DocumentList: (props: DocumentListProps) => React.JSX.Element;
13
+ export {};
@@ -0,0 +1,179 @@
1
+ import { useApiServices } from '@evoke-platform/context';
2
+ import { isEqual } from 'lodash';
3
+ import prettyBytes from 'pretty-bytes';
4
+ import React, { useEffect, useState } from 'react';
5
+ import { FileWithExtension, LaunchRounded, TrashCan, WarningRounded } from '../../../../../../icons';
6
+ import { useFormContext } from '../../../../../../theme/hooks';
7
+ import { Chip, IconButton, Typography } from '../../../../../core';
8
+ import { Box, Grid } from '../../../../../layout';
9
+ import { getPrefixedUrl } from '../../utils';
10
+ const styles = {
11
+ icon: {
12
+ padding: '3px',
13
+ color: '#637381',
14
+ },
15
+ };
16
+ const viewableFileTypes = [
17
+ 'application/pdf',
18
+ 'image/jpeg',
19
+ 'image/jpg',
20
+ 'image/png',
21
+ 'image/gif',
22
+ 'image/bmp',
23
+ 'image/webp',
24
+ 'text/plain',
25
+ ];
26
+ export const DocumentList = (props) => {
27
+ const { handleChange, id, canUpdateProperty, instance, value: documents, setSnackbarError } = props;
28
+ const apiServices = useApiServices();
29
+ const { fetchedOptions, setFetchedOptions, object } = useFormContext();
30
+ const [hasViewPermission, setHasViewPermission] = useState(fetchedOptions[`${id}ViewPermission`] ?? true);
31
+ const [savedDocuments, setSavedDocuments] = useState(fetchedOptions[`${id}SavedDocuments`]);
32
+ useEffect(() => {
33
+ const currentValue = instance?.[id];
34
+ if (currentValue?.length) {
35
+ const currentDocumentIds = currentValue.map((doc) => doc.id);
36
+ if (currentDocumentIds.length &&
37
+ // these need to be sorted otherwise it will evaluate as not equal if the ids are in different orders causing unnecessary fetches
38
+ !isEqual(currentDocumentIds.slice().sort(), savedDocuments
39
+ ?.map((doc) => doc.id)
40
+ .slice()
41
+ .sort())) {
42
+ getDocuments(currentDocumentIds);
43
+ }
44
+ }
45
+ }, [id, documents, object]);
46
+ useEffect(() => {
47
+ if (fetchedOptions[`${id}SavedDocuments`]) {
48
+ setSavedDocuments(fetchedOptions[`${id}SavedDocuments`]);
49
+ }
50
+ }, [fetchedOptions]);
51
+ const getDocuments = (currentDocumentIds, shouldRetry = true) => {
52
+ apiServices.get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents`), {
53
+ params: { filter: { where: { id: { inq: currentDocumentIds } } } },
54
+ }, (error, docs) => {
55
+ // There is a short delay between when a document is uploaded and when
56
+ // it is indexed. Therefore, try again if documents are not found.
57
+ if (shouldRetry &&
58
+ (!docs || currentDocumentIds.some((docId) => !docs.find((doc) => docId === doc.id)))) {
59
+ setTimeout(() => getDocuments(currentDocumentIds, false), 2000);
60
+ }
61
+ else if (error) {
62
+ setSnackbarError('error', 'Error occurred while retrieving saved documents');
63
+ }
64
+ else {
65
+ setSavedDocuments(docs);
66
+ setFetchedOptions({
67
+ [`${id}SavedDocuments`]: docs,
68
+ });
69
+ }
70
+ });
71
+ };
72
+ useEffect(() => {
73
+ if (!fetchedOptions[`${id}ViewPermission`]) {
74
+ checkPermissions();
75
+ }
76
+ }, [object]);
77
+ const checkPermissions = () => {
78
+ if (instance?.[id]?.length) {
79
+ apiServices
80
+ .get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=view`))
81
+ .then((viewPermissionCheck) => {
82
+ setFetchedOptions({
83
+ [`${id}ViewPermission`]: viewPermissionCheck.result,
84
+ });
85
+ setHasViewPermission(viewPermissionCheck.result);
86
+ });
87
+ }
88
+ };
89
+ const isFile = (doc) => doc instanceof File;
90
+ const fileExists = (doc) => savedDocuments?.find((d) => d.id === doc.id);
91
+ const handleRemove = (index) => {
92
+ const updatedDocuments = documents?.filter((_, i) => i !== index) ?? [];
93
+ handleChange(id, updatedDocuments.length === 0 ? undefined : updatedDocuments);
94
+ };
95
+ const openDocument = async (index) => {
96
+ const doc = documents?.[index];
97
+ if (doc) {
98
+ let url;
99
+ const contentType = doc instanceof File
100
+ ? doc.type
101
+ : savedDocuments?.find((savedDocument) => savedDocument.id === doc.id)?.contentType;
102
+ if (!isFile(doc)) {
103
+ try {
104
+ const documentResponse = await apiServices.get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/${doc.id}/content`), { responseType: 'blob' });
105
+ const blob = new Blob([documentResponse], { type: contentType });
106
+ url = window.URL.createObjectURL(blob);
107
+ }
108
+ catch (error) {
109
+ setSnackbarError('error', `Could not open ${doc.name}`);
110
+ return;
111
+ }
112
+ }
113
+ else {
114
+ url = URL.createObjectURL(doc);
115
+ }
116
+ if (contentType && viewableFileTypes.includes(contentType)) {
117
+ window.open(url, '_blank');
118
+ }
119
+ else {
120
+ const link = document.createElement('a');
121
+ link.href = url;
122
+ link.setAttribute('download', doc.name);
123
+ document.body.appendChild(link);
124
+ link.click();
125
+ // Clean up and remove the link
126
+ link.parentNode?.removeChild(link);
127
+ }
128
+ }
129
+ };
130
+ const getDocumentSize = (doc) => {
131
+ let size;
132
+ if (isFile(doc)) {
133
+ size = prettyBytes(doc.size);
134
+ }
135
+ else {
136
+ const savedDoc = savedDocuments?.find((savedDocument) => savedDocument.id === doc.id);
137
+ size = savedDoc ? prettyBytes(savedDoc.size) : '';
138
+ }
139
+ return size;
140
+ };
141
+ return (React.createElement(React.Fragment, null,
142
+ !documents && !canUpdateProperty && (React.createElement(Typography, { variant: "body2", sx: { color: '#637381' } }, "No documents")),
143
+ !!documents?.length && (React.createElement(Box, null, documents.map((doc, index) => (React.createElement(Grid, { container: true, sx: {
144
+ width: '100%',
145
+ border: '1px solid #C4CDD5',
146
+ borderRadius: '6px',
147
+ margin: '5px 2px',
148
+ padding: ' 8px',
149
+ display: 'flex',
150
+ alignItems: 'center',
151
+ } },
152
+ React.createElement(Grid, { item: true, sx: { display: 'flex', justifyContent: 'center', padding: '7px', marginLeft: '4px' } },
153
+ React.createElement(FileWithExtension, { fontFamily: "Arial", fileExtension: doc.name?.split('.')?.pop() ?? '', sx: { height: '1.5em', width: '1.5em' } })),
154
+ React.createElement(Grid, { item: true, sx: { flex: 1, justifyContent: 'center', paddingBottom: '5px' } },
155
+ React.createElement(Grid, { item: true, xs: 12 },
156
+ React.createElement(Typography, { sx: {
157
+ fontSize: '14px',
158
+ fontWeight: 700,
159
+ lineHeight: '15px',
160
+ paddingTop: '8px',
161
+ } }, doc.name)),
162
+ React.createElement(Grid, { item: true, xs: 12 },
163
+ React.createElement(Typography, { sx: { fontSize: '12px', color: '#637381' } }, getDocumentSize(doc)))),
164
+ (isFile(doc) || (hasViewPermission && !isFile(doc) && fileExists(doc))) && (React.createElement(Grid, { item: true },
165
+ React.createElement(IconButton, { "aria-label": "open document", sx: { ...styles.icon, marginRight: '16px' }, onClick: () => openDocument(index) },
166
+ React.createElement(LaunchRounded, { sx: { color: '#637381', fontSize: '22px' } })))),
167
+ !isFile(doc) && savedDocuments && !fileExists(doc) && (React.createElement(Chip, { label: "Deleted", sx: {
168
+ marginRight: '16px',
169
+ backgroundColor: 'rgba(222, 48, 36, 0.16)',
170
+ color: '#A91813',
171
+ borderRadius: '6px',
172
+ height: '25px',
173
+ fontWeight: 700,
174
+ '& .MuiChip-icon': { color: '#A91813', width: '.8em', marginBottom: '2px' },
175
+ }, icon: React.createElement(WarningRounded, null) })),
176
+ canUpdateProperty && (React.createElement(Grid, { item: true },
177
+ React.createElement(IconButton, { "aria-label": "delete document", sx: styles.icon, onClick: () => handleRemove(index) },
178
+ React.createElement(TrashCan, { sx: { ':hover': { color: '#A12723' } } })))))))))));
179
+ };
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ export declare function blobToDataUrl(blob: Blob): Promise<string>;
3
+ type ImageProps = {
4
+ id: string;
5
+ handleChange: (propertyId: string, value: string | null) => void;
6
+ canUpdateProperty?: boolean;
7
+ error?: boolean;
8
+ value?: string;
9
+ hasDescription?: boolean;
10
+ };
11
+ export declare const Image: (props: ImageProps) => React.JSX.Element;
12
+ export {};
@@ -0,0 +1,108 @@
1
+ import { BackupOutlined, ClearRounded } from '@mui/icons-material';
2
+ import { CardMedia } from '@mui/material';
3
+ import React, { useEffect, useState } from 'react';
4
+ import { useDropzone } from 'react-dropzone';
5
+ import { IconButton, Typography } from '../../../../core';
6
+ import { Box, Grid } from '../../../../layout';
7
+ export function blobToDataUrl(blob) {
8
+ const reader = new FileReader();
9
+ return new Promise((resolve) => {
10
+ reader.onloadend = () => resolve(reader.result);
11
+ reader.readAsDataURL(blob);
12
+ });
13
+ }
14
+ const styles = {
15
+ imageContainer: {
16
+ margin: '5px 0',
17
+ height: '160px',
18
+ borderRadius: '8px',
19
+ maxWidth: '100%',
20
+ },
21
+ dropzoneContainer: {
22
+ margin: '5px 0',
23
+ height: '160px',
24
+ borderRadius: '8px',
25
+ display: 'flex',
26
+ justifyContent: 'center',
27
+ alignItems: 'center',
28
+ border: '1px dashed #858585',
29
+ position: 'relative',
30
+ cursor: 'pointer',
31
+ },
32
+ icon: {
33
+ color: '#fff',
34
+ zIndex: 40,
35
+ fontSize: '16px',
36
+ },
37
+ deleteIcon: {
38
+ borderRadius: '50%',
39
+ padding: '3px',
40
+ backgroundColor: '#212B36',
41
+ ':hover': { backgroundColor: '#212B36', cursor: 'pointer' },
42
+ color: '#fff',
43
+ right: '29px',
44
+ bottom: '138px',
45
+ },
46
+ image: {
47
+ borderRadius: '8px',
48
+ width: 'fit-content',
49
+ maxWidth: '95%',
50
+ height: '160px',
51
+ position: 'relative',
52
+ display: 'inline-block',
53
+ objectFit: 'contain',
54
+ },
55
+ };
56
+ export const Image = (props) => {
57
+ const { id, handleChange, canUpdateProperty, error, value, hasDescription } = props;
58
+ const [image, setImage] = useState();
59
+ useEffect(() => {
60
+ if (typeof value === 'string') {
61
+ setImage(value);
62
+ }
63
+ }, [value]);
64
+ const handleUpload = async (file) => {
65
+ if (file?.size && file.size <= 300000) {
66
+ const dataUrl = file ? await blobToDataUrl(file) : null;
67
+ setImage(dataUrl);
68
+ handleChange(id, dataUrl);
69
+ }
70
+ };
71
+ const handleRemove = (e) => {
72
+ setImage(null);
73
+ handleChange(id, '');
74
+ e.stopPropagation();
75
+ };
76
+ const { getRootProps, getInputProps, open } = useDropzone({
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ onDrop: (files) => handleUpload(files?.[0]),
79
+ accept: { 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.svg'] },
80
+ });
81
+ return (React.createElement(React.Fragment, null, image ? (React.createElement(Box, { sx: styles.imageContainer },
82
+ React.createElement(Box, { sx: { position: 'relative', left: 0, zIndex: 5 } },
83
+ React.createElement(CardMedia, { component: "img", image: image, alt: 'Uploaded Image', sx: styles.image }),
84
+ canUpdateProperty && (React.createElement(IconButton, { onClick: handleRemove, "aria-label": "Remove image", sx: styles.deleteIcon },
85
+ React.createElement(ClearRounded, { sx: styles.icon })))))) : canUpdateProperty ? (React.createElement(Box, { sx: {
86
+ ...styles.dropzoneContainer,
87
+ borderColor: error ? 'red' : '#858585',
88
+ }, ...getRootProps(), onClick: open },
89
+ React.createElement("input", { ...getInputProps({ id }), multiple: false, ...(hasDescription ? { 'aria-describedby': `${id}-description` } : undefined) }),
90
+ React.createElement(Grid, { container: true, sx: { width: '100%' } },
91
+ React.createElement(Grid, { item: true, xs: 12, sx: {
92
+ display: 'flex',
93
+ justifyContent: 'center',
94
+ paddingBottom: '5px',
95
+ } },
96
+ React.createElement(BackupOutlined, { sx: { color: '#919EAB', height: '1.5em', width: '1.5em' } })),
97
+ React.createElement(Grid, { item: true, xs: 12 },
98
+ React.createElement(Typography, { variant: "body2", sx: { textAlign: 'center' } },
99
+ "Drag and drop or",
100
+ ' ',
101
+ React.createElement(Typography, { component: 'span', color: 'primary', sx: { fontSize: '14px' } }, "select a file"),
102
+ ' ',
103
+ "to upload")),
104
+ React.createElement(Grid, { item: true, xs: 12 },
105
+ React.createElement(Typography, { variant: "body2", sx: { color: '#637381', textAlign: 'center' } }, "Max file size of 300KB")),
106
+ React.createElement(Grid, { item: true, xs: 12 },
107
+ React.createElement(Typography, { variant: "body2", sx: { color: '#637381', textAlign: 'center' } }, "JPG, PNG, or GIF"))))) : (React.createElement(Typography, { variant: "body2", sx: { color: '#637381' } }, "No image"))));
108
+ };
@@ -0,0 +1,16 @@
1
+ import { UserAccount } from '@evoke-platform/context';
2
+ import React from 'react';
3
+ export type UserPropertyProps = {
4
+ id: string;
5
+ handleChange: (id: string, user: UserAccount | undefined) => void;
6
+ error?: boolean;
7
+ value?: {
8
+ id: string;
9
+ name: string;
10
+ };
11
+ fieldHeight?: 'small' | 'medium';
12
+ readOnly?: boolean;
13
+ hasDescription?: boolean;
14
+ };
15
+ declare const UserProperty: (props: UserPropertyProps) => React.JSX.Element;
16
+ export default UserProperty;