@trackunit/filters-filter-bar 0.0.492 → 0.0.496
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/index.cjs.js +388 -232
- package/index.esm.js +390 -235
- package/package.json +3 -2
- package/src/lib/components/FilterHeader.d.ts +4 -2
- package/src/lib/components/SearchFilterInline.d.ts +21 -0
- package/src/lib/components/StarredFilters.d.ts +1 -1
- package/src/lib/components/StarredFiltersMenu.d.ts +3 -2
- package/src/lib/hooks/mockFilterBar.d.ts +2 -0
- package/src/lib/hooks/useFilterBar.d.ts +3 -2
- package/src/lib/index.d.ts +2 -1
- package/src/lib/types/FilterTypes.d.ts +3 -2
- package/src/translation.d.ts +2 -2
- package/translation.cjs.js +1 -1
- package/translation.cjs8.js +1 -1
- package/translation.esm.js +1 -1
- package/translation.esm8.js +1 -1
package/index.cjs.js
CHANGED
|
@@ -2,19 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var i18nLibraryTranslation = require('@trackunit/i18n-library-translation');
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var reactComponents = require('@trackunit/react-components');
|
|
5
|
+
var reactCoreHooks = require('@trackunit/react-core-hooks');
|
|
6
|
+
var reactFilterComponents = require('@trackunit/react-filter-components');
|
|
8
7
|
var sharedUtils = require('@trackunit/shared-utils');
|
|
9
8
|
var react = require('react');
|
|
10
|
-
var reactFilterComponents = require('@trackunit/react-filter-components');
|
|
11
|
-
var reactCoreHooks = require('@trackunit/react-core-hooks');
|
|
12
9
|
var reactCoreContextsApi = require('@trackunit/react-core-contexts-api');
|
|
10
|
+
var reactComponents = require('@trackunit/react-components');
|
|
13
11
|
var reactFormComponents = require('@trackunit/react-form-components');
|
|
14
12
|
var reactDateAndTimeComponents = require('@trackunit/react-date-and-time-components');
|
|
13
|
+
var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
|
|
14
|
+
var tailwindMerge = require('tailwind-merge');
|
|
15
|
+
var zod = require('zod');
|
|
15
16
|
var dequal = require('dequal');
|
|
16
|
-
var stringTs = require('string-ts');
|
|
17
17
|
var isEqual = require('lodash/isEqual');
|
|
18
|
+
var reduce = require('lodash/reduce');
|
|
19
|
+
var stringTs = require('string-ts');
|
|
18
20
|
|
|
19
21
|
var defaultTranslations = {
|
|
20
22
|
"access.management.filter.operator.role.keyAdmin": "Key Admin",
|
|
@@ -82,8 +84,8 @@ var defaultTranslations = {
|
|
|
82
84
|
"assetFilters.regions.WESTERN_ASIA": "Western Asia",
|
|
83
85
|
"assetFilters.regions.WESTERN_EUROPE": "Western Europe",
|
|
84
86
|
"assetFilters.regions.WHOLE_WORLD": "Whole world",
|
|
85
|
-
"assetFilters.searchFilter.label": "
|
|
86
|
-
"assetFilters.searchPlaceholder": "Search...",
|
|
87
|
+
"assetFilters.searchFilter.label": "Text search",
|
|
88
|
+
"assetFilters.searchPlaceholder": "Search by text...",
|
|
87
89
|
"assetFilters.serviceAssignmentStatusFilter.label": "Assignment Status",
|
|
88
90
|
"assetFilters.servicePlanFilter.fullyConfigured": "Fully configured",
|
|
89
91
|
"assetFilters.servicePlanFilter.label": "Service Plan",
|
|
@@ -105,6 +107,12 @@ var defaultTranslations = {
|
|
|
105
107
|
"auditlog.filter.users": "Users",
|
|
106
108
|
"filter.more.options.if.you.search.description": "Use search to refine results.",
|
|
107
109
|
"filter.more.options.if.you.search.title": "Over {{count}} items found.",
|
|
110
|
+
"filters.shared.clear": "clear",
|
|
111
|
+
"filters.shared.clearAll": "clear all",
|
|
112
|
+
"filters.shared.recentSearches": "Recent searches",
|
|
113
|
+
"filtersBar.appliedFiltersTooltip.none": "No filters applied",
|
|
114
|
+
"filtersBar.appliedFiltersTooltip.plural": "{{count}} filters applied:",
|
|
115
|
+
"filtersBar.appliedFiltersTooltip.singular": "{{filterName}} filter applied",
|
|
108
116
|
"filtersBar.closeFilter": "Close",
|
|
109
117
|
"filtersBar.defaultAssetFilters.followedFilter.ALL": "All Assets",
|
|
110
118
|
"filtersBar.defaultAssetFilters.followedFilter.allLabel": "All Assets",
|
|
@@ -304,128 +312,6 @@ const setupLibraryTranslations = () => {
|
|
|
304
312
|
i18nLibraryTranslation.registerTranslations(translations);
|
|
305
313
|
};
|
|
306
314
|
|
|
307
|
-
/**
|
|
308
|
-
* Custom React hook for grouping and filtering a list of filters.
|
|
309
|
-
*
|
|
310
|
-
* @param {FilterDefinition[]} filterDefinitions - The list of filter definitions to group and filter.
|
|
311
|
-
* @param {string[]} hiddenFilters - An array of filter keys representing filters to be hidden.
|
|
312
|
-
* @returns {UseGroupFiltersReturnType} An object containing the grouped filters.
|
|
313
|
-
*/
|
|
314
|
-
const useStarredGroupFilters = (filterDefinitions, hiddenFilters) => {
|
|
315
|
-
const [t] = useTranslation();
|
|
316
|
-
const filtersGrouped = react.useMemo(() => {
|
|
317
|
-
const keys = uniqueKeysFromGroups(filterDefinitions);
|
|
318
|
-
return keys
|
|
319
|
-
.map(key => ({
|
|
320
|
-
key: t(`filtersBar.groups.${key}`),
|
|
321
|
-
filters: filterDefinitions.filter(rawFilter => rawFilter.group === key && hiddenFilters.includes(rawFilter.filterKey) === false),
|
|
322
|
-
}))
|
|
323
|
-
.filter(filter => filter.filters.length > 0);
|
|
324
|
-
}, [filterDefinitions, hiddenFilters, t]);
|
|
325
|
-
return { filtersGrouped };
|
|
326
|
-
};
|
|
327
|
-
const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Returns the two first values, appends counter if more.
|
|
331
|
-
*
|
|
332
|
-
* @param {string[]} [input] Array of values to reduce
|
|
333
|
-
* @returns {*} {string}
|
|
334
|
-
*/
|
|
335
|
-
const reduceFilterText = (input) => {
|
|
336
|
-
if (!Array.isArray(input)) {
|
|
337
|
-
return input;
|
|
338
|
-
}
|
|
339
|
-
// Only first two elements
|
|
340
|
-
const firstTwo = input.slice(0, 2);
|
|
341
|
-
const remainder = input.slice(2, input.length).length;
|
|
342
|
-
const firstTwoJoined = firstTwo.join(", ");
|
|
343
|
-
const remainderStr = remainder > 0 ? `, +${remainder}` : "";
|
|
344
|
-
return firstTwoJoined + remainderStr;
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Filter is a React component that renders a filter element based on the provided filter definition and state.
|
|
349
|
-
*
|
|
350
|
-
* @returns {JSX.Element} - Returns the Filter component.
|
|
351
|
-
*/
|
|
352
|
-
const Filter = ({ filter, filterBarActions, filterState, }) => {
|
|
353
|
-
const values = filterBarActions.getValuesByKey(filter.filterKey);
|
|
354
|
-
const filterText = () => {
|
|
355
|
-
if (values) {
|
|
356
|
-
if (filter.valueAsText) {
|
|
357
|
-
return reduceFilterText(filter.valueAsText(values));
|
|
358
|
-
}
|
|
359
|
-
else if (filter.type === "valueName") {
|
|
360
|
-
return reduceFilterText(values.map(value => value.name));
|
|
361
|
-
}
|
|
362
|
-
return values.toString();
|
|
363
|
-
}
|
|
364
|
-
return "";
|
|
365
|
-
};
|
|
366
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
367
|
-
const setValue = (callback) => {
|
|
368
|
-
const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
|
|
369
|
-
if (filter.type === "string") {
|
|
370
|
-
filterBarActions.setStringValue(filter.filterKey, newValue);
|
|
371
|
-
}
|
|
372
|
-
else if (filter.type === "stringArray") {
|
|
373
|
-
filterBarActions.setArrayValue(filter.filterKey, newValue);
|
|
374
|
-
}
|
|
375
|
-
else if (filter.type === "dateRange") {
|
|
376
|
-
filterBarActions.setDateRange(filter.filterKey, newValue);
|
|
377
|
-
}
|
|
378
|
-
else if (filter.type === "boundingBox") {
|
|
379
|
-
filterBarActions.setBoundingBox(newValue);
|
|
380
|
-
}
|
|
381
|
-
else if (filter.type === "valueName") {
|
|
382
|
-
filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
|
|
383
|
-
}
|
|
384
|
-
else if (filter.type === "minMax") {
|
|
385
|
-
filterBarActions.setMinMaxValue(filter.filterKey, newValue);
|
|
386
|
-
}
|
|
387
|
-
else if (filter.type === "boolean") {
|
|
388
|
-
filterBarActions.setBooleanValue(filter.filterKey, newValue);
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
filterBarActions.setNumberValue(filter.filterKey, newValue);
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
const text = filterText();
|
|
395
|
-
const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
|
|
396
|
-
const showDirectly = filter.showDirectly || false;
|
|
397
|
-
return showDirectly ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: filter.component({
|
|
398
|
-
filterDefinition: filter,
|
|
399
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
400
|
-
value: values,
|
|
401
|
-
setValue,
|
|
402
|
-
filterBarActions,
|
|
403
|
-
filterState,
|
|
404
|
-
}) })) : (jsxRuntime.jsx(reactFilterComponents.Filter, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterText().length > 0, title: filter.title, withStickyHeader: true, children: filter.component({
|
|
405
|
-
filterDefinition: filter,
|
|
406
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
407
|
-
value: values,
|
|
408
|
-
setValue,
|
|
409
|
-
filterBarActions,
|
|
410
|
-
filterState,
|
|
411
|
-
}) }));
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* ResetFiltersButton is a React component that provides a button for resetting filters.
|
|
416
|
-
*
|
|
417
|
-
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
418
|
-
*/
|
|
419
|
-
const ResetFiltersButton = ({ filtersHaveBeenApplied, resetFiltersToInitialState, }) => {
|
|
420
|
-
const [t] = useTranslation();
|
|
421
|
-
if (!filtersHaveBeenApplied) {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
return (jsxRuntime.jsx(reactComponents.Button, { dataTestId: "reset-filters-button", onClick: () => {
|
|
425
|
-
resetFiltersToInitialState();
|
|
426
|
-
}, size: "small", variant: "ghost-neutral", children: t("filtersBar.resetFilters") }));
|
|
427
|
-
};
|
|
428
|
-
|
|
429
315
|
/**
|
|
430
316
|
* Events — each with a name, and eventType.
|
|
431
317
|
* Adding a Description is encouraged.
|
|
@@ -441,101 +327,6 @@ const FilterEvents = {
|
|
|
441
327
|
"Starring Filter - Toggled": reactCoreContextsApi.createEvent(),
|
|
442
328
|
};
|
|
443
329
|
|
|
444
|
-
/**
|
|
445
|
-
* StarredFiltersMenu is a React component that displays a menu of starred filters within a filter bar.
|
|
446
|
-
*
|
|
447
|
-
* @returns {JSX.Element} - Returns the StarredFiltersMenu component.
|
|
448
|
-
*/
|
|
449
|
-
const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starredFilterKeys, hiddenFilters = [], }) => {
|
|
450
|
-
const [t] = useTranslation();
|
|
451
|
-
const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
|
|
452
|
-
const hideInStarredMenu = react.useMemo(() => {
|
|
453
|
-
return sharedUtils.objectValues(filterBarDefinition)
|
|
454
|
-
.map(filter => {
|
|
455
|
-
const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
|
|
456
|
-
const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
|
|
457
|
-
return !showInStarredMenu || !showInFilterBar ? filter.filterKey : null;
|
|
458
|
-
})
|
|
459
|
-
.filter(sharedUtils.truthy);
|
|
460
|
-
}, [filterBarDefinition]);
|
|
461
|
-
const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
|
|
462
|
-
...hideInStarredMenu,
|
|
463
|
-
...hiddenFilters,
|
|
464
|
-
]);
|
|
465
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.IconButton, { dataTestId: "starred-filters-menu", icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Funnel", size: "small" }), size: "small", title: t("filtersBar.starredFiltersTitle") }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx(reactComponents.MenuList, { className: "max-h-[55vh] w-72 overflow-y-auto", dataTestId: "starred-filters-menu-popover", children: jsxRuntime.jsx("div", { children: filtersGrouped.map((group, idx) => {
|
|
466
|
-
const isLast = idx === filtersGrouped.length - 1;
|
|
467
|
-
return (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "mt-2 mb-1 flex px-2", children: jsxRuntime.jsx(reactComponents.Text, { dataTestId: "starred-filters-group-title", size: "small", subtle: true, uppercase: true, weight: "bold", children: group.key }) }), group.filters.map(filter => {
|
|
468
|
-
return (jsxRuntime.jsxs("div", { className: "flex w-full cursor-pointer items-center justify-between rounded-sm py-0 px-2 transition hover:bg-neutral-50", "data-testid": `${filter.filterKey}-star-filter`, onClick: () => {
|
|
469
|
-
updateStarredFilters(filter.filterKey);
|
|
470
|
-
logEvent("Starring Filter - Toggled", {
|
|
471
|
-
type: filter.filterKey,
|
|
472
|
-
});
|
|
473
|
-
}, children: [jsxRuntime.jsx(reactComponents.Text, { weight: "thick", children: filter.title }), jsxRuntime.jsx(reactComponents.StarButton, { onClick: e => {
|
|
474
|
-
e.preventDefault();
|
|
475
|
-
e.stopPropagation();
|
|
476
|
-
updateStarredFilters(filter.filterKey);
|
|
477
|
-
logEvent("Starring Filter - Toggled", {
|
|
478
|
-
type: filter.filterKey,
|
|
479
|
-
});
|
|
480
|
-
}, starred: starredFilterKeys.includes(filter.filterKey) })] }, filter.filterKey));
|
|
481
|
-
}), !isLast && jsxRuntime.jsx("div", { className: "mt-2 h-[1px] w-full bg-neutral-300" })] }, group.key));
|
|
482
|
-
}) }) }) })] }), starredFilterKeys.length > 0 ? jsxRuntime.jsx("div", { className: "h-7 w-[1px] bg-neutral-300" }) : null] }));
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
const cvaCollapse = cssClassVarianceUtilities.cvaMerge("bg-white", {
|
|
486
|
-
variants: {
|
|
487
|
-
disableCollapse: { true: "hidden" },
|
|
488
|
-
},
|
|
489
|
-
});
|
|
490
|
-
const cvaFilterWithoutCollapse = cssClassVarianceUtilities.cvaMerge([], {
|
|
491
|
-
variants: { disableCollapse: { true: "flex", false: ["hidden", "md:flex"] } },
|
|
492
|
-
});
|
|
493
|
-
/**
|
|
494
|
-
* StarredFilters is a React component that displays a list of starred filters based on the provided filter bar configuration.
|
|
495
|
-
*
|
|
496
|
-
* @template TFilterBarDefinition - The type representing the filter bar definition.
|
|
497
|
-
* @returns {JSX.Element} - Returns the StarredFilters component.
|
|
498
|
-
*/
|
|
499
|
-
const StarredFilters = ({ filterBarDefinition, filterBarConfig, disableCollapse, hiddenFilters = [], }) => {
|
|
500
|
-
const [expanded, setExpanded] = react.useState(false);
|
|
501
|
-
const [t] = useTranslation();
|
|
502
|
-
const { isMd } = reactComponents.useViewportSize();
|
|
503
|
-
return isMd || disableCollapse ? (jsxRuntime.jsx(FilterSelection, { className: cvaFilterWithoutCollapse({ disableCollapse }), filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters })) : (jsxRuntime.jsx(reactComponents.Collapse, { className: cvaCollapse({ disableCollapse }), id: "storybook_collapsible", initialExpanded: expanded, label: t("filtersBar.filtersHeading") + (filterBarConfig.filtersHaveBeenApplied ? " *" : ""), onToggle: () => setExpanded(prev => !prev), children: jsxRuntime.jsx(FilterSelection, { className: "flex", filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }));
|
|
504
|
-
};
|
|
505
|
-
const FilterSelection = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], className, }) => {
|
|
506
|
-
const hideInFilterBar = react.useMemo(() => {
|
|
507
|
-
return sharedUtils.objectValues(filterBarDefinition)
|
|
508
|
-
.map(filter => {
|
|
509
|
-
const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
|
|
510
|
-
return !showInFilterBar ? filter.filterKey : null;
|
|
511
|
-
})
|
|
512
|
-
.filter(sharedUtils.truthy);
|
|
513
|
-
}, [filterBarDefinition]);
|
|
514
|
-
const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
|
|
515
|
-
...hideInFilterBar,
|
|
516
|
-
...hiddenFilters,
|
|
517
|
-
]);
|
|
518
|
-
return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge([className, "flex-wrap", "items-center", "gap-3"]), "data-testid": "starred-filters", children: [jsxRuntime.jsx(StarredFiltersMenu, { filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), filtersGrouped.map(group => group.filters
|
|
519
|
-
.filter(filter => !hiddenFilters.includes(filter.filterKey))
|
|
520
|
-
.map(filter => {
|
|
521
|
-
const showMenuAnyway = filterBarConfig.filterHasChanged(filter.filterKey) &&
|
|
522
|
-
filter.showInStarredMenu &&
|
|
523
|
-
!filter.showInStarredMenu();
|
|
524
|
-
if (filterBarConfig.starredFilterKeys.includes(filter.filterKey) ||
|
|
525
|
-
showMenuAnyway) {
|
|
526
|
-
return (jsxRuntime.jsx(react.Fragment, { children: jsxRuntime.jsx(Filter, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters } }, `filter-${filter.filterKey}`) }, filter.filterKey));
|
|
527
|
-
}
|
|
528
|
-
return null;
|
|
529
|
-
})), jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.filtersHaveBeenApplied, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] }));
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* The FilterBar component serves as a wrapper for managing filters.
|
|
534
|
-
*/
|
|
535
|
-
const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, disableCollapse = false, }) => {
|
|
536
|
-
return (jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("flex", className), "data-testid": `${filterBarConfig.name}-filterbar`, children: jsxRuntime.jsx(StarredFilters, { disableCollapse: disableCollapse, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }));
|
|
537
|
-
};
|
|
538
|
-
|
|
539
330
|
/**
|
|
540
331
|
* Toggles a filter value in an array.
|
|
541
332
|
*
|
|
@@ -561,7 +352,7 @@ const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked
|
|
|
561
352
|
const [t] = useTranslation();
|
|
562
353
|
const parentRef = react.useRef(null);
|
|
563
354
|
const updatedRowCount = react.useMemo(() => (showRequestMoreUseSearch ? rowCount + 1 : rowCount), [rowCount, showRequestMoreUseSearch]);
|
|
564
|
-
return (jsxRuntime.jsx(reactFilterComponents.FilterBody, {
|
|
355
|
+
return (jsxRuntime.jsx(reactFilterComponents.FilterBody, { limitSize: true, ref: parentRef, children: jsxRuntime.jsx(reactComponents.VirtualizedList, { count: updatedRowCount, rowHeight: 40, separator: "space", children: index => {
|
|
565
356
|
return (jsxRuntime.jsx("div", { children: showRequestMoreUseSearch && index === rowCount ? (jsxRuntime.jsxs("div", { className: "p-3 pt-2", children: [jsxRuntime.jsx("span", { className: "text-sm text-gray-600", children: t("filter.more.options.if.you.search.title", { count: rowCount }) }), jsxRuntime.jsx("br", {}), jsxRuntime.jsx("span", { className: "text-sm italic text-gray-400", children: t("filter.more.options.if.you.search.description") })] })) : type === "Radio" ? (jsxRuntime.jsx(reactFilterComponents.RadioFilterItem, { dataTestId: "dynamic-filter-radio-" + keyMapper(index), itemCount: count(index), label: labelMapper(index), selected: checked(index), value: keyMapper(index) }, keyMapper(index))) : (jsxRuntime.jsx(reactFilterComponents.CheckBoxFilterItem, { checked: checked(index), dataTestId: "dynamic-filter-check-box-" + keyMapper(index), itemCount: count(index), label: labelMapper(index), name: keyMapper(index), onChange: () => {
|
|
566
357
|
onChange === null || onChange === void 0 ? void 0 : onChange(index);
|
|
567
358
|
} }, keyMapper(index))) }));
|
|
@@ -573,13 +364,17 @@ const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked
|
|
|
573
364
|
*
|
|
574
365
|
* @returns {JSX.Element} - Returns the FilterHeader component.
|
|
575
366
|
*/
|
|
576
|
-
const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanged, resetIndividualFilterToInitialState, onResetFilter, loading = false, }) => {
|
|
367
|
+
const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanged, resetIndividualFilterToInitialState, onResetFilter, loading = false, className, dataTestId, }) => {
|
|
577
368
|
const [t] = useTranslation();
|
|
578
369
|
const handleResetFilter = () => {
|
|
579
370
|
resetIndividualFilterToInitialState(filterKey);
|
|
580
371
|
onResetFilter === null || onResetFilter === void 0 ? void 0 : onResetFilter();
|
|
581
372
|
};
|
|
582
|
-
return (jsxRuntime.jsx(reactFilterComponents.FilterHeader, { dataTestId: `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), showReset: filterHasChanged(filterKey), title: title, children: searchEnabled ? (jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value),
|
|
373
|
+
return (jsxRuntime.jsx(reactFilterComponents.FilterHeader, { className: className, dataTestId: dataTestId !== null && dataTestId !== void 0 ? dataTestId : `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), showReset: filterHasChanged(filterKey), title: title, children: searchEnabled ? (jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value), onKeyDown: e => {
|
|
374
|
+
if (e.key === "Enter" && searchProps.onEnter) {
|
|
375
|
+
searchProps.onEnter(searchProps.value);
|
|
376
|
+
}
|
|
377
|
+
}, placeholder: t("assetFilters.searchPlaceholder"), suffix: jsxRuntime.jsx(reactComponents.Text, { size: "small", subtle: true, children: searchProps.count }), value: searchProps.value })) : null }));
|
|
583
378
|
};
|
|
584
379
|
|
|
585
380
|
/**
|
|
@@ -794,6 +589,351 @@ const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loadi
|
|
|
794
589
|
}, value: selectedRadioId, children: jsxRuntime.jsx(DynamicFilterList, { checked: index => { var _a; return filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""); }, count: index => { var _a; return (_a = res[index]) === null || _a === void 0 ? void 0 : _a.count; }, keyMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.key) || ""; }, labelMapper: index => { var _a; return ((_a = res[index]) === null || _a === void 0 ? void 0 : _a.label) || ""; }, rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
|
|
795
590
|
};
|
|
796
591
|
|
|
592
|
+
/**
|
|
593
|
+
* Custom React hook for grouping and filtering a list of filters.
|
|
594
|
+
*
|
|
595
|
+
* @param {FilterDefinition[]} filterDefinitions - The list of filter definitions to group and filter.
|
|
596
|
+
* @param {string[]} hiddenFilters - An array of filter keys representing filters to be hidden.
|
|
597
|
+
* @returns {UseGroupFiltersReturnType} An object containing the grouped filters.
|
|
598
|
+
*/
|
|
599
|
+
const useStarredGroupFilters = (filterDefinitions, hiddenFilters) => {
|
|
600
|
+
const [t] = useTranslation();
|
|
601
|
+
const filtersGrouped = react.useMemo(() => {
|
|
602
|
+
const keys = uniqueKeysFromGroups(filterDefinitions);
|
|
603
|
+
return keys
|
|
604
|
+
.map(key => ({
|
|
605
|
+
key: t(`filtersBar.groups.${key}`),
|
|
606
|
+
filters: filterDefinitions.filter(rawFilter => rawFilter.group === key && hiddenFilters.includes(rawFilter.filterKey) === false),
|
|
607
|
+
}))
|
|
608
|
+
.filter(filter => filter.filters.length > 0);
|
|
609
|
+
}, [filterDefinitions, hiddenFilters, t]);
|
|
610
|
+
return { filtersGrouped };
|
|
611
|
+
};
|
|
612
|
+
const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Returns the two first values, appends counter if more.
|
|
616
|
+
*
|
|
617
|
+
* @param {string[]} [input] Array of values to reduce
|
|
618
|
+
* @returns {*} {string}
|
|
619
|
+
*/
|
|
620
|
+
const reduceFilterText = (input) => {
|
|
621
|
+
if (!Array.isArray(input)) {
|
|
622
|
+
return input;
|
|
623
|
+
}
|
|
624
|
+
// Only first two elements
|
|
625
|
+
const firstTwo = input.slice(0, 2);
|
|
626
|
+
const remainder = input.slice(2, input.length).length;
|
|
627
|
+
const firstTwoJoined = firstTwo.join(", ");
|
|
628
|
+
const remainderStr = remainder > 0 ? `, +${remainder}` : "";
|
|
629
|
+
return firstTwoJoined + remainderStr;
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Filter is a React component that renders a filter element based on the provided filter definition and state.
|
|
634
|
+
*
|
|
635
|
+
* @returns {JSX.Element} - Returns the Filter component.
|
|
636
|
+
*/
|
|
637
|
+
const Filter = ({ filter, filterBarActions, filterState, }) => {
|
|
638
|
+
const values = filterBarActions.getValuesByKey(filter.filterKey);
|
|
639
|
+
const filterText = () => {
|
|
640
|
+
if (values) {
|
|
641
|
+
if (filter.valueAsText) {
|
|
642
|
+
return reduceFilterText(filter.valueAsText(values));
|
|
643
|
+
}
|
|
644
|
+
else if (filter.type === "valueName") {
|
|
645
|
+
return reduceFilterText(values.map(value => value.name));
|
|
646
|
+
}
|
|
647
|
+
return values.toString();
|
|
648
|
+
}
|
|
649
|
+
return "";
|
|
650
|
+
};
|
|
651
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
652
|
+
const setValue = (callback) => {
|
|
653
|
+
const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
|
|
654
|
+
if (filter.type === "string") {
|
|
655
|
+
filterBarActions.setStringValue(filter.filterKey, newValue);
|
|
656
|
+
}
|
|
657
|
+
else if (filter.type === "stringArray") {
|
|
658
|
+
filterBarActions.setArrayValue(filter.filterKey, newValue);
|
|
659
|
+
}
|
|
660
|
+
else if (filter.type === "dateRange") {
|
|
661
|
+
filterBarActions.setDateRange(filter.filterKey, newValue);
|
|
662
|
+
}
|
|
663
|
+
else if (filter.type === "boundingBox") {
|
|
664
|
+
filterBarActions.setBoundingBox(newValue);
|
|
665
|
+
}
|
|
666
|
+
else if (filter.type === "valueName") {
|
|
667
|
+
filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
|
|
668
|
+
}
|
|
669
|
+
else if (filter.type === "minMax") {
|
|
670
|
+
filterBarActions.setMinMaxValue(filter.filterKey, newValue);
|
|
671
|
+
}
|
|
672
|
+
else if (filter.type === "boolean") {
|
|
673
|
+
filterBarActions.setBooleanValue(filter.filterKey, newValue);
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
filterBarActions.setNumberValue(filter.filterKey, newValue);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
const text = filterText();
|
|
680
|
+
const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
|
|
681
|
+
const showDirectly = filter.showDirectly || false;
|
|
682
|
+
return showDirectly ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: filter.component({
|
|
683
|
+
filterDefinition: filter,
|
|
684
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
685
|
+
value: values,
|
|
686
|
+
setValue,
|
|
687
|
+
filterBarActions,
|
|
688
|
+
filterState,
|
|
689
|
+
}) })) : (jsxRuntime.jsx(reactFilterComponents.Filter, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterText().length > 0, title: filter.title, withStickyHeader: true, children: filter.component({
|
|
690
|
+
filterDefinition: filter,
|
|
691
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
692
|
+
value: values,
|
|
693
|
+
setValue,
|
|
694
|
+
filterBarActions,
|
|
695
|
+
filterState,
|
|
696
|
+
}) }));
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* ResetFiltersButton is a React component that provides a button for resetting filters.
|
|
701
|
+
*
|
|
702
|
+
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
703
|
+
*/
|
|
704
|
+
const ResetFiltersButton = ({ filtersHaveBeenApplied, resetFiltersToInitialState, }) => {
|
|
705
|
+
const [t] = useTranslation();
|
|
706
|
+
if (!filtersHaveBeenApplied) {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
return (jsxRuntime.jsx(reactComponents.Button, { dataTestId: "reset-filters-button", onClick: () => {
|
|
710
|
+
resetFiltersToInitialState();
|
|
711
|
+
}, size: "small", variant: "ghost-neutral", children: t("filtersBar.resetFilters") }));
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
const MAX_RECENT_SEARCHES = 20;
|
|
715
|
+
/**
|
|
716
|
+
*
|
|
717
|
+
*/
|
|
718
|
+
const SearchFilterInline = ({ filterBarActions, filterState, filter, className, dataTestId, }) => {
|
|
719
|
+
const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
|
|
720
|
+
const searchElementRef = react.useRef(null);
|
|
721
|
+
const geometry = reactComponents.useGeometry(searchElementRef);
|
|
722
|
+
const filterValue = (filter === null || filter === void 0 ? void 0 : filter.filterKey) ? String(filterState.values[filter.filterKey]) : undefined;
|
|
723
|
+
const [searchString, setSearchString] = react.useState(filterValue !== null && filterValue !== void 0 ? filterValue : "");
|
|
724
|
+
const previousFilterValue = reactComponents.usePrevious(filterValue);
|
|
725
|
+
reactComponents.useDebounce(searchString, 500, "both", debouncedSearchString => {
|
|
726
|
+
if (filterValue === debouncedSearchString) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
apply({ value: debouncedSearchString, setRecent: false });
|
|
730
|
+
});
|
|
731
|
+
react.useEffect(() => {
|
|
732
|
+
// Catch a change in filter value from outside the component and update the search string to match
|
|
733
|
+
if (filterValue === searchString || previousFilterValue === filterValue) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
filterValue !== undefined && setSearchString(filterValue);
|
|
737
|
+
}, [filterValue, previousFilterValue, searchString]);
|
|
738
|
+
const [t] = useTranslation();
|
|
739
|
+
const [recentSearches, setRecentSearches] = reactComponents.useLocalStorage({
|
|
740
|
+
defaultState: [],
|
|
741
|
+
key: "free-text-filter-recent-searches",
|
|
742
|
+
schema: zod.z.array(zod.z.string()),
|
|
743
|
+
});
|
|
744
|
+
const apply = react.useCallback(({ value, wasPreviousSearch = false, setRecent = true, }) => {
|
|
745
|
+
if (setRecent !== false) {
|
|
746
|
+
setRecentSearches(prev => {
|
|
747
|
+
const noDuplicatesRecent = prev.filter(r => r !== value);
|
|
748
|
+
const cappedLengthRecent = noDuplicatesRecent.slice(0, MAX_RECENT_SEARCHES - 1);
|
|
749
|
+
return [value, ...cappedLengthRecent];
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
logEvent("Filters Applied - V2", {
|
|
753
|
+
type: "free-text-search",
|
|
754
|
+
value,
|
|
755
|
+
additionalProperties: {
|
|
756
|
+
wasPreviousSearch: Boolean(wasPreviousSearch),
|
|
757
|
+
},
|
|
758
|
+
});
|
|
759
|
+
(filter === null || filter === void 0 ? void 0 : filter.filterKey) && filterBarActions.setStringValue(filter.filterKey, value);
|
|
760
|
+
}, [filter === null || filter === void 0 ? void 0 : filter.filterKey, filterBarActions, logEvent, setRecentSearches]);
|
|
761
|
+
return (jsxRuntime.jsxs(reactComponents.Popover, { activation: defaultActivation => ({ ...defaultActivation, keyboardHandlers: false }), dataTestId: dataTestId ? `${dataTestId}-popover` : undefined, placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactFormComponents.Search
|
|
762
|
+
//TODO: [Buss] remove height restruction once baseinput sixes matches buttons
|
|
763
|
+
, {
|
|
764
|
+
//TODO: [Buss] remove height restruction once baseinput sixes matches buttons
|
|
765
|
+
className: tailwindMerge.twMerge(className, "h-7"), fieldSize: "small", onChange: e => {
|
|
766
|
+
const parsedValue = zod.z.string().safeParse(e.target.value);
|
|
767
|
+
parsedValue.success ? setSearchString(parsedValue.data) : setSearchString("");
|
|
768
|
+
}, onClear: () => setSearchString(""), onKeyDown: e => {
|
|
769
|
+
if (e.key === "Enter") {
|
|
770
|
+
apply({ value: searchString });
|
|
771
|
+
}
|
|
772
|
+
}, ref: searchElementRef, value: searchString }) }), recentSearches.length > 0 ? (jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx("div", { style: { minWidth: geometry.width }, children: jsxRuntime.jsxs(reactComponents.MenuList, { className: "overflow-hidden", children: [jsxRuntime.jsx(reactFilterComponents.FilterBody, { className: "max-w-none p-1", limitSize: true, children: jsxRuntime.jsx("ul", { className: "grid h-full gap-0.5", children: recentSearches.map((option, index) => (jsxRuntime.jsx(reactComponents.MenuItem, { className: "max-h-fit overflow-hidden", id: "free-text-search-filter-inline", label: option, onClick: () => {
|
|
773
|
+
apply({ value: option, wasPreviousSearch: true });
|
|
774
|
+
setRecentSearches(prev => {
|
|
775
|
+
const noDuplicatesRecent = prev.filter(r => r !== option);
|
|
776
|
+
const cappedLengthRecent = noDuplicatesRecent.slice(0, MAX_RECENT_SEARCHES - 1);
|
|
777
|
+
return [option, ...cappedLengthRecent];
|
|
778
|
+
});
|
|
779
|
+
setSearchString(option);
|
|
780
|
+
}, selected: option === filterValue, size: "small", suffix: jsxRuntime.jsx(reactComponents.Tooltip, { label: t("filters.shared.clear"), children: jsxRuntime.jsx(reactComponents.IconButton, { circular: true, icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Trash", size: "small" }), onClick: e => {
|
|
781
|
+
e.stopPropagation();
|
|
782
|
+
setRecentSearches(prev => prev.filter(r => r !== option));
|
|
783
|
+
}, size: "small", variant: "ghost-neutral" }) }) }, `${option}-${index}`))) }) }), jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between gap-x-2 border-t-2 border-slate-300 px-2 py-0.5", children: [jsxRuntime.jsx(reactComponents.Text, { className: "py-0.5", size: "small", subtle: true, children: t("filters.shared.recentSearches") }), recentSearches.length > 1 ? (jsxRuntime.jsx(reactComponents.Button, { onClick: () => setRecentSearches([]), size: "small", variant: "ghost", children: t("filters.shared.clearAll") })) : null] })] }) }) })) : null] }));
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* StarredFiltersMenu is a React component that displays a menu of starred filters within a filter bar.
|
|
788
|
+
*
|
|
789
|
+
* @returns {JSX.Element} - Returns the StarredFiltersMenu component.
|
|
790
|
+
*/
|
|
791
|
+
const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starredFilterKeys, hiddenFilters = [], className, dataTestId, }) => {
|
|
792
|
+
const [t] = useTranslation();
|
|
793
|
+
const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
|
|
794
|
+
const hideInStarredMenu = react.useMemo(() => {
|
|
795
|
+
return sharedUtils.objectValues(filterBarDefinition)
|
|
796
|
+
.map(filter => {
|
|
797
|
+
const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
|
|
798
|
+
const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
|
|
799
|
+
return !showInStarredMenu || !showInFilterBar ? filter.filterKey : null;
|
|
800
|
+
})
|
|
801
|
+
.filter(sharedUtils.truthy);
|
|
802
|
+
}, [filterBarDefinition]);
|
|
803
|
+
const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
|
|
804
|
+
...hideInStarredMenu,
|
|
805
|
+
...hiddenFilters,
|
|
806
|
+
]);
|
|
807
|
+
return (jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.IconButton, { className: className, dataTestId: dataTestId !== null && dataTestId !== void 0 ? dataTestId : "starred-filters-menu", icon: jsxRuntime.jsx(reactComponents.Icon, { name: "Star", size: "small" }), size: "small", title: t("filtersBar.starredFiltersTitle"), variant: "secondary" }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx(reactComponents.MenuList, { className: "overflow-hidden", dataTestId: "starred-filters-menu-popover", children: jsxRuntime.jsx("div", { className: "max-h-[55vh] w-72 overflow-y-auto", children: filtersGrouped.map((group, idx) => {
|
|
808
|
+
const isLast = idx === filtersGrouped.length - 1;
|
|
809
|
+
return (jsxRuntime.jsxs("div", { className: "mb-2", children: [jsxRuntime.jsx("div", { className: "mb-1 mt-2 flex px-2", children: jsxRuntime.jsx(reactComponents.Text, { dataTestId: "starred-filters-group-title", size: "small", subtle: true, uppercase: true, weight: "bold", children: group.key }) }), group.filters.map(filter => {
|
|
810
|
+
return (jsxRuntime.jsxs("div", { className: "flex w-full cursor-pointer items-center justify-between rounded-sm px-2 py-0 transition hover:bg-neutral-50", "data-testid": `${filter.filterKey}-star-filter`, onClick: () => {
|
|
811
|
+
updateStarredFilters(filter.filterKey);
|
|
812
|
+
logEvent("Starring Filter - Toggled", {
|
|
813
|
+
type: filter.filterKey,
|
|
814
|
+
});
|
|
815
|
+
}, children: [jsxRuntime.jsx(reactComponents.Text, { weight: "thick", children: filter.title }), jsxRuntime.jsx(reactComponents.StarButton, { onClick: e => {
|
|
816
|
+
e.preventDefault();
|
|
817
|
+
e.stopPropagation();
|
|
818
|
+
updateStarredFilters(filter.filterKey);
|
|
819
|
+
logEvent("Starring Filter - Toggled", {
|
|
820
|
+
type: filter.filterKey,
|
|
821
|
+
});
|
|
822
|
+
}, starred: starredFilterKeys.includes(filter.filterKey) })] }, filter.filterKey));
|
|
823
|
+
}), !isLast && jsxRuntime.jsx("div", { className: "mt-2 h-[1px] w-full bg-neutral-300" })] }, group.key));
|
|
824
|
+
}) }) }) })] }));
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* StarredFilters is a React component that displays a list of starred filters based on the provided filter bar configuration.
|
|
829
|
+
*
|
|
830
|
+
* @template TFilterBarDefinition - The type representing the filter bar definition.
|
|
831
|
+
* @returns {JSX.Element} - Returns the StarredFilters component.
|
|
832
|
+
*/
|
|
833
|
+
const StarredFilters = ({ filterBarDefinition, filterBarConfig, disableCollapse, hiddenFilters = [], }) => {
|
|
834
|
+
const [t] = useTranslation();
|
|
835
|
+
const { isLg } = reactComponents.useViewportSize();
|
|
836
|
+
return (jsxRuntime.jsxs("div", { className: cvaFiltersAndSearchContainer({ collapsed: !(isLg || disableCollapse) }), children: [isLg || disableCollapse ? (jsxRuntime.jsx(FilterSelection, { filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters })) : (jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { children: jsxRuntime.jsxs(reactComponents.Tooltip, { label: jsxRuntime.jsx(FilterButtonTooltipLabel, { filterBarConfig: filterBarConfig }), children: [jsxRuntime.jsx(reactComponents.Button, { className: "@xs:flex hidden", prefix: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilters.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary", children: t("filtersBar.filtersHeading") }), jsxRuntime.jsx(reactComponents.IconButton, { className: "@xs:hidden", icon: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilters.length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary" })] }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { cellPadding: 100, children: jsxRuntime.jsx(reactComponents.Card, { children: jsxRuntime.jsx(reactComponents.CardBody, { children: jsxRuntime.jsx(FilterSelection, { filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }) }) })] })), filterBarDefinition.search ? (jsxRuntime.jsx("div", { className: "flex w-full", children: jsxRuntime.jsx(SearchFilterInline, { className: "w-full max-w-80", filter: filterBarDefinition.search, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters } }, "filter-search") })) : null] }));
|
|
837
|
+
};
|
|
838
|
+
const FilterSelection = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], className, }) => {
|
|
839
|
+
const hideInFilterBar = react.useMemo(() => {
|
|
840
|
+
return sharedUtils.objectValues(filterBarDefinition)
|
|
841
|
+
.map(filter => {
|
|
842
|
+
const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
|
|
843
|
+
return !showInFilterBar ? filter.filterKey : null;
|
|
844
|
+
})
|
|
845
|
+
.filter(sharedUtils.truthy);
|
|
846
|
+
}, [filterBarDefinition]);
|
|
847
|
+
const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
|
|
848
|
+
...hideInFilterBar,
|
|
849
|
+
...hiddenFilters,
|
|
850
|
+
]);
|
|
851
|
+
return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge([className, "flex-wrap", "items-center", "gap-3", "flex"]), "data-testid": "starred-filters", children: [jsxRuntime.jsx(StarredFiltersMenu, { filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), filterBarConfig.starredFilterKeys.length > 0 ? jsxRuntime.jsx("div", { className: "h-4 w-[1px] bg-slate-300" }) : null, filtersGrouped.map(group => group.filters
|
|
852
|
+
.filter(filter => !hiddenFilters.includes(filter.filterKey))
|
|
853
|
+
.map(filter => {
|
|
854
|
+
const showMenuAnyway = filterBarConfig.filterHasChanged(filter.filterKey) &&
|
|
855
|
+
filter.showInStarredMenu &&
|
|
856
|
+
!filter.showInStarredMenu();
|
|
857
|
+
if (filter.filterKey !== "search" &&
|
|
858
|
+
(filterBarConfig.starredFilterKeys.includes(filter.filterKey) ||
|
|
859
|
+
showMenuAnyway)) {
|
|
860
|
+
return (jsxRuntime.jsx(react.Fragment, { children: jsxRuntime.jsx(Filter, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters } }, `filter-${filter.filterKey}`) }, filter.filterKey));
|
|
861
|
+
}
|
|
862
|
+
return null;
|
|
863
|
+
})), jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilters.length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] }));
|
|
864
|
+
};
|
|
865
|
+
const cvaFiltersAndSearchContainer = cssClassVarianceUtilities.cvaMerge(["grid", "gap-2", "w-full"], {
|
|
866
|
+
variants: {
|
|
867
|
+
collapsed: {
|
|
868
|
+
true: ["grid-cols-min-fr"],
|
|
869
|
+
false: ["grid-cols-[minmax(0,1fr)_300px]"],
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
defaultVariants: {
|
|
873
|
+
collapsed: false,
|
|
874
|
+
},
|
|
875
|
+
});
|
|
876
|
+
const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
|
|
877
|
+
const [t] = useTranslation();
|
|
878
|
+
switch (filterBarConfig.appliedFilters.length) {
|
|
879
|
+
case 0:
|
|
880
|
+
return t("filtersBar.filtersHeading");
|
|
881
|
+
case 1:
|
|
882
|
+
return filterBarConfig.appliedFilters[0]
|
|
883
|
+
? t("filtersBar.appliedFiltersTooltip.singular", {
|
|
884
|
+
filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilters[0]),
|
|
885
|
+
})
|
|
886
|
+
: null; // should never happen though
|
|
887
|
+
default:
|
|
888
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [t("filtersBar.appliedFiltersTooltip.plural", { count: filterBarConfig.appliedFilters.length }), jsxRuntime.jsx("ul", { className: "list-inside", children: filterBarConfig.appliedFilters.map((appliedFilterKey, index) => (jsxRuntime.jsx("li", { className: "list-disc", children: filterBarConfig.getFilterTitle(appliedFilterKey) }, index))) })] }));
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* The FilterBar component serves as a wrapper for managing filters.
|
|
894
|
+
*/
|
|
895
|
+
const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, disableCollapse = false, }) => {
|
|
896
|
+
return (jsxRuntime.jsx("div", { className: tailwindMerge.twMerge("flex", className), "data-testid": `${filterBarConfig.name}-filterbar`, children: jsxRuntime.jsx(StarredFilters, { disableCollapse: disableCollapse, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters }) }));
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
// Can't import jest.fn so must define a function that does nothing but mimics the jest.fn
|
|
900
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
901
|
+
const doNothing = (args) => { };
|
|
902
|
+
const mockFilterBar = {
|
|
903
|
+
filterBarConfig: {
|
|
904
|
+
isFilterIncludedByKey: doNothing,
|
|
905
|
+
appliedFilters: [],
|
|
906
|
+
arrayIncludesValue: doNothing,
|
|
907
|
+
getFilterTitle: doNothing,
|
|
908
|
+
getFilterBarName: doNothing,
|
|
909
|
+
initialState: { customerType: [] },
|
|
910
|
+
name: "test",
|
|
911
|
+
objectArrayIncludesValue: doNothing,
|
|
912
|
+
resetFiltersToInitialState: doNothing,
|
|
913
|
+
resetIndividualFilterToInitialState: doNothing,
|
|
914
|
+
updateStarredFilters: doNothing,
|
|
915
|
+
setArrayObjectValue: doNothing,
|
|
916
|
+
setArrayValue: doNothing,
|
|
917
|
+
setBooleanValue: doNothing,
|
|
918
|
+
setBoundingBox: doNothing,
|
|
919
|
+
setDateRange: doNothing,
|
|
920
|
+
setMinMaxValue: doNothing,
|
|
921
|
+
setNumberValue: doNothing,
|
|
922
|
+
setStringValue: doNothing,
|
|
923
|
+
toggleArrayObjectValue: doNothing,
|
|
924
|
+
toggleArrayValue: doNothing,
|
|
925
|
+
values: { customerType: [] },
|
|
926
|
+
filterHasChanged: doNothing,
|
|
927
|
+
starredFilterKeys: [],
|
|
928
|
+
getValuesByKey: doNothing,
|
|
929
|
+
setters: {
|
|
930
|
+
setCriticality: doNothing,
|
|
931
|
+
setServicePlan: doNothing,
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
filterBarDefinition: {},
|
|
935
|
+
};
|
|
936
|
+
|
|
797
937
|
/**
|
|
798
938
|
* A helper function that returns a default value based on the filter type.
|
|
799
939
|
*
|
|
@@ -1042,9 +1182,13 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
|
|
|
1042
1182
|
}, [filterBarConfig, name]);
|
|
1043
1183
|
const filterMapGetter = react.useMemo(() => {
|
|
1044
1184
|
return {
|
|
1045
|
-
|
|
1185
|
+
getFilterBarName: () => {
|
|
1046
1186
|
return filterBarConfig.name;
|
|
1047
1187
|
},
|
|
1188
|
+
getFilterTitle(key) {
|
|
1189
|
+
var _a, _b;
|
|
1190
|
+
return (_b = (_a = filterBarDefinition[key]) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : key;
|
|
1191
|
+
},
|
|
1048
1192
|
arrayIncludesValue(key, value) {
|
|
1049
1193
|
const filter = filterBarConfig.values[key];
|
|
1050
1194
|
return (filter === null || filter === void 0 ? void 0 : filter.includes(value)) || false;
|
|
@@ -1056,15 +1200,18 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
|
|
|
1056
1200
|
const values = filterBarConfig.values[key];
|
|
1057
1201
|
return Boolean(values);
|
|
1058
1202
|
},
|
|
1059
|
-
get
|
|
1203
|
+
get appliedFilters() {
|
|
1060
1204
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1061
1205
|
const initialStateValues = filterBarConfig.initialState || {};
|
|
1062
1206
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1063
1207
|
const currentFilters = filterBarConfig.values || {};
|
|
1064
1208
|
// parse and stringify to remove empty references before comparing
|
|
1209
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
1065
1210
|
const currentValuesWithoutEmptyRefs = JSON.parse(JSON.stringify(currentFilters));
|
|
1211
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
1066
1212
|
const initialStateValuesWithoutEmptyRefs = JSON.parse(JSON.stringify(initialStateValues));
|
|
1067
|
-
return
|
|
1213
|
+
return getKeysOfUnequalProperties(currentValuesWithoutEmptyRefs, initialStateValuesWithoutEmptyRefs).map(key => key.toString() // TODO: FilterName type should be PropertyKey instead of string to avoid this .toString() bs but out-of-scope for this round of improvements
|
|
1214
|
+
);
|
|
1068
1215
|
},
|
|
1069
1216
|
filterHasChanged(key) {
|
|
1070
1217
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -1081,7 +1228,7 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
|
|
|
1081
1228
|
return (filter === null || filter === void 0 ? void 0 : filter.find(f => f.value === value)) !== undefined || false;
|
|
1082
1229
|
},
|
|
1083
1230
|
};
|
|
1084
|
-
}, [filterBarConfig.initialState, filterBarConfig.name, filterBarConfig.values]);
|
|
1231
|
+
}, [filterBarConfig.initialState, filterBarConfig.name, filterBarConfig.values, filterBarDefinition]);
|
|
1085
1232
|
const filterMapActions = react.useMemo(() => {
|
|
1086
1233
|
// Reset an individual filter to its initial state
|
|
1087
1234
|
const resetIndividualFilterToInitialState = (key) => {
|
|
@@ -1294,6 +1441,14 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, initialState,
|
|
|
1294
1441
|
};
|
|
1295
1442
|
}, [filterBarConfig, filterMapActions, filterMapGetter, filterBarDefinition]);
|
|
1296
1443
|
};
|
|
1444
|
+
const getKeysOfUnequalProperties = (obj1, obj2) => {
|
|
1445
|
+
return reduce(obj1, (result, value, key) => {
|
|
1446
|
+
if (!isEqual(value, obj2[key])) {
|
|
1447
|
+
result.push(key);
|
|
1448
|
+
}
|
|
1449
|
+
return result;
|
|
1450
|
+
}, []);
|
|
1451
|
+
};
|
|
1297
1452
|
|
|
1298
1453
|
/**
|
|
1299
1454
|
* Merges additional filters into an existing filter bar definition.
|
|
@@ -1334,6 +1489,7 @@ exports.isMinMaxFilterValue = isMinMaxFilterValue;
|
|
|
1334
1489
|
exports.isStringArrayFilterValue = isStringArrayFilterValue;
|
|
1335
1490
|
exports.isValueNameFilterValue = isValueNameFilterValue;
|
|
1336
1491
|
exports.mergeFilters = mergeFilters;
|
|
1492
|
+
exports.mockFilterBar = mockFilterBar;
|
|
1337
1493
|
exports.toggleFilterValue = toggleFilterValue;
|
|
1338
1494
|
exports.useFilterBar = useFilterBar;
|
|
1339
1495
|
exports.validateFilter = validateFilter;
|