@k-int/stripes-kint-components 5.14.0 → 5.16.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,19 @@
1
+ # [5.16.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.15.0...v5.16.0) (2025-03-14)
2
+
3
+
4
+ ### Features
5
+
6
+ * Added ability for SASQLookupComponent to accept sasqProps.syncToLocation/query(Getter/Setter) and have that impact on results ([351b503](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/351b50313e0a320191b24fa1df6227103021f106))
7
+ * SASQRoute allow tweaking of SASQLookupComponent and SASQViewComponent component directly in SASQRoute ([6c812b2](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/6c812b2c4c10a049b8a9e56e624cc3dceee3a8fe))
8
+ * SASQRoute override paths for lookup/view ([994b976](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/994b976cbec3af6382066bc6bf6dd945d4a3e239))
9
+
10
+ # [5.15.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.14.0...v5.15.0) (2025-02-21)
11
+
12
+
13
+ ### Features
14
+
15
+ * useSettings sections ([0c062bf](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/0c062bf203811078371820b5106b3106982845ad))
16
+
1
17
  # [5.14.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.13.0...v5.14.0) (2025-02-19)
2
18
 
3
19
 
@@ -34,25 +34,26 @@ const NoResultsMessage = _ref => {
34
34
  searchTerm
35
35
  }
36
36
  });
37
-
38
- // No search term but no results
39
- if (!searchTerm) {
37
+ if (!isLoading && !searchTerm) {
38
+ // No search term and not loading
39
+ icon = filterPaneIsVisible ? 'arrow-left' : null;
40
+ label = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactIntl.FormattedMessage, {
41
+ id: "stripes-smart-components.sas.noResults.noTerms"
42
+ });
43
+ } else if (!isLoading) {
44
+ // Search term but not loading
40
45
  icon = 'search';
41
46
  label = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactIntl.FormattedMessage, {
42
47
  id: "stripes-smart-components.sas.noResults.noResults"
43
48
  });
44
- }
45
-
46
- // Loading results
47
- if (isLoading) {
49
+ } else if (isLoading) {
50
+ // Loading
48
51
  icon = 'spinner-ellipsis';
49
52
  label = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactIntl.FormattedMessage, {
50
53
  id: "stripes-smart-components.sas.noResults.loading"
51
54
  });
52
- }
53
-
54
- // Request failure
55
- if (isError) {
55
+ } else if (isError) {
56
+ // Request failure
56
57
  icon = 'exclamation-circle';
57
58
  label = error?.message;
58
59
  }
@@ -27,10 +27,12 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
27
27
  intlKey: passedIntlKey,
28
28
  intlNS: passedIntlNS,
29
29
  labelOverrides = {},
30
- mainPaneProps,
30
+ mainPaneProps = {},
31
31
  mclProps = {},
32
32
  noSearchField,
33
33
  persistedPanesetProps = {},
34
+ queryParameterGenerator = _utils.generateKiwtQuery,
35
+ // Expects a function which accepts SASQ_MAP and nsValues
34
36
  RenderBody,
35
37
  rowNavigation = true,
36
38
  // Default navigation onRowClick
@@ -40,28 +42,41 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
40
42
  } = props;
41
43
  const kintIntl = (0, _hooks.useKintIntl)(passedIntlKey, passedIntlNS);
42
44
  const [count, setCount] = (0, _react.useState)(0);
45
+
46
+ // Grab things we might need from sasqProps if passed
47
+ const {
48
+ query: sasqPropsQuery,
49
+ // If these are overriden we need to make use of them
50
+ queryGetter: sasqPropsQueryGetter,
51
+ querySetter: sasqPropsQuerySetter,
52
+ syncToLocation = true
53
+ } = sasqProps ?? {};
43
54
  const {
44
55
  currentPage,
45
56
  paginationMCLProps,
46
57
  paginationSASQProps
47
58
  } = (0, _hooks.usePrevNextPagination)({
48
59
  count,
49
- pageSize: fetchParameters.SASQ_MAP?.perPage
60
+ pageSize: fetchParameters.SASQ_MAP?.perPage,
61
+ syncToLocation
50
62
  });
51
63
  const {
52
- query,
53
- queryGetter,
54
- querySetter
64
+ query: kintSASQQuery,
65
+ queryGetter: kintSASQQueryGetter,
66
+ querySetter: kintSASQQuerySetter
55
67
  } = (0, _hooks.useKiwtSASQuery)();
68
+ const query = (0, _react.useMemo)(() => sasqPropsQuery ?? kintSASQQuery, [sasqPropsQuery, kintSASQQuery]);
69
+ const queryGetter = (0, _react.useMemo)(() => sasqPropsQueryGetter ?? kintSASQQueryGetter, [sasqPropsQueryGetter, kintSASQQueryGetter]);
70
+ const querySetter = (0, _react.useMemo)(() => sasqPropsQuerySetter ?? kintSASQQuerySetter, [sasqPropsQuerySetter, kintSASQQuerySetter]);
56
71
  const {
57
72
  0: namespace
58
73
  } = (0, _core.useNamespace)();
59
74
  const ky = (0, _core.useOkapiKy)();
60
75
  const filterPaneVisibileKey = `${namespace}-${id}-filterPaneVisibility`;
