@k-int/stripes-kint-components 5.22.0 → 5.24.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.
@@ -1,13 +1,16 @@
1
- import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
1
+ import {
2
+ forwardRef,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useMemo,
6
+ useState,
7
+ } from 'react';
2
8
 
3
9
  import PropTypes from 'prop-types';
4
10
  import { FormattedMessage } from 'react-intl';
5
11
  import { useQuery } from 'react-query';
6
12
 
7
- import {
8
- useNamespace,
9
- useOkapiKy
10
- } from '@folio/stripes/core';
13
+ import { useNamespace, useOkapiKy } from '@folio/stripes/core';
11
14
 
12
15
  import {
13
16
  CollapseFilterPaneButton,
@@ -25,7 +28,12 @@ import {
25
28
  } from '@folio/stripes/components';
26
29
 
27
30
  import { generateKiwtQuery } from '../utils';
28
- import { useKintIntl, useKiwtSASQuery, useLocalStorageState, usePrevNextPagination } from '../hooks';
31
+ import {
32
+ useKintIntl,
33
+ useKiwtSASQuery,
34
+ useLocalStorageState,
35
+ usePrevNextPagination,
36
+ } from '../hooks';
29
37
 
30
38
  import TableBody from './TableBody';
31
39
  import SearchKeyControl from '../SearchKeyControl';
@@ -41,7 +49,12 @@ const SASQLookupComponent = forwardRef((props, ref) => {
41
49
  intlKey: passedIntlKey,
42
50
  intlNS: passedIntlNS,
43
51
  labelOverrides = {},
44
- lookupQueryNamespaceGenerator = ({ currentPage, namespace, id: passedId, query }) => {
52
+ lookupQueryNamespaceGenerator = ({
53
+ currentPage,
54
+ namespace,
55
+ id: passedId,
56
+ query,
57
+ }) => {
45
58
  const queryNamespace = [namespace, 'SASQ'];
46
59
  if (passedId) {
47
60
  queryNamespace.push(passedId);
@@ -52,6 +65,8 @@ const SASQLookupComponent = forwardRef((props, ref) => {
52
65
 
53
66
  return queryNamespace;
54
67
  },
68
+ lookupQueryPromise = ({ endpoint, ky, queryParams }) => ky.get(`${endpoint}${queryParams}`).json(),
69
+ lookupResponseTransform = (response) => response, // Function to transform the response from the query
55
70
  mainPaneProps = {},
56
71
  mclProps = {},
57
72
  noResultsProps = {},
@@ -73,60 +88,87 @@ const SASQLookupComponent = forwardRef((props, ref) => {
73
88
  query: sasqPropsQuery, // If these are overriden we need to make use of them
74
89
  queryGetter: sasqPropsQueryGetter,
75
90
  querySetter: sasqPropsQuerySetter,
76
- syncToLocation = true
91
+ syncToLocation = true,
77
92
  } = sasqProps ?? {};
78
93
 
79
- const { currentPage, paginationMCLProps, paginationSASQProps } = usePrevNextPagination({
80
- count,
81
- pageSize: fetchParameters.SASQ_MAP?.perPage,
82
- syncToLocation
83
- });
94
+ // We manage our internal state of pagination (URL vs MCL page state) using usePrevNextPagination.
95
+ // Below the queryParameterGenerator can then choose to use the outputs (namely totalRecords and page) as it sees fit.
96
+ const { currentPage, paginationMCLProps, paginationSASQProps } =
97
+ usePrevNextPagination({
98
+ count, // totalRecord
99
+ pageSize: fetchParameters.SASQ_MAP?.perPage,
100
+ syncToLocation,
101
+ });
84
102
 
85
103
  const {
86
104
  query: kintSASQQuery,
87
105
  queryGetter: kintSASQQueryGetter,
88
- querySetter: kintSASQQuerySetter
106
+ querySetter: kintSASQQuerySetter,
89
107
  } = useKiwtSASQuery();
90
108
 
91
- const query = useMemo(() => sasqPropsQuery ?? kintSASQQuery, [sasqPropsQuery, kintSASQQuery]);
92
- const queryGetter = useMemo(() => sasqPropsQueryGetter ?? kintSASQQueryGetter, [sasqPropsQueryGetter, kintSASQQueryGetter]);
93
- const querySetter = useMemo(() => sasqPropsQuerySetter ?? kintSASQQuerySetter, [sasqPropsQuerySetter, kintSASQQuerySetter]);
109
+ const query = useMemo(
110
+ () => sasqPropsQuery ?? kintSASQQuery,
111
+ [sasqPropsQuery, kintSASQQuery]
112
+ );
113
+ const queryGetter = useMemo(
114
+ () => sasqPropsQueryGetter ?? kintSASQQueryGetter,
115
+ [sasqPropsQueryGetter, kintSASQQueryGetter]
116
+ );
117
+ const querySetter = useMemo(
118
+ () => sasqPropsQuerySetter ?? kintSASQQuerySetter,
119
+ [sasqPropsQuerySetter, kintSASQQuerySetter]
120
+ );
94
121
 
95
122
  const { 0: namespace } = useNamespace();
96
123
  const ky = useOkapiKy();
97
124
 
98
125
  const filterPaneVisibileKey = `${namespace}-${id}-filterPaneVisibility`;
99
126
 
100
- const queryParams = useMemo(() => (
101
- queryParameterGenerator(
102
- {
103
- ...fetchParameters.SASQ_MAP,
104
- page: currentPage,
105
- }, (query ?? {})
106
- )
107
- ), [currentPage, fetchParameters.SASQ_MAP, query, queryParameterGenerator]);
127
+ const queryParams = useMemo(
128
+ () => queryParameterGenerator(
129
+ // With Typescript this would be a type of SASQ_MAP, and so totalRecords is a valid property in our new shape
130
+ // In generateKiwtQueryParams we can choose to ignore totalRecords, which while being a valid property is not necessary for KIWT queries
131
+ {
132
+ page: currentPage,
133
+ totalRecords: count,
134
+ ...fetchParameters.SASQ_MAP,
135
+ },
136
+ query ?? {}
137
+ ),
138
+ [count, currentPage, fetchParameters.SASQ_MAP, query, queryParameterGenerator]
139
+ );
108
140
 
109
- const [filterPaneVisible, setFilterPaneVisible] = useLocalStorageState(filterPaneVisibileKey, true);
141
+ const [filterPaneVisible, setFilterPaneVisible] = useLocalStorageState(
142
+ filterPaneVisibileKey,
143
+ true
144
+ );
110
145
  const toggleFilterPane = () => setFilterPaneVisible(!filterPaneVisible);
111
146
 
112
- const {
113
- data = {},
114
- ...restOfQueryProps
115
- } = useQuery(
147
+ const { data = {}, ...restOfQueryProps } = useQuery(
116
148
  lookupQueryNamespaceGenerator({
117
149
  currentPage,
118
150
  endpoint: fetchParameters.endpoint,
119
151
  namespace,
120
152
  id,
121
153
  query,
122
- queryParams
154
+ queryParams,
123
155
  }),
124
156
  () => {
125
- return ky.get(`${fetchParameters.endpoint}${queryParams}`).json();
157
+ return lookupQueryPromise({
158
+ ky,
159
+ queryParams,
160
+ endpoint: fetchParameters.endpoint
161
+ });
126
162
  },
127
163
  {
128
164
  enabled: (!!query?.filters || !!query?.query) && !!currentPage,
129
- ...(fetchParameters.queryOptions ?? {})
165
+ ...(fetchParameters.queryOptions ?? {}),
166
+ // select is a parameter supported by useQuery to transform the response
167
+ // Could be possible to instead pass this down along with the queryOptions
168
+ // Additionally this is assuming the lookupResponseTransform is a func as opposed to an object
169
+ select: (selectData) => {
170
+ return lookupResponseTransform(selectData);
171
+ },
130
172
  }
131
173
  );
132
174
 
@@ -136,15 +178,13 @@ const SASQLookupComponent = forwardRef((props, ref) => {
136
178
  }
137
179
  }, [count, data.totalRecords]);
138
180
 
139
- useImperativeHandle(ref, () => (
140
- {
141
- lookupQueryProps: {
142
- data,
143
- ...restOfQueryProps
144
- },
145
- queryParams
146
- }
147
- ));
181
+ useImperativeHandle(ref, () => ({
182
+ lookupQueryProps: {
183
+ data,
184
+ ...restOfQueryProps,
185
+ },
186
+ queryParams,
187
+ }));
148
188
 
149
189
  return (
150
190
  <SearchAndSortQuery
@@ -155,231 +195,234 @@ const SASQLookupComponent = forwardRef((props, ref) => {
155
195
  {...sasqProps}
156
196
  {...paginationSASQProps}
157
197
  >
158
- {
159
- (sasqRenderProps) => {
160
- const {
161
- activeFilters,
162
- filterChanged,
163
- getFilterHandlers,
164
- getSearchHandlers,
165
- onSubmitSearch,
166
- resetAll,
167
- searchChanged,
168
- searchValue
169
- } = sasqRenderProps;
198
+ {(sasqRenderProps) => {
199
+ const {
200
+ activeFilters,
201
+ filterChanged,
202
+ getFilterHandlers,
203
+ getSearchHandlers,
204
+ onSubmitSearch,
205
+ resetAll,
206
+ searchChanged,
207
+ searchValue,
208
+ } = sasqRenderProps;
170
209
 
171
- const searchHandlers = getSearchHandlers();
172
- const disableReset = !filterChanged && !searchChanged;
210
+ const searchHandlers = getSearchHandlers();
211
+ const disableReset = !filterChanged && !searchChanged;
173
212
 
174
- const filterCount = activeFilters.string ? activeFilters.string.split(',').length : 0;
213
+ const filterCount = activeFilters.string
214
+ ? activeFilters.string.split(',').length
215
+ : 0;
175
216
 
176
- const Body = RenderBody ?? TableBody;
217
+ const Body = RenderBody ?? TableBody;
177
218
 
178
- const {
179
- filterPaneFirstMenu,
180
- filterPaneLastMenu,
181
- ...restOfFilterPaneProps
182
- } = filterPaneProps;
183
- const {
184
- mainPaneFirstMenu,
185
- mainPaneLastMenu,
186
- ...restOfMainPaneProps
187
- } = mainPaneProps;
219
+ const {
220
+ filterPaneFirstMenu,
221
+ filterPaneLastMenu,
222
+ ...restOfFilterPaneProps
223
+ } = filterPaneProps;
224
+ const { mainPaneFirstMenu, mainPaneLastMenu, ...restOfMainPaneProps } =
225
+ mainPaneProps;
188
226
 
189
- const internalStateProps = {
190
- activeFilters,
191
- filterCount,
192
- filterPaneVisible,
193
- searchChanged,
194
- searchValue,
195
- setFilterPaneVisible,
196
- toggleFilterPane
197
- };
227
+ const internalStateProps = {
228
+ activeFilters,
229
+ filterCount,
230
+ filterPaneVisible,
231
+ searchChanged,
232
+ searchValue,
233
+ setFilterPaneVisible,
234
+ toggleFilterPane,
235
+ };
198
236
 
199
- return (
200
- <PersistedPaneset
201
- appId={namespace}
202
- id={`${id}-paneset`}
203
- {...persistedPanesetProps}
204
- >
205
- {filterPaneVisible &&
206
- <Pane
207
- defaultWidth="20%"
208
- firstMenu={filterPaneFirstMenu ?
209
- filterPaneFirstMenu(internalStateProps) :
210
- null
211
- }
212
- id={`${id}-filter-pane`}
213
- lastMenu={filterPaneLastMenu ?
214
- filterPaneLastMenu(internalStateProps) :
237
+ const renderFirstMenu = () => {
238
+ if (mainPaneFirstMenu) {
239
+ return mainPaneFirstMenu(internalStateProps);
240
+ }
241
+
242
+ if (!filterPaneVisible) {
243
+ return (
244
+ <PaneMenu>
245
+ <ExpandFilterPaneButton
246
+ filterCount={filterCount}
247
+ onClick={toggleFilterPane}
248
+ />
249
+ </PaneMenu>
250
+ );
251
+ }
252
+ return null;
253
+ };
254
+
255
+ return (
256
+ <PersistedPaneset
257
+ appId={namespace}
258
+ id={`${id}-paneset`}
259
+ {...persistedPanesetProps}
260
+ >
261
+ {filterPaneVisible && (
262
+ <Pane
263
+ defaultWidth="20%"
264
+ firstMenu={
265
+ filterPaneFirstMenu
266
+ ? filterPaneFirstMenu(internalStateProps)
267
+ : null
268
+ }
269
+ id={`${id}-filter-pane`}
270
+ lastMenu={
271
+ filterPaneLastMenu ? (
272
+ filterPaneLastMenu(internalStateProps)
273
+ ) : (
215
274
  <PaneMenu>
216
275
  <CollapseFilterPaneButton onClick={toggleFilterPane} />
217
276
  </PaneMenu>
218
- }
219
- paneTitle={<FormattedMessage id="stripes-smart-components.searchAndFilter" />}
220
- {...restOfFilterPaneProps}
221
- >
222
- <form onSubmit={onSubmitSearch}>
223
- <FilterPaneHeaderComponent />
224
- {!noSearchField &&
225
- <>
226
- <SearchField
227
- ariaLabel={searchFieldAriaLabel}
228
- autoFocus
229
- id={`sasq-search-field-${id}`}
230
- name="query"
231
- onChange={e => {
232
- if (e.target?.value) {
233
- searchHandlers.query(e); // SASQ needs the whole event here
234
- } else {
235
- searchHandlers.reset();
236
- }
237
- }}
238
- onClear={searchHandlers.reset}
239
- value={searchValue.query}
240
- {...searchFieldProps}
241
- />
242
- {searchableIndexes?.length > 0 && (
243
- <SearchKeyControl options={searchableIndexes} />
244
- )}
245
- <Button
246
- buttonStyle="primary"
247
- disabled={!searchValue.query}
248
- fullWidth
249
- type="submit"
250
- >
251
- <FormattedMessage id="stripes-smart-components.search" />
252
- </Button>
253
- <Button
254
- buttonStyle="none"
255
- disabled={disableReset}
256
- id="clickable-reset-all"
257
- onClick={resetAll}
258
- >
259
- <Icon icon="times-circle-solid">
260
- <FormattedMessage id="stripes-smart-components.resetAll" />
261
- </Icon>
262
- </Button>
263
- </>
264
- }
265
- <FilterComponent
266
- activeFilters={activeFilters.state}
267
- filterChanged={filterChanged}
268
- filterHandlers={getFilterHandlers()}
269
- resetAll={resetAll}
270
- searchChanged={searchChanged}
271
- searchHandlers={getSearchHandlers()}
272
- searchValue={searchValue}
273
- />
274
- </form>
275
- </Pane>
276
- }
277
- <Pane
278
- defaultWidth="fill"
279
- firstMenu={mainPaneFirstMenu ?
280
- mainPaneFirstMenu(internalStateProps) :
281
- !filterPaneVisible ?
282
- <PaneMenu>
283
- <ExpandFilterPaneButton filterCount={filterCount} onClick={toggleFilterPane} />
284
- </PaneMenu> :
285
- null
277
+ )
286
278
  }
287
- id={`${id}-main-pane`}
288
- lastMenu={mainPaneLastMenu ?
289
- mainPaneLastMenu(internalStateProps) :
290
- null
279
+ paneTitle={
280
+ <FormattedMessage id="stripes-smart-components.searchAndFilter" />
291
281
  }
292
- noOverflow
293
- padContent={false}
294
- paneSub={
295
- kintIntl.formatKintMessage({
296
- id: 'found#Values',
297
- overrideValue: labelOverrides?.foundValues
298
- }, { total: data?.total })
299
- }
300
- {...restOfMainPaneProps}
282
+ {...restOfFilterPaneProps}
301
283
  >
302
- <Body
303
- data={data}
304
- filterPaneVisible={filterPaneVisible}
305
- intlKey={passedIntlKey}
306
- intlNS={passedIntlNS}
307
- labelOverrides={labelOverrides}
308
- noResultsProps={noResultsProps}
309
- query={query}
310
- rowNavigation={rowNavigation}
311
- toggleFilterPane={toggleFilterPane}
312
- {...restOfQueryProps}
313
- {...sasqRenderProps}
314
- /*
315
- * This is insane, it looks like SASQProps `initialSortState`
316
- * needs to be passed through to MCL, as it relies on MCL calling the initial
317
- * sort handler. Passing through SASQProps.
318
- */
319
- {...sasqProps}
320
- // pass down all props handed to us except mclProps (pass those down below with our extra prev/next goodies)
321
- {
322
- ...{
323
- ...props,
324
- mclProps: {
325
- ...paginationMCLProps,
326
- ...mclProps,
327
- }
328
- }
329
- }
330
- />
284
+ <form onSubmit={onSubmitSearch}>
285
+ <FilterPaneHeaderComponent />
286
+ {!noSearchField && (
287
+ <>
288
+ <SearchField
289
+ ariaLabel={searchFieldAriaLabel}
290
+ autoFocus
291
+ id={`sasq-search-field-${id}`}
292
+ name="query"
293
+ onChange={(e) => {
294
+ if (e.target?.value) {
295
+ searchHandlers.query(e); // SASQ needs the whole event here
296
+ } else {
297
+ searchHandlers.reset();
298
+ }
299
+ }}
300
+ onClear={searchHandlers.reset}
301
+ value={searchValue.query}
302
+ {...searchFieldProps}
303
+ />
304
+ {searchableIndexes?.length > 0 && (
305
+ <SearchKeyControl options={searchableIndexes} />
306
+ )}
307
+ <Button
308
+ buttonStyle="primary"
309
+ disabled={!searchValue.query}
310
+ fullWidth
311
+ type="submit"
312
+ >
313
+ <FormattedMessage id="stripes-smart-components.search" />
314
+ </Button>
315
+ <Button
316
+ buttonStyle="none"
317
+ disabled={disableReset}
318
+ id="clickable-reset-all"
319
+ onClick={resetAll}
320
+ >
321
+ <Icon icon="times-circle-solid">
322
+ <FormattedMessage id="stripes-smart-components.resetAll" />
323
+ </Icon>
324
+ </Button>
325
+ </>
326
+ )}
327
+ <FilterComponent
328
+ activeFilters={activeFilters.state}
329
+ filterChanged={filterChanged}
330
+ filterHandlers={getFilterHandlers()}
331
+ resetAll={resetAll}
332
+ searchChanged={searchChanged}
333
+ searchHandlers={getSearchHandlers()}
334
+ searchValue={searchValue}
335
+ />
336
+ </form>
331
337
  </Pane>
332
- {children}
333
- </PersistedPaneset>
334
- );
335
- }
336
- }
338
+ )}
339
+ <Pane
340
+ defaultWidth="fill"
341
+ firstMenu={renderFirstMenu()}
342
+ id={`${id}-main-pane`}
343
+ lastMenu={
344
+ mainPaneLastMenu ? mainPaneLastMenu(internalStateProps) : null
345
+ }
346
+ noOverflow
347
+ padContent={false}
348
+ paneSub={kintIntl.formatKintMessage(
349
+ {
350
+ id: 'found#Values',
351
+ overrideValue: labelOverrides?.foundValues,
352
+ },
353
+ { total: data?.total }
354
+ )}
355
+ {...restOfMainPaneProps}
356
+ >
357
+ <Body
358
+ data={data}
359
+ filterPaneVisible={filterPaneVisible}
360
+ intlKey={passedIntlKey}
361
+ intlNS={passedIntlNS}
362
+ labelOverrides={labelOverrides}
363
+ noResultsProps={noResultsProps}
364
+ query={query}
365
+ rowNavigation={rowNavigation}
366
+ toggleFilterPane={toggleFilterPane}
367
+ {...restOfQueryProps}
368
+ {...sasqRenderProps}
369
+ /*
370
+ * This is insane, it looks like SASQProps `initialSortState`
371
+ * needs to be passed through to MCL, as it relies on MCL calling the initial
372
+ * sort handler. Passing through SASQProps.
373
+ */
374
+ {...sasqProps}
375
+ // pass down all props handed to us except mclProps (pass those down below with our extra prev/next goodies)
376
+ {...{
377
+ ...props,
378
+ mclProps: {
379
+ ...paginationMCLProps,
380
+ ...mclProps,
381
+ },
382
+ }}
383
+ />
384
+ </Pane>
385
+ {children}
386
+ </PersistedPaneset>
387
+ );
388
+ }}
337
389
  </SearchAndSortQuery>
338
390
  );
339
391
  });
340
392
 
341
393
  SASQLookupComponent.propTypes = {
342
- children: PropTypes.oneOfType([
343
- PropTypes.func,
344
- PropTypes.node
345
- ]),
394
+ children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
346
395
  fetchParameters: PropTypes.object,
347
396
  filterPaneProps: PropTypes.object,
348
- FilterComponent: PropTypes.oneOfType([
349
- PropTypes.func,
350
- PropTypes.node
351
- ]),
397
+ FilterComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
352
398
  FilterPaneHeaderComponent: PropTypes.oneOfType([
353
399
  PropTypes.func,
354
- PropTypes.node
400
+ PropTypes.node,
355
401
  ]),
356
402
  queryParameterGenerator: PropTypes.func,
357
- history: PropTypes.object,
358
403
  id: PropTypes.string.isRequired,
359
404
  intlKey: PropTypes.string,
360
405
  intlNS: PropTypes.string,
361
406
  labelOverrides: PropTypes.object,
362
- location: PropTypes.object,
407
+ lookupQueryNamespaceGenerator: PropTypes.func,
408
+ lookupQueryPromise: PropTypes.func,
409
+ lookupResponseTransform: PropTypes.func,
363
410
  mainPaneProps: PropTypes.object,
364
- match: PropTypes.object,
365
411
  mclProps: PropTypes.object,
412
+ noResultsProps: PropTypes.object,
366
413
  noSearchField: PropTypes.bool,
367
- path: PropTypes.string.isRequired,
368
414
  persistedPanesetProps: PropTypes.object,
369
- RenderBody: PropTypes.oneOfType([
370
- PropTypes.func,
371
- PropTypes.node
372
- ]),
373
- resource: PropTypes.object,
374
- resultColumns: PropTypes.arrayOf(PropTypes.object),
415
+ RenderBody: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
375
416
  rowNavigation: PropTypes.bool,
376
417
  sasqProps: PropTypes.object,
377
- searchableIndexes: PropTypes.arrayOf(PropTypes.shape({
378
- key: PropTypes.string.isRequired,
379
- label: PropTypes.string
380
- })),
418
+ searchableIndexes: PropTypes.arrayOf(
419
+ PropTypes.shape({
420
+ key: PropTypes.string.isRequired,
421
+ label: PropTypes.string,
422
+ })
423
+ ),
381
424
  searchFieldAriaLabel: PropTypes.string,
382
- searchFieldProps: PropTypes.object
425
+ searchFieldProps: PropTypes.object,
383
426
  };
384
427
 
385
428
  export default SASQLookupComponent;