@evoke-platform/ui-components 1.10.0-testing.15 → 1.10.0-testing.17
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/FormRenderer.js +6 -5
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +1 -15
- package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
- package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +5 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +57 -66
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +2 -2
- package/dist/published/components/custom/FormV2/components/Header.d.ts +12 -4
- package/dist/published/components/custom/FormV2/components/Header.js +7 -9
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +14 -19
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
- package/dist/published/components/custom/FormV2/components/types.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/utils.js +7 -7
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +155 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Renderer.d.ts +13 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Renderer.js +140 -0
- package/dist/published/components/custom/ViewDetailsV2/index.d.ts +2 -0
- package/dist/published/components/custom/ViewDetailsV2/index.js +2 -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 +1 -1
- package/dist/published/index.js +1 -1
- package/dist/published/stories/FormRendererData.d.ts +12 -0
- package/dist/published/stories/FormRendererData.js +23 -0
- package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
- package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
- package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
- package/dist/published/stories/ViewDetailsV2Data.js +203 -0
- package/dist/published/stories/sharedMswHandlers.js +49 -10
- package/dist/published/theme/hooks.d.ts +3 -3
- package/package.json +2 -2
|
@@ -38,11 +38,7 @@ function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, displ
|
|
|
38
38
|
}
|
|
39
39
|
export function RecursiveEntryRenderer(props) {
|
|
40
40
|
const { entry } = props;
|
|
41
|
-
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor, parameters, handleChange, onAutosave, fieldHeight, triggerFieldReset, associatedObject, form, width, } = useFormContext();
|
|
42
|
-
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
43
|
-
if (!entryIsVisible(entry, getValues(), instance)) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
41
|
+
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor: RichTextEditor, parameters, handleChange, onAutosave, fieldHeight, triggerFieldReset, associatedObject, form, width, } = useFormContext();
|
|
46
42
|
const { isBelow, breakpoints } = useWidgetSize({
|
|
47
43
|
scroll: false,
|
|
48
44
|
defaultWidth: width,
|
|
@@ -53,20 +49,24 @@ export function RecursiveEntryRenderer(props) {
|
|
|
53
49
|
const userAccount = useAuthenticationContext()?.account;
|
|
54
50
|
const entryId = getEntryId(entry) || 'defaultId';
|
|
55
51
|
const display = 'display' in entry ? entry.display : undefined;
|
|
56
|
-
const fieldValue = entry.type === 'readonlyField' ? instance?.[entryId] : getValues(entryId);
|
|
52
|
+
const fieldValue = entry.type === 'readonlyField' ? instance?.[entryId] : getValues ? getValues(entryId) : undefined;
|
|
57
53
|
const initialMiddleObjectInstances = fetchedOptions[`${entryId}InitialMiddleObjectInstances`];
|
|
58
54
|
const middleObject = fetchedOptions[`${entryId}MiddleObject`];
|
|
59
55
|
const fieldDefinition = useMemo(() => {
|
|
60
56
|
return getFieldDefinition(entry, object, parameters, form?.id === 'documentForm');
|
|
61
57
|
}, [entry, parameters, object]);
|
|
62
58
|
const validation = fieldDefinition?.validation || {};
|
|
63
|
-
if (associatedObject?.propertyId === entryId)
|
|
64
|
-
return null;
|
|
65
59
|
useEffect(() => {
|
|
66
60
|
if (fieldDefinition?.type === 'collection' && fieldDefinition?.manyToManyPropertyId && instance) {
|
|
67
61
|
fetchCollectionData(apiServices, fieldDefinition, setFetchedOptions, instance.id, fetchedOptions, initialMiddleObjectInstances);
|
|
68
62
|
}
|
|
69
63
|
}, [fieldDefinition, instance]);
|
|
64
|
+
if (associatedObject?.propertyId === entryId)
|
|
65
|
+
return null;
|
|
66
|
+
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
67
|
+
if (!getValues || !entryIsVisible(entry, instance, getValues())) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
70
|
if (entry.type === 'content') {
|
|
71
71
|
return (React.createElement(Box, { dangerouslySetInnerHTML: { __html: DOMPurify.sanitize(entry.html) }, sx: {
|
|
72
72
|
fontFamily: 'Roboto, Helvetica, Arial, sans-serif',
|
|
@@ -75,7 +75,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
75
75
|
else if ((entry.type === 'input' || entry.type === 'readonlyField' || entry.type === 'inputField') &&
|
|
76
76
|
fieldDefinition) {
|
|
77
77
|
if (isAddressProperty(entryId)) {
|
|
78
|
-
return React.createElement(AddressFields, { entry: entry, entryId: entryId, fieldDefinition: fieldDefinition });
|
|
78
|
+
return (React.createElement(AddressFields, { entry: entry, entryId: entryId, fieldDefinition: fieldDefinition, readOnly: entry.type === 'readonlyField' }));
|
|
79
79
|
}
|
|
80
80
|
else if (fieldDefinition.type === 'image') {
|
|
81
81
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
@@ -118,16 +118,11 @@ export function RecursiveEntryRenderer(props) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
else if (fieldDefinition.type === 'richText') {
|
|
121
|
-
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
121
|
+
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) }, RichTextEditor && handleChange ? (React.createElement(RichTextEditor
|
|
122
|
+
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
123
|
+
, {
|
|
122
124
|
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
123
|
-
id: entry.uniqueId,
|
|
124
|
-
value: fieldValue,
|
|
125
|
-
handleUpdate: (value) => handleChange(entryId, value),
|
|
126
|
-
format: 'rtf',
|
|
127
|
-
disabled: entry.type === 'readonlyField',
|
|
128
|
-
rows: display?.rowCount,
|
|
129
|
-
hasError: !!errors?.[entryId],
|
|
130
|
-
})) : (React.createElement(FormField, { id: entryId, property: fieldDefinition, defaultValue: fieldValue || getValues(entryId), onChange: handleChange, onBlur: () => {
|
|
125
|
+
id: entry.uniqueId, value: fieldValue, handleUpdate: (value) => handleChange(entryId, value), format: "rtf", disabled: entry.type === 'readonlyField', rows: display?.rowCount, hasError: !!errors?.[entryId] })) : (React.createElement(FormField, { id: entryId, property: fieldDefinition, defaultValue: fieldValue, onChange: handleChange, onBlur: () => {
|
|
131
126
|
onAutosave?.(entryId)?.catch((error) => {
|
|
132
127
|
console.error('Autosave failed:', error);
|
|
133
128
|
});
|
|
@@ -174,7 +169,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
174
169
|
: `${entryId}-reset-false`, ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors), errorMessage: undefined },
|
|
175
170
|
React.createElement(FormField, { id: entryId,
|
|
176
171
|
// TODO: Ideally the FormField prop should be called parameter but can't change the name for backwards compatibility reasons
|
|
177
|
-
property: fieldDefinition, defaultValue: fieldValue
|
|
172
|
+
property: fieldDefinition, defaultValue: fieldValue, onChange: handleChange, onBlur: () => {
|
|
178
173
|
// Blur event - reads current value from formData
|
|
179
174
|
onAutosave?.(entryId)?.catch((error) => {
|
|
180
175
|
console.error('Autosave failed:', error);
|
package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js
CHANGED
|
@@ -29,7 +29,7 @@ function ValidationErrors(props) {
|
|
|
29
29
|
border: '1px solid #721c24',
|
|
30
30
|
padding: '8px 24px',
|
|
31
31
|
borderRadius: '4px',
|
|
32
|
-
flex: 1,
|
|
32
|
+
flex: '1 1 100%',
|
|
33
33
|
...sx,
|
|
34
34
|
} },
|
|
35
35
|
React.createElement(Typography, { sx: { color: '#721c24', mt: '16px', mb: '8px' } }, "Please fix the following errors before submitting:"),
|
|
@@ -10,7 +10,7 @@ export declare function isAddressProperty(key: string): boolean;
|
|
|
10
10
|
/**
|
|
11
11
|
* Determine if a form entry is visible or not.
|
|
12
12
|
*/
|
|
13
|
-
export declare const entryIsVisible: (entry: FormEntry,
|
|
13
|
+
export declare const entryIsVisible: (entry: FormEntry, instance?: FieldValues, formValues?: FieldValues) => boolean;
|
|
14
14
|
/**
|
|
15
15
|
* Recursively retrieves all parameter IDs from a given entry of type Sections or Columns.
|
|
16
16
|
*
|
|
@@ -86,7 +86,7 @@ export declare function formatSubmission(submission: FieldValues, apiServices?:
|
|
|
86
86
|
message?: string;
|
|
87
87
|
isError: boolean;
|
|
88
88
|
}>>): Promise<FieldValues>;
|
|
89
|
-
export declare function filterEmptySections(entry: Sections | Columns,
|
|
89
|
+
export declare function filterEmptySections(entry: Sections | Columns, instance?: FieldValues, formData?: FieldValues): Sections | Columns | null;
|
|
90
90
|
export declare function assignIdsToSectionsAndRichText(entries: FormEntry[], object?: Obj, parameters?: InputParameter[]): FormEntry[];
|
|
91
91
|
/**
|
|
92
92
|
* Converts a plain text string to RTF format suitable for a RichTextEditor.
|
|
@@ -68,7 +68,7 @@ export function isAddressProperty(key) {
|
|
|
68
68
|
/**
|
|
69
69
|
* Determine if a form entry is visible or not.
|
|
70
70
|
*/
|
|
71
|
-
export const entryIsVisible = (entry,
|
|
71
|
+
export const entryIsVisible = (entry, instance, formValues) => {
|
|
72
72
|
const display = 'display' in entry ? entry.display : undefined;
|
|
73
73
|
const { visibility } = display ?? ('visibility' in entry ? entry : {});
|
|
74
74
|
if (isObject(visibility) && 'conditions' in visibility && isArray(visibility.conditions)) {
|
|
@@ -669,21 +669,21 @@ export async function formatSubmission(submission, apiServices, objectId, instan
|
|
|
669
669
|
}
|
|
670
670
|
return submission;
|
|
671
671
|
}
|
|
672
|
-
export function filterEmptySections(entry,
|
|
672
|
+
export function filterEmptySections(entry, instance, formData) {
|
|
673
673
|
if (entry.type === 'sections' && isArray(entry.sections)) {
|
|
674
674
|
const visibleSections = entry.sections.filter((section) => {
|
|
675
675
|
if (!section.entries || section.entries.length === 0)
|
|
676
676
|
return false;
|
|
677
677
|
for (const sectionEntry of section.entries) {
|
|
678
678
|
if (sectionEntry.type === 'sections' || sectionEntry.type === 'columns') {
|
|
679
|
-
if (sectionEntry.visibility && !entryIsVisible(sectionEntry,
|
|
679
|
+
if (sectionEntry.visibility && !entryIsVisible(sectionEntry, instance, formData)) {
|
|
680
680
|
return false;
|
|
681
681
|
}
|
|
682
|
-
else if (filterEmptySections(sectionEntry,
|
|
682
|
+
else if (filterEmptySections(sectionEntry, instance, formData)) {
|
|
683
683
|
return true;
|
|
684
684
|
}
|
|
685
685
|
}
|
|
686
|
-
else if (entryIsVisible(sectionEntry,
|
|
686
|
+
else if (entryIsVisible(sectionEntry, instance, formData)) {
|
|
687
687
|
return true;
|
|
688
688
|
}
|
|
689
689
|
}
|
|
@@ -703,13 +703,13 @@ export function filterEmptySections(entry, formData, instance) {
|
|
|
703
703
|
let hasVisibleEntry = false;
|
|
704
704
|
for (const columnEntry of column.entries) {
|
|
705
705
|
if (columnEntry.type === 'sections' || columnEntry.type === 'columns') {
|
|
706
|
-
if (filterEmptySections(columnEntry,
|
|
706
|
+
if (filterEmptySections(columnEntry, instance, formData)) {
|
|
707
707
|
hasVisibleEntry = true;
|
|
708
708
|
break;
|
|
709
709
|
}
|
|
710
710
|
}
|
|
711
711
|
else {
|
|
712
|
-
if (entryIsVisible(columnEntry,
|
|
712
|
+
if (entryIsVisible(columnEntry, instance, formData)) {
|
|
713
713
|
hasVisibleEntry = true;
|
|
714
714
|
break;
|
|
715
715
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { useApiServices, useApp, } from '@evoke-platform/context';
|
|
2
|
+
import { CancelRounded, CheckCircleRounded } from '@mui/icons-material';
|
|
3
|
+
import DOMPurify from 'dompurify';
|
|
4
|
+
import { isEmpty } from 'lodash';
|
|
5
|
+
import { DateTime } from 'luxon';
|
|
6
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
7
|
+
import { useFormContext } from '../../../theme/hooks';
|
|
8
|
+
import { Link, Typography } from '../../core';
|
|
9
|
+
import { Box, Grid } from '../../layout';
|
|
10
|
+
import AccordionSections from '../FormV2/components/AccordionSections';
|
|
11
|
+
import FieldWrapper from '../FormV2/components/FieldWrapper';
|
|
12
|
+
import AddressFields from '../FormV2/components/FormFieldTypes/AddressFields';
|
|
13
|
+
import DropdownRepeatableField from '../FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField';
|
|
14
|
+
import RepeatableField from '../FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField';
|
|
15
|
+
import Criteria from '../FormV2/components/FormFieldTypes/Criteria';
|
|
16
|
+
import { Document } from '../FormV2/components/FormFieldTypes/DocumentFiles/Document';
|
|
17
|
+
import { Image } from '../FormV2/components/FormFieldTypes/Image';
|
|
18
|
+
import { entryIsVisible, fetchCollectionData, filterEmptySections, getDefaultPages, isAddressProperty, } from '../FormV2/components/utils';
|
|
19
|
+
function ViewOnlyEntryRenderer(props) {
|
|
20
|
+
const { entry } = props;
|
|
21
|
+
const { fetchedOptions, setFetchedOptions, object, instance, richTextEditor: RichTextEditor } = useFormContext();
|
|
22
|
+
const entryId = entry.propertyId || 'defaultId';
|
|
23
|
+
const apiServices = useApiServices();
|
|
24
|
+
const { defaultPages, findDefaultPageSlugFor } = useApp();
|
|
25
|
+
const [navigationSlug, setNavigationSlug] = useState(fetchedOptions[`${entryId}NavigationSlug`]);
|
|
26
|
+
const initialMiddleObjectInstances = fetchedOptions[`${entryId}InitialMiddleObjectInstances`];
|
|
27
|
+
const middleObject = fetchedOptions[`${entryId}MiddleObject`];
|
|
28
|
+
const display = 'display' in entry ? entry.display : undefined;
|
|
29
|
+
const fieldDefinition = useMemo(() => {
|
|
30
|
+
const def = entry.type === 'readonlyField'
|
|
31
|
+
? isAddressProperty(entry.propertyId)
|
|
32
|
+
? object?.properties?.find((prop) => prop.id === entry.propertyId.split('.')[0])
|
|
33
|
+
: object?.properties?.find((prop) => prop.id === entry.propertyId)
|
|
34
|
+
: undefined;
|
|
35
|
+
if (def?.enum && def.type === 'string') {
|
|
36
|
+
const cloned = structuredClone(def);
|
|
37
|
+
// single select must be made to be type choices for label and error handling
|
|
38
|
+
cloned.type = 'choices';
|
|
39
|
+
return cloned;
|
|
40
|
+
}
|
|
41
|
+
return def;
|
|
42
|
+
}, [entry, object]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (fieldDefinition?.type === 'collection' && fieldDefinition?.manyToManyPropertyId && instance) {
|
|
45
|
+
fetchCollectionData(apiServices, fieldDefinition, setFetchedOptions, instance.id, fetchedOptions, initialMiddleObjectInstances);
|
|
46
|
+
}
|
|
47
|
+
}, [fieldDefinition, initialMiddleObjectInstances, setFetchedOptions, instance, fetchedOptions, apiServices]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
(async () => {
|
|
50
|
+
if (object?.properties && !fetchedOptions[`${entryId}NavigationSlug`]) {
|
|
51
|
+
const pages = await getDefaultPages(object.properties, defaultPages, findDefaultPageSlugFor);
|
|
52
|
+
if (fieldDefinition?.objectId && pages[fieldDefinition.objectId]) {
|
|
53
|
+
setNavigationSlug(pages[fieldDefinition.objectId]);
|
|
54
|
+
setFetchedOptions({
|
|
55
|
+
[`${entryId}NavigationSlug`]: pages[fieldDefinition.objectId],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
}, [object, defaultPages, findDefaultPageSlugFor, fieldDefinition]);
|
|
61
|
+
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
62
|
+
if (!entryIsVisible(entry, instance)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (entry.type === 'content') {
|
|
66
|
+
return (React.createElement(Box, { dangerouslySetInnerHTML: { __html: DOMPurify.sanitize(entry.html) }, sx: {
|
|
67
|
+
fontFamily: 'Roboto, Helvetica, Arial, sans-serif',
|
|
68
|
+
} }));
|
|
69
|
+
}
|
|
70
|
+
else if (entry.type === 'readonlyField') {
|
|
71
|
+
if (isAddressProperty(entryId)) {
|
|
72
|
+
return (React.createElement(AddressFields, { entry: entry, viewOnly: true, entryId: entryId, fieldDefinition: fieldDefinition }));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
let fieldValue = instance?.[entryId] || '';
|
|
76
|
+
switch (fieldDefinition?.type) {
|
|
77
|
+
case 'object':
|
|
78
|
+
if (navigationSlug && fieldDefinition?.objectId) {
|
|
79
|
+
return (React.createElement(FieldWrapper, { inputId: entryId, inputType: "object", label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, viewOnly: true },
|
|
80
|
+
React.createElement(Link, { sx: { cursor: 'pointer', fontFamily: 'sans-serif' }, href: `${'/app'}${navigationSlug.replace(':instanceId', fieldValue.id)}` }, fieldValue.name)));
|
|
81
|
+
}
|
|
82
|
+
fieldValue = fieldValue.name;
|
|
83
|
+
break;
|
|
84
|
+
case 'array':
|
|
85
|
+
if (!isEmpty(fieldValue)) {
|
|
86
|
+
fieldValue = fieldValue && fieldValue.join(', ');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
fieldValue = '';
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
case 'user':
|
|
93
|
+
fieldValue = fieldValue && fieldValue.name;
|
|
94
|
+
break;
|
|
95
|
+
case 'date':
|
|
96
|
+
fieldValue = fieldValue && DateTime.fromISO(fieldValue).toFormat('MM/dd/yyyy');
|
|
97
|
+
break;
|
|
98
|
+
case 'date-time':
|
|
99
|
+
fieldValue =
|
|
100
|
+
fieldValue && fieldValue instanceof Date
|
|
101
|
+
? DateTime.fromJSDate(fieldValue).toFormat('MM/dd/yyyy hh:mm a')
|
|
102
|
+
: fieldValue && DateTime.fromISO(fieldValue).toFormat('MM/dd/yyyy hh:mm a');
|
|
103
|
+
break;
|
|
104
|
+
case 'time':
|
|
105
|
+
fieldValue =
|
|
106
|
+
fieldValue &&
|
|
107
|
+
DateTime.fromISO(DateTime.now().toISODate() + 'T' + fieldValue).toFormat('hh:mm a');
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (fieldDefinition) {
|
|
113
|
+
if (fieldDefinition.type === 'image') {
|
|
114
|
+
return (React.createElement(FieldWrapper, { inputId: entryId, inputType: 'image', label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, viewOnly: true },
|
|
115
|
+
React.createElement(Image, { id: entryId, canUpdateProperty: false, value: fieldValue, hasDescription: !!display?.description })));
|
|
116
|
+
}
|
|
117
|
+
else if (fieldDefinition.type === 'richText') {
|
|
118
|
+
return (React.createElement(FieldWrapper, { inputId: entryId, inputType: 'richText', label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, viewOnly: true }, RichTextEditor ? (React.createElement(RichTextEditor
|
|
119
|
+
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
120
|
+
, {
|
|
121
|
+
// RichTexts get a uniqueId when the form is loaded to prevent issues with multiple rich text fields on one form
|
|
122
|
+
id: entry.uniqueId, value: fieldValue, format: "rtf", disabled: true, rows: display?.rowCount, hasError: false })) : (React.createElement(Typography, { variant: "body1", key: entryId, sx: { height: '24px' } }, fieldValue))));
|
|
123
|
+
}
|
|
124
|
+
else if (fieldDefinition.type === 'document') {
|
|
125
|
+
return (React.createElement(FieldWrapper, { inputId: entryId, inputType: 'document', label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, prefix: display?.prefix, suffix: display?.suffix, viewOnly: true },
|
|
126
|
+
React.createElement(Document, { id: entryId, error: false, value: fieldValue, canUpdateProperty: false })));
|
|
127
|
+
}
|
|
128
|
+
else if (fieldDefinition.type === 'collection') {
|
|
129
|
+
return fieldDefinition?.manyToManyPropertyId ? (middleObject && initialMiddleObjectInstances && (React.createElement(FieldWrapper, { inputId: entryId, inputType: 'collection', label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, viewOnly: true },
|
|
130
|
+
React.createElement(DropdownRepeatableField, { initialMiddleObjectInstances: fetchedOptions[`${entryId}MiddleObjectInstances`] ||
|
|
131
|
+
initialMiddleObjectInstances, id: entryId, middleObject: middleObject, fieldDefinition: fieldDefinition, readOnly: true })))) : (React.createElement(FieldWrapper, { inputId: entryId, inputType: 'collection', label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, viewOnly: true },
|
|
132
|
+
React.createElement(RepeatableField, { fieldDefinition: fieldDefinition, canUpdateProperty: false, entry: entry, viewLayout: display?.viewLayout })));
|
|
133
|
+
}
|
|
134
|
+
else if (fieldDefinition.type === 'criteria') {
|
|
135
|
+
return (React.createElement(FieldWrapper, { inputId: entryId, inputType: 'criteria', label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, viewOnly: true },
|
|
136
|
+
React.createElement(Criteria, { value: fieldValue, fieldDefinition: fieldDefinition, canUpdateProperty: false })));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
return (React.createElement(FieldWrapper, { inputId: entryId, inputType: fieldDefinition?.type || 'string', label: display?.label || fieldDefinition?.name || 'default', value: fieldValue, required: display?.required || false, prefix: display?.prefix, suffix: display?.suffix, viewOnly: true }, fieldDefinition?.type === 'boolean' && fieldValue ? (React.createElement(CheckCircleRounded, { "aria-label": "Checked", color: "success" })) : fieldDefinition?.type === 'boolean' ? (React.createElement(CancelRounded, { "aria-label": "Unchecked", color: "error" })) : (React.createElement(Typography, { variant: "body1", key: entryId, sx: { height: '24px' } }, fieldValue))));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (entry.type === 'columns') {
|
|
145
|
+
return (React.createElement(Grid, { container: true, spacing: 4 }, entry.columns.map((column, colIndex) => (React.createElement(Grid, { item: true, key: colIndex, xs: 12, sm: column.width }, column.entries?.map((columnEntry, entryIndex) => {
|
|
146
|
+
return (React.createElement(ViewOnlyEntryRenderer, { key: entryIndex + (columnEntry?.parameterId ?? ''), entry: columnEntry }));
|
|
147
|
+
}))))));
|
|
148
|
+
}
|
|
149
|
+
else if (entry.type === 'sections') {
|
|
150
|
+
const filteredEntry = filterEmptySections(entry, instance);
|
|
151
|
+
return filteredEntry ? React.createElement(AccordionSections, { entry: filteredEntry, readOnly: true }) : null;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
export default ViewOnlyEntryRenderer;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { ComponentType } from 'react';
|
|
2
|
+
import { FormRendererProps } from '../FormV2/FormRenderer';
|
|
3
|
+
import { BaseProps, SimpleEditorProps } from '../FormV2/components/types';
|
|
4
|
+
export type ViewDetailsV2ContainerProps = BaseProps & {
|
|
5
|
+
panelLayoutId?: string;
|
|
6
|
+
instanceId?: string;
|
|
7
|
+
objectId: string;
|
|
8
|
+
richTextEditor?: ComponentType<SimpleEditorProps>;
|
|
9
|
+
renderHeader?: FormRendererProps['renderHeader'];
|
|
10
|
+
renderBody?: FormRendererProps['renderBody'];
|
|
11
|
+
};
|
|
12
|
+
declare function ViewDetailsV2Container(props: ViewDetailsV2ContainerProps): React.JSX.Element;
|
|
13
|
+
export default ViewDetailsV2Container;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useApiServices, useObject, } from '@evoke-platform/context';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { useWidgetSize } from '../../../theme';
|
|
5
|
+
import { Skeleton, Snackbar } from '../../core';
|
|
6
|
+
import { Box } from '../../layout';
|
|
7
|
+
import ErrorComponent from '../ErrorComponent';
|
|
8
|
+
import { FormContext } from '../FormV2';
|
|
9
|
+
import Header from '../FormV2/components/Header';
|
|
10
|
+
import { assignIdsToSectionsAndRichText, getPrefixedUrl } from '../FormV2/components/utils';
|
|
11
|
+
import ViewOnlyEntryRenderer from './InstanceEntryRenderer';
|
|
12
|
+
function ViewDetailsV2Container(props) {
|
|
13
|
+
const { instanceId, panelLayoutId, objectId, richTextEditor, renderHeader, renderBody } = props;
|
|
14
|
+
const apiServices = useApiServices();
|
|
15
|
+
const [sanitizedObject, setSanitizedObject] = useState();
|
|
16
|
+
const [instance, setInstance] = useState();
|
|
17
|
+
const [error, setError] = useState();
|
|
18
|
+
const [panelLayout, setPanelLayout] = useState();
|
|
19
|
+
const [expandedSections, setExpandedSections] = useState([]);
|
|
20
|
+
const [fetchedOptions, setFetchedOptions] = useState({});
|
|
21
|
+
const [expandAll, setExpandAll] = useState();
|
|
22
|
+
const [snackbarError, setSnackbarError] = useState({
|
|
23
|
+
showAlert: false,
|
|
24
|
+
isError: true,
|
|
25
|
+
});
|
|
26
|
+
function handleExpandAll() {
|
|
27
|
+
setExpandAll(true);
|
|
28
|
+
}
|
|
29
|
+
function handleCollapseAll() {
|
|
30
|
+
setExpandAll(false);
|
|
31
|
+
}
|
|
32
|
+
const updateFetchedOptions = (newData) => {
|
|
33
|
+
setFetchedOptions((prev) => ({
|
|
34
|
+
...prev,
|
|
35
|
+
...newData,
|
|
36
|
+
}));
|
|
37
|
+
};
|
|
38
|
+
const { ref: containerRef, breakpoints, width, } = useWidgetSize({
|
|
39
|
+
scroll: false,
|
|
40
|
+
defaultWidth: 1200,
|
|
41
|
+
});
|
|
42
|
+
const { isXs, isSm } = breakpoints;
|
|
43
|
+
const objectStore = useObject(panelLayout?.objectId ?? objectId);
|
|
44
|
+
const onError = (err) => {
|
|
45
|
+
const code = axios.isAxiosError(err) ? err.response?.status : undefined;
|
|
46
|
+
setSnackbarError({ ...snackbarError, isError: true });
|
|
47
|
+
setError(code ?? true);
|
|
48
|
+
};
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
(async () => {
|
|
51
|
+
try {
|
|
52
|
+
if (instanceId) {
|
|
53
|
+
const instance = await objectStore.getInstance(instanceId);
|
|
54
|
+
setInstance(instance);
|
|
55
|
+
}
|
|
56
|
+
const object = await apiServices.get(getPrefixedUrl(`/objects/${objectId}${instanceId ? `/instances/${instanceId}/object` : '/effective'}`), { params: { sanitizedVersion: true } });
|
|
57
|
+
setSanitizedObject(object);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const isAxiosErr = axios.isAxiosError(error);
|
|
61
|
+
console.error(isAxiosErr ? error.message : 'Error while fetching object');
|
|
62
|
+
setError(isAxiosErr ? error.status : true);
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
}, [objectId, instanceId]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (panelLayoutId || sanitizedObject?.defaultPanelLayoutId) {
|
|
68
|
+
apiServices
|
|
69
|
+
.get(getPrefixedUrl(`/objects/${objectId}/panelLayouts/${panelLayoutId || sanitizedObject?.defaultPanelLayoutId}`))
|
|
70
|
+
.then((evokeForm) => {
|
|
71
|
+
setPanelLayout(evokeForm);
|
|
72
|
+
})
|
|
73
|
+
.catch((error) => {
|
|
74
|
+
onError(error);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}, [panelLayoutId, sanitizedObject, objectId]);
|
|
78
|
+
const updatedEntries = useMemo(() => {
|
|
79
|
+
return assignIdsToSectionsAndRichText(panelLayout?.entries || []);
|
|
80
|
+
}, [panelLayout?.id]);
|
|
81
|
+
const isLoading = (instanceId && !instance) || !panelLayout || !sanitizedObject;
|
|
82
|
+
const hasSections = updatedEntries.some((entry) => entry.type === 'sections');
|
|
83
|
+
const headerProps = {
|
|
84
|
+
title: panelLayout?.name,
|
|
85
|
+
onExpandAll: handleExpandAll,
|
|
86
|
+
onCollapseAll: handleCollapseAll,
|
|
87
|
+
expandedSections,
|
|
88
|
+
hasAccordions: hasSections,
|
|
89
|
+
autosaveEnabled: false,
|
|
90
|
+
};
|
|
91
|
+
return !error ? (React.createElement(Box, { sx: {
|
|
92
|
+
backgroundColor: '#ffffff',
|
|
93
|
+
borderRadius: '6px',
|
|
94
|
+
padding: '0px',
|
|
95
|
+
border: !isLoading ? '1px solid #dbe0e4' : undefined,
|
|
96
|
+
} },
|
|
97
|
+
!isLoading ? (React.createElement(Box, { ref: containerRef },
|
|
98
|
+
(hasSections || panelLayout.name || renderHeader) &&
|
|
99
|
+
(renderHeader ? renderHeader({ ...headerProps }) : React.createElement(Header, { ...headerProps })),
|
|
100
|
+
React.createElement(Box, { sx: {
|
|
101
|
+
paddingX: isSm || isXs ? 2 : 3,
|
|
102
|
+
borderTop: '1px solid #e9ecef',
|
|
103
|
+
} },
|
|
104
|
+
React.createElement(FormContext.Provider, { value: {
|
|
105
|
+
fetchedOptions,
|
|
106
|
+
setFetchedOptions: updateFetchedOptions,
|
|
107
|
+
object: sanitizedObject,
|
|
108
|
+
instance,
|
|
109
|
+
richTextEditor,
|
|
110
|
+
expandedSections,
|
|
111
|
+
setExpandedSections,
|
|
112
|
+
expandAll,
|
|
113
|
+
setExpandAll,
|
|
114
|
+
width,
|
|
115
|
+
} }, renderBody
|
|
116
|
+
? renderBody({
|
|
117
|
+
isInitializing: isLoading,
|
|
118
|
+
entries: updatedEntries,
|
|
119
|
+
onExpandAll: handleExpandAll,
|
|
120
|
+
onCollapseAll: handleCollapseAll,
|
|
121
|
+
expandedSections,
|
|
122
|
+
hasAccordions: hasSections,
|
|
123
|
+
})
|
|
124
|
+
: updatedEntries.map((entry, index) => (React.createElement(ViewOnlyEntryRenderer, { entry: entry, key: index }))))))) : (React.createElement(Box, { sx: { padding: '20px' } },
|
|
125
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
126
|
+
React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
127
|
+
React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
128
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
129
|
+
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
130
|
+
React.createElement(Skeleton, { width: '33%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
131
|
+
React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
|
|
132
|
+
React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
|
|
133
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } }),
|
|
134
|
+
React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))),
|
|
135
|
+
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({
|
|
136
|
+
isError: snackbarError.isError,
|
|
137
|
+
showAlert: false,
|
|
138
|
+
}), message: snackbarError.message, error: snackbarError.isError }))) : (React.createElement(ErrorComponent, { colspan: props.colspan, code: error === 403 ? 'AccessDenied' : error === 404 ? 'NotFound' : 'Misconfigured' }));
|
|
139
|
+
}
|
|
140
|
+
export default ViewDetailsV2Container;
|
|
@@ -14,3 +14,4 @@ export { RepeatableField } from './RepeatableField';
|
|
|
14
14
|
export { ResponsiveOverflow } from './ResponsiveOverflow';
|
|
15
15
|
export { RichTextViewer } from './RichTextViewer';
|
|
16
16
|
export { UserAvatar } from './UserAvatar';
|
|
17
|
+
export { ViewDetailsV2Container, ViewOnlyEntryRenderer } from './ViewDetailsV2';
|
|
@@ -12,3 +12,4 @@ export { RepeatableField } from './RepeatableField';
|
|
|
12
12
|
export { ResponsiveOverflow } from './ResponsiveOverflow';
|
|
13
13
|
export { RichTextViewer } from './RichTextViewer';
|
|
14
14
|
export { UserAvatar } from './UserAvatar';
|
|
15
|
+
export { ViewDetailsV2Container, ViewOnlyEntryRenderer } from './ViewDetailsV2';
|
|
@@ -2,7 +2,7 @@ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar, useMe
|
|
|
2
2
|
export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
|
|
3
3
|
export * from './colors';
|
|
4
4
|
export * from './components/core';
|
|
5
|
-
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormContext, FormField, FormRenderer, FormRendererContainer, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RecursiveEntryRenderer, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, } from './components/custom';
|
|
5
|
+
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormContext, FormField, FormRenderer, FormRendererContainer, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RecursiveEntryRenderer, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, ViewDetailsV2Container, ViewOnlyEntryRenderer, } from './components/custom';
|
|
6
6
|
export type { BodyProps, FooterProps, FormRef, GridSortModel, HeaderProps } from './components/custom';
|
|
7
7
|
export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
|
|
8
8
|
export { Box, Container, Grid, Stack } from './components/layout';
|
package/dist/published/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar, useMe
|
|
|
2
2
|
export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
|
|
3
3
|
export * from './colors';
|
|
4
4
|
export * from './components/core';
|
|
5
|
-
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormContext, FormField, FormRenderer, FormRendererContainer, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RecursiveEntryRenderer, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, } from './components/custom';
|
|
5
|
+
export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormContext, FormField, FormRenderer, FormRendererContainer, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RecursiveEntryRenderer, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, ViewDetailsV2Container, ViewOnlyEntryRenderer, } from './components/custom';
|
|
6
6
|
export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
|
|
7
7
|
export { Box, Container, Grid, Stack } from './components/layout';
|
|
8
8
|
export * from './theme';
|
|
@@ -121,3 +121,15 @@ export declare const users: {
|
|
|
121
121
|
email: string;
|
|
122
122
|
name: string;
|
|
123
123
|
}[];
|
|
124
|
+
export declare const customerLayout: {
|
|
125
|
+
id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
properties: {
|
|
128
|
+
id: string;
|
|
129
|
+
}[];
|
|
130
|
+
sort: {
|
|
131
|
+
colId: string;
|
|
132
|
+
sort: string;
|
|
133
|
+
};
|
|
134
|
+
objectId: string;
|
|
135
|
+
};
|
|
@@ -93,6 +93,11 @@ export const mockGenericEvokeFormObject = {
|
|
|
93
93
|
name: 'User',
|
|
94
94
|
type: 'user',
|
|
95
95
|
},
|
|
96
|
+
{
|
|
97
|
+
id: 'boolean',
|
|
98
|
+
name: 'Boolean',
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
},
|
|
96
101
|
],
|
|
97
102
|
actions: [
|
|
98
103
|
{
|
|
@@ -1054,3 +1059,21 @@ export const users = [
|
|
|
1054
1059
|
name: 'User 2',
|
|
1055
1060
|
},
|
|
1056
1061
|
];
|
|
1062
|
+
// Layouts
|
|
1063
|
+
export const customerLayout = {
|
|
1064
|
+
id: 'layoutId',
|
|
1065
|
+
name: 'Generic Layout 3',
|
|
1066
|
+
properties: [
|
|
1067
|
+
{
|
|
1068
|
+
id: 'name',
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
id: 'document',
|
|
1072
|
+
},
|
|
1073
|
+
],
|
|
1074
|
+
sort: {
|
|
1075
|
+
colId: 'name',
|
|
1076
|
+
sort: 'asc',
|
|
1077
|
+
},
|
|
1078
|
+
objectId: 'customers',
|
|
1079
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
declare const _default: import("@storybook/types").ComponentAnnotations<import("@storybook/react/dist/types-0fc72a6d").R, import("../components/custom/FormV2/components/types").BaseProps & {
|
|
3
|
+
panelLayoutId?: string | undefined;
|
|
4
|
+
instanceId?: string | undefined;
|
|
5
|
+
objectId: string;
|
|
6
|
+
richTextEditor?: React.ComponentType<import("../components/custom/FormV2/components/types").SimpleEditorProps> | undefined;
|
|
7
|
+
renderHeader?: ((props: import("../components/custom").HeaderProps) => React.ReactNode) | undefined;
|
|
8
|
+
renderBody?: ((props: import("../components/custom").BodyProps) => React.ReactNode) | undefined;
|
|
9
|
+
}>;
|
|
10
|
+
export default _default;
|
|
11
|
+
export declare const ViewDetails: import("@storybook/types").AnnotatedStoryFn<import("@storybook/react/dist/types-0fc72a6d").R, import("../components/custom/FormV2/components/types").BaseProps & {
|
|
12
|
+
panelLayoutId?: string | undefined;
|
|
13
|
+
instanceId?: string | undefined;
|
|
14
|
+
objectId: string;
|
|
15
|
+
richTextEditor?: React.ComponentType<import("../components/custom/FormV2/components/types").SimpleEditorProps> | undefined;
|
|
16
|
+
renderHeader?: ((props: import("../components/custom").HeaderProps) => React.ReactNode) | undefined;
|
|
17
|
+
renderBody?: ((props: import("../components/custom").BodyProps) => React.ReactNode) | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
export declare const ViewWithSections: import("@storybook/types").AnnotatedStoryFn<import("@storybook/react/dist/types-0fc72a6d").R, import("../components/custom/FormV2/components/types").BaseProps & {
|
|
20
|
+
panelLayoutId?: string | undefined;
|
|
21
|
+
instanceId?: string | undefined;
|
|
22
|
+
objectId: string;
|
|
23
|
+
richTextEditor?: React.ComponentType<import("../components/custom/FormV2/components/types").SimpleEditorProps> | undefined;
|
|
24
|
+
renderHeader?: ((props: import("../components/custom").HeaderProps) => React.ReactNode) | undefined;
|
|
25
|
+
renderBody?: ((props: import("../components/custom").BodyProps) => React.ReactNode) | undefined;
|
|
26
|
+
}>;
|