@evoke-platform/ui-components 1.3.0-testing.0 → 1.4.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.
Files changed (42) hide show
  1. package/dist/published/components/core/Alert/Alert.js +1 -1
  2. package/dist/published/components/core/Autocomplete/Autocomplete.js +3 -3
  3. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +2 -18
  4. package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +2 -2
  5. package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +23 -8
  6. package/dist/published/components/custom/CriteriaBuilder/index.d.ts +2 -1
  7. package/dist/published/components/custom/CriteriaBuilder/index.js +2 -1
  8. package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +13 -0
  9. package/dist/published/components/custom/CriteriaBuilder/utils.js +58 -1
  10. package/dist/published/components/custom/Form/Common/Form.js +37 -26
  11. package/dist/published/components/custom/Form/Common/FormComponentWrapper.js +2 -1
  12. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.d.ts +2 -0
  13. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectComponent.js +75 -26
  14. package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +3 -2
  15. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.d.ts +5 -2
  16. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.js +4 -6
  17. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +4 -2
  18. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableFieldComponent.js +31 -13
  19. package/dist/published/components/custom/Form/tests/Form.test.d.ts +1 -0
  20. package/dist/published/components/custom/Form/tests/Form.test.js +158 -0
  21. package/dist/published/components/custom/Form/tests/test-data.d.ts +13 -0
  22. package/dist/published/components/custom/Form/tests/test-data.js +381 -0
  23. package/dist/published/components/custom/Form/utils.d.ts +10 -4
  24. package/dist/published/components/custom/Form/utils.js +206 -90
  25. package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +6 -2
  26. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -2
  27. package/dist/published/components/custom/HistoryLog/DisplayedProperty.d.ts +2 -1
  28. package/dist/published/components/custom/HistoryLog/DisplayedProperty.js +5 -2
  29. package/dist/published/components/custom/HistoryLog/HistoryData.d.ts +1 -0
  30. package/dist/published/components/custom/HistoryLog/HistoryData.js +9 -3
  31. package/dist/published/components/custom/HistoryLog/index.js +24 -2
  32. package/dist/published/components/custom/Menubar/Menubar.d.ts +2 -3
  33. package/dist/published/components/custom/Menubar/Menubar.js +15 -12
  34. package/dist/published/components/custom/index.d.ts +1 -1
  35. package/dist/published/components/custom/index.js +1 -1
  36. package/dist/published/index.d.ts +3 -2
  37. package/dist/published/index.js +3 -2
  38. package/dist/published/theme/hooks.d.ts +31 -0
  39. package/dist/published/theme/hooks.js +35 -0
  40. package/dist/published/theme/index.d.ts +3 -0
  41. package/dist/published/theme/index.js +3 -0
  42. package/package.json +4 -5
@@ -1,6 +1,10 @@
1
1
  import { camelCase, get, isArray, isEmpty, isNil, isObject, isUndefined, pick, startCase, transform, uniq, } from 'lodash';
2
2
  import { DateTime } from 'luxon';
3
3
  import { nanoid } from 'nanoid';
4
+ import Handlebars from 'no-eval-handlebars';
5
+ import qs from 'qs';
6
+ import { defaultRuleProcessorMongoDB, formatQuery, parseMongoDB, } from 'react-querybuilder';
7
+ import escape from 'string-escape-regex';
4
8
  // The following functions are used to build the FormIO form components from the form parameters.
5
9
  export function determineComponentType(properties, parameter) {
6
10
  if (!parameter)
@@ -96,15 +100,7 @@ export function convertFormToComponents(entries, parameters, object) {
96
100
  ? convertFormToComponents(section.entries, parameters, object)
97
101
  : [],
98
102
  })),
99
- conditional: typeof entry.visibility !== 'string' && entry.visibility?.conditions?.length
100
- ? {
101
- show: entry.visibility.conditions[0].operator === 'eq',
102
- when: entry.visibility.conditions[0].property,
103
- eq: entry.visibility.conditions[0].value,
104
- }
105
- : {
106
- json: entry.visibility,
107
- },
103
+ conditional: convertVisibilityToConditional(entry.visibility),
108
104
  };
109
105
  }
110
106
  else if (entry.type === 'columns') {
@@ -120,15 +116,7 @@ export function convertFormToComponents(entries, parameters, object) {
120
116
  ? convertFormToComponents(column.entries, parameters, object)
121
117
  : [],
122
118
  })),
