@k-int/stripes-kint-components 5.25.3 → 5.27.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,23 @@
1
+ # [5.27.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.26.0...v5.27.0) (2025-10-21)
2
+
3
+
4
+ ### Features
5
+
6
+ * **generateKiwtQueryParams:** Added filterConfig.filterPrefix and filterConfig.valuesMapping options ([77915db](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/77915db6b9d189531e80e9ea8af3bbf82636b9cb))
7
+ * useStandaloneSASQQueryParameter for non-stripes filter options (not recommended) ([631ecfe](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/631ecfe4dae3d90b71e9468a1c2a8e2f30babb6b))
8
+
9
+ # [5.26.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.25.3...v5.26.0) (2025-10-15)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * Agreements/Licenses: Term / Suppl. property values disappear when other terms / optional properties of same category are deleted -- ERM-3813 ([37a70ac](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/37a70ac46b98146ca96f15cc39cf927bb32596a6))
15
+
16
+
17
+ ### Features
18
+
19
+ * Add ability to choose view URL identifier in SASQRoute (and children) via `getNavigationIdentifier` -- refs ERM-3804 ([8356b97](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/8356b97488bba5220ed2cb81017149527e444068))
20
+
1
21
  ## [5.25.3](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.25.2...v5.25.3) (2025-09-16)
2
22
 
3
23
 
@@ -89,7 +89,7 @@ const CustomPropertiesList = _ref => {
89
89
  onChange,
90
90
  setCustomProperties,
91
91
  value
92
- }, "customPropertyField-".concat(customProperty.value, "-").concat(index));
92
+ }, "customPropertyField-".concat(customProperty.value));
93
93
  }).filter(cp => cp !== undefined);
94
94
  };
95
95
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
@@ -41,6 +41,7 @@ const TableBody = _ref => {
41
41
  resultColumns,
42
42
  rowNavigation = true,
43
43
  // Default navigation onRowClick
44
+ getNavigationIdentifier = row => row === null || row === void 0 ? void 0 : row.id,
44
45
  toggleFilterPane,
45
46
  query
46
47
  } = _ref,
