@evoke-platform/ui-components 1.14.0 → 1.15.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.
Files changed (39) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +2 -2
  2. package/dist/published/components/custom/CriteriaBuilder/types.d.ts +0 -15
  3. package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +0 -10
  4. package/dist/published/components/custom/CriteriaBuilder/utils.js +2 -161
  5. package/dist/published/components/custom/Form/utils.js +3 -2
  6. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
  7. package/dist/published/components/custom/FormV2/FormRenderer.js +25 -27
  8. package/dist/published/components/custom/FormV2/FormRendererContainer.js +131 -100
  9. package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.d.ts +5 -0
  10. package/dist/published/components/custom/FormV2/components/ConditionalQueryClientProvider.js +21 -0
  11. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +86 -143
  12. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +0 -2
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +1 -4
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +105 -185
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +36 -49
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +18 -26
  17. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +18 -18
  18. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +17 -21
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +96 -169
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +0 -2
  21. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +57 -13
  22. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.d.ts +2 -1
  23. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +61 -29
  24. package/dist/published/components/custom/FormV2/components/utils.d.ts +23 -4
  25. package/dist/published/components/custom/FormV2/components/utils.js +136 -26
  26. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +28 -14
  27. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +40 -46
  28. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +2 -1
  29. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +56 -19
  30. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +7 -2
  31. package/dist/published/components/custom/index.d.ts +2 -0
  32. package/dist/published/components/custom/index.js +1 -0
  33. package/dist/published/components/custom/types.d.ts +15 -0
  34. package/dist/published/components/custom/types.js +1 -0
  35. package/dist/published/components/custom/util.d.ts +10 -0
  36. package/dist/published/components/custom/util.js +161 -1
  37. package/dist/published/index.d.ts +2 -2
  38. package/dist/published/index.js +1 -1
  39. package/package.json +3 -4
@@ -9,9 +9,9 @@ import { TrashCan } from '../../../icons/custom';
9
9
  import { Autocomplete, Button, IconButton, Typography } from '../../core';
10
10
  import { Box } from '../../layout';
11
11
  import { OverflowTextField } from '../OverflowTextField';
12
- import { difference } from '../util';
12
+ import { difference, parseMongoDB } from '../util';
13
13
  import PropertyTree from './PropertyTree';
14
- import { ALL_OPERATORS, parseMongoDB, traversePropertyPath } from './utils';
14
+ import { ALL_OPERATORS, traversePropertyPath } from './utils';
15
15
  import ValueEditor from './ValueEditor';
