@evoke-platform/ui-components 1.4.0-testing.0 → 1.4.0-testing.10

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 (41) 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 +25 -17
  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 +213 -90
  25. package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
  26. package/dist/published/components/custom/HistoryLog/DisplayedProperty.d.ts +2 -1
  27. package/dist/published/components/custom/HistoryLog/DisplayedProperty.js +5 -2
  28. package/dist/published/components/custom/HistoryLog/HistoryData.d.ts +1 -0
  29. package/dist/published/components/custom/HistoryLog/HistoryData.js +9 -3
  30. package/dist/published/components/custom/HistoryLog/index.js +24 -2
  31. package/dist/published/components/custom/Menubar/Menubar.d.ts +2 -3
  32. package/dist/published/components/custom/Menubar/Menubar.js +15 -12
  33. package/dist/published/components/custom/index.d.ts +1 -1
  34. package/dist/published/components/custom/index.js +1 -1
  35. package/dist/published/index.d.ts +2 -1
  36. package/dist/published/index.js +2 -1
  37. package/dist/published/theme/hooks.d.ts +31 -0
  38. package/dist/published/theme/hooks.js +35 -0
  39. package/dist/published/theme/index.d.ts +3 -0
  40. package/dist/published/theme/index.js +3 -0
  41. package/package.json +3 -4
@@ -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,27 @@ 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
+ // If "conditional" is defined, the "hidden" property isn't respected.
648
+ // Remove the "conditional" property after setting "hidden" to true.
649
+ delete component.conditional;
650
+ }
652
651
  return {
653
652
  ...component,
654
653
  ...(component.type === 'Object' && objectPropertyInputProps),
655
654
  type: `${readOnly ? 'ViewOnly' : ''}${component.type}`,
656
655
  defaultValue,
656
+ allCriteriaInputs,
657
+ properties,
657
658
  property,
659
+ associatedObject,
658
660
  readOnly: component.readOnly || readOnly || property?.formula,
659
661
  multiple: ['array', 'document'].includes(property.type),
660
662
  instance: instance,
@@ -693,7 +695,7 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
693
695
  }
694
696
  if (component.columns) {
695
697
  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);
698
+ column.components = await addObjectPropertiesToComponentProps(properties, column.components, allCriteriaInputs, instance, objectPropertyInputProps, associatedObject, autoSave, readOnly, undefined, undefined, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor);
697
699
  }
698
700
  return component;
699
701
  }
@@ -713,6 +715,17 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
713
715
  if (item.type.includes('RepeatableField') && !!instance) {
714
716
  item.instance = instance;
715
717
  }
718
+ // If "associatedObject" is defined, that means the form is associating an existing instance with the current instance.
719
+ // Set the associated instance as a default value and hide the field.
720
+ if (associatedObject?.instanceId &&
721
+ associatedObject?.propertyId &&
722
+ item.property.id === associatedObject.propertyId) {
723
+ item.defaultValue = { id: associatedObject.instanceId };
724
+ item.hidden = true;
725
+ // If "conditional" is defined, the "hidden" property isn't respected.
726
+ // Remove the "conditional" property after setting "hidden" to true.
727
+ delete item.conditional;
728
+ }
716
729
  }
717
730
  if (nestedFieldProperty) {
718
731
  item.type = `${readOnly ? 'ViewOnly' : ''}${item.type}`;
@@ -725,6 +738,8 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
725
738
  item.user = objectPropertyInputProps?.user;
726
739
  item.defaultPages = defaultPages;
727
740
  item.navigateTo = navigateTo;
741
+ item.allCriteriaInputs = allCriteriaInputs;
742
+ item.properties = properties;
728
743
  item.isModal = isModal;
729
744
  item.fieldHeight = fieldHeight;
730
745
  item.richTextEditor = ['RepeatableField', 'Object'].includes(item.type)
@@ -751,7 +766,7 @@ formComponents, instance, objectPropertyInputProps, autoSave, readOnly, defaultP
751
766
  }
752
767
  }
753
768
  return {
754
- components: await addObjectPropertiesToComponentProps(properties, component.components, instance, objectPropertyInputProps, autoSave, readOnly, defaultPages, navigateTo, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor),
769
+ components: await addObjectPropertiesToComponentProps(properties, component.components, allCriteriaInputs, instance, objectPropertyInputProps, associatedObject, autoSave, readOnly, defaultPages, navigateTo, queryAddresses, apiServices, isModal, fieldHeight, richTextEditor),
755
770
  ...component,
756
771
  ...(component.type === 'Object' && objectPropertyInputProps),
757
772
  type: `${readOnly ? 'ViewOnly' : ''}${component.type}`,
@@ -1127,61 +1142,100 @@ export function transformToWhere(mongoQuery) {
1127
1142
  result[newKey] = isObject(value) ? transformToWhere(value) : value;
1128
1143
  });