@@ -60,14 +61,14 @@ const TableBody = _ref => {
60
61
  // Build the list of visible columns
61
62
  const visibleColumns = resultColumns.map(e => e.propertyPath);
62
63
  const getRowUrl = (0, _react.useCallback)(rowData => {
63
- const baseUrl = "".concat(path, "/").concat(rowData === null || rowData === void 0 ? void 0 : rowData.id);
64
+ const baseUrl = "".concat(path, "/").concat(getNavigationIdentifier(rowData));
64
65
  return {
65
66
  url: "".concat(baseUrl).concat(location === null || location === void 0 ? void 0 : location.search),
66
67
  path,
67
68
  baseUrl,
68
69
  location
69
70
  };
70
- }, [location, path]);
71
+ }, [getNavigationIdentifier, location, path]);
71
72
  const getEnhancedFormatter = (0, _react.useCallback)(() => {
72
73
  const enhancedFormatter = {};
73
74
  for (const [key, value] of Object.entries(formatter)) {
@@ -90,8 +91,8 @@ const TableBody = _ref => {
90
91
  let {
91
92
  item
92
93
  } = _ref2;
93
- return item.id === (match === null || match === void 0 || (_match$params = match.params) === null || _match$params === void 0 ? void 0 : _match$params.id);
94
- }, [match === null || match === void 0 || (_match$params2 = match.params) === null || _match$params2 === void 0 ? void 0 : _match$params2.id]);
94
+ return getNavigationIdentifier(item) === (match === null || match === void 0 || (_match$params = match.params) === null || _match$params === void 0 ? void 0 : _match$params.id);
95
+ }, [getNavigationIdentifier, match === null || match === void 0 || (_match$params2 = match.params) === null || _match$params2 === void 0 ? void 0 : _match$params2.id]);
95
96
  const NoResultsComponent = (0, _react.useMemo)(() => {
96
97
  var _noResultsProps$compo;
97
98
  return (_noResultsProps$compo = noResultsProps === null || noResultsProps === void 0 ? void 0 : noResultsProps.component) !== null && _noResultsProps$compo !== void 0 ? _noResultsProps$compo : _NoResultsMessage.default;
@@ -148,6 +149,7 @@ TableBody.propTypes = {
148
149
  query: _propTypes.default.object,
149
150
  resultColumns: _propTypes.default.arrayOf(_propTypes.default.object),
150
151
  rowNavigation: _propTypes.default.bool,
152
+ getNavigationIdentifier: _propTypes.default.func,
151
153
  toggleFilterPane: _propTypes.default.func
152
154
  };
153
155
  var _default = exports.default = TableBody;
@@ -22,7 +22,8 @@ var _exportNames = {
22
22
  useMutateRefdataCategory: true,
23
23
  useMutateCustomProperties: true,
24
24
  useMutateModConfigEntry: true,
25
- usePrevNextPagination: true
25
+ usePrevNextPagination: true,
26
+ useStandaloneSASQQueryParameter: true
26
27
  };
27
28
  Object.defineProperty(exports, "useActionListRef", {
28
29
  enumerable: true,
@@ -132,6 +133,12 @@ Object.defineProperty(exports, "useSASQQueryMeta", {
132
133
  return _useSASQQueryMeta.default;
133
134
  }
134
135
  });
136
+ Object.defineProperty(exports, "useStandaloneSASQQueryParameter", {
137
+ enumerable: true,
138
+ get: function () {
139
+ return _useStandaloneSASQQueryParameter.default;
140
+ }
141
+ });
135
142
  Object.defineProperty(exports, "useTemplates", {
136
143
  enumerable: true,
137
144
  get: function () {
@@ -157,6 +164,7 @@ var _useMutateRefdataCategory = _interopRequireDefault(require("./useMutateRefda
157
164
  var _useMutateCustomProperties = _interopRequireDefault(require("./useMutateCustomProperties"));
158
165
  var _useMutateModConfigEntry = _interopRequireDefault(require("./useMutateModConfigEntry"));
159
166
  var _usePrevNextPagination = _interopRequireDefault(require("./usePrevNextPagination"));
167
+ var _useStandaloneSASQQueryParameter = _interopRequireDefault(require("./useStandaloneSASQQueryParameter"));
160
168
  var _intlHooks = require("./intlHooks");
161
169
  Object.keys(_intlHooks).forEach(function (key) {
162
170
  if (key === "default" || key === "__esModule") return;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "default", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _useStandaloneSASQQueryParameter.default;
10
+ }
11
+ });
12
+ var _useStandaloneSASQQueryParameter = _interopRequireDefault(require("./useStandaloneSASQQueryParameter"));
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = require("react");
8
+ var _reactRouter = require("react-router");
9
+ var _queryString = _interopRequireDefault(require("query-string"));
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
12
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
13
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
14
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
15
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
16
+ // !WARNING! This pattern is not recommended
17
+ // You likely want to make use of the filterConfig valuesMapping/filterPrefix
18
+ // instead so SASQ filters can be used natively
19
+ /**
20
+ * Custom hook for managing a single, standalone URL query parameter.
21
+ *
22
+ * This hook automatically reads a specified query parameter from the URL. If the
23
+ * parameter is missing on the initial load, it uses `defaultValue` and updates
24
+ * the URL to include it. It provides a `handler` function to update the
25
+ * parameter's value in the URL, which defaults to `fallbackValue` if no new
26
+ * value is explicitly provided to the handler.
27
+ *
28
+ * @param {object} props - The properties object.
29
+ * @param {string} props.name - The name of the query parameter to manage (e.g., 'showDetails').
30
+ * @param {string} [props.defaultValue='true'] - The value to set in the URL if the parameter is missing on initial load.
31
+ * @param {string} [props.fallbackValue='false'] - The value to set when the `handler` is called without a new value.
32
+ * @returns {{ handler: function, param: string }} An object containing the update handler and the current parameter value.
33
+ */
34
+ const useStandaloneSASQQueryParameter = _ref => {
35
+ let {
36
+ name,
37
+ defaultValue,
38
+ // This is the value that will be spun up if no value is present in the url
39
+ fallbackValue // This is the value that will be set if there is no value passed to handler (values[0])
40
+ } = _ref;
41
+ const location = (0, _reactRouter.useLocation)();
42
+ const history = (0, _reactRouter.useHistory)();
43
+
44
+ // Special URL management for includeTerminal
45
+ const urlQuery = _queryString.default.parse(location.search);
46
+ const param = urlQuery[name];
47
+ const handler = _ref2 => {
48
+ var _values$;
49
+ let {
50
+ name: _name,
51
+ values
52
+ } = _ref2;
53
+ const newQuery = _objectSpread(_objectSpread({}, urlQuery), {}, {
54
+ [name]: (_values$ = values[1]) !== null && _values$ !== void 0 ? _values$ : fallbackValue // SECOND value is the new one
55
+ });
56
+ history.push({
57
+ pathname: location.pathname,
58
+ search: "?".concat(_queryString.default.stringify(newQuery))
59
+ });
60
+ };
61
+ (0, _react.useEffect)(() => {
62
+ if (!param) {
63
+ const newQuery = _objectSpread(_objectSpread({}, urlQuery), {}, {
64
+ [name]: defaultValue
65
+ });
66
+ history.push({
67
+ pathname: location.pathname,
68
+ search: "?".concat(_queryString.default.stringify(newQuery))
69
+ });
70
+ }
71
+ }, [defaultValue, history, location.pathname, name, param, urlQuery]);
72
+ return {
73
+ handler,
74
+ param
75
+ };
76
+ };
77
+ var _default = exports.default = useStandaloneSASQQueryParameter;
@@ -82,6 +82,29 @@ const generateKiwtQueryParams = function (options, nsValues) {
82
82
  } = nsValues;
83
83
  const {
84
84
  searchKey = '',
85
+ /*
86
+ * Array of objects to configure how filters coming from `nsValues.filters` are mapped into KIWT filter strings.
87
+ *
88
+ * Example structure:
89
+ * [
90
+ * {
91
+ * name: 'status', // Matches filter key in URL (e.g., filters=status.active)
92
+ * filterPrefix: 'filters=', // Optional, defaults to 'filters='
93
+ * // Optional function to entirely customize how the array of values is mapped to a string.
94
+ * // (e.g., ['active', 'cancelled'] => 'is:active and is:cancelled')
95
+ * // Defaults to constructing from `values` map below
96
+ * // valuesMapping: (filterValues) => string,
97
+ * values: [ // Optional: Specific configuration for individual filter values
98
+ * {
99
+ * name: 'active', // Matches filter value in URL
100
+ * value: 'activeValue', // Actual value to use in the KIWT query (defaults to name)
101
+ * comparator: '==' // Optional comparator override (defaults to '==')
102
+ * },
103
+ * // ... other values
104
+ * ]
105
+ * }
106
+ * ]
107
+ */
85
108
  filterConfig = [],
86
109
  /* Assumtion made that if no filterKey is provided then the given filterValues for that key are standalaone, ie require no comparator or key */
87
110
  filterKeys = {},
@@ -153,8 +176,11 @@ const generateKiwtQueryParams = function (options, nsValues) {
153
176
  const filterConfigEntry = filterConfig.find(conf => conf.name === filterName);
154
177
  const filterKey = filterKeys[filterName];
155
178
  if (filterConfigEntry) {
179
+ var _filterConfigEntry$fi;
156
180
  // We have a direct mapping instruction, use it
157
- const filterString = filterValues.map(v => {
181
+
182
+ // Do we have a specific way to map values to a string?
183
+ const filterString = filterConfigEntry.valuesMapping ? filterConfigEntry.valuesMapping(filterValues) : filterValues.map(v => {
158
184
  var _filterConfigEntry$va, _filterConfigEntry$va2, _fcValueEntry$value, _fcValueEntry$compara;
159
185
  const fcValueEntry = (_filterConfigEntry$va = filterConfigEntry === null || filterConfigEntry === void 0 || (_filterConfigEntry$va2 = filterConfigEntry.values) === null || _filterConfigEntry$va2 === void 0 ? void 0 : _filterConfigEntry$va2.find(fce => fce.name === v)) !== null && _filterConfigEntry$va !== void 0 ? _filterConfigEntry$va : {};
160
186
  const fceValue = (_fcValueEntry$value = fcValueEntry.value) !== null && _fcValueEntry$value !== void 0 ? _fcValueEntry$value : v;
@@ -162,7 +188,10 @@ const generateKiwtQueryParams = function (options, nsValues) {
162
188
  const fceComparator = (_fcValueEntry$compara = fcValueEntry.comparator) !== null && _fcValueEntry$compara !== void 0 ? _fcValueEntry$compara : '==';
163
189
  return "".concat(filterKey !== null && filterKey !== void 0 ? filterKey : filterName).concat(fceComparator).concat(fceValue !== null && fceValue !== void 0 ? fceValue : v);
164
190
  }).join('||');
165
- paramsArray.push("filters=".concat(conditionalEncodeURIComponent(filterString, encode)));
191
+
192
+ // This will NOT be url encoded by this component
193
+ const filterPrefix = (_filterConfigEntry$fi = filterConfigEntry.filterPrefix) !== null && _filterConfigEntry$fi !== void 0 ? _filterConfigEntry$fi : 'filters=';
194
+ paramsArray.push("".concat(filterPrefix).concat(conditionalEncodeURIComponent(filterString, encode)));
166
195
  } else if (!filterKey) {
167
196
  // These filters have no key mapping so we just pass the values to the backend as-is.
168
197
  paramsArray.push(...(filterValues !== null && filterValues !== void 0 ? filterValues : []).map(f => "filters=".concat(conditionalEncodeURIComponent(f, encode))));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-int/stripes-kint-components",
3
- "version": "5.25.3",
3
+ "version": "5.27.0",
4
4
  "description": "Stripes Component library for K-Int specific applications",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -72,7 +72,7 @@ const CustomPropertiesList = ({
72
72
 
73
73
  return (
74
74
  <CustomPropertyFormCard
75
- key={`customPropertyField-${customProperty.value}-${index}`}
75
+ key={`customPropertyField-${customProperty.value}`}
76
76
  {...{
77
77
  availableCustomProperties,
78
78
  customProperty,
@@ -88,6 +88,7 @@ In this example, **SASQLookupComponent**:
88
88
  | `path` | `string` | (Provided by React Router) The path for the current route. | | ✓ |
89
89
  | `resource` | `object` | (Provided by Stripes) The resource object, typically containing data and metadata for the current resource. | | ✕ |
90
90
  | `resultColumns` | `arrayOf(object)` | An array of objects defining the columns for the MCL component, typically used by `RenderBody` (e.g., `TableBody`). | | ✕ |
91
+ | `...props` | `object` | Additional props will be passed down to the `RenderBody`. Use these to further customize behavior or appearance as needed. | | ✕ |
91
92
 
92
93
  ## Exposed Ref Functionality
93
94
 
@@ -48,15 +48,15 @@ export default MyTableView;
48
48
 
49
49
  ### Explanation
50
50
 
51
- - **data:**
51
+ - **data:**
52
52
  Contains `results` (array of items) and `totalRecords` (total items for pagination).
53
- - **query:**
53
+ - **query:**
54
54
  Provides current search/sort state. Must include `sort` and `query` if applicable.
55
- - **path:**
55
+ - **path:**
56
56
  Base URL path for constructing row navigation links (e.g., `/my-items/1`).
57
- - **resultColumns:**
57
+ - **resultColumns:**
58
58
  Defines table columns via `propertyPath` (data key) and `label` (header text).
59
- - **fetchNextPage:**
59
+ - **fetchNextPage:**
60
60
  Called when more data is needed (e.g., scrolling to the bottom).
61
61
 
62
62
  ## Props
@@ -80,19 +80,20 @@ export default MyTableView;
80
80
  | `resultColumns` | `Array<{ propertyPath: string, label: string }>` | ✓ | Columns to render. `propertyPath` accesses the data field, `label` sets the header. |
81
81
  | `rowNavigation` | `boolean` | ✕ | Enables navigation to detail views on row click. Default: `true`. |
82
82
  | `toggleFilterPane` | `() => void` | ✕ | Callback to toggle filter pane. Used in `NoResultsMessage`. |
83
+ | `getNavigationIdentifier` | `(row: object) => string \| number` | ✕ | When row navigation is enabled, this function parses the row data to find the unique identitifer for URL routing. Defaults to `(row) => row.id`. |
83
84
 
84
85
  ## Key Features
85
86
 
86
- 1. **Row Navigation:**
87
- When `rowNavigation={true}` (default), clicking a row navigates to `{path}/{rowData.id}`. Uses `react-router` internally.
87
+ 1. **Row Navigation:**
88
+ When `rowNavigation={true}` (default), clicking a row navigates to `{path}/{getNavigationIdentifier(row)}` (Default is `row.id`). Uses `react-router` internally.
88
89
 
89
- 2. **Sorting:**
90
+ 2. **Sorting:**
90
91
  Column headers trigger `onSort`, updating the `query.sort` value. The current sort is derived from `query.sort`.
91
92
 
92
- 3. **Infinite Scroll:**
93
+ 3. **Infinite Scroll:**
93
94
  `fetchNextPage` is called automatically as the user scrolls. Can alternatively use with pagination logic (e.g., `usePrevNextPagination` props passed to mclProps).
94
95
 
95
- 4. **Custom Formatters:**
96
+ 4. **Custom Formatters:**
96
97
  Pass a `formatter` in `mclProps` to customize cell rendering. Each formatter receives the row data and a `defaultRowUrl`:
97
98
  ```jsx
98
99
  mclProps={{
@@ -102,13 +103,13 @@ export default MyTableView;
102
103
  }}
103
104
  ```
104
105
 
105
- 5. **Empty States:**
106
+ 5. **Empty States:**
106
107
  `NoResultsMessage` handles loading, errors, and no-results states.
107
108
 
108
109
  ## Integration Notes
109
110
 
110
- - **Parent Components:**
111
+ - **Parent Components:**
111
112
  Designed to work within **SASQLookupComponent** (as its default `RenderBody`), but can be used standalone.
112
113
 
113
- - **Routing:**
114
- Must be rendered within a `react-router` context for navigation to work.
114
+ - **Routing:**
115
+ Must be rendered within a `react-router` context for navigation to work.
@@ -28,6 +28,7 @@ const TableBody = ({
28
28
  path,
29
29
  resultColumns,
30
30
  rowNavigation = true, // Default navigation onRowClick
31
+ getNavigationIdentifier = (row) => row?.id,
31
32
  toggleFilterPane,
32
33
  query,
33
34
  }) => {
@@ -48,14 +49,14 @@ const TableBody = ({
48
49
  const visibleColumns = resultColumns.map(e => e.propertyPath);
49
50
 
50
51
  const getRowUrl = useCallback((rowData) => {
51
- const baseUrl = `${path}/${rowData?.id}`;
52
+ const baseUrl = `${path}/${getNavigationIdentifier(rowData)}`;
52
53
  return {
53
54
  url: `${baseUrl}${location?.search}`,
54
55
  path,
55
56
  baseUrl,
56
57
  location
57
58
  };
58
- }, [location, path]);
59
+ }, [getNavigationIdentifier, location, path]);
59
60
 
60
61
  const getEnhancedFormatter = useCallback(() => {
61
62
  const enhancedFormatter = {};
@@ -76,7 +77,10 @@ const TableBody = ({
76
77
  return null;
77
78
  }, [getRowUrl, history, rowNavigation]);
78
79
 
79
- const isSelected = useCallback(({ item }) => item.id === match?.params?.id, [match?.params?.id]);
80
+ const isSelected = useCallback(
81
+ ({ item }) => getNavigationIdentifier(item) === match?.params?.id,
82
+ [getNavigationIdentifier, match?.params?.id]
83
+ );
80
84
  const NoResultsComponent = useMemo(() => noResultsProps?.component ?? NoResultsMessage, [noResultsProps?.component]);
81
85
 
82
86
  return (
@@ -139,6 +143,7 @@ TableBody.propTypes = {
139
143
  query: PropTypes.object,
140
144
  resultColumns: PropTypes.arrayOf(PropTypes.object),
141
145
  rowNavigation: PropTypes.bool,
146
+ getNavigationIdentifier: PropTypes.func,
142
147
  toggleFilterPane: PropTypes.func
143
148
  };
144
149
 
@@ -19,5 +19,6 @@ export { default as useMutateCustomProperties } from './useMutateCustomPropertie
19
19
  export { default as useMutateModConfigEntry } from './useMutateModConfigEntry';
20
20
 
21
21
  export { default as usePrevNextPagination } from './usePrevNextPagination';
22
+ export { default as useStandaloneSASQQueryParameter } from './useStandaloneSASQQueryParameter';
22
23
 
23
24
  export * from './intlHooks';
@@ -0,0 +1 @@
1
+ export { default } from './useStandaloneSASQQueryParameter';
@@ -0,0 +1,67 @@
1
+ import { useEffect } from 'react';
2
+ import { useHistory, useLocation } from 'react-router';
3
+ import queryString from 'query-string';
4
+
5
+ // !WARNING! This pattern is not recommended
6
+ // You likely want to make use of the filterConfig valuesMapping/filterPrefix
7
+ // instead so SASQ filters can be used natively
8
+ /**
9
+ * Custom hook for managing a single, standalone URL query parameter.
10
+ *
11
+ * This hook automatically reads a specified query parameter from the URL. If the
12
+ * parameter is missing on the initial load, it uses `defaultValue` and updates
13
+ * the URL to include it. It provides a `handler` function to update the
14
+ * parameter's value in the URL, which defaults to `fallbackValue` if no new
15
+ * value is explicitly provided to the handler.
16
+ *
17
+ * @param {object} props - The properties object.
18
+ * @param {string} props.name - The name of the query parameter to manage (e.g., 'showDetails').
19
+ * @param {string} [props.defaultValue='true'] - The value to set in the URL if the parameter is missing on initial load.
20
+ * @param {string} [props.fallbackValue='false'] - The value to set when the `handler` is called without a new value.
21
+ * @returns {{ handler: function, param: string }} An object containing the update handler and the current parameter value.
22
+ */
23
+ const useStandaloneSASQQueryParameter = ({
24
+ name,
25
+ defaultValue, // This is the value that will be spun up if no value is present in the url
26
+ fallbackValue, // This is the value that will be set if there is no value passed to handler (values[0])
27
+ }) => {
28
+ const location = useLocation();
29
+ const history = useHistory();
30
+
31
+ // Special URL management for includeTerminal
32
+ const urlQuery = queryString.parse(location.search);
33
+ const param = urlQuery[name];
34
+
35
+ const handler = ({ name: _name, values }) => {
36
+ const newQuery = {
37
+ ...urlQuery,
38
+ [name]: values[1] ?? fallbackValue // SECOND value is the new one
39
+ };
40
+
41
+ history.push({
42
+ pathname: location.pathname,
43
+ search: `?${queryString.stringify(newQuery)}`
44
+ });
45
+ };
46
+
47
+ useEffect(() => {
48
+ if (!param) {
49
+ const newQuery = {
50
+ ...urlQuery,
51
+ [name]: defaultValue
52
+ };
53
+
54
+ history.push({
55
+ pathname: location.pathname,
56
+ search: `?${queryString.stringify(newQuery)}`
57
+ });
58
+ }
59
+ }, [defaultValue, history, location.pathname, name, param, urlQuery]);
60
+
61
+ return {
62
+ handler,
63
+ param
64
+ };
65
+ };
66
+
67
+ export default useStandaloneSASQQueryParameter;
@@ -67,6 +67,29 @@ const generateKiwtQueryParams = (options, nsValues, encode = true) => {
67
67
  const { qindex, query, filters, sort } = nsValues;
68
68
  const {
69
69
  searchKey = '',
70
+ /*
71
+ * Array of objects to configure how filters coming from `nsValues.filters` are mapped into KIWT filter strings.
72
+ *
73
+ * Example structure:
74
+ * [
75
+ * {
76
+ * name: 'status', // Matches filter key in URL (e.g., filters=status.active)
77
+ * filterPrefix: 'filters=', // Optional, defaults to 'filters='
78
+ * // Optional function to entirely customize how the array of values is mapped to a string.
79
+ * // (e.g., ['active', 'cancelled'] => 'is:active and is:cancelled')
80
+ * // Defaults to constructing from `values` map below
81
+ * // valuesMapping: (filterValues) => string,
82
+ * values: [ // Optional: Specific configuration for individual filter values
83
+ * {
84
+ * name: 'active', // Matches filter value in URL
85
+ * value: 'activeValue', // Actual value to use in the KIWT query (defaults to name)
86
+ * comparator: '==' // Optional comparator override (defaults to '==')
87
+ * },
88
+ * // ... other values
89
+ * ]
90
+ * }
91
+ * ]
92
+ */
70
93
  filterConfig = [],
71
94
  /* Assumtion made that if no filterKey is provided then the given filterValues for that key are standalaone, ie require no comparator or key */
72
95
  filterKeys = {},
@@ -140,15 +163,22 @@ const generateKiwtQueryParams = (options, nsValues, encode = true) => {
140
163
  const filterKey = filterKeys[filterName];
141
164
  if (filterConfigEntry) {
142
165
  // We have a direct mapping instruction, use it
143
- const filterString = filterValues.map(v => {
144
- const fcValueEntry = filterConfigEntry?.values?.find(fce => fce.name === v) ?? {};
145
- const fceValue = fcValueEntry.value ?? v;
146
- // This is especially useful where comparator acts strangely for a single value, such as `filters=foo isNotSet`
147
- const fceComparator = fcValueEntry.comparator ?? '==';
148
- return `${filterKey ?? filterName}${fceComparator}${fceValue ?? v}`;
149
- }).join('||');
150
166
 
151
- paramsArray.push(`filters=${conditionalEncodeURIComponent(filterString, encode)}`);
167
+ // Do we have a specific way to map values to a string?
168
+ const filterString = filterConfigEntry.valuesMapping ?
169
+ filterConfigEntry.valuesMapping(filterValues) :
170
+ filterValues.map(v => {
171
+ const fcValueEntry = filterConfigEntry?.values?.find(fce => fce.name === v) ?? {};
172
+ const fceValue = fcValueEntry.value ?? v;
173
+ // This is especially useful where comparator acts strangely for a single value, such as `filters=foo isNotSet`
174
+ const fceComparator = fcValueEntry.comparator ?? '==';
175
+ return `${filterKey ?? filterName}${fceComparator}${fceValue ?? v}`;
176
+ }).join('||');
177
+
178
+ // This will NOT be url encoded by this component
179
+ const filterPrefix = filterConfigEntry.filterPrefix ?? 'filters=';
180
+
181
+ paramsArray.push(`${filterPrefix}${conditionalEncodeURIComponent(filterString, encode)}`);
152
182
  } else if (!filterKey) {
153
183
  // These filters have no key mapping so we just pass the values to the backend as-is.
154
184
  paramsArray.push(...(filterValues ?? []).map(f => `filters=${conditionalEncodeURIComponent(f, encode)}`));