@evoke-platform/ui-components 1.10.0-dev.11 → 1.10.0-dev.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
  2. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +14 -1
  3. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
  4. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
  5. package/dist/published/components/custom/FormV2/FormRenderer.js +2 -1
  6. package/dist/published/components/custom/FormV2/FormRendererContainer.js +1 -15
  7. package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
  8. package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
  9. package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
  10. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -2
  11. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
  12. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +5 -5
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +1 -1
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +1 -1
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +1 -1
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +1 -1
  17. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +2 -2
  18. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +1 -1
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +57 -66
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +2 -2
  21. package/dist/published/components/custom/FormV2/components/Header.d.ts +11 -3
  22. package/dist/published/components/custom/FormV2/components/Header.js +4 -4
  23. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +14 -19
  24. package/dist/published/components/custom/FormV2/components/types.d.ts +1 -0
  25. package/dist/published/components/custom/FormV2/components/utils.d.ts +2 -2
  26. package/dist/published/components/custom/FormV2/components/utils.js +7 -7
  27. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +9 -18
  28. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
  29. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +155 -0
  30. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Renderer.d.ts +13 -0
  31. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Renderer.js +140 -0
  32. package/dist/published/components/custom/ViewDetailsV2/index.d.ts +2 -0
  33. package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
  34. package/dist/published/components/custom/index.d.ts +1 -0
  35. package/dist/published/components/custom/index.js +1 -0
  36. package/dist/published/index.d.ts +1 -1
  37. package/dist/published/index.js +1 -1
  38. package/dist/published/stories/FormRendererData.d.ts +12 -0
  39. package/dist/published/stories/FormRendererData.js +23 -0
  40. package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
  41. package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
  42. package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
  43. package/dist/published/stories/ViewDetailsV2Data.js +203 -0
  44. package/dist/published/stories/sharedMswHandlers.js +49 -10
  45. package/dist/published/theme/hooks.d.ts +3 -3
  46. package/package.json +2 -2
@@ -7,7 +7,7 @@ import { Typography } from '../../../core/Typography';
7
7
  import Box from '../../../layout/Box/Box';
8
8
  import ValidationErrors from './ValidationFiles/ValidationErrors';
9
9
  const Header = (props) => {
10
- const { title, errors, hasAccordions, shouldShowValidationErrors, validationErrorsRef, form, sx } = props;
10
+ const { title, errors, hasAccordions, shouldShowValidationErrors, validationErrorsRef, sx, isDeleteForm, autosaveEnabled, } = props;
11
11
  const { width } = useFormContext();
12
12
  const { breakpoints, isBelow } = useWidgetSize({
13
13
  scroll: false,
@@ -24,7 +24,7 @@ const Header = (props) => {
24
24
  flexWrap: 'wrap',
25
25
  paddingY: isSm || isXs ? 2 : 3,
26
26
  // when rendering the default delete action, we don't want a border
27
- borderBottom: !form.id ? undefined : '1px solid #e9ecef',
27
+ borderBottom: isDeleteForm ? undefined : '1px solid #e9ecef',
28
28
  gap: isSm || isXs ? 2 : 3,
29
29
  '.evoke-form-renderer-header': {
30
30
  flex: '1 1 100%',
@@ -37,14 +37,14 @@ const Header = (props) => {
37
37
  hasAccordions && (React.createElement(Box, { sx: { flex: '0 0 auto', display: 'flex', alignItems: 'center' } },
38
38
  React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
39
39
  React.createElement(AccordionActions, { ...props })),
40
- React.createElement(Box, { sx: {
40
+ autosaveEnabled && (React.createElement(Box, { sx: {
41
41
  width: '96px',
42
42
  minWidth: '72px',
43
43
  display: 'flex',
44
44
  justifyContent: 'flex-end',
45
45
  alignItems: 'center',
46
46
  marginLeft: 0.5,
47
- } }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } })))),
47
+ } }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } }))))),
48
48
  React.createElement("div", { ref: validationErrorsRef, className: 'evoke-form-renderer-header' }, shouldShowValidationErrors && !isEmpty(errors) ? React.createElement(ValidationErrors, { errors: errors }) : null)));
49
49
  };
50
50
  // Default slot components for convenience
@@ -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) }, richTextEditor ? (React.createElement(richTextEditor, {
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 || getValues(entryId), onChange: handleChange, onBlur: () => {
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);
@@ -93,6 +93,7 @@ export type EntryRendererProps = {
93
93
  };
94
94
  export type SectionsProps = {
95
95
  entry: ExpandedSections;
96
+ readOnly?: boolean;
96
97
  };
97
98
  export type DocumentData = {
98
99
  id: string;
@@ -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, formValues: FieldValues, instance?: FieldValues) => boolean;
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, formData: FieldValues, instance?: FieldValues): Sections | Columns | null;
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, formValues, instance) => {
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, formData, instance) {
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, formData, instance)) {
679
+ if (sectionEntry.visibility && !entryIsVisible(sectionEntry, instance, formData)) {
680
680
  return false;
681
681
  }
682
- else if (filterEmptySections(sectionEntry, formData, instance)) {
682
+ else if (filterEmptySections(sectionEntry, instance, formData)) {
683
683
  return true;
684
684
  }
685
685
  }
686
- else if (entryIsVisible(sectionEntry, formData, instance)) {
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, formData, instance)) {
706
+ if (filterEmptySections(columnEntry, instance, formData)) {
707
707
  hasVisibleEntry = true;
708
708
  break;
709
709
  }
710
710
  }
