@evoke-platform/ui-components 1.13.0-dev.7 → 1.14.0
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/CriteriaBuilder/CriteriaBuilder.d.ts +4 -4
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +72 -145
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +67 -189
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.d.ts +6 -6
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +25 -12
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.d.ts +5 -4
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.js +22 -34
- package/dist/published/components/custom/CriteriaBuilder/types.d.ts +11 -2
- package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +34 -6
- package/dist/published/components/custom/CriteriaBuilder/utils.js +89 -18
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +3 -6
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
- package/dist/published/components/custom/Form/utils.d.ts +0 -1
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +1 -2
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -2
- package/dist/published/components/custom/FormV2/FormRenderer.js +29 -26
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +3 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +88 -95
- package/dist/published/components/custom/FormV2/components/Body.js +1 -1
- package/dist/published/components/custom/FormV2/components/Footer.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +0 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +0 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +143 -86
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +4 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +186 -106
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +49 -36
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +2 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +32 -51
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +3 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +38 -40
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +21 -17
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +169 -95
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +6 -12
- package/dist/published/components/custom/FormV2/components/FormSections.js +0 -1
- package/dist/published/components/custom/FormV2/components/Header.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Header.js +19 -8
- package/dist/published/components/custom/FormV2/components/HtmlView.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/HtmlView.js +46 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +1 -2
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +20 -46
- package/dist/published/components/custom/FormV2/components/types.d.ts +1 -6
- package/dist/published/components/custom/FormV2/components/utils.d.ts +11 -11
- package/dist/published/components/custom/FormV2/components/utils.js +104 -181
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +17 -50
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +131 -40
- package/dist/published/components/custom/HistoryLog/HistoryData.js +1 -2
- package/dist/published/components/custom/HistoryLog/index.js +1 -2
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +1 -2
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +22 -61
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +5 -8
- package/dist/published/stories/Backdrop.stories.d.ts +2 -2
- package/dist/published/stories/CriteriaBuilder.stories.js +22 -70
- package/dist/published/stories/FormLabel.stories.d.ts +2 -2
- package/dist/published/stories/FormRenderer.stories.d.ts +3 -3
- package/dist/published/stories/FormRendererContainer.stories.d.ts +15 -5
- package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +9 -0
- package/dist/published/theme/hooks.d.ts +1 -2
- package/package.json +11 -17
- package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.d.ts +0 -5
- package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.js +0 -21
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { useApiServices, useAuthenticationContext, } from '@evoke-platform/context';
|
|
2
2
|
import { WarningRounded } from '@mui/icons-material';
|
|
3
|
-
import { useQuery } from '@tanstack/react-query';
|
|
4
|
-
import DOMPurify from 'dompurify';
|
|
5
3
|
import { isEmpty } from 'lodash';
|
|
6
|
-
import React, { useMemo } from 'react';
|
|
4
|
+
import React, { useEffect, useMemo } from 'react';
|
|
7
5
|
import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
|
|
8
|
-
import {
|
|
6
|
+
import { TextField, Typography } from '../../../core';
|
|
9
7
|
import { Box } from '../../../layout';
|
|
10
8
|
import FormField from '../../FormField';
|
|
11
9
|
import AccordionSections from './AccordionSections';
|
|
@@ -19,7 +17,8 @@ import { Image } from './FormFieldTypes/Image';
|
|
|
19
17
|
import ObjectPropertyInput from './FormFieldTypes/relatedObjectFiles/ObjectPropertyInput';
|
|
20
18
|
import UserProperty from './FormFieldTypes/UserProperty';
|
|
21
19
|
import FormSections from './FormSections';
|
|
22
|
-
import { entryIsVisible,
|
|
20
|
+
import { entryIsVisible, fetchCollectionData, filterEmptySections, getEntryId, getFieldDefinition, isAddressProperty, isOptionEqualToValue, updateCriteriaInputs, } from './utils';
|
|
21
|
+
import HtmlView from './HtmlView';
|
|
23
22
|
function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors, validation) {
|
|
24
23
|
return {
|
|
25
24
|
inputId: entryId,
|
|
@@ -39,7 +38,7 @@ function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, displ
|
|
|
39
38
|
}
|
|
40
39
|
export function RecursiveEntryRenderer(props) {
|
|
41
40
|
const { entry } = props;
|
|
42
|
-
const { object, getValues, errors, instance, richTextEditor: RichTextEditor, parameters, handleChange, onAutosave, fieldHeight, triggerFieldReset, associatedObject, width, } = useFormContext();
|
|
41
|
+
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor: RichTextEditor, parameters, handleChange, onAutosave, fieldHeight, triggerFieldReset, associatedObject, width, } = useFormContext();
|
|
43
42
|
const { isBelow, breakpoints } = useWidgetSize({
|
|
44
43
|
scroll: false,
|
|
45
44
|
defaultWidth: width,
|
|
@@ -51,41 +50,19 @@ export function RecursiveEntryRenderer(props) {
|
|
|
51
50
|
const entryId = getEntryId(entry) || 'defaultId';
|
|
52
51
|
const display = 'display' in entry ? entry.display : undefined;
|
|
53
52
|
const fieldValue = entry.type === 'readonlyField' ? instance?.[entryId] : getValues ? getValues(entryId) : undefined;
|
|
53
|
+
const initialMiddleObjectInstances = fetchedOptions[`${entryId}InitialMiddleObjectInstances`];
|
|
54
|
+
const middleObject = fetchedOptions[`${entryId}MiddleObject`];
|
|
54
55
|
const fieldDefinition = useMemo(() => {
|
|
55
56
|
if (!object)
|
|
56
57
|
return undefined;
|
|
57
58
|
return getFieldDefinition(entry, object, parameters);
|
|
58
59
|
}, [entry, parameters, object]);
|
|
59
60
|
const validation = fieldDefinition?.validation || {};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
fieldDefinition?.type === 'collection' &&
|
|
66
|
-
fieldDefinition?.manyToManyPropertyId),
|
|
67
|
-
meta: {
|
|
68
|
-
errorMessage: 'Failed to fetch middle object: ',
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
const { data: initialMiddleObjectInstances = [], isLoading: isLoadingInstances } = useQuery({
|
|
72
|
-
queryKey: [
|
|
73
|
-
fieldDefinition?.objectId,
|
|
74
|
-
instance?.id,
|
|
75
|
-
fieldDefinition?.relatedPropertyId,
|
|
76
|
-
'InitialMiddleObjectInstances',
|
|
77
|
-
],
|
|
78
|
-
queryFn: () => fetchInitialMiddleObjectInstances(apiServices, fieldDefinition, instance?.id),
|
|
79
|
-
staleTime: Infinity,
|
|
80
|
-
enabled: !!(fieldDefinition?.objectId &&
|
|
81
|
-
instance?.id &&
|
|
82
|
-
fieldDefinition?.type === 'collection' &&
|
|
83
|
-
fieldDefinition?.manyToManyPropertyId &&
|
|
84
|
-
fieldDefinition?.relatedPropertyId),
|
|
85
|
-
meta: {
|
|
86
|
-
errorMessage: 'Failed to fetch middle object instances: ',
|
|
87
|
-
},
|
|
88
|
-
});
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (fieldDefinition?.type === 'collection' && fieldDefinition?.manyToManyPropertyId && instance) {
|
|
63
|
+
fetchCollectionData(apiServices, fieldDefinition, setFetchedOptions, instance.id, fetchedOptions, initialMiddleObjectInstances);
|
|
64
|
+
}
|
|
65
|
+
}, [fieldDefinition, instance]);
|
|
89
66
|
if (associatedObject?.propertyId === entryId)
|
|
90
67
|
return null;
|
|
91
68
|
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
@@ -93,9 +70,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
93
70
|
return null;
|
|
94
71
|
}
|
|
95
72
|
if (entry.type === 'content') {
|
|
96
|
-
return
|
|
97
|
-
fontFamily: 'Roboto, Helvetica, Arial, sans-serif',
|
|
98
|
-
} }));
|
|
73
|
+
return React.createElement(HtmlView, { value: entry.html });
|
|
99
74
|
}
|
|
100
75
|
else if ((entry.type === 'input' || entry.type === 'readonlyField' || entry.type === 'inputField') &&
|
|
101
76
|
fieldDefinition) {
|
|
@@ -110,9 +85,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
110
85
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
111
86
|
React.createElement(ObjectPropertyInput, { relatedObjectId: !fieldDefinition.objectId ? display?.relatedObjectId : fieldDefinition.objectId, fieldDefinition: fieldDefinition, id: entryId, mode: display?.mode || 'default', error: !!errors?.[entryId], displayOption: display?.relatedObjectDisplay || 'dialogBox', initialValue: fieldValue, readOnly: entry.type === 'readonlyField', filter: 'criteria' in validation && validation.criteria
|
|
112
87
|
? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
|
|
113
|
-
:
|
|
114
|
-
? updateCriteriaInputs(entry.display?.criteria, getValues(), userAccount, instance)
|
|
115
|
-
: undefined, sortBy: typeof display?.defaultValue === 'object' && 'sortBy' in display.defaultValue
|
|
88
|
+
: undefined, sortBy: typeof display?.defaultValue === 'object' && 'sortBy' in display.defaultValue
|
|
116
89
|
? display?.defaultValue.sortBy
|
|
117
90
|
: undefined, orderBy: typeof display?.defaultValue === 'object' && 'orderBy' in display.defaultValue
|
|
118
91
|
? display?.defaultValue.orderBy
|
|
@@ -126,11 +99,12 @@ export function RecursiveEntryRenderer(props) {
|
|
|
126
99
|
}
|
|
127
100
|
else if (fieldDefinition.type === 'collection') {
|
|
128
101
|
if (fieldDefinition?.manyToManyPropertyId) {
|
|
129
|
-
if (!isEmpty(middleObject)) {
|
|
130
|
-
return
|
|
131
|
-
React.createElement(DropdownRepeatableField, { initialMiddleObjectInstances:
|
|
102
|
+
if (middleObject && !isEmpty(middleObject)) {
|
|
103
|
+
return (initialMiddleObjectInstances && (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
104
|
+
React.createElement(DropdownRepeatableField, { initialMiddleObjectInstances: fetchedOptions[`${entryId}MiddleObjectInstances`] ||
|
|
105
|
+
initialMiddleObjectInstances, fieldDefinition: fieldDefinition, id: entryId, middleObject: middleObject, readOnly: entry.type === 'readonlyField', criteria: 'criteria' in validation && validation.criteria
|
|
132
106
|
? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
|
|
133
|
-
: undefined, hasDescription: !!display?.description })));
|
|
107
|
+
: undefined, hasDescription: !!display?.description }))));
|
|
134
108
|
}
|
|
135
109
|
else {
|
|
136
110
|
// when in the builder preview, the middle object won't be fetched so instead show an empty field
|
|
@@ -160,7 +134,7 @@ export function RecursiveEntryRenderer(props) {
|
|
|
160
134
|
}
|
|
161
135
|
else if (fieldDefinition.type === 'document' || fieldDefinition.type === 'file') {
|
|
162
136
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
163
|
-
React.createElement(Document, { id: entryId,
|
|
137
|
+
React.createElement(Document, { id: entryId, error: !!errors?.[entryId], value: fieldValue, canUpdateProperty: !(entry.type === 'readonlyField'), hasDescription: !!display?.description, validate: validation })));
|
|
164
138
|
}
|
|
165
139
|
else if (fieldDefinition.type === 'criteria') {
|
|
166
140
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
@@ -12,10 +12,9 @@ export type FieldAddress = {
|
|
|
12
12
|
export type AccessCheck = {
|
|
13
13
|
result: boolean;
|
|
14
14
|
};
|
|
15
|
-
export type
|
|
15
|
+
export type SavedDocumentReference = {
|
|
16
16
|
id: string;
|
|
17
17
|
name: string;
|
|
18
|
-
unsaved?: boolean;
|
|
19
18
|
};
|
|
20
19
|
export type Document = {
|
|
21
20
|
id: string;
|
|
@@ -113,7 +112,3 @@ export type InstanceLink = {
|
|
|
113
112
|
id: string;
|
|
114
113
|
objectId: string;
|
|
115
114
|
};
|
|
116
|
-
export type FileUploadBatchResult = {
|
|
117
|
-
errorMessage?: string;
|
|
118
|
-
successfulUploads: DocumentReference[];
|
|
119
|
-
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { Action, ApiServices, Column, Columns, EvokeForm, FormEntry, InputField, InputParameter, InputParameterReference, Obj, ObjectInstance,
|
|
2
|
+
import { Action, ApiServices, Column, Columns, EvokeForm, FormEntry, InputField, InputParameter, InputParameterReference, Obj, ObjectInstance, Property, Section, Sections, UserAccount } from '@evoke-platform/context';
|
|
3
3
|
import { LocalDateTime } from '@js-joda/core';
|
|
4
4
|
import { FieldErrors, FieldValues } from 'react-hook-form';
|
|
5
5
|
import { ObjectProperty } from '../../../../types';
|
|
6
6
|
import { AutocompleteOption } from '../../../core';
|
|
7
|
-
import {
|
|
7
|
+
import { InstanceLink, SavedDocumentReference } from './types';
|
|
8
8
|
export declare const scrollIntoViewWithOffset: (el: HTMLElement, offset: number, container?: HTMLElement) => void;
|
|
9
9
|
export declare const normalizeDateTime: (dateTime: LocalDateTime) => string;
|
|
10
10
|
export declare function isAddressProperty(key: string): boolean;
|
|
@@ -41,8 +41,7 @@ export declare function getDefaultPages(parameters: InputParameter[], defaultPag
|
|
|
41
41
|
[x: string]: string;
|
|
42
42
|
}>;
|
|
43
43
|
export declare function updateCriteriaInputs(criteria: Record<string, unknown>, data: Record<string, unknown>, user?: UserAccount, instance?: Record<string, unknown>): Record<string, unknown>;
|
|
44
|
-
export declare
|
|
45
|
-
export declare const fetchInitialMiddleObjectInstances: (apiServices: ApiServices, fieldDefinition: InputParameter | Property, instanceId: string) => Promise<ObjectInstance[]>;
|
|
44
|
+
export declare function fetchCollectionData(apiServices: ApiServices, fieldDefinition: InputParameter | Property, setFetchedOptions: (newData: FieldValues) => void, instanceId?: string, fetchedOptions?: Record<string, unknown>, initialMiddleObjectInstances?: ObjectInstance[]): Promise<void>;
|
|
46
45
|
export declare const getErrorCountForSection: (section: Section | Column, errors?: FieldErrors) => number;
|
|
47
46
|
export declare const propertyToParameter: (property: Property) => InputParameter;
|
|
48
47
|
export declare const propertyValidationToParameterValidation: (property: Property) => InputParameter['validation'];
|
|
@@ -50,13 +49,16 @@ export declare const convertPropertiesToParams: (object: Obj) => InputParameter[
|
|
|
50
49
|
export declare function getUnnestedEntries(entries: FormEntry[]): FormEntry[];
|
|
51
50
|
export declare const isEmptyWithDefault: (fieldValue: unknown, entry: InputParameterReference | InputField, instance: Record<string, unknown> | object) => boolean | "" | 0 | undefined;
|
|
52
51
|
export declare const docProperties: Property[];
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Upload files using the POST /files endpoint for sys__file objects
|
|
54
|
+
*/
|
|
55
|
+
export declare const uploadFiles: (files: (File | SavedDocumentReference)[], apiServices: ApiServices, actionId?: string, metadata?: Record<string, string>, linkTo?: InstanceLink) => Promise<SavedDocumentReference[]>;
|
|
54
56
|
/**
|
|
55
57
|
* Creates file links for uploaded files by calling the objects endpoint with sys__fileLink
|
|
56
58
|
* This is used after instance creation when the instance ID becomes available
|
|
57
59
|
*/
|
|
58
|
-
export declare const createFileLinks: (
|
|
59
|
-
export declare const uploadDocuments: (files: (File |
|
|
60
|
+
export declare const createFileLinks: (files: SavedDocumentReference[], linkedInstance: InstanceLink, apiServices: ApiServices) => Promise<void>;
|
|
61
|
+
export declare const uploadDocuments: (files: (File | SavedDocumentReference)[], metadata: Record<string, string>, apiServices: ApiServices, instanceId: string, objectId: string) => Promise<SavedDocumentReference[]>;
|
|
60
62
|
export declare const deleteDocuments: (submittedFields: FieldValues, requestSuccess: boolean, apiServices: ApiServices, object: Obj, instance: FieldValues, action?: Action, setSnackbarError?: React.Dispatch<React.SetStateAction<{
|
|
61
63
|
showAlert: boolean;
|
|
62
64
|
message?: string;
|
|
@@ -82,10 +84,9 @@ export declare function formatSubmission(submission: FieldValues, apiServices?:
|
|
|
82
84
|
}>>, associatedObject?: {
|
|
83
85
|
instanceId: string;
|
|
84
86
|
propertyId: string;
|
|
85
|
-
objectId?: string;
|
|
86
87
|
}, parameters?: InputParameter[]): Promise<FieldValues>;
|
|
87
88
|
export declare function filterEmptySections(entry: Sections | Columns, instance?: FieldValues, formData?: FieldValues): Sections | Columns | null;
|
|
88
|
-
export declare function assignIdsToSectionsAndRichText(entries: FormEntry[]
|
|
89
|
+
export declare function assignIdsToSectionsAndRichText(entries: FormEntry[], object: Obj, parameters?: InputParameter[]): FormEntry[];
|
|
89
90
|
/**
|
|
90
91
|
* Converts a plain text string to RTF format suitable for a RichTextEditor.
|
|
91
92
|
*
|
|
@@ -99,6 +100,5 @@ export declare function assignIdsToSectionsAndRichText(entries: FormEntry[] | Pa
|
|
|
99
100
|
* This ensures that any plain text input will be safely represented in RTF without losing formatting or characters.
|
|
100
101
|
*/
|
|
101
102
|
export declare function plainTextToRtf(plainText: string): string;
|
|
102
|
-
export declare function getFieldDefinition(entry: FormEntry
|
|
103
|
+
export declare function getFieldDefinition(entry: FormEntry, object: Obj, parameters?: InputParameter[]): InputParameter | Property | undefined;
|
|
103
104
|
export declare function obfuscateValue(value: unknown, property?: Partial<Property> | Partial<ObjectProperty>): unknown;
|
|
104
|
-
export declare function useFormById(formId: string, apiServices: ApiServices, errorMessage?: string): import("@tanstack/react-query/build/legacy/types").UseQueryResult<EvokeForm, Error>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { LocalDateTime } from '@js-joda/core';
|
|
2
|
-
import { useQuery } from '@tanstack/react-query';
|
|
3
2
|
import { jsonLogic } from 'json-logic-js-graphql';
|
|
4
3
|
import { get, isArray, isEmpty, isObject, omit, pick, startCase, transform } from 'lodash';
|
|
5
4
|
import { DateTime } from 'luxon';
|
|
@@ -116,27 +115,9 @@ export const getEntryId = (entry) => {
|
|
|
116
115
|
? entry.input.id
|
|
117
116
|
: undefined;
|
|
118
117
|
};
|
|
119
|
-
const getEntryType = (entry, parameters) => {
|
|
120
|
-
if (entry?.type === 'inputField') {
|
|
121
|
-
return entry?.input?.type;
|
|
122
|
-
}
|
|
123
|
-
else if (entry?.type === 'input') {
|
|
124
|
-
// For 'input' type entries, look up the parameter by parameterId
|
|
125
|
-
const parameter = parameters?.find((param) => param.id === entry.parameterId);
|
|
126
|
-
return parameter?.type;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
118
|
export function getPrefixedUrl(url) {
|
|
130
119
|
const wcsMatchers = ['/apps', '/pages', '/widgets'];
|
|
131
|
-
const dataMatchers = [
|
|
132
|
-
'/objects',
|
|
133
|
-
'/correspondenceTemplates',
|
|
134
|
-
'/documents',
|
|
135
|
-
'/payments',
|
|
136
|
-
'/forms',
|
|
137
|
-
'/locations',
|
|
138
|
-
'/files',
|
|
139
|
-
];
|
|
120
|
+
const dataMatchers = ['/objects', '/correspondenceTemplates', '/documents', '/payments', '/forms', '/locations'];
|
|
140
121
|
const signalrMatchers = ['/hubs'];
|
|
141
122
|
const accessManagementMatchers = ['/users'];
|
|
142
123
|
const workflowMatchers = ['/workflows'];
|
|
@@ -351,19 +332,31 @@ export function updateCriteriaInputs(criteria, data, user, instance) {
|
|
|
351
332
|
},
|
|
352
333
|
}));
|
|
353
334
|
}
|
|
354
|
-
export
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
335
|
+
export async function fetchCollectionData(apiServices, fieldDefinition, setFetchedOptions, instanceId, fetchedOptions, initialMiddleObjectInstances) {
|
|
336
|
+
try {
|
|
337
|
+
if ((fetchedOptions && !fetchedOptions[`${fieldDefinition.id}MiddleObject`]) || !fetchedOptions) {
|
|
338
|
+
const fetchedMiddleObject = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/effective?sanitizedVersion=true`), {
|
|
339
|
+
params: {
|
|
340
|
+
filter: { fields: ['properties', 'actions', 'rootObjectId'] },
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
setFetchedOptions({ [`${fieldDefinition.id}MiddleObject`]: fetchedMiddleObject });
|
|
344
|
+
}
|
|
345
|
+
// Fetch the initial middle object instances
|
|
346
|
+
const filter = instanceId ? getMiddleObjectFilter(fieldDefinition, instanceId) : {};
|
|
347
|
+
if (!isArray(initialMiddleObjectInstances)) {
|
|
348
|
+
const fetchedInitialMiddleObjectInstances = await apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances`), {
|
|
349
|
+
params: { filter: JSON.stringify(filter) },
|
|
350
|
+
});
|
|
351
|
+
setFetchedOptions({
|
|
352
|
+
[`${fieldDefinition.id}InitialMiddleObjectInstances`]: fetchedInitialMiddleObjectInstances,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
console.error('Error fetching collection data:', error);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
367
360
|
export const getErrorCountForSection = (section, errors) => {
|
|
368
361
|
const entries = section.entries || [];
|
|
369
362
|
return isArray(section.entries)
|
|
@@ -476,90 +469,53 @@ export const docProperties = [
|
|
|
476
469
|
type: 'string',
|
|
477
470
|
},
|
|
478
471
|
];
|
|
479
|
-
|
|
472
|
+
/**
|
|
473
|
+
* Upload files using the POST /files endpoint for sys__file objects
|
|
474
|
+
*/
|
|
475
|
+
export const uploadFiles = async (files, apiServices, actionId = '_create', metadata, linkTo) => {
|
|
480
476
|
// Separate already uploaded files from files that need uploading
|
|
481
477
|
const alreadyUploaded = files.filter((file) => !('size' in file));
|
|
482
478
|
const filesToUpload = files.filter((file) => 'size' in file);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const uploadPromises = [];
|
|
486
|
-
for (const file of filesToUpload) {
|
|
479
|
+
// Upload all files in parallel
|
|
480
|
+
const uploadPromises = filesToUpload.map(async (file) => {
|
|
487
481
|
const formData = new FormData();
|
|
488
482
|
formData.append('file', file);
|
|
489
483
|
formData.append('actionId', actionId);
|
|
490
484
|
formData.append('objectId', 'sys__file');
|
|
491
|
-
|
|
485
|
+
if (metadata) {
|
|
486
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
487
|
+
formData.append(key, value);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
492
490
|
if (linkTo) {
|
|
493
491
|
formData.append('linkTo', JSON.stringify(linkTo));
|
|
494
492
|
}
|
|
495
|
-
const
|
|
496
|
-
try {
|
|
497
|
-
const fileInstance = await apiServices.post(getPrefixedUrl(`/files`), formData);
|
|
498
|
-
return {
|
|
499
|
-
id: fileInstance.id,
|
|
500
|
-
name: fileInstance.name,
|
|
501
|
-
unsaved: true,
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
catch (error) {
|
|
505
|
-
console.error(`Failed to upload file ${file.name}:`, error);
|
|
506
|
-
failedUpload = true;
|
|
507
|
-
}
|
|
508
|
-
})();
|
|
509
|
-
uploadPromises.push(uploadPromise);
|
|
510
|
-
}
|
|
511
|
-
if (!shortCircuit) {
|
|
512
|
-
// Wait for all upload attempts to complete (successes and failures)
|
|
513
|
-
const uploadResults = await Promise.allSettled(uploadPromises);
|
|
514
|
-
const uploadedFiles = uploadResults
|
|
515
|
-
.filter((result) => result.status === 'fulfilled' && !!result.value)
|
|
516
|
-
.map((result) => result.value);
|
|
517
|
-
const failedCount = filesToUpload.length - uploadedFiles.length;
|
|
493
|
+
const fileInstance = await apiServices.post(getPrefixedUrl(`/files`), formData);
|
|
518
494
|
return {
|
|
519
|
-
|
|
520
|
-
|
|
495
|
+
id: fileInstance.id,
|
|
496
|
+
name: fileInstance.name,
|
|
521
497
|
};
|
|
522
|
-
}
|
|
498
|
+
});
|
|
523
499
|
const uploadedFiles = await Promise.all(uploadPromises);
|
|
524
|
-
|
|
525
|
-
return {
|
|
526
|
-
successfulUploads: [],
|
|
527
|
-
errorMessage: 'An error occurred when uploading files',
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
return {
|
|
531
|
-
successfulUploads: [...alreadyUploaded, ...uploadedFiles],
|
|
532
|
-
};
|
|
500
|
+
return [...alreadyUploaded, ...uploadedFiles];
|
|
533
501
|
};
|
|
534
502
|
/**
|
|
535
503
|
* Creates file links for uploaded files by calling the objects endpoint with sys__fileLink
|
|
536
504
|
* This is used after instance creation when the instance ID becomes available
|
|
537
505
|
*/
|
|
538
|
-
export const createFileLinks = async (
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
},
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
catch (error) {
|
|
555
|
-
console.error(`Failed to create file link for ${file.name}:`, error);
|
|
556
|
-
// The file remains unlinked and can be retried later
|
|
557
|
-
}
|
|
558
|
-
})();
|
|
559
|
-
linkPromises.push(linkPromise);
|
|
560
|
-
}
|
|
561
|
-
// Wait for all linking attempts to complete (successes and failures)
|
|
562
|
-
await Promise.allSettled(linkPromises);
|
|
506
|
+
export const createFileLinks = async (files, linkedInstance, apiServices) => {
|
|
507
|
+
const linkPromises = files.map(async (file) => {
|
|
508
|
+
await apiServices.post(getPrefixedUrl(`/objects/sys__fileLink/instances`), {
|
|
509
|
+
name: 'File Link',
|
|
510
|
+
file: { id: file.id, name: file.name },
|
|
511
|
+
linkedInstance,
|
|
512
|
+
}, {
|
|
513
|
+
params: {
|
|
514
|
+
actionId: '_create',
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
await Promise.all(linkPromises);
|
|
563
519
|
};
|
|
564
520
|
export const uploadDocuments = async (files, metadata, apiServices, instanceId, objectId) => {
|
|
565
521
|
const allDocuments = [];
|
|
@@ -587,29 +543,20 @@ export const uploadDocuments = async (files, metadata, apiServices, instanceId,
|
|
|
587
543
|
export const deleteDocuments = async (submittedFields, requestSuccess, apiServices, object, instance, action, setSnackbarError) => {
|
|
588
544
|
const documentProperties = action?.parameters
|
|
589
545
|
? action.parameters.filter((param) => ['document', 'file'].includes(param.type))
|
|
590
|
-
: object
|
|
546
|
+
: object?.properties?.filter((prop) => ['document', 'file'].includes(prop.type));
|
|
591
547
|
for (const docProperty of documentProperties ?? []) {
|
|
592
548
|
const savedValue = submittedFields[docProperty.id];
|
|
593
|
-
const originalValue = instance[docProperty.id];
|
|
549
|
+
const originalValue = instance?.[docProperty.id];
|
|
594
550
|
const documentsToRemove = requestSuccess
|
|
595
551
|
? (originalValue?.filter((file) => !savedValue?.some((f) => f.id === file.id)) ?? [])
|
|
596
552
|
: (savedValue?.filter((file) => !originalValue?.some((f) => f.id === file.id)) ?? []);
|
|
597
553
|
for (const doc of documentsToRemove) {
|
|
598
554
|
try {
|
|
599
|
-
//
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
await apiServices.post(getPrefixedUrl(`/files/${doc.id}/unlinkInstance`), {
|
|
605
|
-
linkedInstanceId: instance.id,
|
|
606
|
-
linkedObjectId: object.id,
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
// For document properties, delete the document
|
|
611
|
-
await apiServices.delete(getPrefixedUrl(`/objects/${object.id}/instances/${instance.id}/documents/${doc.id}`));
|
|
612
|
-
}
|
|
555
|
+
// Use different endpoints based on property type
|
|
556
|
+
const deleteEndpoint = docProperty.type === 'file'
|
|
557
|
+
? getPrefixedUrl(`/files/${doc.id}`)
|
|
558
|
+
: getPrefixedUrl(`/objects/${object.id}/instances/${instance.id}/documents/${doc.id}`);
|
|
559
|
+
await apiServices?.delete(deleteEndpoint);
|
|
613
560
|
}
|
|
614
561
|
catch (error) {
|
|
615
562
|
if (error) {
|
|
@@ -624,28 +571,16 @@ export const deleteDocuments = async (submittedFields, requestSuccess, apiServic
|
|
|
624
571
|
}
|
|
625
572
|
}
|
|
626
573
|
};
|
|
627
|
-
|
|
628
|
-
if (
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
view_permission: '',
|
|
638
|
-
...entry?.documentMetadata,
|
|
639
|
-
}, apiServices, instanceId, objectId);
|
|
640
|
-
return { successfulUploads: docs };
|
|
641
|
-
}
|
|
642
|
-
catch (error) {
|
|
643
|
-
console.error('Error uploading documents:', error);
|
|
644
|
-
return { successfulUploads: [], errorMessage: 'Error uploading documents' };
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return { successfulUploads: [] };
|
|
648
|
-
}
|
|
574
|
+
const getEntryType = (entry, parameters) => {
|
|
575
|
+
if (entry?.type === 'inputField') {
|
|
576
|
+
return entry.input.type;
|
|
577
|
+
}
|
|
578
|
+
else if (entry?.type === 'input') {
|
|
579
|
+
// For 'input' type entries, look up the parameter by parameterId
|
|
580
|
+
const parameter = parameters?.find((param) => param.id === entry.parameterId);
|
|
581
|
+
return parameter?.type;
|
|
582
|
+
}
|
|
583
|
+
};
|
|
649
584
|
/**
|
|
650
585
|
* Transforms a form submission into a format safe for API submission.
|
|
651
586
|
*
|
|
@@ -663,45 +598,45 @@ export async function formatSubmission(submission, apiServices, objectId, instan
|
|
|
663
598
|
if (associatedObject) {
|
|
664
599
|
delete submission[associatedObject.propertyId];
|
|
665
600
|
}
|
|
666
|
-
const allEntries = getUnnestedEntries(form?.entries ?? []);
|
|
601
|
+
const allEntries = getUnnestedEntries(form?.entries ?? []) ?? [];
|
|
667
602
|
for (const [key, value] of Object.entries(submission)) {
|
|
668
603
|
const entry = allEntries?.find((entry) => getEntryId(entry) === key);
|
|
669
604
|
if (isArray(value)) {
|
|
670
|
-
|
|
671
|
-
// The only array types we need to handle specially are 'file' and 'document'.
|
|
672
|
-
if (propertyType !== 'file' && propertyType !== 'document') {
|
|
673
|
-
continue;
|
|
674
|
-
}
|
|
675
|
-
// Only upload if array contains File instances (not SavedDocumentReference).
|
|
605
|
+
// Only upload if array contains File instances (not SavedDocumentReference)
|
|
676
606
|
const fileInArray = value.some((item) => item instanceof File);
|
|
677
607
|
if (fileInArray && apiServices && objectId) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
608
|
+
// Determine property type from the entry
|
|
609
|
+
const parameterType = getEntryType(entry, parameters);
|
|
610
|
+
try {
|
|
611
|
+
let uploadedDocuments = [];
|
|
612
|
+
if (parameterType === 'file') {
|
|
613
|
+
uploadedDocuments = await uploadFiles(value, apiServices, '_create', {
|
|
614
|
+
...entry?.documentMetadata,
|
|
615
|
+
},
|
|
616
|
+
// Only pass linkTo if instanceId exists (update action)
|
|
617
|
+
instanceId ? { id: instanceId, objectId } : undefined);
|
|
618
|
+
}
|
|
619
|
+
else if (parameterType === 'document' && instanceId) {
|
|
620
|
+
uploadedDocuments = await uploadDocuments(value, {
|
|
621
|
+
type: '',
|
|
622
|
+
view_permission: '',
|
|
623
|
+
...entry?.documentMetadata,
|
|
624
|
+
}, apiServices, instanceId, objectId);
|
|
625
|
+
}
|
|
626
|
+
submission[key] = uploadedDocuments;
|
|
687
627
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
628
|
+
catch (err) {
|
|
629
|
+
if (err) {
|
|
630
|
+
setSnackbarError &&
|
|
631
|
+
setSnackbarError({
|
|
632
|
+
showAlert: true,
|
|
633
|
+
message: `An error occurred while uploading associated ${parameterType === 'file' ? 'files' : 'documents'}`,
|
|
634
|
+
isError: true,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
return submission;
|
|
691
638
|
}
|
|
692
639
|
}
|
|
693
|
-
else {
|
|
694
|
-
// Filter out 'unsaved' flag before submission since the api doesn't know about it.
|
|
695
|
-
submission[key] = value
|
|
696
|
-
// This should never happen but it's possible that the else branch
|
|
697
|
-
// is reached because either 'apiServices' or 'objectId' is undefined.
|
|
698
|
-
// If that's the case the submission will fail if we submit File blobs.
|
|
699
|
-
.filter((file) => !(file instanceof File))
|
|
700
|
-
.map((file) => ({
|
|
701
|
-
id: file.id,
|
|
702
|
-
name: file.name,
|
|
703
|
-
}));
|
|
704
|
-
}
|
|
705
640
|
// if there are address fields with no value address needs to be set to undefined to be able to submit
|
|
706
641
|
}
|
|
707
642
|
else if (typeof value === 'object' && value !== null) {
|
|
@@ -714,8 +649,7 @@ export async function formatSubmission(submission, apiServices, objectId, instan
|
|
|
714
649
|
submission[key] =
|
|
715
650
|
entry &&
|
|
716
651
|
['input', 'inputField'].includes(entry.type) &&
|
|
717
|
-
|
|
718
|
-
associatedObject?.objectId)
|
|
652
|
+
entry.display?.relatedObjectId
|
|
719
653
|
? pick(value, 'id', 'name', 'objectId')
|
|
720
654
|
: pick(value, 'id', 'name');
|
|
721
655
|
}
|
|
@@ -947,14 +881,3 @@ function applyMaskToObfuscatedValue(value, mask) {
|
|
|
947
881
|
}
|
|
948
882
|
return maskedValue;
|
|
949
883
|
}
|
|
950
|
-
export function useFormById(formId, apiServices, errorMessage) {
|
|
951
|
-
return useQuery({
|
|
952
|
-
queryKey: ['form', formId],
|
|
953
|
-
enabled: formId !== '_auto_' && !!formId,
|
|
954
|
-
staleTime: Infinity,
|
|
955
|
-
queryFn: () => apiServices.get(getPrefixedUrl(`/forms/${formId}`)),
|
|
956
|
-
meta: {
|
|
957
|
-
errorMessage,
|
|
958
|
-
},
|
|
959
|
-
});
|
|
960
|
-
}
|