@evoke-platform/ui-components 1.6.0-testing.13 → 1.6.0-testing.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
- package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +3 -3
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +7 -1
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +7 -1
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -3
- package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +13 -3
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +19 -0
- package/dist/published/components/custom/FormV2/FormRenderer.js +183 -0
- package/dist/published/components/custom/FormV2/components/AccordionSections.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/AccordionSections.js +131 -0
- package/dist/published/components/custom/FormV2/components/ActionButtons.d.ts +19 -0
- package/dist/published/components/custom/FormV2/components/ActionButtons.js +106 -0
- package/dist/published/components/custom/FormV2/components/FieldWrapper.d.ts +24 -0
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +100 -0
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormContext.js +8 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +17 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +50 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +14 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +88 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.d.ts +13 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.js +140 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.d.ts +17 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +233 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +40 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +95 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +526 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +93 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +73 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +13 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +179 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +108 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +129 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.d.ts +15 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +226 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +439 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +29 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +74 -0
- package/dist/published/components/custom/FormV2/components/FormSections.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/FormSections.js +104 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +209 -0
- package/dist/published/components/custom/FormV2/components/TabNav.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/TabNav.js +23 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.d.ts +3 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.js +176 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +45 -0
- package/dist/published/components/custom/FormV2/components/types.d.ts +131 -0
- package/dist/published/components/custom/FormV2/components/types.js +1 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +47 -0
- package/dist/published/components/custom/FormV2/components/utils.js +434 -0
- package/dist/published/components/custom/FormV2/index.d.ts +1 -0
- package/dist/published/components/custom/FormV2/index.js +1 -0
- package/dist/published/components/custom/index.d.ts +1 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +2 -2
- package/dist/published/index.js +2 -2
- package/dist/published/theme/hooks.d.ts +7 -0
- package/dist/published/theme/hooks.js +9 -0
- package/package.json +4 -2
package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useApiServices } from '@evoke-platform/context';
|
|
2
|
+
import { isNil } from 'lodash';
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { useDropzone } from 'react-dropzone';
|
|
5
|
+
import { UploadCloud } from '../../../../../../icons';
|
|
6
|
+
import { useFormContext } from '../../../../../../theme/hooks';
|
|
7
|
+
import { Skeleton, Snackbar, Typography } from '../../../../../core';
|
|
8
|
+
import { Box, Grid } from '../../../../../layout';
|
|
9
|
+
import { getPrefixedUrl } from '../../utils';
|
|
10
|
+
import { DocumentList } from './DocumentList';
|
|
11
|
+
export const Document = (props) => {
|
|
12
|
+
const { id, handleChange, canUpdateProperty, error, instance, value, validate, hasDescription } = props;
|
|
13
|
+
const apiServices = useApiServices();
|
|
14
|
+
const { fetchedOptions, setFetchedOptions, object } = useFormContext();
|
|
15
|
+
const [snackbarError, setSnackbarError] = useState();
|
|
16
|
+
const [documents, setDocuments] = useState();
|
|
17
|
+
const [hasUpdatePermission, setHasUpdatePermission] = useState(fetchedOptions[`${id}UpdatePermission`]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setDocuments(value);
|
|
20
|
+
}, [value]);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
checkPermissions();
|
|
23
|
+
}, [object]);
|
|
24
|
+
const checkPermissions = () => {
|
|
25
|
+
if (canUpdateProperty && !fetchedOptions[`${id}UpdatePermission`]) {
|
|
26
|
+
apiServices
|
|
27
|
+
.get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=update`))
|
|
28
|
+
.then((accessCheck) => {
|
|
29
|
+
setFetchedOptions({
|
|
30
|
+
[`${id}UpdatePermission`]: accessCheck.result,
|
|
31
|
+
});
|
|
32
|
+
setHasUpdatePermission(accessCheck.result);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const handleUpload = async (files) => {
|
|
37
|
+
const newDocuments = [...(documents ?? []), ...(files ?? [])];
|
|
38
|
+
setDocuments(newDocuments);
|
|
39
|
+
handleChange(id, newDocuments);
|
|
40
|
+
};
|
|
41
|
+
const uploadDisabled = !!validate?.maxDocuments && (documents?.length ?? 0) >= validate.maxDocuments;
|
|
42
|
+
const { getRootProps, getInputProps, open } = useDropzone({
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
onDrop: (files) => handleUpload(files),
|
|
45
|
+
disabled: uploadDisabled,
|
|
46
|
+
});
|
|
47
|
+
return (React.createElement(React.Fragment, null,
|
|
48
|
+
canUpdateProperty && hasUpdatePermission && (React.createElement(Box, { sx: {
|
|
49
|
+
margin: '5px 0',
|
|
50
|
+
height: '115px',
|
|
51
|
+
borderRadius: '8px',
|
|
52
|
+
display: 'flex',
|
|
53
|
+
justifyContent: 'center',
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
border: `1px dashed ${error ? 'red' : uploadDisabled ? '#DFE3E8' : '#858585'}`,
|
|
56
|
+
position: 'relative',
|
|
57
|
+
cursor: uploadDisabled ? 'cursor' : 'pointer',
|
|
58
|
+
}, ...getRootProps(), onClick: open },
|
|
59
|
+
React.createElement("input", { ...getInputProps({ id }), disabled: uploadDisabled, ...(hasDescription ? { 'aria-describedby': `${id}-description` } : undefined) }),
|
|
60
|
+
React.createElement(Grid, { container: true, sx: { width: '100%' } },
|
|
61
|
+
React.createElement(Grid, { item: true, xs: 12, sx: { display: 'flex', justifyContent: 'center', paddingBottom: '7px' } },
|
|
62
|
+
React.createElement(UploadCloud, { sx: { color: '#919EAB', width: '50px', height: '30px' } })),
|
|
63
|
+
React.createElement(Grid, { item: true, xs: 12 },
|
|
64
|
+
React.createElement(Typography, { variant: "body2", sx: { color: uploadDisabled ? '#919EAB' : '#212B36', textAlign: 'center' } },
|
|
65
|
+
"Drag and drop or",
|
|
66
|
+
' ',
|
|
67
|
+
React.createElement(Typography, { component: 'span', color: uploadDisabled ? '#919EAB' : 'primary', sx: { fontSize: '14px' } }, "select file"),
|
|
68
|
+
' ',
|
|
69
|
+
"to upload"))))),
|
|
70
|
+
canUpdateProperty && isNil(hasUpdatePermission) && (React.createElement(Skeleton, { variant: "rectangular", height: "115px", sx: { margin: '5px 0', borderRadius: '8px' } })),
|
|
71
|
+
React.createElement(DocumentList, { id: id, instance: instance, handleChange: handleChange, value: value, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission }),
|
|
72
|
+
React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' })));
|
|
73
|
+
};
|
|
@@ -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;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useApiServices } from '@evoke-platform/context';
|
|
2
|
+
import { ExpandMore } from '@mui/icons-material';
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { useFormContext } from '../../../../../theme/hooks';
|
|
5
|
+
import { Autocomplete, Paper, TextField, Typography } from '../../../../core';
|
|
6
|
+
import { getPrefixedUrl, isOptionEqualToValue } from '../utils';
|
|
7
|
+
const UserProperty = (props) => {
|
|
8
|
+
const { id, handleChange, error, value, fieldHeight, readOnly, hasDescription } = props;
|
|
9
|
+
const { fetchedOptions, setFetchedOptions } = useFormContext();
|
|
10
|
+
const [loadingOptions, setLoadingOptions] = useState(false);
|
|
11
|
+
const apiServices = useApiServices();
|
|
12
|
+
const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
|
|
13
|
+
const [openOptions, setOpenOptions] = useState(false);
|
|
14
|
+
const [users, setUsers] = useState();
|
|
15
|
+
const [userValue, setUserValue] = useState();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (value && typeof value == 'object' && 'name' in value && 'id' in value) {
|
|
18
|
+
setUserValue({ label: value.name, value: value.id });
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
setUserValue(undefined);
|
|
22
|
+
}
|
|
23
|
+
}, [value]);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!fetchedOptions[`${id}Options`]) {
|
|
26
|
+
setLoadingOptions(true);
|
|
27
|
+
apiServices.get(getPrefixedUrl(`/users`), (error, userList) => {
|
|
28
|
+
setUsers(userList);
|
|
29
|
+
setOptions((userList ?? []).map((user) => ({
|
|
30
|
+
label: user.name,
|
|
31
|
+
value: user.id,
|
|
32
|
+
})));
|
|
33
|
+
setFetchedOptions({
|
|
34
|
+
[`${id}Options`]: (userList ?? []).map((user) => ({
|
|
35
|
+
label: user.name,
|
|
36
|
+
value: user.id,
|
|
37
|
+
})),
|
|
38
|
+
});
|
|
39
|
+
setLoadingOptions(false);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}, [id]);
|
|
43
|
+
function handleChangeUserProperty(id, value) {
|
|
44
|
+
const updatedValue = typeof value.value === 'string' ? { name: value.label, id: value.value } : undefined;
|
|
45
|
+
handleChange(id, updatedValue);
|
|
46
|
+
}
|
|
47
|
+
return (options && (React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, popupIcon: userValue || readOnly ? '' : React.createElement(ExpandMore, null), PaperComponent: ({ children }) => {
|
|
48
|
+
return (React.createElement(Paper, { sx: {
|
|
49
|
+
borderRadius: '12px',
|
|
50
|
+
boxShadow: '0px 24px 48px 0px rgba(145, 158, 171, 0.2)',
|
|
51
|
+
'& .MuiAutocomplete-listbox': {
|
|
52
|
+
maxHeight: '25vh',
|
|
53
|
+
},
|
|
54
|
+
'& .MuiAutocomplete-noOptions': {
|
|
55
|
+
fontFamily: 'sans-serif',
|
|
56
|
+
fontSize: '14px',
|
|
57
|
+
paddingLeft: '24px',
|
|
58
|
+
color: 'rgba(145, 158, 171, 1)',
|
|
59
|
+
},
|
|
60
|
+
'& .MuiAutocomplete-loading': {
|
|
61
|
+
fontFamily: 'sans-serif',
|
|
62
|
+
fontSize: '14px',
|
|
63
|
+
paddingLeft: '24px',
|
|
64
|
+
color: 'rgba(145, 158, 171, 1)',
|
|
65
|
+
},
|
|
66
|
+
} }, children));
|
|
67
|
+
}, sx: {
|
|
68
|
+
'& button.MuiButtonBase-root': {
|
|
69
|
+
...(!loadingOptions && value ? { visibility: 'visible' } : {}),
|
|
70
|
+
},
|
|
71
|
+
'.MuiAutocomplete-clearIndicator': {
|
|
72
|
+
...(!value ? { display: 'none' } : undefined),
|
|
73
|
+
},
|
|
74
|
+
}, noOptionsText: 'No options available', renderOption: (props, option) => {
|
|
75
|
+
return (React.createElement("li", { ...props, key: option.id },
|
|
76
|
+
React.createElement(Typography, { sx: { marginLeft: '8px', fontSize: '14px' } },
|
|
77
|
+
option.label,
|
|
78
|
+
" ",
|
|
79
|
+
'',
|
|
80
|
+
users?.find((user) => option.value === user.id)?.status === 'Inactive' ? (React.createElement("span", null, "(Inactive)")) : (''))));
|
|
81
|
+
}, onOpen: () => {
|
|
82
|
+
setOpenOptions(true);
|
|
83
|
+
}, onClose: () => setOpenOptions(false), value: userValue ?? '', options: options, getOptionLabel: (option) => {
|
|
84
|
+
if (typeof option === 'string') {
|
|
85
|
+
return options.find((o) => o.value === option)?.label ?? '';
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
{
|
|
89
|
+
if (users?.find((user) => option.value === user.id)?.status === 'Inactive') {
|
|
90
|
+
return option.label + ' (Inactive)';
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
return option.label ?? '';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}, onKeyDownCapture: (e) => {
|
|
98
|
+
// prevents keyboard trap
|
|
99
|
+
if (e.key === 'Tab') {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (value) {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
}
|
|
105
|
+
}, loading: loadingOptions, isOptionEqualToValue: isOptionEqualToValue, onChange: (event, value) => {
|
|
106
|
+
handleChangeUserProperty(id, value);
|
|
107
|
+
}, renderInput: (params) => (React.createElement(TextField, { ...params, placeholder: !readOnly ? 'Select' : undefined, readOnly: !loadingOptions && !value && readOnly, inputProps: {
|
|
108
|
+
...params.inputProps,
|
|
109
|
+
...(hasDescription ? { 'aria-describedby': `${id}-description` } : undefined),
|
|
110
|
+
}, sx: {
|
|
111
|
+
...(!loadingOptions && value
|
|
112
|
+
? {
|
|
113
|
+
'.MuiOutlinedInput-root': {
|
|
114
|
+
background: 'white',
|
|
115
|
+
border: 'auto',
|
|
116
|
+
},
|
|
117
|
+
'& fieldset': { borderColor: 'auto' },
|
|
118
|
+
'&:hover .MuiOutlinedInput-notchedOutline': {
|
|
119
|
+
border: 'auto',
|
|
120
|
+
},
|
|
121
|
+
caretColor: 'white',
|
|
122
|
+
'& svg': {
|
|
123
|
+
display: readOnly ? 'none' : 'block',
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
127
|
+
} })), size: fieldHeight ?? 'medium', readOnly: readOnly, error: error })));
|
|
128
|
+
};
|
|
129
|
+
export default UserProperty;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Obj, ObjectInstance, TableViewLayout } from '@evoke-platform/context';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { BaseProps } from '../../types';
|
|
4
|
+
export type InstanceLookUpProps = BaseProps & {
|
|
5
|
+
object: Obj;
|
|
6
|
+
instanceId?: string;
|
|
7
|
+
setSelectedInstance: (selectedInstance: ObjectInstance) => void;
|
|
8
|
+
setRelationType: (relationType: 'new' | 'existing') => void;
|
|
9
|
+
mode: 'default' | 'existingOnly';
|
|
10
|
+
nestedFieldsView?: boolean;
|
|
11
|
+
filter?: Record<string, unknown>;
|
|
12
|
+
layout?: TableViewLayout;
|
|
13
|
+
};
|
|
14
|
+
declare const InstanceLookup: (props: InstanceLookUpProps) => React.JSX.Element;
|
|
15
|
+
export default InstanceLookup;
|