orc-shared 5.10.0-dev.2 → 5.10.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.
Files changed (186) hide show
  1. package/dist/actions/metadata.js +30 -11
  2. package/dist/actions/requestsApi.js +10 -1
  3. package/dist/components/AppFrame/About.js +136 -100
  4. package/dist/components/AppFrame/Anchor.js +45 -21
  5. package/dist/components/AppFrame/AppFrame.js +53 -31
  6. package/dist/components/AppFrame/Help.js +35 -15
  7. package/dist/components/AppFrame/MenuItem.js +148 -114
  8. package/dist/components/AppFrame/Preferences.js +136 -97
  9. package/dist/components/AppFrame/Sidebar.js +51 -28
  10. package/dist/components/AppFrame/Topbar.js +61 -36
  11. package/dist/components/ColumnWrapper.js +28 -5
  12. package/dist/components/Culture.js +33 -14
  13. package/dist/components/DropMenu/Menu.js +79 -45
  14. package/dist/components/DropMenu/index.js +34 -29
  15. package/dist/components/Form/Combination.js +45 -16
  16. package/dist/components/Form/Field.js +57 -38
  17. package/dist/components/Form/FieldElements.js +0 -11
  18. package/dist/components/Form/Fieldset.js +47 -19
  19. package/dist/components/Form/Form.js +22 -9
  20. package/dist/components/Form/FormElement.js +40 -7
  21. package/dist/components/Form/Inputs/Button.js +63 -18
  22. package/dist/components/Form/Inputs/ReadOnly.js +50 -27
  23. package/dist/components/{AppFrame/ApplicationSelector/Header.js → Form/Inputs/Selector.js} +30 -31
  24. package/dist/components/Form/Inputs/Text.js +20 -37
  25. package/dist/components/Form/Inputs/Toggles.js +39 -40
  26. package/dist/components/Form/Inputs/index.js +2 -13
  27. package/dist/components/MaterialUI/DataDisplay/PredefinedElements/Placeholder.js +31 -11
  28. package/dist/components/MaterialUI/DataDisplay/PredefinedElements/SectionToolbar.js +89 -0
  29. package/dist/components/MaterialUI/DataDisplay/Table.js +109 -18
  30. package/dist/components/MaterialUI/DataDisplay/TableProps.js +5 -1
  31. package/dist/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js +198 -0
  32. package/dist/components/MaterialUI/DataDisplay/TooltippedElements/MultipleLinesText.js +4 -1
  33. package/dist/components/MaterialUI/Inputs/DatePicker.js +14 -14
  34. package/dist/components/MaterialUI/Inputs/PredefinedElements/SearchControl.js +1 -0
  35. package/dist/components/MaterialUI/Inputs/Select.js +2 -0
  36. package/dist/components/MaterialUI/Inputs/SelectProps.js +2 -0
  37. package/dist/components/MaterialUI/Inputs/Switch.js +17 -1
  38. package/dist/components/MaterialUI/Inputs/SwitchProps.js +2 -0
  39. package/dist/components/MaterialUI/Inputs/TimePicker.js +14 -21
  40. package/dist/components/MaterialUI/hocs/withDeferredTooltip.js +3 -1
  41. package/dist/components/MaterialUI/muiThemes.js +2 -1
  42. package/dist/components/Provision.js +1 -1
  43. package/dist/constants.js +2 -1
  44. package/dist/content/iconsSheet.svg +740 -116
  45. package/dist/hocs/withScrollBox.js +27 -12
  46. package/dist/hooks/useDaysAndMonthsLocalization.js +77 -0
  47. package/dist/hooks/useInMemoryPaging.js +135 -0
  48. package/dist/hooks/useMultipleFieldEditState.js +12 -3
  49. package/dist/reducers/metadata.js +6 -0
  50. package/dist/schemas/metadata.js +9 -1
  51. package/dist/selectors/locale.js +1 -0
  52. package/dist/selectors/metadata.js +14 -11
  53. package/dist/sharedMessages.js +184 -0
  54. package/dist/utils/ListHelper.js +271 -0
  55. package/dist/utils/comparisonHelper.js +185 -0
  56. package/dist/utils/propertyBagHelper.js +3 -1
  57. package/dist/utils/timezoneHelper.js +18 -31
  58. package/package.json +4 -3
  59. package/src/actions/metadata.js +11 -0
  60. package/src/actions/metadata.test.js +27 -0
  61. package/src/actions/requestsApi.js +6 -0
  62. package/src/components/AppFrame/About.js +97 -117
  63. package/src/components/AppFrame/About.test.js +128 -90
  64. package/src/components/AppFrame/Anchor.js +34 -36
  65. package/src/components/AppFrame/Anchor.test.js +5 -68
  66. package/src/components/AppFrame/AppFrame.js +31 -40
  67. package/src/components/AppFrame/AppFrame.test.js +424 -445
  68. package/src/components/AppFrame/Help.js +23 -20
  69. package/src/components/AppFrame/Help.test.js +3 -3
  70. package/src/components/AppFrame/MenuItem.js +106 -126
  71. package/src/components/AppFrame/MenuItem.test.js +78 -169
  72. package/src/components/AppFrame/Preferences.js +110 -98
  73. package/src/components/AppFrame/Preferences.test.js +115 -219
  74. package/src/components/AppFrame/Sidebar.js +39 -41
  75. package/src/components/AppFrame/Sidebar.test.js +88 -168
  76. package/src/components/AppFrame/Topbar.js +59 -52
  77. package/src/components/AppFrame/Topbar.test.js +31 -39
  78. package/src/components/ColumnWrapper.js +18 -9
  79. package/src/components/Culture.js +20 -10
  80. package/src/components/Culture.test.js +27 -16
  81. package/src/components/DropMenu/DropMenu.test.js +185 -224
  82. package/src/components/DropMenu/Menu.js +73 -80
  83. package/src/components/DropMenu/Menu.test.js +35 -86
  84. package/src/components/DropMenu/index.js +19 -15
  85. package/src/components/Form/Combination.js +35 -28
  86. package/src/components/Form/Combination.test.js +6 -19
  87. package/src/components/Form/Field.js +53 -66
  88. package/src/components/Form/Field.test.js +29 -51
  89. package/src/components/Form/FieldElements.js +0 -14
  90. package/src/components/Form/FieldElements.test.js +104 -111
  91. package/src/components/Form/Fieldset.js +42 -37
  92. package/src/components/Form/Fieldset.test.js +14 -7
  93. package/src/components/Form/Form.js +11 -7
  94. package/src/components/Form/Form.test.js +75 -56
  95. package/src/components/Form/FormElement.js +24 -16
  96. package/src/components/Form/InputField.test.js +24 -30
  97. package/src/components/Form/Inputs/Button.js +58 -14
  98. package/src/components/Form/Inputs/Button.test.js +32 -7
  99. package/src/components/Form/Inputs/Inputs.test.js +0 -7
  100. package/src/components/Form/Inputs/ReadOnly.js +34 -28
  101. package/src/components/Form/Inputs/ReadOnly.test.js +45 -7
  102. package/src/components/Form/Inputs/Selector.js +22 -0
  103. package/src/components/Form/Inputs/Selector.test.js +105 -0
  104. package/src/components/Form/Inputs/Text.js +15 -44
  105. package/src/components/Form/Inputs/Text.test.js +20 -29
  106. package/src/components/Form/Inputs/Toggles.js +27 -26
  107. package/src/components/Form/Inputs/Toggles.test.js +22 -28
  108. package/src/components/Form/Inputs/index.js +4 -15
  109. package/src/components/MaterialUI/DataDisplay/PredefinedElements/InformationItem.test.js +1 -4
  110. package/src/components/MaterialUI/DataDisplay/PredefinedElements/Placeholder.js +32 -6
  111. package/src/components/MaterialUI/DataDisplay/PredefinedElements/Placeholder.test.js +3 -1
  112. package/src/components/MaterialUI/DataDisplay/PredefinedElements/SectionToolbar.js +39 -0
  113. package/src/components/MaterialUI/DataDisplay/Table.js +190 -114
  114. package/src/components/MaterialUI/DataDisplay/Table.test.js +246 -1
  115. package/src/components/MaterialUI/DataDisplay/TableProps.js +4 -0
  116. package/src/components/MaterialUI/DataDisplay/TableProps.test.js +2 -0
  117. package/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.js +145 -0
  118. package/src/components/MaterialUI/DataDisplay/TableWithInMemoryPaging.test.js +457 -0
  119. package/src/components/MaterialUI/DataDisplay/TooltippedElements/MultipleLinesText.js +5 -1
  120. package/src/components/MaterialUI/DataDisplay/TooltippedElements/MultipleLinesText.test.js +7 -1
  121. package/src/components/MaterialUI/Inputs/DatePicker.js +19 -20
  122. package/src/components/MaterialUI/Inputs/DatePicker.test.js +11 -6
  123. package/src/components/MaterialUI/Inputs/PredefinedElements/SearchControl.js +1 -0
  124. package/src/components/MaterialUI/Inputs/Select.js +2 -0
  125. package/src/components/MaterialUI/Inputs/SelectProps.js +2 -0
  126. package/src/components/MaterialUI/Inputs/SelectProps.test.js +2 -0
  127. package/src/components/MaterialUI/Inputs/Switch.js +22 -1
  128. package/src/components/MaterialUI/Inputs/Switch.test.js +23 -0
  129. package/src/components/MaterialUI/Inputs/SwitchProps.js +2 -0
  130. package/src/components/MaterialUI/Inputs/SwitchProps.test.js +2 -0
  131. package/src/components/MaterialUI/Inputs/TimePicker.js +10 -19
  132. package/src/components/MaterialUI/Inputs/TimePicker.test.js +278 -117
  133. package/src/components/MaterialUI/hocs/withDeferredTooltip.js +4 -1
  134. package/src/components/MaterialUI/hocs/withDeferredTooltip.test.js +27 -0
  135. package/src/components/MaterialUI/muiThemes.js +1 -0
  136. package/src/components/Navigation/Bar.test.js +92 -87
  137. package/src/components/Provision.js +1 -1
  138. package/src/components/TaskDetailsModal.test.js +1 -3
  139. package/src/constants.js +1 -0
  140. package/src/content/iconsSheet.svg +740 -116
  141. package/src/hocs/withScrollBox.js +32 -19
  142. package/src/hocs/withScrollBox.test.js +15 -3
  143. package/src/hooks/useDaysAndMonthsLocalization.js +79 -0
  144. package/src/hooks/useDaysAndMonthsLocalization.test.js +107 -0
  145. package/src/hooks/useInMemoryPaging.js +78 -0
  146. package/src/hooks/useInMemoryPaging.test.js +515 -0
  147. package/src/hooks/useMultipleFieldEditState.js +11 -4
  148. package/src/hooks/useMultipleFieldEditState.test.js +49 -1
  149. package/src/reducers/metadata.js +6 -1
  150. package/src/reducers/metadata.test.js +31 -0
  151. package/src/requests +1 -0
  152. package/src/schemas/metadata.js +3 -0
  153. package/src/selectors/locale.js +1 -1
  154. package/src/selectors/metadata.js +12 -9
  155. package/src/selectors/metadata.test.js +92 -11
  156. package/src/sharedMessages.js +184 -0
  157. package/src/timezones.json +883 -0
  158. package/src/translations/en-US.json +46 -0
  159. package/src/translations/fr-CA.json +46 -0
  160. package/src/utils/ListHelper.js +203 -0
  161. package/src/utils/ListHelper.test.js +710 -0
  162. package/src/utils/comparisonHelper.js +135 -0
  163. package/src/utils/comparisonHelper.test.js +334 -0
  164. package/src/utils/propertyBagHelper.js +2 -0
  165. package/src/utils/propertyBagHelper.test.js +6 -0
  166. package/src/utils/timezoneHelper.js +10 -135
  167. package/src/utils/timezoneHelper.test.js +7 -7
  168. package/dist/components/Form/FieldList.js +0 -270
  169. package/dist/components/Form/Inputs/FieldButtons.js +0 -66
  170. package/dist/components/Form/Inputs/Number.js +0 -117
  171. package/dist/components/Form/Inputs/SmallButton.js +0 -91
  172. package/dist/components/Form/Inputs/Time.js +0 -86
  173. package/dist/components/Form/Inputs/Translation.js +0 -169
  174. package/src/components/AppFrame/ApplicationSelector/Header.js +0 -34
  175. package/src/components/AppFrame/ApplicationSelector/Header.test.js +0 -23
  176. package/src/components/Form/FieldList.js +0 -210
  177. package/src/components/Form/FieldList.test.js +0 -558
  178. package/src/components/Form/Inputs/FieldButtons.js +0 -90
  179. package/src/components/Form/Inputs/Number.js +0 -60
  180. package/src/components/Form/Inputs/Number.test.js +0 -435
  181. package/src/components/Form/Inputs/SmallButton.js +0 -37
  182. package/src/components/Form/Inputs/SmallButton.test.js +0 -65
  183. package/src/components/Form/Inputs/Time.js +0 -32
  184. package/src/components/Form/Inputs/Time.test.js +0 -41
  185. package/src/components/Form/Inputs/Translation.js +0 -93
  186. package/src/components/Form/Inputs/Translation.test.js +0 -204
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
1
+ import React, { useCallback, useEffect, useRef, useState, useImperativeHandle } from "react";
2
2
  import TableMui from "@material-ui/core/Table";
