@k-int/stripes-kint-components 5.10.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/es/index.js +12 -0
  3. package/es/lib/EditableRefdataList/EditableRefdataList.js +1 -1
  4. package/es/lib/SASQLookupComponent/SASQLookupComponent.js +38 -30
  5. package/es/lib/Tags/Tags.js +143 -0
  6. package/es/lib/Tags/Tags.test.js +80 -0
  7. package/es/lib/Tags/hooks/index.js +28 -0
  8. package/es/lib/Tags/hooks/useTags.js +15 -0
  9. package/es/lib/Tags/hooks/useTagsEnabled.js +25 -0
  10. package/es/lib/Tags/index.js +40 -0
  11. package/es/lib/Tags/tagsConfig.js +10 -0
  12. package/es/lib/constants/pagination.js +15 -0
  13. package/es/lib/hooks/index.js +7 -0
  14. package/es/lib/hooks/useHelperApp.js +18 -15
  15. package/es/lib/hooks/useLocalPageStore.js +25 -0
  16. package/es/lib/hooks/usePrevNextPagination.js +168 -0
  17. package/package.json +11 -10
  18. package/src/index.js +2 -0
  19. package/src/lib/EditableRefdataList/EditableRefdataList.js +5 -5
  20. package/src/lib/SASQLookupComponent/SASQLookupComponent.js +60 -49
  21. package/src/lib/Tags/Tags.js +145 -0
  22. package/src/lib/Tags/Tags.test.js +77 -0
  23. package/src/lib/Tags/hooks/index.js +2 -0
  24. package/src/lib/Tags/hooks/useTags.js +16 -0
  25. package/src/lib/Tags/hooks/useTagsEnabled.js +19 -0
  26. package/src/lib/Tags/index.js +4 -0
  27. package/src/lib/Tags/tagsConfig.js +16 -0
  28. package/src/lib/constants/pagination.js +10 -0
  29. package/src/lib/hooks/index.js +2 -0
  30. package/src/lib/hooks/useHelperApp.js +21 -13
  31. package/src/lib/hooks/useLocalPageStore.js +18 -0
  32. package/src/lib/hooks/usePrevNextPagination.js +203 -0
  33. package/test/helpers/test-implementor-translations.json +4 -1
@@ -0,0 +1,16 @@
1
+ import { useQuery } from 'react-query';
2
+ import { useOkapiKy } from '@folio/stripes/core';
3
+ import { defaultTagQuery, tagNamespaceArray } from '../tagsConfig';
4
+
5
+ const useTags = (namespaceArray, options) => {
6
+ const ky = useOkapiKy();
7
+ const nsArray = namespaceArray ?? tagNamespaceArray;
8
+
9
+ return useQuery(
10
+ nsArray,
11
+ () => ky.get(defaultTagQuery).json(),
12
+ options
13
+ );
14
+ };
15
+
16
+ export default useTags;
@@ -0,0 +1,19 @@
1
+ import { useOkapiKy } from '@folio/stripes/core';
2
+ import { useQuery } from 'react-query';
3
+ import { MOD_SETTINGS_ENDPOINT } from '../../constants/endpoints';
4
+
5
+ export const tagsEnabledQueryKey = [MOD_SETTINGS_ENDPOINT, 'query=(module==TAGS and configName==tags_enabled)', 'stripes-kint-components', 'useTagsEnabled'];
6
+
7
+ const useTagsEnabled = () => {
8
+ const ky = useOkapiKy();
9
+
10
+ const queryObject = useQuery(
11
+ tagsEnabledQueryKey,
12
+ () => ky.get(`${MOD_SETTINGS_ENDPOINT}?query=(module==TAGS and configName==tags_enabled)`).json()
13
+ );
14
+
15
+ const { data: { configs: { 0: { value } = {} } = [] } = {} } = queryObject;
16
+ return !value || value === 'true';
17
+ };
18
+
19
+ export default useTagsEnabled;
@@ -0,0 +1,4 @@
1
+ export { default as Tags } from './Tags';
2
+
3
+ export * from './tagsConfig';
4
+ export * from './hooks';
@@ -0,0 +1,16 @@
1
+ const tagNamespaceArray = ['tags', 'stripes-kint-components', 'Tags'];
2
+
3
+ const tagsPath = 'tags';
4
+ const defaultTagsParams = [
5
+ 'limit=1000',
6
+ 'query=cql.allRecords%3D1%20sortby%20label'
7
+ ];
8
+
9
+ const defaultTagQuery = `${tagsPath}?${defaultTagsParams?.join('&')}`;
10
+
11
+ export {
12
+ tagsPath,
13
+ defaultTagsParams,
14
+ defaultTagQuery,
15
+ tagNamespaceArray
16
+ };
@@ -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';
@@ -1,15 +1,16 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
  import { useHistory, useLocation } from 'react-router-dom';