123
- conditional: typeof entry.visibility !== 'string' && entry.visibility?.conditions?.length
124
- ? {
125
- show: entry.visibility.conditions[0].operator === 'eq',
126
- when: entry.visibility.conditions[0].property,
127
- eq: entry.visibility.conditions[0].value,
128
- }
129
- : {
130
- json: entry.visibility,
131
- },
119
+ conditional: convertVisibilityToConditional(entry.visibility),
132
120
  };
133
121
  }
134
122
  else if (entry.type === 'content') {
@@ -136,15 +124,7 @@ export function convertFormToComponents(entries, parameters, object) {
136
124
  type: 'Content',
137
125
  key: nanoid(),
138
126
  html: entry.html,
139
- conditional: typeof entry.visibility !== 'string' && entry.visibility?.conditions?.length
140
- ? {
141
- show: entry.visibility.conditions[0].operator === 'eq',
142
- when: entry.visibility.conditions[0].property,
143
- eq: entry.visibility.conditions[0].value,
144
- }
145
- : {
146
- json: entry.visibility,
147
- },
127
+ conditional: convertVisibilityToConditional(entry.visibility),
148
128
  };
149
129
  }
150
130
  else {
@@ -163,6 +143,7 @@ export function convertFormToComponents(entries, parameters, object) {
163
143
  name: topLevelProperty?.name
164
144
  ? `${topLevelProperty.name} ${startCase(property.id.split('.')[1])}`
165
145
  : startCase(property.id.split('.')[1]),
146
+ type: topLevelProperty?.type,
166
147
  };
167
148
  }
168
149
  const type = determineComponentType(object.properties ?? [], parameter);
@@ -261,17 +242,7 @@ export function convertFormToComponents(entries, parameters, object) {
261
242
  widget: (parameter.type === 'string' && parameter.enum) || parameter.type === 'array'
262
243
  ? 'choicejs'
263
244
  : undefined,
264
- conditional: displayOptions?.visibility &&
265
- typeof displayOptions?.visibility !== 'string' &&
266
- displayOptions.visibility.conditions?.length
267
- ? {
268
- show: displayOptions?.visibility.conditions[0].operator === 'eq',
269
- when: displayOptions?.visibility.conditions[0].property,
270
- eq: displayOptions?.visibility.conditions[0].value,
271
- }
272
- : {
273
- json: displayOptions?.visibility,
274
- },
245
+ conditional: convertVisibilityToConditional(displayOptions?.visibility),
275
246
  viewLayout: displayOptions?.viewLayout,
276
247
  };
277
248
  }
@@ -482,11 +453,26 @@ export function getPrefixedUrl(url) {
482
453
  console.error('Invalid URL');
483
454
  return url;
484
455
  }
456
+ export function flattenFormComponents(components) {
457
+ if (!components)
458
+ return [];
459
+ return components.reduce((acc, component) => {
460
+ if (component.type === 'Section') {
461
+ return acc.concat(component.components?.flatMap((components) => flattenFormComponents(components.components)) ?? []);
462
+ }
463
+ else if (component.type === 'Columns') {
464
+ return acc.concat(component.columns?.flatMap((column) => flattenFormComponents(column.components)) ?? []);
465
+ }
466
+ else {
467
+ return acc.concat(component);
468
+ }
469
+ }, []);
470
+ }
485
471
  // The following function adds the object properties to the form components.
486
472
  // This function is used when there is no form configured in the form builder.