3
3
  import TableContainer from "@material-ui/core/TableContainer";
4
4
  import TableHead from "@material-ui/core/TableHead";
@@ -10,6 +10,7 @@ import TableProps, { isTableProps } from "./TableProps";
10
10
  import classNames from "classnames";
11
11
  import ResizeDetector from "react-resize-detector";
12
12
  import { isEqual } from "lodash";
13
+ import useViewState from "../../../hooks/useViewState";
13
14
 
14
15
  export const useStyles = makeStyles(theme => ({
15
16
  container: {
@@ -303,6 +304,14 @@ const buildTableRows = (
303
304
  };
304
305
 
305
306
  const FullTable = React.forwardRef((props, ref) => {
307
+ return props.saveScrollbarPosition ? (
308
+ <FullTableWithSavedScrollbar {...props} ref={ref} />
309
+ ) : (
310
+ <DefaultFullTable {...props} ref={ref} />
311
+ );
312
+ });
313
+
314
+ const DefaultFullTable = React.forwardRef((props, ref) => {
306
315
  const scrollEvent = evt => {
307
316
  if (
308
317
  evt.target.scrollHeight - (evt.target.scrollTop + evt.target.offsetHeight) < 100 &&
@@ -310,6 +319,8 @@ const FullTable = React.forwardRef((props, ref) => {
310
319
  ) {
311
320
  props.scrollLoader(props.latestPage + 1);
312
321
  }
322
+
323
+ props.saveScrollBarPosition?.(evt);
313
324
  };
314
325
 
315
326
  return (
@@ -318,6 +329,7 @@ const FullTable = React.forwardRef((props, ref) => {
318
329
  className={classNames(props.classes.tableContainer, props.customClasses.tableContainer)}
319
330
  ref={ref}
320
331
  onScroll={scrollEvent}
332
+ data-qa="scrollable-table-div"
321
333
  >
322
334
  <ResizeDetector onResize={props.onResize} />
323
335
  <TableMui
@@ -345,132 +357,196 @@ const FullTable = React.forwardRef((props, ref) => {
345
357
  );
346
358
  });
347
359
 
348
- const Table = ({
349
- tableInfo,
350
- headers,
351
- rows,
352
- scrollLoader,
353
- latestPage,
354
- pageLength,
355
- placeholder,
356
- tableProps,
357
- context,
358
- }) => {
359
- if (isTableProps(tableProps) === false) {
360
- throw new TypeError("tableProps property is not of type TableProps");
360
+ const FullTableWithSavedScrollbar = React.forwardRef((props, ref) => {
361
+ if (props.saveScrollbarPosition && !props.tableName) {
362
+ throw new Error("prop 'tableName' is required if 'saveScrollbarPosition' is set to true.");
361
363
  }
362
364
 
363
- const customClasses = tableProps?.get(TableProps.propNames.classes) || {};
364
- const selectMode = tableProps?.get(TableProps.propNames.selectMode) || false;
365
- const stickyHeader = tableProps?.get(TableProps.propNames.stickyHeader) || false;
366
- const withoutTopBorder = tableProps?.get(TableProps.propNames.withoutTopBorder) || false;
367
- const onRowClick = tableProps?.get(TableProps.propNames.onRowClick) || null;
368
- const deepPropsComparation = tableProps?.get(TableProps.propNames.deepPropsComparation) || false;
369
- const isEditingMode = tableProps?.get(TableProps.propNames.isEditingMode) || false;
370
- const selectedRows = tableProps?.get(TableProps.propNames.selectedRows) || null;
371
- const selectedRowsChanged = tableProps?.get(TableProps.propNames.selectedRowsChanged) || null;
372
- const constrained = tableProps?.get(TableProps.propNames.constrained) || false;
373
-
374
- customClasses["tableHeader"] = tableProps?.getStyle(TableProps.ruleNames.tableHeader) || null;
375
- customClasses["tableRow"] = tableProps?.getStyle(TableProps.ruleNames.tableRow) || null;
376
- customClasses["tableCell"] = tableProps?.getStyle(TableProps.ruleNames.tableCell) || null;
377
- customClasses["headerCell"] = tableProps?.getStyle(TableProps.ruleNames.headerCell) || null;
378
- customClasses["tableContainer"] = tableProps?.getStyle(TableProps.ruleNames.tableContainer) || null;
379
- customClasses["container"] = tableProps?.getStyle(TableProps.ruleNames.container) || null;
380
- customClasses["table"] = tableProps?.getStyle(TableProps.ruleNames.table) || null;
381
-
382
- if ((selectedRows && !selectedRowsChanged) || (!selectedRows && selectedRowsChanged))
383
- throw new Error("Both 'selectedRows' and 'selectedRowsChanged' need to be defined if one of them is.");
384
-
385
- const refScrolled = useRef();
386
-
387
- const [scrolled, setScrolled] = useState(0);
388
- const [tableSize, setTableSize] = useState({ width: 0, height: 0 });
389
-
390
- const [selectedNumber, tableSelectionStatus, selectionMethods] = useTableSelection(
391
- rows,
392
- selectedRows,
393
- selectedRowsChanged,
365
+ const [scrollbarViewState, updateScrollbarViewState] = useViewState(props.tableName + "ScrollbarPosition");
366
+ const [scrollbar, setScrollbarPosition] = useState(scrollbarViewState.scrollBarPosition);
367
+
368
+ useEffect(
369
+ () => {
370
+ const handler = setTimeout(() => {
371
+ updateScrollbarViewState("scrollBarPosition", scrollbar);
372
+ }, 500);
373
+ return () => {
374
+ clearTimeout(handler);
375
+ };
376
+ },
377
+ // eslint-disable-next-line react-hooks/exhaustive-deps
378
+ [scrollbar],
394
379
  );
395
380
 
396
- const classes = useStyles({
397
- withoutTopBorder,
398
- selectMode,
399
- stickyHeader,
400
- scrolled,
401
- onRowClick,
402
- });
381
+ const saveScrollBarPosition = evt => {
382
+ setScrollbarPosition(evt.target.scrollTop);
383
+ };
403
384
 
404
385
  useEffect(() => {
405
- const handleResize = () => {
406
- if (refScrolled.current.offsetHeight < refScrolled.current.scrollHeight)
407
- setScrolled(refScrolled.current.offsetWidth - refScrolled.current.clientWidth);
408
- else setScrolled(0);
409
- };
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;
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
+ }
413
+ }
414
+ // eslint-disable-next-line react-hooks/exhaustive-deps
415
+ }, [props.tableName]); // forcing this effect to run again when the tableName changes
410
416
 
411
- handleResize();
417
+ return <DefaultFullTable {...props} ref={ref} saveScrollBarPosition={saveScrollBarPosition} />;
418
+ });
412
419
 
413
- window.addEventListener("resize", handleResize);
420
+ const Table = React.forwardRef(
421
+ ({ tableInfo, headers, rows, scrollLoader, latestPage, pageLength, placeholder, tableProps, context }, ref) => {
422
+ if (isTableProps(tableProps) === false) {
423
+ throw new TypeError("tableProps property is not of type TableProps");
424
+ }
414
425
 
415
- /* istanbul ignore next */
416
- return () => window.removeEventListener("resize", handleResize);
417
- }, [refScrolled, tableSize.width, tableSize.height]);
418
-
419
- const tableHeaders = buildTableHeaders(
420
- headers,
421
- classes,
422
- customClasses,
423
- selectMode,
424
- tableSelectionStatus,
425
- selectionMethods,
426
- );
426
+ const customClasses = tableProps?.get(TableProps.propNames.classes) || {};
427
+ const selectMode = tableProps?.get(TableProps.propNames.selectMode) || false;
428
+ const stickyHeader = tableProps?.get(TableProps.propNames.stickyHeader) || false;
429
+ const withoutTopBorder = tableProps?.get(TableProps.propNames.withoutTopBorder) || false;
430
+ const onRowClick = tableProps?.get(TableProps.propNames.onRowClick) || null;
431
+ const deepPropsComparation = tableProps?.get(TableProps.propNames.deepPropsComparation) || false;
432
+ const isEditingMode = tableProps?.get(TableProps.propNames.isEditingMode) || false;
433
+ const selectedRows = tableProps?.get(TableProps.propNames.selectedRows) || null;
434
+ const selectedRowsChanged = tableProps?.get(TableProps.propNames.selectedRowsChanged) || null;
435
+ const constrained = tableProps?.get(TableProps.propNames.constrained) || false;
436
+ const tableName = tableProps?.get(TableProps.propNames.tableName) || null;
437
+ const saveScrollbarPosition = tableProps?.get(TableProps.propNames.saveScrollbarPosition) || false;
438
+
439
+ customClasses["tableHeader"] = tableProps?.getStyle(TableProps.ruleNames.tableHeader) || null;
440
+ customClasses["tableRow"] = tableProps?.getStyle(TableProps.ruleNames.tableRow) || null;
441
+ customClasses["tableCell"] = tableProps?.getStyle(TableProps.ruleNames.tableCell) || null;
442
+ customClasses["headerCell"] = tableProps?.getStyle(TableProps.ruleNames.headerCell) || null;
443
+ customClasses["tableContainer"] = tableProps?.getStyle(TableProps.ruleNames.tableContainer) || null;
444
+ customClasses["container"] = tableProps?.getStyle(TableProps.ruleNames.container) || null;
445
+ customClasses["table"] = tableProps?.getStyle(TableProps.ruleNames.table) || null;
446
+
447
+ if ((selectedRows && !selectedRowsChanged) || (!selectedRows && selectedRowsChanged))
448
+ throw new Error("Both 'selectedRows' and 'selectedRowsChanged' need to be defined if one of them is.");
449
+
450
+ const refScrolled = useRef();
451
+
452
+ useImperativeHandle(ref, () => ({
453
+ scrollToTop: () => {
454
+ if (refScrolled.current.scrollTop > 0) {
455
+ refScrolled.current.scrollTop = 0;
456
+ }
457
+ },
458
+ }));
459
+
460
+ const [scrolled, setScrolled] = useState(0);
461
+ const [tableSize, setTableSize] = useState({ width: 0, height: 0 });
462
+
463
+ const [selectedNumber, tableSelectionStatus, selectionMethods] = useTableSelection(
464
+ rows,
465
+ selectedRows,
466
+ selectedRowsChanged,
467
+ );
427
468
 
428
- const tableRows = buildTableRows(
429
- rows,
430
- classes,
431
- customClasses,
432
- selectMode,
433
- onRowClick,
434
- selectionMethods,
435
- deepPropsComparation,
436
- context,
437
- isEditingMode,
438
- );
469
+ const classes = useStyles({
470
+ withoutTopBorder,
471
+ selectMode,
472
+ stickyHeader,
473
+ scrolled,
474
+ onRowClick,
475
+ });
476
+
477
+ useEffect(() => {
478
+ const handleResize = () => {
479
+ if (refScrolled.current.offsetHeight < refScrolled.current.scrollHeight)
480
+ setScrolled(refScrolled.current.offsetWidth - refScrolled.current.clientWidth);
481
+ else setScrolled(0);
482
+ };
483
+
484
+ handleResize();
485
+
486
+ window.addEventListener("resize", handleResize);
487
+
488
+ /* istanbul ignore next */
489
+ return () => window.removeEventListener("resize", handleResize);
490
+ }, [refScrolled, tableSize.width, tableSize.height]);
491
+
492
+ const tableHeaders = buildTableHeaders(
493
+ headers,
494
+ classes,
495
+ customClasses,
496
+ selectMode,
497
+ tableSelectionStatus,
498
+ selectionMethods,
499
+ );
439
500
 
440
- const stickerTableHeader =
441
- stickyHeader === true ? (
442
- <StickerTableHeader selectMode={selectMode} classes={classes} tableHeaders={tableHeaders} />
443
- ) : null;
501
+ const tableRows = buildTableRows(
502
+ rows,
503
+ classes,
504
+ customClasses,
505
+ selectMode,
506
+ onRowClick,
507
+ selectionMethods,
508
+ deepPropsComparation,
509
+ context,
510
+ isEditingMode,
511
+ );
444
512
 
445
- /* istanbul ignore next */
446
- const onResize = useCallback((width, height) => setTableSize({ width: width, height: height }), [setTableSize]);
513
+ const stickerTableHeader =
514
+ stickyHeader === true ? (
515
+ <StickerTableHeader selectMode={selectMode} classes={classes} tableHeaders={tableHeaders} />
516
+ ) : null;
447
517
 
448
- return (
449
- <TableContainer className={classNames(classes.container, customClasses.container)}>
450
- {tableInfo}
451
- {stickerTableHeader}
452
- <FullTable
453
- ref={refScrolled}
454
- classes={classes}
455
- customClasses={customClasses}
456
- constrained={constrained}
457
- onResize={onResize}
458
- selectedNumber={selectedNumber}
459
- scrollLoader={scrollLoader}
460
- tableHeaders={tableHeaders}
461
- dataRows={rows}
462
- tableRows={tableRows}
463
- stickyHeader={stickyHeader}
464
- latestPage={latestPage}
465
- pageLength={pageLength}
466
- placeholder={placeholder}
467
- deepPropsComparation={deepPropsComparation}
468
- isEditingMode={isEditingMode}
469
- context={context}
470
- />
471
- </TableContainer>
472
- );
473
- };
518
+ /* istanbul ignore next */
519
+ const onResize = useCallback((width, height) => setTableSize({ width: width, height: height }), [setTableSize]);
520
+
521
+ return (
522
+ <TableContainer className={classNames(classes.container, customClasses.container)}>
523
+ {tableInfo}
524
+ {stickerTableHeader}
525
+ <FullTable
526
+ ref={refScrolled}
527
+ classes={classes}
528
+ customClasses={customClasses}
529
+ constrained={constrained}
530
+ onResize={onResize}
531
+ selectedNumber={selectedNumber}
532
+ scrollLoader={scrollLoader}
533
+ tableHeaders={tableHeaders}
534
+ dataRows={rows}
535
+ tableRows={tableRows}
536
+ stickyHeader={stickyHeader}
537
+ latestPage={latestPage}
538
+ pageLength={pageLength}
539
+ placeholder={placeholder}
540
+ deepPropsComparation={deepPropsComparation}
541
+ isEditingMode={isEditingMode}
542
+ context={context}
543
+ tableName={tableName}
544
+ saveScrollbarPosition={saveScrollbarPosition}
545
+ />
546
+ </TableContainer>
547
+ );
548
+ },
549
+ );
474
550
 
475
551
  export default React.memo(
476
552
  Table,
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useRef } from "react";
2
2
  import ReactDOM from "react-dom";
3
3
  import { act } from "react-dom/test-utils";
4
4
  import sinon from "sinon";
@@ -19,6 +19,8 @@ import ResizeDetector from "react-resize-detector";
19
19
  import CheckboxMui from "../Inputs/Checkbox";
20
20
  import { cloneDeep } from "lodash";
21
21
  import TooltippedTypography from "./TooltippedElements/TooltippedTypography";
22
+ import Immutable from "immutable";
23
+ import { TestWrapper } from "../../../utils/testUtils";
22
24
 
23
25
  const TestComp = ({ classToTest, styleProps }) => {
24
26
  const classes = useStyles({ ...styleProps });
@@ -507,6 +509,22 @@ describe("Table", () => {
507
509
  { fieldName: "a2", label: headerLabels.column2 },
508
510
  ];
509
511
 
512
+ let store, state;
513
+
514
+ beforeEach(() => {
515
+ state = Immutable.fromJS({
516
+ modules: {
517
+ tree: {},
518
+ },
519
+ view: {},
520
+ });
521
+ store = {
522
+ subscribe: () => {},
523
+ getState: () => state,
524
+ dispatch: sinon.spy().named("dispatch"),
525
+ };
526
+ });
527
+
510
528
  it("Fails if tableProps has wrong type", () => {
511
529
  ignoreConsoleError(() => {
512
530
  const component = <Table rows={[]} headers={[]} tableProps="Wrong type" />;
@@ -1113,4 +1131,231 @@ describe("Table", () => {
1113
1131
 
1114
1132
  expect(style.customClass, "to equal", "specialClass");
1115
1133
  });
1134
+
1135
+ it("Table throws if saveScrollbar is true but tableName is falsy", () => {
1136
+ const { headers, rows } = buildHeaderAndRowFromConfig(config, elements);
1137
+
1138
+ const tableProps = new TableProps();
1139
+
1140
+ tableProps.set(TableProps.propNames.tableName, null);
1141
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
1142
+
1143
+ ignoreConsoleError(() => {
1144
+ const component = <Table rows={rows} headers={headers} tableProps={tableProps} />;
1145
+ expect(() => mount(component), "to throw a", Error).then(error => {
1146
+ expect(error, "to have message", "prop 'tableName' is required if 'saveScrollbarPosition' is set to true.");
1147
+ });
1148
+ });
1149
+ });
1150
+
1151
+ it("handle scrolling event with save ", () => {
1152
+ const { headers, rows } = buildHeaderAndRowFromConfig(config, elements);
1153
+
1154
+ const tableProps = new TableProps();
1155
+
1156
+ tableProps.set(TableProps.propNames.selectMode, true);
1157
+ tableProps.set(TableProps.propNames.tableName, "test");
1158
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
1159
+
1160
+ const scrollLoader = sinon.spy().named("scrollLoader");
1161
+
1162
+ const component = (
1163
+ <TestWrapper provider={{ store }}>
1164
+ <Table
1165
+ rows={rows}
1166
+ headers={headers}
1167
+ pageLength={2}
1168
+ latestPage={1}
1169
+ tableProps={tableProps}
1170
+ scrollLoader={scrollLoader}
1171
+ />
1172
+ </TestWrapper>
1173
+ );
1174
+
1175
+ const mountedComponent = mount(component);
1176
+
1177
+ const scrollEvent = document.createEvent("MouseEvents");
1178
+ scrollEvent.initEvent("scroll", true, false);
1179
+
1180
+ const table = mountedComponent.find(TableMui);
1181
+
1182
+ table.simulate("scroll", {
1183
+ target: { scrollHeight: 1000, scrollTop: 40, offsetHeight: 100 },
1184
+ });
1185
+
1186
+ expect(scrollLoader, "was not called");
1187
+
1188
+ table.simulate("scroll", {
1189
+ target: { scrollHeight: 1000, scrollTop: 850, offsetHeight: 100 },
1190
+ });
1191
+
1192
+ return expect(scrollLoader, "to have calls satisfying", [{ args: [2] }])
1193
+ .then(() => new Promise(r => setTimeout(r, 600)))
1194
+ .then(() =>
1195
+ expect(store.dispatch, "to have calls satisfying", [
1196
+ {
1197
+ args: [
1198
+ {
1199
+ type: "VIEW_STATE_SET_FIELD",
1200
+ payload: {
1201
+ name: "testScrollbarPosition",
1202
+ field: "scrollBarPosition",
1203
+ value: 850,
1204
+ },
1205
+ },
1206
+ ],
1207
+ },
1208
+ ]),
1209
+ );
1210
+ });
1211
+
1212
+ it("should initialize scrollbar to saved state", () => {
1213
+ const { headers, rows } = buildHeaderAndRowFromConfig(config, elements);
1214
+
1215
+ state = state.setIn(
1216
+ ["view", "testScrollbarPosition"],
1217
+ Immutable.fromJS({
1218
+ scrollBarPosition: 800,
1219
+ }),
1220
+ );
1221
+
1222
+ const tableProps = new TableProps();
1223
+
1224
+ tableProps.set(TableProps.propNames.selectMode, true);
1225
+ tableProps.set(TableProps.propNames.tableName, "test");
1226
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
1227
+
1228
+ const scrollLoader = sinon.spy().named("scrollLoader");
1229
+
1230
+ const component = (
1231
+ <TestWrapper provider={{ store }}>
1232
+ <Table
1233
+ rows={rows}
1234
+ headers={headers}
1235
+ pageLength={2}
1236
+ latestPage={1}
1237
+ tableProps={tableProps}
1238
+ scrollLoader={scrollLoader}
1239
+ />
1240
+ </TestWrapper>
1241
+ );
1242
+
1243
+ const mountedComponent = mount(component);
1244
+ const scrollableDiv = mountedComponent.find({ "data-qa": "scrollable-table-div" });
1245
+ expect(scrollableDiv?.getElement()?.ref?.current?.scrollTop, "to equal", 800);
1246
+ });
1247
+
1248
+ it("should scroll to top", () => {
1249
+ const { headers, rows } = buildHeaderAndRowFromConfig(config, elements);
1250
+
1251
+ state = state.setIn(
1252
+ ["view", "testScrollbarPosition"],
1253
+ Immutable.fromJS({
1254
+ scrollBarPosition: 800,
1255
+ }),
1256
+ );
1257
+
1258
+ const tableProps = new TableProps();
1259
+
1260
+ tableProps.set(TableProps.propNames.selectMode, true);
1261
+ tableProps.set(TableProps.propNames.tableName, "test");
1262
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
1263
+
1264
+ const scrollLoader = sinon.spy().named("scrollLoader");
1265
+
1266
+ const CustomTable = () => {
1267
+ const ref = useRef(null);
1268
+
1269
+ const clickToTop = () => {
1270
+ ref.current && ref.current.scrollToTop();
1271
+ };
1272
+
1273
+ return (
1274
+ <>
1275
+ <input type="button" onClick={clickToTop} data-qa="scroll-btn" />
1276
+ <Table
1277
+ ref={ref}
1278
+ rows={rows}
1279
+ headers={headers}
1280
+ pageLength={2}
1281
+ latestPage={1}
1282
+ tableProps={tableProps}
1283
+ scrollLoader={scrollLoader}
1284
+ />
1285
+ </>
1286
+ );
1287
+ };
1288
+
1289
+ const component = (
1290
+ <TestWrapper provider={{ store }}>
1291
+ <CustomTable />
1292
+ </TestWrapper>
1293
+ );
1294
+
1295
+ const mountedComponent = mount(component);
1296
+ const scrollableDiv = mountedComponent.find({ "data-qa": "scrollable-table-div" });
1297
+ expect(scrollableDiv?.getElement()?.ref?.current?.scrollTop, "to equal", 800);
1298
+
1299
+ const scrollBtn = mountedComponent.find({ "data-qa": "scroll-btn" });
1300
+ scrollBtn.simulate("click");
1301
+
1302
+ expect(scrollableDiv?.getElement()?.ref?.current?.scrollTop, "to equal", 0);
1303
+ });
1304
+
1305
+ it("should not scroll to top because already at the top", () => {
1306
+ const { headers, rows } = buildHeaderAndRowFromConfig(config, elements);
1307
+
1308
+ state = state.setIn(
1309
+ ["view", "testScrollbarPosition"],
1310
+ Immutable.fromJS({
1311
+ scrollBarPosition: 0,
1312
+ }),
1313
+ );
1314
+
1315
+ const tableProps = new TableProps();
1316
+
1317
+ tableProps.set(TableProps.propNames.selectMode, true);
1318
+ tableProps.set(TableProps.propNames.tableName, "test");
1319
+ tableProps.set(TableProps.propNames.saveScrollbarPosition, true);
1320
+
1321
+ const scrollLoader = sinon.spy().named("scrollLoader");
1322
+
1323
+ const CustomTable = () => {
1324
+ const ref = useRef(null);
1325
+
1326
+ const clickToTop = () => {
1327
+ ref.current && ref.current.scrollToTop();
1328
+ };
1329
+
1330
+ return (
1331
+ <>
1332
+ <input type="button" onClick={clickToTop} data-qa="scroll-btn" />
1333
+ <Table
1334
+ ref={ref}
1335
+ rows={rows}
1336
+ headers={headers}
1337
+ pageLength={2}
1338
+ latestPage={1}
1339
+ tableProps={tableProps}
1340
+ scrollLoader={scrollLoader}
1341
+ />
1342
+ </>
1343
+ );
1344
+ };
1345
+
1346
+ const component = (
1347
+ <TestWrapper provider={{ store }}>
1348
+ <CustomTable />
1349
+ </TestWrapper>
1350
+ );
1351
+
1352
+ const mountedComponent = mount(component);
1353
+ const scrollableDiv = mountedComponent.find({ "data-qa": "scrollable-table-div" });
1354
+ expect(scrollableDiv?.getElement()?.ref?.current?.scrollTop, "to equal", 0);
1355
+
1356
+ const scrollBtn = mountedComponent.find({ "data-qa": "scroll-btn" });
1357
+ scrollBtn.simulate("click");
1358
+
1359
+ expect(scrollableDiv?.getElement()?.ref?.current?.scrollTop, "to equal", 0);
1360
+ });
1116
1361
  });
@@ -12,6 +12,8 @@ class TableProps extends ComponentProps {
12
12
  selectedRows: "selectedRows",
13
13
  selectedRowsChanged: "selectedRowsChanged",
14
14
  constrained: "constrained",
15
+ tableName: "tableName",
16
+ saveScrollbarPosition: "saveScrollbarPosition",
15
17
  };
16
18
 
17
19
  static ruleNames = {
@@ -37,6 +39,8 @@ class TableProps extends ComponentProps {
37
39
  this.componentProps.set(this.constructor.propNames.selectedRows, null);
38
40
  this.componentProps.set(this.constructor.propNames.selectedRowsChanged, null);
39
41
  this.componentProps.set(this.constructor.propNames.constrained, false);
42
+ this.componentProps.set(this.constructor.propNames.tableName, null);
43
+ this.componentProps.set(this.constructor.propNames.saveScrollbarPosition, null);
40
44
 
41
45
  this.componentClasses.set(this.constructor.ruleNames.tableHeader, null);
42
46
  this.componentClasses.set(this.constructor.ruleNames.tableRow, null);
@@ -30,6 +30,8 @@ describe("TableProps Props", () => {
30
30
  "selectedRows",
31
31
  "selectedRowsChanged",
32
32
  "constrained",
33
+ "tableName",
34
+ "saveScrollbarPosition",
33
35
  ];
34
36
 
35
37
  const ruleNames = ["tableHeader", "tableRow", "tableCell", "headerCell", "tableContainer", "container", "table"];