@k-int/stripes-kint-components 5.13.0 → 5.15.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,17 @@
1
+ # [5.15.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.14.0...v5.15.0) (2025-02-21)
2
+
3
+
4
+ ### Features
5
+
6
+ * useSettings sections ([0c062bf](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/0c062bf203811078371820b5106b3106982845ad))
7
+
8
+ # [5.14.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.13.0...v5.14.0) (2025-02-19)
9
+
10
+
11
+ ### Features
12
+
13
+ * Added filterConfig options to generateKiwtQueryParams ([8502b91](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/8502b91beef93065a24c1b1ab6ed4b73759b851d))
14
+
1
15
  # [5.13.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.12.0...v5.13.0) (2025-02-17)
2
16
 
3
17
 
@@ -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;
@@ -4,11 +4,10 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _generateKiwtQueryParams = _interopRequireDefault(require("./generateKiwtQueryParams"));
8
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ var _generateKiwtQueryParams = require("./generateKiwtQueryParams");
9
8
  const generateKiwtQuery = function (options, nsValues) {
10
9
  let encode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
11
- const paramsArray = (0, _generateKiwtQueryParams.default)(options, nsValues, encode);
10
+ const paramsArray = (0, _generateKiwtQueryParams.generateKiwtQueryParams)(options, nsValues, encode);
12
11
  return paramsArray.length ? '?' + paramsArray.join('&') : '';
13
12
  };
14
13
  var _default = exports.default = generateKiwtQuery;