16
16
  const styles = {
17
17
  buttons: {
@@ -36,21 +36,6 @@ export type TreeViewObject = {
36
36
  name: string;
37
37
  properties: TreeViewProperty[];
38
38
  };
39
- export type MongoDBQueryValue = null | string | boolean | {
40
- $not?: {
41
- $regex?: string;
42
- };
43
- $regex?: string;
44
- $expr?: boolean;
45
- $eq?: unknown;
46
- $ne?: unknown;
47
- $lt?: unknown;
48
- $lte?: unknown;
49
- $gt?: unknown;
50
- $gte?: unknown;
51
- $in?: unknown[];
52
- $nin?: unknown[];
53
- };
54
39
  export type TreeItem = TreeViewBaseItem<{
55
40
  id: string;
56
41
  label: string;
@@ -1,5 +1,4 @@
1
1
  import { Property } from '@evoke-platform/context';
2
- import { RuleGroupType } from 'react-querybuilder';
3
2
  import { Obj, ObjectProperty } from '../../../types';
4
3
  import { TreeItem } from './types';
5
4
  /**
@@ -46,15 +45,6 @@ export declare const traversePropertyPath: (propertyPath: string, rootObject: Ob
46
45
  * @returns {string} - The truncated name path if it exceeds the limit, otherwise the original name path.
47
46
  */
48
47
  export declare const truncateNamePath: (namePath: string, limit?: number) => string;
49
- /**
50
- * Parses a MongoDB query into a RuleGroupType or a single RuleType.
51
- * This function recursively processes the MongoDB query and transforms it into
52
- * a structured format that can be used in the CriteriaBuilder.
53
- *
54
- * @param {Record<string, unknown>} mongoQuery - The MongoDB query to be parsed.
55
- * @returns {RuleGroupType} - Correctly formatted rule or rules for the query builder.
56
- */
57
- export declare function parseMongoDB(mongoQuery: Record<string, unknown>): RuleGroupType;
58
48
  export declare const ALL_OPERATORS: {
59
49
  name: string;
60
50
  label: string;
@@ -1,4 +1,5 @@
1
- import { isArray, isEmpty, startCase } from 'lodash';
1
+ import { startCase } from 'lodash';
2
+ import { parseMongoDB } from '../util';
2
3
  /**
3
4
  * Recursively updates a node in a tree structure by applying an updater function to the node with the specified ID.
4
5
  *
@@ -122,166 +123,6 @@ export const truncateNamePath = (namePath, limit = 20) => {
122
123
  return `${namePath.substring(0, limit - 3)}...`;
123
124
  }
124
125
  };
125
- /**
126
- * Parses a MongoDB query into a RuleGroupType or a single RuleType.
127
- * This function recursively processes the MongoDB query and transforms it into
128
- * a structured format that can be used in the CriteriaBuilder.
129
- *
130
- * @param {Record<string, unknown>} mongoQuery - The MongoDB query to be parsed.
131
- * @returns {RuleGroupType} - Correctly formatted rule or rules for the query builder.
132
- */
133
- export function parseMongoDB(mongoQuery) {
134
- /**
135
- * Parses a single rule from the MongoDB query.
136
- *
137
- * @param {string} key - The field name for the rule.
138
- * @param {MongoDBQueryValue} value - The value associated with the key to parse.
139
- * @returns {RuleType | undefined} - A RuleType if the value is valid, otherwise undefined.
140
- */
141
- const parseRule = (key, value) => {
142
- if (key === '$expr') {
143
- return undefined;
144
- }
145
- else if (value === null) {
146
- return {
147
- field: key,
148
- operator: 'null',
149
- value: null,
150
- };
151
- }
152
- else if (typeof value === 'string' ||
153
- typeof value === 'number' ||
154
- typeof value === 'boolean' ||
155
- '$eq' in value) {
156
- return {
157
- field: key,
158
- operator: '=',
159
- value: typeof value === 'object' && '$eq' in value ? value.$eq : value,
160
- };
161
- }
162
- else if (value.$not && typeof value.$not.$regex === 'string') {
163
- return {
164
- field: key,
165
- operator: 'doesNotContain',
166
- value: value.$not.$regex,
167
- };
168
- }
169
- else if (typeof value.$regex === 'string') {
170
- let operator = 'contains';
171
- let regexValue = value.$regex;
172
- if (regexValue.startsWith('^')) {
173
- operator = 'beginsWith';
174
- regexValue = regexValue.slice(1);
175
- }
176
- else if (regexValue.endsWith('$')) {
177
- operator = 'endsWith';
178
- regexValue = regexValue.slice(0, -1);
179
- }
180
- if (regexValue) {
181
- // remove escape characters for display
182
- regexValue = regexValue.replace(/\\(.)/g, '$1');
183
- }
184
- return {
185
- field: key,
186
- operator: operator,
187
- value: regexValue,
188
- };
189
- }
190
- else if ('$ne' in value) {
191
- return {
192
- field: key,
193
- operator: value.$ne === null ? 'notNull' : '!=',
194
- value: value.$ne,
195
- };
196
- }
197
- else if ('$lt' in value) {
198
- return {
199
- field: key,
200
- operator: '<',
201
- value: value.$lt,
202
- };
203
- }
204
- else if ('$lte' in value) {
205
- return {
206
- field: key,
207
- operator: '<=',
208
- value: value.$lte,
209
- };
210
- }
211
- else if ('$gt' in value) {
212
- return {
213
- field: key,
214
- operator: '>',
215
- value: value.$gt,
216
- };
217
- }
218
- else if ('$gte' in value) {
219
- return {
220
- field: key,
221
- operator: '>=',
222
- value: value.$gte,
223
- };
224
- }
225
- else if ('$in' in value) {
226
- return {
227
- field: key,
228
- operator: 'in',
229
- value: value.$in ?? [],
230
- };
231
- }
232
- else if ('$nin' in value) {
233
- return {
234
- field: key,
235
- operator: 'notIn',
236
- value: value.$nin ?? [],
237
- };
238
- }
239
- else {
240
- return undefined;
241
- }
242
- };
243
- /**
244
- * Recursively parses a MongoDB query into a RuleGroupType or RuleType.
245
- *
246
- * @param {Record<string, unknown>} query - The MongoDB query object to be parsed.
247
- * @returns {RuleGroupType | RuleType} - A RuleGroupType with combinator and rules, or a single RuleType.
248
- */
249
- const parseGroup = (query) => {
250
- if ('$and' in query && isArray(query.$and)) {
251
- return {
252
- combinator: 'and',
253
- rules: query.$and.map(parseGroup).filter((rule) => rule !== undefined),
254
- };
255
- }
256
- else if ('$or' in query && isArray(query.$or)) {
257
- return {
258
- combinator: 'or',
259
- rules: query.$or.map(parseGroup).filter((rule) => rule !== undefined),
260
- };
261
- }
262
- else if (isEmpty(query)) {
263
- return { combinator: 'and', rules: [] };
264
- }
265
- else {
266
- const rules = Object.entries(query)
267
- .map(([key, value]) => parseRule(key, value))
268
- .filter((rule) => rule !== null);
269
- return rules[0];
270
- }
271
- };
272
- const result = parseGroup(mongoQuery);
273
- // Check if the result is a RuleGroupType (i.e., has a combinator)
274
- if (result && 'combinator' in result) {
275
- return result;
276
- }
277
- else {
278
- // If there are no condition groups configured so it's not a RuleGroupType, wrap it in a default 'and' combinator
279
- return {
280
- combinator: 'and',
281
- rules: result ? [result] : [],
282
- };
283
- }
284
- }
285
126
  export const ALL_OPERATORS = [
286
127
  { name: '=', label: 'Is' },
287
128
  { name: '!=', label: 'Is not' },
@@ -3,8 +3,9 @@ import { DateTime } from 'luxon';
3
3
  import { nanoid } from 'nanoid';
4
4
  import Handlebars from 'no-eval-handlebars';
5
5
  import qs from 'qs';
6
- import { defaultRuleProcessorMongoDB, formatQuery, parseMongoDB, } from 'react-querybuilder';
6
+ import { defaultRuleProcessorMongoDB, formatQuery, } from 'react-querybuilder';
7
7
  import escape from 'string-escape-regex';
8
+ import { parseMongoDB } from '../util';
8
9
  // The following functions are used to build the FormIO form components from the form parameters.
9
10
  export function determineComponentType(properties, parameter) {
10
11
  if (!parameter)
@@ -374,7 +375,7 @@ export function convertComponentsToForm(components) {
374
375
  ? { booleanDisplay: component.displayOption }
375
376
  : {}),
376
377
  ...(component.displayOption && component?.property?.type === 'object'
377
- ? { relatedObjectDisplay: component.displayOption }
378
+ ? { relatedObjectDisplay: component.displayOption, viewLayout: component.viewLayout }
378
379
  : {}),
379
380
  ...(component.defaultToCurrentDate ||
380
381
  component.defaultToCurrentTime ||
@@ -27,7 +27,7 @@ export type FormRendererProps = BaseProps & {
27
27
  renderBody?: (props: BodyProps) => React.ReactNode;
28
28
  renderFooter?: (props: FooterProps) => React.ReactNode;
29
29
  };
30
- export declare const FormRenderer: React.FC<FormRendererProps> & {
30
+ export declare const FormRenderer: ((props: FormRendererProps) => React.JSX.Element) & {
31
31
  Header: React.FC<HeaderProps>;
32
32
  Body: React.FC<BodyProps>;
33
33
  Footer: React.FC<FooterProps>;
@@ -1,14 +1,16 @@
1
- import { useObject } from '@evoke-platform/context';
1
+ import { useApiServices, } from '@evoke-platform/context';
2
+ import { useQuery } from '@tanstack/react-query';
2
3
  import { isEmpty, isEqual, omit } from 'lodash';
3
4
  import React, { useEffect, useMemo, useRef, useState } from 'react';
4
5
  import { useForm } from 'react-hook-form';
5
6
  import { useWidgetSize } from '../../../theme';
6
7
  import { Box } from '../../layout';
7
8
  import { Body } from './components/Body';
9
+ import ConditionalQueryClientProvider from './components/ConditionalQueryClientProvider';
8
10
  import { Footer, FooterActions } from './components/Footer';
9
11
  import { FormContext } from './components/FormContext';
10
12
  import Header, { AccordionActions, Title } from './components/Header';
11
- import { assignIdsToSectionsAndRichText, convertPropertiesToParams, entryIsVisible, getEntryId, getNestedParameterIds, isAddressProperty, obfuscateValue, } from './components/utils';
13
+ import { assignIdsToSectionsAndRichText, convertPropertiesToParams, entryIsVisible, getEntryId, getNestedParameterIds, getPrefixedUrl, isAddressProperty, obfuscateValue, } from './components/utils';
12
14
  import { handleValidation } from './components/ValidationFiles/Validation';
13
15
  import ValidationErrors from './components/ValidationFiles/ValidationErrors';
14
16
  const FormRendererInternal = (props) => {
@@ -23,15 +25,14 @@ const FormRendererInternal = (props) => {
23
25
  defaultWidth: 1200,
24
26
  });
25
27
  const isSmallerThanMd = isBelow('md');
28
+ const apiServices = useApiServices();
26
29
  const [expandedSections, setExpandedSections] = useState([]);
27
30
  const [fetchedOptions, setFetchedOptions] = useState({});
28
31
  const [expandAll, setExpandAll] = useState();
29
32
  const [action, setAction] = useState();
30
- const [object, setObject] = useState();
31
33
  const [triggerFieldReset, setTriggerFieldReset] = useState(false);
32
34
  const [isInitializing, setIsInitializing] = useState(true);
33
35
  const [parameters, setParameters] = useState();
34
- const objectStore = useObject(objectId);
35
36
  const validationContainerRef = useRef(null);
36
37
  const updateFetchedOptions = (newData) => {
37
38
  setFetchedOptions((prev) => ({
@@ -45,32 +46,26 @@ const FormRendererInternal = (props) => {
45
46
  function handleCollapseAll() {
46
47
  setExpandAll(false);
47
48
  }
49
+ const { data: object } = useQuery({
50
+ queryKey: [objectId, 'sanitized'],
51
+ queryFn: () => apiServices.get(getPrefixedUrl(`/objects/${objectId}/effective`), {
52
+ params: { sanitizedVersion: true },
53
+ }),
54
+ staleTime: Infinity,
55
+ enabled: !!objectId,
56
+ });
48
57
  const updatedEntries = useMemo(() => {
49
58
  return object ? assignIdsToSectionsAndRichText(entries, object, parameters) : [];
50
59
  }, [entries, object, parameters]);
51
60
  useEffect(() => {
52
- (async () => {
53
- try {
54
- const object = await objectStore.get({ sanitized: true });
55
- const action = object?.actions?.find((a) => a.id === actionId);
56
- setObject(object);
57
- setAction(action);
58
- if (action?.parameters) {
59
- setParameters(action.parameters);
60
- }
61
- else if (object) {
62
- // if forms actionId is synced with object properties
63
- setParameters(convertPropertiesToParams(object));
64
- }
65
- }
66
- catch (error) {
67
- console.error('Failed to fetch object, action or parameters:', error);
68
- }
69
- finally {
70
- setIsInitializing(false);
71
- }
72
- })();
73
- }, [objectStore, actionId]);
61
+ if (!object)
62
+ return;
63
+ const action = object.actions?.find((a) => a.id === actionId);
64
+ setAction(action);
65
+ // if forms action is synced with object properties then convertPropertiesToParams
66
+ setParameters(action?.parameters ?? convertPropertiesToParams(object));
67
+ setIsInitializing(false);
68
+ }, [object, actionId]);
74
69
  useEffect(() => {
75
70
  const currentValues = getValues();
76
71
  if (value) {
@@ -244,7 +239,10 @@ const FormRendererInternal = (props) => {
244
239
  } })),
245
240
  action && onSubmit && (renderFooter ? renderFooter(footerProps) : React.createElement(Footer, { ...footerProps }))))));
246
241
  };
247
- export const FormRenderer = Object.assign(FormRendererInternal, {
242
+ export const FormRenderer = Object.assign(function FormRenderer(props) {
243
+ return (React.createElement(ConditionalQueryClientProvider, null,
244
+ React.createElement(FormRendererInternal, { ...props })));
245
+ }, {
248
246
  Header,
249
247
  Body,
250
248
  Footer,