@evoke-platform/ui-components 1.6.0-dev.2 → 1.6.0-dev.21
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.
- package/dist/published/components/core/SwipeableDrawer/SwipeableDrawer.d.ts +4 -0
- package/dist/published/components/core/SwipeableDrawer/SwipeableDrawer.js +8 -0
- package/dist/published/components/core/SwipeableDrawer/index.d.ts +3 -0
- package/dist/published/components/core/SwipeableDrawer/index.js +3 -0
- package/dist/published/components/core/index.d.ts +2 -1
- package/dist/published/components/core/index.js +2 -1
- package/dist/published/components/custom/BuilderGrid/BuilderGrid.js +27 -27
- package/dist/published/components/custom/Form/FormComponents/ImageComponent/Image.js +2 -2
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +87 -57
- package/dist/published/components/custom/Form/tests/Form.test.js +1 -1
- package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
- package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +3 -3
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +7 -1
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +7 -1
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -3
- package/dist/published/components/custom/FormField/Select/Select.js +17 -5
- package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +13 -3
- package/dist/published/components/custom/FormV2/components/ActionButtons.d.ts +19 -0
- package/dist/published/components/custom/FormV2/components/ActionButtons.js +106 -0
- package/dist/published/components/custom/FormV2/components/FieldWrapper.d.ts +24 -0
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +100 -0
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormContext.js +8 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +17 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +50 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +14 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +83 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.d.ts +13 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.js +140 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.d.ts +17 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableField.js +233 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.d.ts +40 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +95 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +526 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +93 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +73 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +13 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +179 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.d.ts +12 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +108 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +125 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.d.ts +15 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +226 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.d.ts +4 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +439 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +29 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +74 -0
- package/dist/published/components/custom/FormV2/components/TabNav.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/TabNav.js +23 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.d.ts +3 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/Validation.js +176 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +10 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +45 -0
- package/dist/published/components/custom/FormV2/components/types.d.ts +87 -0
- package/dist/published/components/custom/FormV2/components/types.js +1 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +39 -0
- package/dist/published/components/custom/FormV2/components/utils.js +269 -0
- package/dist/published/components/custom/HistoryLog/HistoryData.d.ts +1 -0
- package/dist/published/components/custom/HistoryLog/HistoryData.js +14 -6
- package/dist/published/components/custom/HistoryLog/HistoryLoading.d.ts +4 -1
- package/dist/published/components/custom/HistoryLog/HistoryLoading.js +14 -8
- package/dist/published/components/custom/HistoryLog/index.d.ts +2 -0
- package/dist/published/components/custom/HistoryLog/index.js +4 -4
- package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.d.ts +33 -0
- package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.js +143 -0
- package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.test.d.ts +1 -0
- package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.test.js +100 -0
- package/dist/published/components/custom/ResponsiveOverflow/index.d.ts +4 -0
- package/dist/published/components/custom/ResponsiveOverflow/index.js +3 -0
- package/dist/published/components/custom/index.d.ts +1 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +1 -1
- package/dist/published/index.js +1 -1
- package/dist/published/stories/ResponsiveOverflow.stories.d.ts +8 -0
- package/dist/published/stories/ResponsiveOverflow.stories.js +91 -0
- package/dist/published/theme/hooks.d.ts +7 -0
- package/dist/published/theme/hooks.js +9 -0
- package/package.json +4 -2
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { LocalDateTime } from '@js-joda/core';
|
|
2
|
+
import jsonLogic from 'json-logic-js';
|
|
3
|
+
import { get, isArray, isObject, transform } from 'lodash';
|
|
4
|
+
import { DateTime } from 'luxon';
|
|
5
|
+
export const scrollIntoViewWithOffset = (el, offset, container) => {
|
|
6
|
+
const elementRect = el.getBoundingClientRect();
|
|
7
|
+
const containerRect = container ? container.getBoundingClientRect() : document.body.getBoundingClientRect();
|
|
8
|
+
const topPosition = elementRect.top - containerRect.top + (container?.scrollTop || 0) - offset;
|
|
9
|
+
if (container) {
|
|
10
|
+
container.scrollTo({
|
|
11
|
+
behavior: 'smooth',
|
|
12
|
+
top: topPosition,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
window.scrollTo({
|
|
17
|
+
behavior: 'smooth',
|
|
18
|
+
top: topPosition,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const normalizeDateTime = (dateTime) => new Date(dateTime.toString()).toISOString();
|
|
23
|
+
const evaluateCondition = (condition, formValues, instance) => {
|
|
24
|
+
if (typeof condition !== 'object') {
|
|
25
|
+
console.error('Invalid condition format: ', condition);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (isArray(condition)) {
|
|
29
|
+
const firstCondition = condition[0];
|
|
30
|
+
const { property, value, operator } = firstCondition;
|
|
31
|
+
let fieldValue = firstCondition.isInstanceProperty ? get(instance, property) : get(formValues, property);
|
|
32
|
+
if (typeof fieldValue === 'object' && fieldValue !== null) {
|
|
33
|
+
if (fieldValue instanceof LocalDateTime) {
|
|
34
|
+
fieldValue = normalizeDateTime(fieldValue);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
fieldValue = Object.values(fieldValue).includes(value)
|
|
38
|
+
? value
|
|
39
|
+
: Object.values(fieldValue).find((val) => val !== undefined && val !== null);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
switch (operator) {
|
|
43
|
+
case 'ne':
|
|
44
|
+
return fieldValue != value;
|
|
45
|
+
case 'eq':
|
|
46
|
+
return fieldValue == value;
|
|
47
|
+
default:
|
|
48
|
+
console.error(`Unsupported operator: ${operator}`);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Handling custom JSON logic
|
|
53
|
+
else {
|
|
54
|
+
const data = {
|
|
55
|
+
data: formValues,
|
|
56
|
+
instance: instance,
|
|
57
|
+
};
|
|
58
|
+
const result = jsonLogic.apply(condition, data);
|
|
59
|
+
return result === true;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
export function isAddressProperty(key) {
|
|
63
|
+
return /\.line1|\.line2|\.city|\.county|\.state|\.zipCode$/.test(key);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Determine if a form entry is visible or not.
|
|
67
|
+
*/
|
|
68
|
+
export const entryIsVisible = (entry, formValues, instance) => {
|
|
69
|
+
const display = 'display' in entry ? entry.display : undefined;
|
|
70
|
+
const { visibility } = display ?? ('visibility' in entry ? entry : {});
|
|
71
|
+
if (isObject(visibility) && 'conditions' in visibility && isArray(visibility.conditions)) {
|
|
72
|
+
// visibility is a simple condition
|
|
73
|
+
const { conditions } = visibility;
|
|
74
|
+
return evaluateCondition(conditions, formValues, instance);
|
|
75
|
+
}
|
|
76
|
+
else if (visibility) {
|
|
77
|
+
// visibility is a JSONlogic condition
|
|
78
|
+
return evaluateCondition(visibility, formValues, instance);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Recursively retrieves all parameter IDs from a given entry of type Sections or Columns.
|
|
84
|
+
*
|
|
85
|
+
* @param {Sections | Columns} entry - The entry object, which can be of type Sections or Columns.
|
|
86
|
+
* @returns {string[]} - An array of parameter IDs found within the entry.
|
|
87
|
+
*/
|
|
88
|
+
export const getNestedParameterIds = (entry) => {
|
|
89
|
+
const parameterIds = [];
|
|
90
|
+
const entries = entry.type === 'columns'
|
|
91
|
+
? entry.columns.flatMap((column) => column.entries ?? [])
|
|
92
|
+
: entry.sections.flatMap((section) => section.entries ?? []);
|
|
93
|
+
for (const subEntry of entries) {
|
|
94
|
+
if (subEntry.type === 'columns' || subEntry.type === 'sections') {
|
|
95
|
+
parameterIds.push(...getNestedParameterIds(subEntry));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const paramId = getEntryId(subEntry);
|
|
99
|
+
if (paramId && paramId !== 'collection') {
|
|
100
|
+
parameterIds.push(paramId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return parameterIds;
|
|
105
|
+
};
|
|
106
|
+
export const getEntryId = (entry) => {
|
|
107
|
+
return entry.type === 'input'
|
|
108
|
+
? entry.parameterId
|
|
109
|
+
: entry.type === 'readonlyField'
|
|
110
|
+
? entry.propertyId
|
|
111
|
+
: entry.type === 'inputField'
|
|
112
|
+
? entry.input.id
|
|
113
|
+
: undefined;
|
|
114
|
+
};
|
|
115
|
+
export function getPrefixedUrl(url) {
|
|
116
|
+
const wcsMatchers = ['/apps', '/pages', '/widgets'];
|
|
117
|
+
const dataMatchers = ['/objects', '/correspondenceTemplates', '/documents', '/payments', '/locations'];
|
|
118
|
+
const signalrMatchers = ['/hubs'];
|
|
119
|
+
const accessManagementMatchers = ['/users'];
|
|
120
|
+
const workflowMatchers = ['/workflows'];
|
|
121
|
+
if (wcsMatchers.some((endpoint) => url.startsWith(endpoint)))
|
|
122
|
+
return `/webContent${url}`;
|
|
123
|
+
if (dataMatchers.some((endpoint) => url.startsWith(endpoint)))
|
|
124
|
+
return `/data${url}`;
|
|
125
|
+
if (signalrMatchers.some((endpoint) => url.startsWith(endpoint)))
|
|
126
|
+
return `/signalr${url}`;
|
|
127
|
+
if (accessManagementMatchers.some((endpoint) => url.startsWith(endpoint)))
|
|
128
|
+
return `/accessManagement${url}`;
|
|
129
|
+
if (workflowMatchers.some((endpoint) => url.startsWith(endpoint)))
|
|
130
|
+
return `/workflow${url}`;
|
|
131
|
+
console.error('Invalid URL');
|
|
132
|
+
return url;
|
|
133
|
+
}
|
|
134
|
+
export const isOptionEqualToValue = (option, value) => {
|
|
135
|
+
if (typeof option === 'string') {
|
|
136
|
+
return option === value;
|
|
137
|
+
}
|
|
138
|
+
return option.value === value;
|
|
139
|
+
};
|
|
140
|
+
export function addressProperties(addressProperty) {
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
id: `${addressProperty.id}.line1`,
|
|
144
|
+
name: `${addressProperty.name} Line 1`,
|
|
145
|
+
type: 'string',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: `${addressProperty.id}.line2`,
|
|
149
|
+
name: `${addressProperty.name} Line 2`,
|
|
150
|
+
type: 'string',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: `${addressProperty.id}.city`,
|
|
154
|
+
name: `${addressProperty.name} City`,
|
|
155
|
+
type: 'string',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: `${addressProperty.id}.county`,
|
|
159
|
+
name: `${addressProperty.name} County`,
|
|
160
|
+
type: 'string',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: `${addressProperty.id}.state`,
|
|
164
|
+
name: `${addressProperty.name} State`,
|
|
165
|
+
type: 'string',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: `${addressProperty.id}.zipCode`,
|
|
169
|
+
name: `${addressProperty.name} Zip Code`,
|
|
170
|
+
type: 'string',
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
export const normalizeDates = (instance, evokeObject) => {
|
|
175
|
+
const dateProps = ['date', 'date-time', 'time'];
|
|
176
|
+
const properties = evokeObject?.properties
|
|
177
|
+
?.filter((property) => dateProps.includes(property.type))
|
|
178
|
+
.reduce((agg, property) => Object.assign(agg, { [property.id]: property.type }), {}) ?? {};
|
|
179
|
+
const propKeys = Object.keys(properties);
|
|
180
|
+
const updatedInstance = { ...instance };
|
|
181
|
+
Object.keys(instance).forEach((key) => {
|
|
182
|
+
// Ignore non-datelike and empty fields.
|
|
183
|
+
if (!propKeys.includes(key) || !instance[key]) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
switch (properties[key]) {
|
|
187
|
+
case 'date':
|
|
188
|
+
// Casting here is valid because the value has already been
|
|
189
|
+
// determined to be a datelike field that is non-empty.
|
|
190
|
+
updatedInstance[key] = DateTime.fromISO(instance[key]).toLocaleString(DateTime.DATE_SHORT);
|
|
191
|
+
break;
|
|
192
|
+
case 'date-time':
|
|
193
|
+
// Casting here is valid because the value has already been
|
|
194
|
+
// determined to be a datelike field that is non-empty.
|
|
195
|
+
updatedInstance[key] = DateTime.fromISO(instance[key]).toLocaleString(DateTime.DATETIME_SHORT);
|
|
196
|
+
break;
|
|
197
|
+
case 'time':
|
|
198
|
+
// Casting here is valid because the value has already been
|
|
199
|
+
// determined to be a datelike field that is non-empty.
|
|
200
|
+
updatedInstance[key] = DateTime.fromISO(instance[key]).toLocaleString(DateTime.TIME_SIMPLE);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
return updatedInstance;
|
|
205
|
+
};
|
|
206
|
+
const OPERATOR_MAP = {
|
|
207
|
+
$and: 'and',
|
|
208
|
+
$or: 'or',
|
|
209
|
+
$eq: 'eq',
|
|
210
|
+
$ne: 'neq',
|
|
211
|
+
$lt: 'lt',
|
|
212
|
+
$lte: 'lte',
|
|
213
|
+
$gt: 'gt',
|
|
214
|
+
$gte: 'gte',
|
|
215
|
+
$in: 'inq',
|
|
216
|
+
$nin: 'nin',
|
|
217
|
+
$regex: 'regexp',
|
|
218
|
+
$exists: 'exists',
|
|
219
|
+
$not: 'not',
|
|
220
|
+
};
|
|
221
|
+
export const transformToWhere = (mongoQuery) => {
|
|
222
|
+
return transform(mongoQuery, (result, value, key) => {
|
|
223
|
+
const newKey = typeof key === 'string' && key.startsWith('$') ? OPERATOR_MAP[key] : key;
|
|
224
|
+
if (newKey === undefined) {
|
|
225
|
+
throw new Error(`Unsupported operator ${key}`);
|
|
226
|
+
}
|
|
227
|
+
result[newKey] = isObject(value) ? transformToWhere(value) : value;
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
export const getMiddleObject = (fieldDefinition, endObjectId, endObjectName, instance) => {
|
|
231
|
+
if (fieldDefinition.relatedPropertyId && fieldDefinition.manyToManyPropertyId) {
|
|
232
|
+
const middleObject = {
|
|
233
|
+
[fieldDefinition.relatedPropertyId]: {
|
|
234
|
+
id: instance?.id,
|
|
235
|
+
name: instance?.name,
|
|
236
|
+
},
|
|
237
|
+
[fieldDefinition.manyToManyPropertyId]: {
|
|
238
|
+
id: endObjectId,
|
|
239
|
+
name: endObjectName,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
return middleObject;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
export const getMiddleObjectFilter = (fieldDefinition, instanceId) => {
|
|
246
|
+
const filterProperty = `${fieldDefinition.relatedPropertyId}.id`;
|
|
247
|
+
const filter = { where: { [filterProperty]: instanceId } };
|
|
248
|
+
return filter;
|
|
249
|
+
};
|
|
250
|
+
export const encodePageSlug = (slug) => {
|
|
251
|
+
return encodeURIComponent(encodeURIComponent(slug));
|
|
252
|
+
};
|
|
253
|
+
export async function getDefaultPages(parameters, defaultPages, findDefaultPageSlugFor) {
|
|
254
|
+
let foundDefaultPages = defaultPages;
|
|
255
|
+
const relatedObjectProperties = parameters?.filter((param) => param.type === 'object');
|
|
256
|
+
if (relatedObjectProperties) {
|
|
257
|
+
foundDefaultPages = await relatedObjectProperties.reduce(async (acc, parameter) => {
|
|
258
|
+
const result = await acc;
|
|
259
|
+
if (parameter.objectId && defaultPages && defaultPages[parameter.objectId]) {
|
|
260
|
+
const slug = await findDefaultPageSlugFor(parameter.objectId);
|
|
261
|
+
if (slug) {
|
|
262
|
+
return { ...result, [parameter.objectId]: slug };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}, Promise.resolve({}));
|
|
267
|
+
}
|
|
268
|
+
return { ...defaultPages, ...foundDefaultPages };
|
|
269
|
+
}
|
|
@@ -19,8 +19,7 @@ const styles = {
|
|
|
19
19
|
color: '#637381',
|
|
20
20
|
},
|
|
21
21
|
timelineConnector: {
|
|
22
|
-
padding: '1px 0px 24px
|
|
23
|
-
borderLeft: '2px rgba(145, 158, 171, 0.24) solid',
|
|
22
|
+
padding: '1px 0px 24px 0px',
|
|
24
23
|
marginLeft: '5px',
|
|
25
24
|
},
|
|
26
25
|
historyData: {
|
|
@@ -30,15 +29,24 @@ const styles = {
|
|
|
30
29
|
},
|
|
31
30
|
};
|
|
32
31
|
const HistoricalData = (props) => {
|
|
33
|
-
const { records, documentHistory, object, referencedObjects } = props;
|
|
32
|
+
const { records, documentHistory, object, referencedObjects, hideTimeline } = props;
|
|
34
33
|
const getPastDocumentVersion = (history) => {
|
|
35
34
|
const documentVersions = documentHistory?.[history.subject?.id ?? 'unknown'] ?? [];
|
|
36
35
|
const currentVersion = documentVersions?.map((v) => v.timestamp).indexOf(history.timestamp);
|
|
37
36
|
return currentVersion ? documentVersions[currentVersion - 1] : undefined;
|
|
38
37
|
};
|
|
39
|
-
return (React.createElement(Box, { sx:
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
return (React.createElement(Box, { sx: {
|
|
39
|
+
...styles.timelineConnector,
|
|
40
|
+
borderLeft: !hideTimeline ? '2px rgba(145, 158, 171, 0.24) solid' : undefined,
|
|
41
|
+
} }, records.map((r) => (React.createElement(Box, { margin: '16px 0px', key: `${r.timestamp}-${nanoid()}` },
|
|
42
|
+
React.createElement(Box, { sx: {
|
|
43
|
+
display: 'flex',
|
|
44
|
+
justifyContent: 'space-between',
|
|
45
|
+
flexDirection: { xs: 'column', sm: 'row' },
|
|
46
|
+
alignItems: { xs: 'flex-start', sm: 'center' },
|
|
47
|
+
gap: 0,
|
|
48
|
+
} },
|
|
49
|
+
React.createElement(Box, { sx: { display: 'flex', maxWidth: { sx: '100%', sm: '60%' }, alignContent: 'flex-start' } },
|
|
42
50
|
React.createElement(Typography, { sx: { fontSize: '12px', color: '#637381' } },
|
|
43
51
|
React.createElement(Typography, { component: 'span', sx: { fontWeight: 600, fontSize: '12px' } }, r.user.name ?? 'Unknown User'),
|
|
44
52
|
"\u00A0",
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
import { Circle } from '@mui/icons-material';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import { Box } from '../../layout';
|
|
3
3
|
import { Skeleton } from '../../core';
|
|
4
|
-
import {
|
|
4
|
+
import { Box } from '../../layout';
|
|
5
5
|
const styles = {
|
|
6
6
|
timelineConnector: {
|
|
7
7
|
padding: '1px 0px 5px 22px',
|
|
8
|
-
borderLeft: '2px rgba(145, 158, 171, 0.24) solid',
|
|
9
8
|
marginLeft: '5px',
|
|
10
9
|
},
|
|
11
10
|
headerSkeleton: {
|
|
@@ -18,20 +17,27 @@ const styles = {
|
|
|
18
17
|
borderRadius: '7px',
|
|
19
18
|
},
|
|
20
19
|
};
|
|
21
|
-
const HistoryLoading = () => {
|
|
20
|
+
const HistoryLoading = (props) => {
|
|
21
|
+
const { hideTimeline } = props;
|
|
22
22
|
return (React.createElement(React.Fragment, { key: 'history-log-loading' },
|
|
23
23
|
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
24
|
-
React.createElement(Circle, { color: "primary", sx: { fontSize: '12px', marginRight: '12px' } }),
|
|
24
|
+
!hideTimeline && React.createElement(Circle, { color: "primary", sx: { fontSize: '12px', marginRight: '12px' } }),
|
|
25
25
|
React.createElement(Skeleton, { variant: "text", sx: styles.headerSkeleton })),
|
|
26
|
-
React.createElement(Box, { sx:
|
|
26
|
+
React.createElement(Box, { sx: {
|
|
27
|
+
...styles.timelineConnector,
|
|
28
|
+
borderLeft: !hideTimeline ? '2px rgba(145, 158, 171, 0.24) solid' : undefined,
|
|
29
|
+
} },
|
|
27
30
|
React.createElement(Box, { margin: '4px 0px 24px 0px' },
|
|
28
31
|
React.createElement(Skeleton, { variant: "text", sx: { ...styles.dataSkeleton, width: '120px' } }),
|
|
29
32
|
React.createElement(Skeleton, { variant: "text", sx: { ...styles.dataSkeleton, width: '65%' } }),
|
|
30
33
|
React.createElement(Skeleton, { variant: "text", sx: { ...styles.dataSkeleton, width: '75%' } }))),
|
|
31
34
|
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
32
|
-
React.createElement(Circle, { color: "primary", sx: { fontSize: '12px', marginRight: '12px' } }),
|
|
35
|
+
!hideTimeline && React.createElement(Circle, { color: "primary", sx: { fontSize: '12px', marginRight: '12px' } }),
|
|
33
36
|
React.createElement(Skeleton, { variant: "text", sx: styles.headerSkeleton })),
|
|
34
|
-
React.createElement(Box, { sx:
|
|
37
|
+
React.createElement(Box, { sx: {
|
|
38
|
+
...styles.timelineConnector,
|
|
39
|
+
borderLeft: !hideTimeline ? '2px rgba(145, 158, 171, 0.24) solid' : undefined,
|
|
40
|
+
} },
|
|
35
41
|
React.createElement(Box, { margin: '4px 0px 20px 0px' },
|
|
36
42
|
React.createElement(Skeleton, { variant: "text", sx: { ...styles.dataSkeleton, width: '120px' } }),
|
|
37
43
|
React.createElement(Skeleton, { variant: "text", sx: { ...styles.dataSkeleton, width: '55%' } })))));
|
|
@@ -10,6 +10,8 @@ export type HistoryLogProps = {
|
|
|
10
10
|
loading?: boolean;
|
|
11
11
|
/** The title displayed above the timeline */
|
|
12
12
|
title?: string;
|
|
13
|
+
/** Whether to hide the timeline in the history log */
|
|
14
|
+
hideTimeline?: boolean;
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
15
17
|
* Renders a timeline of the instance's history log.
|
|
@@ -20,7 +20,7 @@ const { format } = require('small-date');
|
|
|
20
20
|
* @returns {JSX.Element} A timeline view representing the instance's history.
|
|
21
21
|
*/
|
|
22
22
|
export const HistoryLog = (props) => {
|
|
23
|
-
const { object, history, loading, title } = props;
|
|
23
|
+
const { object, history, loading, title, hideTimeline } = props;
|
|
24
24
|
const [historyMap, setHistoryMap] = useState({});
|
|
25
25
|
const [documentHistory, setDocumentHistory] = useState({});
|
|
26
26
|
const [referencedObjects, setReferencedObjects] = useState([]);
|
|
@@ -91,7 +91,7 @@ export const HistoryLog = (props) => {
|
|
|
91
91
|
if (records.length) {
|
|
92
92
|
return (React.createElement(React.Fragment, { key: date },
|
|
93
93
|
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
94
|
-
React.createElement(Circle, { color: "primary", sx: { fontSize: '12px', marginRight: '12px' } }),
|
|
94
|
+
!hideTimeline && (React.createElement(Circle, { color: "primary", sx: { fontSize: '12px', marginRight: '12px' } })),
|
|
95
95
|
React.createElement(Typography, { sx: { fontWeight: 600, fontSize: '16px' } },
|
|
96
96
|
format(new Date(), 'yyyy-MM-dd') === date
|
|
97
97
|
? 'Today'
|
|
@@ -99,14 +99,14 @@ export const HistoryLog = (props) => {
|
|
|
99
99
|
' ',
|
|
100
100
|
"\u00A0"),
|
|
101
101
|
React.createElement(Typography, { sx: { fontWeight: 600, fontSize: '16px', color: '#637381' } }, format(new Date(date + ' 00:00:000'), 'MMM dd, yyyy'))),
|
|
102
|
-
React.createElement(HistoricalData, { object: object, records: records, documentHistory: documentHistory, referencedObjects: referencedObjects })));
|
|
102
|
+
React.createElement(HistoricalData, { object: object, records: records, documentHistory: documentHistory, referencedObjects: referencedObjects, hideTimeline: hideTimeline })));
|
|
103
103
|
}
|
|
104
104
|
return null;
|
|
105
105
|
}),
|
|
106
106
|
!loading && filteredHistory && Object.values(filteredHistory).every((v) => !v.length) && (React.createElement(Box, { width: '100%', display: 'grid', justifyContent: 'center', marginTop: '60px' },
|
|
107
107
|
React.createElement(Typography, { fontSize: '20px', fontWeight: 700 }, "You Have No History"),
|
|
108
108
|
React.createElement(Typography, { fontSize: '14px', fontWeight: 400 }, "Try modifying the history type."))),
|
|
109
|
-
loading && React.createElement(HistoryLoading,
|
|
109
|
+
loading && React.createElement(HistoryLoading, { hideTimeline: hideTimeline }),
|
|
110
110
|
React.createElement(Snackbar, { open: showSnackbar, handleClose: () => setShowSnackbar(false), message: 'Error occurred when loading referenced objects', error: true })));
|
|
111
111
|
};
|
|
112
112
|
export default HistoryLog;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ResponsiveOverflowProps {
|
|
3
|
+
/**
|
|
4
|
+
* Children elements to be displayed horizontally
|
|
5
|
+
*/
|
|
6
|
+
children: React.ReactNode[];
|
|
7
|
+
/**
|
|
8
|
+
* Text for the overflow button. Default is "More"
|
|
9
|
+
*/
|
|
10
|
+
moreButtonText?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Custom button element to replace the default "More" button
|
|
13
|
+
*/
|
|
14
|
+
customMoreButton?: React.ReactNode;
|
|
15
|
+
/**
|
|
16
|
+
* Maximum number of children to be displayed before overflowing
|
|
17
|
+
*/
|
|
18
|
+
maxVisible?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Function to render each overflow menu item. Receives the overflowed child and its index.
|
|
21
|
+
*/
|
|
22
|
+
renderOverflowMenuItem?: (item: React.ReactNode) => React.ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* If true, the container will take full width
|
|
25
|
+
*/
|
|
26
|
+
fullWidth?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* ResponsiveOverflow is a container component that displays children horizontally,
|
|
30
|
+
* automatically moving overflow items into a dropdown menu when there isn't enough space.
|
|
31
|
+
*/
|
|
32
|
+
export declare const ResponsiveOverflow: (props: ResponsiveOverflowProps) => React.JSX.Element;
|
|
33
|
+
export default ResponsiveOverflow;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { KeyboardArrowDownRounded } from '@mui/icons-material';
|
|
2
|
+
import { Box, Menu, MenuItem } from '@mui/material';
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { Button } from '../../core';
|
|
5
|
+
const styles = {
|
|
6
|
+
container: {
|
|
7
|
+
display: 'flex',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
flex: '1 1 auto',
|
|
10
|
+
minWidth: 0,
|
|
11
|
+
overflow: 'hidden',
|
|
12
|
+
justifyContent: 'flex-end',
|
|
13
|
+
},
|
|
14
|
+
moreButton: {
|
|
15
|
+
color: '#637381',
|
|
16
|
+
fontSize: '14px',
|
|
17
|
+
fontWeight: 700,
|
|
18
|
+
minWidth: 'unset',
|
|
19
|
+
'& .MuiButton-endIcon': { margin: '0px' },
|
|
20
|
+
},
|
|
21
|
+
menuPaper: {
|
|
22
|
+
border: '0.5px solid #919EAB52',
|
|
23
|
+
borderRadius: '8px',
|
|
24
|
+
minWidth: '150px',
|
|
25
|
+
boxShadow: '0px 8px 16px 0px #0000001F',
|
|
26
|
+
},
|
|
27
|
+
horizontalItems: {
|
|
28
|
+
display: 'inline-block',
|
|
29
|
+
verticalAlign: 'top',
|
|
30
|
+
whiteSpace: 'nowrap',
|
|
31
|
+
},
|
|
32
|
+
fullWidthItems: {
|
|
33
|
+
padding: '0 4px',
|
|
34
|
+
flexGrow: 1,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* ResponsiveOverflow is a container component that displays children horizontally,
|
|
39
|
+
* automatically moving overflow items into a dropdown menu when there isn't enough space.
|
|
40
|
+
*/
|
|
41
|
+
export const ResponsiveOverflow = (props) => {
|
|
42
|
+
const { children, customMoreButton, maxVisible, renderOverflowMenuItem, moreButtonText, fullWidth } = props;
|
|
43
|
+
const containerRef = useRef(null);
|
|
44
|
+
const itemRefs = useRef([]);
|
|
45
|
+
const moreButtonBoxRef = useRef(null);
|
|
46
|
+
const [overflowItems, setOverflowItems] = useState([]);
|
|
47
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
48
|
+
// Initialize refs array with correct length
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
itemRefs.current = itemRefs.current.slice(0, children.length);
|
|
51
|
+
handleResize();
|
|
52
|
+
}, [children]);
|
|
53
|
+
const handleResize = useCallback(() => {
|
|
54
|
+
if (!containerRef.current)
|
|
55
|
+
return;
|
|
56
|
+
// Reset all items to visible before measuring
|
|
57
|
+
itemRefs.current.forEach((item) => {
|
|
58
|
+
if (item) {
|
|
59
|
+
item.style.display = 'inline-block';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const availableWidth = containerRef.current.getBoundingClientRect().width;
|
|
63
|
+
let usedWidth = 0;
|
|
64
|
+
let maxFit = children.length;
|
|
65
|
+
// Determine which items fit and which overflow
|
|
66
|
+
for (let i = 0; i < itemRefs.current.length; i++) {
|
|
67
|
+
const item = itemRefs.current[i];
|
|
68
|
+
if (!item) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const itemWidth = item.getBoundingClientRect().width;
|
|
72
|
+
// Calculate if we need the "More" button
|
|
73
|
+
const remainingItems = itemRefs.current.length - i - 1;
|
|
74
|
+
const moreButtonWidth = remainingItems > 0 && moreButtonBoxRef.current
|
|
75
|
+
? moreButtonBoxRef.current.getBoundingClientRect().width
|
|
76
|
+
: 0;
|
|
77
|
+
if (usedWidth + itemWidth + (remainingItems > 0 ? moreButtonWidth : 0) > availableWidth) {
|
|
78
|
+
maxFit = i;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
usedWidth += itemWidth;
|
|
82
|
+
}
|
|
83
|
+
// If maxVisible is set, limit the number of visible items
|
|
84
|
+
if (typeof maxVisible === 'number') {
|
|
85
|
+
maxFit = Math.min(maxFit, maxVisible);
|
|
86
|
+
}
|
|
87
|
+
const newOverflow = [];
|
|
88
|
+
itemRefs.current.forEach((item, idx) => {
|
|
89
|
+
if (!item) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
else if (idx >= maxFit) {
|
|
93
|
+
item.style.display = 'none';
|
|
94
|
+
if (children[idx]) {
|
|
95
|
+
newOverflow.push(children[idx]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
item.style.display = 'inline-block';
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
setOverflowItems(newOverflow);
|
|
103
|
+
// Close menu if nothing is overflowing
|
|
104
|
+
if (newOverflow.length === 0) {
|
|
105
|
+
setAnchorEl(null);
|
|
106
|
+
}
|
|
107
|
+
}, [children, maxVisible]);
|
|
108
|
+
// Set up resize observer
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
111
|
+
requestAnimationFrame(handleResize);
|
|
112
|
+
});
|
|
113
|
+
if (containerRef.current) {
|
|
114
|
+
resizeObserver.observe(containerRef.current);
|
|
115
|
+
}
|
|
116
|
+
handleResize();
|
|
117
|
+
return () => {
|
|
118
|
+
if (containerRef.current) {
|
|
119
|
+
resizeObserver.unobserve(containerRef.current);
|
|
120
|
+
}
|
|
121
|
+
resizeObserver.disconnect();
|
|
122
|
+
};
|
|
123
|
+
}, [handleResize]);
|
|
124
|
+
const handleMenuOpen = (e) => setAnchorEl(e.currentTarget);
|
|
125
|
+
const handleMenuClose = () => setAnchorEl(null);
|
|
126
|
+
return (React.createElement(Box, { ref: containerRef, sx: styles.container },
|
|
127
|
+
children.map((item, index) => (React.createElement(Box, { ref: (el) => (itemRefs.current[index] = el), key: index, sx: { ...styles.horizontalItems, ...(fullWidth ? styles.fullWidthItems : {}) } }, item))),
|
|
128
|
+
!!overflowItems.length && (React.createElement(React.Fragment, null,
|
|
129
|
+
React.createElement(Box, { ref: moreButtonBoxRef, onClick: anchorEl ? handleMenuClose : handleMenuOpen }, customMoreButton ?? (React.createElement(Button, { variant: "text", sx: styles.moreButton, size: "small", endIcon: React.createElement(KeyboardArrowDownRounded, null) }, moreButtonText ?? 'More'))),
|
|
130
|
+
React.createElement(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: handleMenuClose, disablePortal: false, disableScrollLock: true, keepMounted: true, anchorOrigin: {
|
|
131
|
+
vertical: 'bottom',
|
|
132
|
+
horizontal: 'right',
|
|
133
|
+
}, transformOrigin: {
|
|
134
|
+
vertical: 'top',
|
|
135
|
+
horizontal: 'right',
|
|
136
|
+
}, sx: { '& .MuiPaper-root': styles.menuPaper }, onClick: handleMenuClose }, overflowItems.map((item, index) => {
|
|
137
|
+
if (renderOverflowMenuItem) {
|
|
138
|
+
return renderOverflowMenuItem(item);
|
|
139
|
+
}
|
|
140
|
+
return (React.createElement(MenuItem, { key: `overflow-item-${index}`, onClick: handleMenuClose }, item));
|
|
141
|
+
}))))));
|
|
142
|
+
};
|
|
143
|
+
export default ResponsiveOverflow;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|