@evoke-platform/ui-components 1.13.0-dev.4 → 1.13.0-dev.6
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 +145 -72
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +189 -67
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.d.ts +6 -6
- package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +12 -25
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.d.ts +4 -5
- package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.js +34 -22
- package/dist/published/components/custom/CriteriaBuilder/types.d.ts +2 -11
- package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +6 -34
- package/dist/published/components/custom/CriteriaBuilder/utils.js +18 -89
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +6 -3
- package/dist/published/components/custom/Form/utils.d.ts +1 -0
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +25 -22
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +3 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +46 -10
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +4 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +29 -23
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +4 -2
- package/dist/published/components/custom/FormV2/components/types.d.ts +6 -1
- package/dist/published/components/custom/FormV2/components/utils.d.ts +5 -7
- package/dist/published/components/custom/FormV2/components/utils.js +146 -69
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +20 -8
- package/dist/published/stories/CriteriaBuilder.stories.js +70 -22
- package/dist/published/stories/FormRenderer.stories.d.ts +3 -0
- package/dist/published/stories/FormRendererContainer.stories.d.ts +5 -0
- package/dist/published/theme/hooks.d.ts +2 -1
- package/package.json +1 -1
|
@@ -4,38 +4,25 @@ import { Autocomplete, RichTreeView } from '../../core';
|
|
|
4
4
|
import { Box } from '../../layout';
|
|
5
5
|
import { OverflowTextField } from '../OverflowTextField';
|
|
6
6
|
import { PropertyTreeItem } from './PropertyTreeItem';
|
|
7
|
-
import { findTreeItemById, truncateNamePath,
|
|
7
|
+
import { convertTreeViewPropertyToTreeItem, findTreeItemById, truncateNamePath, updateTreeViewProperty } from './utils';
|
|
8
8
|
const NAME_PATH_LIMIT = 35;
|
|
9
9
|
const PropertyTree = (props) => {
|
|
10
|
-
const { fetchObject, handleTreePropertySelect, rootObject, value, propertyTreeMap } = props;
|
|
10
|
+
const { fetchObject, handleTreePropertySelect, rootObject, setRootObject, value, propertyTreeMap } = props;
|
|
11
11
|
const [options, setOptions] = useState([]);
|
|
12
12
|
const [expandedItems, setExpandedItems] = useState([]);
|
|
13
13
|
const [openDropdown, setOpenDropdown] = useState(false);
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
// Transform rootObject properties to TreeItem format
|
|
16
|
-
setOptions(rootObject.properties.map(
|
|
17
|
-
id: property.id,
|
|
18
|
-
label: property.name,
|
|
19
|
-
value: property.id,
|
|
20
|
-
type: property.type,
|
|
21
|
-
objectId: property.objectId,
|
|
22
|
-
children: property.children
|
|
23
|
-
? property.children.map((child) => ({
|
|
24
|
-
id: child.id,
|
|
25
|
-
label: child.name,
|
|
26
|
-
value: child.id,
|
|
27
|
-
type: child.type,
|
|
28
|
-
objectId: child.objectId,
|
|
29
|
-
}))
|
|
30
|
-
: undefined,
|
|
31
|
-
})));
|
|
32
|
-
setExpandedItems([]);
|
|
16
|
+
setOptions(rootObject.properties.map(convertTreeViewPropertyToTreeItem));
|
|
33
17
|
}, [rootObject.properties]);
|
|
34
18
|
const handleExpandedItemsChange = (e, itemIds) => {
|
|
35
19
|
setExpandedItems(itemIds);
|
|
36
20
|
};
|
|
37
21
|
const handleUpdateNodeChildren = (nodeId, children) => {
|
|
38
|
-
|
|
22
|
+
setRootObject((prevObject) => ({
|
|
23
|
+
...prevObject,
|
|
24
|
+
properties: updateTreeViewProperty(prevObject.properties, nodeId, (node) => ({ ...node, children })),
|
|
25
|
+
}));
|
|
39
26
|
};
|
|
40
27
|
return (React.createElement(Autocomplete, { "aria-label": "Property Selector", value: value, fullWidth: true, sx: { width: '37%' }, size: "small", disableClearable: true, options: options, open: openDropdown, onBlur: (e) => {
|
|
41
28
|
const targetComponents = e.relatedTarget?.getAttribute('id')?.split('-');
|
|
@@ -58,11 +45,11 @@ const PropertyTree = (props) => {
|
|
|
58
45
|
}, onFocus: () => setOpenDropdown(true), getOptionLabel: (option) => {
|
|
59
46
|
// Retrieve the full name path from the map
|
|
60
47
|
const namePath = typeof option === 'string'
|
|
61
|
-
? (
|
|
62
|
-
:
|
|
48
|
+
? (propertyTreeMap[option] ?? '') || option
|
|
49
|
+
: propertyTreeMap[option.value] || option.value;
|
|
63
50
|
return truncateNamePath(namePath, NAME_PATH_LIMIT);
|
|
64
51
|
}, renderInput: (params) => {
|
|
65
|
-
const fullDisplayName = value && propertyTreeMap[value]
|
|
52
|
+
const fullDisplayName = value && propertyTreeMap[value];
|
|
66
53
|
return (React.createElement(OverflowTextField, { ...params, "aria-label": fullDisplayName, value: fullDisplayName, placeholder: "Select a property" }));
|
|
67
54
|
}, ListboxComponent: (props) => {
|
|
68
55
|
return (React.createElement(ClickAwayListener, { onClickAway: (e) => {
|
|
@@ -73,8 +60,8 @@ const PropertyTree = (props) => {
|
|
|
73
60
|
} },
|
|
74
61
|
React.createElement(Box, { ...props },
|
|
75
62
|
React.createElement(RichTreeView, { sx: { width: '100%', paddingLeft: 0 }, role: "menu", "aria-label": "Property Tree", expandedItems: expandedItems, onExpandedItemsChange: handleExpandedItemsChange, items: options, slots: {
|
|
76
|
-
item: (itemProps) => (React.createElement(PropertyTreeItem, { ...itemProps, items: options, expanded: expandedItems, setExpanded: setExpandedItems, fetchObject: fetchObject, updateNodeChildren: handleUpdateNodeChildren, handleTreePropertySelect:
|
|
77
|
-
|
|
63
|
+
item: (itemProps) => (React.createElement(PropertyTreeItem, { ...itemProps, items: options, expanded: expandedItems, setExpanded: setExpandedItems, fetchObject: fetchObject, updateNodeChildren: handleUpdateNodeChildren, handleTreePropertySelect: (propertyId) => {
|
|
64
|
+
handleTreePropertySelect(propertyId);
|
|
78
65
|
setOpenDropdown(false);
|
|
79
66
|
} })),
|
|
80
67
|
} }))));
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { TreeItemProps } from '@mui/x-tree-view';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { TreeItem } from './types';
|
|
3
|
+
import { TreeItem, TreeViewObject, TreeViewProperty } from './types';
|
|
5
4
|
type PropertyTreeItemProps = TreeItemProps & {
|
|
6
5
|
items: TreeItem[];
|
|
7
6
|
expanded: string[];
|
|
8
7
|
setExpanded: (expanded: string[]) => void;
|
|
9
|
-
updateNodeChildren: (nodeId: string, children:
|
|
10
|
-
fetchObject: (id: string) => Promise<
|
|
11
|
-
handleTreePropertySelect: (propertyId: string) =>
|
|
8
|
+
updateNodeChildren: (nodeId: string, children: TreeViewProperty[]) => void;
|
|
9
|
+
fetchObject: (id: string) => Promise<TreeViewObject | undefined>;
|
|
10
|
+
handleTreePropertySelect: (propertyId: string) => void;
|
|
12
11
|
};
|
|
13
12
|
export declare const PropertyTreeItem: (props: PropertyTreeItemProps) => React.JSX.Element;
|
|
14
13
|
export {};
|
|
@@ -11,35 +11,47 @@ export const PropertyTreeItem = (props) => {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
const item = findTreeItemById(items, itemId);
|
|
14
|
-
if (item?.type === '
|
|
14
|
+
if (item?.type === 'loadingFailed') {
|
|
15
|
+
e.stopPropagation();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (item?.children) {
|
|
15
19
|
e.stopPropagation();
|
|
16
20
|
// If the item has an associated "objectId", fetch the properties of the object and expand the item.
|
|
17
|
-
if (item
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
if (item?.type === 'object' &&
|
|
22
|
+
item.objectId &&
|
|
23
|
+
item.children?.length === 1 &&
|
|
24
|
+
item.children[0].type === 'loading') {
|
|
25
|
+
try {
|
|
26
|
+
const object = await fetchObject(item.objectId);
|
|
27
|
+
if (object) {
|
|
28
|
+
updateNodeChildren(itemId, object.properties
|
|
29
|
+
.filter((prop) => prop.type !== 'collection')
|
|
30
|
+
.map((prop) => ({
|
|
31
|
+
...prop,
|
|
32
|
+
id: `${itemId}.${prop.id}`,
|
|
33
|
+
children: prop.children?.map((child) => ({
|
|
34
|
+
...child,
|
|
35
|
+
id: `${itemId}.${prop.id}.${child.id}`,
|
|
36
|
+
})),
|
|
37
|
+
})));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
updateNodeChildren(itemId, [
|
|
42
|
+
{
|
|
43
|
+
id: `${itemId}-failed`,
|
|
44
|
+
name: 'Loading Failed',
|
|
45
|
+
type: 'loadingFailed',
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
console.error('Error fetching object:', error);
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
setExpanded([...expanded, itemId]);
|
|
40
52
|
return;
|
|
41
53
|
}
|
|
42
|
-
|
|
54
|
+
handleTreePropertySelect(itemId);
|
|
43
55
|
};
|
|
44
56
|
return React.createElement(RichTreeItem, { itemId: itemId, label: label, children: children, onClick: onClick });
|
|
45
57
|
};
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
+
import { Property } from '@evoke-platform/context';
|
|
2
3
|
import { BaseSelectorProps } from 'react-querybuilder';
|
|
3
4
|
import { ExpandedProperty } from '../../../types';
|
|
4
5
|
import { AutocompleteOption, TreeViewBaseItem } from '../../core';
|
|
5
|
-
export type ObjectProperty = {
|
|
6
|
-
id: string;
|
|
7
|
-
name: string;
|
|
8
|
-
type: string;
|
|
9
|
-
enum?: string[];
|
|
10
|
-
required?: boolean;
|
|
11
|
-
searchable?: boolean;
|
|
12
|
-
objectId?: string;
|
|
13
|
-
formula?: string;
|
|
14
|
-
};
|
|
15
6
|
export type CustomSelectorProps = BaseSelectorProps & {
|
|
16
7
|
options: AutocompleteOption[] | any[];
|
|
17
8
|
fieldData?: Record<string, any>;
|
|
@@ -28,7 +19,7 @@ export type PresetValue = {
|
|
|
28
19
|
};
|
|
29
20
|
type?: string;
|
|
30
21
|
};
|
|
31
|
-
export type TreeViewProperty =
|
|
22
|
+
export type TreeViewProperty = Property & {
|
|
32
23
|
children?: TreeViewProperty[];
|
|
33
24
|
};
|
|
34
25
|
export type TreeViewObject = {
|
|
@@ -1,43 +1,16 @@
|
|
|
1
1
|
import { Property } from '@evoke-platform/context';
|
|
2
2
|
import { RuleGroupType } from 'react-querybuilder';
|
|
3
|
-
import {
|
|
4
|
-
import { TreeItem } from './types';
|
|
3
|
+
import { TreeItem, TreeViewProperty } from './types';
|
|
5
4
|
/**
|
|
6
5
|
* Recursively updates a node in a tree structure by applying an updater function to the node with the specified ID.
|
|
7
6
|
*
|
|
8
|
-
* @param {
|
|
7
|
+
* @param {TreeViewProperty[]} tree - The tree structure to update.
|
|
9
8
|
* @param {string} nodeId - The ID of the node to update.
|
|
10
|
-
* @param {(node:
|
|
11
|
-
* @returns {
|
|
9
|
+
* @param {(node: TreeViewProperty) => TreeViewProperty} updater - The function to apply to the node.
|
|
10
|
+
* @returns {TreeViewProperty[]} - The updated tree structure.
|
|
12
11
|
*/
|
|
13
|
-
export declare const
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Fetches the display name path for a given property ID within an object hierarchy.
|
|
17
|
-
*
|
|
18
|
-
* @param {string} propertyId - The property ID to find the display name for.
|
|
19
|
-
* @param {Obj} rootObject - The root object to start the search from.
|
|
20
|
-
* @param {FetchObjectFunction} fetchObject - Function to fetch an object by its ID.
|
|
21
|
-
* @returns {Promise<string>} - A promise that resolves to the display name path.
|
|
22
|
-
*/
|
|
23
|
-
export declare const fetchDisplayNamePath: (propertyId: string, rootObject: Obj, fetchObject: FetchObjectFunction) => Promise<string>;
|
|
24
|
-
/**
|
|
25
|
-
* stores full dot-notation path to each property ID in the given array of properties.
|
|
26
|
-
*
|
|
27
|
-
* @param {ObjectProperty[]} properties - The array of properties to update.
|
|
28
|
-
* @param {string} parentPath - The parent path to attach to each property ID.
|
|
29
|
-
* @returns {ObjectProperty[]} The updated array of properties with the parent path attached to each property ID.
|
|
30
|
-
*/
|
|
31
|
-
export declare const setIdPaths: (properties: ObjectProperty[], parentPath: string) => ObjectProperty[];
|
|
32
|
-
/**
|
|
33
|
-
* Traverses a property path within an object hierarchy to retrieve detailed property information.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} propertyPath - The dot-separated path of the property to traverse.
|
|
36
|
-
* @param {Obj} rootObject - The root object from which to start the traversal.
|
|
37
|
-
* @param {FetchObjectFunction} fetchObject - A function to fetch an object by its ID.
|
|
38
|
-
* @returns {Promise<ObjectProperty | null>} A promise that resolves to an ObjectProperty if found, or null otherwise.
|
|
39
|
-
*/
|
|
40
|
-
export declare const traversePropertyPath: (propertyPath: string, rootObject: Obj, fetchObject: (objectId: string) => Promise<Obj | undefined>) => Promise<ObjectProperty | null>;
|
|
12
|
+
export declare const updateTreeViewProperty: (tree: TreeViewProperty[], nodeId: string, updater: (node: TreeViewProperty) => TreeViewProperty) => TreeViewProperty[];
|
|
13
|
+
export declare const convertTreeViewPropertyToTreeItem: (property: TreeViewProperty) => TreeItem;
|
|
41
14
|
/**
|
|
42
15
|
* Truncates the name path if it exceeds the specified character limit.
|
|
43
16
|
*
|
|
@@ -68,4 +41,3 @@ export declare const ALL_OPERATORS: {
|
|
|
68
41
|
*/
|
|
69
42
|
export declare const getReadableQuery: (mongoQuery?: Record<string, unknown>, properties?: Property[]) => string;
|
|
70
43
|
export declare const findTreeItemById: (nodes: TreeItem[], nodeId: string) => TreeItem | null;
|
|
71
|
-
export {};
|
|
@@ -2,105 +2,32 @@ import { isArray, isEmpty, startCase } from 'lodash';
|
|
|
2
2
|
/**
|
|
3
3
|
* Recursively updates a node in a tree structure by applying an updater function to the node with the specified ID.
|
|
4
4
|
*
|
|
5
|
-
* @param {
|
|
5
|
+
* @param {TreeViewProperty[]} tree - The tree structure to update.
|
|
6
6
|
* @param {string} nodeId - The ID of the node to update.
|
|
7
|
-
* @param {(node:
|
|
8
|
-
* @returns {
|
|
7
|
+
* @param {(node: TreeViewProperty) => TreeViewProperty} updater - The function to apply to the node.
|
|
8
|
+
* @returns {TreeViewProperty[]} - The updated tree structure.
|
|
9
9
|
*/
|
|
10
|
-
export const
|
|
10
|
+
export const updateTreeViewProperty = (tree, nodeId, updater) => {
|
|
11
11
|
return tree.map((node) => {
|
|
12
12
|
if (node.id === nodeId) {
|
|
13
13
|
return updater(node);
|
|
14
14
|
}
|
|
15
15
|
else if (node.children) {
|
|
16
|
-
return { ...node, children:
|
|
16
|
+
return { ...node, children: updateTreeViewProperty(node.children, nodeId, updater) };
|
|
17
17
|
}
|
|
18
18
|
else {
|
|
19
19
|
return node;
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
22
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export const fetchDisplayNamePath = async (propertyId, rootObject, fetchObject) => {
|
|
32
|
-
const propertyInfo = await traversePropertyPath(propertyId, rootObject, fetchObject);
|
|
33
|
-
return propertyInfo ? propertyInfo.name : '';
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* stores full dot-notation path to each property ID in the given array of properties.
|
|
37
|
-
*
|
|
38
|
-
* @param {ObjectProperty[]} properties - The array of properties to update.
|
|
39
|
-
* @param {string} parentPath - The parent path to attach to each property ID.
|
|
40
|
-
* @returns {ObjectProperty[]} The updated array of properties with the parent path attached to each property ID.
|
|
41
|
-
*/
|
|
42
|
-
export const setIdPaths = (properties, parentPath) => {
|
|
43
|
-
return properties.map((prop) => {
|
|
44
|
-
const fullPath = parentPath ? `${parentPath}.${prop.id}` : prop.id;
|
|
45
|
-
return {
|
|
46
|
-
...prop,
|
|
47
|
-
id: fullPath,
|
|
48
|
-
};
|
|
49
|
-
});
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Traverses a property path within an object hierarchy to retrieve detailed property information.
|
|
53
|
-
*
|
|
54
|
-
* @param {string} propertyPath - The dot-separated path of the property to traverse.
|
|
55
|
-
* @param {Obj} rootObject - The root object from which to start the traversal.
|
|
56
|
-
* @param {FetchObjectFunction} fetchObject - A function to fetch an object by its ID.
|
|
57
|
-
* @returns {Promise<ObjectProperty | null>} A promise that resolves to an ObjectProperty if found, or null otherwise.
|
|
58
|
-
*/
|
|
59
|
-
export const traversePropertyPath = async (propertyPath, rootObject, fetchObject) => {
|
|
60
|
-
const segments = propertyPath.split('.');
|
|
61
|
-
let currentObject = rootObject;
|
|
62
|
-
let fullPath = '';
|
|
63
|
-
let namePath = '';
|
|
64
|
-
for (let i = 0; i < segments.length; i++) {
|
|
65
|
-
const remainingPath = segments.slice(i).join('.');
|
|
66
|
-
let prop = currentObject.properties?.find((p) => p.id === remainingPath);
|
|
67
|
-
if (prop) {
|
|
68
|
-
// flattened address or user properties
|
|
69
|
-
fullPath = fullPath ? `${fullPath}.${remainingPath}` : remainingPath;
|
|
70
|
-
namePath = namePath ? `${namePath} / ${prop.name}` : prop.name;
|
|
71
|
-
return {
|
|
72
|
-
...prop,
|
|
73
|
-
id: fullPath,
|
|
74
|
-
name: namePath,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
prop = currentObject.properties?.find((p) => p.id === segments[i]);
|
|
79
|
-
if (!prop) {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
fullPath = fullPath ? `${fullPath}.${prop.id}` : prop.id;
|
|
83
|
-
namePath = namePath ? `${namePath} / ${prop.name}` : prop.name;
|
|
84
|
-
if (i === segments.length - 1) {
|
|
85
|
-
return {
|
|
86
|
-
...prop,
|
|
87
|
-
id: fullPath,
|
|
88
|
-
name: namePath,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
if (prop.type === 'object' && prop.objectId) {
|
|
92
|
-
const fetchedObject = await fetchObject(prop.objectId);
|
|
93
|
-
if (fetchedObject) {
|
|
94
|
-
currentObject = fetchedObject;
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
};
|
|
23
|
+
export const convertTreeViewPropertyToTreeItem = (property) => ({
|
|
24
|
+
id: property.id,
|
|
25
|
+
label: property.name,
|
|
26
|
+
value: property.id,
|
|
27
|
+
type: property.type,
|
|
28
|
+
objectId: property.objectId,
|
|
29
|
+
children: property.children?.map(convertTreeViewPropertyToTreeItem),
|
|
30
|
+
});
|
|
104
31
|
/**
|
|
105
32
|
* Truncates the name path if it exceeds the specified character limit.
|
|
106
33
|
*
|
|
@@ -343,9 +270,11 @@ export const findTreeItemById = (nodes, nodeId) => {
|
|
|
343
270
|
for (const node of nodes) {
|
|
344
271
|
if (node.id === nodeId)
|
|
345
272
|
return node;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
273
|
+
if (nodeId.startsWith(node.id)) {
|
|
274
|
+
const found = node.children && findTreeItemById(node.children, nodeId);
|
|
275
|
+
if (found)
|
|
276
|
+
return found;
|
|
277
|
+
}
|
|
349
278
|
}
|
|
350
279
|
return null;
|
|
351
280
|
};
|
|
@@ -43,7 +43,7 @@ export const Document = (props) => {
|
|
|
43
43
|
// For 'file' type properties, check permissions on the sys__file object
|
|
44
44
|
// For 'document' type properties, check document attachment permissions
|
|
45
45
|
const endpoint = property.type === 'file'
|
|
46
|
-
? getPrefixedUrl(
|
|
46
|
+
? getPrefixedUrl('/objects/sys__file/instances/checkAccess?action=execute&field=_create')
|
|
47
47
|
: getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=update`);
|
|
48
48
|
apiServices
|
|
49
49
|
.get(endpoint)
|
package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js
CHANGED
|
@@ -64,12 +64,15 @@ export const DocumentList = (props) => {
|
|
|
64
64
|
}, []);
|
|
65
65
|
const checkPermissions = () => {
|
|
66
66
|
if (instance?.[property.id]?.length) {
|
|
67
|
-
// For 'file' type properties, check
|
|
67
|
+
// For 'file' type properties, check regular object instance permissions
|
|
68
68
|
// For 'document' type properties, check document attachment permissions
|
|
69
69
|
const endpoint = property.type === 'file'
|
|
70
|
-
? getPrefixedUrl(`/objects/sys__file/instances
|
|
70
|
+
? getPrefixedUrl(`/objects/sys__file/instances/checkAccess?action=read&field=content`)
|
|
71
71
|
: getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=view`);
|
|
72
|
-
apiServices
|
|
72
|
+
apiServices
|
|
73
|
+
.get(endpoint)
|
|
74
|
+
.then((accessCheck) => setHasViewPermission(accessCheck.result))
|
|
75
|
+
.catch(() => setHasViewPermission(false));
|
|
73
76
|
}
|
|
74
77
|
};
|
|
75
78
|
const isFile = (doc) => doc instanceof File;
|
|
@@ -25,6 +25,7 @@ export declare function flattenFormComponents(components?: ActionInput[]): Actio
|
|
|
25
25
|
export declare function addObjectPropertiesToComponentProps(properties: Property[], formComponents: any[], allCriteriaInputs?: string[], instance?: ObjectInstance, objectPropertyInputProps?: ObjectPropertyInputProps, associatedObject?: {
|
|
26
26
|
instanceId?: string;
|
|
27
27
|
propertyId?: string;
|
|
28
|
+
objectId?: string;
|
|
28
29
|
}, autoSave?: (data: Record<string, unknown>) => void, readOnly?: boolean, defaultPages?: Record<string, string>, navigateTo?: (path: string) => void, queryAddresses?: (query: string) => Promise<Address[]>, apiServices?: ApiServices, isModal?: boolean, fieldHeight?: 'small' | 'medium', richTextEditor?: typeof ReactComponent): Promise<ActionInput[]>;
|
|
29
30
|
export declare function getDefaultValue(initialValue: unknown, selectOptions?: AutocompleteOption[]): unknown;
|
|
30
31
|
export declare const buildComponentPropsFromObjectProperties: (properties: Property[], objectId: string, instance?: ObjectInstance, objectPropertyInputProps?: ObjectPropertyInputProps, hasActionPermissions?: boolean, autoSave?: ((data: Record<string, unknown>) => void) | undefined, readOnly?: boolean, queryAddresses?: ((query: string) => Promise<Address[]>) | undefined, isModal?: boolean, fieldHeight?: 'small' | 'medium', richTextEditor?: typeof ReactComponent) => unknown[];
|
|
@@ -31,6 +31,7 @@ export type FormRendererContainerProps = BaseProps & {
|
|
|
31
31
|
associatedObject?: {
|
|
32
32
|
instanceId: string;
|
|
33
33
|
propertyId: string;
|
|
34
|
+
objectId?: string;
|
|
34
35
|
};
|
|
35
36
|
renderContainer?: (state: FormRendererState) => React.ReactNode;
|
|
36
37
|
renderHeader?: FormRendererProps['renderHeader'];
|
|
@@ -158,7 +158,7 @@ function FormRendererContainer(props) {
|
|
|
158
158
|
});
|
|
159
159
|
if (navigationSlug) {
|
|
160
160
|
if (navigationSlug.includes(':instanceId')) {
|
|
161
|
-
const navigateInstanceId = action?.type === 'create' ? updatedInstance
|
|
161
|
+
const navigateInstanceId = action?.type === 'create' ? updatedInstance.id : instanceId;
|
|
162
162
|
navigateTo(`/${appId}/${navigationSlug.replace(':instanceId', navigateInstanceId ?? ':instanceId')}`);
|
|
163
163
|
}
|
|
164
164
|
else {
|
|
@@ -167,18 +167,17 @@ function FormRendererContainer(props) {
|
|
|
167
167
|
}
|
|
168
168
|
setInstance(updatedInstance);
|
|
169
169
|
};
|
|
170
|
+
/**
|
|
171
|
+
* Manually links any newly uploaded files in the submission to the specified instance.
|
|
172
|
+
* @param submission The form submission data
|
|
173
|
+
* @param linkTo The instance to link the files to
|
|
174
|
+
*/
|
|
170
175
|
const linkFiles = async (submission, linkTo) => {
|
|
171
|
-
// Create file links for any uploaded files
|
|
176
|
+
// Create file links for any uploaded files that haven't been linked yet
|
|
172
177
|
for (const property of sanitizedObject?.properties?.filter((property) => property.type === 'file') ?? []) {
|
|
173
178
|
const files = submission[property.id];
|
|
174
179
|
if (files?.length) {
|
|
175
|
-
|
|
176
|
-
await createFileLinks(files, linkTo, apiServices);
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
console.error('Failed to create file links:', error);
|
|
180
|
-
// Don't fail the entire submission if file linking fails
|
|
181
|
-
}
|
|
180
|
+
await createFileLinks(files, linkTo, apiServices);
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
};
|
|
@@ -195,11 +194,9 @@ function FormRendererContainer(props) {
|
|
|
195
194
|
?.filter((property) => property.formula || property.type === 'collection')
|
|
196
195
|
.map((property) => property.id) ?? []),
|
|
197
196
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
onSubmissionSuccess(response);
|
|
202
|
-
}
|
|
197
|
+
// Manually link files to created instance.
|
|
198
|
+
await linkFiles(submission, { id: response.id, objectId: form.objectId });
|
|
199
|
+
onSubmissionSuccess(response);
|
|
203
200
|
}
|
|
204
201
|
else if (instanceId && action) {
|
|
205
202
|
const response = await objectStore.instanceAction(instanceId, {
|
|
@@ -209,21 +206,26 @@ function FormRendererContainer(props) {
|
|
|
209
206
|
.map((property) => property.id) ?? []),
|
|
210
207
|
});
|
|
211
208
|
if (sanitizedObject && instance) {
|
|
209
|
+
if (!onAutosave) {
|
|
210
|
+
// For non-autosave updates, link any uploaded files to the instance.
|
|
211
|
+
await linkFiles(submission, { id: instanceId, objectId: objectId });
|
|
212
|
+
}
|
|
212
213
|
onSubmissionSuccess(response);
|
|
213
|
-
|
|
214
|
+
// Only delete the necessary files after submission succeeds to avoid deleting a file prematurely.
|
|
215
|
+
await deleteDocuments(submission, true, apiServices, sanitizedObject, instance, action, setSnackbarError);
|
|
214
216
|
}
|
|
215
217
|
}
|
|
216
218
|
}
|
|
217
219
|
catch (error) {
|
|
218
|
-
// Handle deleteDocuments for uploaded documents if the main submission fails
|
|
219
|
-
if (instanceId && action && sanitizedObject && instance) {
|
|
220
|
-
deleteDocuments(submission, false, apiServices, sanitizedObject, instance, action, setSnackbarError);
|
|
221
|
-
}
|
|
222
220
|
setSnackbarError({
|
|
223
221
|
isError: true,
|
|
224
222
|
showAlert: true,
|
|
225
223
|
message: error.response?.data?.error?.message ?? 'An error occurred',
|
|
226
224
|
});
|
|
225
|
+
if (instanceId && action && sanitizedObject && instance) {
|
|
226
|
+
// For an update, uploaded documents have been linked to the instance and need to be deleted.
|
|
227
|
+
await deleteDocuments(submission, false, apiServices, sanitizedObject, instance, action, setSnackbarError);
|
|
228
|
+
}
|
|
227
229
|
throw error; // Throw error so caller knows submission failed
|
|
228
230
|
}
|
|
229
231
|
};
|
|
@@ -255,10 +257,10 @@ function FormRendererContainer(props) {
|
|
|
255
257
|
const parameter = parameters?.find((param) => param.id === fieldId);
|
|
256
258
|
if (associatedObject?.propertyId === fieldId &&
|
|
257
259
|
associatedObject?.instanceId &&
|
|
258
|
-
parameter &&
|
|
260
|
+
(parameter || associatedObject.objectId) &&
|
|
259
261
|
action?.type === 'create') {
|
|
260
262
|
try {
|
|
261
|
-
const instance = await apiServices.get(getPrefixedUrl(`/objects/${parameter.objectId}/instances/${associatedObject.instanceId}`));
|
|
263
|
+
const instance = await apiServices.get(getPrefixedUrl(`/objects/${parameter?.objectId || associatedObject.objectId}/instances/${associatedObject.instanceId}`));
|
|
262
264
|
result[associatedObject.propertyId] = instance;
|
|
263
265
|
}
|
|
264
266
|
catch (error) {
|
|
@@ -327,7 +329,7 @@ function FormRendererContainer(props) {
|
|
|
327
329
|
try {
|
|
328
330
|
setIsSaving(true);
|
|
329
331
|
const cleanedData = removeUneditedProtectedValues(formDataRef.current);
|
|
330
|
-
const submission = await formatSubmission(cleanedData, apiServices, objectId, instanceId, form, setSnackbarError);
|
|
332
|
+
const submission = await formatSubmission(cleanedData, apiServices, objectId, instanceId, form, setSnackbarError, undefined, parameters);
|
|
331
333
|
// Handle object instance autosave
|
|
332
334
|
if (instanceId && action?.type === 'update') {
|
|
333
335
|
await apiServices.post(getPrefixedUrl(`/objects/${objectId}/instances/${instanceId}/actions`), {
|
|
@@ -336,6 +338,7 @@ function FormRendererContainer(props) {
|
|
|
336
338
|
?.filter((property) => !property.formula && property.type !== 'collection')
|
|
337
339
|
.map((property) => property.id) ?? []),
|
|
338
340
|
});
|
|
341
|
+
await linkFiles(submission, { id: instanceId, objectId });
|
|
339
342
|
}
|
|
340
343
|
setLastSavedData(cloneDeep(formDataRef.current));
|
|
341
344
|
setIsSaving(false);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { DocumentParameterValidation } from '@evoke-platform/context';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { DocumentReference } from '../../types';
|
|
4
4
|
type DocumentProps = {
|
|
5
5
|
id: string;
|
|
6
|
+
fieldType?: 'file' | 'document';
|
|
6
7
|
canUpdateProperty: boolean;
|
|
7
8
|
error: boolean;
|
|
8
9
|
validate?: DocumentParameterValidation;
|
|
9
|
-
value: (File |
|
|
10
|
+
value: (File | DocumentReference)[] | undefined;
|
|
10
11
|
hasDescription?: boolean;
|
|
11
12
|
};
|
|
12
13
|
export declare const Document: (props: DocumentProps) => React.JSX.Element;
|