@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.
- package/CHANGELOG.md +15 -0
- package/es/index.js +12 -0
- package/es/lib/EditableRefdataList/EditableRefdataList.js +1 -1
- package/es/lib/SASQLookupComponent/SASQLookupComponent.js +38 -30
- package/es/lib/Tags/Tags.js +143 -0
- package/es/lib/Tags/Tags.test.js +80 -0
- package/es/lib/Tags/hooks/index.js +28 -0
- package/es/lib/Tags/hooks/useTags.js +15 -0
- package/es/lib/Tags/hooks/useTagsEnabled.js +25 -0
- package/es/lib/Tags/index.js +40 -0
- package/es/lib/Tags/tagsConfig.js +10 -0
- package/es/lib/constants/pagination.js +15 -0
- package/es/lib/hooks/index.js +7 -0
- package/es/lib/hooks/useHelperApp.js +18 -15
- package/es/lib/hooks/useLocalPageStore.js +25 -0
- package/es/lib/hooks/usePrevNextPagination.js +168 -0
- package/package.json +11 -10
- package/src/index.js +2 -0
- package/src/lib/EditableRefdataList/EditableRefdataList.js +5 -5
- package/src/lib/SASQLookupComponent/SASQLookupComponent.js +60 -49
- package/src/lib/Tags/Tags.js +145 -0
- package/src/lib/Tags/Tags.test.js +77 -0
- package/src/lib/Tags/hooks/index.js +2 -0
- package/src/lib/Tags/hooks/useTags.js +16 -0
- package/src/lib/Tags/hooks/useTagsEnabled.js +19 -0
- package/src/lib/Tags/index.js +4 -0
- package/src/lib/Tags/tagsConfig.js +16 -0
- package/src/lib/constants/pagination.js +10 -0
- package/src/lib/hooks/index.js +2 -0
- package/src/lib/hooks/useHelperApp.js +21 -13
- package/src/lib/hooks/useLocalPageStore.js +18 -0
- package/src/lib/hooks/usePrevNextPagination.js +203 -0
- 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,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';
|
package/src/lib/hooks/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
}
|
|
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;
|