61
- const queryParams = (0, _react.useMemo)(() => (0, _utils.generateKiwtQuery)({
76
+ const queryParams = (0, _react.useMemo)(() => queryParameterGenerator({
62
77
  ...fetchParameters.SASQ_MAP,
63
78
  page: currentPage
64
- }, query ?? {}), [currentPage, fetchParameters.SASQ_MAP, query]);
79
+ }, query ?? {}), [currentPage, fetchParameters.SASQ_MAP, query, queryParameterGenerator]);
65
80
  const [filterPaneVisible, setFilterPaneVisible] = (0, _hooks.useLocalStorageState)(filterPaneVisibileKey, true);
66
81
  const toggleFilterPane = () => setFilterPaneVisible(!filterPaneVisible);
67
82
  const queryNamespace = [namespace, 'SASQ'];
@@ -97,6 +112,7 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
97
112
  },
98
113
  queryGetter: queryGetter,
99
114
  querySetter: querySetter,
115
+ syncToLocation: syncToLocation,
100
116
  ...sasqProps,
101
117
  ...paginationSASQProps,
102
118
  children: sasqRenderProps => {
@@ -246,6 +262,7 @@ SASQLookupComponent.propTypes = {
246
262
  filterPaneProps: _propTypes.default.object,
247
263
  FilterComponent: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]),
248
264
  FilterPaneHeaderComponent: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]),
265
+ queryParameterGenerator: _propTypes.default.func,
249
266
  history: _propTypes.default.object,
250
267
  id: _propTypes.default.string.isRequired,
251
268
  intlKey: _propTypes.default.string,
@@ -15,7 +15,11 @@ const SASQRoute = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
15
15
  let {
16
16
  children,
17
17
  fetchParameters,
18
+ getPathLookup = path => `${path}/:id?`,
19
+ getPathView = path => `${path}/:id`,
20
+ SASQLookupComponent: RouteLookupComponent = _SASQLookupComponent.SASQLookupComponent,
18
21
  path,
22
+ SASQViewComponent: RouteViewComponent = _SASQViewComponent.default,
19
23
  ...props
20
24
  } = _ref;
21
25
  // Grab the SASQ_MAP and tweak it
@@ -36,9 +40,9 @@ const SASQRoute = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
36
40
  // Reinsert the SASQ_MAP
37
41
  fetchParameters.SASQ_MAP = SASQ_MAP;