3
3
 
4
4
  import queryString from 'query-string';
5
5
  import isEqual from 'lodash/isEqual';
6
6
 
7
- let helperObject = {};
8
-
9
7
  const useHelperApp = (helpers) => {
10
8
  const history = useHistory();
11
9
  const location = useLocation();
12
10
 
11
+ const [helperObject, setHelperObject] = useState({});
12
+ const [helperToggleFunctions, setHelperToggleFunctions] = useState({});
13
+
13
14
  const query = queryString.parse(location.search);
14
15
 
15
16
  const [currentHelper, setCurrentHelper] = useState(query?.helper);
@@ -22,9 +23,18 @@ const useHelperApp = (helpers) => {
22
23
  };
23
24
 
24
25
  useEffect(() => {
25
- // Keep object outside of hook to avoid redraw, oncly change when keys change
26
26
  if (!isEqual(Object.keys(helperObject), Object.keys(helpers))) {
27
- helperObject = helpers;
27
+ setHelperObject(helpers);
28
+ }
29
+
30
+ const newHelperToggleFunctions = {};
31
+ Object.keys(helperObject).forEach(h => {
32
+ newHelperToggleFunctions[h] = () => handleToggleHelper(h);
33
+ });
34
+
35
+ if (!isEqual(Object.keys(helperToggleFunctions), Object.keys(newHelperToggleFunctions))) {
36
+ // This makes sure adding/removing helpers changes the functions
37
+ setHelperToggleFunctions(newHelperToggleFunctions);
28
38
  }
29
39
 
30
40
  if (currentHelper !== query?.helper) {
@@ -37,11 +47,14 @@ const useHelperApp = (helpers) => {
37
47
  pathname: location.pathname,
38
48
  search: `?${queryString.stringify(newQuery)}`
39
49
  });
50
+
51
+ // When helper changes, reset helperToggleFunctions
52
+ setHelperToggleFunctions(newHelperToggleFunctions);
40
53
  }
41
- }, [currentHelper, helpers, history, location, query]);
54
+ }, [currentHelper, handleToggleHelper, helperObject, helperToggleFunctions, helpers, history, location, query]);
42
55
 
43
56
  // Set the HelperComponent
44
- const HelperComponent = useMemo(() => ((props) => {
57
+ const HelperComponent = useCallback((props) => {
45
58
  if (!query?.helper) return null;
46
59
 
47
60
  let Component = null;
@@ -56,13 +69,8 @@ const useHelperApp = (helpers) => {
56
69
  {...props}
57
70
  />
58
71
  );
59
- }), [handleToggleHelper, query.helper]);
72
+ }, [handleToggleHelper, helperObject, query?.helper]);
60
73
 
61
- // Set up the helperToggleFunctions
62
- const helperToggleFunctions = {};
63
- Object.keys(helperObject).forEach(h => {
64
- helperToggleFunctions[h] = () => handleToggleHelper(h);
65
- });
66
74
 
67
75
  return { currentHelper, HelperComponent, helperToggleFunctions, isOpen };
68
76
  };
@@ -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;
@@ -76,5 +76,8 @@
76
76
  "edit": "Edit",
77
77
  "save": "Save",
78
78
  "cancel": "Cancel",
79
- "apply": "Apply"
79
+ "apply": "Apply",
80
+ "tags": "Tags",
81
+ "numberOfTags": "{count, number} {count, plural, one {Tag} other {Tags}}",
82
+ "newTagCreated": "New tag created"
80
83
  }