@evoke-platform/ui-components 1.0.2-testing.2 → 1.1.0-dev.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.
@@ -1,4 +1,4 @@
1
- import { ElementType } from 'react';
1
+ /// <reference types="react" />
2
2
  import 'react-querybuilder/dist/query-builder.css';
3
3
  import { EvokeObject } from '../../../types';
4
4
  import { ObjectProperty, Operator, PresetValue, TreeViewObject } from './types';
@@ -10,10 +10,9 @@ export type CriteriaInputProps = {
10
10
  originalCriteria?: Record<string, unknown>;
11
11
  enablePresetValues?: boolean;
12
12
  presetValues?: PresetValue[];
13
- dynamicContentInput?: {
14
- component: ElementType;
15
- previousSteps: unknown[];
16
- trigger?: Record<string, unknown>;
13
+ customValueEditor?: {
14
+ component: (props: ValueEditorProps) => JSX.Element;
15
+ props?: Record<string, unknown>;
17
16
  };
18
17
  operators?: Operator[];
19
18
  disabledCriteria?: {
@@ -3,7 +3,7 @@ import { Typography } from '@mui/material';
3
3
  import { QueryBuilderMaterial } from '@react-querybuilder/material';
4
4
  import { isArray, isEmpty, startCase } from 'lodash';
5
5
  import React, { useEffect, useMemo, useState } from 'react';
6
- import { QueryBuilder, RuleGroupBodyComponents, RuleGroupHeaderComponents, TestID, defaultRuleProcessorMongoDB, formatQuery, useRuleGroup, } from 'react-querybuilder';
6
+ import { QueryBuilder, RuleGroupBodyComponents, RuleGroupHeaderComponents, TestID, add, defaultRuleProcessorMongoDB, formatQuery, useRuleGroup, } from 'react-querybuilder';
7
7
  import 'react-querybuilder/dist/query-builder.css';
8
8
  import escape from 'string-escape-regex';
9
9
  import { TrashCan } from '../../../icons/custom';
@@ -28,6 +28,14 @@ const ALL_OPERATORS = [
28
28
  { name: 'in', label: 'In' },
29
29
  { name: 'notIn', label: 'Not in' },
30
30
  ];
31
+ const styles = {
32
+ buttons: {
33
+ padding: '6px 16px',
34
+ fontSize: '0.875rem',
35
+ marginRight: '0px',
36
+ boxShadow: 'none',
37
+ },
38
+ };
31
39
  const CustomRuleGroup = (props) => {
32
40
  const rg = { ...props, ...useRuleGroup(props) };
33
41
  const [addRule, addGroup, cloneGroup, toggleLockGroup, removeGroup] = [
@@ -73,11 +81,8 @@ const customButton = (props) => {
73
81
  case 'Add rule':
74
82
  buttonLabel = 'Add Condition';
75
83
  break;
76
- case 'Remove group':
77
- buttonLabel = 'Clear All';
78
- break;
79
84
  }
80
- return (React.createElement(React.Fragment, null, (path.length < nestedConditionLimit || title === 'Add rule') && (React.createElement(Button, { onClick: handleOnClick, startIcon: React.createElement(AddRounded, null), sx: {
85
+ return (React.createElement(React.Fragment, null, !!path.length && (path.length < nestedConditionLimit || title === 'Add rule') && (React.createElement(Button, { onClick: handleOnClick, startIcon: React.createElement(AddRounded, null), sx: {
81
86
  padding: '6px 16px',
82
87
  fontSize: '0.875rem',
83
88
  marginRight: path.length === 0 ? '.5rem' : '0px',
@@ -207,7 +212,10 @@ const customSelector = (props) => {
207
212
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
213
  onChange: (event, newValue) => {
209
214
  handleOnChange(newValue?.value.name);
210
- }, renderInput: (params) => React.createElement(TextField, { ...params, placeholder: placeholder, size: "small" }), sx: { width: width, background: '#fff' }, disableClearable: true, readOnly: readOnly }))));
215
+ }, renderInput: (params) => (React.createElement(TextField, { ...params, placeholder: placeholder, size: "small", inputProps: {
216
+ ...params.inputProps,
217
+ 'aria-label': placeholder,
218
+ } })), sx: { width: width, background: '#fff' }, disableClearable: true, readOnly: readOnly }))));
211
219
  };
212
220
  const customCombinator = (props) => {
213
221
  const { value, handleOnChange, context, level, path } = props;
@@ -272,7 +280,7 @@ export const valueEditor = (props) => {
272
280
  return ValueEditor(props);
273
281
  };
274
282
  const CriteriaBuilder = (props) => {
275
- const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators, dynamicContentInput, disabled, disabledCriteria, hideBorder, presetGroupLabel, treeViewOpts, disableRegexEscapeChars, } = props;
283
+ const { properties, criteria, setCriteria, originalCriteria, enablePresetValues, presetValues, operators, disabled, disabledCriteria, hideBorder, presetGroupLabel, customValueEditor, treeViewOpts, disableRegexEscapeChars, } = props;
276
284
  const [query, setQuery] = useState(undefined);
277
285
  const [propertyTreeMap, setPropertyTreeMap] = useState();
278
286
  useEffect(() => {
@@ -282,33 +290,40 @@ const CriteriaBuilder = (props) => {
282
290
  !isEmpty(treeViewOpts) && updatePropertyTreeMap(updatedQuery);
283
291
  setQuery({
284
292
  ...updatedQuery,
285
- rules: processRules(updatedQuery.rules),
293
+ rules: processRules(updatedQuery.rules, true),
286
294
  });
287
295
  }
288
296
  else {
289
297
  setQuery({ combinator: 'and', rules: [] });
290
298
  }
291
299
  }, [originalCriteria]);
292
- function processRules(rules) {
300
+ const processRules = (rules, isSavedValue) => {
293
301
  return rules.map((rule) => {
294
302
  if ('rules' in rule) {
295
303
  return {
296
304
  ...rule,
297
- rules: processRules(rule.rules),
305
+ rules: processRules(rule.rules, isSavedValue),
298
306
  };
299
307
  }
300
308
  else {
301
309
  const propertyType = properties.find((property) => property.id === rule.field)?.type;
310
+ let adjustedValue = rule.value;
311
+ if ((propertyType === 'array' ||
312
+ (propertyType === 'string' && (rule.operator === 'in' || rule.operator === 'notIn'))) &&
313
+ isSavedValue) {
314
+ adjustedValue = rule.value.split(',');
315
+ }
316
+ else if ((rule.operator === 'null' || rule.operator === 'notNull') && rule.value) {
317
+ adjustedValue = null;
318
+ }
302
319
  return {
303
320
  ...rule,
304
- value: propertyType === 'array' ||
305
- (propertyType === 'string' && (rule.operator === 'in' || rule.operator === 'notIn'))
306
- ? rule.value?.split(',')
307
- : rule.value,
321
+ operator: propertyType === 'array' && rule.operator === '=' ? 'in' : rule.operator,
322
+ value: adjustedValue,
308
323
  };
309
324
  }
310
325
  });
311
- }
326
+ };
312
327
  // this retrieves the properties from a treeview for each property in the query
313
328
  // they are then used in the custom query builder components to determine the input type etc
314
329
  const updatePropertyTreeMap = (q) => {
@@ -334,9 +349,31 @@ const CriteriaBuilder = (props) => {
334
349
  setPropertyTreeMap(tempPropertyMap);
335
350
  });
336
351
  };
352
+ const handleClearAll = () => {
353
+ handleQueryChange({ combinator: 'and', rules: [] });
354
+ };
355
+ const handleAddRule = () => {
356
+ if (query) {
357
+ const newQuery = add(query, { field: '', operator: '', value: '' }, []);
358
+ setQuery(newQuery);
359
+ }
360
+ };
361
+ const handleAddGroup = () => {
362
+ if (query) {
363
+ const newQuery = add(query, {
364
+ combinator: 'and',
365
+ rules: [{ field: '', operator: '', value: '' }],
366
+ }, []);
367
+ setQuery(newQuery);
368
+ }
369
+ };
337
370
  const handleQueryChange = (q) => {
338
- setQuery(q);
339
- const newCriteria = JSON.parse(formatQuery(q, {
371
+ const processedQuery = {
372
+ ...q,
373
+ rules: processRules(q.rules, false),
374
+ };
375
+ setQuery(processedQuery);
376
+ const newCriteria = JSON.parse(formatQuery(processedQuery, {
340
377
  format: 'mongodb',
341
378
  ruleProcessor: (rule, options) => {
342
379
  let newRule = rule;
@@ -352,16 +389,12 @@ const CriteriaBuilder = (props) => {
352
389
  return defaultRuleProcessorMongoDB(newRule, options);
353
390
  },
354
391
  }));
355
- //when q has no rules, it formats and parses to { $and: [{ $expr: true }] }
356
- const allRulesDeleted = isEmpty(difference(newCriteria, { $and: [{ $expr: true }] }));
357
- // since the Add Condition / Add Group buttons add rules with all the fields empty,
358
- // we need to check if the first rule was added because q will still parse to { $and: [{ $expr: true }] }
359
- const firstRuleAdded = isEmpty(criteria) && q.rules.length > 0;
360
- if (allRulesDeleted && !firstRuleAdded) {
361
- setCriteria(undefined);
392
+ if (!isEmpty(difference(newCriteria, { $and: [{ $expr: true }] }))) {
393
+ setCriteria(newCriteria);
362
394
  }
363
395
  else {
364
- setCriteria(newCriteria);
396
+ if (q.rules.length === 0)
397
+ setCriteria(undefined);
365
398
  }
366
399
  };
367
400
  const fields = useMemo(() => {
@@ -405,6 +438,7 @@ const CriteriaBuilder = (props) => {
405
438
  borderStyle: 'hidden',
406
439
  background: '#fff',
407
440
  maxWidth: '70vw',
441
+ margin: 'auto',
408
442
  },
409
443
  '.ruleGroup-header': {
410
444
  display: 'block',
@@ -424,7 +458,9 @@ const CriteriaBuilder = (props) => {
424
458
  '.ruleGroup .ruleGroup': { borderStyle: 'solid' },
425
459
  } },
426
460
  React.createElement(QueryBuilderMaterial, null,
427
- React.createElement(QueryBuilder, { query: !criteria && !originalCriteria ? { combinator: 'and', rules: [] } : query, fields: fields, onQueryChange: (q) => {
461
+ React.createElement(QueryBuilder, { query: !criteria && !originalCriteria && query.rules.length === 0
462
+ ? { combinator: 'and', rules: [] }
463
+ : query, fields: fields, onQueryChange: (q) => {
428
464
  handleQueryChange(q);
429
465
  }, onAddRule: (rule) => {
430
466
  // overrides new rule and sets up an empty rule with all three fields empty
@@ -461,9 +497,9 @@ const CriteriaBuilder = (props) => {
461
497
  ruleGroup: CustomRuleGroup,
462
498
  removeGroupAction: customDelete,
463
499
  removeRuleAction: customDelete,
464
- valueEditor: valueEditor,
500
+ valueEditor: customValueEditor ? customValueEditor.component : valueEditor,
465
501
  }, context: {
466
- dynamicContentInput,
502
+ ...(customValueEditor?.props ?? {}),
467
503
  presetValues,
468
504
  enablePresetValues,
469
505
  presetGroupLabel,
@@ -483,7 +519,45 @@ const CriteriaBuilder = (props) => {
483
519
  ruleGroup: 'container',
484
520
  }, operators: operators
485
521
  ? ALL_OPERATORS.filter((o) => operators.includes(o.name))
486
- : ALL_OPERATORS }))));
522
+ : ALL_OPERATORS })),
523
+ React.createElement(Box, { sx: {
524
+ display: 'flex',
525
+ justifyContent: 'space-between',
526
+ alignItems: 'center',
527
+ marginBottom: '10px',
528
+ maxWidth: '71vw',
529
+ marginLeft: 'auto',
530
+ marginRight: 'auto',
531
+ } },
532
+ React.createElement(Box, null,
533
+ React.createElement(Button, { sx: {
534
+ backgroundColor: 'rgba(0, 117, 167, 0.08)',
535
+ color: '#0075A7',
536
+ marginLeft: '10px',
537
+ '&:hover': {
538
+ backgroundColor: 'rgba(0, 117, 167, 0.08)',
539
+ },
540
+ ...styles.buttons,
541
+ }, startIcon: React.createElement(AddRounded, null), onClick: handleAddRule }, "Add Condition"),
542
+ React.createElement(Button, { sx: {
543
+ backgroundColor: '#f6f7f8',
544
+ color: '#000',
545
+ marginLeft: '8px',
546
+ '&:hover': {
547
+ backgroundColor: '#f6f7f8',
548
+ },
549
+ ...styles.buttons,
550
+ }, startIcon: React.createElement(AddRounded, null), onClick: handleAddGroup, title: "Add a rule at the bottom of the group" }, "Add Condition Group")),
551
+ React.createElement(Button, { variant: 'text', sx: {
552
+ justifyContent: 'flex-end',
553
+ color: '#000',
554
+ fontWeight: 500,
555
+ paddingLeft: '0px',
556
+ '&:hover': {
557
+ backgroundColor: 'transparent',
558
+ },
559
+ ...styles.buttons,
560
+ }, onClick: handleClearAll, title: "Clear all conditions", disabled: isEmpty(query.rules) }, "Clear All"))));
487
561
  }
488
562
  return React.createElement(React.Fragment, null);
489
563
  };
@@ -1,7 +1,8 @@
1
+ import { Instant, LocalDate, LocalDateTime, LocalTime, ZoneId } from '@js-joda/core';
1
2
  import { ClearRounded } from '@mui/icons-material';
2
3
  import { Box, darken, lighten, styled } from '@mui/material';
3
- import { TimePicker } from '@mui/x-date-pickers';
4
- import React, { useRef, useState } from 'react';
4
+ import { DateTimePicker, TimePicker } from '@mui/x-date-pickers';
5
+ import React, { useEffect, useRef, useState } from 'react';
5
6
  import { Autocomplete, Chip, DatePicker, LocalizationProvider, Menu, MenuItem, TextField, Typography, } from '../../core';
6
7
  import { NumericFormat } from '../FormField/InputFieldComponent';
7
8
  const GroupHeader = styled('div')(({ theme }) => ({
@@ -17,7 +18,7 @@ const GroupHeader = styled('div')(({ theme }) => ({
17
18
  }));
18
19
  const GroupItems = styled('ul')({ padding: 0 });
19
20
  const ValueEditor = (props) => {
20
- const { handleOnChange, value, operator, context, level, rule } = props;
21
+ const { handleOnChange, value, operator, context, level, rule, fieldData } = props;
21
22
  let inputType = props.inputType;
22
23
  let values = props.values;
23
24
  const property = context.propertyTreeMap?.[rule.field];
@@ -31,6 +32,7 @@ const ValueEditor = (props) => {
31
32
  }));
32
33
  }
33
34
  }
35
+ const [anchorEl, setAnchorEl] = useState(null);
34
36
  const inputRef = useRef(null);
35
37
  const [openPresetValues, setOpenPresetValues] = useState(false);
36
38
  // Manages input value for Autocomplete when using 'in/not in' operators, ensuring correct handling on blur.
@@ -41,6 +43,15 @@ const ValueEditor = (props) => {
41
43
  const isPresetValueSelected = presetValues && typeof value === 'string' && isPresetValue(value);
42
44
  const presetDisplayValue = presetValues?.find((option) => option.value.name === value)?.label ?? '';
43
45
  let readOnly = false;
46
+ useEffect(() => {
47
+ if (!['in', 'notIn'].includes(operator) && Array.isArray(value)) {
48
+ handleOnChange('');
49
+ }
50
+ else if (['in', 'notIn'].includes(operator) && !Array.isArray(value)) {
51
+ handleOnChange([]);
52
+ setInputValue('');
53
+ }
54
+ }, [operator]);
44
55
  if (context.disabledCriteria) {
45
56
  readOnly =
46
57
  Object.entries(context.disabledCriteria.criteria).some(([key, value]) => key === rule.field && value === rule.value && rule.operator === '=') && level === context.disabledCriteria.level;
@@ -48,7 +59,7 @@ const ValueEditor = (props) => {
48
59
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
60
  const onClick = (e) => {
50
61
  // if property is date and date picker is open, don't open preset values
51
- if (inputType && ['date', 'time'].includes(inputType) && e.target?.tagName !== 'INPUT') {
62
+ if (inputType && ['date', 'time', 'date-time'].includes(inputType) && e.target?.tagName !== 'INPUT') {
52
63
  return;
53
64
  }
54
65
  setOpenPresetValues(true);
@@ -66,6 +77,13 @@ const ValueEditor = (props) => {
66
77
  const groupRenderGroup = (params) => (React.createElement("li", { key: params.key },
67
78
  React.createElement(GroupHeader, null, params.group),
68
79
  React.createElement(GroupItems, null, params.children)));
80
+ function parseISOStringToLocalDateTime(value) {
81
+ if (!value) {
82
+ return null;
83
+ }
84
+ const d = new Date(value);
85
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(d.getTime()), ZoneId.systemDefault());
86
+ }
69
87
  const getEditor = () => {
70
88
  if (isPresetValueSelected) {
71
89
  return;
@@ -82,6 +100,28 @@ const ValueEditor = (props) => {
82
100
  return (React.createElement(LocalizationProvider, null,
83
101
  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 })));
84
102
  }
103
+ else if (inputType === 'date-time') {
104
+ const dateTimeValue = parseISOStringToLocalDateTime(value);
105
+ return (React.createElement(LocalizationProvider, null,
106
+ React.createElement(DateTimePicker, { inputRef: inputRef, value: dateTimeValue, onChange: (date) => {
107
+ if (!date) {
108
+ handleOnChange('');
109
+ return;
110
+ }
111
+ let localDateTime;
112
+ if (date instanceof LocalDate && !(date instanceof LocalDateTime)) {
113
+ // onChange initially returns a LocalDate after date is selected
114
+ localDateTime = LocalDateTime.of(date, LocalTime.of(0));
115
+ }
116
+ else {
117
+ localDateTime = date;
118
+ }
119
+ handleOnChange(new Date(localDateTime.toString()).toISOString());
120
+ }, onClose: onClose, PopperProps: {
121
+ anchorEl,
122
+ }, renderInput: (params) => (React.createElement(Box, { sx: { width: '33%', background: '#fff' }, ref: setAnchorEl },
123
+ React.createElement(TextField, { ...params, disabled: disabled, onClick: onClick, placeholder: "Value", size: "small", inputRef: inputRef }))), readOnly: readOnly })));
124
+ }
85
125
  else if (inputType === 'number' || inputType === 'integer') {
86
126
  const isMultiple = ['in', 'notIn'].includes(operator);
87
127
  const options = presetValues;
@@ -116,7 +156,7 @@ const ValueEditor = (props) => {
116
156
  ...(presetValues?.sort((a, b) => a.label.localeCompare(b.label)) ?? []),
117
157
  ];
118
158
  if (isMultiple || values?.length) {
119
- return (React.createElement(Autocomplete, { freeSolo: inputType !== 'array' && inputType !== 'select', multiple: isMultiple, options: options, value: isMultiple ? (Array.isArray(value) ? value : []) : value,
159
+ return (React.createElement(Autocomplete, { freeSolo: inputType !== 'array' && fieldData.valueEditorType !== 'select', multiple: isMultiple, options: options, value: isMultiple ? (Array.isArray(value) ? value : []) : Array.isArray(value) ? '' : value,
120
160
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
161
  onChange: (event, newValue) => {
122
162
  let value;
@@ -130,7 +170,19 @@ const ValueEditor = (props) => {
130
170
  }
131
171
  handleOnChange(value);
132
172
  }, onBlur: () => {
133
- if (inputValue && (operator === 'in' || operator === 'notIn')) {
173
+ if (inputValue &&
174
+ (options.some((option) => option.name === inputValue) || !options.length) &&
175
+ (operator === 'in' || operator === 'notIn')) {
176
+ const newValues = Array.isArray(value) ? [...value, inputValue] : [inputValue];
177
+ handleOnChange(Array.from(new Set(newValues)));
178
+ setInputValue('');
179
+ }
180
+ }, onKeyDown: (event) => {
181
+ if (event.key === 'Enter' &&
182
+ inputValue &&
183
+ (options.some((option) => option.name === inputValue) || !options.length) &&
184
+ (operator === 'in' || operator === 'notIn')) {
185
+ event.preventDefault();
134
186
  const newValues = Array.isArray(value) ? [...value, inputValue] : [inputValue];
135
187
  handleOnChange(Array.from(new Set(newValues)));
136
188
  setInputValue('');
@@ -1,7 +1,7 @@
1
1
  import { useApp, useAuthenticationContext, } from '@evoke-platform/context';
2
2
  import { Components, Form as FormIO, Utils } from '@formio/react';
3
3
  import { flatten } from 'flat';
4
- import { isEqual, toPairs } from 'lodash';
4
+ import { isEmpty, isEqual, toPairs } from 'lodash';
5
5
  import React, { useEffect, useRef, useState } from 'react';
6
6
  import '../../../../styles/form-component.css';
7
7
  import { Skeleton, Snackbar } from '../../../core';
@@ -320,7 +320,9 @@ export function Form(props) {
320
320
  const submittedFields = {};
321
321
  for (const field in submission.data) {
322
322
  const value = submission.data[field];
323
- if (value === '' || (Array.isArray(value) && !value.length)) {
323
+ if (value === '' ||
324
+ (Array.isArray(value) && !value.length) ||
325
+ (typeof value === 'object' && isEmpty(value))) {
324
326
  submittedFields[field] = null;
325
327
  }
326
328
  else {
@@ -78,7 +78,11 @@ export const ObjectPropertyInput = (props) => {
78
78
  updatedFilter = {};
79
79
  }
80
80
  updatedFilter.limit = 100;
81
- updatedFilter.order = 'name ASC';
81
+ const { propertyId, direction } = layout?.sort ?? {
82
+ propertyId: 'name',
83
+ direction: 'asc',
84
+ };
85
+ updatedFilter.order = `${propertyId} ${direction}`;
82
86
  const where = name
83
87
  ? transformToWhere({
84
88
  name: {
@@ -103,7 +107,7 @@ export const ObjectPropertyInput = (props) => {
103
107
  setLoadingOptions(false);
104
108
  }
105
109
  });
106
- }, [relatedObject, setLoadingOptions, setOptions, property, filter]);
110
+ }, [relatedObject, setLoadingOptions, setOptions, property, filter, layout]);
107
111
  useEffect(() => {
108
112
  if (displayOption === 'dropdown') {
109
113
  getDropdownOptions();
@@ -281,7 +285,7 @@ export const ObjectPropertyInput = (props) => {
281
285
  caretColor: 'white',
282
286
  }
283
287
  : {}),
284
- } })), readOnly: !loadingOptions && !canUpdateProperty, error: error }))) : (React.createElement(Box, { sx: {
288
+ } })), readOnly: !loadingOptions && !canUpdateProperty, error: error, sortBy: "NONE" }))) : (React.createElement(Box, { sx: {
285
289
  padding: (instance?.[property.id]?.name ?? selectedInstance?.name)
286
290
  ? '16.5px 14px'
287
291
  : '10.5px 0',
@@ -78,9 +78,13 @@ export const DropdownRepeatableField = (props) => {
78
78
  setLoading(true);
79
79
  const endObjectProperty = middleObject.properties?.find((currProperty) => property.manyToManyPropertyId === currProperty.id);
80
80
  if (endObjectProperty?.objectId) {
81
+ const { propertyId, direction } = layout?.sort ?? {
82
+ propertyId: 'name',
83
+ direction: 'asc',
84
+ };
81
85
  const filter = {
82
86
  limit: 100,
83
- order: 'name ASC',
87
+ order: `${propertyId} ${direction}`,
84
88
  };
85
89
  let searchCriteria = criteria && !isEmpty(criteria) ? transformToWhere(criteria) : {};
86
90
  if (searchedName?.length) {
@@ -106,7 +110,7 @@ export const DropdownRepeatableField = (props) => {
106
110
  });
107
111
  }
108
112
  }
109
- }, [property.objectId, property.manyToManyPropertyId, apiServices, middleObject]);
113
+ }, [property.objectId, property.manyToManyPropertyId, apiServices, middleObject, layout]);
110
114
  const debouncedEndObjectSearch = useCallback(debounce(fetchEndObjectInstances, 500), [fetchEndObjectInstances]);
111
115
  useEffect(() => {
112
116
  debouncedEndObjectSearch(searchValue);
@@ -86,6 +86,7 @@ export const DropdownRepeatableFieldInput = (props) => {
86
86
  setSearchValue(event.target.value);
87
87
  } })),
88
88
  loading: loading,
89
+ sortBy: 'NONE',
89
90
  } }),
90
91
  React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({ isError: snackbarError.isError, showAlert: false }), message: snackbarError.message, error: snackbarError.isError })))) : (React.createElement(Typography, null, selectedOptions && selectedOptions.map((option) => option.label).join(', ')))));
91
92
  };
@@ -384,7 +384,7 @@ export function convertComponentsToForm(components) {
384
384
  ...(component.orderBy ? { sortBy: component.orderBy } : {}),
385
385
  }
386
386
  : isArray(component.initialValue)
387
- ? component.initialValue.map((c) => c.value)
387
+ ? component.initialValue.map((c) => typeof c === 'string' ? c : c.value)
388
388
  : component.initialValue?.value
389
389
  ? component.initialValue?.value
390
390
  : component.initialValue,
@@ -1,11 +1,13 @@
1
- export { ClickAwayListener, Toolbar, createTheme, styled } from '@mui/material';
1
+ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar } from '@mui/material';
2
2
  export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
3
+ export * from './colors';
3
4
  export * from './components/core';
4
- export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, MenuBar, MultiSelect, RepeatableField, UserAvatar, HistoryLog, RichTextViewer, } from './components/custom';
5
+ export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, HistoryLog, MenuBar, MultiSelect, RepeatableField, RichTextViewer, UserAvatar, } from './components/custom';
6
+ export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
5
7
  export { Box, Container, Grid, Stack } from './components/layout';
6
8
  export * as EVOKE_TYPES from './types';
7
9
  export * from './util';
8
- export type { ButtonBaseActions, ButtonBaseClasses, FormControlProps, FormHelperTextProps, GridSize, MenuItemClasses, Theme, } from '@mui/material';
10
+ export type { AutocompleteRenderGroupParams, ButtonBaseActions, ButtonBaseClasses, FormControlProps, FormHelperTextProps, GridSize, MenuItemClasses, TextFieldProps, Theme, } from '@mui/material';
9
11
  export type { TouchRippleActions, TouchRippleProps } from '@mui/material/ButtonBase/TouchRipple';
10
12
  export type { CommonProps } from '@mui/material/OverridableComponent';
11
13
  export type { SxProps } from '@mui/system';
@@ -1,7 +1,9 @@
1
- export { ClickAwayListener, Toolbar, createTheme, styled } from '@mui/material';
1
+ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar } from '@mui/material';
2
2
  export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
