@evoke-platform/ui-components 1.6.0-dev.17 → 1.6.0-dev.19
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/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/FormContext.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +2 -3
- 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/UserProperty.js +2 -3
- package/dist/published/components/custom/FormV2/components/types.d.ts +18 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +18 -1
- package/dist/published/components/custom/FormV2/components/utils.js +113 -0
- package/dist/published/theme/hooks.d.ts +1 -2
- package/package.json +2 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ActionType, FormEntry } from '@evoke-platform/context';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { FieldErrors, FieldValues, UseFormReturn, UseFormSetValue, UseFormUnregister } from 'react-hook-form';
|
|
4
|
+
type ActionButtonProps = {
|
|
5
|
+
onSubmit: (data: FieldValues) => void;
|
|
6
|
+
handleSubmit: UseFormReturn['handleSubmit'];
|
|
7
|
+
isModal: boolean;
|
|
8
|
+
submitButtonLabel?: string;
|
|
9
|
+
actionType?: ActionType;
|
|
10
|
+
onReset: () => void;
|
|
11
|
+
errors?: FieldErrors;
|
|
12
|
+
unregister: UseFormUnregister<FieldValues>;
|
|
13
|
+
entries: FormEntry[];
|
|
14
|
+
setValue: UseFormSetValue<FieldValues>;
|
|
15
|
+
formId?: string;
|
|
16
|
+
instance?: FieldValues;
|
|
17
|
+
};
|
|
18
|
+
declare function ActionButtons(props: ActionButtonProps): React.JSX.Element;
|
|
19
|
+
export default ActionButtons;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { isEmpty, omit } from 'lodash';
|
|
2
|
+
import React, { useContext, useState } from 'react';
|
|
3
|
+
import { useResponsive } from '../../../../theme';
|
|
4
|
+
import { Button, LoadingButton } from '../../../core';
|
|
5
|
+
import { Box } from '../../../layout';
|
|
6
|
+
import { FormContext } from './FormContext';
|
|
7
|
+
import { entryIsVisible, getEntryId, getNestedParameterIds, isAddressProperty, scrollIntoViewWithOffset, } from './utils';
|
|
8
|
+
function ActionButtons(props) {
|
|
9
|
+
const { onSubmit, submitButtonLabel, actionType, handleSubmit, onReset, unregister, errors, isModal, entries, setValue, formId, instance, } = props;
|
|
10
|
+
const { isXs } = useResponsive();
|
|
11
|
+
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
|
12
|
+
const { getValues } = useContext(FormContext);
|
|
13
|
+
const unregisterHiddenFields = (entriesToCheck) => {
|
|
14
|
+
entriesToCheck.forEach((entry) => {
|
|
15
|
+
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
16
|
+
const subEntries = entry.type === 'sections' ? entry.sections : entry.columns;
|
|
17
|
+
subEntries.forEach((subEntry) => {
|
|
18
|
+
if (subEntry.entries) {
|
|
19
|
+
unregisterHiddenFields(subEntry.entries);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (!entryIsVisible(entry, getValues(), instance)) {
|
|
24
|
+
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
25
|
+
const fieldsToUnregister = getNestedParameterIds(entry);
|
|
26
|
+
fieldsToUnregister.forEach(processFieldUnregister);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const fieldId = getEntryId(entry);
|
|
30
|
+
if (fieldId)
|
|
31
|
+
processFieldUnregister(fieldId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const processFieldUnregister = (fieldId) => {
|
|
37
|
+
if (isAddressProperty(fieldId)) {
|
|
38
|
+
// Unregister entire 'address' to clear hidden field errors, then restore existing values since unregistering address.line1 etc is not working
|
|
39
|
+
let addressValues = getValues('address');
|
|
40
|
+
addressValues = omit(addressValues, fieldId.split('.')[1]);
|
|
41
|
+
unregister('address');
|
|
42
|
+
setValue('address', addressValues);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
unregister(fieldId);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
async function showErrorsOrSubmit() {
|
|
49
|
+
setIsSubmitLoading(true);
|
|
50
|
+
unregisterHiddenFields(entries ?? []);
|
|
51
|
+
try {
|
|
52
|
+
await handleSubmit((data) => onSubmit(actionType === 'delete' ? {} : data))();
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
setIsSubmitLoading(false);
|
|
56
|
+
}
|
|
57
|
+
if (!isEmpty(errors)) {
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
const errorElement = document.getElementById(`validation-error-display-${formId}`);
|
|
60
|
+
let modal = errorElement?.closest('.MuiPaper-root');
|
|
61
|
+
const hasCloseIcon = modal?.querySelector('svg[data-testid="CloseIcon"]');
|
|
62
|
+
modal = hasCloseIcon ? document.querySelector('.MuiDialogContent-root') : modal;
|
|
63
|
+
if (errorElement) {
|
|
64
|
+
scrollIntoViewWithOffset(errorElement, 170, modal);
|
|
65
|
+
}
|
|
66
|
+
}, 0);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return (React.createElement(Box, { sx: {
|
|
70
|
+
display: 'flex',
|
|
71
|
+
justifyContent: 'flex-end',
|
|
72
|
+
flexWrap: 'wrap-reverse',
|
|
73
|
+
width: '100%',
|
|
74
|
+
} },
|
|
75
|
+
React.createElement(Button, { key: "cancel", variant: "outlined", sx: {
|
|
76
|
+
margin: '5px',
|
|
77
|
+
marginX: isXs ? '0px' : undefined,
|
|
78
|
+
color: 'black',
|
|
79
|
+
border: '1px solid rgb(206, 212, 218)',
|
|
80
|
+
width: isXs ? '100%' : 'auto',
|
|
81
|
+
'&:hover': {
|
|
82
|
+
backgroundColor: '#f2f4f7',
|
|
83
|
+
border: '1px solid rgb(206, 212, 218)',
|
|
84
|
+
},
|
|
85
|
+
}, onClick: () => onReset() }, isModal ? 'Cancel' : 'Discard Changes'),
|
|
86
|
+
React.createElement(LoadingButton, { key: "submit", variant: "contained", sx: {
|
|
87
|
+
lineHeight: '2.75',
|
|
88
|
+
margin: '5px 0 5px 5px',
|
|
89
|
+
marginX: isXs ? '0px' : undefined,
|
|
90
|
+
padding: '0 23px',
|
|
91
|
+
backgroundColor: actionType === 'delete' ? '#A12723' : 'primary',
|
|
92
|
+
borderRadius: '8px',
|
|
93
|
+
boxShadow: 'none',
|
|
94
|
+
whiteSpace: 'nowrap',
|
|
95
|
+
width: isXs ? '100%' : 'auto',
|
|
96
|
+
'& .MuiCircularProgress-root': {
|
|
97
|
+
color: 'white',
|
|
98
|
+
},
|
|
99
|
+
'&:hover': {
|
|
100
|
+
backgroundColor: actionType === 'delete' ? ' #8C2421' : '#014E7B',
|
|
101
|
+
},
|
|
102
|
+
}, onClick: () => {
|
|
103
|
+
showErrorsOrSubmit();
|
|
104
|
+
}, loading: isSubmitLoading }, submitButtonLabel || 'Submit')));
|
|
105
|
+
}
|
|
106
|
+
export default ActionButtons;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
1
2
|
import { Obj } from '@evoke-platform/context';
|
|
2
|
-
import { Dispatch, SetStateAction } from 'react';
|
|
3
3
|
import { FieldValues, UseFormGetValues } from 'react-hook-form';
|
|
4
4
|
type FormContextType = {
|
|
5
5
|
fetchedOptions: FieldValues;
|
|
6
|
-
setFetchedOptions:
|
|
6
|
+
setFetchedOptions: (newData: FieldValues) => void;
|
|
7
7
|
getValues: UseFormGetValues<FieldValues>;
|
|
8
8
|
stickyFooter?: boolean;
|
|
9
9
|
object?: Obj;
|
|
@@ -42,13 +42,12 @@ export default function Criteria(props) {
|
|
|
42
42
|
return prop;
|
|
43
43
|
});
|
|
44
44
|
setProperties(flattenProperties);
|
|
45
|
-
setFetchedOptions(
|
|
46
|
-
...prev,
|
|
45
|
+
setFetchedOptions({
|
|
47
46
|
[`${fieldDefinition.id}Options`]: flattenProperties.map((prop) => ({
|
|
48
47
|
id: prop.id,
|
|
49
48
|
name: prop.name,
|
|
50
49
|
})),
|
|
51
|
-
})
|
|
50
|
+
});
|
|
52
51
|
setLoadingError(false);
|
|
53
52
|
}
|
|
54
53
|
setLoading(false);
|
|
@@ -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 {};
|
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
|
+
};
|
|
@@ -30,13 +30,12 @@ const UserProperty = (props) => {
|
|
|
30
30
|
label: user.name,
|
|
31
31
|
value: user.id,
|
|
32
32
|
})));
|
|
33
|
-
setFetchedOptions(
|
|
34
|
-
...prev,
|
|
33
|
+
setFetchedOptions({
|
|
35
34
|
[`${id}Options`]: (userList ?? []).map((user) => ({
|
|
36
35
|
label: user.name,
|
|
37
36
|
value: user.id,
|
|
38
37
|
})),
|
|
39
|
-
})
|
|
38
|
+
});
|
|
40
39
|
setLoadingOptions(false);
|
|
41
40
|
});
|
|
42
41
|
}
|
|
@@ -6,3 +6,21 @@ export type FieldAddress = {
|
|
|
6
6
|
state?: string;
|
|
7
7
|
zipCode?: string;
|
|
8
8
|
};
|
|
9
|
+
export type AccessCheck = {
|
|
10
|
+
result: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type SavedDocumentReference = {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
export type Document = {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
contentType: string;
|
|
20
|
+
size: number;
|
|
21
|
+
uploadedDate: string;
|
|
22
|
+
metadata: {
|
|
23
|
+
[field: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
versionId?: string;
|
|
26
|
+
};
|
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
import { Property } from '@evoke-platform/context';
|
|
1
|
+
import { Columns, FormEntry, Property, Sections } from '@evoke-platform/context';
|
|
2
|
+
import { LocalDateTime } from '@js-joda/core';
|
|
3
|
+
import { FieldValues } from 'react-hook-form';
|
|
2
4
|
import { AutocompleteOption } from '../../../core';
|
|
5
|
+
export declare const scrollIntoViewWithOffset: (el: HTMLElement, offset: number, container?: HTMLElement) => void;
|
|
6
|
+
export declare const normalizeDateTime: (dateTime: LocalDateTime) => string;
|
|
7
|
+
export declare function isAddressProperty(key: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Determine if a form entry is visible or not.
|
|
10
|
+
*/
|
|
11
|
+
export declare const entryIsVisible: (entry: FormEntry, formValues: FieldValues, instance?: FieldValues) => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Recursively retrieves all parameter IDs from a given entry of type Sections or Columns.
|
|
14
|
+
*
|
|
15
|
+
* @param {Sections | Columns} entry - The entry object, which can be of type Sections or Columns.
|
|
16
|
+
* @returns {string[]} - An array of parameter IDs found within the entry.
|
|
17
|
+
*/
|
|
18
|
+
export declare const getNestedParameterIds: (entry: Sections | Columns) => string[];
|
|
19
|
+
export declare const getEntryId: (entry: FormEntry) => string | undefined;
|
|
3
20
|
export declare function getPrefixedUrl(url: string): string;
|
|
4
21
|
export declare const isOptionEqualToValue: (option: AutocompleteOption | string, value: unknown) => boolean;
|
|
5
22
|
export declare function addressProperties(addressProperty: Property): Property[];
|
|
@@ -1,3 +1,116 @@
|
|
|
1
|
+
import { LocalDateTime } from '@js-joda/core';
|
|
2
|
+
import jsonLogic from 'json-logic-js';
|
|
3
|
+
import { get, isArray, isObject } from 'lodash';
|
|
4
|
+
export const scrollIntoViewWithOffset = (el, offset, container) => {
|
|
5
|
+
const elementRect = el.getBoundingClientRect();
|
|
6
|
+
const containerRect = container ? container.getBoundingClientRect() : document.body.getBoundingClientRect();
|
|
7
|
+
const topPosition = elementRect.top - containerRect.top + (container?.scrollTop || 0) - offset;
|
|
8
|
+
if (container) {
|
|
9
|
+
container.scrollTo({
|
|
10
|
+
behavior: 'smooth',
|
|
11
|
+
top: topPosition,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
window.scrollTo({
|
|
16
|
+
behavior: 'smooth',
|
|
17
|
+
top: topPosition,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export const normalizeDateTime = (dateTime) => new Date(dateTime.toString()).toISOString();
|
|
22
|
+
const evaluateCondition = (condition, formValues, instance) => {
|
|
23
|
+
if (typeof condition !== 'object') {
|
|
24
|
+
console.error('Invalid condition format: ', condition);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (isArray(condition)) {
|
|
28
|
+
const firstCondition = condition[0];
|
|
29
|
+
const { property, value, operator } = firstCondition;
|
|
30
|
+
let fieldValue = firstCondition.isInstanceProperty ? get(instance, property) : get(formValues, property);
|
|
31
|
+
if (typeof fieldValue === 'object' && fieldValue !== null) {
|
|
32
|
+
if (fieldValue instanceof LocalDateTime) {
|
|
33
|
+
fieldValue = normalizeDateTime(fieldValue);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
fieldValue = Object.values(fieldValue).includes(value)
|
|
37
|
+
? value
|
|
38
|
+
: Object.values(fieldValue).find((val) => val !== undefined && val !== null);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
switch (operator) {
|
|
42
|
+
case 'ne':
|
|
43
|
+
return fieldValue != value;
|
|
44
|
+
case 'eq':
|
|
45
|
+
return fieldValue == value;
|
|
46
|
+
default:
|
|
47
|
+
console.error(`Unsupported operator: ${operator}`);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Handling custom JSON logic
|
|
52
|
+
else {
|
|
53
|
+
const data = {
|
|
54
|
+
data: formValues,
|
|
55
|
+
instance: instance,
|
|
56
|
+
};
|
|
57
|
+
const result = jsonLogic.apply(condition, data);
|
|
58
|
+
return result === true;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
export function isAddressProperty(key) {
|
|
62
|
+
return /\.line1|\.line2|\.city|\.county|\.state|\.zipCode$/.test(key);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Determine if a form entry is visible or not.
|
|
66
|
+
*/
|
|
67
|
+
export const entryIsVisible = (entry, formValues, instance) => {
|
|
68
|
+
const display = 'display' in entry ? entry.display : undefined;
|
|
69
|
+
const { visibility } = display ?? ('visibility' in entry ? entry : {});
|
|
70
|
+
if (isObject(visibility) && 'conditions' in visibility && isArray(visibility.conditions)) {
|
|
71
|
+
// visibility is a simple condition
|
|
72
|
+
const { conditions } = visibility;
|
|
73
|
+
return evaluateCondition(conditions, formValues, instance);
|
|
74
|
+
}
|
|
75
|
+
else if (visibility) {
|
|
76
|
+
// visibility is a JSONlogic condition
|
|
77
|
+
return evaluateCondition(visibility, formValues, instance);
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Recursively retrieves all parameter IDs from a given entry of type Sections or Columns.
|
|
83
|
+
*
|
|
84
|
+
* @param {Sections | Columns} entry - The entry object, which can be of type Sections or Columns.
|
|
85
|
+
* @returns {string[]} - An array of parameter IDs found within the entry.
|
|
86
|
+
*/
|
|
87
|
+
export const getNestedParameterIds = (entry) => {
|
|
88
|
+
const parameterIds = [];
|
|
89
|
+
const entries = entry.type === 'columns'
|
|
90
|
+
? entry.columns.flatMap((column) => column.entries ?? [])
|
|
91
|
+
: entry.sections.flatMap((section) => section.entries ?? []);
|
|
92
|
+
for (const subEntry of entries) {
|
|
93
|
+
if (subEntry.type === 'columns' || subEntry.type === 'sections') {
|
|
94
|
+
parameterIds.push(...getNestedParameterIds(subEntry));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const paramId = getEntryId(subEntry);
|
|
98
|
+
if (paramId && paramId !== 'collection') {
|
|
99
|
+
parameterIds.push(paramId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return parameterIds;
|
|
104
|
+
};
|
|
105
|
+
export const getEntryId = (entry) => {
|
|
106
|
+
return entry.type === 'input'
|
|
107
|
+
? entry.parameterId
|
|
108
|
+
: entry.type === 'readonlyField'
|
|
109
|
+
? entry.propertyId
|
|
110
|
+
: entry.type === 'inputField'
|
|
111
|
+
? entry.input.id
|
|
112
|
+
: undefined;
|
|
113
|
+
};
|
|
1
114
|
export function getPrefixedUrl(url) {
|
|
2
115
|
const wcsMatchers = ['/apps', '/pages', '/widgets'];
|
|
3
116
|
const dataMatchers = ['/objects', '/correspondenceTemplates', '/documents', '/payments', '/locations'];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
import { Breakpoint } from '@mui/material/styles';
|
|
3
2
|
/**
|
|
4
3
|
* Custom hook for responsive design breakpoints using size terminology.
|
|
@@ -110,7 +109,7 @@ export declare const useWidgetSize: (options?: {
|
|
|
110
109
|
export default useWidgetSize;
|
|
111
110
|
export declare function useFormContext(): {
|
|
112
111
|
fetchedOptions: import("react-hook-form").FieldValues;
|
|
113
|
-
setFetchedOptions:
|
|
112
|
+
setFetchedOptions: (newData: import("react-hook-form").FieldValues) => void;
|
|
114
113
|
getValues: import("react-hook-form").UseFormGetValues<import("react-hook-form").FieldValues>;
|
|
115
114
|
stickyFooter?: boolean | undefined;
|
|
116
115
|
object?: import("@evoke-platform/context").Obj | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evoke-platform/ui-components",
|
|
3
|
-
"version": "1.6.0-dev.
|
|
3
|
+
"version": "1.6.0-dev.19",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/published/index.js",
|
|
6
6
|
"module": "dist/published/index.js",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@testing-library/user-event": "^14.5.2",
|
|
55
55
|
"@types/flat": "^5.0.5",
|
|
56
56
|
"@types/jest": "^28.1.4",
|
|
57
|
+
"@types/json-logic-js": "^2.0.8",
|
|
57
58
|
"@types/luxon": "^3.4.2",
|
|
58
59
|
"@types/nanoid-dictionary": "^4.2.3",
|
|
59
60
|
"@types/node": "^18.0.0",
|