711
711
  else {
712
- if (entryIsVisible(columnEntry, formData, instance)) {
712
+ if (entryIsVisible(columnEntry, instance, formData)) {
713
713
  hasVisibleEntry = true;
714
714
  break;
715
715
  }
@@ -123,8 +123,7 @@ describe('FormRendererContainer', () => {
123
123
  license: null,
124
124
  });
125
125
  }));
126
- render(React.createElement(MemoryRouter, null,
127
- React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
126
+ render(React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' }));
128
127
  await waitFor(() => {
129
128
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
130
129
  });
@@ -174,8 +173,7 @@ describe('FormRendererContainer', () => {
174
173
  license: null,
175
174
  });
176
175
  }));
177
- render(React.createElement(MemoryRouter, null,
178
- React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
176
+ render(React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' }));
179
177
  await waitFor(() => {
180
178
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
181
179
  });
@@ -222,8 +220,7 @@ describe('FormRendererContainer', () => {
222
220
  autosaveActionSpy();
223
221
  return HttpResponse.json({ error: 'Save failed' }, { status: 500 });
224
222
  }));
225
- render(React.createElement(MemoryRouter, null,
226
- React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
223
+ render(React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' }));
227
224
  await waitFor(() => {
228
225
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
229
226
  });
@@ -267,8 +264,7 @@ describe('FormRendererContainer', () => {
267
264
  license: null,
268
265
  });
269
266
  }));
270
- render(React.createElement(MemoryRouter, null,
271
- React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
267
+ render(React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' }));
272
268
  await waitFor(() => {
273
269
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
274
270
  });
@@ -321,8 +317,7 @@ describe('FormRendererContainer', () => {
321
317
  },
322
318
  });
323
319
  }));
324
- render(React.createElement(MemoryRouter, null,
325
- React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' })));
320
+ render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
326
321
  await waitFor(() => {
327
322
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
328
323
  });
@@ -382,8 +377,7 @@ describe('FormRendererContainer', () => {
382
377
  address: body.input.address,
383
378
  });
384
379
  }));
385
- render(React.createElement(MemoryRouter, null,
386
- React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' })));
380
+ render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
387
381
  await waitFor(() => {
388
382
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
389
383
  });
@@ -444,8 +438,7 @@ describe('FormRendererContainer', () => {
444
438
  address: body.input.address,
445
439
  });
446
440
  }));
447
- render(React.createElement(MemoryRouter, null,
448
- React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' })));
441
+ render(React.createElement(FormRendererContainer, { objectId: 'license', formId: 'licenseForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-license' }));
449
442
  await waitFor(() => {
450
443
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
451
444
  });
@@ -492,8 +485,7 @@ describe('FormRendererContainer', () => {
492
485
  autosaveActionId: '_autosave',
493
486
  });
494
487
  }));
495
- render(React.createElement(MemoryRouter, null,
496
- React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
488
+ render(React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' }));
497
489
  await waitFor(() => {
498
490
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
499
491
  });
@@ -526,8 +518,7 @@ describe('FormRendererContainer', () => {
526
518
  });
527
519
  }));
528
520
  const user = userEvent.setup();
529
- render(React.createElement(MemoryRouter, null,
530
- React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' })));
521
+ render(React.createElement(FormRendererContainer, { objectId: 'specialty', formId: 'specialtyForm', dataType: 'objectInstances', actionId: '_update', instanceId: 'test-instance' }));
531
522
  await waitFor(() => {
532
523
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
533
524
  });
@@ -0,0 +1,3 @@
1
+ import { EntryRendererProps } from '../FormV2/components/types';
2
+ declare function ViewOnlyEntryRenderer(props: EntryRendererProps): any;
3
+ export default ViewOnlyEntryRenderer;
@@ -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;
@@ -0,0 +1,2 @@
1
+ export { default as ViewOnlyEntryRenderer } from './InstanceEntryRenderer';
2
+ export { default as ViewDetailsV2Container } from './ViewDetailsV2Renderer';
@@ -0,0 +1,2 @@
1
+ export { default as ViewOnlyEntryRenderer } from './InstanceEntryRenderer';
2
+ export { default as ViewDetailsV2Container } from './ViewDetailsV2Renderer';
@@ -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';