38
42
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRouterDom.Route, {
39
- path: `${path}/:id?`,
43
+ path: getPathLookup(path),
40
44
  render: routeProps => {
41
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SASQLookupComponent.SASQLookupComponent, {
45
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteLookupComponent, {
42
46
  ref: lookupRef,
43
47
  ...routeProps,
44
48
  fetchParameters: fetchParameters,
@@ -46,8 +50,8 @@ const SASQRoute = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
46
50
  ...props,
47
51
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactRouterDom.Switch, {
48
52
  children: [children, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRouterDom.Route, {
49
- path: `${path}/:id`,
50
- render: innerProps => /*#__PURE__*/(0, _jsxRuntime.jsx)(_SASQViewComponent.default, {
53
+ path: getPathView(path),
54
+ render: innerProps => /*#__PURE__*/(0, _jsxRuntime.jsx)(RouteViewComponent, {
51
55
  ref: viewRef,
52
56
  ...innerProps,
53
57
  fetchParameters: fetchParameters,
@@ -64,7 +68,11 @@ const SASQRoute = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
64
68
  SASQRoute.propTypes = {
65
69
  children: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.element, _propTypes.default.func]),
66
70
  fetchParameters: _propTypes.default.object,
71
+ getPathLookup: _propTypes.default.func,
72
+ getPathView: _propTypes.default.func,
67
73
  path: _propTypes.default.string,
74
+ SASQLookupComponent: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]),
75
+ SASQViewComponent: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]),
68
76
  ViewComponent: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node])
69
77
  };
70
78
  var _default = exports.default = SASQRoute;
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _react = _interopRequireDefault(require("react"));
7
+ var _react = _interopRequireWildcard(require("react"));
8
8
  var _propTypes = _interopRequireDefault(require("prop-types"));
9
9
  var _reactQuery = require("react-query");
10
10
  var _core = require("@folio/stripes/core");
@@ -15,97 +15,213 @@ var _utils = require("../utils");
15
15
  var _hooks = require("../hooks");
16
16
  var _jsxRuntime = require("react/jsx-runtime");
17
17
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
- const useSettings = _ref => {
19
- let {
18
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
19
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
20
+ // TODO work underway to make the settings hook more useful when trying to render multiple sections at a time
21
+ const useSettings = function () {
22
+ let settingsProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
23
+ const {
20
24
  allowGlobalEdit = true,
21
25
  dynamicPageExclusions,
22
26
  intlKey: passedIntlKey,
23
27
  intlNS: passedIntlNS,
24
28
  labelOverrides = {},
25
- persistentPages,
29
+ pages,
30
+ // If this is present, act completely as a passthrough either in sections OR standalone
31
+ persistentPages = [],
32
+ preventQueries = false,
26
33
  refdataEndpoint,
34
+ renderSingleSection = false,
35
+ // IF SECTIONS == NULL, this prop allows the implementor to render the pageList as a section with label etc
36
+ sectionPermission,
37
+ // If we're rendering sections, this prevents the rendering of said section (works as fallback or prevention for single section)
38
+ sectionRoute,
39
+ sections,
40
+ // if present, should be an array of objects with the SAME shape as the overall props here.
41
+ // If a prop isn't present it can fall back to the "global" prop.
27
42
  settingEndpoint,
28
43
  templateEndpoint
29
- } = _ref;
44
+ } = settingsProps;
30
45
  const ky = (0, _core.useOkapiKy)();
46
+ const stripes = (0, _core.useStripes)();
31
47
  const kintIntl = (0, _hooks.useKintIntl)(passedIntlKey, passedIntlNS);
32
- const queryParams = (0, _utils.generateKiwtQueryParams)({
33
- filters: dynamicPageExclusions?.map(dpe => ({
34
- path: 'section',
35
- comparator: '!=',
36
- value: dpe
37
- })),
38
- perPage: 100,
39
- stats: false
40
- }, {});
41
- const {
42
- data: appSettings = [],
43
- isLoading
44
- } = (0, _reactQuery.useQuery)(['stripes-kint-components', 'useSettings', 'appSettings', settingEndpoint, queryParams], () => ky(`${settingEndpoint}?${queryParams?.join('&')}`).json());
45
- const sections = [...new Set(appSettings.map(s => s.section))];
46
- const dynamic = sections.map(section => {
48
+
49
+ // Set up array of queryParams, pageLists etc etc ready for us to do all the work in a single place
50
+
51
+ const sectionMetaArray = (0, _react.useMemo)(() => {
52
+ const array = [];
53
+ const getSectionQueryProps = sectionProps => {
54
+ const dqp = sectionProps.dynamicPageExclusions ?? dynamicPageExclusions;
55
+ const queryParams = (0, _utils.generateKiwtQueryParams)({
56
+ filters: dqp?.map(dpe => ({
57
+ path: 'section',
58
+ comparator: '!=',
59
+ value: dpe
60
+ })),
61
+ perPage: 100,
62
+ stats: false
63
+ }, {});
64
+ const finalSettingEndpoint = sectionProps.settingEndpoint ?? settingEndpoint;
65
+ const queryKey = ['stripes-kint-components', 'useSettings', 'appSettings', finalSettingEndpoint, queryParams];
66
+ const queryFn = () => ky(`${finalSettingEndpoint}?${queryParams?.join('&')}`).json();
67
+ return {
68
+ queryParams,
69
+ queryKey,
70
+ queryFn
71
+ };
72
+ };
73
+ if ((sections?.length ?? 0) > 0) {
74
+ array.push(...sections.map(section => {
75
+ const querySectionProps = getSectionQueryProps(section);
76
+ return {
77
+ ...settingsProps,
78
+ // ensure these fall through to section based props
79
+ ...querySectionProps,
80
+ ...section // return original section object
81
+ };
82
+ }));
83
+ } else {
84
+ const querySectionProps = getSectionQueryProps(settingsProps);
85
+ array.push({
86
+ ...settingsProps,
87
+ ...querySectionProps
88
+ });
89
+ }
90
+ return array;
91
+ }, [dynamicPageExclusions, ky, sections, settingEndpoint, settingsProps]);
92
+ const queries = (0, _react.useMemo)(() => sectionMetaArray.map(sma => {
93
+ let enabled = true;
94
+ if (sma.pages || sma.preventQueries || pages || preventQueries) {
95
+ enabled = false;
96
+ }
47
97
  return {
48
- route: section,
49
- label: kintIntl.formatKintMessage({
50
- id: `settings.settingsSection.${(0, _utils.toCamelCase)(section)}`,
51
- fallbackMessage: section
52
- }),
53
- component: props => /*#__PURE__*/(0, _jsxRuntime.jsx)(_SettingPage.SettingPagePane, {
54
- intlKey: passedIntlKey,
55
- intlNS: passedIntlNS,
56
- sectionName: section,
57
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_SettingPage.SettingPage, {
58
- allowEdit: allowGlobalEdit,
98
+ queryKey: sma.queryKey,
99
+ queryFn: sma.queryFn,
100
+ enabled
101
+ };
102
+ }) ?? [], [pages, preventQueries, sectionMetaArray]);
103
+ const queryReturnArray = (0, _reactQuery.useQueries)(queries);
104
+ const [isLoading, setIsLoading] = (0, _react.useState)(true);
105
+ const getDynamicPages = (0, _react.useCallback)((qra, sma) => {
106
+ const pagesFromQueryReturn = [...new Set(qra.data?.map(s => s.section))];
107
+ const dynamic = pagesFromQueryReturn.map(page => {
108
+ const finalSectionRoute = sma.sectionRoute ?? sectionRoute;
109
+ const pageRoute = (finalSectionRoute ? `${finalSectionRoute}/` : '') + page;
110
+ return {
111
+ route: pageRoute,
112
+ label: kintIntl.formatKintMessage({
113
+ id: `settings.settingsSection.${(0, _utils.toCamelCase)(page)}`,
114
+ fallbackMessage: page
115
+ }),
116
+ component: props => /*#__PURE__*/(0, _jsxRuntime.jsx)(_SettingPage.SettingPagePane, {
59
117
  intlKey: passedIntlKey,
60
118
  intlNS: passedIntlNS,
61
- labelOverrides: labelOverrides,
62
- sectionName: section,
63
- ...props
119
+ sectionName: page,
120
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_SettingPage.SettingPage, {
121
+ allowEdit: allowGlobalEdit,
122
+ intlKey: passedIntlKey,
123
+ intlNS: passedIntlNS,
124
+ labelOverrides: labelOverrides,
125
+ sectionName: page,
126
+ ...props
127
+ })
64
128
  })
65
- })
66
- };
67
- });
68
- const pageList = persistentPages.concat(dynamic).sort(_utils.sortByLabel);
69
- const intlKey = (0, _hooks.useIntlKey)(passedIntlKey, passedIntlNS);
70
- const SettingsContextProvider = _ref2 => {
71
- let {
72
- children
73
- } = _ref2;
74
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_contexts.SettingsContext.Provider, {
75
- value: {
76
- intlKey,
77
- // This is only here for backwards compatibility... is now grabbed from useIntlKey instead of what's passed in directly
78
- refdataEndpoint,
79
- settingEndpoint,
80
- templateEndpoint
81
- },
82
- children: children
129
+ };
83
130
  });
84
- };
85
- SettingsContextProvider.propTypes = {
86
- children: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node])
87
- };
131
+ return {
132
+ pages: pagesFromQueryReturn,
133
+ dynamic
134
+ };
135
+ }, [allowGlobalEdit, kintIntl, labelOverrides, passedIntlKey, passedIntlNS, sectionRoute]);
136
+ (0, _react.useEffect)(() => {
137
+ // Handle loading
138
+ if (queryReturnArray.length > 0 && queryReturnArray.every(qra => qra.isLoading === false) && isLoading === true) {
139
+ setIsLoading(false);
140
+ }
141
+ }, [isLoading, queryReturnArray]);
142
+ const finalSections = (0, _react.useMemo)(() => {
143
+ return sectionMetaArray.map((sma, idx) => {
144
+ const SettingsContextProvider = _ref => {
145
+ let {
146
+ children
147
+ } = _ref;
148
+ const intlKey = (0, _hooks.useIntlKey)(passedIntlKey, passedIntlNS);
149
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_contexts.SettingsContext.Provider, {
150
+ value: {
151
+ intlKey: sma.intlKey ?? intlKey,
152
+ // This is only here for backwards compatibility... is now grabbed from useIntlKey instead of what's passed in directly
153
+ refdataEndpoint: sma.refdataEndpoint ?? refdataEndpoint,
154
+ settingEndpoint: sma.settingEndpoint ?? settingEndpoint,
155
+ templateEndpoint: sma.templateEndpoint ?? templateEndpoint
156
+ },
157
+ children: children
158
+ });
159
+ };
160
+ SettingsContextProvider.propTypes = {
161
+ children: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node])
162
+ };
163
+ const finalPermission = sma.sectionPermission ?? sectionPermission;
164
+ if (!finalPermission || stripes.hasPerm(finalPermission)) {
165
+ const dynamicPagesFromQueryReturn = getDynamicPages(queryReturnArray[idx], sma);
166
+ const finalPages = sma.pages ?? pages ?? (sma.persistentPages ?? persistentPages).concat(dynamicPagesFromQueryReturn.dynamic).sort(_utils.sortByLabel).map(p => {
167
+ return {
168
+ ...p,
169
+ component: pCProps => /*#__PURE__*/(0, _jsxRuntime.jsx)(SettingsContextProvider, {
170
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(p.component, {
171
+ ...pCProps
172
+ })
173
+ })
174
+ };
175
+ });
176
+ return {
177
+ ...sma,
178
+ SettingsContextProvider,
179
+ ...dynamicPagesFromQueryReturn,
180
+ pageList: finalPages,
181
+ // DEPRECATED Here for backwards compatibility
182
+ pages: finalPages // ASSUMPTION MADE THAT INDICES REMAIN THE SAME
183
+ };
184
+ }
185
+ return null;
186
+ }).filter(Boolean);
187
+ }, [getDynamicPages, pages, passedIntlKey, passedIntlNS, persistentPages, queryReturnArray, refdataEndpoint, sectionMetaArray, sectionPermission, settingEndpoint, stripes, templateEndpoint]);
188
+ const pageList = (0, _react.useMemo)(() => {
189
+ let finalPages = null;
190
+ if (finalSections && finalSections.length === 1 && !renderSingleSection) {
191
+ finalPages = pages ?? finalSections[0].pages;
192
+ }
193
+ return finalPages;
194
+ }, [finalSections, pages, renderSingleSection]);
195
+ const passedSections = (0, _react.useMemo)(() => {
196
+ if (finalSections.length > 1 || finalSections.length === 1 && renderSingleSection) {
197
+ return finalSections;
198
+ }
199
+ return null;
200
+ }, [finalSections, renderSingleSection]);
88
201
  const SettingsComponent = props => {
89
202
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_smartComponents.Settings, {
90
203
  pages: pageList,
91
204
  paneTitle: kintIntl.formatKintMessage({
92
205
  id: 'meta.title'
93
206
  }),
207
+ sections: passedSections,
94
208
  ...props
95
209
  });