1129
1144
  }
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);
1145
+ function compileQueryValues(query, data) {
1146
+ if ('rules' in query && Array.isArray(query.rules)) {
1147
+ const updatedRules = query.rules
1148
+ .map((item) => compileQueryValues(item, data))
1149
+ .filter((item) => item !== undefined);
1150
+ if (updatedRules?.length) {
1151
+ return {
1152
+ ...query,
1153
+ rules: updatedRules,
1154
+ };
1149
1155
  }
1150
1156
  else {
1151
- criteria[key] =
1152
- typeof value === 'string'
1153
- ? value
1154
- ?.replaceAll(!isInputField ? `{{{${field}}}}` : `{{{input.${field}}}}`, fieldValue ?? '')
1155
- .trim() || undefined
1156
- : value ?? undefined;
1157
+ return undefined;
1157
1158
  }
1158
1159
  }
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
- }
1160
+ else {
1161
+ if ('value' in query && typeof query.value === 'string') {
1162
+ const template = Handlebars.compileAST(query.value);
1163
+ const result = template(data);
1164
+ if (result !== '') {
1165
+ return {
1166
+ ...query,
1167
+ value: result,
1168
+ };
1169
+ }
1170
+ else {
1171
+ return undefined;
1173
1172
  }
1174
1173
  }
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
- }
1174
+ return query;
1175
+ }
1176
+ }
1177
+ export function updateCriteriaInputs(criteria, data, user) {
1178
+ const dataSet = {
1179
+ input: {
1180
+ ...data,
1181
+ },
1182
+ user: user,
1183
+ };
1184
+ const compiledQuery = compileQueryValues(parseMongoDB(criteria), dataSet);
1185
+ // The "compiledQueryValues" function filters out rules that have a value of "undefined".
1186
+ // If "compiledQuery" is "undefined", that means that all rules and nested rules have a value of "undefined".
1187
+ // Therefore, the resulting query is empty.
1188
+ return !compiledQuery
1189
+ ? {}
1190
+ : JSON.parse(formatQuery(compiledQuery, {
1191
+ format: 'mongodb',
1192
+ ruleProcessor: (rule, options) => {
1193
+ let updatedRule = rule;
1194
+ if (['contains', 'beginsWith', 'endsWith'].includes(rule.operator)) {
1195
+ updatedRule = { ...rule, value: escape(rule.value) };
1196
+ }
1197
+ return defaultRuleProcessorMongoDB(updatedRule, options);
1198
+ },
1199
+ }));
1200
+ }
1201
+ function getRuleValues(query) {
1202
+ const values = [];
1203
+ if ('rules' in query && Array.isArray(query.rules)) {
1204
+ query.rules.forEach((item) => {
1205
+ values.push(...getRuleValues(item));
1206
+ });
1207
+ }
1208
+ else {
1209
+ values.push(query.value);
1210
+ }
1211
+ return values;
1212
+ }
1213
+ export function getCriteriaInputs(criteria) {
1214
+ if (!criteria)
1215
+ return [];
1216
+ const values = getRuleValues(parseMongoDB(criteria)).filter((val) => typeof val === 'string' && val.match(/{{{input\..*}}}/g));
1217
+ return uniq(values).map((item) => item.replace('{{{input.', '').replace('}}}', ''));
1218
+ }
1219
+ export function getAllCriteriaInputs(components) {
1220
+ const allComponents = components;
1221
+ const results = allComponents.reduce((inputs, c) => inputs.concat(getCriteriaInputs(c.validate?.criteria ??
1222
+ c.validation?.criteria)), []);
1223
+ return results;
1224
+ }
1225
+ export async function populateInstanceWithNestedData(instanceId, objectId, paths, apiServices) {
1226
+ let instance = { id: instanceId, objectId };
1227
+ try {
1228
+ instance = await apiServices.get(getPrefixedUrl(`/objects/${objectId}/instances/${instanceId}`), {
1229
+ params: {
1230
+ expand: paths,
1231
+ },
1232
+ paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
1233
+ });
1234
+ }
1235
+ catch (err) {
1236
+ console.log(err);
1183
1237
  }
1184
- return uniq(result.map((item) => item.replace('{{{input.', '').replace('}}}', '')));
1238
+ return instance;
1185
1239
  }
