@evoke-platform/ui-components 1.0.0-dev.241 → 1.0.0-dev.243

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.
@@ -1,6 +1,8 @@
1
1
  import { ElementType } from 'react';
2
+ import { ObjectProperty, TreeViewObject } from './types';
2
3
  import 'react-querybuilder/dist/query-builder.css';
3
- import { ObjectProperty, Operator, PresetValue } from './types';
4
+ import { EvokeObject } from '../../../types';
5
+ import { Operator, PresetValue } from './types';
4
6
  import { ValueEditorProps } from './ValueEditor';
5
7
  export type CriteriaInputProps = {
6
8
  properties: ObjectProperty[];
@@ -22,6 +24,10 @@ export type CriteriaInputProps = {
22
24
  disabled?: boolean;
23
25
  hideBorder?: boolean;
24
26
  presetGroupLabel?: string;
27
+ treeViewOpts?: {
28
+ fetchObject?: (objectId: string) => Promise<EvokeObject | undefined>;
29
+ object: TreeViewObject;
30
+ };
25
31
  };
26
32
  export declare const valueEditor: (props: ValueEditorProps) => JSX.Element;
27
33
  declare const CriteriaBuilder: (props: CriteriaInputProps) => JSX.Element;
@@ -1,12 +1,15 @@
1
- import { AddRounded, RemoveCircleOutlineRounded } from '@mui/icons-material';
1
+ import { AddRounded, UnfoldMore } from '@mui/icons-material';
2
+ import { Typography } from '@mui/material';
2
3
  import { QueryBuilderMaterial } from '@react-querybuilder/material';
3
4
  import { isEmpty, startCase } from 'lodash';
4
5
  import React, { useEffect, useMemo, useState } from 'react';
5
6
  import { QueryBuilder, RuleGroupBodyComponents, RuleGroupHeaderComponents, TestID, formatQuery, parseMongoDB, useRuleGroup, } from 'react-querybuilder';
6
7
  import 'react-querybuilder/dist/query-builder.css';
8
+ import { TrashCan } from '../../../icons/custom';
7
9
  import { Autocomplete, Button, IconButton, TextField } from '../../core';
8
10
  import { Box } from '../../layout';
9
11
  import { difference } from '../util';
12
+ import PropertyTree from './PropertyTree';
10
13
  import ValueEditor from './ValueEditor';
11
14
  const ALL_OPERATORS = [
12
15
  { name: '=', label: 'Is' },
@@ -49,8 +52,9 @@ const CustomRuleGroup = (props) => {
49
52
  removeGroup,
50
53
  };
51
54
  return (React.createElement("div", { ref: rg.previewRef, className: rg.outerClassName, "data-testid": TestID.ruleGroup, "data-dragmonitorid": rg.dragMonitorId, "data-dropmonitorid": rg.dropMonitorId, "data-rule-group-id": rg.id, "data-level": rg.path.length, "data-path": JSON.stringify(rg.path) },
52
- React.createElement("div", { className: rg.classNames.body },
53
- React.createElement(RuleGroupBodyComponents, { ...subComponentProps })),
55
+ rg.ruleGroup.rules.length > 0 && (React.createElement("div", { className: rg.classNames.body },
56
+ React.createElement(Typography, { className: "betweenRules" }, "Where"),
57
+ React.createElement(RuleGroupBodyComponents, { ...subComponentProps }))),
54
58
  React.createElement("div", { ref: rg.dropRef, className: rg.classNames.header },
55
59
  React.createElement(RuleGroupHeaderComponents, { ...subComponentProps, schema: {
56
60
  ...subComponentProps.schema,
@@ -60,24 +64,41 @@ const CustomRuleGroup = (props) => {
60
64
  } }))));
61
65
  };
62
66
  const customButton = (props) => {
63
- const { title, handleOnClick, label } = props;
67
+ const { title, handleOnClick, label, path } = props;
64
68
  let buttonLabel = label;
69
+ const nestedConditionLimit = 2;
65
70
  switch (title) {
66
71
  case 'Add group':
67
- buttonLabel = 'Condition Group';
72
+ buttonLabel = 'Add Condition Group';
68
73
  break;
69
74
  case 'Add rule':
70
- buttonLabel = 'Condition';
75
+ buttonLabel = 'Add Condition';
76
+ break;
77
+ case 'Remove group':
78
+ buttonLabel = 'Clear All';
71
79
  break;
72
80
  }
73
- return (React.createElement(Button, { variant: "contained", onClick: handleOnClick, size: "small", startIcon: React.createElement(AddRounded, null), sx: {
74
- padding: '6px 20px',
75
- fontSize: '14.5px',
76
- backgroundColor: '#ebf4f8',
77
- color: '#0678a9',
81
+ return (React.createElement(React.Fragment, null, (path.length < nestedConditionLimit || title === 'Add rule') && (React.createElement(Button, { onClick: handleOnClick, startIcon: React.createElement(AddRounded, null), sx: {
82
+ padding: '6px 16px',
83
+ fontSize: '0.875rem',
84
+ marginRight: path.length === 0 ? '.5rem' : '0px',
85
+ color: title === 'Add group' ? 'black' : '#0075A7',
78
86
  boxShadow: 'none',
79
- '&:hover': { backgroundColor: '#dcecf3', boxShadow: 'none' },
80
- } }, buttonLabel));
87
+ float: 'left',
88
+ backgroundColor: path.length === 0
89
+ ? title === 'Add rule'
90
+ ? 'rgba(0, 117, 167, 0.08)'
91
+ : '#f6f7f8'
92
+ : 'transparent',
93
+ '&:hover': {
94
+ backgroundColor: path.length === 0
95
+ ? title === 'Add rule'
96
+ ? 'rgba(0, 117, 167, 0.08)'
97
+ : '#f6f7f8'
98
+ : 'transparent',
99
+ boxShadow: 'none',
100
+ },
101
+ } }, buttonLabel))));
81
102
  };
82
103
  const customSelector = (props) => {
83
104
  const { options, value, handleOnChange, title, context, level } = props;
@@ -88,6 +109,9 @@ const customSelector = (props) => {
88
109
  let opts = options;
89
110
  const inputType = props.fieldData?.inputType;
90
111
  let readOnly = false;
112
+ const isTreeViewEnabled = context.treeViewOpts && title === 'Fields';
113
+ const fetchObject = context.treeViewOpts?.fetchObject;
114
+ const object = context.treeViewOpts?.object;
91
115
  if (context.disabledCriteria) {
92
116
  readOnly =
93
117
  Object.entries(context.disabledCriteria.criteria).some(([key, value]) => key === rule.field && value === rule.value && rule.operator === '=') && level === context.disabledCriteria.level;
@@ -98,7 +122,7 @@ const customSelector = (props) => {
98
122
  }
99
123
  switch (title) {
100
124
  case 'Operators':
101
- width = '20%';
125
+ width = '25%';
102
126
  if (inputType === 'array') {
103
127
  opts = options
104
128
  .filter((option) => ['null', 'notNull', 'in', 'notIn'].includes(option.name))
@@ -141,7 +165,7 @@ const customSelector = (props) => {
141
165
  val = options.find((option) => option.name === val)?.name;
142
166
  break;
143
167
  }
144
- return (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
168
+ return (React.createElement(React.Fragment, null, isTreeViewEnabled ? (React.createElement(PropertyTree, { value: val ?? value, rootObject: object, fetchObject: fetchObject, handleOnChange: handleOnChange })) : (React.createElement(Autocomplete, { options: opts, value: val ?? null, getOptionLabel: (option) => {
145
169
  if (typeof option === 'string') {
146
170
  return opts.find((o) => option === o.name)?.label || '';
147
171
  }
@@ -152,26 +176,36 @@ const customSelector = (props) => {
152
176
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
177
  onChange: (event, newValue) => {
154
178
  handleOnChange(newValue?.value.name);
155
- }, renderInput: (params) => React.createElement(TextField, { ...params, size: "small" }), sx: { width: width, maxWidth: title === 'Operators' ? '200px' : 'none' }, disableClearable: true, readOnly: readOnly }));
179
+ }, renderInput: (params) => React.createElement(TextField, { ...params, size: "small" }), sx: { width: width, background: '#fff' }, disableClearable: true, readOnly: readOnly }))));
156
180
  };
157
181
  const customCombinator = (props) => {
158
- const { options, value, handleOnChange, context, level } = props;
159
- return (React.createElement(Autocomplete, { options: options, value: startCase(value),
160
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
- getOptionLabel: (option) => startCase(option?.name ?? option),
162
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
- isOptionEqualToValue: (option, value) => option === value,
164
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
165
- onChange: (event, newValue) => handleOnChange(newValue?.value.name), size: 'small', renderInput: (params) => React.createElement(TextField, { ...params, size: "small", sx: { '& fieldset': { border: 'none' } } }), sx: {
166
- width: '80px',
167
- backgroundColor: '#DFE3E8',
168
- borderWidth: 'none',
182
+ const { value, handleOnChange, context, level, path } = props;
183
+ const isReadOnly = !!context.disabledCriteria && context.disabledCriteria.level - 1 === level;
184
+ const conditionPositionInGroup = path[path.length - 1];
185
+ const toggleCombinator = () => {
186
+ const newCombinator = value === 'and' ? 'or' : 'and';
187
+ handleOnChange(newCombinator);
188
+ };
189
+ return conditionPositionInGroup === 1 ? (React.createElement(Box, { sx: {
190
+ display: 'flex',
191
+ justifyContent: 'space-between',
192
+ backgroundColor: isReadOnly ? '#f5f5f5' : '#fff',
193
+ border: '1px solid #ddd',
169
194
  borderRadius: '8px',
170
- '.MuiInputBase-input': {
171
- fontWeight: 550,
172
- fontSize: '14px',
173
- },
174
- }, readOnly: context.disabledCriteria && context.disabledCriteria.level - 1 === level, disableClearable: true }));
195
+ cursor: 'pointer',
196
+ width: '72px',
197
+ padding: '5px 10px',
198
+ fontSize: '16px',
199
+ opacity: isReadOnly ? 0.6 : 1,
200
+ }, onClick: !isReadOnly ? toggleCombinator : undefined },
201
+ React.createElement(Typography, null, startCase(value)),
202
+ React.createElement(Box, { sx: {
203
+ display: 'flex',
204
+ alignItems: 'center',
205
+ fontSize: '16px',
206
+ color: '#212B36',
207
+ } },
208
+ React.createElement(UnfoldMore, null)))) : (React.createElement(Typography, { "aria-label": "combinator-text", sx: { color: '#637381' } }, startCase(value)));
175
209
  };
176
210
  const customDelete = (props) => {
177
211
  const { handleOnClick, context, level } = props;
@@ -182,8 +216,16 @@ const customDelete = (props) => {
182
216
  hideDelete =
183
217
  Object.entries(context.disabledCriteria.criteria).some(([key, value]) => key === rule.field && value === rule.value && rule.operator === '=') && level === context.disabledCriteria.level;
184
218
  }
185
- return !hideDelete ? (React.createElement(IconButton, { onClick: handleOnClick, size: "small" },
186
- React.createElement(RemoveCircleOutlineRounded, null))) : (React.createElement(React.Fragment, null));
219
+ return !hideDelete ? (props.title === 'Remove group' ? (React.createElement(Button, { onClick: handleOnClick, className: 'ruleGroup-remove', sx: {
220
+ padding: '6px 16px',
221
+ opacity: 0,
222
+ fontSize: '14.5px',
223
+ boxShadow: 'none',
224
+ '&:hover': { backgroundColor: 'transparent', boxShadow: 'none' },
225
+ color: '#212B36',
226
+ fontWeight: '400',
227
+ } }, "Delete group")) : (React.createElement(IconButton, { onClick: handleOnClick, size: "small" },
228
+ React.createElement(TrashCan, { sx: { ':hover': { color: '#637381' } } })))) : (React.createElement(React.Fragment, null));
187
229
  };
188
230
  export const valueEditor = (props) => {
189
231
  // For backward compatibility, if enable preset values is true, but preset
@@ -199,7 +241,7 @@ export const valueEditor = (props) => {
199
241
  return ValueEditor(props);
200
242
  };
201
243
  const CriteriaBuilder = (props) => {
202
- const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators, dynamicContentInput, disabled, disabledCriteria, hideBorder, presetGroupLabel, } = props;
244
+ const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators, dynamicContentInput, disabled, disabledCriteria, hideBorder, presetGroupLabel, treeViewOpts, } = props;
203
245
  const [query, setQuery] = useState(undefined);
204
246
  useEffect(() => {
205
247
  if (criteria || originalCriteria) {
@@ -267,23 +309,39 @@ const CriteriaBuilder = (props) => {
267
309
  borderColor: '#ddd',
268
310
  borderRadius: '8px',
269
311
  },
270
- '.queryBuilder-branches .ruleGroup-body': {
271
- marginLeft: hideBorder ? '0px' : 'calc(2 * 0.5rem)',
312
+ '.ruleGroup': {
313
+ borderColor: '#c2c2c2',
314
+ borderLeftWidth: '4px',
315
+ background: '#F9FAFB',
272
316
  },
273
- '.queryBuilder-branches': { color: '#ddd' },
274
- '.queryBuilder-branches .betweenRules::before': {
275
- borderColor: '#ddd',
276
- borderStyle: hideBorder ? 'hidden' : 'solid',
317
+ '.ruleGroup:hover, .ruleGroup:has(:focus)': {
318
+ borderColor: '#0678a9',
277
319
  },
278
- '.ruleGroup': {
279
- borderColor: '#ddd',
280
- borderStyle: hideBorder ? 'hidden' : 'solid',
320
+ '.ruleGroup:hover > .ruleGroup-header > .ruleGroup-remove, .ruleGroup:has(:focus) > .ruleGroup-header > .ruleGroup-remove ': {
321
+ opacity: 1,
322
+ float: 'right',
281
323
  },
282
- '.ruleGroup .ruleGroup': { borderStyle: 'solid' },
283
- '.queryBuilder-branches .rule::before, .queryBuilder-branches .ruleGroup .ruleGroup::before, .queryBuilder-branches .rule::after, .queryBuilder-branches .ruleGroup .ruleGroup::after': {
284
- borderColor: '#ddd',
285
- borderStyle: hideBorder ? 'hidden' : 'solid',
324
+ '.ruleGroup:not(.ruleGroup .ruleGroup)': {
325
+ borderStyle: 'hidden',
326
+ background: '#fff',
327
+ maxWidth: '70vw',
286
328
  },
329
+ '.ruleGroup-header': {
330
+ display: 'block',
331
+ },
332
+ '.betweenRules': {
333
+ textAlign: 'right',
334
+ },
335
+ '.ruleGroup-body': {
336
+ display: 'grid !important',
337
+ gridTemplateRows: 'auto 1fr auto',
338
+ gridTemplateColumns: 'min-content',
339
+ alignItems: 'center',
340
+ },
341
+ '.ruleGroup-body > .rule, .ruleGroup-body > .ruleGroup': {
342
+ gridColumnStart: 2,
343
+ },
344
+ '.ruleGroup .ruleGroup': { borderStyle: 'solid' },
287
345
  } },
288
346
  React.createElement(QueryBuilderMaterial, null,
289
347
  React.createElement(QueryBuilder, { query: !criteria && !originalCriteria ? { combinator: 'and', rules: [] } : query, fields: fields, onQueryChange: (q) => {
@@ -304,8 +362,8 @@ const CriteriaBuilder = (props) => {
304
362
  enablePresetValues,
305
363
  presetGroupLabel,
306
364
  disabledCriteria,
365
+ treeViewOpts,
307
366
  }, controlClassnames: {
308
- queryBuilder: 'queryBuilder-branches',
309
367
  ruleGroup: 'container',
310
368
  }, operators: operators
311
369
  ? ALL_OPERATORS.filter((o) => operators.includes(o.name))
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ import { EvokeObject } from '../../../types';
3
+ type PropertyTreeProps = {
4
+ fetchObject: (id: string) => Promise<EvokeObject | undefined>;
5
+ rootObject: EvokeObject;
6
+ handleOnChange: (property: string) => void;
7
+ value: string | undefined;
8
+ };
9
+ declare const PropertyTree: ({ fetchObject, handleOnChange, rootObject, value }: PropertyTreeProps) => JSX.Element;
10
+ export default PropertyTree;
@@ -0,0 +1,168 @@
1
+ import { ChevronRight, ExpandMore } from '@mui/icons-material';
2
+ import React, { useEffect, useState } from 'react';
3
+ import { Autocomplete, MenuItem, TextField, Tooltip, TreeView } from '../../core';
4
+ import PropertyTreeItem from './PropertyTreeItem';
5
+ import { fetchDisplayNamePath, findPropertyById, setIdPaths, truncateNamePath, updateTreeNode } from './utils';
6
+ const PropertyTree = ({ fetchObject, handleOnChange, rootObject, value }) => {
7
+ const [expandedNodes, setExpandedNodes] = useState([]);
8
+ const [objectPropertyNamePathMap, setObjectPropertyNamePathMap] = useState({});
9
+ const [objectProperties, setObjectProperties] = useState(rootObject.properties ?? []);
10
+ const [propertyOptions, setPropertyOptions] = useState([]);
11
+ const NAME_PATH_LIMIT = 35;
12
+ const originalValue = value ?? '';
13
+ const flattenObjectProperties = (properties) => {
14
+ let result = [];
15
+ properties.forEach((property) => {
16
+ const { children, ...rest } = property;
17
+ const fullId = property.id;
18
+ const fullName = property.name;
19
+ const newProperty = {
20
+ ...rest,
21
+ id: fullId,
22
+ name: fullName,
23
+ };
24
+ result.push(newProperty);
25
+ // If the property has children, recursively process them
26
+ if (children && children.length > 0) {
27
+ const childProperties = flattenObjectProperties(children);
28
+ result = result.concat(childProperties);
29
+ }
30
+ });
31
+ return result;
32
+ };
33
+ useEffect(() => {
34
+ const flattendProperties = flattenObjectProperties(objectProperties);
35
+ setPropertyOptions(flattendProperties);
36
+ if (objectProperties) {
37
+ const fetchDisplayNames = async () => {
38
+ const results = await Promise.all(flattendProperties.map(async (property) => {
39
+ const objectPropertyNamePath = await fetchDisplayNamePath(property.id, rootObject, fetchObject);
40
+ return { [property.id]: objectPropertyNamePath };
41
+ }));
42
+ let newObjectPropertyNamePathMap = {};
43
+ results.forEach((result) => {
44
+ newObjectPropertyNamePathMap = { ...newObjectPropertyNamePathMap, ...result };
45
+ });
46
+ setObjectPropertyNamePathMap(newObjectPropertyNamePathMap);
47
+ //Fetch the display name path for the original value
48
+ if (originalValue) {
49
+ if (originalValue.includes('.')) {
50
+ const parts = originalValue.split('.');
51
+ if (parts.length >= 2) {
52
+ const parentId = parts.slice(0, -1).join('.');
53
+ await fetchObject(parentId);
54
+ if (!newObjectPropertyNamePathMap[originalValue]) {
55
+ const fieldNamePath = await fetchDisplayNamePath(originalValue, rootObject, fetchObject);
56
+ newObjectPropertyNamePathMap = {
57
+ ...newObjectPropertyNamePathMap,
58
+ [originalValue]: fieldNamePath,
59
+ };
60
+ setObjectPropertyNamePathMap(newObjectPropertyNamePathMap);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ };
66
+ fetchDisplayNames();
67
+ }
68
+ }, [originalValue, rootObject, objectProperties, fetchObject]);
69
+ const handleNodeToggle = (event, nodeIds) => {
70
+ const newlyExpandedNodes = nodeIds.filter((id) => !expandedNodes.includes(id));
71
+ newlyExpandedNodes.forEach((nodeId) => {
72
+ handleLoadData(nodeId);
73
+ });
74
+ setExpandedNodes(nodeIds);
75
+ };
76
+ const updateObjectProperties = (nodeId, newChildren) => {
77
+ setObjectProperties((prevProperties) => updateTreeNode(prevProperties, nodeId, (node) => ({ ...node, children: newChildren })));
78
+ };
79
+ const handleLoadData = async (nodeId) => {
80
+ const availableProperty = findPropertyById(objectProperties, nodeId);
81
+ if (!availableProperty || !availableProperty.objectId)
82
+ return;
83
+ const object = await fetchObject(availableProperty.objectId);
84
+ if (object?.properties) {
85
+ const properties = object.properties.filter((prop) => !['collection', 'image', 'documents'].includes(prop.type));
86
+ // this step adds path specific ids to the user and address fields
87
+ const newChildren = setIdPaths(properties, nodeId);
88
+ updateObjectProperties(availableProperty.id, newChildren);
89
+ }
90
+ };
91
+ const handleNodeSelect = (event, nodeId) => {
92
+ const node = findPropertyById(objectProperties, nodeId);
93
+ if (node?.children) {
94
+ event.preventDefault();
95
+ event.stopPropagation();
96
+ }
97
+ };
98
+ const handleTreeItemClick = (propertyId) => {
99
+ const property = findPropertyById(objectProperties, propertyId);
100
+ // this prevents the selection of a parent node
101
+ if (property && !property.children) {
102
+ handleOnChange(property.id);
103
+ }
104
+ };
105
+ return (React.createElement(Autocomplete, { "aria-label": "Property Selector", value: value, fullWidth: true, sx: {
106
+ width: '33%',
107
+ }, disableClearable: true, options: propertyOptions.map((property) => {
108
+ return {
109
+ label: objectPropertyNamePathMap[property.id],
110
+ value: property.id,
111
+ };
112
+ }), componentsProps: {
113
+ paper: {
114
+ sx: {
115
+ '& .MuiAutocomplete-option': {
116
+ '&.Mui-focused': {
117
+ '&:has(.MuiTreeItem-group)': {
118
+ backgroundColor: 'transparent', // Remove background color if child of TreeView
119
+ },
120
+ },
121
+ },
122
+ },
123
+ },
124
+ }, getOptionLabel: (option) => {
125
+ // Retrieve the full name path from the map
126
+ const namePath = typeof option === 'string'
127
+ ? objectPropertyNamePathMap[option] ?? ''
128
+ : objectPropertyNamePathMap[option.value] ?? '';
129
+ return truncateNamePath(namePath, NAME_PATH_LIMIT);
130
+ }, renderInput: (params) => {
131
+ let isTruncated = false;
132
+ const fullDisplayValue = value && objectPropertyNamePathMap[value];
133
+ let displayValue = fullDisplayValue;
134
+ if (!!displayValue && displayValue.length > NAME_PATH_LIMIT) {
135
+ isTruncated = true;
136
+ displayValue = truncateNamePath(displayValue, NAME_PATH_LIMIT);
137
+ }
138
+ return (React.createElement(Tooltip, { title: isTruncated ? fullDisplayValue : '', arrow: true },
139
+ React.createElement(TextField, { ...params, "aria-label": fullDisplayValue, value: displayValue, size: "small", placeholder: "Select a property", variant: "outlined" })));
140
+ }, isOptionEqualToValue: (option, val) => {
141
+ if (typeof val === 'string') {
142
+ return option.value === val;
143
+ }
144
+ return option.value === val?.value;
145
+ }, renderOption: (props, option) => {
146
+ // Find the corresponding property in objectProperties
147
+ const property = objectProperties.find((prop) => prop.id === option.value);
148
+ if (property) {
149
+ if (property.type === 'object') {
150
+ return (React.createElement("li", { ...props },
151
+ React.createElement(TreeView, { role: "menu", "aria-label": "Property Tree", defaultCollapseIcon: React.createElement(ExpandMore, null), defaultExpandIcon: React.createElement(ChevronRight, null), onNodeToggle: handleNodeToggle, expanded: expandedNodes, onNodeSelect: handleNodeSelect, sx: { width: '100%' } },
152
+ React.createElement(PropertyTreeItem, { key: option.value, treeData: objectProperties, property: property, fetchObject: fetchObject, onClick: handleTreeItemClick }))));
153
+ }
154
+ // top level items
155
+ return (React.createElement(MenuItem, { sx: {
156
+ '&:hover': {
157
+ backgroundColor: 'transparent',
158
+ },
159
+ '.MuiTreeItem-group &': {
160
+ '&:hover': {
161
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
162
+ },
163
+ },
164
+ }, onClick: () => handleTreeItemClick(property?.id), key: property.id, value: property.id }, property.name));
165
+ }
166
+ } }));
167
+ };
168
+ export default PropertyTree;
@@ -0,0 +1,10 @@
1
+ /// <reference types="react" />
2
+ import { ExpandedProperty, Obj } from '../../../types';
3
+ type PropertyTreeItemProps = {
4
+ property: ExpandedProperty | undefined;
5
+ fetchObject: (id: string) => Promise<Obj | undefined>;
6
+ treeData: ExpandedProperty[];
7
+ onClick: (property: string) => void;
8
+ };
9
+ declare const PropertyTreeItem: ({ property, fetchObject, treeData, onClick }: PropertyTreeItemProps) => JSX.Element | null;
10
+ export default PropertyTreeItem;
@@ -0,0 +1,43 @@
1
+ import { TreeItem } from '@mui/lab';
2
+ import { MenuItem } from '@mui/material';
3
+ import React from 'react';
4
+ const PropertyTreeItem = ({ property, fetchObject, treeData, onClick }) => {
5
+ if (!property) {
6
+ return null;
7
+ }
8
+ const hasChildren = property.children && property.children.length > 0;
9
+ if (hasChildren) {
10
+ // Parent node
11
+ return (React.createElement(TreeItem, { nodeId: property.id, role: "menu", label: property.name, onClick: () => onClick(property.id), sx: {
12
+ paddingTop: '6px',
13
+ paddingBottom: '6px',
14
+ '.MuiCollapse-root': {
15
+ 'MuiTreeItem-group': {
16
+ width: '100%',
17
+ },
18
+ },
19
+ '& .MuiTreeItem-content': {
20
+ '&:hover': {
21
+ backgroundColor: 'transparent',
22
+ },
23
+ },
24
+ '& .MuiTreeItem-group .MuiTreeItem-content:hover': {
25
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
26
+ },
27
+ } }, property.children?.map((child) => (React.createElement(PropertyTreeItem, { onClick: onClick, treeData: treeData, key: child.id, property: child, fetchObject: fetchObject })))));
28
+ }
29
+ else {
30
+ // Leaf node, render as MenuItem
31
+ return (React.createElement(MenuItem, { sx: {
32
+ '&:hover': {
33
+ backgroundColor: 'transparent',
34
+ },
35
+ '.MuiTreeItem-group &': {
36
+ '&:hover': {
37
+ backgroundColor: 'rgba(0, 0, 0, 0.04)',
38
+ },
39
+ },
40
+ }, onClick: () => onClick(property.id), key: property.id, value: property.id }, property.name));
41
+ }
42
+ };
43
+ export default PropertyTreeItem;
@@ -61,11 +61,11 @@ const ValueEditor = (props) => {
61
61
  if (inputType === 'date') {
62
62
  // date editor
63
63
  return (React.createElement(LocalizationProvider, null,
64
- React.createElement(DatePicker, { inputRef: inputRef, disabled: disabled, value: disabled ? null : value, onChange: handleOnChange, onClose: onClose, renderInput: (params) => (React.createElement(TextField, { ...params, onClick: onClick, placeholder: "Value", size: "small", sx: { width: '33%' } })), readOnly: readOnly })));
64
+ React.createElement(DatePicker, { inputRef: inputRef, disabled: disabled, value: disabled ? null : value, onChange: handleOnChange, onClose: onClose, renderInput: (params) => (React.createElement(TextField, { ...params, onClick: onClick, placeholder: "Value", size: "small", sx: { width: '33%', background: '#fff' } })), readOnly: readOnly })));
65
65
  }
66
66
  else if (inputType === 'time') {
67
67
  return (React.createElement(LocalizationProvider, null,
68
- React.createElement(TimePicker, { inputRef: inputRef, disabled: disabled, value: disabled || !value ? null : value, onChange: handleOnChange, renderInput: (params) => (React.createElement(TextField, { ...params, onClick: onClick, placeholder: "Value", size: "small", sx: { width: '33%' } })), readOnly: readOnly })));
68
+ React.createElement(TimePicker, { inputRef: inputRef, disabled: disabled, value: disabled || !value ? null : value, onChange: handleOnChange, renderInput: (params) => (React.createElement(TextField, { ...params, onClick: onClick, placeholder: "Value", size: "small", sx: { width: '33%', background: '#fff' } })), readOnly: readOnly })));
69
69
  }
70
70
  else if (inputType === 'number' || inputType === 'integer') {
71
71
  const isMultiple = ['in', 'notIn'].includes(operator);
@@ -79,7 +79,7 @@ const ValueEditor = (props) => {
79
79
  handleOnChange(uniqueSelections.length ? uniqueSelections : '');
80
80
  },
81
81
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
82
- isOptionEqualToValue: (option, value) => option === value, renderInput: (params) => (React.createElement(TextField, { label: params.label, ...params, size: "small" })), groupBy: (option) => isPresetValue(option.value?.name) ? context.presetGroupLabel || 'Preset Values' : 'Options', renderGroup: groupRenderGroup, sx: { width: '33%' }, readOnly: readOnly }));
82
+ isOptionEqualToValue: (option, value) => option === value, renderInput: (params) => (React.createElement(TextField, { label: params.label, ...params, size: "small" })), groupBy: (option) => isPresetValue(option.value?.name) ? context.presetGroupLabel || 'Preset Values' : 'Options', renderGroup: groupRenderGroup, sx: { width: '33%', background: '#fff' }, readOnly: readOnly }));
83
83
  }
84
84
  else {
85
85
  return (React.createElement(TextField, { inputRef: inputRef, value: ['null', 'notNull'].includes(operator) ? '' : value, disabled: ['null', 'notNull'].includes(operator), onChange: (e) => {
@@ -91,7 +91,7 @@ const ValueEditor = (props) => {
91
91
  }
92
92
  }, ...(inputType === 'number'
93
93
  ? { InputProps: { inputComponent: NumericFormat } }
94
- : { type: 'number' }), placeholder: "Value", size: "small", onClick: onClick, sx: { width: '33%' }, readOnly: readOnly }));
94
+ : { type: 'number' }), placeholder: "Value", size: "small", onClick: onClick, sx: { width: '33%', background: '#fff' }, readOnly: readOnly }));
95
95
  }
96
96
  }
97
97
  else {
@@ -133,7 +133,7 @@ const ValueEditor = (props) => {
133
133
  }, groupBy: (option) => isPresetValue(option.value?.name) ? context.presetGroupLabel || 'Preset Values' : 'Options', renderGroup: groupRenderGroup, sortBy: "NONE", sx: { width: '33%' }, readOnly: readOnly }));
134
134
  }
135
135
  else {
136
- return (React.createElement(TextField, { inputRef: inputRef, value: ['null', 'notNull'].includes(operator) ? '' : value, disabled: ['null', 'notNull'].includes(operator), onChange: (e) => handleOnChange(e.target.value), onClick: onClick, placeholder: "Value", size: "small", sx: { width: '33%' }, readOnly: readOnly }));
136
+ return (React.createElement(TextField, { inputRef: inputRef, value: ['null', 'notNull'].includes(operator) ? '' : value, disabled: ['null', 'notNull'].includes(operator), onChange: (e) => handleOnChange(e.target.value), onClick: onClick, placeholder: "Value", size: "small", sx: { width: '33%', background: '#fff' }, readOnly: readOnly }));
137
137
  }
138
138
  }
