@k-int/stripes-kint-components 5.11.0 → 5.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # [5.12.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.11.0...v5.12.0) (2025-02-13)
2
+
3
+
4
+ ### Features
5
+
6
+ * **deps:** Stripes v10 dependency updates ([be3892f](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/be3892f9a3f61bf7e52cf1c1287082aed412161b))
7
+ * Paginated display as default for SASQRoute (refs UISER-185) ([9f379c9](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/9f379c91ef284dad9408cccb8d80cc314c969d8d))
8
+
1
9
  # [5.11.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.10.0...v5.11.0) (2025-02-07)
2
10
 
3
11
 
@@ -116,7 +116,7 @@ const EditableRefdataList = _ref => {
116
116
  // or one provided in labelOverrides, which is passed the error message and refdata in question
117
117
  delete: async err => {
118
118
  const errorResp = await (0, _utils.parseErrorResponse)(err.response);
119
- console.log("ERRORRESP: %o", errorResp);
119
+ // console.log('ERRORRESP: %o', errorResp);
120
120
  callout.sendCallout({
121
121
  message: kintIntl.formatKintMessage({
122
122
  id: 'refdata.deleteRefdataValue.errorMessage',
@@ -28,6 +28,7 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
28
28
  intlNS: passedIntlNS,
29
29
  labelOverrides = {},
30
30
  mainPaneProps,
31
+ mclProps = {},
31
32
  noSearchField,
32
33
  persistedPanesetProps = {},
33
34
  RenderBody,
@@ -38,6 +39,15 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
38
39
  searchFieldProps
39
40
  } = props;
40
41
  const kintIntl = (0, _hooks.useKintIntl)(passedIntlKey, passedIntlNS);
42
+ const [count, setCount] = (0, _react.useState)(0);
43
+ const {
44
+ currentPage,
45
+ paginationMCLProps,
46
+ paginationSASQProps
47
+ } = (0, _hooks.usePrevNextPagination)({
48
+ count,
49
+ pageSize: fetchParameters.SASQ_MAP?.perPage
50
+ });
41
51
  const {
42
52
  query,
43
53
  queryGetter,
@@ -48,14 +58,10 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
48
58
  } = (0, _core.useNamespace)();
49
59
  const ky = (0, _core.useOkapiKy)();
50
60
  const filterPaneVisibileKey = `${namespace}-${id}-filterPaneVisibility`;
51
- const fetchPageData = _ref => {
52
- let {
53
- pageParam = 0
54
- } = _ref;
55
- const queryMap = fetchParameters.SASQ_MAP;
56
- queryMap.offset = pageParam;
57
- return ky(`${fetchParameters.endpoint}${(0, _utils.generateKiwtQuery)(queryMap, query)}`).json();
58
- };
61
+ const queryParams = (0, _react.useMemo)(() => (0, _utils.generateKiwtQuery)({
62
+ ...fetchParameters.SASQ_MAP,
63
+ page: currentPage
64
+ }, query ?? {}), [currentPage, fetchParameters.SASQ_MAP, query]);
59
65
  const [filterPaneVisible, setFilterPaneVisible] = (0, _hooks.useLocalStorageState)(filterPaneVisibileKey, true);
60
66
  const toggleFilterPane = () => setFilterPaneVisible(!filterPaneVisible);
61
67
  const queryNamespace = [namespace, 'SASQ'];
@@ -64,30 +70,27 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
64
70
  }
65
71
  queryNamespace.push('viewAll');
66
72
  queryNamespace.push(query);
73
+ queryNamespace.push(currentPage);
67
74
  const {
68
- data: totalData = {},
69
- ...restOfInfiniteQueryProps
70
- } = (0, _reactQuery.useInfiniteQuery)(queryNamespace, fetchPageData);
75
+ data = {},
76
+ ...restOfQueryProps
77
+ } = (0, _reactQuery.useQuery)(queryNamespace, () => {
78
+ return ky.get(`${fetchParameters.endpoint}${queryParams}`).json();
79
+ }, {
80
+ enabled: !!currentPage
81
+ });
82
+ (0, _react.useEffect)(() => {
83
+ if (count !== data?.totalRecords) {
84
+ setCount(data?.totalRecords);
85
+ }
86
+ }, [count, data.totalRecords]);
71
87
  (0, _react.useImperativeHandle)(ref, () => ({
72
88
  lookupQueryProps: {
73
- data: totalData,
74
- ...restOfInfiniteQueryProps
75
- }
89
+ data,
90
+ ...restOfQueryProps
91
+ },
92
+ queryParams
76
93
  }));
77
- const data = totalData.pages?.reduce((acc, curr) => {
78
- const newAcc = {
79
- ...acc
80
- };
81
- for (const [key, value] of Object.entries(curr)) {
82
- if (key !== 'page' && key !== 'result' && acc[key] !== value) {
83
- newAcc[key] = value;
84
- }
85
- }
86
- const newResults = [...(acc.results ?? [])];
87
- newResults.push(...(curr.results ?? []));
88
- newAcc.results = newResults;
89
- return newAcc;
90
- }, {}) ?? {};
91
94
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_smartComponents.SearchAndSortQuery, {
92
95
  initialSearchState: {
93
96
  query: ''
@@ -95,6 +98,7 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
95
98
  queryGetter: queryGetter,
96
99
  querySetter: querySetter,
97
100
  ...sasqProps,
101
+ ...paginationSASQProps,
98
102
  children: sasqRenderProps => {
99
103
  const {
100
104
  activeFilters,
@@ -222,10 +226,14 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
222
226
  query: query,
223
227
  rowNavigation: rowNavigation,
224
228
  toggleFilterPane: toggleFilterPane,
225
- ...restOfInfiniteQueryProps,
229
+ ...restOfQueryProps,
226
230
  ...sasqRenderProps,
227
231
  ...sasqProps,
228
- ...props
232
+ ...props,
233
+ mclProps: {
234
+ ...paginationMCLProps,
235
+ ...mclProps
236
+ }
229
237
  })
230
238
  }), children]
231
239
  });
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.PREV = exports.NEXT = exports.MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX = exports.DEFAULT_PAGINATION_SIZE = exports.DEFAULT_PAGE_KEY = void 0;
7
+ const NEXT = exports.NEXT = 'next';
8
+ const PREV = exports.PREV = 'prev';
9
+
10
+ // THIS SHOULD BE SET BY CALLING CODE, ONLY DEFAULTED TO AVOID DIVISION BY UNDEFINED
11
+ const DEFAULT_PAGINATION_SIZE = exports.DEFAULT_PAGINATION_SIZE = 25;
12
+ // Only here because we need argument 3 from onNeedMoreData
13
+ // and want to avoid "magic number" sonarlint
14
+ const MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX = exports.MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX = 3;
15
+ const DEFAULT_PAGE_KEY = exports.DEFAULT_PAGE_KEY = 'defaultPageKey';
@@ -105,6 +105,12 @@ Object.defineProperty(exports, "useMutateRefdataValue", {
105
105
  return _useMutateRefdataValue.default;
106
106
  }
107
107
  });