@@ -134,8 +134,11 @@ const generateKiwtQueryParams = function (options, nsValues) {
134
134
  if (filterConfigEntry) {
135
135
  // We have a direct mapping instruction, use it
136
136
  const filterString = filterValues.map(v => {
137
- const fceValue = filterConfigEntry?.values?.find(fce => fce.name === v)?.value;
138
- return `${filterName}==${fceValue ?? v}`;
137
+ const fcValueEntry = filterConfigEntry?.values?.find(fce => fce.name === v) ?? {};
138
+ const fceValue = fcValueEntry.value ?? v;
139
+ // This is especially useful where comparator acts strangely for a single value, such as `filters=foo isNotSet`
140
+ const fceComparator = fcValueEntry.comparator ?? '==';
141
+ return `${filterKey ?? filterName}${fceComparator}${fceValue ?? v}`;
139
142
  }).join('||');
140
143
  paramsArray.push(`filters=${conditionalEncodeURIComponent(filterString, encode)}`);
141
144
  } else if (!filterKey) {
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "generateKiwtQueryParams", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _generateKiwtQueryParams.default;
10
+ }
11
+ });
12
+ var _generateKiwtQueryParams = _interopRequireDefault(require("./generateKiwtQueryParams"));
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "generateKiwtQuery", {
36
36
  Object.defineProperty(exports, "generateKiwtQueryParams", {
37
37
  enumerable: true,
38
38
  get: function () {
39
- return _generateKiwtQueryParams.default;
39
+ return _generateKiwtQueryParams.generateKiwtQueryParams;
40
40
  }
41
41
  });
42
42
  Object.defineProperty(exports, "groupCustomPropertiesByCtx", {
@@ -136,7 +136,7 @@ Object.defineProperty(exports, "typedownQueryKey", {
136
136
  }
137
137
  });
138
138
  var _generateKiwtQuery = _interopRequireDefault(require("./generateKiwtQuery"));
139
- var _generateKiwtQueryParams = _interopRequireDefault(require("./generateKiwtQueryParams"));
139
+ var _generateKiwtQueryParams = require("./generateKiwtQueryParams");
140
140
  var _selectorSafe = _interopRequireDefault(require("./selectorSafe"));
141
141
  var _buildUrl = _interopRequireDefault(require("./buildUrl"));
142
142
  var _refdataOptions = _interopRequireDefault(require("./refdataOptions"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-int/stripes-kint-components",
3
- "version": "5.13.0",
3
+ "version": "5.15.0",
4
4
  "description": "Stripes Component library for K-Int specific applications",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -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
 
@@ -3,7 +3,7 @@
3
3
  ## generateKiwtQuery
4
4
 
5
5
  The `generateKiwtQuery` function generates a URL query string in "KIWT" (K-Int Web Toolkit) style from a SASQ (Search and Sort Query) object. It handles search terms, filters, sorting, and other parameters, making it easy to construct complex query strings for backend APIs.
6
-
6
+ This component calls and then joins the output of `generateKiwtQueryParams`. For more granular information on how this works, see the generateKiwtQueryParams README.
7
7
  ### Basic Usage
8
8
 
9
9
  ```javascript
@@ -39,105 +39,3 @@ const queryString = generateKiwtQuery(options, nsValues);
39
39
  | options | object | An object with keys: `searchKey`, `filterKeys`, `sortKeys`, and `stats`, which maps the incoming `nsValues` object to a KIWT query. You can also pass arbitrary `key`/`value` pairs to append `key==value` onto the query. | | ✓ |
40
40
  | nsValues | object | An object containing the query parameters. Can contain `query`, `filters`, and `sort`, as well as any other arbitrary parameters. | | ✓ |
41
41
  | encode | boolean | A boolean indicating whether to URL-encode the values. | `true` | ✕ |
42
-
43
- ## generateKiwtQueryParams
44
-
45
- The `generateKiwtQueryParams` function is similar to `generateKiwtQuery`, but instead of returning a string, it returns an array of query parameters. This can be useful for more advanced use cases where you need to manipulate the parameters before constructing the final query string.
46
-
47
- ### Basic Usage
48
-
49
- ```javascript
50
- import { generateKiwtQueryParams } from '@k-int/stripes-kint-components';
51
-
52
- const nsValues = {
53
- query: 'test',
54
- filters: 'requestStatus.completed,journalVolume.test1,startDate.startDate>=2021-10-28'
55
- };
56
-
57
- const options = {
58
- searchKey: 'request.name',
59
- filterKeys: {
60
- requestStatus: 'requestStatus.value',
61
- journalVolume: 'journalVolume'
62
- }
63
- };
64
-
65
- const queryArray = generateKiwtQueryParams(options, nsValues);
66
-
67
- // queryArray will be:
68
- // [
69
- // "match=request.name",
70
- // "term=test",
71
- // "filters=requestStatus.value==completed",
72
- // "filters=journalVolume==test1",
73
- // "filters=startDate%3E=2021-10-28",
74
- // "stats=true"
75
- // ]
76
- ```
77
-
78
- ### Props
79
-
80
- | Name | Type | Description | Default | Required |
81
- |---|---|---|---|---|
82
- | options | object | An object with keys: `searchKey`, `filterKeys`, `sortKeys`, and `stats`, which maps the incoming `nsValues` object to a KIWT query array. You can also pass arbitrary `key`/`value` pairs to append `key==value` onto the query. | | ✓ |
83
- | nsValues | object | An object containing the query parameters. Can contain `query`, `filters`, and `sort`, as well as any other arbitrary parameters. | | ✓ |
84
- | encode | boolean | A boolean indicating whether to URL-encode the values. | `true` | ✕ |
85
-
86
-
87
- ### Options Parameter Structure
88
-
89
- The `options` parameter is a powerful tool for customizing the query string generation. It has the following key elements:
90
-
91
- * **Search:**
92
- * `searchKey`: Specifies the field to search on. If using SASQ, the corresponding value in `nsValues.query` will be used as the search term. Otherwise, you can directly specify the search term using the `term` key in `options`.
93
- * **Filters:** See the "Filters Object Structure" section below for details.
94
- * **Sorting:**
95
- * `sortKeys`: An object mapping sort field names from `nsValues.sort` to their corresponding backend keys.
96
- * `sort`: An array of sort objects. Each object can have `path` (field to sort on) and `direction` (`asc` or `desc`).
97
- * **Statistics:**
98
- * `stats`: A boolean value indicating whether to include the `stats=true` parameter.
99
- * **Other parameters:** Any other key-value pairs in the `options` object will be directly added as query parameters.
100
-
101
-
102
- ### Filters Object Structure
103
-
104
- The `filters` option in the `options` parameter allows you to define complex filter expressions using an array of filter objects. Each filter object can have the following properties:
105
-
106
- * **path:** The path to the field to filter on (e.g., `item.title`).
107
- * **comparator:** *(Optional)* The comparator to use for the filter (e.g., `==`, `!=`, `>`, `<`). Defaults to `==`.
108
- * **value:** A single value to filter on.
109
- * **values:** An array of values to filter on. If present, this property takes precedence over `value`, and an `OR` condition is implied between the values.
110
- * **groupValues:** An object defining nested filter groups with `AND` or `OR` logic. This property allows for building complex nested filters. See the explanation below.
111
-
112
- #### Nested Filter Groups (`groupValues`)
113
-
114
- The `groupValues` property enables the creation of nested filter expressions with `AND` and `OR` logic. It is an object with either `AND` or `OR` properties (or both, with `AND` taking precedence). The value of these properties should be an array of filter objects, allowing for recursive nesting.
115
-
116
- ```javascript
117
- // Example: (status==active AND type==local) OR (status==pending)
118
- const filters = [
119
- {
120
- groupValues: {
121
- OR: [
122
- {
123
- groupValues: {
124
- AND: [
125
- { path: 'item.status', value: 'active' },
126
- { path: 'item.type', value: 'local' },
127
- ],
128
- }
129
- },
130
- { path: 'item.status', value: 'pending' },
131
- ]
132
- },
133
- },
134
- ];
135
- ```
136
-
137
- This structure allows you to express complex filter logic in a clear and organized way.
138
-
139
- ### Key Takeaways
140
-
141
- * `generateKiwtQuery` provides a flexible way to construct URL query strings for backend APIs, especially when working with Stripes SASQ.
142
- * The `options` parameter offers fine-grained control over the query generation process, allowing for complex filtering, sorting, and other customizations.
143
- * `generateKiwtQueryParams` is useful for advanced use cases where you need to manipulate the parameters before constructing the final query string.
@@ -1,4 +1,4 @@
1
- import generateKiwtQueryParams from './generateKiwtQueryParams';
1
+ import { generateKiwtQueryParams } from './generateKiwtQueryParams';
2
2
 
3
3
  const generateKiwtQuery = (options, nsValues, encode = true) => {
4
4
  const paramsArray = generateKiwtQueryParams(options, nsValues, encode);
@@ -0,0 +1,199 @@
1
+ # generateKiwtQueryParams
2
+
3
+ The `generateKiwtQueryParams` function is similar to `generateKiwtQuery`, but instead of returning a string, it returns an array of query parameters. This can be useful for more advanced use cases where you need to manipulate the parameters before constructing the final query string.
4
+
5
+ ## Basic Usage
6
+
7
+ ```javascript
8
+ import { generateKiwtQueryParams } from '@k-int/stripes-kint-components';
9
+
10
+ const nsValues = {
11
+ query: 'test',
12
+ filters: 'requestStatus.completed,journalVolume.test1,startDate.startDate>=2021-10-28'
13
+ };
14
+
15
+ const options = {
16
+ searchKey: 'request.name',
17
+ filterKeys: {
18
+ requestStatus: 'requestStatus.value',
19
+ journalVolume: 'journalVolume'
20
+ }
21
+ };
22
+
23
+ const queryArray = generateKiwtQueryParams(options, nsValues);
24
+
25
+ // queryArray will be:
26
+ // [
27
+ // "match=request.name",
28
+ // "term=test",
29
+ // "filters=requestStatus.value==completed",
30
+ // "filters=journalVolume==test1",
31
+ // "filters=startDate%3E=2021-10-28",
32
+ // "stats=true"
33
+ // ]
34
+ ```
35
+
36
+ ## Props
37
+
38
+ | Name | Type | Description | Default | Required |
39
+ |---|---|---|---|---|
40
+ | options | object | An object with keys: `searchKey`, `filterKeys`, `sortKeys`, and `stats`, which maps the incoming `nsValues` object to a KIWT query array. You can also pass arbitrary `key`/`value` pairs to append `key==value` onto the query. | | ✓ |
41
+ | nsValues | object | An object containing the query parameters. Can contain `query`, `filters`, and `sort`, as well as any other arbitrary parameters. | | ✓ |
42
+ | encode | boolean | A boolean indicating whether to URL-encode the values. | `true` | ✕ |
43
+
44
+
45
+ ## Options Parameter Structure
46
+
47
+ The `options` parameter is a powerful tool for customizing the query string generation manually. It has the following key elements:
48
+
49
+ * **Search:**
50
+ * `searchKey`: Specifies the field to search on. If using SASQ, the corresponding value in `nsValues.query` will be used as the search term. Otherwise, you can directly specify the search term using the `term` key in `options`.
51
+ * **Filters:** See the "Filters Object Structure" section below for details.
52
+ * **Sorting:**
53
+ * `sortKeys`: An object mapping sort field names from `nsValues.sort` to their corresponding backend keys.
54
+ * `sort`: An array of sort objects. Each object can have `path` (field to sort on) and `direction` (`asc` or `desc`).
55
+ * **Statistics:**
56
+ * `stats`: A boolean value indicating whether to include the `stats=true` parameter.
57
+ * **Other parameters:** Any other key-value pairs in the `options` object will be directly added as query parameters.
58
+
59
+
60
+ ### Filters Object Structure
61
+
62
+ The `filters` option in the `options` parameter allows you to define complex filter expressions using an array of filter objects. Each filter object can have the following properties:
63
+
64
+ * **path:** The path to the field to filter on (e.g., `item.title`).
65
+ * **comparator:** *(Optional)* The comparator to use for the filter (e.g., `==`, `!=`, `>`, `<`). Defaults to `==`.
66
+ * **value:** A single value to filter on.
67
+ * **values:** An array of values to filter on. If present, this property takes precedence over `value`, and an `OR` condition is implied between the values.
68
+ * **groupValues:** An object defining nested filter groups with `AND` or `OR` logic. This property allows for building complex nested filters. See the explanation below.
69
+
70
+ ### Nested Filter Groups (`groupValues`)
71
+
72
+ The `groupValues` property enables the creation of nested filter expressions with `AND` and `OR` logic. It is an object with either `AND` or `OR` properties (or both, with `AND` taking precedence). The value of these properties should be an array of filter objects, allowing for recursive nesting.
73
+
74
+ ```javascript
75
+ // Example: (status==active AND type==local) OR (status==pending)
76
+ const filters = [
77
+ {
78
+ groupValues: {
79
+ OR: [
80
+ {
81
+ groupValues: {
82
+ AND: [
83
+ { path: 'item.status', value: 'active' },
84
+ { path: 'item.type', value: 'local' },
85
+ ],
86
+ }
87
+ },
88
+ { path: 'item.status', value: 'pending' },
89
+ ]
90
+ },
91
+ },
92
+ ];
93
+ ```
94
+
95
+ This structure allows you to express complex filter logic in a clear and organized way.
96
+
97
+ ## nsValues options
98
+
99
+ In addition to the manual filtering capabilities, `generateKiwtQueryParams` supports advanced filtering options including custom comparators and dynamic filter key overrides for the `query` nsValues passed by SASQ. These features enable you to generate complex query expressions more easily.
100
+
101
+ ### Custom Comparators & Filter Key Overrides
102
+ When using the older `nsValues.filters` string (e.g., `"requestStatus.completed,journalVolume.test1"`), you can use `filterConfig` to customize how filters are interpreted:
103
+
104
+ - **Mapping Filter Names:**
105
+ Each entry in `filterConfig` should include a `name` field that matches the filter name parsed from `nsValues.filters`.
106
+
107
+ - **Overriding Values and Comparators:**
108
+ Optionally, a `values` array can be provided for more granular control. For each possible value, you can specify:
109
+ - A replacement value.
110
+ - A custom comparator to be used instead of the default `'=='`.
111
+
112
+ For example, if you want the filter `status` to use a comparator like `isNotSet` for a certain value, your configuration might look like this:
113
+
114
+ ```javascript
115
+ const options = {
116
+ filterKeys: { status: 'requestStatus.value' },
117
+ filterConfig: [
118
+ {
119
+ name: 'status',
120
+ values: [
121
+ { name: 'notSet', value: '', comparator: ' isNotSet' },
122
+ { name: 'isNotActive', value: 'active', comparator: '!=' },
123
+ // other value mappings... Any not present will be evaluated as is
124
+ ]
125
+ }
126
+ ]
127
+ };
128
+ ```
129
+
130
+ With this configuration, the generated query parameter for various nsValues.filters would be:
131
+
132
+ | nsValues.filters | outcome |
133
+ | -- | -- |
134
+ | status.notSet | `filters=requestStatus.value isNotSet` |
135
+ | status.wibble | `filters=requestStatus.value==wibble` |
136
+ | status.isNotActive | `filters=requestStatus.value!=active` |
137
+
138
+
139
+
140
+
141
+ ### Dynamic Date Example with `moreThanToday`
142
+
143
+ You can also dynamically set filter values based on runtime data. For instance, if you want to filter records where a date field is greater than or equal to today’s date, you can calculate the date value first and then reference it in your `filterConfig`:
144
+
145
+ ```javascript
146
+ // Dynamically calculate today's date in YYYY-MM-DD format
147
+ const today = new Date().toISOString().split('T')[0];
148
+
149
+ const filterConfig = [
150
+ {
151
+ name: 'startDate',
152
+ values: [
153
+ {
154
+ name: 'moreThanToday',
155
+ value: today,
156
+ comparator: '>='
157
+ }
158
+ ]
159
+ }
160
+ ];
161
+
162
+ const options = {
163
+ filterConfig,
164
+ };
165
+
166
+ const nsValues = {
167
+ // When 'startDate.moreThanToday' is encountered, it will use the filterConfig mapping
168
+ filters: 'startDate.moreThanToday'
169
+ };
170
+
171
+ const queryArray = generateKiwtQueryParams(options, nsValues);
172
+ console.log(queryArray);
173
+ // Example output (URL encoded):
174
+ // [
175
+ // "filters=startDate%3E%3D2025-02-19", // The date value will reflect today's date
176
+ // "stats=true"
177
+ // ]
178
+ ```
179
+
180
+ In this example, when `nsValues.filters` contains `startDate.moreThanToday`, the function looks up the corresponding configuration in `filterConfig`, uses the dynamically calculated date stored in `today`, and applies the comparator `>=` to produce a query parameter like:
181
+
182
+ ```
183
+ filters=startDate>=2025-02-19
184
+ ```
185
+
186
+ This approach makes it easy to filter records based on dynamic criteria, such as dates relative to the current day.
187
+
188
+ ### How It Works
189
+
190
+ - **For `options.filters` (object-based filtering):**
191
+ The function handles nested filters, groups (using `groupValues` with `AND`/`OR` logic), and applies the specified comparators. This provides full flexibility when building complex queries.
192
+
193
+ - **For Legacy SASQ `nsValues.filters` (string-based filtering):**
194
+ The code splits the incoming comma-separated string into individual filters. For each filter:
195
+ - It checks if there’s a matching entry in `filterConfig`.
196
+ - If found, it applies the mapping from `filterConfig` and uses the comparator specified there (or defaults to `'=='`).
197
+ - If no mapping exists and no filter key is provided in `filterKeys`, the raw value is passed to the backend.
198
+
199
+ By utilizing these advanced filtering options, you can tailor your query string generation to meet complex backend requirements while maintaining clarity and control over your filter logic.
@@ -129,8 +129,11 @@ const generateKiwtQueryParams = (options, nsValues, encode = true) => {
129
129
  if (filterConfigEntry) {
130
130
  // We have a direct mapping instruction, use it
131
131
  const filterString = filterValues.map(v => {
132
- const fceValue = filterConfigEntry?.values?.find(fce => fce.name === v)?.value;
133
- return `${filterName}==${fceValue ?? v}`;
132
+ const fcValueEntry = filterConfigEntry?.values?.find(fce => fce.name === v) ?? {};
133
+ const fceValue = fcValueEntry.value ?? v;
134
+ // This is especially useful where comparator acts strangely for a single value, such as `filters=foo isNotSet`
135
+ const fceComparator = fcValueEntry.comparator ?? '==';
136
+ return `${filterKey ?? filterName}${fceComparator}${fceValue ?? v}`;
134
137
  }).join('||');
135
138
 
136
139
  paramsArray.push(`filters=${conditionalEncodeURIComponent(filterString, encode)}`);
@@ -0,0 +1 @@
1
+ export { default as generateKiwtQueryParams } from './generateKiwtQueryParams';
@@ -1,5 +1,5 @@
1
1
  export { default as generateKiwtQuery } from './generateKiwtQuery';
2
- export { default as generateKiwtQueryParams } from './generateKiwtQueryParams';
2
+ export { generateKiwtQueryParams } from './generateKiwtQueryParams';
3
3
  export { default as selectorSafe } from './selectorSafe';
4
4
 
5
5
  export { default as buildUrl } from './buildUrl';