1186
1240
  export function isPropertyVisible(conditional, formData) {
1187
1241
  const isConditional = !!conditional?.when;
@@ -1232,3 +1286,72 @@ export function normalizeDates(instances, object) {
1232
1286
  });
1233
1287
  });
1234
1288
  }
1289
+ /**
1290
+ * Given an object entry in a JsonLogic object, map it to the correct value.
1291
+ * @param entry An entry in a JsonLogic object.
1292
+ * @returns entry with all values starting with "instance" replaced with "data._instance"
1293
+ */
1294
+ function processJsonLogicEntry(entry) {
1295
+ if (entry !== Object(entry)) {
1296
+ // entry is a primitive
1297
+ return typeof entry === 'string' ? entry.replace(/^instance/, 'data._instance') : entry;
1298
+ }
1299
+ else if (isArray(entry)) {
1300
+ return entry.map((element) => processJsonLogicEntry(element));
1301
+ }
1302
+ else if (typeof entry === 'object' && entry !== null) {
1303
+ let result = {};
1304
+ const entries = Object.entries(entry);
1305
+ for (const [key, val] of entries) {
1306
+ result = {
1307
+ ...result,
1308
+ [key]: processJsonLogicEntry(val),
1309
+ };
1310
+ }
1311
+ return result;
1312
+ }
1313
+ return entry;
1314
+ }
1315
+ /**
1316
+ * Given a JsonLogic, replace all keys (if any) starting with "instance" with
1317
+ * "data._instance".
1318
+ *
1319
+ * @param jsonLogic A JsonLogic instance.
1320
+ * @returns jsonLogic, with all keys starting with "instance" replaced with "data._instance".
1321
+ */
1322
+ function normalizeInstanceDataInJsonLogic(jsonLogic) {
1323
+ if (typeof jsonLogic === 'object' && jsonLogic !== null) {
1324
+ // jsonLogic is a Record<string, unknown>
1325
+ let result = {};
1326
+ const entries = Object.entries(jsonLogic);
1327
+ for (const [key, entry] of entries) {
1328
+ result = {
1329
+ ...result,
1330
+ [key]: processJsonLogicEntry(entry),
1331
+ };
1332
+ }
1333
+ return result;
1334
+ }
1335
+ // jsonLogic is a primitive
1336
+ return jsonLogic;
1337
+ }
1338
+ /**
1339
+ *
1340
+ * @param visibility A form's visibility entry.
1341
+ * @returns The form's visibility entry, converted to formio-readable form.
1342
+ */
1343
+ function convertVisibilityToConditional(visibility) {
1344
+ if (isObject(visibility) && 'conditions' in visibility && isArray(visibility.conditions)) {
1345
+ const [condition] = visibility.conditions;
1346
+ return {
1347
+ show: condition.operator === 'eq',
1348
+ when: condition.isInstanceProperty ? `_instance.${condition.property}` : condition.property,
1349
+ eq: condition.value,
1350
+ };
1351
+ }
1352
+ return visibility
1353
+ ? {
1354
+ json: normalizeInstanceDataInJsonLogic(visibility),
1355
+ }
1356
+ : undefined;
1357
+ }
@@ -58,7 +58,7 @@ const AddressFieldComponent = (props) => {
58
58
  backgroundColor: '#f4f6f8',
59
59
  },
60
60
  }
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 ?? {}) })
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
62
  // Casting to `React.ReactNode` is necessary to resolve TypeScript errors
63
63
  // due to compatibility issues with the outdated `react-input-mask` version
64
64
  // and the newer `@types/react` package.
@@ -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';
@@ -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';
@@ -2,10 +2,11 @@ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar } from
2
2
  export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
3
3
  export * from './colors';
4
4
  export * from './components/core';
5
- export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, HistoryLog, MenuBar, MultiSelect, RepeatableField, RichTextViewer, UserAvatar, } from './components/custom';
5
+ export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, RichTextViewer, UserAvatar, } from './components/custom';
6
6
  export type { FormRef } from './components/custom';
7
7
  export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
8
8
  export { Box, Container, Grid, Stack } from './components/layout';
9
+ export * from './theme';
9
10
  export * as EVOKE_TYPES from './types';
10
11
  export * from './util';
11
12
  export type { AutocompleteRenderGroupParams, ButtonBaseActions, ButtonBaseClasses, FormControlProps, FormHelperTextProps, GridSize, MenuItemClasses, TextFieldProps, Theme, } from '@mui/material';