3
+ export * from './colors';
3
4
  export * from './components/core';
4
- export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, MenuBar, MultiSelect, RepeatableField, UserAvatar, HistoryLog, RichTextViewer, } from './components/custom';
5
+ export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, HistoryLog, MenuBar, MultiSelect, RepeatableField, RichTextViewer, UserAvatar, } from './components/custom';
6
+ export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
5
7
  export { Box, Container, Grid, Stack } from './components/layout';
6
8
  export * as EVOKE_TYPES from './types';
7
9
  export * from './util';
@@ -4,6 +4,98 @@ export default {
4
4
  title: 'Input/CriteriaBuilder',
5
5
  component: BuildCriteria,
6
6
  };
7
+ const defaultProperties = [
8
+ {
9
+ id: 'name',
10
+ name: 'name',
11
+ type: 'string',
12
+ required: true,
13
+ },
14
+ {
15
+ id: 'licenseNumber',
16
+ name: 'license number',
17
+ type: 'string',
18
+ required: true,
19
+ },
20
+ {
21
+ id: 'issueDate',
22
+ name: 'issue date',
23
+ type: 'date',
24
+ required: false,
25
+ },
26
+ {
27
+ id: 'applicationDate',
28
+ name: 'Application Date',
29
+ type: 'date-time',
30
+ required: false,
31
+ },
32
+ {
33
+ id: 'status',
34
+ name: 'status',
35
+ type: 'string',
36
+ enum: ['active', 'expired', 'pending approval'],
37
+ required: false,
38
+ },
39
+ {
40
+ id: 'licenseeName',
41
+ name: 'Licensee Name',
42
+ type: 'string',
43
+ required: false,
44
+ formula: '{{person.firstName}} {{person.middleName}} {{person.lastName}}',
45
+ },
46
+ {
47
+ id: 'calculated',
48
+ name: 'calculated',
49
+ type: 'string',
50
+ required: false,
51
+ formula: '{{status}} - {{licensee.firstName}}',
52
+ },
53
+ {
54
+ id: 'application',
55
+ name: 'applicant',
56
+ type: 'object',
57
+ required: false,
58
+ objectId: 'application',
59
+ },
60
+ {
61
+ id: 'applicantName',
62
+ name: 'Application Info',
63
+ type: 'string',
64
+ required: false,
65
+ formula: '{{application.name}} - {{status}}',
66
+ },
67
+ {
68
+ id: 'licensedFor',
69
+ name: 'licensed for',
70
+ type: 'array',
71
+ enum: [
72
+ 'amusements',
73
+ 'food and beverage',
74
+ 'entertainment',
75
+ 'transportation',
76
+ 'hot dogs',
77
+ 'swimming pools',
78
+ 'ferris wheels',
79
+ 'bungee jumping',
80
+ ],
81
+ required: true,
82
+ searchable: false,
83
+ },
84
+ {
85
+ id: 'inState',
86
+ name: 'In State',
87
+ type: 'string',
88
+ enum: ['Yes', 'No'],
89
+ required: false,
90
+ searchable: false,
91
+ },
92
+ {
93
+ id: 'amountOfInspections',
94
+ name: 'Inspection Count',
95
+ type: 'integer',
96
+ required: false,
97
+ },
98
+ ];
7
99
  const CriteriaBuilderTemplate = (args) => {
8
100
  const [criteria, setCriteria] = React.useState(args.criteria);
9
101
  args.setCriteria = setCriteria;
@@ -13,182 +105,12 @@ const CriteriaBuilderTemplate = (args) => {
13
105
  };
14
106
  export const CriteriaBuilderEmpty = CriteriaBuilderTemplate.bind({});
15
107
  CriteriaBuilderEmpty.args = {
16
- properties: [
17
- {
18
- id: 'name',
19
- name: 'name',
20
- type: 'string',
21
- required: true,
22
- },
23
- {
24
- id: 'licenseNumber',
25
- name: 'license number',
26
- type: 'string',
27
- required: true,
28
- },
29
- {
30
- id: 'issueDate',
31
- name: 'issue date',
32
- type: 'date',
33
- required: false,
34
- objectId: '',
35
- },
36
- {
37
- id: 'status',
38
- name: 'status',
39
- type: 'string',
40
- enum: ['active', 'expired', 'pending approval'],
41
- required: false,
42
- objectId: '',
43
- },
44
- {
45
- id: 'licenseeName',
46
- name: 'Licensee Name',
47
- type: 'string',
48
- required: false,
49
- formula: '{{person.firstName}} {{person.middleName}} {{person.lastName}}',
50
- },
51
- {
52
- id: 'calculated',
53
- name: 'calculated',
54
- type: 'string',
55
- required: false,
56
- objectId: '',
57
- formula: '{{status}} - {{licensee.firstName}}',
58
- },
59
- {
60
- id: 'application',
61
- name: 'applicant',
62
- type: 'object',
63
- required: false,
64
- objectId: 'application',
65
- },
66
- {
67
- id: 'applicantName',
68
- name: 'Application Info',
69
- type: 'string',
70
- required: false,
71
- formula: '{{application.name}} - {{status}}',
72
- },
73
- {
74
- id: 'licensedFor',
75
- name: 'licensed for',
76
- type: 'array',
77
- enum: [
78
- 'amusements',
79
- 'food and beverage',
80
- 'entertainment',
81
- 'transportation',
82
- 'hot dogs',
83
- 'swimming pools',
84
- 'ferris wheels',
85
- 'bungee jumping',
86
- ],
87
- required: true,
88
- searchable: false,
89
- },
90
- {
91
- id: 'inState',
92
- name: 'In State',
93
- type: 'string',
94
- enum: ['Yes', 'No'],
95
- required: false,
96
- searchable: false,
97
- },
98
- ],
108
+ properties: [...defaultProperties],
99
109
  criteria: {},
100
110
  };
101
111
  export const CriteriaBuilder = CriteriaBuilderTemplate.bind({});
102
112
  CriteriaBuilder.args = {
103
- properties: [
104
- {
105
- id: 'name',
106
- name: 'name',
107
- type: 'string',
108
- required: true,
109
- },
110
- {
111
- id: 'licenseNumber',
112
- name: 'license number',
113
- type: 'string',
114
- required: true,
115
- },
116
- {
117
- id: 'issueDate',
118
- name: 'issue date',
119
- type: 'date',
120
- required: false,
121
- objectId: '',
122
- },
123
- {
124
- id: 'status',
125
- name: 'status',
126
- type: 'string',
127
- enum: ['active', 'expired', 'pending approval'],
128
- required: false,
129
- objectId: '',
130
- },
131
- {
132
- id: 'licenseeName',
133
- name: 'Licensee Name',
134
- type: 'string',
135
- required: false,
136
- formula: '{{person.firstName}} {{person.middleName}} {{person.lastName}}',
137
- },
138
- {
139
- id: 'calculated',
140
- name: 'calculated',
141
- type: 'string',
142
- required: false,
143
- objectId: '',
144
- formula: '{{status}} - {{licensee.firstName}}',
145
- },
146
- {
147
- id: 'application',
148
- name: 'applicant',
149
- type: 'object',
150
- required: false,
151
- objectId: 'application',
152
- },
153
- {
154
- id: 'applicantName',
155
- name: 'Application Info',
156
- type: 'string',
157
- required: false,
158
- formula: '{{application.name}} - {{status}}',
159
- },
160
- {
161
- id: 'licensedFor',
162
- name: 'licensed for',
163
- type: 'array',
164
- enum: [
165
- 'amusements',
166
- 'food and beverage',
167
- 'entertainment',
168
- 'transportation',
169
- 'hot dogs',
170
- 'swimming pools',
171
- 'ferris wheels',
172
- 'bungee jumping',
173
- ],
174
- required: true,
175
- searchable: false,
176
- },
177
- {
178
- id: 'inState',
179
- name: 'In State',
180
- type: 'string',
181
- enum: ['Yes', 'No'],
182
- required: false,
183
- searchable: false,
184
- },
185
- {
186
- id: 'amountOfInspections',
187
- name: 'Inspection Count',
188
- type: 'integer',
189
- required: false,
190
- },
191
- ],
113
+ properties: [...defaultProperties],
192
114
  criteria: {
193
115
  $or: [
194
116
  {
@@ -202,6 +124,9 @@ CriteriaBuilder.args = {
202
124
  $lt: 3,
203
125
  },
204
126
  },
127
+ {
128
+ applicationDate: '2025-02-18T20:15:00.000Z',
129
+ },
205
130
  {
206
131
  licensedFor: { $in: ['hot dogs', 'amusements', 'entertainment'] },
207
132
  },
@@ -222,95 +147,7 @@ CriteriaBuilder.args = {
222
147
  };
223
148
  export const CriteriaBuilderPresetUserID = CriteriaBuilderTemplate.bind({});
224
149
  CriteriaBuilderPresetUserID.args = {
225
- properties: [
226
- {
227
- id: 'name',
228
- name: 'name',
229
- type: 'string',
230
- required: true,
231
- },
232
- {
233
- id: 'licenseNumber',
234
- name: 'license number',
235
- type: 'string',
236
- required: true,
237
- },
238
- {
239
- id: 'issueDate',
240
- name: 'issue date',
241
- type: 'date',
242
- required: false,
243
- objectId: '',
244
- },
245
- {
246
- id: 'status',
247
- name: 'status',
248
- type: 'string',
249
- enum: ['active', 'expired', 'pending approval'],
250
- required: false,
251
- objectId: '',
252
- },
253
- {
254
- id: 'licenseeName',
255
- name: 'Licensee Name',
256
- type: 'string',
257
- required: false,
258
- formula: '{{person.firstName}} {{person.middleName}} {{person.lastName}}',
259
- },
260
- {
261
- id: 'calculated',
262
- name: 'calculated',
263
- type: 'string',
264
- required: false,
265
- objectId: '',
266
- formula: '{{status}} - {{licensee.firstName}}',
267
- },
268
- {
269
- id: 'application',
270
- name: 'applicant',
271
- type: 'object',
272
- required: false,
273
- objectId: 'application',
274
- },
275
- {
276
- id: 'applicantName',
277
- name: 'Application Info',
278
- type: 'string',
279
- required: false,
280
- formula: '{{application.name}} - {{status}}',
281
- },
282
- {
283
- id: 'licensedFor',
284
- name: 'licensed for',
285
- type: 'array',
286
- enum: [
287
- 'amusements',
288
- 'food and beverage',
289
- 'entertainment',
290
- 'transportation',
291
- 'hot dogs',
292
- 'swimming pools',
293
- 'ferris wheels',
294
- 'bungee jumping',
295
- ],
296
- required: true,
297
- searchable: false,
298
- },
299
- {
300
- id: 'inState',
301
- name: 'In State',
302
- type: 'string',
303
- enum: ['Yes', 'No'],
304
- required: false,
305
- searchable: false,
306
- },
307
- {
308
- id: 'amountOfInspections',
309
- name: 'Inspection Count',
310
- type: 'integer',
311
- required: false,
312
- },
313
- ],
150
+ properties: [...defaultProperties],
314
151
  criteria: {
315
152
  $or: [
316
153
  {
@@ -325,60 +162,7 @@ CriteriaBuilderPresetUserID.args = {
325
162
  };
326
163
  export const CriteriaBuilderGroupedPresetValues = CriteriaBuilderTemplate.bind({});
327
164
  CriteriaBuilderGroupedPresetValues.args = {
328
- properties: [
329
- {
330
- id: 'name',
331
- name: 'name',
332
- type: 'string',
333
- required: true,
334
- },
335
- {
336
- id: 'issueDate',
337
- name: 'issue date',
338
- type: 'date',
339
- required: false,
340
- objectId: '',
341
- },
342
- {
343
- id: 'status',
344
- name: 'status',
345
- type: 'string',
346
- enum: ['active', 'expired', 'pending approval'],
347
- required: false,
348
- objectId: '',
349
- },
350
- {
351
- id: 'amountOfInspections',
352
- name: 'Inspection Count',
353
- type: 'integer',
354
- required: false,
355
- },
356
- {
357
- id: 'licensedFor',
358
- name: 'licensed for',
359
- type: 'array',
360
- enum: [
361
- 'amusements',
362
- 'food and beverage',
363
- 'entertainment',
364
- 'transportation',
365
- 'hot dogs',
366
- 'swimming pools',
367
- 'ferris wheels',
368
- 'bungee jumping',
369
- ],
370
- required: true,
371
- searchable: false,
372
- },
373
- {
374
- id: 'inState',
375
- name: 'In State',
376
- type: 'string',
377
- enum: ['Yes', 'No'],
378
- required: false,
379
- searchable: false,
380
- },
381
- ],
165
+ properties: [...defaultProperties],
382
166
  criteria: {
383
167
  $or: [
384
168
  {
@@ -8,6 +8,7 @@ export declare const NumberField: ComponentStory<(props: FormFieldProps) => JSX.
8
8
  export declare const MaskedInput: ComponentStory<(props: FormFieldProps) => JSX.Element>;
9
9
  export declare const ChoicesSelectField: ComponentStory<(props: FormFieldProps) => JSX.Element>;
10
10
  export declare const DatePickerField: ComponentStory<(props: FormFieldProps) => JSX.Element>;
11
+ export declare const DateTimePickerField: ComponentStory<(props: FormFieldProps) => JSX.Element>;
11
12
  export declare const BooleanField: ComponentStory<(props: FormFieldProps) => JSX.Element>;
12
13
  export declare const FileUploadField: ComponentStory<(props: FormFieldProps) => JSX.Element>;
13
14
  export declare const ArraySelectField: ComponentStory<(props: FormFieldProps) => JSX.Element>;
@@ -101,6 +101,21 @@ DatePickerField.args = {
101
101
  readOnly: false,
102
102
  size: 'small',
103
103
  };
104
+ export const DateTimePickerField = FormFieldTemplate.bind({});
105
+ DateTimePickerField.args = {
106
+ property: {
107
+ id: 'date-time',
108
+ name: 'Date Time',
109
+ type: 'date-time',
110
+ required: true,
111
+ },
112
+ onChange: (id, value) => console.log('id= ', id, 'Value= ', value),
113
+ defaultValue: '2025-02-19T18:05:00.000Z',
114
+ error: false,
115
+ required: true,
116
+ readOnly: false,
117
+ size: 'small',
118
+ };
104
119
  export const BooleanField = FormFieldTemplate.bind({});
105
120
  BooleanField.args = {
106
121
  property: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.0.2-testing.2",
3
+ "version": "1.1.0-dev.0",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",
@@ -94,7 +94,7 @@
94
94
  "@dnd-kit/sortable": "^7.0.1",
95
95
  "@emotion/react": "^11.13.5",
96
96
  "@emotion/styled": "^11.8.1",
97
- "@evoke-platform/context": "^1.0.0-dev.118",
97
+ "@evoke-platform/context": "^1.0.0-dev.126",
98
98
  "@formio/react": "^5.2.4-rc.1",
99
99
  "@js-joda/core": "^3.2.0",
100
100
  "@js-joda/locale_en-us": "^3.2.2",
@@ -135,12 +135,6 @@
135
135
  "npm run lint:fix"
136
136
  ]
137
137
  },
138
- "husky": {
139
- "hooks": {
140
- "pre-commit": "npx lint-staged",
141
- "commit-msg": "npx commitlint --edit $1"
142
- }
143
- },
144
138
  "jest": {
145
139
  "verbose": true,
146
140
  "testEnvironment": "jsdom",