108
+ Object.defineProperty(exports, "usePrevNextPagination", {
109
+ enumerable: true,
110
+ get: function () {
111
+ return _usePrevNextPagination.default;
112
+ }
113
+ });
108
114
  Object.defineProperty(exports, "useQIndex", {
109
115
  enumerable: true,
110
116
  get: function () {
@@ -150,4 +156,5 @@ var _useMutateRefdataValue = _interopRequireDefault(require("./useMutateRefdataV
150
156
  var _useMutateRefdataCategory = _interopRequireDefault(require("./useMutateRefdataCategory"));
151
157
  var _useMutateCustomProperties = _interopRequireDefault(require("./useMutateCustomProperties"));
152
158
  var _useMutateModConfigEntry = _interopRequireDefault(require("./useMutateModConfigEntry"));
159
+ var _usePrevNextPagination = _interopRequireDefault(require("./usePrevNextPagination"));
153
160
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _zustand = require("zustand");
8
+ var _pagination = require("../constants/pagination");
9
+ // Any time that usePrevNextPagination is NOT synced to location,
10
+ // store a keyed currentPage here instead
11
+ const useLocalPageStore = (0, _zustand.create)(set => ({
12
+ pageStore: {},
13
+ setPage: (id, page) => set(state => {
14
+ // Any non-id keyed pages will go into a single storage slot
15
+ const key = id ?? _pagination.DEFAULT_PAGE_KEY;
16
+ return {
17
+ ...state,
18
+ pageStore: {
19
+ ...state.pageStore,
20
+ [key]: page
21
+ }
22
+ };
23
+ })
24
+ }));
25
+ var _default = exports.default = useLocalPageStore;
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = require("react");
8
+ var _reactRouterDom = require("react-router-dom");
9
+ var _queryString = _interopRequireDefault(require("query-string"));
10
+ var _pagination = require("../constants/pagination");
11
+ var _useLocalPageStore = _interopRequireDefault(require("./useLocalPageStore"));
12
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
+ // Currently there are several places in which this hook is used twice within the same component
14
+ // Once in order to get the current page, and again in order to handle changes to paginations
15
+ // This hook should be refactored in order to resolve these issue - @EthanFreestone
16
+ const usePrevNextPagination = function () {
17
+ let {
18
+ count = 0,
19
+ // Only needed for reading back MCL props
20
+ defaultToPageOne = true,
21
+ // A prop to allow the implementor to turn off the defaulting to page=1
22
+ pageSize = _pagination.DEFAULT_PAGINATION_SIZE,
23
+ // Only needed for reading back MCL props
24
+ id = _pagination.DEFAULT_PAGE_KEY,
25
+ // This id is ONLY used for syncToLocation: false cases, as a key to the zustand store
26
+ syncToLocation = true,
27
+ // Used to turn on/off location syncing, so can be used as standalone state if required,
28
+ hasNextPage = null // Override for canGoNext, used in the case in which resources are fetched with no stats
29
+ } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
30
+ /* ------ ZUSTAND STORE ------ */
31
+ // For NON-SYNC-TO-LOCATION use cases, store the currentPage in a keyed store
32
+ const pageStore = (0, _useLocalPageStore.default)(state => state.pageStore);
33
+ const setPage = (0, _useLocalPageStore.default)(state => state.setPage);
34
+
35
+ /* ------ CURRENTPAGE STATE ------ */
36
+ // Set up initialValue
37
+ const getInitialCurrentPage = (0, _react.useCallback)(() => {
38
+ let initialCurrentPage;
39
+ if (!syncToLocation) {
40
+ if (pageStore[id]) {
41
+ initialCurrentPage = pageStore[id];
42
+ } else {
43
+ // Initialise store state
44
+ setPage(id, 1);
45
+ initialCurrentPage = 1;
46
+ }
47
+ }
48
+ return initialCurrentPage;
49
+ }, [id, pageStore, setPage, syncToLocation]);
50
+ // State itself
51
+ const [currentPage, setCurrentPage] = (0, _react.useState)(getInitialCurrentPage());
52
+ const location = (0, _reactRouterDom.useLocation)();
53
+ const history = (0, _reactRouterDom.useHistory)();
54
+
55
+ /* ------ HANDLEPAGECHANGE ------ */
56
+ // Takes in a direction "prev" or "next" and performs the requisite logic to move
57
+ // currentPage state and/or zustand store state
58
+ const handlePageChange = (0, _react.useCallback)(direction => {
59
+ const urlQuery = _queryString.default.parse(location.search);
60
+ let newPage;
61
+ if (direction === _pagination.NEXT) {
62
+ newPage = currentPage + 1;
63
+ } else if (direction === _pagination.PREV) {
64
+ newPage = currentPage - 1;
65
+ }
66
+ if (!syncToLocation) {
67
+ // We're manipulating the state directly in this case
68
+ // We're dealing with the zustand store in this case,
69
+ // change the store and the currentPage will update below
70
+ setPage(id, newPage);
71
+ setCurrentPage(newPage);
72
+ } else if (newPage !== urlQuery?.page) {
73
+ const newQuery = {
74
+ ...urlQuery,
75
+ page: newPage
76
+ };
77
+ history.push({
78
+ pathname: location.pathname,
79
+ search: `?${_queryString.default.stringify(newQuery)}`
80
+ });
81
+ }
82
+ }, [currentPage, history, id, location.pathname, location.search, setPage, syncToLocation]);
83
+ const [resetPageState, setResetPageState] = (0, _react.useState)(false);
84
+ const resetPage = (0, _react.useCallback)(() => {
85
+ if (syncToLocation) {
86
+ setResetPageState(true);
87
+ } else {
88
+ setPage(id, 1);
89
+ setCurrentPage(1);
90
+ }
91
+ }, [id, setPage, syncToLocation]);
92
+ (0, _react.useEffect)(() => {
93
+ if (syncToLocation) {
94
+ const urlQuery = _queryString.default.parse(location.search);
95
+ if (urlQuery?.page && currentPage !== urlQuery?.page) {
96
+ setCurrentPage(Number(urlQuery?.page));
97
+ } else if (!urlQuery?.page && defaultToPageOne) {
98
+ // If url query "page" is not yet set, set it to 1
99
+ setCurrentPage(1);
100
+ const newQuery = {
101
+ ...urlQuery,
102
+ page: 1
103
+ };
104
+ history.push({
105
+ pathname: location.pathname,
106
+ search: `?${_queryString.default.stringify(newQuery)}`
107
+ });
108
+ }
109
+ if (resetPageState) {
110
+ const newQuery = {
111
+ ...urlQuery,
112
+ page: 1
113
+ };
114
+ history.push({
115
+ pathname: location.pathname,
116
+ search: `?${_queryString.default.stringify(newQuery)}`
117
+ });
118
+ setResetPageState(false);
119
+ }
120
+ } else if (currentPage !== pageStore[id]) {
121
+ // Only do this when not syncing to location...
122
+ // If current page state is not what we have in the store, set current page state
123
+ setCurrentPage(pageStore[id]);
124
+ }
125
+ }, [currentPage, defaultToPageOne, history, id, location, pageStore, resetPageState, syncToLocation]);
126
+
127
+ // Set up MCL specific props based on page
128
+ const pagingCanGoNext = hasNextPage ?? (currentPage && currentPage < Number(count) / pageSize);
129
+ const pagingCanGoPrevious = currentPage && Number(currentPage) > 1;
130
+ const pagingOffset = currentPage ? (currentPage - 1) * pageSize : 0;
131
+ const onNeedMoreData = function () {
132
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
133
+ args[_key] = arguments[_key];
134
+ }
135
+ if (args[_pagination.MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX]) {
136
+ handlePageChange(args[_pagination.MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX]);
137
+ }
138
+ };
139
+
140
+ // Set up SASQ callback handling
141
+ // If extras are needed, these can be set up
142
+ // manually using resetPage per SASQ
143
+ const queryStateReducer = (0, _react.useCallback)((_currState, nextState) => {
144
+ const resetPageEvents = ['clear.all', 'reset.all', 'filter.state', 'filter.clearGroup', 'sort.change', 'search.reset', 'search.submit'];
145
+ if (resetPageEvents.includes(nextState.changeType)) {
146
+ resetPage();
147
+ }
148
+ return nextState;
149
+ }, [resetPage]);
150
+ const paginationSASQProps = (0, _react.useMemo)(() => ({
151
+ queryStateReducer
152
+ }), [queryStateReducer]);
153
+ return {
154
+ currentPage,
155
+ handlePageChange,
156
+ paginationMCLProps: {
157
+ onNeedMoreData,
158
+ pagingCanGoNext,
159
+ pagingCanGoPrevious,
160
+ pagingOffset,
161
+ pagingType: 'prev-next'
162
+ },
163
+ paginationSASQProps,
164
+ queryStateReducer,
165
+ resetPage
166
+ };
167
+ };
168
+ var _default = exports.default = usePrevNextPagination;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-int/stripes-kint-components",
3
- "version": "5.11.0",
3
+ "version": "5.12.0",
4
4
  "description": "Stripes Component library for K-Int specific applications",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -40,12 +40,12 @@
40
40
  "@babel/preset-flow": "^7.18.6",
41
41
  "@babel/preset-react": "^7.18.6",
42
42
  "@babel/preset-typescript": "^7.18.6",
43
- "@folio/eslint-config-stripes": "^7.0.0",
44
- "@folio/jest-config-stripes": "^2.0.0",
45
- "@folio/stripes": "^9.0.0",
46
- "@folio/stripes-cli": "^3.0.0",
43
+ "@folio/eslint-config-stripes": "^7.0.0 || ^8.0.0",
44
+ "@folio/jest-config-stripes": "^2.0.0 || ^3.0.0",
45
+ "@folio/stripes": "^9.0.0 || ^10.0.0",
46
+ "@folio/stripes-cli": "^3.0.0 || ^4.0.0",
47
47
  "@folio/stripes-erm-testing": "^2.0.0",
48
- "@formatjs/cli": "^6.1.3",
48
+ "@formatjs/cli": "^6.6.0",
49
49
  "@semantic-release/changelog": "^6.0.3",
50
50
  "@semantic-release/git": "^10.0.1",
51
51
  "@semantic-release/gitlab": "^12.0.6",
@@ -64,7 +64,7 @@
64
64
  "prop-types-extra": ">=1.1.0",
65
65
  "react": "^18.2.0",
66
66
  "react-dom": "^18.2.0",
67
- "react-intl": "^6.4.4",
67
+ "react-intl": "^6.4.4 || ^7.1.5",
68
68
  "react-query": "^3.6.0",
69
69
  "react-redux": "^9.0.0",
70
70
  "react-router": "^5.2.0",
@@ -77,7 +77,7 @@
77
77
  "sinon": "^18.0.0"
78
78
  },
79
79
  "peerDependencies": {
80
- "@folio/stripes": ">=9.0.0",
80
+ "@folio/stripes": "^9.0.0 || ^10.0.0",
81
81
  "final-form": ">=4.18.4",
82
82
  "final-form-arrays": ">=3.0.1",
83
83
  "lodash": ">=4.17.0",
@@ -86,7 +86,7 @@
86
86
  "react": "*",
87
87
  "react-final-form": ">=6.3.0",
88
88
  "react-final-form-arrays": ">=3.1.0",
89
- "react-intl": ">=6.4.4",
89
+ "react-intl": "^6.4.4 || ^7.1.5",
90
90
  "react-query": ">=3.9.0",
91
91
  "react-router-dom": ">=5.2.0"
92
92
  },
@@ -106,16 +106,16 @@ const EditableRefdataList = ({
106
106
  // or one provided in labelOverrides, which is passed the error message and refdata in question
107
107
  delete: async (err) => {
108
108
  const errorResp = await parseErrorResponse(err.response);
109
- console.log("ERRORRESP: %o", errorResp);
109
+ // console.log('ERRORRESP: %o', errorResp);
110
110
  callout.sendCallout({
111
111
  message: kintIntl.formatKintMessage({
112
112
  id: 'refdata.deleteRefdataValue.errorMessage',
113
113
  overrideValue: labelOverrides?.deleteError
114
114
  },
115
- {
116
- label: deleteModal?.refdata?.label,
117
- error: errorResp?.message
118
- }),
115
+ {
116
+ label: deleteModal?.refdata?.label,
117
+ error: errorResp?.message
118
+ }),
119
119
  type: 'error',
120
120
  });
121
121
  },
@@ -1,8 +1,8 @@
1
- import { forwardRef, useImperativeHandle } from 'react';
1
+ import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
2
2
 
3
3
  import PropTypes from 'prop-types';
4
4
  import { FormattedMessage } from 'react-intl';
5
- import { useInfiniteQuery } from 'react-query';
5
+ import { useQuery } from 'react-query';
6
6
 
7
7
  import {
8
8
  useNamespace,
@@ -25,7 +25,7 @@ import {
25
25
  } from '@folio/stripes/components';
26
26
 
27
27
  import { generateKiwtQuery } from '../utils';
28
- import { useKintIntl, useKiwtSASQuery, useLocalStorageState } from '../hooks';
28
+ import { useKintIntl, useKiwtSASQuery, useLocalStorageState, usePrevNextPagination } from '../hooks';
29
29
 
30
30
  import TableBody from './TableBody';
31
31
 
@@ -41,6 +41,7 @@ const SASQLookupComponent = forwardRef((props, ref) => {
41
41
  intlNS: passedIntlNS,
42
42
  labelOverrides = {},
43
43
  mainPaneProps,
44
+ mclProps = {},
44
45
  noSearchField,
45
46
  persistedPanesetProps = {},
46
47
  RenderBody,
@@ -50,6 +51,12 @@ const SASQLookupComponent = forwardRef((props, ref) => {
50
51
  searchFieldProps
51
52
  } = props;
52
53
  const kintIntl = useKintIntl(passedIntlKey, passedIntlNS);
54
+ const [count, setCount] = useState(0);
55
+
56
+ const { currentPage, paginationMCLProps, paginationSASQProps } = usePrevNextPagination({
57
+ count,
58
+ pageSize: fetchParameters.SASQ_MAP?.perPage
59
+ });
53
60
 
54
61
  const { query, queryGetter, querySetter } = useKiwtSASQuery();
55
62
  const { 0: namespace } = useNamespace();
@@ -57,11 +64,14 @@ const SASQLookupComponent = forwardRef((props, ref) => {
57
64
 
58
65
  const filterPaneVisibileKey = `${namespace}-${id}-filterPaneVisibility`;
59
66
 
60
- const fetchPageData = ({ pageParam = 0 }) => {
61
- const queryMap = fetchParameters.SASQ_MAP;
62
- queryMap.offset = pageParam;
63
- return ky(`${fetchParameters.endpoint}${generateKiwtQuery(queryMap, query)}`).json();
64
- };
67
+ const queryParams = useMemo(() => (
68
+ generateKiwtQuery(
69
+ {
70
+ ...fetchParameters.SASQ_MAP,
71
+ page: currentPage,
72
+ }, (query ?? {})
73
+ )
74
+ ), [currentPage, fetchParameters.SASQ_MAP, query]);
65
75
 
66
76
  const [filterPaneVisible, setFilterPaneVisible] = useLocalStorageState(filterPaneVisibileKey, true);
67
77
  const toggleFilterPane = () => setFilterPaneVisible(!filterPaneVisible);
@@ -72,65 +82,57 @@ const SASQLookupComponent = forwardRef((props, ref) => {
72
82
  }
73
83
  queryNamespace.push('viewAll');
74
84
  queryNamespace.push(query);
85
+ queryNamespace.push(currentPage);
75
86
 
76
87
  const {
77
- data: totalData = {},
78
- ...restOfInfiniteQueryProps
79
- } = useInfiniteQuery(
88
+ data = {},
89
+ ...restOfQueryProps
90
+ } = useQuery(
80
91
  queryNamespace,
81
- fetchPageData
92
+ () => {
93
+ return ky.get(`${fetchParameters.endpoint}${queryParams}`).json();
94
+ },
95
+ {
96
+ enabled: !!currentPage,
97
+ }
82
98
  );
83
99
 
100
+ useEffect(() => {
101
+ if (count !== data?.totalRecords) {
102
+ setCount(data?.totalRecords);
103
+ }
104
+ }, [count, data.totalRecords]);
105
+
84
106
  useImperativeHandle(ref, () => (
85
107
  {
86
108
  lookupQueryProps: {
87
- data: totalData,
88
- ...restOfInfiniteQueryProps
89
- }
109
+ data,
110
+ ...restOfQueryProps
111
+ },
112
+ queryParams
90
113
  }
91
114
  ));
92
115
 
93
- const data = totalData.pages?.reduce(
94
- (acc, curr) => {
95
- const newAcc = { ...acc };
96
- for (const [key, value] of Object.entries(curr)) {
97
- if (
98
- key !== 'page' &&
99
- key !== 'result' &&
100
- acc[key] !== value
101
- ) {
102
- newAcc[key] = value;
103
- }
104
- }
105
-
106
- const newResults = [...(acc.results ?? [])];
107
- newResults.push(...(curr.results ?? []));
108
- newAcc.results = newResults;
109
-
110
- return newAcc;
111
- },
112
- {}
113
- ) ?? {};
114
-
115
116
  return (
116
117
  <SearchAndSortQuery
117
118
  initialSearchState={{ query: '' }}
118
119
  queryGetter={queryGetter}
119
120
  querySetter={querySetter}
120
121
  {...sasqProps}
122
+ {...paginationSASQProps}
121
123
  >
122
124
  {
123
125
  (sasqRenderProps) => {
124
126
  const {
125
- activeFilters,
126
- filterChanged,
127
- getFilterHandlers,
128
- getSearchHandlers,
129
- onSubmitSearch,
130
- resetAll,
131
- searchChanged,
132
- searchValue
133
- } = sasqRenderProps;
127
+ activeFilters,
128
+ filterChanged,
129
+ getFilterHandlers,
130
+ getSearchHandlers,
131
+ onSubmitSearch,
132
+ resetAll,
133
+ searchChanged,
134
+ searchValue
135
+ } = sasqRenderProps;
134
136
 
135
137
  const searchHandlers = getSearchHandlers();
136
138
  const disableReset = !filterChanged && !searchChanged;
@@ -143,7 +145,7 @@ const SASQLookupComponent = forwardRef((props, ref) => {
143
145
  filterPaneFirstMenu,
144
146
  filterPaneLastMenu,
145
147
  ...restOfFilterPaneProps
146
- } = filterPaneProps;
148
+ } = filterPaneProps;
147
149
  const {
148
150
  mainPaneFirstMenu,
149
151
  mainPaneLastMenu,
@@ -269,7 +271,7 @@ const SASQLookupComponent = forwardRef((props, ref) => {
269
271
  query={query}
270
272
  rowNavigation={rowNavigation}
271
273
  toggleFilterPane={toggleFilterPane}
272
- {...restOfInfiniteQueryProps}
274
+ {...restOfQueryProps}
273
275
  {...sasqRenderProps}
274
276
  /*
275
277
  * This is insane, it looks like SASQProps `initialSortState`
@@ -277,7 +279,16 @@ const SASQLookupComponent = forwardRef((props, ref) => {
277
279
  * sort handler. Passing through SASQProps.
278
280
  */
279
281
  {...sasqProps}
280
- {...props}
282
+ // pass down all props handed to us except mclProps (pass those down below with our extra prev/next goodies)
283
+ {
284
+ ...{
285
+ ...props,
286
+ mclProps: {
287
+ ...paginationMCLProps,
288
+ ...mclProps,
289
+ }
290
+ }
291
+ }
281
292
  />
282
293
  </Pane>
283
294
  {children}
@@ -0,0 +1,10 @@
1
+ export const NEXT = 'next';
2
+ export const PREV = 'prev';
3
+
4
+ // THIS SHOULD BE SET BY CALLING CODE, ONLY DEFAULTED TO AVOID DIVISION BY UNDEFINED
5
+ export const DEFAULT_PAGINATION_SIZE = 25;
6
+ // Only here because we need argument 3 from onNeedMoreData
7
+ // and want to avoid "magic number" sonarlint
8
+ export const MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX = 3;
9
+
10
+ export const DEFAULT_PAGE_KEY = 'defaultPageKey';
@@ -20,3 +20,5 @@ export { default as useMutateRefdataValue } from './useMutateRefdataValue';
20
20
  export { default as useMutateRefdataCategory } from './useMutateRefdataCategory';
21
21
  export { default as useMutateCustomProperties } from './useMutateCustomProperties';
22
22
  export { default as useMutateModConfigEntry } from './useMutateModConfigEntry';
23
+
24
+ export { default as usePrevNextPagination } from './usePrevNextPagination';
@@ -0,0 +1,18 @@
1
+ import { create } from 'zustand';
2
+ import { DEFAULT_PAGE_KEY } from '../constants/pagination';
3
+
4
+ // Any time that usePrevNextPagination is NOT synced to location,
5
+ // store a keyed currentPage here instead
6
+ const useLocalPageStore = create(
7
+ (set) => ({
8
+ pageStore: {},
9
+ setPage: (id, page) => set((state) => {
10
+ // Any non-id keyed pages will go into a single storage slot
11
+ const key = id ?? DEFAULT_PAGE_KEY;
12
+
13
+ return { ...state, pageStore: { ...state.pageStore, [key]: page } };
14
+ }),
15
+ }),
16
+ );
17
+
18
+ export default useLocalPageStore;
@@ -0,0 +1,203 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useLocation, useHistory } from 'react-router-dom';
3
+
4
+ import queryString from 'query-string';
5
+ import {
6
+ DEFAULT_PAGINATION_SIZE,
7
+ DEFAULT_PAGE_KEY,
8
+ MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX,
9
+ NEXT,
10
+ PREV
11
+ } from '../constants/pagination';
12
+
13
+ import useLocalPageStore from './useLocalPageStore';
14
+
15
+ // Currently there are several places in which this hook is used twice within the same component
16
+ // Once in order to get the current page, and again in order to handle changes to paginations
17
+ // This hook should be refactored in order to resolve these issue - @EthanFreestone
18
+ const usePrevNextPagination = ({
19
+ count = 0, // Only needed for reading back MCL props
20
+ defaultToPageOne = true, // A prop to allow the implementor to turn off the defaulting to page=1
21
+ pageSize = DEFAULT_PAGINATION_SIZE, // Only needed for reading back MCL props
22
+ id = DEFAULT_PAGE_KEY, // This id is ONLY used for syncToLocation: false cases, as a key to the zustand store
23
+ syncToLocation = true, // Used to turn on/off location syncing, so can be used as standalone state if required,
24
+ hasNextPage = null // Override for canGoNext, used in the case in which resources are fetched with no stats
25
+ } = {}) => {
26
+ /* ------ ZUSTAND STORE ------ */
27
+ // For NON-SYNC-TO-LOCATION use cases, store the currentPage in a keyed store
28
+ const pageStore = useLocalPageStore(state => state.pageStore);
29
+ const setPage = useLocalPageStore(state => state.setPage);
30
+
31
+ /* ------ CURRENTPAGE STATE ------ */
32
+ // Set up initialValue
33
+ const getInitialCurrentPage = useCallback(() => {
34
+ let initialCurrentPage;
35
+ if (!syncToLocation) {
36
+ if (pageStore[id]) {
37
+ initialCurrentPage = pageStore[id];
38
+ } else {
39
+ // Initialise store state
40
+ setPage(id, 1);
41
+ initialCurrentPage = 1;
42
+ }
43
+ }
44
+
45
+ return initialCurrentPage;
46
+ }, [id, pageStore, setPage, syncToLocation]);
47
+ // State itself
48
+ const [currentPage, setCurrentPage] = useState(getInitialCurrentPage());
49
+
50
+ const location = useLocation();
51
+ const history = useHistory();
52
+
53
+ /* ------ HANDLEPAGECHANGE ------ */
54
+ // Takes in a direction "prev" or "next" and performs the requisite logic to move
55
+ // currentPage state and/or zustand store state
56
+ const handlePageChange = useCallback((direction) => {
57
+ const urlQuery = queryString.parse(location.search);
58
+
59
+ let newPage;
60
+ if (direction === NEXT) {
61
+ newPage = currentPage + 1;
62
+ } else if (direction === PREV) {
63
+ newPage = currentPage - 1;
64
+ }
65
+
66
+ if (!syncToLocation) {
67
+ // We're manipulating the state directly in this case
68
+ // We're dealing with the zustand store in this case,
69
+ // change the store and the currentPage will update below
70
+ setPage(id, newPage);
71
+ setCurrentPage(newPage);
72
+ } else if (newPage !== urlQuery?.page) {
73
+ const newQuery = {
74
+ ...urlQuery,
75
+ page: newPage
76
+ };
77
+ history.push({
78
+ pathname: location.pathname,
79
+ search: `?${queryString.stringify(newQuery)}`
80
+ });
81
+ }
82
+ }, [
83
+ currentPage,
84
+ history,
85
+ id,
86
+ location.pathname,
87
+ location.search,
88
+ setPage,
89
+ syncToLocation
90
+ ]);
91
+
92
+
93
+
94
+ const [resetPageState, setResetPageState] = useState(false);
95
+ const resetPage = useCallback(() => {
96
+ if (syncToLocation) {
97
+ setResetPageState(true);
98
+ } else {
99
+ setPage(id, 1);
100
+ setCurrentPage(1);
101
+ }
102
+ }, [id, setPage, syncToLocation]);
103
+
104
+ useEffect(() => {
105
+ if (syncToLocation) {
106
+ const urlQuery = queryString.parse(location.search);
107
+
108
+ if (urlQuery?.page && currentPage !== urlQuery?.page) {
109
+ setCurrentPage(Number(urlQuery?.page));
110
+ } else if (!urlQuery?.page && defaultToPageOne) {
111
+ // If url query "page" is not yet set, set it to 1
112
+ setCurrentPage(1);
113
+ const newQuery = {
114
+ ...urlQuery,
115
+ page: 1
116
+ };
117
+
118
+ history.push({
119
+ pathname: location.pathname,
120
+ search: `?${queryString.stringify(newQuery)}`
121
+ });
122
+ }
123
+
124
+ if (resetPageState) {
125
+ const newQuery = {
126
+ ...urlQuery,
127
+ page: 1
128
+ };
129
+
130
+ history.push({
131
+ pathname: location.pathname,
132
+ search: `?${queryString.stringify(newQuery)}`
133
+ });
134
+ setResetPageState(false);
135
+ }
136
+ } else if (currentPage !== pageStore[id]) {
137
+ // Only do this when not syncing to location...
138
+ // If current page state is not what we have in the store, set current page state
139
+ setCurrentPage(pageStore[id]);
140
+ }
141
+ }, [
142
+ currentPage,
143
+ defaultToPageOne,
144
+ history,
145
+ id,
146
+ location,
147
+ pageStore,
148
+ resetPageState,
149
+ syncToLocation
150
+ ]);
151
+
152
+ // Set up MCL specific props based on page
153
+ const pagingCanGoNext = hasNextPage ?? (currentPage && (currentPage < Number(count) / pageSize));
154
+ const pagingCanGoPrevious = currentPage && Number(currentPage) > 1;
155
+ const pagingOffset = currentPage ? (currentPage - 1) * pageSize : 0;
156
+ const onNeedMoreData = (...args) => {
157
+ if (args[MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX]) {
158
+ handlePageChange(args[MCL_NEED_MORE_DATA_PREV_NEXT_ARG_INDEX]);
159
+ }
160
+ };
161
+
162
+ // Set up SASQ callback handling
163
+ // If extras are needed, these can be set up
164
+ // manually using resetPage per SASQ
165
+ const queryStateReducer = useCallback((_currState, nextState) => {
166
+ const resetPageEvents = [
167
+ 'clear.all',
168
+ 'reset.all',
169
+ 'filter.state',
170
+ 'filter.clearGroup',
171
+ 'sort.change',
172
+ 'search.reset',
173
+ 'search.submit'
174
+ ];
175
+
176
+ if (resetPageEvents.includes(nextState.changeType)) {
177
+ resetPage();
178
+ }
179
+
180
+ return nextState;
181
+ }, [resetPage]);
182
+
183
+ const paginationSASQProps = useMemo(() => ({
184
+ queryStateReducer
185
+ }), [queryStateReducer]);
186
+
187
+ return ({
188
+ currentPage,
189
+ handlePageChange,
190
+ paginationMCLProps: {
191
+ onNeedMoreData,
192
+ pagingCanGoNext,
193
+ pagingCanGoPrevious,
194
+ pagingOffset,
195
+ pagingType: 'prev-next'
196
+ },
197
+ paginationSASQProps,
198
+ queryStateReducer,
199
+ resetPage,
200
+ });
201
+ };
202
+
203
+ export default usePrevNextPagination;