487
473
  export async function addObjectPropertiesToComponentProps(properties,
488
474
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
489
- formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultPages, navigateTo, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor) {
475
+ formComponents, allCriteriaInputs, instance, objectPropertyInputProps, associatedObject, autoSave, readOnly, defaultPages, navigateTo, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor) {
490
476
  return [
491
477
  ...(await Promise.all(formComponents
492
478
  ?.filter((component) => !isUndefined(component) && !isNil(component))
@@ -624,6 +610,8 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
624
610
  ...(component.type === 'Object' && objectPropertyInputProps),
625
611
  defaultValue,
626
612
  property,
613
+ allCriteriaInputs,
614
+ properties,
627
615
  type: `${readOnly ? 'ViewOnly' : ''}${component.type}`,
628
616
  readOnly: component.readOnly || readOnly || property?.formula,
629
617
  multiple: ['array', 'document'].includes(property.type),
@@ -648,13 +636,24 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
648
636
  : undefined,
649
637
  };
650
638
  }
651
- const defaultValue = getDefaultValue(isNil(instanceValue) ? component.initialValue : instanceValue, component?.data?.values);
639
+ let defaultValue = getDefaultValue(isNil(instanceValue) ? component.initialValue : instanceValue, component?.data?.values);
640
+ // If "associatedObject" is defined, that means the form is associating an existing instance with the current instance.
641
+ // Set the associated instance as a default value and hide the field.
642
+ if (associatedObject?.instanceId &&
643
+ associatedObject?.propertyId &&
644
+ associatedObject?.propertyId === property.id) {
645
+ defaultValue = { id: associatedObject.instanceId };
646
+ component.hidden = true;
647
+ }
652
648
  return {
653
649
  ...component,
654
650
  ...(component.type === 'Object' && objectPropertyInputProps),
655
651
  type: `${readOnly ? 'ViewOnly' : ''}${component.type}`,
656
652
  defaultValue,
653
+ allCriteriaInputs,
654
+ properties,
657
655
  property,
656
+ associatedObject,
658
657
  readOnly: component.readOnly || readOnly || property?.formula,
659
658
  multiple: ['array', 'document'].includes(property.type),
660
659
  instance: instance,
@@ -693,7 +692,7 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
693
692
  }
694
693
  if (component.columns) {
695
694
  for (const column of component.columns) {
696
- column.components = await addObjectPropertiesToComponentProps(properties, column.components, instance, objectPropertyInputProps, autoSave, readOnly, undefined, undefined, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor);
695
+ column.components = await addObjectPropertiesToComponentProps(properties, column.components, allCriteriaInputs, instance, objectPropertyInputProps, associatedObject, autoSave, readOnly, undefined, undefined, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor);
697
696
  }
698
697
  return component;
699
698
  }
@@ -713,6 +712,14 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
713
712
  if (item.type.includes('RepeatableField') && !!instance) {
714
713
  item.instance = instance;
715
714
  }
715
+ // If "associatedObject" is defined, that means the form is associating an existing instance with the current instance.
716
+ // Set the associated instance as a default value and hide the field.
717
+ if (associatedObject?.instanceId &&
718
+ associatedObject?.propertyId &&
719
+ item.property.id === associatedObject.propertyId) {
720
+ item.defaultValue = { id: associatedObject.instanceId };
721
+ item.hidden = true;
722
+ }
716
723
  }
717
724
  if (nestedFieldProperty) {
718
725
  item.type = `${readOnly ? 'ViewOnly' : ''}${item.type}`;
@@ -725,6 +732,7 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
725
732
  item.user = objectPropertyInputProps?.user;
726
733
  item.defaultPages = defaultPages;
727
734
  item.navigateTo = navigateTo;
735
+ item.allCriteriaInputs = allCriteriaInputs;
728
736
  item.isModal = isModal;
729
737
  item.fieldHeight = fieldHeight;
730
738
  item.richTextEditor = ['RepeatableField', 'Object'].includes(item.type)
@@ -751,7 +759,7 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
751
759
  }
752
760
  }
753
761
  return {
754
- components: await addObjectPropertiesToComponentProps(properties, component.components, instance, objectPropertyInputProps, autoSave, readOnly, defaultPages, navigateTo, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor),
762
+ components: await addObjectPropertiesToComponentProps(properties, component.components, allCriteriaInputs, instance, objectPropertyInputProps, associatedObject, autoSave, readOnly, defaultPages, navigateTo, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor),
755
763
  ...component,
756
764
  ...(component.type === 'Object' && objectPropertyInputProps),
757
765
  type: `${readOnly ? 'ViewOnly' : ''}${component.type}`,
@@ -1127,61 +1135,100 @@ export function transformToWhere(mongoQuery) {
1127
1135
  result[newKey] = isObject(value) ? transformToWhere(value) : value;
1128
1136
  });
1129
1137
  }
