@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,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.
|
|
3
|
+
"version": "5.12.0",
|
|
4
4
|
"description": "Stripes Component library for K-Int specific applications",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"*.css"
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"build:es": "rm -rf ./es && babel --extensions .js --ignore ./src/**/tests/**/* ./src --out-dir es",
|
|
14
14
|
"build": "yarn build:es",
|
|
15
15
|
"clean": "rm -rf ./node_modules ./*/node_modules ./yarn.lock && yarn install --ignore-scripts",
|
|
16
|
-
"clean-
|
|
16
|
+
"clean-install": "yarn clean && yarn install --ignore-scripts",
|
|
17
|
+
"clean-build": "yarn clean-install && yarn build",
|
|
17
18
|
"prepare": "yarn build",
|
|
18
19
|
"semantic-release": "semantic-release"
|
|
19
20
|
},
|
|
@@ -39,12 +40,12 @@
|
|
|
39
40
|
"@babel/preset-flow": "^7.18.6",
|
|
40
41
|
"@babel/preset-react": "^7.18.6",
|
|
41
42
|
"@babel/preset-typescript": "^7.18.6",
|
|
42
|
-
"@folio/eslint-config-stripes": "^7.0.0",
|
|
43
|
-
"@folio/jest-config-stripes": "^2.0.0",
|
|
44
|
-
"@folio/stripes": "^9.0.0",
|
|
45
|
-
"@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",
|
|
46
47
|
"@folio/stripes-erm-testing": "^2.0.0",
|
|
47
|
-
"@formatjs/cli": "^6.
|
|
48
|
+
"@formatjs/cli": "^6.6.0",
|
|
48
49
|
"@semantic-release/changelog": "^6.0.3",
|
|
49
50
|
"@semantic-release/git": "^10.0.1",
|
|
50
51
|
"@semantic-release/gitlab": "^12.0.6",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"prop-types-extra": ">=1.1.0",
|
|
64
65
|
"react": "^18.2.0",
|
|
65
66
|
"react-dom": "^18.2.0",
|
|
66
|
-
"react-intl": "^6.4.4",
|
|
67
|
+
"react-intl": "^6.4.4 || ^7.1.5",
|
|
67
68
|
"react-query": "^3.6.0",
|
|
68
69
|
"react-redux": "^9.0.0",
|
|
69
70
|
"react-router": "^5.2.0",
|
|
@@ -76,7 +77,7 @@
|
|
|
76
77
|
"sinon": "^18.0.0"
|
|
77
78
|
},
|
|
78
79
|
"peerDependencies": {
|
|
79
|
-
"@folio/stripes": "
|
|
80
|
+
"@folio/stripes": "^9.0.0 || ^10.0.0",
|
|
80
81
|
"final-form": ">=4.18.4",
|
|
81
82
|
"final-form-arrays": ">=3.0.1",
|
|
82
83
|
"lodash": ">=4.17.0",
|
|
@@ -85,7 +86,7 @@
|
|
|
85
86
|
"react": "*",
|
|
86
87
|
"react-final-form": ">=6.3.0",
|
|
87
88
|
"react-final-form-arrays": ">=3.1.0",
|
|
88
|
-
"react-intl": "
|
|
89
|
+
"react-intl": "^6.4.4 || ^7.1.5",
|
|
89
90
|
"react-query": ">=3.9.0",
|
|
90
91
|
"react-router-dom": ">=5.2.0"
|
|
91
92
|
},
|
package/src/index.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
117
|
-
|
|
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 {
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
78
|
-
...
|
|
79
|
-
} =
|
|
88
|
+
data = {},
|
|
89
|
+
...restOfQueryProps
|
|
90
|
+
} = useQuery(
|
|
80
91
|
queryNamespace,
|
|
81
|
-
|
|
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
|
|
88
|
-
...
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
{...
|
|
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
|
-
|
|
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,145 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
|
5
|
+
import { CalloutContext, useOkapiKy } from '@folio/stripes/core';
|
|
6
|
+
|
|
7
|
+
import { uniqBy, sortBy, difference } from 'lodash';
|
|
8
|
+
import { Pane } from '@folio/stripes/components';
|
|
9
|
+
|
|
10
|
+
import { TagsForm } from '@folio/stripes/smart-components';
|
|
11
|
+
|
|
12
|
+
import { tagNamespaceArray } from './tagsConfig';
|
|
13
|
+
import { useTags } from './hooks';
|
|
14
|
+
import { useKintIntl } from '../hooks';
|
|
15
|
+
|
|
16
|
+
const Tags = ({
|
|
17
|
+
invalidateLinks = [], // If there are other queries that need invalidating, pass those here
|
|
18
|
+
labelOverrides = {},
|
|
19
|
+
link,
|
|
20
|
+
onToggle,
|
|
21
|
+
intlKey: passedIntlKey,
|
|
22
|
+
intlNS: passedIntlNS,
|
|
23
|
+
}) => {
|
|
24
|
+
const kintIntl = useKintIntl(passedIntlKey, passedIntlNS);
|
|
25
|
+
|
|
26
|
+
const ky = useOkapiKy();
|
|
27
|
+
const callout = useContext(CalloutContext);
|
|
28
|
+
const queryClient = useQueryClient();
|
|
29
|
+
|
|
30
|
+
// TAG GET/POST
|
|
31
|
+
const { data: { tags = [] } = {} } = useTags(
|
|
32
|
+
[...tagNamespaceArray, link]
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// istanbul ignore next
|
|
36
|
+
const { mutateAsync: postTags } = useMutation(
|
|
37
|
+
['tags', 'stripes-erm-components', 'Tags', 'postTags'],
|
|
38
|
+
(data) => ky.post('tags', { json: data }).then(() => {
|
|
39
|
+
queryClient.invalidateQueries('tags');
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// ENTITY GET/PUT
|
|
44
|
+
const { data: entity } = useQuery(
|
|
45
|
+
[link, 'stripes-erm-components', 'Tags'],
|
|
46
|
+
() => ky.get(link).json()
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// istanbul ignore next
|
|
50
|
+
const { mutateAsync: putEntity } = useMutation(
|
|
51
|
+
[link, 'stripes-erm-components', 'Tags', 'putEntity'],
|
|
52
|
+
(data) => ky.put(link, { json: data }).then(() => {
|
|
53
|
+
queryClient.invalidateQueries(link);
|
|
54
|
+
if (invalidateLinks?.length) {
|
|
55
|
+
invalidateLinks.forEach(il => queryClient.invalidateQueries(il));
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// add tags to global list of tags
|
|
61
|
+
// istanbul ignore next
|
|
62
|
+
const saveTags = (tagsToSave) => {
|
|
63
|
+
const newTag = difference(tagsToSave.map(t => (t.value || t)), tags.map(t => t.label.toLowerCase()));
|
|
64
|
+
if (!newTag || !newTag.length) return;
|
|
65
|
+
|
|
66
|
+
postTags({
|
|
67
|
+
label: newTag[0],
|
|
68
|
+
description: newTag[0]
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
callout.sendCallout({
|
|
72
|
+
message: kintIntl.formatKintMessage({
|
|
73
|
+
id: 'newTagCreated',
|
|
74
|
+
overrideValue: labelOverrides.newTagCreated
|
|
75
|
+
})
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// add tag to the list of entity tags
|
|
80
|
+
// istanbul ignore next
|
|
81
|
+
const saveEntityTags = (tagsToSave) => {
|
|
82
|
+
const tagListMap = (entity?.tags ?? []).map(tag => ({ 'value': tag.value }));
|
|
83
|
+
const tagsMap = tagsToSave.map(tag => ({ 'value': tag.value || tag }));
|
|
84
|
+
|
|
85
|
+
const newTags = sortBy(uniqBy([...tagListMap, ...tagsMap], 'value'));
|
|
86
|
+
|
|
87
|
+
putEntity({
|
|
88
|
+
tags: newTags
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const onAdd = (addTags) => {
|
|
93
|
+
saveEntityTags(addTags);
|
|
94
|
+
saveTags(addTags);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const onRemove = (tag) => {
|
|
98
|
+
const tagToDelete = (entity?.tags ?? []).filter(t => t.value.toLowerCase() === tag.toLowerCase());
|
|
99
|
+
|
|
100
|
+
putEntity({
|
|
101
|
+
tags: [{ id: tagToDelete[0].id, _delete:true }]
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const entityTags = (entity?.tags ?? []).map(tag => tag.value.toLowerCase());
|
|
106
|
+
return (
|
|
107
|
+
<Pane
|
|
108
|
+
defaultWidth="20%"
|
|
109
|
+
dismissible
|
|
110
|
+
id="tags-helper-pane"
|
|
111
|
+
onClose={onToggle}
|
|
112
|
+
paneSub={kintIntl.formatKintMessage(
|
|
113
|
+
{
|
|
114
|
+
id: 'numberOfTags',
|
|
115
|
+
overrideValue: labelOverrides.numberOfTags
|
|
116
|
+
},
|
|
117
|
+
{ count: entity?.tags?.length ?? 0 }
|
|
118
|
+
)}
|
|
119
|
+
paneTitle={kintIntl.formatKintMessage(
|
|
120
|
+
{
|
|
121
|
+
id: 'tags',
|
|
122
|
+
overrideValue: labelOverrides.tags
|
|
123
|
+
}
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
<TagsForm
|
|
127
|
+
entityTags={entityTags}
|
|
128
|
+
onAdd={onAdd}
|
|
129
|
+
onRemove={onRemove}
|
|
130
|
+
tags={tags}
|
|
131
|
+
/>
|
|
132
|
+
</Pane>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
Tags.propTypes = {
|
|
137
|
+
intlKey: PropTypes.string,
|
|
138
|
+
intlNS: PropTypes.string,
|
|
139
|
+
invalidateLinks: PropTypes.arrayOf(PropTypes.string),
|
|
140
|
+
labelOverrides: PropTypes.object,
|
|
141
|
+
link: PropTypes.string,
|
|
142
|
+
onToggle: PropTypes.func
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default Tags;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
IconButton,
|
|
6
|
+
Pane,
|
|
7
|
+
PaneHeader
|
|
8
|
+
} from '@folio/stripes-erm-testing';
|
|
9
|
+
|
|
10
|
+
import Tags from './Tags';
|
|
11
|
+
import { renderWithKintHarness } from '../../../test/jest';
|
|
12
|
+
|
|
13
|
+
const onToggle = jest.fn();
|
|
14
|
+
const onAdd = jest.fn();
|
|
15
|
+
const link = 'erm/sas/14c16fc4-f986-4e60-aa59-4e627fcf160b';
|
|
16
|
+
|
|
17
|
+
describe('Tags', () => {
|
|
18
|
+
let renderComponent;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
renderComponent = renderWithKintHarness(
|
|
21
|
+
<MemoryRouter>
|
|
22
|
+
<Tags
|
|
23
|
+
invalidateLinks={[]}
|
|
24
|
+
link={link}
|
|
25
|
+
onAdd={onAdd}
|
|
26
|
+
onToggle={onToggle}
|
|
27
|
+
/>
|
|
28
|
+
</MemoryRouter>
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('renders the expected label', () => {
|
|
33
|
+
const { getByText } = renderComponent;
|
|
34
|
+
expect(getByText('0 Tags')).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('renders expected pane dismiss button ', async () => {
|
|
38
|
+
await IconButton('Close Tags').exists();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('renders expected open menu button ', () => {
|
|
42
|
+
const { getByRole } = renderComponent;
|
|
43
|
+
expect(
|
|
44
|
+
getByRole('button', {
|
|
45
|
+
name: 'stripes-components.multiSelection.dropdownTriggerLabel',
|
|
46
|
+
})
|
|
47
|
+
).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('renders tags heading ', () => {
|
|
51
|
+
const { getByRole } = renderComponent;
|
|
52
|
+
expect(getByRole('heading', { name: 'Tags' })).toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('renders expected region with zero tags', () => {
|
|
56
|
+
const { getByRole } = renderComponent;
|
|
57
|
+
expect(getByRole('region', { name: 'Tags 0 Tags' })).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('renders the expected multiSelectDescription', () => {
|
|
61
|
+
const { getByText } = renderComponent;
|
|
62
|
+
expect(getByText('Contains a list of any selected values, followed by an autocomplete textfield for selecting additional values.')).toBeInTheDocument();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('renders the expected label', () => {
|
|
66
|
+
const { getByText } = renderComponent;
|
|
67
|
+
expect(getByText('0 items selected')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('displays the tags pane', async () => {
|
|
71
|
+
await Pane('Tags').is({ visible: true });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('displays the tags pane header', async () => {
|
|
75
|
+
await PaneHeader('Tags').is({ visible: true });
|
|
76
|
+
});
|
|
77
|
+
});
|