orc-shared 5.10.0-dev.12 → 5.10.0-dev.13

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.
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _react = _interopRequireDefault(require("react"));
6
+ var _styles = require("@material-ui/core/styles");
7
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
+ (function () {
9
+ var enterModule = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.enterModule : undefined;
10
+ enterModule && enterModule(module);
11
+ })();
12
+ (function () {
13
+ var enterModule = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.enterModule : undefined;
14
+ enterModule && enterModule(module);
15
+ })();
16
+ var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) {
17
+ return a;
18
+ };
19
+ var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) {
20
+ return a;
21
+ };
22
+ var useStyles = (0, _styles.makeStyles)(function (theme) {
23
+ return {
24
+ container: {
25
+ display: "flex",
26
+ padding: "".concat(theme.spacing(1), " ").concat(theme.spacing(2)),
27
+ minHeight: theme.spacing(4),
28
+ alignItems: "center",
29
+ borderBottom: "".concat(theme.spacing(0.1), " solid ").concat(theme.palette.grey.borders)
30
+ },
31
+ rightContent: {
32
+ justifyContent: "flex-end",
33
+ alignItems: "center",
34
+ display: "flex",
35
+ flex: 1
36
+ }
37
+ };
38
+ });
39
+ var SectionToolbar = function SectionToolbar(_ref) {
40
+ var children = _ref.children,
41
+ rightContent = _ref.rightContent;
42
+ var classes = useStyles();
43
+ var container = /*#__PURE__*/_react.default.createElement("div", {
44
+ className: classes.container
45
+ }, children, [rightContent && /*#__PURE__*/_react.default.createElement("div", {
46
+ key: "rightContent",
47
+ className: classes.rightContent
48
+ }, rightContent)]);
49
+ return container;
50
+ };
51
+ __signature__(SectionToolbar, "useStyles{classes}", function () {
52
+ return [useStyles];
53
+ });
54
+ __signature__(SectionToolbar, "useStyles{classes}", function () {
55
+ return [useStyles];
56
+ });
57
+ var _default = SectionToolbar;
58
+ var _default2 = _default;
59
+ var _default3 = exports.default = _default2;
60
+ ;
61
+ (function () {
62
+ var reactHotLoader = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default : undefined;
63
+ if (!reactHotLoader) {
64
+ return;
65
+ }
66
+ reactHotLoader.register(useStyles, "useStyles", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/PredefinedElements/SectionToolbar.js");
67
+ reactHotLoader.register(SectionToolbar, "SectionToolbar", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/PredefinedElements/SectionToolbar.js");
68
+ reactHotLoader.register(_default, "default", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/PredefinedElements/SectionToolbar.js");
69
+ })();
70
+ ;
71
+ (function () {
72
+ var leaveModule = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.leaveModule : undefined;
73
+ leaveModule && leaveModule(module);
74
+ })();
75
+ ;
76
+ (function () {
77
+ var reactHotLoader = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default : undefined;
78
+ if (!reactHotLoader) {
79
+ return;
80
+ }
81
+ reactHotLoader.register(useStyles, "useStyles", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/PredefinedElements/SectionToolbar.js");
82
+ reactHotLoader.register(SectionToolbar, "SectionToolbar", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/PredefinedElements/SectionToolbar.js");
83
+ reactHotLoader.register(_default2, "default", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/PredefinedElements/SectionToolbar.js");
84
+ })();
85
+ ;
86
+ (function () {
87
+ var leaveModule = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.leaveModule : undefined;
88
+ leaveModule && leaveModule(module);
89
+ })();
@@ -383,10 +383,36 @@ var FullTableWithSavedScrollbar = _react.default.forwardRef(__signature__(__sign
383
383
  };
384
384
  (0, _react.useEffect)(function () {
385
385
  if (scrollbarViewState.scrollBarPosition > 0) {
386
+ // The Table component usually display the provided list ASAP however it can happen that the Table is not fully rendered with its items when we attempt to change the scrollTop value.
387
+ // The timer should ensure that the list is rendered before attempting to change the scrollTop if the first attempt was not able to set the scroll position
388
+ // This edge case was found using these steps:
389
+ // * Create an organization with 50+ customers
390
+ // * Go to the customer section of the organization and scroll down a bit
391
+ // * Wait 1 second for the scroll position to be stored
392
+ // * Go to the organization list (click on the tab)
393
+ // * Go back to the organization (its tab should still be visible)
394
+ // * We expect the customer list to remember its scroll position but it wasn't the case
395
+ //
396
+ // In addition to the timer, the tableName was added as a dependency to force the effect to execute again when it changes.
397
+ // The tableName value in the organization's customers page is composed with 2 parts: "OrganizationCustomers_" + organizationId. This allows 2 organizations to use different scroll position for their respective customers pages.
398
+ // When the page first load, it is possible for organizationId to not yet be defined (strange but it's routing related) and because of that this useEffect was executed but with an unknown scrollBarPosition.
399
+ // The organizationId will be set on the next render and this useEffect needs be executed again to have the list scrolled to the correct position.
400
+
401
+ var previousScrollTop = ref.current.scrollTop;
386
402
  ref.current.scrollTop = scrollbarViewState.scrollBarPosition;
403
+
404
+ // Note AD20250714: I was not able to create a test for the code below because it needs to run in a real browser otherwise the behavior with scrollTop does not match reality
405
+ /* istanbul ignore if */
406
+ if (ref.current.scrollTop === previousScrollTop) {
407
+ setTimeout(function () {
408
+ // using a timer to give a chance to the UI to populate the list
409
+ ref.current.scrollTop = scrollbarViewState.scrollBarPosition;
410
+ }, 250);
411
+ }
387
412
  }
388
413
  // eslint-disable-next-line react-hooks/exhaustive-deps
389
- }, []);
414
+ }, [props.tableName]); // forcing this effect to run again when the tableName changes
415
+
390
416
  return /*#__PURE__*/_react.default.createElement(DefaultFullTable, _extends({}, props, {
391
417
  ref: ref,
392
418
  saveScrollBarPosition: saveScrollBarPosition
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _react = _interopRequireWildcard(require("react"));
6
+ var _TableProps = _interopRequireDefault(require("./TableProps"));
7
+ var _tableHelpers = _interopRequireDefault(require("./tableHelpers"));
8
+ var _useInMemoryPaging2 = _interopRequireDefault(require("../../../hooks/useInMemoryPaging"));
9
+ var _Placeholder = _interopRequireDefault(require("./PredefinedElements/Placeholder"));
10
+ var _comparisonHelper = require("../../../utils/comparisonHelper");
11
+ var _reactIntl = require("react-intl");
12
+ var _sharedMessages = _interopRequireDefault(require("../../../sharedMessages"));
13
+ var _SearchControl = _interopRequireDefault(require("../Inputs/PredefinedElements/SearchControl"));
14
+ var _Table = _interopRequireDefault(require("./Table"));
15
+ var _SectionToolbar = _interopRequireDefault(require("./PredefinedElements/SectionToolbar"));
16
+ var _TableInfoBar = _interopRequireDefault(require("./PredefinedElements/TableInfoBar"));
17
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
19
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
20
+ (function () {
21
+ var enterModule = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.enterModule : undefined;
22
+ enterModule && enterModule(module);
23
+ })();
24
+ (function () {
25
+ var enterModule = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.enterModule : undefined;
26
+ enterModule && enterModule(module);
27
+ })();
28
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
29
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
30
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
31
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
32
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
33
+ var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) {
34
+ return a;
35
+ };
36
+ var __signature__ = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default.signature : function (a) {
37
+ return a;
38
+ };
39
+ var listFilter = function listFilter(list, searchTerm, searchProperties) {
40
+ if (!searchTerm) {
41
+ return list;
42
+ }
43
+ return list.filter(function (item) {
44
+ return (0, _comparisonHelper.doesObjectContainsTextCaseInsensitive)(item, searchTerm, searchProperties);
45
+ });
46
+ };
47
+ function getTableInfo(totalCountLabelId, noRowsTotalCountLabelId, formatMessage, totalCount) {
48
+ if (totalCountLabelId && noRowsTotalCountLabelId) {
49
+ if (totalCount) {
50
+ return /*#__PURE__*/_react.default.createElement(_TableInfoBar.default, {
51
+ tableLabel: formatMessage(totalCountLabelId, {
52
+ quantity: totalCount
53
+ })
54
+ });
55
+ }
56
+ return /*#__PURE__*/_react.default.createElement(_TableInfoBar.default, {
57
+ tableLabel: formatMessage(noRowsTotalCountLabelId)
58
+ });
59
+ }
60
+ return null;
61
+ }
62
+ var TableWithInMemoryPaging = function TableWithInMemoryPaging(_ref) {
63
+ var sortedRows = _ref.sortedRows,
64
+ tableName = _ref.tableName,
65
+ _ref$searchProperties = _ref.searchProperties,
66
+ searchProperties = _ref$searchProperties === void 0 ? [] : _ref$searchProperties,
67
+ columnDefs = _ref.columnDefs,
68
+ toolbarRightContent = _ref.toolbarRightContent,
69
+ _ref$pageSize = _ref.pageSize,
70
+ pageSize = _ref$pageSize === void 0 ? 20 : _ref$pageSize,
71
+ _ref$onItemClick = _ref.onItemClick,
72
+ onItemClick = _ref$onItemClick === void 0 ? null : _ref$onItemClick,
73
+ _ref$isReadMode = _ref.isReadMode,
74
+ isReadMode = _ref$isReadMode === void 0 ? true : _ref$isReadMode,
75
+ _ref$totalCountLabelI = _ref.totalCountLabelId,
76
+ totalCountLabelId = _ref$totalCountLabelI === void 0 ? null : _ref$totalCountLabelI,
77
+ _ref$noRowsTotalCount = _ref.noRowsTotalCountLabelId,
78
+ noRowsTotalCountLabelId = _ref$noRowsTotalCount === void 0 ? null : _ref$noRowsTotalCount,
79
+ _ref$placeholderIcon = _ref.placeholderIcon,
80
+ placeholderIcon = _ref$placeholderIcon === void 0 ? null : _ref$placeholderIcon,
81
+ _ref$placeholderTitle = _ref.placeholderTitle,
82
+ placeholderTitle = _ref$placeholderTitle === void 0 ? null : _ref$placeholderTitle,
83
+ _ref$placeholderSubti = _ref.placeholderSubtitle,
84
+ placeholderSubtitle = _ref$placeholderSubti === void 0 ? null : _ref$placeholderSubti;
85
+ var _useIntl = (0, _reactIntl.useIntl)(),
86
+ formatMessage = _useIntl.formatMessage;
87
+ var tableRef = (0, _react.useRef)(null);
88
+ var initialSearchProperties = (0, _react.useRef)(searchProperties);
89
+ var searchPlaceholder = formatMessage(_sharedMessages.default.search);
90
+ var sortAndFilter = (0, _react.useCallback)(function (_ref2) {
91
+ var list = _ref2.list,
92
+ filters = _ref2.filters;
93
+ var searchTerm = filters.searchTerm;
94
+ return listFilter(list, searchTerm, initialSearchProperties.current);
95
+ }, [initialSearchProperties]);
96
+ var _useInMemoryPaging = (0, _useInMemoryPaging2.default)({
97
+ viewStateName: tableName,
98
+ tableRef: tableRef,
99
+ records: sortedRows,
100
+ pageSize: pageSize,
101
+ sortAndFilterFn: sortAndFilter
102
+ }),
103
+ rowsSlice = _useInMemoryPaging.rows,
104
+ scrollLoader = _useInMemoryPaging.scrollLoader,
105
+ currentPage = _useInMemoryPaging.currentPage,
106
+ filters = _useInMemoryPaging.filters,
107
+ setFilter = _useInMemoryPaging.setFilter,
108
+ totalCount = _useInMemoryPaging.totalCount;
109
+ var _buildHeaderAndRowFro = (0, _tableHelpers.default)(columnDefs, rowsSlice, isReadMode),
110
+ headers = _buildHeaderAndRowFro.headers,
111
+ rows = _buildHeaderAndRowFro.rows;
112
+ var placeholder = /*#__PURE__*/_react.default.createElement(_Placeholder.default, {
113
+ icon: placeholderIcon,
114
+ title: placeholderTitle,
115
+ subtitle: placeholderSubtitle,
116
+ cellList: columnDefs.map(function (col) {
117
+ return col.placeholder;
118
+ })
119
+ });
120
+ var onSearch = function onSearch(searchOption, searchText) {
121
+ setFilter(_objectSpread(_objectSpread({}, filters), {}, {
122
+ searchTerm: searchText
123
+ }));
124
+ };
125
+ var tableProps = new _TableProps.default();
126
+ tableProps.set(_TableProps.default.propNames.stickyHeader, true);
127
+ tableProps.set(_TableProps.default.propNames.withoutTopBorder, true);
128
+ tableProps.set(_TableProps.default.propNames.deepPropsComparation, true);
129
+ tableProps.set(_TableProps.default.propNames.saveScrollbarPosition, true);
130
+ tableProps.set(_TableProps.default.propNames.tableName, tableName);
131
+ if (onItemClick) {
132
+ tableProps.set(_TableProps.default.propNames.onRowClick, onItemClick);
133
+ }
134
+ var hasToolbar = toolbarRightContent || searchProperties.length > 0;
135
+ var toolbar = hasToolbar ? /*#__PURE__*/_react.default.createElement(_SectionToolbar.default, {
136
+ rightContent: toolbarRightContent
137
+ }, searchProperties.length > 0 && /*#__PURE__*/_react.default.createElement(_SearchControl.default, {
138
+ placeholder: searchPlaceholder,
139
+ defaultValue: filters.searchTerm,
140
+ onSearch: onSearch,
141
+ focusAndSelectSearchFieldOnLoad: false
142
+ })) : null;
143
+ var tableInfo = getTableInfo(totalCountLabelId, noRowsTotalCountLabelId, formatMessage, totalCount);
144
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, toolbar, /*#__PURE__*/_react.default.createElement(_Table.default, {
145
+ ref: tableRef,
146
+ headers: headers,
147
+ tableInfo: tableInfo,
148
+ rows: rows,
149
+ tableProps: tableProps,
150
+ placeholder: placeholder,
151
+ scrollLoader: scrollLoader,
152
+ latestPage: currentPage,
153
+ pageLength: pageSize
154
+ }));
155
+ };
156
+ __signature__(TableWithInMemoryPaging, "useIntl{{ formatMessage }}\nuseRef{tableRef}\nuseRef{initialSearchProperties}\nuseCallback{sortAndFilter}\nuseInMemoryPaging{{\n\t\trows: rowsSlice,\n\t\tscrollLoader,\n\t\tcurrentPage,\n\t\tfilters,\n\t\tsetFilter,\n\t\ttotalCount,\n\t}}", function () {
157
+ return [_reactIntl.useIntl, _useInMemoryPaging2.default];
158
+ });
159
+ __signature__(TableWithInMemoryPaging, "useIntl{{ formatMessage }}\nuseRef{tableRef}\nuseRef{initialSearchProperties}\nuseCallback{sortAndFilter}\nuseInMemoryPaging{{\n\t\trows: rowsSlice,\n\t\tscrollLoader,\n\t\tcurrentPage,\n\t\tfilters,\n\t\tsetFilter,\n\t\ttotalCount,\n\t}}", function () {
160
+ return [_reactIntl.useIntl, _useInMemoryPaging2.default];
161
+ });
162
+ var _default = TableWithInMemoryPaging;
163
+ var _default2 = _default;
164
+ var _default3 = exports.default = _default2;
165
+ ;
166
+ (function () {
167
+ var reactHotLoader = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default : undefined;
168
+ if (!reactHotLoader) {
169
+ return;
170
+ }
171
+ reactHotLoader.register(listFilter, "listFilter", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js");
172
+ reactHotLoader.register(getTableInfo, "getTableInfo", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js");
173
+ reactHotLoader.register(TableWithInMemoryPaging, "TableWithInMemoryPaging", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js");
174
+ reactHotLoader.register(_default, "default", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js");
175
+ })();
176
+ ;
177
+ (function () {
178
+ var leaveModule = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.leaveModule : undefined;
179
+ leaveModule && leaveModule(module);
180
+ })();
181
+ ;
182
+ (function () {
183
+ var reactHotLoader = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.default : undefined;
184
+ if (!reactHotLoader) {
185
+ return;
186
+ }
187
+ reactHotLoader.register(listFilter, "listFilter", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js");
188
+ reactHotLoader.register(getTableInfo, "getTableInfo", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js");
189
+ reactHotLoader.register(TableWithInMemoryPaging, "TableWithInMemoryPaging", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js");
190
+ reactHotLoader.register(_default2, "default", "/home/vsts/work/1/s/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js");
191
+ })();
192
+ ;
193
+ (function () {
194
+ var leaveModule = typeof reactHotLoaderGlobal !== 'undefined' ? reactHotLoaderGlobal.leaveModule : undefined;
195
+ leaveModule && leaveModule(module);
196
+ })();
@@ -274,6 +274,7 @@ var SearchControl = function SearchControl(_ref) {
274
274
  })));
275
275
  var searchSection = /*#__PURE__*/_react.default.createElement(_IconButton.default, {
276
276
  "data-qa": "searchButton",
277
+ "data-testid": "searchButton",
277
278
  variant: "contained",
278
279
  disabled: disabled,
279
280
  classes: {
@@ -48,16 +48,12 @@ var useInMemoryPaging = function useInMemoryPaging(_ref) {
48
48
  _useViewState2 = _slicedToArray(_useViewState, 2),
49
49
  viewState = _useViewState2[0],
50
50
  updateViewState = _useViewState2[1];
51
- var internalSortAndFilterFn = (0, _react.useRef)(sortAndFilterFn);
52
51
  var internalInitialSort = (0, _react.useRef)(initialSort);
53
52
  var internalInitialFilters = (0, _react.useRef)(initialFilters);
54
53
  var filters = (_viewState$filters = viewState.filters) != null ? _viewState$filters : internalInitialFilters.current;
55
54
  var sorting = (_viewState$sorting = viewState.sorting) != null ? _viewState$sorting : internalInitialSort.current;
56
55
  var currentPage = (_viewState$currentPag = viewState.currentPage) != null ? _viewState$currentPag : 1;
57
56
  var nextPageToLoad = (_viewState$nextPageTo = viewState.nextPageToLoad) != null ? _viewState$nextPageTo : 1;
58
- if (internalSortAndFilterFn.current !== sortAndFilterFn) {
59
- console.warn("[useInMemoryPaging]: a different value for sortAndFilterFn was detected between renders, ensure that it never changes (define it outside of your component).");
60
- }
61
57
  var setFilter = function setFilter(filters) {
62
58
  scrollTableRef(tableRef);
63
59
  updateViewState("filters", filters);
@@ -77,13 +73,13 @@ var useInMemoryPaging = function useInMemoryPaging(_ref) {
77
73
  }
78
74
  }, [nextPageToLoad, updateViewState]);
79
75
  var _useMemo = (0, _react.useMemo)(function () {
80
- var list = internalSortAndFilterFn.current({
76
+ var list = sortAndFilterFn({
81
77
  list: (0, _utils.unwrapImmutable)(records),
82
78
  filters: filters,
83
79
  sorting: sorting
84
80
  });
85
81
  return [(0, _utils.unwrapImmutable)(list.slice(0, currentPage * pageSize)), list.length];
86
- }, [currentPage, pageSize, records, filters, sorting, internalSortAndFilterFn]),
82
+ }, [currentPage, pageSize, records, filters, sorting, sortAndFilterFn]),
87
83
  _useMemo2 = _slicedToArray(_useMemo, 2),
88
84
  rows = _useMemo2[0],
89
85
  totalCount = _useMemo2[1];
@@ -98,10 +94,10 @@ var useInMemoryPaging = function useInMemoryPaging(_ref) {
98
94
  totalCount: totalCount
99
95
  };
100
96
  };
101
- __signature__(useInMemoryPaging, "useViewState{[viewState, updateViewState]}\nuseRef{internalSortAndFilterFn}\nuseRef{internalInitialSort}\nuseRef{internalInitialFilters}\nuseCallback{scrollLoader}\nuseMemo{[rows, totalCount]}", function () {
97
+ __signature__(useInMemoryPaging, "useViewState{[viewState, updateViewState]}\nuseRef{internalInitialSort}\nuseRef{internalInitialFilters}\nuseCallback{scrollLoader}\nuseMemo{[rows, totalCount]}", function () {
102
98
  return [_useViewState3.default];
103
99
  });
104
- __signature__(useInMemoryPaging, "useViewState{[viewState, updateViewState]}\nuseRef{internalSortAndFilterFn}\nuseRef{internalInitialSort}\nuseRef{internalInitialFilters}\nuseCallback{scrollLoader}\nuseMemo{[rows, totalCount]}", function () {
100
+ __signature__(useInMemoryPaging, "useViewState{[viewState, updateViewState]}\nuseRef{internalInitialSort}\nuseRef{internalInitialFilters}\nuseCallback{scrollLoader}\nuseMemo{[rows, totalCount]}", function () {
105
101
  return [_useViewState3.default];
106
102
  });
107
103
  var _default = useInMemoryPaging;
@@ -313,6 +313,10 @@ var sharedMessages = (0, _reactIntl.defineMessages)({
313
313
  lastModifiedBy: {
314
314
  id: "orc-shared.lastModifiedBy",
315
315
  defaultMessage: "Last Modified By"
316
+ },
317
+ search: {
318
+ id: "orc-shared.search",
319
+ defaultMessage: "Search"
316
320
  }
317
321
  });
318
322
  var _default = sharedMessages;
@@ -128,7 +128,16 @@ var doesObjectContainsTextCaseInsensitive = exports.doesObjectContainsTextCaseIn
128
128
  var loweredSearchTerm = searchTerm.toLowerCase();
129
129
  var propertiesToSearch = properties.length > 0 ? properties : Object.keys(obj);
130
130
  var result = propertiesToSearch.find(function (key) {
131
- return typeof obj[key] === "string" && caseInsensitiveIncludes(obj[key], loweredSearchTerm);
131
+ if (typeof obj[key] === "string") {
132
+ return caseInsensitiveIncludes(obj[key], loweredSearchTerm);
133
+ }
134
+ if (typeof obj[key] === "object" && obj[key]) {
135
+ var objectValues = Object.values(obj[key]);
136
+ return objectValues.find(function (ovKey) {
137
+ return typeof ovKey === "string" && caseInsensitiveIncludes(ovKey, loweredSearchTerm);
138
+ });
139
+ }
140
+ return false;
132
141
  }) !== undefined;
133
142
  return result;
134
143
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orc-shared",
3
- "version": "5.10.0-dev.12",
3
+ "version": "5.10.0-dev.13",
4
4
  "description": "Shared code for Orckestra applications",
5
5
  "main": "./src/index.js",
6
6
  "exports": {
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import { makeStyles } from "@material-ui/core/styles";
3
+
4
+ const useStyles = makeStyles(theme => ({
5
+ container: {
6
+ display: "flex",
7
+ padding: `${theme.spacing(1)} ${theme.spacing(2)}`,
8
+ minHeight: theme.spacing(4),
9
+ alignItems: "center",
10
+ borderBottom: `${theme.spacing(0.1)} solid ${theme.palette.grey.borders}`,
11
+ },
12
+ rightContent: {
13
+ justifyContent: "flex-end",
14
+ alignItems: "center",
15
+ display: "flex",
16
+ flex: 1,
17
+ },
18
+ }));
19
+
20
+ const SectionToolbar = ({ children, rightContent }) => {
21
+ const classes = useStyles();
22
+
23
+ const container = (
24
+ <div className={classes.container}>
25
+ {children}
26
+ {[
27
+ rightContent && (
28
+ <div key={"rightContent"} className={classes.rightContent}>
29
+ {rightContent}
30
+ </div>
31
+ ),
32
+ ]}
33
+ </div>
34
+ );
35
+
36
+ return container;
37
+ };
38
+
39
+ export default SectionToolbar;
@@ -384,10 +384,35 @@ const FullTableWithSavedScrollbar = React.forwardRef((props, ref) => {
384
384
 
385
385
  useEffect(() => {
386
386
  if (scrollbarViewState.scrollBarPosition > 0) {
387
+ // The Table component usually display the provided list ASAP however it can happen that the Table is not fully rendered with its items when we attempt to change the scrollTop value.
388
+ // The timer should ensure that the list is rendered before attempting to change the scrollTop if the first attempt was not able to set the scroll position
389
+ // This edge case was found using these steps:
390
+ // * Create an organization with 50+ customers
391
+ // * Go to the customer section of the organization and scroll down a bit
392
+ // * Wait 1 second for the scroll position to be stored
393
+ // * Go to the organization list (click on the tab)
394
+ // * Go back to the organization (its tab should still be visible)
395
+ // * We expect the customer list to remember its scroll position but it wasn't the case
396
+ //
397
+ // In addition to the timer, the tableName was added as a dependency to force the effect to execute again when it changes.
398
+ // The tableName value in the organization's customers page is composed with 2 parts: "OrganizationCustomers_" + organizationId. This allows 2 organizations to use different scroll position for their respective customers pages.
399
+ // When the page first load, it is possible for organizationId to not yet be defined (strange but it's routing related) and because of that this useEffect was executed but with an unknown scrollBarPosition.
400
+ // The organizationId will be set on the next render and this useEffect needs be executed again to have the list scrolled to the correct position.
401
+
402
+ const previousScrollTop = ref.current.scrollTop;
387
403
  ref.current.scrollTop = scrollbarViewState.scrollBarPosition;
404
+
405
+ // Note AD20250714: I was not able to create a test for the code below because it needs to run in a real browser otherwise the behavior with scrollTop does not match reality
406
+ /* istanbul ignore if */
407
+ if (ref.current.scrollTop === previousScrollTop) {
408
+ setTimeout(() => {
409
+ // using a timer to give a chance to the UI to populate the list
410
+ ref.current.scrollTop = scrollbarViewState.scrollBarPosition;
411
+ }, 250);
412
+ }
388
413
  }
389
414
  // eslint-disable-next-line react-hooks/exhaustive-deps
390
- }, []);
415
+ }, [props.tableName]); // forcing this effect to run again when the tableName changes
391
416
 
392
417
  return <DefaultFullTable {...props} ref={ref} saveScrollBarPosition={saveScrollBarPosition} />;
393
418
  });
@@ -0,0 +1,144 @@
1
+ import React, { useCallback, useRef } from "react";
2
+ import TableProps from "./TableProps";
3
+ import buildHeaderAndRowFromConfig from "./tableHelpers";
4
+ import useInMemoryPaging from "../../../hooks/useInMemoryPaging";
5
+ import Placeholder from "./PredefinedElements/Placeholder";
6
+ import { doesObjectContainsTextCaseInsensitive } from "../../../utils/comparisonHelper";
7
+ import { useIntl } from "react-intl";
8
+ import sharedMessages from "../../../sharedMessages";
9
+ import SearchControl from "../Inputs/PredefinedElements/SearchControl";
10
+ import Table from "./Table";
11
+ import SectionToolbar from "./PredefinedElements/SectionToolbar";
12
+ import TableInfoBar from "./PredefinedElements/TableInfoBar";
13
+
14
+ const listFilter = (list, searchTerm, searchProperties) => {
15
+ if (!searchTerm) {
16
+ return list;
17
+ }
18
+
19
+ return list.filter(item => doesObjectContainsTextCaseInsensitive(item, searchTerm, searchProperties));
20
+ };
21
+
22
+ function getTableInfo(totalCountLabelId, noRowsTotalCountLabelId, formatMessage, totalCount) {
23
+ if (totalCountLabelId && noRowsTotalCountLabelId) {
24
+ if (totalCount) {
25
+ return (
26
+ <TableInfoBar
27
+ tableLabel={formatMessage(totalCountLabelId, {
28
+ quantity: totalCount,
29
+ })}
30
+ />
31
+ );
32
+ }
33
+ return <TableInfoBar tableLabel={formatMessage(noRowsTotalCountLabelId)} />;
34
+ }
35
+
36
+ return null;
37
+ }
38
+
39
+ const TableWithInMemoryPaging = ({
40
+ sortedRows,
41
+ tableName,
42
+ searchProperties = [],
43
+ columnDefs,
44
+ toolbarRightContent,
45
+ pageSize = 20,
46
+ onItemClick = null,
47
+ isReadMode = true,
48
+ totalCountLabelId = null,
49
+ noRowsTotalCountLabelId = null,
50
+ placeholderIcon = null,
51
+ placeholderTitle = null,
52
+ placeholderSubtitle = null,
53
+ }) => {
54
+ const { formatMessage } = useIntl();
55
+ const tableRef = useRef(null);
56
+ const initialSearchProperties = useRef(searchProperties);
57
+ const searchPlaceholder = formatMessage(sharedMessages.search);
58
+ const sortAndFilter = useCallback(
59
+ ({ list, filters }) => {
60
+ const { searchTerm } = filters;
61
+
62
+ return listFilter(list, searchTerm, initialSearchProperties.current);
63
+ },
64
+ [initialSearchProperties],
65
+ );
66
+
67
+ const {
68
+ rows: rowsSlice,
69
+ scrollLoader,
70
+ currentPage,
71
+ filters,
72
+ setFilter,
73
+ totalCount,
74
+ } = useInMemoryPaging({
75
+ viewStateName: tableName,
76
+ tableRef,
77
+ records: sortedRows,
78
+ pageSize: pageSize,
79
+ sortAndFilterFn: sortAndFilter,
80
+ });
81
+
82
+ const { headers, rows } = buildHeaderAndRowFromConfig(columnDefs, rowsSlice, isReadMode);
83
+ const placeholder = (
84
+ <Placeholder
85
+ icon={placeholderIcon}
86
+ title={placeholderTitle}
87
+ subtitle={placeholderSubtitle}
88
+ cellList={columnDefs.map(col => col.placeholder)}
89
+ />
90
+ );
91
+
92
+ const onSearch = (searchOption, searchText) => {
93
+ setFilter({
94
+ ...filters,
95
+ searchTerm: searchText,
96
+ });
97
+ };
98
+
99
+ const tableProps = new TableProps();
100
+ tableProps.set(TableProps.propNames.stickyHeader, true);
101
+ tableProps.set(TableProps.propNames.withoutTopBorder, true);
102
+ tableProps.set(TableProps.propNames.deepPropsComparation, true);
103
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
104
+ tableProps.set(TableProps.propNames.tableName, tableName);
105
+ if (onItemClick) {
106
+ tableProps.set(TableProps.propNames.onRowClick, onItemClick);
107
+ }
108
+
109
+ const hasToolbar = toolbarRightContent || searchProperties.length > 0;
110
+ const toolbar = hasToolbar ? (
111
+ <SectionToolbar rightContent={toolbarRightContent}>
112
+ {searchProperties.length > 0 && (
113
+ <SearchControl
114
+ placeholder={searchPlaceholder}
115
+ defaultValue={filters.searchTerm}
116
+ onSearch={onSearch}
117
+ focusAndSelectSearchFieldOnLoad={false}
118
+ />
119
+ )}
120
+ </SectionToolbar>
121
+ ) : null;
122
+
123
+ const tableInfo = getTableInfo(totalCountLabelId, noRowsTotalCountLabelId, formatMessage, totalCount);
124
+
125
+ return (
126
+ <>
127
+ {toolbar}
128
+
129
+ <Table
130
+ ref={tableRef}
131
+ headers={headers}
132
+ tableInfo={tableInfo}
133
+ rows={rows}
134
+ tableProps={tableProps}
135
+ placeholder={placeholder}
136
+ scrollLoader={scrollLoader}
137
+ latestPage={currentPage}
138
+ pageLength={pageSize}
139
+ />
140
+ </>
141
+ );
142
+ };
143
+
144
+ export default TableWithInMemoryPaging;
@@ -0,0 +1,421 @@
1
+ import React from "react";
2
+ import sinon from "sinon";
3
+ import { mount } from "enzyme";
4
+ import Table from "./Table";
5
+ import { buildHeaderAndRowFromConfig } from "./tableHelpers";
6
+ import TableInfoBar from "./PredefinedElements/TableInfoBar";
7
+ import { extractMessages } from "../../../utils/testUtils";
8
+ import TableProps from "./TableProps";
9
+ import { createMuiTheme } from "./../../../utils/testUtils";
10
+ import { TestWrapper } from "../../../utils/testUtils";
11
+ import TableWithInMemoryPaging from "./TableWithInMemoryPaging";
12
+ import sharedMessages from "../../../sharedMessages";
13
+ import SearchControl from "../Inputs/PredefinedElements/SearchControl";
14
+ import SectionToolbar from "./PredefinedElements/SectionToolbar";
15
+ import buildStore from "../../../buildStore";
16
+ import { fireEvent, render, getByPlaceholderText, getByTestId, queryAllByRole } from "@testing-library/react";
17
+ import Placeholder from "./PredefinedElements/Placeholder";
18
+
19
+ const messages = extractMessages(sharedMessages);
20
+
21
+ const getColumnDefs = () => [
22
+ {
23
+ fieldName: "name",
24
+ label: "name",
25
+ placeholder: "t",
26
+ },
27
+ ];
28
+
29
+ const standardListRecords = Array.from(Array(50).keys()).map(k => ({ name: "n" + k, id: k }));
30
+
31
+ describe("TableWithInMemoryPaging", () => {
32
+ const theme = createMuiTheme();
33
+
34
+ let store;
35
+
36
+ beforeEach(() => {
37
+ global.SUPPORTED_LOCALES = undefined;
38
+ store = buildStore([]); // use the real reducers
39
+ });
40
+
41
+ afterEach(() => {
42
+ delete process.env.SUPPORTED_LOCALES;
43
+ });
44
+
45
+ it("Renders TableWithInMemoryPaging", () => {
46
+ const configDefs = getColumnDefs();
47
+ const { headers, rows } = buildHeaderAndRowFromConfig(configDefs, standardListRecords.slice(0, 20));
48
+
49
+ const component = (
50
+ <TestWrapper
51
+ provider={{ store }}
52
+ memoryRouter
53
+ intlProvider={{ messages }}
54
+ stylesProvider
55
+ muiThemeProvider={{ theme }}
56
+ >
57
+ <TableWithInMemoryPaging
58
+ sortedRows={standardListRecords}
59
+ tableName={"StateName"}
60
+ columnDefs={configDefs}
61
+ pageSize={20}
62
+ />
63
+ </TestWrapper>
64
+ );
65
+
66
+ const tableProps = new TableProps();
67
+
68
+ tableProps.set(TableProps.propNames.stickyHeader, true);
69
+ tableProps.set(TableProps.propNames.withoutTopBorder, true);
70
+ tableProps.set(TableProps.propNames.deepPropsComparation, true);
71
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
72
+ tableProps.set(TableProps.propNames.tableName, "StateName");
73
+
74
+ const expected = (
75
+ <TestWrapper provider={{ store }} memoryRouter intlProvider stylesProvider>
76
+ <Table rows={rows} headers={headers} tableProps={tableProps} />
77
+ </TestWrapper>
78
+ );
79
+
80
+ expect(component, "when mounted", "to satisfy", expected);
81
+ });
82
+
83
+ it("Renders TableWithInMemoryPaging with onItemClick", () => {
84
+ const configDefs = getColumnDefs();
85
+ const onItemClick = sinon.spy().named("onItemClick");
86
+
87
+ const component = (
88
+ <TestWrapper
89
+ provider={{ store }}
90
+ memoryRouter
91
+ intlProvider={{ messages }}
92
+ stylesProvider
93
+ muiThemeProvider={{ theme }}
94
+ >
95
+ <TableWithInMemoryPaging
96
+ sortedRows={standardListRecords}
97
+ tableName={"StateName"}
98
+ columnDefs={configDefs}
99
+ pageSize={20}
100
+ onItemClick={onItemClick}
101
+ />
102
+ </TestWrapper>
103
+ );
104
+
105
+ const mountedComponent = mount(component);
106
+ const row = mountedComponent.find("td").first();
107
+
108
+ row.simulate("click");
109
+ expect(onItemClick, "was called once");
110
+ });
111
+
112
+ it("Renders TableWithInMemoryPaging with right toolbar", () => {
113
+ const configDefs = getColumnDefs();
114
+ const { headers, rows } = buildHeaderAndRowFromConfig(configDefs, standardListRecords.slice(0, 20));
115
+
116
+ const toolbarRightContent = <div>hello</div>;
117
+
118
+ const component = (
119
+ <TestWrapper
120
+ provider={{ store }}
121
+ memoryRouter
122
+ intlProvider={{ messages }}
123
+ stylesProvider
124
+ muiThemeProvider={{ theme }}
125
+ >
126
+ <div>
127
+ <TableWithInMemoryPaging
128
+ sortedRows={standardListRecords}
129
+ tableName={"StateName"}
130
+ columnDefs={configDefs}
131
+ toolbarRightContent={toolbarRightContent}
132
+ pageSize={20}
133
+ />
134
+ </div>
135
+ </TestWrapper>
136
+ );
137
+
138
+ const tableProps = new TableProps();
139
+
140
+ tableProps.set(TableProps.propNames.stickyHeader, true);
141
+ tableProps.set(TableProps.propNames.withoutTopBorder, true);
142
+ tableProps.set(TableProps.propNames.deepPropsComparation, true);
143
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
144
+ tableProps.set(TableProps.propNames.tableName, "StateName");
145
+
146
+ const expected = (
147
+ <TestWrapper
148
+ provider={{ store }}
149
+ memoryRouter
150
+ intlProvider={{ messages }}
151
+ stylesProvider
152
+ muiThemeProvider={{ theme }}
153
+ >
154
+ <div>
155
+ <SectionToolbar rightContent={toolbarRightContent}></SectionToolbar>
156
+ <Table rows={rows} headers={headers} tableProps={tableProps} />
157
+ </div>
158
+ </TestWrapper>
159
+ );
160
+
161
+ expect(component, "when mounted", "to satisfy", expected);
162
+ });
163
+
164
+ it("Renders TableWithInMemoryPaging with search", () => {
165
+ const configDefs = getColumnDefs();
166
+ const { headers, rows } = buildHeaderAndRowFromConfig(configDefs, standardListRecords.slice(0, 20));
167
+
168
+ const component = (
169
+ <TestWrapper
170
+ provider={{ store }}
171
+ memoryRouter
172
+ intlProvider={{ messages }}
173
+ stylesProvider
174
+ muiThemeProvider={{ theme }}
175
+ >
176
+ <div>
177
+ <TableWithInMemoryPaging
178
+ sortedRows={standardListRecords}
179
+ tableName={"StateName"}
180
+ columnDefs={configDefs}
181
+ pageSize={20}
182
+ searchProperties={["name"]}
183
+ />
184
+ </div>
185
+ </TestWrapper>
186
+ );
187
+
188
+ const tableProps = new TableProps();
189
+
190
+ tableProps.set(TableProps.propNames.stickyHeader, true);
191
+ tableProps.set(TableProps.propNames.withoutTopBorder, true);
192
+ tableProps.set(TableProps.propNames.deepPropsComparation, true);
193
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
194
+ tableProps.set(TableProps.propNames.tableName, "StateName");
195
+
196
+ const expected = (
197
+ <TestWrapper
198
+ provider={{ store }}
199
+ memoryRouter
200
+ intlProvider={{ messages }}
201
+ stylesProvider
202
+ muiThemeProvider={{ theme }}
203
+ >
204
+ <div>
205
+ <SectionToolbar>
206
+ <SearchControl placeholder="Search" />
207
+ </SectionToolbar>
208
+ <Table rows={rows} headers={headers} tableProps={tableProps} />
209
+ </div>
210
+ </TestWrapper>
211
+ );
212
+
213
+ expect(component, "when mounted", "to satisfy", expected);
214
+ });
215
+
216
+ it("Renders TableWithInMemoryPaging with search and right toolbar", () => {
217
+ const configDefs = getColumnDefs();
218
+ const { headers, rows } = buildHeaderAndRowFromConfig(configDefs, standardListRecords.slice(0, 20));
219
+ const toolbarRightContent = <div>hello</div>;
220
+
221
+ const component = (
222
+ <TestWrapper
223
+ provider={{ store }}
224
+ memoryRouter
225
+ intlProvider={{ messages }}
226
+ stylesProvider
227
+ muiThemeProvider={{ theme }}
228
+ >
229
+ <div>
230
+ <TableWithInMemoryPaging
231
+ sortedRows={standardListRecords}
232
+ tableName={"StateName"}
233
+ columnDefs={configDefs}
234
+ searchProperties={["name"]}
235
+ toolbarRightContent={toolbarRightContent}
236
+ />
237
+ </div>
238
+ </TestWrapper>
239
+ );
240
+
241
+ const tableProps = new TableProps();
242
+
243
+ tableProps.set(TableProps.propNames.stickyHeader, true);
244
+ tableProps.set(TableProps.propNames.withoutTopBorder, true);
245
+ tableProps.set(TableProps.propNames.deepPropsComparation, true);
246
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
247
+ tableProps.set(TableProps.propNames.tableName, "StateName");
248
+
249
+ const expected = (
250
+ <TestWrapper
251
+ provider={{ store }}
252
+ memoryRouter
253
+ intlProvider={{ messages }}
254
+ stylesProvider
255
+ muiThemeProvider={{ theme }}
256
+ >
257
+ <div>
258
+ <SectionToolbar rightContent={toolbarRightContent}>
259
+ <SearchControl placeholder="Search" />
260
+ </SectionToolbar>
261
+ <Table rows={rows} headers={headers} tableProps={tableProps} />
262
+ </div>
263
+ </TestWrapper>
264
+ );
265
+
266
+ expect(component, "when mounted", "to satisfy", expected);
267
+ });
268
+
269
+ it("Renders TableWithInMemoryPaging search for something", () => {
270
+ const configDefs = getColumnDefs();
271
+
272
+ const component = (
273
+ <TestWrapper
274
+ provider={{ store }}
275
+ memoryRouter
276
+ intlProvider={{ messages }}
277
+ stylesProvider
278
+ muiThemeProvider={{ theme }}
279
+ >
280
+ <div>
281
+ <TableWithInMemoryPaging
282
+ sortedRows={standardListRecords}
283
+ tableName={"StateName"}
284
+ columnDefs={configDefs}
285
+ searchProperties={["name"]}
286
+ />
287
+ </div>
288
+ </TestWrapper>
289
+ );
290
+
291
+ const { container } = render(component);
292
+
293
+ const searchInput = getByPlaceholderText(container, "Search");
294
+ const searchButton = getByTestId(container, "searchButton");
295
+
296
+ const beforeTableRowCount = queryAllByRole(container, "cell").length;
297
+ expect(beforeTableRowCount, "not to be", 0);
298
+
299
+ fireEvent.change(searchInput, {
300
+ target: {
301
+ value: "invalid text",
302
+ },
303
+ });
304
+ fireEvent.click(searchButton);
305
+
306
+ const afterTableRowCount = queryAllByRole(container, "getAllByRole").length;
307
+
308
+ expect(beforeTableRowCount, "not to be", afterTableRowCount);
309
+ expect(afterTableRowCount, "to be", 0);
310
+ });
311
+
312
+ it("Renders TableWithInMemoryPaging with filled count bar", () => {
313
+ const configDefs = getColumnDefs();
314
+ const { headers, rows } = buildHeaderAndRowFromConfig(configDefs, standardListRecords.slice(0, 20));
315
+
316
+ const component = (
317
+ <TestWrapper
318
+ provider={{ store }}
319
+ memoryRouter
320
+ intlProvider={{ messages }}
321
+ stylesProvider
322
+ muiThemeProvider={{ theme }}
323
+ >
324
+ <div>
325
+ <TableWithInMemoryPaging
326
+ sortedRows={standardListRecords}
327
+ tableName={"StateName"}
328
+ columnDefs={configDefs}
329
+ totalCountLabelId={sharedMessages.about}
330
+ noRowsTotalCountLabelId={sharedMessages.help}
331
+ />
332
+ </div>
333
+ </TestWrapper>
334
+ );
335
+
336
+ const tableProps = new TableProps();
337
+
338
+ tableProps.set(TableProps.propNames.stickyHeader, true);
339
+ tableProps.set(TableProps.propNames.withoutTopBorder, true);
340
+ tableProps.set(TableProps.propNames.deepPropsComparation, true);
341
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
342
+ tableProps.set(TableProps.propNames.tableName, "StateName");
343
+
344
+ const tableInfo = <TableInfoBar tableLabel={"About"} />;
345
+
346
+ const expected = (
347
+ <TestWrapper
348
+ provider={{ store }}
349
+ memoryRouter
350
+ intlProvider={{ messages }}
351
+ stylesProvider
352
+ muiThemeProvider={{ theme }}
353
+ >
354
+ <div>
355
+ <Table rows={rows} headers={headers} tableProps={tableProps} tableInfo={tableInfo} />
356
+ </div>
357
+ </TestWrapper>
358
+ );
359
+
360
+ expect(component, "when mounted", "to satisfy", expected);
361
+ });
362
+
363
+ it("Renders TableWithInMemoryPaging with empty count bar and with placeholder", () => {
364
+ const configDefs = getColumnDefs();
365
+ const { headers, rows } = buildHeaderAndRowFromConfig(configDefs, []);
366
+
367
+ const component = (
368
+ <TestWrapper
369
+ provider={{ store }}
370
+ memoryRouter
371
+ intlProvider={{ messages }}
372
+ stylesProvider
373
+ muiThemeProvider={{ theme }}
374
+ >
375
+ <div>
376
+ <TableWithInMemoryPaging
377
+ sortedRows={[]}
378
+ tableName={"StateName"}
379
+ columnDefs={configDefs}
380
+ totalCountLabelId={sharedMessages.about}
381
+ noRowsTotalCountLabelId={sharedMessages.help}
382
+ />
383
+ </div>
384
+ </TestWrapper>
385
+ );
386
+
387
+ const tableProps = new TableProps();
388
+
389
+ tableProps.set(TableProps.propNames.stickyHeader, true);
390
+ tableProps.set(TableProps.propNames.withoutTopBorder, true);
391
+ tableProps.set(TableProps.propNames.deepPropsComparation, true);
392
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
393
+ tableProps.set(TableProps.propNames.tableName, "StateName");
394
+
395
+ const tableInfo = <TableInfoBar tableLabel={"Help"} />;
396
+
397
+ const placeholder = <Placeholder cellList={configDefs.map(col => col.placeholder)} />;
398
+
399
+ const expected = (
400
+ <TestWrapper
401
+ provider={{ store }}
402
+ memoryRouter
403
+ intlProvider={{ messages }}
404
+ stylesProvider
405
+ muiThemeProvider={{ theme }}
406
+ >
407
+ <div>
408
+ <Table
409
+ rows={rows}
410
+ headers={headers}
411
+ tableProps={tableProps}
412
+ tableInfo={tableInfo}
413
+ placeholder={placeholder}
414
+ />
415
+ </div>
416
+ </TestWrapper>
417
+ );
418
+
419
+ expect(component, "when mounted", "to satisfy", expected);
420
+ });
421
+ });
@@ -255,6 +255,7 @@ const SearchControl = ({
255
255
  const searchSection = (
256
256
  <IconButton
257
257
  data-qa="searchButton"
258
+ data-testid="searchButton"
258
259
  variant="contained"
259
260
  disabled={disabled}
260
261
  classes={{ root: classes.searchButton }}
@@ -20,7 +20,6 @@ const useInMemoryPaging = ({
20
20
  sortAndFilterFn,
21
21
  }) => {
22
22
  const [viewState, updateViewState] = useViewState(viewStateName);
23
- const internalSortAndFilterFn = useRef(sortAndFilterFn);
24
23
  const internalInitialSort = useRef(initialSort);
25
24
  const internalInitialFilters = useRef(initialFilters);
26
25
 
@@ -29,12 +28,6 @@ const useInMemoryPaging = ({
29
28
  const currentPage = viewState.currentPage ?? 1;
30
29
  const nextPageToLoad = viewState.nextPageToLoad ?? 1;
31
30
 
32
- if (internalSortAndFilterFn.current !== sortAndFilterFn) {
33
- console.warn(
34
- "[useInMemoryPaging]: a different value for sortAndFilterFn was detected between renders, ensure that it never changes (define it outside of your component).",
35
- );
36
- }
37
-
38
31
  const setFilter = filters => {
39
32
  scrollTableRef(tableRef);
40
33
  updateViewState("filters", filters);
@@ -60,13 +53,13 @@ const useInMemoryPaging = ({
60
53
  );
61
54
 
62
55
  const [rows, totalCount] = useMemo(() => {
63
- let list = internalSortAndFilterFn.current({
56
+ const list = sortAndFilterFn({
64
57
  list: unwrapImmutable(records),
65
58
  filters: filters,
66
59
  sorting: sorting,
67
60
  });
68
61
  return [unwrapImmutable(list.slice(0, currentPage * pageSize)), list.length];
69
- }, [currentPage, pageSize, records, filters, sorting, internalSortAndFilterFn]);
62
+ }, [currentPage, pageSize, records, filters, sorting, sortAndFilterFn]);
70
63
 
71
64
  return {
72
65
  rows,
@@ -512,40 +512,4 @@ describe.each([
512
512
  ],
513
513
  });
514
514
  });
515
-
516
- it("mutating sortAndFilterFn should display a warning", () => {
517
- state = state.setIn(
518
- ["view", "inMemory"],
519
- Immutable.fromJS({
520
- currentPage: 2,
521
- nextPageToLoad: 2,
522
- }),
523
- );
524
-
525
- const tableRef = {
526
- current: {
527
- scrollToTop: sinon.spy().named("tableRefcurrent"),
528
- },
529
- };
530
-
531
- const component = (
532
- <Provider store={store}>
533
- <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={sortAndFilter} tableRef={tableRef} />
534
- </Provider>
535
- );
536
-
537
- const mountedComponent = mount(component);
538
- mountedComponent.setProps({
539
- children: (
540
- <TestComp allRecords={listValues} stateName={"inMemory"} sortAndFilterFn={list => list} tableRef={tableRef} />
541
- ),
542
- });
543
- mountedComponent.update();
544
-
545
- expect(console.warn, "to have a call satisfying", {
546
- args: [
547
- "[useInMemoryPaging]: a different value for sortAndFilterFn was detected between renders, ensure that it never changes (define it outside of your component).",
548
- ],
549
- });
550
- });
551
515
  });
@@ -299,6 +299,10 @@ const sharedMessages = defineMessages({
299
299
  id: "orc-shared.lastModifiedBy",
300
300
  defaultMessage: "Last Modified By",
301
301
  },
302
+ search: {
303
+ id: "orc-shared.search",
304
+ defaultMessage: "Search",
305
+ },
302
306
  });
303
307
 
304
308
  export default sharedMessages;
@@ -58,6 +58,7 @@
58
58
  "orc-shared.scopeChangeWithUnsavedDataConfirmation": "One or more entities opened will be closed with unsaved data. Would you like to change scope?",
59
59
  "orc-shared.scopeChangeWithUnsavedDataTitle": "Confirm scope change without saving data",
60
60
  "orc-shared.scopeFilterPlaceholder": "Type a scope name",
61
+ "orc-shared.search": "Search",
61
62
  "orc-shared.shared": "Shared",
62
63
  "orc-shared.showFewerLanguages": "Show fewer languages",
63
64
  "orc-shared.showMoreLanguages": "Show more languages",
@@ -58,6 +58,7 @@
58
58
  "orc-shared.scopeChangeWithUnsavedDataConfirmation": "Une ou plusieurs entités ouvertes seront fermées, entraînant la perte de données non-sauvegardées. Voulez-vous vraiment changer de Scope?",
59
59
  "orc-shared.scopeChangeWithUnsavedDataTitle": "Confirmation du changement de Scope sans sauvegarde",
60
60
  "orc-shared.scopeFilterPlaceholder": "Entrez un nom de Scope",
61
+ "orc-shared.search": "Recherche",
61
62
  "orc-shared.shared": "Partagé",
62
63
  "orc-shared.showFewerLanguages": "Afficher moins de langues",
63
64
  "orc-shared.showMoreLanguages": "Afficher plus de langues",
@@ -117,8 +117,19 @@ export const doesObjectContainsTextCaseInsensitive = (obj, searchTerm, propertie
117
117
  const loweredSearchTerm = searchTerm.toLowerCase();
118
118
  const propertiesToSearch = properties.length > 0 ? properties : Object.keys(obj);
119
119
  const result =
120
- propertiesToSearch.find(
121
- key => typeof obj[key] === "string" && caseInsensitiveIncludes(obj[key], loweredSearchTerm),
122
- ) !== undefined;
120
+ propertiesToSearch.find(key => {
121
+ if (typeof obj[key] === "string") {
122
+ return caseInsensitiveIncludes(obj[key], loweredSearchTerm);
123
+ }
124
+
125
+ if (typeof obj[key] === "object" && obj[key]) {
126
+ const objectValues = Object.values(obj[key]);
127
+ return objectValues.find(
128
+ ovKey => typeof ovKey === "string" && caseInsensitiveIncludes(ovKey, loweredSearchTerm),
129
+ );
130
+ }
131
+
132
+ return false;
133
+ }) !== undefined;
123
134
  return result;
124
135
  };
@@ -227,8 +227,18 @@ describe("doesObjectContainsTextCaseInsensitive function", () => {
227
227
  expect(result, "to be", true);
228
228
  });
229
229
 
230
- it("only look at first level properties (not deep)", function () {
230
+ it("does look at second level properties of type string", function () {
231
231
  const result = doesObjectContainsTextCaseInsensitive({ prop: { prop2: "VALUE" } }, "val");
232
+ expect(result, "to be", true);
233
+ });
234
+
235
+ it("does not look at second level properties of type number", function () {
236
+ const result = doesObjectContainsTextCaseInsensitive({ prop: { prop2: 123 } }, "val");
237
+ expect(result, "to be", false);
238
+ });
239
+
240
+ it("does not look at third level properties", function () {
241
+ const result = doesObjectContainsTextCaseInsensitive({ prop: { prop2: { prop3: "VALUE" } } }, "val");
232
242
  expect(result, "to be", false);
233
243
  });
234
244