1130
- export function updateCriteriaInputs(criteria, field, fieldValue, isInputField) {
1131
- for (const [key, value] of Object.entries(criteria)) {
1132
- if (isArray(value)) {
1133
- for (const index in value) {
1134
- if (isObject(value[index])) {
1135
- updateCriteriaInputs(value[index], field, fieldValue, isInputField);
1136
- }
1137
- else {
1138
- value[index] =
1139
- typeof value[index] === 'string'
1140
- ? value[index]
1141
- ?.replaceAll(!isInputField ? `{{{${field}}}}` : `{{{input.${field}}}}`, fieldValue ?? '')
1142
- .trim() || undefined
1143
- : value ?? undefined;
1144
- }
1145
- }
1146
- }
1147
- else if (isObject(value)) {
1148
- updateCriteriaInputs(value, field, fieldValue, isInputField);
1138
+ function compileQueryValues(query, data) {
1139
+ if ('rules' in query && Array.isArray(query.rules)) {
1140
+ const updatedRules = query.rules
1141
+ .map((item) => compileQueryValues(item, data))
1142
+ .filter((item) => item !== undefined);
1143
+ if (updatedRules?.length) {
1144
+ return {
1145
+ ...query,
1146
+ rules: updatedRules,
1147
+ };
1149
1148
  }
1150
1149
  else {
1151
- criteria[key] =
1152
- typeof value === 'string'
1153
- ? value
1154
- ?.replaceAll(!isInputField ? `{{{${field}}}}` : `{{{input.${field}}}}`, fieldValue ?? '')
1155
- .trim() || undefined
1156
- : value ?? undefined;
1150
+ return undefined;
1157
1151
  }
1158
1152
  }
1159
- }
1160
- export function getAllCriteriaInputs(criteria) {
1161
- const result = [];
1162
- for (const [, value] of Object.entries(criteria)) {
1163
- if (isArray(value)) {
1164
- for (const item of value) {
1165
- if (item && typeof item === 'object') {
1166
- const inputProps = getAllCriteriaInputs(item);
1167
- inputProps && result.push(...inputProps);
1168
- }
1169
- else {
1170
- const inputProps = typeof item === 'string' ? item.match(/{{{input\..*}}}/g) : undefined;
1171
- inputProps && result.push(...inputProps);
1172
- }
1153
+ else {
1154
+ if ('value' in query && typeof query.value === 'string') {
1155
+ const template = Handlebars.compileAST(query.value);
1156
+ const result = template(data);
1157
+ if (result !== '') {
1158
+ return {
1159
+ ...query,
1160
+ value: result,
1161
+ };
1162
+ }
1163
+ else {
1164
+ return undefined;
1173
1165
  }
1174
1166
  }
1175
- else if (value && typeof value === 'object') {
1176
- const inputProps = getAllCriteriaInputs(value);
1177
- inputProps && result.push(...inputProps);
1178
- }
1179
- else {
1180
- const inputProps = typeof value === 'string' ? value.match(/{{{input\..*}}}/g) : undefined;
1181
- inputProps && result.push(...inputProps);
1182
- }
1167
+ return query;
1168
+ }
1169
+ }
1170
+ export function updateCriteriaInputs(criteria, data, user) {
1171
+ const dataSet = {
1172
+ input: {
1173
+ ...data,
1174
+ },
1175
+ user: user,
1176
+ };
1177
+ const compiledQuery = compileQueryValues(parseMongoDB(criteria), dataSet);
1178
+ // The "compiledQueryValues" function filters out rules that have a value of "undefined".
1179
+ // If "compiledQuery" is "undefined", that means that all rules and nested rules have a value of "undefined".
1180
+ // Therefore, the resulting query is empty.
1181
+ return !compiledQuery
1182
+ ? {}
1183
+ : JSON.parse(formatQuery(compiledQuery, {
1184
+ format: 'mongodb',
1185
+ ruleProcessor: (rule, options) => {
1186
+ let updatedRule = rule;
1187
+ if (['contains', 'beginsWith', 'endsWith'].includes(rule.operator)) {
1188
+ updatedRule = { ...rule, value: escape(rule.value) };
1189
+ }
1190
+ return defaultRuleProcessorMongoDB(updatedRule, options);
1191
+ },
1192
+ }));
1193
+ }
1194
+ function getRuleValues(query) {
1195
+ const values = [];
1196
+ if ('rules' in query && Array.isArray(query.rules)) {
1197
+ query.rules.forEach((item) => {
1198
+ values.push(...getRuleValues(item));
1199
+ });
1200
+ }
1201
+ else {
1202
+ values.push(query.value);
1203
+ }
1204
+ return values;
1205
+ }
1206
+ export function getCriteriaInputs(criteria) {
1207
+ if (!criteria)
1208
+ return [];
1209
+ const values = getRuleValues(parseMongoDB(criteria)).filter((val) => typeof val === 'string' && val.match(/{{{input\..*}}}/g));
1210
+ return uniq(values).map((item) => item.replace('{{{input.', '').replace('}}}', ''));
1211
+ }
1212
+ export function getAllCriteriaInputs(components) {
1213
+ const allComponents = components;
1214
+ const results = allComponents.reduce((inputs, c) => inputs.concat(getCriteriaInputs(c.validate?.criteria ??
1215
+ c.validation?.criteria)), []);
1216
+ return results;
1217
+ }
1218
+ export async function populateInstanceWithNestedData(instanceId, objectId, paths, apiServices) {
1219
+ let instance = { id: instanceId, objectId };
1220
+ try {
1221
+ instance = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instanceId}`), {
1222
+ params: {
1223
+ expand: paths,
1224
+ },
1225
+ paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
1226
+ });
1227
+ }
1228
+ catch (err) {
1229
+ console.log(err);
1183
1230
  }
1184
- return uniq(result.map((item) => item.replace('{{{input.', '').replace('}}}', '')));
1231
+ return instance;
1185
1232
  }
1186
1233
  export function isPropertyVisible(conditional, formData) {
1187
1234
  const isConditional = !!conditional?.when;
@@ -1232,3 +1279,72 @@ export function normalizeDates(instances, object) {
1232
1279
  });
1233
1280
  });
1234
1281
  }
1282
+ /**
1283
+ * Given an object entry in a JsonLogic object, map it to the correct value.
1284
+ * @param entry An entry in a JsonLogic object.
1285
+ * @returns entry with all values starting with "instance" replaced with "data._instance"
1286
+ */
1287
+ function processJsonLogicEntry(entry) {
1288
+ if (entry !== Object(entry)) {
1289
+ // entry is a primitive
1290
+ return typeof entry === 'string' ? entry.replace(/^instance/, 'data._instance') : entry;
1291
+ }
1292
+ else if (isArray(entry)) {
1293
+ return entry.map((element) => processJsonLogicEntry(element));
1294
+ }
1295
+ else if (typeof entry === 'object' && entry !== null) {
1296
+ let result = {};
1297
+ const entries = Object.entries(entry);
1298
+ for (const [key, val] of entries) {
1299
+ result = {
1300
+ ...result,
1301
+ [key]: processJsonLogicEntry(val),
1302
+ };
1303
+ }
1304
+ return result;
1305
+ }
1306
+ return entry;
1307
+ }
1308
+ /**
1309
+ * Given a JsonLogic, replace all keys (if any) starting with "instance" with
1310
+ * "data._instance".
1311
+ *
1312
+ * @param jsonLogic A JsonLogic instance.
1313
+ * @returns jsonLogic, with all keys starting with "instance" replaced with "data._instance".
1314
+ */
1315
+ function normalizeInstanceDataInJsonLogic(jsonLogic) {
1316
+ if (typeof jsonLogic === 'object' && jsonLogic !== null) {
1317
+ // jsonLogic is a Record<string, unknown>
1318
+ let result = {};
1319
+ const entries = Object.entries(jsonLogic);
1320
+ for (const [key, entry] of entries) {
1321
+ result = {
1322
+ ...result,
1323
+ [key]: processJsonLogicEntry(entry),
1324
+ };
1325
+ }
1326
+ return result;
1327
+ }
1328
+ // jsonLogic is a primitive
1329
+ return jsonLogic;
1330
+ }
1331
+ /**
1332
+ *
1333
+ * @param visibility A form's visibility entry.
1334
+ * @returns The form's visibility entry, converted to formio-readable form.
1335
+ */
1336
+ function convertVisibilityToConditional(visibility) {
1337
+ if (isObject(visibility) && 'conditions' in visibility && isArray(visibility.conditions)) {
1338
+ const [condition] = visibility.conditions;
1339
+ return {
1340
+ show: condition.operator === 'eq',
1341
+ when: condition.isInstanceProperty ? `_instance.${condition.property}` : condition.property,
1342
+ eq: condition.value,
1343
+ };
1344
+ }
1345
+ return visibility
1346
+ ? {
1347
+ json: normalizeInstanceDataInJsonLogic(visibility),
1348
+ }
1349
+ : undefined;
1350
+ }
@@ -48,7 +48,7 @@ const AddressFieldComponent = (props) => {
48
48
  !mask ? (React.createElement(TextField, { id: id, inputRef: textFieldRef, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: placeholder, InputProps: {
49
49
  type: 'search',
50
50
  autoComplete: 'off',
51
- }, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, disabled: false, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (inputProps) => (React.createElement(TextField, { id: id, inputRef: textFieldRef, sx: readOnly
51
+ }, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, inputRef: textFieldRef, sx: readOnly
52
52
  ? {
53
53
  '& .MuiOutlinedInput-notchedOutline': {
54
54
  border: 'none',
@@ -58,7 +58,11 @@ const AddressFieldComponent = (props) => {
58
58
  backgroundColor: '#f4f6f8',
59
59
  },
60
60
  }
61
- : undefined, required: required, error: error, errorMessage: errorMessage, InputProps: { ...inputProps, readOnly: readOnly }, fullWidth: true, size: size ?? 'medium', type: 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? rows ?? 3 : undefined, value: value, ...(additionalProps ?? {}) })))),
61
+ : undefined, required: required, error: error, errorMessage: errorMessage, InputProps: { readOnly: readOnly }, fullWidth: true, size: size ?? 'medium', type: 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ?? 3) : undefined, value: value, ...(additionalProps ?? {}) })
62
+ // Casting to `React.ReactNode` is necessary to resolve TypeScript errors
63
+ // due to compatibility issues with the outdated `react-input-mask` version
64
+ // and the newer `@types/react` package.
65
+ )))),
62
66
  !readOnly && (React.createElement(Popover, { id: popoverId, open: open, anchorEl: anchorEl, onClose: handleClose, disableAutoFocus: true, marginThreshold: undefined, anchorOrigin: {
63
67
  vertical: 'bottom',
64
68
  horizontal: 'left',
@@ -62,7 +62,7 @@ const InputFieldComponent = (props) => {
62
62
  backgroundColor: '#f4f6f8',
63
63
  },
64
64
  }),
65
- }, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, readOnly: readOnly }, required: required, fullWidth: true, onBlur: onBlur, placeholder: placeholder, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, disabled: false, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (inputProps) => (React.createElement(TextField, { id: id, sx: readOnly
65
+ }, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, readOnly: readOnly }, required: required, fullWidth: true, onBlur: onBlur, placeholder: placeholder, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, sx: readOnly
66
66
  ? {
67
67
  '& .MuiOutlinedInput-notchedOutline': {
68
68
  border: 'none',
@@ -72,6 +72,10 @@ const InputFieldComponent = (props) => {
72
72
  backgroundColor: '#f4f6f8',
73
73
  },
74
74
  }
75
- : undefined, required: required, error: error, errorMessage: errorMessage, InputProps: { ...inputProps, ...InputProps, readOnly: readOnly }, fullWidth: true, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) }))));
75
+ : undefined, required: required, error: error, errorMessage: errorMessage, InputProps: { ...InputProps, readOnly: readOnly }, fullWidth: true, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })
76
+ // Casting to `React.ReactNode` is necessary to resolve TypeScript errors
77
+ // due to compatibility issues with the outdated `react-input-mask` version
78
+ // and the newer `@types/react` package.
79
+ ))));
76
80
  };
77
81
  export default InputFieldComponent;
@@ -1,8 +1,9 @@
1
- import { Property } from '@evoke-platform/context';
1
+ import { Obj, Property } from '@evoke-platform/context';
2
2
  import React from 'react';
3
3
  type DisplayedPropertyProps = {
4
4
  property: Property;
5
5
  value: unknown;
6
+ referencedObject?: Obj;
6
7
  };
7
8
  declare const DisplayedProperty: (props: DisplayedPropertyProps) => React.JSX.Element;
8
9
  export default DisplayedProperty;
@@ -2,9 +2,10 @@ import { DateTime } from 'luxon';
2
2
  import React from 'react';
3
3
  import { CardMedia, Typography } from '../../core';
4
4
  import { Grid } from '../../layout';
5
+ import { getReadableQuery } from '../CriteriaBuilder';
5
6
  import { RichTextViewer } from '../RichTextViewer';
6
7
  const DisplayedProperty = (props) => {
7
- const { property, value } = props;
8
+ const { property, value, referencedObject } = props;
8
9
  const getAddressAsString = (address) => {
9
10
  let stringAddress = '';
10
11
  if (address?.line1)
@@ -22,7 +23,7 @@ const DisplayedProperty = (props) => {
22
23
  return stringAddress;
23
24
  };
24
25
  const formatData = (property, value) => {
25
- if (property?.objectId) {
26
+ if (property?.objectId && property?.type === 'object') {
26
27
  return value?.name ?? value?.id;
27
28
  }
28
29
  switch (property?.type) {
@@ -47,6 +48,8 @@ const DisplayedProperty = (props) => {
47
48
  return value ? DateTime.fromISO(value).toFormat('MM/dd/yyyy hh:mm a') : undefined;
48
49
  case 'document':
49
50
  return value && Array.isArray(value) ? value.map((v) => v.name).join(', ') : undefined;
51
+ case 'criteria':
52
+ return getReadableQuery(value ?? {}, referencedObject?.properties);
50
53
  }
51
54
  return value;
52
55
  };
@@ -6,6 +6,7 @@ type HistoryDataProps = {
6
6
  records: History[];
7
7
  documentHistory?: Record<string, History[]>;
8
8
  object: Obj;
9
+ referencedObjects?: Obj[];
9
10
  };
10
11
  declare const HistoricalData: (props: HistoryDataProps) => React.JSX.Element;
11
12
  export default HistoricalData;
@@ -30,7 +30,7 @@ const styles = {
30
30
  },
31
31
  };
32
32
  const HistoricalData = (props) => {
33
- const { records, documentHistory, object } = props;
33
+ const { records, documentHistory, object, referencedObjects } = props;
34
34
  const getPastDocumentVersion = (history) => {
35
35
  const documentVersions = documentHistory?.[history.subject?.id ?? 'unknown'] ?? [];
36
36
  const currentVersion = documentVersions?.map((v) => v.timestamp).indexOf(history.timestamp);
@@ -65,11 +65,17 @@ const HistoricalData = (props) => {
65
65
  fontWeight: 600,
66
66
  minWidth: 'fit-content',
67
67
  alignSelf: 'flex-start',
68
+ lineHeight: '17px',
69
+ padding: '3px 8px',
68
70
  } }, property.name)),
69
- React.createElement(DisplayedProperty, { property: property, value: d.historicalValue }),
71
+ React.createElement(DisplayedProperty, { property: property, value: d.historicalValue, referencedObject: property.objectId
72
+ ? referencedObjects?.find((o) => o.id === property.objectId)
73
+ : undefined }),
70
74
  React.createElement(Grid, { item: true, xs: 0.5 },
71
75
  React.createElement(ArrowForward, { sx: { fontSize: '12px' } })),
72
- React.createElement(DisplayedProperty, { property: property, value: d.updatedValue }))));
76
+ React.createElement(DisplayedProperty, { property: property, value: d.updatedValue, referencedObject: property.objectId
77
+ ? referencedObjects?.find((o) => o.id === property.objectId)
78
+ : undefined }))));
73
79
  }))),
74
80
  ['document', 'correspondence'].includes(r.type) && (React.createElement(Box, null,
75
81
  React.createElement(Box, { display: "grid", gridTemplateColumns: 'fit-content(100%) fit-content(2%) fit-content(100%)', alignItems: "center", sx: { overflowWrap: 'break-word' } },
@@ -1,6 +1,9 @@
1
+ import { useApiServices } from '@evoke-platform/context';
1
2
  import { Circle } from '@mui/icons-material';
3
+ import { uniq } from 'lodash';
2
4
  import { DateTime } from 'luxon';
3
5
  import React, { useEffect, useState } from 'react';
6
+ import { Snackbar } from '../../core';
4
7
  import Typography from '../../core/Typography';
5
8
  import Box from '../../layout/Box';
6
9
  import HistoryFilter from './Filter';
@@ -20,9 +23,27 @@ export const HistoryLog = (props) => {
20
23
  const { object, history, loading, title } = props;
21
24
  const [historyMap, setHistoryMap] = useState({});
22
25
  const [documentHistory, setDocumentHistory] = useState({});
26
+ const [referencedObjects, setReferencedObjects] = useState([]);
23
27
  const [filteredHistory, setFilteredHistory] = useState({});
24
28
  const [filter, setFilter] = useState([]);
25
29
  const [order, setOrder] = useState('desc');
30
+ const [showSnackbar, setShowSnackbar] = useState(false);
31
+ const apiServices = useApiServices();
32
+ useEffect(() => {
33
+ const criteriaProperties = object.properties?.filter((property) => property.type === 'criteria');
34
+ if (criteriaProperties?.length) {
35
+ const uniqueObjectIds = uniq(criteriaProperties?.map((property) => property.objectId));
36
+ Promise.all(uniqueObjectIds.map((objectId) => apiServices.get(`/data/objects/${objectId}/effective`, {
37
+ params: {
38
+ filter: {
39
+ fields: ['id', 'name', 'properties'],
40
+ },
41
+ },
42
+ })))
43
+ .then((objs) => setReferencedObjects(objs))
44
+ .catch(() => setShowSnackbar(true));
45
+ }
46
+ }, [object, apiServices]);
26
47
  const sortHistoryByTimestamp = (historicalData, order) => {
27
48
  return historicalData.sort((a, b) => order === 'desc' ? b.timestamp.localeCompare(a.timestamp) : a.timestamp.localeCompare(b.timestamp));
28
49
  };
@@ -78,13 +99,14 @@ export const HistoryLog = (props) => {
78
99
  ' ',
79
100
  "\u00A0"),
80
101
  React.createElement(Typography, { sx: { fontWeight: 600, fontSize: '16px', color: '#637381' } }, format(new Date(date + ' 00:00:000'), 'MMM dd, yyyy'))),
81
- React.createElement(HistoricalData, { object: object, records: records, documentHistory: documentHistory })));
102
+ React.createElement(HistoricalData, { object: object, records: records, documentHistory: documentHistory, referencedObjects: referencedObjects })));
82
103
  }
83
104
  return null;
84
105
  }),
85
106
  !loading && filteredHistory && Object.values(filteredHistory).every((v) => !v.length) && (React.createElement(Box, { width: '100%', display: 'grid', justifyContent: 'center', marginTop: '60px' },
86
107
  React.createElement(Typography, { fontSize: '20px', fontWeight: 700 }, "You Have No History"),
87
108
  React.createElement(Typography, { fontSize: '14px', fontWeight: 400 }, "Try modifying the history type."))),
88
- loading && React.createElement(HistoryLoading, null)));
109
+ loading && React.createElement(HistoryLoading, null),
110
+ React.createElement(Snackbar, { open: showSnackbar, handleClose: () => setShowSnackbar(false), message: 'Error occurred when loading referenced objects', error: true })));
89
111
  };
90
112
  export default HistoryLog;
@@ -1,10 +1,9 @@
1
- import { ReactJSXElement } from '@emotion/react/types/jsx-namespace';
2
- import React from 'react';
1
+ import React, { ReactNode } from 'react';
3
2
  export type MenuBarProps = {
4
3
  showNotifications: boolean;
5
4
  logo: string;
6
5
  logoAltText?: string;
7
- navItems?: ReactJSXElement;
6
+ navItems?: ReactNode;
8
7
  envName?: string;
9
8
  };
10
9
  export default function MenuBar(props: MenuBarProps): React.JSX.Element;
@@ -1,18 +1,21 @@
1
1
  import { AppBar, Box, CardMedia, Toolbar, Typography } from '@mui/material';
2
2
  import React from 'react';
3
- import UIThemeProvider from '../../../theme';
4
- const classes = {
5
- title: {
6
- flexGrow: 1,
7
- height: 70,
8
- },
9
- logo: {
10
- paddingRight: '16px',
11
- height: '70px',
12
- width: 'inherit',
13
- },
14
- };
3
+ import UIThemeProvider, { useResponsive } from '../../../theme';
15
4
  export default function MenuBar(props) {
5
+ const { isXs: isMobileView } = useResponsive();
6
+ const classes = {
7
+ title: {
8
+ height: 70,
9
+ flexGrow: 1,
10
+ },
11
+ logo: {
12
+ paddingRight: '16px',
13
+ height: '70px',
14
+ maxWidth: isMobileView ? '220px' : null,
15
+ objectFit: 'contain',
16
+ width: 'auto',
17
+ },
18
+ };
16
19
  return (React.createElement(UIThemeProvider, null,
17
20
  React.createElement(AppBar, { color: "inherit", position: "fixed", elevation: 0, sx: { zIndex: (theme) => theme.zIndex.drawer + 1, borderBottom: '1px solid #919EAB3D' } },
18
21
  React.createElement(Toolbar, { sx: { justifyContent: 'space-between' } },
@@ -1,5 +1,5 @@
1
1
  export { BuilderGrid } from './BuilderGrid';
2
- export { CriteriaBuilder } from './CriteriaBuilder';
2
+ export { CriteriaBuilder, getReadableQuery } from './CriteriaBuilder';
3
3
  export { DataGrid } from './DataGrid';
4
4
  export { ErrorComponent } from './ErrorComponent';
5
5
  export { Form } from './Form';