139
139
  };
@@ -1,4 +1,6 @@
1
+ /// <reference types="react" />
1
2
  import { BaseSelectorProps } from 'react-querybuilder';
3
+ import { ExpandedProperty } from '../../../types';
2
4
  import { AutocompleteOption } from '../../core';
3
5
  export type ObjectProperty = {
4
6
  id: string;
@@ -13,6 +15,8 @@ export type ObjectProperty = {
13
15
  export type CustomSelectorProps = BaseSelectorProps & {
14
16
  options: AutocompleteOption[] | any[];
15
17
  fieldData?: Record<string, any>;
18
+ availableProperties?: ExpandedProperty[];
19
+ setAvailableProperties?: React.Dispatch<React.SetStateAction<ExpandedProperty[]>>;
16
20
  };
17
21
  export type Operator = '=' | '!=' | '<' | '>' | '<=' | '>=' | 'contains' | 'beginsWith' | 'endsWith' | 'doesNotContain' | 'doesNotBeginWith' | 'doesNotEndWith' | 'null' | 'notNull' | 'in' | 'notIn' | 'between' | 'notBetween';
18
22
  export type PresetValue = {
@@ -23,3 +27,11 @@ export type PresetValue = {
23
27
  sublabel?: string;
24
28
  };
25
29
  };
30
+ export type TreeViewProperty = ObjectProperty & {
31
+ children?: TreeViewProperty[];
32
+ };
33
+ export type TreeViewObject = {
34
+ id: string;
35
+ name: string;
36
+ properties: TreeViewProperty[];
37
+ };
@@ -0,0 +1,54 @@
1
+ import { ExpandedProperty, Obj, ObjectProperty } from '../../../types';
2
+ /**
3
+ * Recursively updates a node in a tree structure by applying an updater function to the node with the specified ID.
4
+ *
5
+ * @param {ExpandedProperty[]} tree - The tree structure to update.
6
+ * @param {string} nodeId - The ID of the node to update.
7
+ * @param {(node: ExpandedProperty) => ExpandedProperty} updater - The function to apply to the node.
8
+ * @returns {ExpandedProperty[]} - The updated tree structure.
9
+ */
10
+ export declare const updateTreeNode: (tree: ExpandedProperty[], nodeId: string, updater: (node: ExpandedProperty) => ExpandedProperty) => ExpandedProperty[];
11
+ /**
12
+ * Recursively searches for a property in a tree structure by its ID.
13
+ *
14
+ * @param {ExpandedProperty[]} properties - The tree structure to search.
15
+ * @param {string} id - The ID of the property to find.
16
+ * @returns {ExpandedProperty | null} - The found property or null if not found.
17
+ */
18
+ export declare const findPropertyById: (properties: ExpandedProperty[], id: string) => ExpandedProperty | null;
19
+ type FetchObjectFunction = (id: string) => Promise<Obj | undefined>;
20
+ /**
21
+ * Fetches the display name path for a given property ID within an object hierarchy.
22
+ *
23
+ * @param {string} propertyId - The property ID to find the display name for.
24
+ * @param {Obj} rootObject - The root object to start the search from.
25
+ * @param {FetchObjectFunction} fetchObject - Function to fetch an object by its ID.
26
+ * @returns {Promise<string>} - A promise that resolves to the display name path.
27
+ */
28
+ export declare const fetchDisplayNamePath: (propertyId: string, rootObject: Obj, fetchObject: FetchObjectFunction) => Promise<string>;
29
+ /**
30
+ * stores full dot-notation path to each property ID in the given array of properties.
31
+ *
32
+ * @param {ObjectProperty[]} properties - The array of properties to update.
33
+ * @param {string} parentPath - The parent path to attach to each property ID.
34
+ * @returns {ObjectProperty[]} The updated array of properties with the parent path attached to each property ID.
35
+ */
36
+ export declare const setIdPaths: (properties: ObjectProperty[], parentPath: string) => ObjectProperty[];
37
+ /**
38
+ * Traverses a property path within an object hierarchy to retrieve detailed property information.
39
+ *
40
+ * @param {string} propertyPath - The dot-separated path of the property to traverse.
41
+ * @param {Obj} rootObject - The root object from which to start the traversal.
42
+ * @param {(objectId: string) => Promise<Obj | undefined>} fetchObject - A function to fetch an object by its ID.
43
+ * @returns {Promise<ObjectProperty | null>} A promise that resolves to an ObjectProperty if found, or null otherwise.
44
+ */
45
+ export declare const traversePropertyPath: (propertyPath: string, rootObject: Obj, fetchObject: (objectId: string) => Promise<Obj | undefined>) => Promise<ObjectProperty | null>;
46
+ /**
47
+ * Truncates the name path if it exceeds the specified character limit.
48
+ *
49
+ * @param {string} namePath - The full name path to be truncated.
50
+ * @param {number} limit - The maximum allowed length for the name path.
51
+ * @returns {string} - The truncated name path if it exceeds the limit, otherwise the original name path.
52
+ */
53
+ export declare const truncateNamePath: (namePath: string, limit?: number) => string;
54
+ export {};
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Recursively updates a node in a tree structure by applying an updater function to the node with the specified ID.
3
+ *
4
+ * @param {ExpandedProperty[]} tree - The tree structure to update.
5
+ * @param {string} nodeId - The ID of the node to update.
6
+ * @param {(node: ExpandedProperty) => ExpandedProperty} updater - The function to apply to the node.
7
+ * @returns {ExpandedProperty[]} - The updated tree structure.
8
+ */
9
+ export const updateTreeNode = (tree, nodeId, updater) => {
10
+ return tree.map((node) => {
11
+ if (node.id === nodeId) {
12
+ return updater(node);
13
+ }
14
+ else if (node.children) {
15
+ return { ...node, children: updateTreeNode(node.children, nodeId, updater) };
16
+ }
17
+ else {
18
+ return node;
19
+ }
20
+ });
21
+ };
22
+ /**
23
+ * Recursively searches for a property in a tree structure by its ID.
24
+ *
25
+ * @param {ExpandedProperty[]} properties - The tree structure to search.
26
+ * @param {string} id - The ID of the property to find.
27
+ * @returns {ExpandedProperty | null} - The found property or null if not found.
28
+ */
29
+ export const findPropertyById = (properties, id) => {
30
+ for (const prop of properties) {
31
+ if (prop.id === id) {
32
+ return prop;
33
+ }
34
+ else if (prop.children) {
35
+ const result = findPropertyById(prop.children, id);
36
+ if (result)
37
+ return result;
38
+ }
39
+ }
40
+ return null;
41
+ };
42
+ /**
43
+ * Fetches the display name path for a given property ID within an object hierarchy.
44
+ *
45
+ * @param {string} propertyId - The property ID to find the display name for.
46
+ * @param {Obj} rootObject - The root object to start the search from.
47
+ * @param {FetchObjectFunction} fetchObject - Function to fetch an object by its ID.
48
+ * @returns {Promise<string>} - A promise that resolves to the display name path.
49
+ */
50
+ export const fetchDisplayNamePath = async (propertyId, rootObject, fetchObject) => {
51
+ const propertyInfo = await traversePropertyPath(propertyId, rootObject, fetchObject);
52
+ return propertyInfo ? propertyInfo.name : '';
53
+ };
54
+ /**
55
+ * stores full dot-notation path to each property ID in the given array of properties.
56
+ *
57
+ * @param {ObjectProperty[]} properties - The array of properties to update.
58
+ * @param {string} parentPath - The parent path to attach to each property ID.
59
+ * @returns {ObjectProperty[]} The updated array of properties with the parent path attached to each property ID.
60
+ */
61
+ export const setIdPaths = (properties, parentPath) => {
62
+ return properties.map((prop) => {
63
+ const fullPath = parentPath ? `${parentPath}.${prop.id}` : prop.id;
64
+ return {
65
+ ...prop,
66
+ id: fullPath,
67
+ };
68
+ });
69
+ };
70
+ /**
71
+ * Traverses a property path within an object hierarchy to retrieve detailed property information.
72
+ *
73
+ * @param {string} propertyPath - The dot-separated path of the property to traverse.
74
+ * @param {Obj} rootObject - The root object from which to start the traversal.
75
+ * @param {(objectId: string) => Promise<Obj | undefined>} fetchObject - A function to fetch an object by its ID.
76
+ * @returns {Promise<ObjectProperty | null>} A promise that resolves to an ObjectProperty if found, or null otherwise.
77
+ */
78
+ export const traversePropertyPath = async (propertyPath, rootObject, fetchObject) => {
79
+ const segments = propertyPath.split('.');
80
+ let currentObject = rootObject;
81
+ let fullPath = '';
82
+ let namePath = '';
83
+ for (let i = 0; i < segments.length; i++) {
84
+ const remainingPath = segments.slice(i).join('.');
85
+ let prop = currentObject.properties?.find((p) => p.id === remainingPath);
86
+ if (prop) {
87
+ // flattened address or user properties
88
+ fullPath = fullPath ? `${fullPath}.${remainingPath}` : remainingPath;
89
+ namePath = namePath ? `${namePath} / ${prop.name}` : prop.name;
90
+ return {
91
+ ...prop,
92
+ id: fullPath,
93
+ name: namePath,
94
+ };
95
+ }
96
+ else {
97
+ prop = currentObject.properties?.find((p) => p.id === segments[i]);
98
+ if (!prop) {
99
+ return null;
100
+ }
101
+ fullPath = fullPath ? `${fullPath}.${prop.id}` : prop.id;
102
+ namePath = namePath ? `${namePath} / ${prop.name}` : prop.name;
103
+ if (i === segments.length - 1) {
104
+ return {
105
+ ...prop,
106
+ id: fullPath,
107
+ name: namePath,
108
+ };
109
+ }
110
+ if (prop.type === 'object' && prop.objectId) {
111
+ const fetchedObject = await fetchObject(prop.objectId);
112
+ if (fetchedObject) {
113
+ currentObject = fetchedObject;
114
+ }
115
+ else {
116
+ return null;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return null;
122
+ };
123
+ /**
124
+ * Truncates the name path if it exceeds the specified character limit.
125
+ *
126
+ * @param {string} namePath - The full name path to be truncated.
127
+ * @param {number} limit - The maximum allowed length for the name path.
128
+ * @returns {string} - The truncated name path if it exceeds the limit, otherwise the original name path.
129
+ */
130
+ export const truncateNamePath = (namePath, limit = 20) => {
131
+ if (namePath.length <= limit) {
132
+ return namePath;
133
+ }
134
+ const parts = namePath.split('/');
135
+ if (parts.length >= 2) {
136
+ // Display the first and last segments separated by '/.../'
137
+ return `${parts[0]}/.../${parts[parts.length - 1]}`;
138
+ }
139
+ else {
140
+ // If there's only one segment, truncate it and add ellipsis
141
+ return `${namePath.substring(0, limit - 3)}...`;
142
+ }
143
+ };
@@ -6,3 +6,4 @@ export default _default;
6
6
  export declare const CriteriaBuilder: ComponentStory<(props: CriteriaInputProps) => JSX.Element>;
7
7
  export declare const CriteriaBuilderPresetUserID: ComponentStory<(props: CriteriaInputProps) => JSX.Element>;
8
8
  export declare const CriteriaBuilderGroupedPresetValues: ComponentStory<(props: CriteriaInputProps) => JSX.Element>;
9
+ export declare const CriteriaBuilderRelatedObject: ComponentStory<(props: CriteriaInputProps) => JSX.Element>;
@@ -282,3 +282,433 @@ CriteriaBuilderGroupedPresetValues.args = {
282
282
  presetGroupLabel: 'Parameter Values',
283
283
  enablePresetValues: true,
284
284
  };
285
+ export const CriteriaBuilderRelatedObject = CriteriaBuilderTemplate.bind({});
286
+ CriteriaBuilderRelatedObject.args = {
287
+ treeViewOpts: {
288
+ fetchObject: async (objectId) => {
289
+ if (objectId === 'people') {
290
+ return Promise.resolve({
291
+ id: 'people',
292
+ name: 'People',
293
+ properties: [
294
+ {
295
+ id: 'avgVehicleRating',
296
+ name: 'Avg Vehicle Rating',
297
+ type: 'integer',
298
+ required: false,
299
+ formula: '{{avg object="vehicle" relatedProperty="registeredOwner" aggregateProperty="rating" }}',
300
+ formulaType: 'aggregate',
301
+ },
302
+ {
303
+ id: 'email',
304
+ name: 'Email',
305
+ type: 'string',
306
+ required: false,
307
+ },
308
+ {
309
+ id: 'firstName',
310
+ name: 'FirstName',
311
+ type: 'string',
312
+ required: false,
313
+ searchable: true,
314
+ },
315
+ {
316
+ id: 'id',
317
+ name: 'ID',
318
+ type: 'string',
319
+ required: true,
320
+ },
321
+ {
322
+ id: 'lastName',
323
+ name: 'LastName',
324
+ type: 'string',
325
+ required: false,
326
+ searchable: true,
327
+ },
328
+ {
329
+ id: 'license',
330
+ name: 'License',
331
+ type: 'object',
332
+ objectId: 'license',
333
+ children: [
334
+ {
335
+ id: 'license-loading',
336
+ name: 'Loading...',
337
+ type: 'loading',
338
+ },
339
+ ],
340
+ },
341
+ {
342
+ id: 'name',
343
+ name: 'Name',
344
+ type: 'string',
345
+ required: false,
346
+ searchable: true,
347
+ formula: '{{firstName}} {{lastName}} ',
348
+ },
349
+ {
350
+ id: 'phone',
351
+ name: 'Phone',
352
+ type: 'string',
353
+ required: false,
354
+ },
355
+ {
356
+ id: 'vehicleCount',
357
+ name: 'Vehicle Count',
358
+ type: 'integer',
359
+ required: false,
360
+ formula: '{{count object="vehicle" relatedProperty="registeredOwner" aggregateProperty="name" }}',
361
+ formulaType: 'aggregate',
362
+ },
363
+ {
364
+ id: 'homeAddress.line1',
365
+ name: 'Applicant Address Line 1',
366
+ type: 'string',
367
+ },
368
+ {
369
+ id: 'homeAddress.line2',
370
+ name: 'Applicant Address Line 2',
371
+ type: 'string',
372
+ },
373
+ {
374
+ id: 'homeAddress.city',
375
+ name: 'Applicant Address City',
376
+ type: 'string',
377
+ },
378
+ {
379
+ id: 'homeAddress.state',
380
+ name: 'Applicant Address State',
381
+ type: 'string',
382
+ },
383
+ {
384
+ id: 'homeAddress.zipCode',
385
+ name: 'Applicant Address Zip Code',
386
+ type: 'string',
387
+ },
388
+ {
389
+ id: 'homeAddress.county',
390
+ name: 'Applicant Address County',
391
+ type: 'string',
392
+ },
393
+ ],
394
+ actions: [],
395
+ });
396
+ }
397
+ else if (objectId === 'license') {
398
+ return Promise.resolve({
399
+ id: 'license',
400
+ name: 'License',
401
+ actions: [],
402
+ properties: [
403
+ {
404
+ id: 'expirationDate',
405
+ name: 'ExpirationDate',
406
+ type: 'date',
407
+ required: false,
408
+ },
409
+ {
410
+ id: 'id',
411
+ name: 'ID',
412
+ type: 'string',
413
+ required: true,
414
+ },
415
+ {
416
+ id: 'issueDate',
417
+ name: 'IssueDate',
418
+ type: 'date',
419
+ required: false,
420
+ },
421
+ {
422
+ id: 'licenseNumber',
423
+ name: 'LicenseNumber',
424
+ type: 'string',
425
+ required: false,
426
+ },
427
+ {
428
+ id: 'name',
429
+ name: 'Name',
430
+ type: 'string',
431
+ required: false,
432
+ },
433
+ {
434
+ id: 'person',
435
+ name: 'Person',
436
+ type: 'object',
437
+ objectId: 'people',
438
+ children: [
439
+ {
440
+ id: 'person-loading',
441
+ name: 'Loading...',
442
+ type: 'loading',
443
+ },
444
+ ],
445
+ },
446
+ ],
447
+ });
448
+ }
449
+ else if (objectId === 'applicationType') {
450
+ return Promise.resolve({
451
+ id: 'applicationType',
452
+ name: 'Application Type',
453
+ actions: [],
454
+ properties: [
455
+ {
456
+ id: 'id',
457
+ name: 'ID',
458
+ type: 'string',
459
+ required: true,
460
+ },
461
+ {
462
+ id: 'name',
463
+ name: 'Name',
464
+ type: 'string',
465
+ required: true,
466
+ },
467
+ {
468
+ id: 'description',
469
+ name: 'Description',
470
+ type: 'string',
471
+ required: false,
472
+ },
473
+ ],
474
+ });
475
+ }
476
+ else {
477
+ Promise.resolve(null);
478
+ }
479
+ },
480
+ object: {
481
+ id: 'application',
482
+ name: 'Application',
483
+ properties: [
484
+ {
485
+ id: 'applicant',
486
+ name: 'Applicant',
487
+ type: 'object',
488
+ required: false,
489
+ objectId: 'people',
490
+ children: [
491
+ {
492
+ id: 'applicant-loading',
493
+ name: 'Loading...',
494
+ type: 'loading',
495
+ },
496
+ ],
497
+ },
498
+ {
499
+ id: 'applicationType',
500
+ name: 'Application Type',
501
+ type: 'object',
502
+ objectId: 'applicationType',
503
+ required: false,
504
+ children: [
505
+ {
506
+ id: 'applicationType-loading',
507
+ name: 'Loading...',
508
+ type: 'loading',
509
+ },
510
+ ],
511
+ },
512
+ {
513
+ id: 'appName',
514
+ name: 'Applicant Name',
515
+ type: 'string',
516
+ required: false,
517
+ objectId: 'people',
518
+ formula: '{{applicant.firstName}} {{applicant.lastName}} ',
519
+ },
520
+ {
521
+ id: 'applicationId',
522
+ name: 'ApplicationID',
523
+ type: 'string',
524
+ required: false,
525
+ },
526
+ {
527
+ id: 'completeDate',
528
+ name: 'CompleteDate',
529
+ type: 'date',
530
+ required: false,
531
+ },
532
+ {
533
+ id: 'correspondenceTemplate',
534
+ name: 'Correspondence Template',
535
+ type: 'string',
536
+ },
537
+ {
538
+ id: 'id',
539
+ name: 'ID',
540
+ type: 'string',
541
+ required: true,
542
+ },
543
+ {
544
+ id: 'homeAddress.line1',
545
+ name: 'Applicant Address Line 1',
546
+ type: 'string',
547
+ },
548
+ {
549
+ id: 'homeAddress.line2',
550
+ name: 'Applicant Address Line 2',
551
+ type: 'string',
552
+ },
553
+ {
554
+ id: 'homeAddress.city',
555
+ name: 'Applicant Address City',
556
+ type: 'string',
557
+ },
558
+ {
559
+ id: 'homeAddress.state',
560
+ name: 'Applicant Address State',
561
+ type: 'string',
562
+ },
563
+ {
564
+ id: 'homeAddress.zipCode',
565
+ name: 'Applicant Address Zip Code',
566
+ type: 'string',
567
+ },
568
+ {
569
+ id: 'homeAddress.county',
570
+ name: 'Applicant Address County',
571
+ type: 'string',
572
+ },
573
+ {
574
+ id: 'name',
575
+ name: 'Name',
576
+ type: 'string',
577
+ required: true,
578
+ },
579
+ {
580
+ id: 'startDate',
581
+ name: 'StartDate',
582
+ type: 'date',
583
+ required: false,
584
+ },
585
+ {
586
+ id: 'status',
587
+ name: 'Status',
588
+ type: 'string',
589
+ enum: ['Approved', 'Denied', 'InProgress', 'Received'],
590
+ required: false,
591
+ },
592
+ {
593
+ id: 'trailName',
594
+ name: 'TrailName',
595
+ type: 'string',
596
+ required: false,
597
+ },
598
+ ],
599
+ },
600
+ },
601
+ properties: [
602
+ {
603
+ id: 'applicant',
604
+ name: 'Applicant',
605
+ type: 'object',
606
+ objectId: 'person',
607
+ },
608
+ {
609
+ id: 'applicationType',
610
+ name: 'Application Type',
611
+ type: 'object',
612
+ objectId: 'applicationType',
613
+ required: false,
614
+ },
615
+ {
616
+ id: 'appName',
617
+ name: 'Applicant Name',
618
+ type: 'string',
619
+ required: false,
620
+ objectId: 'people',
621
+ formula: '{{applicant.firstName}} {{applicant.lastName}} ',
622
+ },
623
+ {
624
+ id: 'applicationId',
625
+ name: 'ApplicationID',
626
+ type: 'string',
627
+ required: false,
628
+ },
629
+ {
630
+ id: 'completeDate',
631
+ name: 'CompleteDate',
632
+ type: 'date',
633
+ required: false,
634
+ },
635
+ {
636
+ id: 'correspondenceTemplate',
637
+ name: 'Correspondence Template',
638
+ type: 'string',
639
+ },
640
+ {
641
+ id: 'id',
642
+ name: 'ID',
643
+ type: 'string',
644
+ required: true,
645
+ },
646
+ {
647
+ id: 'homeAddress.line1',
648
+ name: 'Applicant Address Line 1',
649
+ type: 'string',
650
+ },
651
+ {
652
+ id: 'homeAddress.line2',
653
+ name: 'Applicant Address Line 2',
654
+ type: 'string',
655
+ },
656
+ {
657
+ id: 'homeAddress.city',
658
+ name: 'Applicant Address City',
659
+ type: 'string',
660
+ },
661
+ {
662
+ id: 'homeAddress.state',
663
+ name: 'Applicant Address State',
664
+ type: 'string',
665
+ },
666
+ {
667
+ id: 'homeAddress.zipCode',
668
+ name: 'Applicant Address Zip Code',
669
+ type: 'string',
670
+ },
671
+ {
672
+ id: 'homeAddress.county',
673
+ name: 'Applicant Address County',
674
+ type: 'string',
675
+ },
676
+ {
677
+ id: 'name',
678
+ name: 'Name',
679
+ type: 'string',
680
+ required: true,
681
+ },
682
+ {
683
+ id: 'startDate',
684
+ name: 'StartDate',
685
+ type: 'date',
686
+ required: false,
687
+ },
688
+ {
689
+ id: 'status',
690
+ name: 'Status',
691
+ type: 'string',
692
+ enum: ['Approved', 'Denied', 'InProgress', 'Received'],
693
+ required: false,
694
+ },
695
+ {
696
+ id: 'trailName',
697
+ name: 'TrailName',
698
+ type: 'string',
699
+ required: false,
700
+ },
701
+ ],
702
+ setCriteria: (criteria) => console.log('criteria= ', criteria),
703
+ criteria: {
704
+ $and: [
705
+ {
706
+ name: 'bert',
707
+ },
708
+ {
709
+ 'applicant.lastName': '',
710
+ },
711
+ ],
712
+ },
713
+ enablePresetValues: false,
714
+ };
@@ -14,6 +14,19 @@ export type ObjectProperty = {
14
14
  objectId?: string;
15
15
  formula?: string;
16
16
  };
17
+ export type Obj = {
18
+ id: string;
19
+ name: string;
20
+ properties?: ObjectProperty[];
21
+ };
22
+ export type ExpandedProperty = {
23
+ id: string;
24
+ name: string;
25
+ type: string;
26
+ objectId?: string;
27
+ open?: boolean;
28
+ children?: ExpandedProperty[];
29
+ };
17
30
  type Action = {
18
31
  id: string;
19
32
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.0.0-dev.241",
3
+ "version": "1.0.0-dev.243",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",