96
210
  };
97
- const SettingsComponentWithContext = props => {
98
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(SettingsContextProvider, {
99
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(SettingsComponent, {
100
- ...props
101
- })
102
- });
103
- };
104
211
  return {
105
212
  isLoading,
106
213
  pageList,
107
- SettingsComponent: SettingsComponentWithContext,
108
- SettingsContextProvider
214
+ sections: finalSections,
215
+ SettingsComponent,
216
+ // This doesn't make much sense if there is more than one section, here to avoid breaking changes
217
+ SettingsContextProvider: finalSections && finalSections.length === 1 ? finalSections[0].SettingsContextProvider : _ref2 => {
218
+ let {
219
+ children
220
+ } = _ref2;
221
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
222
+ children: [" ", children, " "]
223
+ });
224
+ }
109
225
  };
110
226
  };
111
227
  var _default = exports.default = useSettings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-int/stripes-kint-components",
3
- "version": "5.14.0",
3
+ "version": "5.16.0",
4
4
  "description": "Stripes Component library for K-Int specific applications",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -44,7 +44,7 @@
44
44
  "@folio/jest-config-stripes": "^2.0.0 || ^3.0.0",
45
45
  "@folio/stripes": "^9.0.0 || ^10.0.0",
46
46
  "@folio/stripes-cli": "^3.0.0 || ^4.0.0",
47
- "@folio/stripes-erm-testing": "^2.0.0",
47
+ "@folio/stripes-erm-testing": "^2.0.0 || ^3.0.0",
48
48
  "@formatjs/cli": "^6.6.0",
49
49
  "@semantic-release/changelog": "^6.0.3",
50
50
  "@semantic-release/git": "^10.0.1",
@@ -23,20 +23,20 @@ const NoResultsMessage = ({
23
23
  let icon = 'search';
24
24
  let label = <FormattedMessage id="stripes-smart-components.sas.noResults.default" values={{ searchTerm }} />;
25
25
 
26
- // No search term but no results
27
- if (!searchTerm) {
26
+ if (!isLoading && !searchTerm) {
27
+ // No search term and not loading
28
+ icon = filterPaneIsVisible ? 'arrow-left' : null;
29
+ label = <FormattedMessage id="stripes-smart-components.sas.noResults.noTerms" />;
30
+ } else if (!isLoading) {
31
+ // Search term but not loading
28
32
  icon = 'search';
29
33
  label = <FormattedMessage id="stripes-smart-components.sas.noResults.noResults" />;
30
- }
31
-
32
- // Loading results
33
- if (isLoading) {
34
+ } else if (isLoading) {
35
+ // Loading
34
36
  icon = 'spinner-ellipsis';
35
37
  label = <FormattedMessage id="stripes-smart-components.sas.noResults.loading" />;
36
- }
37
-
38
- // Request failure
39
- if (isError) {
38
+ } else if (isError) {
39
+ // Request failure
40
40
  icon = 'exclamation-circle';
41
41
  label = error?.message;
42
42
  }
@@ -40,10 +40,11 @@ const SASQLookupComponent = forwardRef((props, ref) => {
40
40
  intlKey: passedIntlKey,
41
41
  intlNS: passedIntlNS,
42
42
  labelOverrides = {},
43
- mainPaneProps,
43
+ mainPaneProps = {},
44
44
  mclProps = {},
45
45
  noSearchField,
46
46
  persistedPanesetProps = {},
47
+ queryParameterGenerator = generateKiwtQuery, // Expects a function which accepts SASQ_MAP and nsValues
47
48
  RenderBody,
48
49
  rowNavigation = true, // Default navigation onRowClick
49
50
  sasqProps,
@@ -53,25 +54,43 @@ const SASQLookupComponent = forwardRef((props, ref) => {
53
54
  const kintIntl = useKintIntl(passedIntlKey, passedIntlNS);
54
55
  const [count, setCount] = useState(0);
55
56
 
57
+ // Grab things we might need from sasqProps if passed
58
+ const {
59
+ query: sasqPropsQuery, // If these are overriden we need to make use of them
60
+ queryGetter: sasqPropsQueryGetter,
61
+ querySetter: sasqPropsQuerySetter,
62
+ syncToLocation = true
63
+ } = sasqProps ?? {};
64
+
56
65
  const { currentPage, paginationMCLProps, paginationSASQProps } = usePrevNextPagination({
57
66
  count,
58
- pageSize: fetchParameters.SASQ_MAP?.perPage
67
+ pageSize: fetchParameters.SASQ_MAP?.perPage,
68
+ syncToLocation
59
69
  });
60
70
 
61
- const { query, queryGetter, querySetter } = useKiwtSASQuery();
71
+ const {
72
+ query: kintSASQQuery,
73
+ queryGetter: kintSASQQueryGetter,
74
+ querySetter: kintSASQQuerySetter
75
+ } = useKiwtSASQuery();
76
+
77
+ const query = useMemo(() => sasqPropsQuery ?? kintSASQQuery, [sasqPropsQuery, kintSASQQuery]);
78
+ const queryGetter = useMemo(() => sasqPropsQueryGetter ?? kintSASQQueryGetter, [sasqPropsQueryGetter, kintSASQQueryGetter]);
79
+ const querySetter = useMemo(() => sasqPropsQuerySetter ?? kintSASQQuerySetter, [sasqPropsQuerySetter, kintSASQQuerySetter]);
80
+
62
81
  const { 0: namespace } = useNamespace();
63
82
  const ky = useOkapiKy();
64
83
 
65
84
  const filterPaneVisibileKey = `${namespace}-${id}-filterPaneVisibility`;
66
85
 
67
86
  const queryParams = useMemo(() => (
68
- generateKiwtQuery(
87
+ queryParameterGenerator(
69
88
  {
70
89
  ...fetchParameters.SASQ_MAP,
71
90
  page: currentPage,
72
91
  }, (query ?? {})
73
92
  )
74
- ), [currentPage, fetchParameters.SASQ_MAP, query]);
93
+ ), [currentPage, fetchParameters.SASQ_MAP, query, queryParameterGenerator]);
75
94
 
76
95
  const [filterPaneVisible, setFilterPaneVisible] = useLocalStorageState(filterPaneVisibileKey, true);
77
96
  const toggleFilterPane = () => setFilterPaneVisible(!filterPaneVisible);
@@ -118,6 +137,7 @@ const SASQLookupComponent = forwardRef((props, ref) => {
118
137
  initialSearchState={{ query: '' }}
119
138
  queryGetter={queryGetter}
120
139
  querySetter={querySetter}
140
+ syncToLocation={syncToLocation}
121
141
  {...sasqProps}
122
142
  {...paginationSASQProps}
123
143
  >
@@ -315,6 +335,7 @@ SASQLookupComponent.propTypes = {
315
335
  PropTypes.func,
316
336
  PropTypes.node
317
337
  ]),
338
+ queryParameterGenerator: PropTypes.func,
318
339
  history: PropTypes.object,
319
340
  id: PropTypes.string.isRequired,
320
341
  intlKey: PropTypes.string,
@@ -12,7 +12,11 @@ import SASQViewComponent from '../SASQViewComponent';
12
12
  const SASQRoute = forwardRef(({
13
13
  children,
14
14
  fetchParameters,
15
+ getPathLookup = (path) => `${path}/:id?`,
16
+ getPathView = (path) => `${path}/:id`,
17
+ SASQLookupComponent: RouteLookupComponent = SASQLookupComponent,
15
18
  path,
19
+ SASQViewComponent: RouteViewComponent = SASQViewComponent,
16
20
  ...props
17
21
  }, ref) => {
18
22
  // Grab the SASQ_MAP and tweak it
@@ -37,10 +41,10 @@ const SASQRoute = forwardRef(({
37
41
 
38
42
  return (
39
43
  <Route
40
- path={`${path}/:id?`}
44
+ path={getPathLookup(path)}
41
45
  render={routeProps => {
42
46
  return (
43
- <SASQLookupComponent
47
+ <RouteLookupComponent
44
48
  ref={lookupRef}
45
49
  {...routeProps}
46
50
  fetchParameters={fetchParameters}
@@ -50,9 +54,9 @@ const SASQRoute = forwardRef(({
50
54
  <Switch>
51
55
  {children}
52
56
  <Route
53
- path={`${path}/:id`}
57
+ path={getPathView(path)}
54
58
  render={innerProps => (
55
- <SASQViewComponent
59
+ <RouteViewComponent
56
60
  ref={viewRef}
57
61
  {...innerProps}
58
62
  fetchParameters={fetchParameters}
@@ -63,7 +67,7 @@ const SASQRoute = forwardRef(({
63
67
  )}
64
68
  />
65
69
  </Switch>
66
- </SASQLookupComponent>
70
+ </RouteLookupComponent>
67
71
  );
68
72
  }}
69
73
  />
@@ -77,7 +81,17 @@ SASQRoute.propTypes = {
77
81
  PropTypes.func
78
82
  ]),
79
83
  fetchParameters: PropTypes.object,
84
+ getPathLookup: PropTypes.func,
85
+ getPathView: PropTypes.func,
80
86
  path: PropTypes.string,
87
+ SASQLookupComponent: PropTypes.oneOfType([
88
+ PropTypes.func,
89
+ PropTypes.node
90
+ ]),
91
+ SASQViewComponent: PropTypes.oneOfType([
92
+ PropTypes.func,
93
+ PropTypes.node
94
+ ]),
81
95
  ViewComponent: PropTypes.oneOfType([
82
96
  PropTypes.func,
83
97
  PropTypes.node
@@ -1,8 +1,8 @@
1
- import React from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
- import { useQuery } from 'react-query';
5
- import { useOkapiKy } from '@folio/stripes/core';
4
+ import { useQueries } from 'react-query';
5
+ import { useOkapiKy, useStripes } from '@folio/stripes/core';
6
6
 
7
7
  import { Settings } from '@folio/stripes/smart-components';
8
8
 
@@ -12,88 +12,207 @@ import { SettingsContext } from '../contexts';
12
12
  import { generateKiwtQueryParams, sortByLabel, toCamelCase } from '../utils';
13
13
  import { useKintIntl, useIntlKey } from '../hooks';
14
14
 
15
- const useSettings = ({
16
- allowGlobalEdit = true,
17
- dynamicPageExclusions,
18
- intlKey: passedIntlKey,
19
- intlNS: passedIntlNS,
20
- labelOverrides = {},
21
- persistentPages,
22
- refdataEndpoint,
23
- settingEndpoint,
24
- templateEndpoint
25
- }) => {
15
+ // TODO work underway to make the settings hook more useful when trying to render multiple sections at a time
16
+ const useSettings = (settingsProps = {}) => {
17
+ const {
18
+ allowGlobalEdit = true,
19
+ dynamicPageExclusions,
20
+ intlKey: passedIntlKey,
21
+ intlNS: passedIntlNS,
22
+ labelOverrides = {},
23
+ pages, // If this is present, act completely as a passthrough either in sections OR standalone
24
+ persistentPages = [],
25
+ preventQueries = false,
26
+ refdataEndpoint,
27
+ renderSingleSection = false, // IF SECTIONS == NULL, this prop allows the implementor to render the pageList as a section with label etc
28
+ sectionPermission, // If we're rendering sections, this prevents the rendering of said section (works as fallback or prevention for single section)
29
+ sectionRoute,
30
+ sections, // if present, should be an array of objects with the SAME shape as the overall props here.
31
+ // If a prop isn't present it can fall back to the "global" prop.
32
+ settingEndpoint,
33
+ templateEndpoint
34
+ } = settingsProps;
35
+
26
36
  const ky = useOkapiKy();
37
+ const stripes = useStripes();
27
38
  const kintIntl = useKintIntl(passedIntlKey, passedIntlNS);
28
39
 
29
- const queryParams = generateKiwtQueryParams({
30
- filters: dynamicPageExclusions?.map(dpe => ({
31
- path: 'section',
32
- comparator: '!=',
33
- value: dpe
34
- })),
35
- perPage: 100,
36
- stats: false
37
- }, {});
38
-
39
- const { data: appSettings = [], isLoading } = useQuery(
40
- ['stripes-kint-components', 'useSettings', 'appSettings', settingEndpoint, queryParams],
41
- () => ky(`${settingEndpoint}?${queryParams?.join('&')}`).json()
42
- );
43
-
44
- const sections = [...new Set(appSettings.map(s => s.section))];
45
- const dynamic = sections.map(section => {
46
- return (
47
- {
48
- route: section,
49
- label: kintIntl.formatKintMessage({
50
- id: `settings.settingsSection.${toCamelCase(section)}`,
51
- fallbackMessage: section
52
- }),
53
- component: (props) => (
54
- <SettingPagePane
55
- intlKey={passedIntlKey}
56
- intlNS={passedIntlNS}
57
- sectionName={section}
58
- >
59
- <SettingPage
60
- allowEdit={allowGlobalEdit}
40
+ // Set up array of queryParams, pageLists etc etc ready for us to do all the work in a single place
41
+
42
+ const sectionMetaArray = useMemo(() => {
43
+ const array = [];
44
+ const getSectionQueryProps = (sectionProps) => {
45
+ const dqp = sectionProps.dynamicPageExclusions ?? dynamicPageExclusions;
46
+ const queryParams = generateKiwtQueryParams({
47
+ filters: dqp?.map(dpe => ({
48
+ path: 'section',
49
+ comparator: '!=',
50
+ value: dpe
51
+ })),
52
+ perPage: 100,
53
+ stats: false
54
+ }, {});
55
+ const finalSettingEndpoint = sectionProps.settingEndpoint ?? settingEndpoint;
56
+ const queryKey = ['stripes-kint-components', 'useSettings', 'appSettings', finalSettingEndpoint, queryParams];
57
+ const queryFn = () => ky(`${finalSettingEndpoint}?${queryParams?.join('&')}`).json();
58
+
59
+ return { queryParams, queryKey, queryFn };
60
+ };
61
+
62
+ if ((sections?.length ?? 0) > 0) {
63
+ array.push(...sections.map((section) => {
64
+ const querySectionProps = getSectionQueryProps(section);
65
+
66
+ return ({
67
+ ...settingsProps, // ensure these fall through to section based props
68
+ ...querySectionProps,
69
+ ...section // return original section object
70
+ });
71
+ }));
72
+ } else {
73
+ const querySectionProps = getSectionQueryProps(settingsProps);
74
+ array.push({
75
+ ...settingsProps,
76
+ ...querySectionProps,
77
+ });
78
+ }
79
+
80
+ return array;
81
+ }, [dynamicPageExclusions, ky, sections, settingEndpoint, settingsProps]);
82
+
83
+ const queries = useMemo(() => sectionMetaArray.map(sma => {
84
+ let enabled = true;
85
+ if (sma.pages || sma.preventQueries || pages || preventQueries) {
86
+ enabled = false;
87
+ }
88
+
89
+ return {
90
+ queryKey: sma.queryKey,
91
+ queryFn: sma.queryFn,
92
+ enabled
93
+ };
94
+ }) ?? [], [pages, preventQueries, sectionMetaArray]);
95
+
96
+ const queryReturnArray = useQueries(queries);
97
+
98
+ const [isLoading, setIsLoading] = useState(true);
99
+
100
+ const getDynamicPages = useCallback((qra, sma) => {
101
+ const pagesFromQueryReturn = [...new Set(qra.data?.map(s => s.section))];
102
+ const dynamic = pagesFromQueryReturn.map(page => {
103
+ const finalSectionRoute = sma.sectionRoute ?? sectionRoute;
104
+ const pageRoute = (finalSectionRoute ? `${finalSectionRoute}/` : '') + page;
105
+ return (
106
+ {
107
+ route: pageRoute,
108
+ label: kintIntl.formatKintMessage({
109
+ id: `settings.settingsSection.${toCamelCase(page)}`,
110
+ fallbackMessage: page
111
+ }),
112
+ component: (props) => (
113
+ <SettingPagePane
61
114
  intlKey={passedIntlKey}
62
115
  intlNS={passedIntlNS}
63
- labelOverrides={labelOverrides}
64
- sectionName={section}
65
- {...props}
66
- />
67
- </SettingPagePane>
68
- )
116
+ sectionName={page}
117
+ >
118
+ <SettingPage
119
+ allowEdit={allowGlobalEdit}
120
+ intlKey={passedIntlKey}
121
+ intlNS={passedIntlNS}
122
+ labelOverrides={labelOverrides}
123
+ sectionName={page}
124
+ {...props}
125
+ />
126
+ </SettingPagePane>
127
+ )
128
+ }
129
+ );
130
+ });
131
+
132
+ return {
133
+ pages: pagesFromQueryReturn,
134
+ dynamic,
135
+ };
136
+ }, [allowGlobalEdit, kintIntl, labelOverrides, passedIntlKey, passedIntlNS, sectionRoute]);
137
+
138
+ useEffect(() => {
139
+ // Handle loading
140
+ if (
141
+ queryReturnArray.length > 0 &&
142
+ queryReturnArray.every(qra => qra.isLoading === false) &&
143
+ isLoading === true
144
+ ) {
145
+ setIsLoading(false);
146
+ }
147
+ }, [isLoading, queryReturnArray]);
148
+
149
+ const finalSections = useMemo(() => {
150
+ return sectionMetaArray.map(((sma, idx) => {
151
+ const SettingsContextProvider = ({ children }) => {
152
+ const intlKey = useIntlKey(passedIntlKey, passedIntlNS);
153
+ return (
154
+ <SettingsContext.Provider
155
+ value={{
156
+ intlKey: sma.intlKey ?? intlKey, // This is only here for backwards compatibility... is now grabbed from useIntlKey instead of what's passed in directly
157
+ refdataEndpoint: sma.refdataEndpoint ?? refdataEndpoint,
158
+ settingEndpoint: sma.settingEndpoint ?? settingEndpoint,
159
+ templateEndpoint: sma.templateEndpoint ?? templateEndpoint,
160
+ }}
161
+ >
162
+ {children}
163
+ </SettingsContext.Provider>
164
+ );
165
+ };
166
+
167
+ SettingsContextProvider.propTypes = {
168
+ children: PropTypes.oneOfType([
169
+ PropTypes.func,
170
+ PropTypes.node
171
+ ])
172
+ };
173
+
174
+ const finalPermission = sma.sectionPermission ?? sectionPermission;
175
+ if (!finalPermission || stripes.hasPerm(finalPermission)) {
176
+ const dynamicPagesFromQueryReturn = getDynamicPages(queryReturnArray[idx], sma);
177
+
178
+ const finalPages = sma.pages ?? pages ?? (sma.persistentPages ?? persistentPages).concat(dynamicPagesFromQueryReturn.dynamic).sort(sortByLabel).map((p) => {
179
+ return {
180
+ ...p,
181
+ component: (pCProps) => (
182
+ <SettingsContextProvider>
183
+ <p.component {...pCProps} />
184
+ </SettingsContextProvider>
185
+ )
186
+ };
187
+ });
188
+
189
+ return {
190
+ ...sma,
191
+ SettingsContextProvider,
192
+ ...dynamicPagesFromQueryReturn,
193
+ pageList: finalPages, // DEPRECATED Here for backwards compatibility
194
+ pages: finalPages // ASSUMPTION MADE THAT INDICES REMAIN THE SAME
195
+ };
69
196
  }
70
- );
71
- });
197
+ return null;
198
+ })).filter(Boolean);
199
+ }, [getDynamicPages, pages, passedIntlKey, passedIntlNS, persistentPages, queryReturnArray, refdataEndpoint, sectionMetaArray, sectionPermission, settingEndpoint, stripes, templateEndpoint]);
72
200
 
73
- const pageList = persistentPages.concat(dynamic).sort(sortByLabel);
201
+ const pageList = useMemo(() => {
202
+ let finalPages = null;
203
+ if (finalSections && finalSections.length === 1 && !renderSingleSection) {
204
+ finalPages = pages ?? finalSections[0].pages;
205
+ }
74
206
 
75
- const intlKey = useIntlKey(passedIntlKey, passedIntlNS);
76
- const SettingsContextProvider = ({ children }) => {
77
- return (
78
- <SettingsContext.Provider
79
- value={{
80
- intlKey, // This is only here for backwards compatibility... is now grabbed from useIntlKey instead of what's passed in directly
81
- refdataEndpoint,
82
- settingEndpoint,
83
- templateEndpoint
84
- }}
85
- >
86
- {children}
87
- </SettingsContext.Provider>
88
- );
89
- };
207
+ return finalPages;
208
+ }, [finalSections, pages, renderSingleSection]);
90
209
 
91
- SettingsContextProvider.propTypes = {
92
- children: PropTypes.oneOfType([
93
- PropTypes.func,
94
- PropTypes.node
95
- ])
96
- };
210
+ const passedSections = useMemo(() => {
211
+ if (finalSections.length > 1 || (finalSections.length === 1 && renderSingleSection)) {
212
+ return finalSections;
213
+ }
214
+ return null;
215
+ }, [finalSections, renderSingleSection]);
97
216
 
98
217
  const SettingsComponent = (props) => {
99
218
  return (
@@ -101,26 +220,21 @@ const useSettings = ({
101
220
  pages={pageList}
102
221
  paneTitle={
103
222
  kintIntl.formatKintMessage({
104
- id: 'meta.title'
105
- })}
223
+ id: 'meta.title'
224
+ })}
225
+ sections={passedSections}
106
226
  {...props}
107
227
  />
108
228
  );
109
229
  };
110
230
 
111
- const SettingsComponentWithContext = (props) => {
112
- return (
113
- <SettingsContextProvider>
114
- <SettingsComponent {...props} />
115
- </SettingsContextProvider>
116
- );
117
- };
118
-
119
231
  return {
120
232
  isLoading,
121
233
  pageList,
122
- SettingsComponent: SettingsComponentWithContext,
123
- SettingsContextProvider
234
+ sections: finalSections,
235
+ SettingsComponent,
236
+ // This doesn't make much sense if there is more than one section, here to avoid breaking changes
237
+ SettingsContextProvider: (finalSections && finalSections.length === 1) ? finalSections[0].SettingsContextProvider : ({ children }) => <> { children } </>
124
238
  };
125
239
  };
126
240