@trackunit/filters-filter-bar 1.3.152 → 1.3.155
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 +317 -278
- package/index.esm.js +315 -281
- package/package.json +12 -11
- package/src/lib/components/DefaultFilterTypes.d.ts +4 -0
- package/src/lib/components/DynamicFilterList.d.ts +5 -1
- package/src/lib/components/FilterComponent.d.ts +47 -0
- package/src/lib/components/FilterHeader.d.ts +11 -5
- package/src/lib/components/FilterOptionsResults.d.ts +15 -0
- package/src/lib/components/FilterTableComponent.d.ts +23 -0
- package/src/lib/components/{StarredFilters.d.ts → FiltersMenu.d.ts} +5 -4
- package/src/lib/components/FiltersRenderer.d.ts +22 -0
- package/src/lib/components/GroupedFiltersList.d.ts +18 -0
- package/src/lib/components/ResetFiltersButton.d.ts +1 -6
- package/src/lib/components/index.d.ts +6 -1
- package/src/lib/hooks/useGroupFilters.d.ts +2 -2
- package/src/lib/types/FilterTypes.d.ts +1 -0
- package/src/lib/utils/FilterEvents.d.ts +3 -0
- package/src/translation.d.ts +2 -2
- package/src/lib/components/Filter.d.ts +0 -23
- package/src/lib/components/StarredFiltersMenu.d.ts +0 -29
package/index.cjs.js
CHANGED
|
@@ -11,8 +11,8 @@ var reactCoreContextsApi = require('@trackunit/react-core-contexts-api');
|
|
|
11
11
|
var reactFormComponents = require('@trackunit/react-form-components');
|
|
12
12
|
var reactDateAndTimeComponents = require('@trackunit/react-date-and-time-components');
|
|
13
13
|
var sharedUtils = require('@trackunit/shared-utils');
|
|
14
|
-
var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
|
|
15
14
|
var tailwindMerge = require('tailwind-merge');
|
|
15
|
+
var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
|
|
16
16
|
var dequal = require('dequal');
|
|
17
17
|
var isEqual = require('lodash/isEqual');
|
|
18
18
|
var geoJsonUtils = require('@trackunit/geo-json-utils');
|
|
@@ -128,7 +128,7 @@ var defaultTranslations = {
|
|
|
128
128
|
"filtersBar.defaultMinMaxFilters.min": "Min",
|
|
129
129
|
"filtersBar.deselectAll": "Deselect All",
|
|
130
130
|
"filtersBar.emptyResults": "No results were found for your search term.",
|
|
131
|
-
"filtersBar.filtersHeading": "
|
|
131
|
+
"filtersBar.filtersHeading": "Filter",
|
|
132
132
|
"filtersBar.groups.ASSET": "Asset",
|
|
133
133
|
"filtersBar.groups.CUSTOM_FIELDS": "Custom Fields",
|
|
134
134
|
"filtersBar.groups.CUSTOMERS": "Customers",
|
|
@@ -147,10 +147,15 @@ var defaultTranslations = {
|
|
|
147
147
|
"filtersBar.loading": "Loading...",
|
|
148
148
|
"filtersBar.myNetworkGroup": "My Network",
|
|
149
149
|
"filtersBar.resetFilter": "Reset",
|
|
150
|
-
"filtersBar.resetFilters": "Reset
|
|
150
|
+
"filtersBar.resetFilters": "Reset all",
|
|
151
|
+
"filtersBar.searchFiltersPlaceholder": "Search filters...",
|
|
152
|
+
"filtersBar.searchPlaceholder": "Search...",
|
|
151
153
|
"filtersBar.selectAll": "Select All",
|
|
154
|
+
"filtersBar.showAll": "Show all",
|
|
152
155
|
"filtersBar.starredFilters.title": "Filters",
|
|
153
156
|
"filtersBar.starredFiltersTitle": "Starred Filters",
|
|
157
|
+
"filtersMenu.appliedFiltersLabel.plural": "{{count}} filters applied",
|
|
158
|
+
"filtersMenu.appliedFiltersLabel.singular": "1 filter applied",
|
|
154
159
|
"fleetlist.column.activity": "Activity",
|
|
155
160
|
"fleetlist.column.assetIds": "Asset Ids",
|
|
156
161
|
"fleetlist.column.assetType": "Asset Type",
|
|
@@ -329,6 +334,7 @@ const FilterEvents = {
|
|
|
329
334
|
"Starring Filter - Toggled": reactCoreContextsApi.createEvent({
|
|
330
335
|
description: "A filter was starred or unstarred",
|
|
331
336
|
}),
|
|
337
|
+
"Showing Filters - All or Favorites": reactCoreContextsApi.createEvent(),
|
|
332
338
|
};
|
|
333
339
|
|
|
334
340
|
/**
|
|
@@ -352,11 +358,11 @@ const toggleFilterValue = (value) => {
|
|
|
352
358
|
*
|
|
353
359
|
* @returns {ReactElement} - Returns the DynamicFilterList component.
|
|
354
360
|
*/
|
|
355
|
-
const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked, count, showRequestMoreUseSearch = false, type, }) => {
|
|
361
|
+
const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked, count, showRequestMoreUseSearch = false, type, className, }) => {
|
|
356
362
|
const [t] = useTranslation();
|
|
357
363
|
const parentRef = react.useRef(null);
|
|
358
364
|
const updatedRowCount = react.useMemo(() => (showRequestMoreUseSearch ? rowCount + 1 : rowCount), [rowCount, showRequestMoreUseSearch]);
|
|
359
|
-
return (jsxRuntime.jsx(reactFilterComponents.FilterBody, { limitSize: true, ref: parentRef, children: jsxRuntime.jsx(reactComponents.VirtualizedList, { count: updatedRowCount, rowHeight: 40, separator: "space", children: index => {
|
|
365
|
+
return (jsxRuntime.jsx(reactFilterComponents.FilterBody, { limitSize: true, ref: parentRef, children: jsxRuntime.jsx(reactComponents.VirtualizedList, { className: className, count: updatedRowCount, rowHeight: 40, separator: "space", children: index => {
|
|
360
366
|
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: () => {
|
|
361
367
|
onChange?.(index);
|
|
362
368
|
} }, keyMapper(index))) }));
|
|
@@ -368,17 +374,26 @@ const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked
|
|
|
368
374
|
*
|
|
369
375
|
* @returns {ReactElement} - Returns the FilterHeader component.
|
|
370
376
|
*/
|
|
371
|
-
const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanges, resetIndividualFilterToInitialState, onResetFilter, loading = false,
|
|
377
|
+
const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanges, resetIndividualFilterToInitialState, onResetFilter, loading = false, className, dataTestId, hideHeader = false, children, }) => {
|
|
372
378
|
const [t] = useTranslation();
|
|
373
379
|
const handleResetFilter = () => {
|
|
374
380
|
resetIndividualFilterToInitialState(filterKey);
|
|
375
381
|
onResetFilter?.();
|
|
376
382
|
};
|
|
377
|
-
return (jsxRuntime.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
383
|
+
return (jsxRuntime.jsx(reactFilterComponents.FilterHeader, { className: className, dataTestId: dataTestId ?? `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), searchComponent: searchEnabled ? jsxRuntime.jsx(FilterHeaderSearchComponent, { filterKey: filterKey, searchProps: searchProps }) : undefined, showReset: filterHasChanges, title: hideHeader ? undefined : title, children: children }));
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* FilterHeaderSearchComponent renders a search input for the FilterHeader component.
|
|
387
|
+
*/
|
|
388
|
+
const FilterHeaderSearchComponent = ({ searchProps, filterKey, }) => {
|
|
389
|
+
const [t] = useTranslation();
|
|
390
|
+
return (jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, className: "w-full", fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value), onClear: () => {
|
|
391
|
+
searchProps.onClear?.();
|
|
392
|
+
}, onKeyDown: e => {
|
|
393
|
+
if (e.key === "Enter" && searchProps.onEnter) {
|
|
394
|
+
searchProps.onEnter(searchProps.value);
|
|
395
|
+
}
|
|
396
|
+
}, placeholder: searchProps.placeholder ?? t("filtersBar.searchPlaceholder"), suffix: jsxRuntime.jsx(reactComponents.Text, { size: "small", subtle: true, children: searchProps.count }), value: searchProps.value }));
|
|
382
397
|
};
|
|
383
398
|
|
|
384
399
|
/**
|
|
@@ -398,7 +413,7 @@ const FilterResults = ({ results, ignoreUndefined, loading, children, }) => {
|
|
|
398
413
|
};
|
|
399
414
|
const EmptyResults = ({ loading }) => {
|
|
400
415
|
const [t] = useTranslation();
|
|
401
|
-
return (jsxRuntime.jsx("div", { className: "grid h-20 w-
|
|
416
|
+
return (jsxRuntime.jsx("div", { className: "grid h-20 w-[280px] place-content-center pl-2 pr-2", "data-testid": "empty-filter-results", children: jsxRuntime.jsx(reactComponents.Text, { align: "center", className: "w-full break-words", italicize: true, subtle: true, children: loading ? t("filtersBar.loading") : t("filtersBar.emptyResults") }) }));
|
|
402
417
|
};
|
|
403
418
|
|
|
404
419
|
/**
|
|
@@ -411,7 +426,6 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
|
|
|
411
426
|
const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
|
|
412
427
|
const [hasSelectedAll, setHasSelectedAll] = react.useState(false);
|
|
413
428
|
const { t } = useTranslation();
|
|
414
|
-
const [selectedCount, setSelectedCount] = react.useState(0);
|
|
415
429
|
const handleSetValue = (value) => {
|
|
416
430
|
logEvent("Filters Applied - V2", {
|
|
417
431
|
type: filterName ?? `${stringTs.capitalize(filterDefinition.filterKey)}Filter`,
|
|
@@ -476,19 +490,18 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
|
|
|
476
490
|
react.useEffect(() => {
|
|
477
491
|
const values = filterBarActions.getValuesByKey(filterDefinition.filterKey);
|
|
478
492
|
if (Array.isArray(values)) {
|
|
479
|
-
|
|
480
|
-
if (values.length ===
|
|
481
|
-
setHasSelectedAll(
|
|
493
|
+
const optionsLength = undefinedCount ? options.length - 1 : options.length;
|
|
494
|
+
if (values.length === optionsLength) {
|
|
495
|
+
setHasSelectedAll(true);
|
|
482
496
|
}
|
|
483
497
|
else {
|
|
484
|
-
setHasSelectedAll(
|
|
498
|
+
setHasSelectedAll(false);
|
|
485
499
|
}
|
|
486
500
|
}
|
|
487
501
|
else {
|
|
488
|
-
setSelectedCount(0);
|
|
489
502
|
setHasSelectedAll(true);
|
|
490
503
|
}
|
|
491
|
-
}, [filterBarActions, filterDefinition.filterKey, undefinedCount]);
|
|
504
|
+
}, [filterBarActions, filterDefinition.filterKey, options.length, undefinedCount]);
|
|
492
505
|
const handleSelectAll = () => {
|
|
493
506
|
if (hasSelectedAll) {
|
|
494
507
|
setMultipleValues([]);
|
|
@@ -506,19 +519,20 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
|
|
|
506
519
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilterHeader, { ...filterDefinition, ...filterBarActions, filterHasChanges: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), loading: loading, searchEnabled: true, searchProps: {
|
|
507
520
|
value: customSearch?.value ?? searchText,
|
|
508
521
|
onChange: customSearch?.onChange ?? setSearchText,
|
|
522
|
+
onClear: customSearch?.onClear ?? (() => setSearchText("")),
|
|
509
523
|
count: undefinedCount ? filteredOptions.length - 1 : filteredOptions.length,
|
|
510
|
-
}
|
|
524
|
+
}, children: options.length >= 2 ? (jsxRuntime.jsx(reactComponents.Button, { className: "place-self-start", dataTestId: "selectAllButton", disabled: hasSelectedAll, onClick: handleSelectAll, size: "small", variant: "ghost", children: t("filtersBar.selectAll") })) : null }), jsxRuntime.jsx(FilterResults, { ignoreUndefined: undefinedCount !== null, loading: loading, results: results, children: res => (jsxRuntime.jsx(DynamicFilterList, { checked: index => {
|
|
511
525
|
return filterDefinition.type === "valueNameArray"
|
|
512
526
|
? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, res[index]?.key || "")
|
|
513
527
|
: filterBarActions.arrayIncludesValue(filterDefinition.filterKey, res[index]?.key || "");
|
|
514
|
-
}, count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", onChange: index => {
|
|
528
|
+
}, className: "mb-1", count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", onChange: index => {
|
|
515
529
|
const result = res[index];
|
|
516
530
|
if (result) {
|
|
517
531
|
handleSetValue(result);
|
|
518
532
|
}
|
|
519
533
|
}, rowCount: undefinedCount ? res.length - 1 : res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "CheckBox" })) }), showUndefinedOptionWithCountAtBottom && undefinedCount ? (jsxRuntime.jsx("div", { className: "m-1", children: jsxRuntime.jsx(reactFilterComponents.CheckBoxFilterItem, { checked: filterDefinition.type === "valueNameArray"
|
|
520
534
|
? filterBarActions.objectArrayIncludesValue(filterDefinition.filterKey, results[undefinedCount.index]?.key || "")
|
|
521
|
-
: filterBarActions.arrayIncludesValue(filterDefinition.filterKey, results[undefinedCount.index]?.key || ""),
|
|
535
|
+
: filterBarActions.arrayIncludesValue(filterDefinition.filterKey, results[undefinedCount.index]?.key || ""), dataTestId: "dynamic-filter-check-box-undefined", itemCount: undefinedCount.count, label: results[undefinedCount.index]?.label, name: "dynamic-filter-check-box-undefined", onChange: () => {
|
|
522
536
|
const result = results[undefinedCount.index];
|
|
523
537
|
if (result) {
|
|
524
538
|
handleSetValue(result);
|
|
@@ -623,10 +637,282 @@ const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loadi
|
|
|
623
637
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilterHeader, { ...filterBarActions, ...filterDefinition, filterHasChanges: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), loading: loading, searchEnabled: true, searchProps: {
|
|
624
638
|
value: customSearch?.value ?? searchText,
|
|
625
639
|
onChange: customSearch?.onChange ?? setSearchText,
|
|
640
|
+
onClear: customSearch?.onClear ?? (() => setSearchText("")),
|
|
626
641
|
count: filteredOptions.length,
|
|
627
642
|
} }), jsxRuntime.jsx(FilterResults, { loading: loading, results: customSearch ? options : filteredOptions, children: res => (jsxRuntime.jsx(reactFormComponents.RadioGroup, { id: "DefaultRadioFilter", onChange: e => {
|
|
628
643
|
handleClick(e.currentTarget.value);
|
|
629
|
-
}, value: selectedRadioId?.key || "", children: jsxRuntime.jsx(DynamicFilterList, { checked: index => filterBarActions.objectIncludesValue(filterDefinition.filterKey, res[index]?.key || ""), count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
|
|
644
|
+
}, value: selectedRadioId?.key || "", children: jsxRuntime.jsx(DynamicFilterList, { checked: index => filterBarActions.objectIncludesValue(filterDefinition.filterKey, res[index]?.key || ""), className: "m-1 mt-0", count: index => res[index]?.count, keyMapper: index => res[index]?.key || "", labelMapper: index => res[index]?.label || "", rowCount: res.length, showRequestMoreUseSearch: showRequestMoreUseSearch, type: "Radio" }) })) })] }));
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Returns the two first values, appends counter if more.
|
|
649
|
+
*
|
|
650
|
+
* @param {string[]} [input] Array of values to reduce
|
|
651
|
+
* @returns {*} {string}
|
|
652
|
+
*/
|
|
653
|
+
const reduceFilterText = (input) => {
|
|
654
|
+
if (!Array.isArray(input)) {
|
|
655
|
+
return input;
|
|
656
|
+
}
|
|
657
|
+
// Only first two elements
|
|
658
|
+
const firstTwo = input.slice(0, 2);
|
|
659
|
+
const remainder = input.slice(2, input.length).length;
|
|
660
|
+
const firstTwoJoined = firstTwo.join(", ");
|
|
661
|
+
const remainderStr = remainder > 0 ? `, +${remainder}` : "";
|
|
662
|
+
return firstTwoJoined + remainderStr;
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Filter is a React component that renders a filter element based on the provided filter definition and state.
|
|
667
|
+
*
|
|
668
|
+
* @returns {ReactElement} - Returns the Filter component.
|
|
669
|
+
*/
|
|
670
|
+
const FilterComponent = ({ filter, filterBarActions, filterState, readOnly = false, visualStyle = "button", className, asIcon, size, }) => {
|
|
671
|
+
const values = filterBarActions.getValuesByKey(filter.filterKey);
|
|
672
|
+
const valuesLength = values && Array.isArray(values) ? values.length : undefined;
|
|
673
|
+
const getFilterText = () => {
|
|
674
|
+
if (!values) {
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
if (filter.valueAsText) {
|
|
678
|
+
return reduceFilterText(filter.valueAsText(values));
|
|
679
|
+
}
|
|
680
|
+
else if (filter.type === "valueNameArray") {
|
|
681
|
+
return reduceFilterText(values.map(value => value.name));
|
|
682
|
+
}
|
|
683
|
+
else if (filter.type === "valueName") {
|
|
684
|
+
return values.name;
|
|
685
|
+
}
|
|
686
|
+
return values.toString();
|
|
687
|
+
};
|
|
688
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
689
|
+
const setValue = (callback) => {
|
|
690
|
+
const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
|
|
691
|
+
if (filter.type === "string") {
|
|
692
|
+
filterBarActions.setStringValue(filter.filterKey, newValue);
|
|
693
|
+
}
|
|
694
|
+
else if (filter.type === "stringArray") {
|
|
695
|
+
filterBarActions.setArrayValue(filter.filterKey, newValue);
|
|
696
|
+
}
|
|
697
|
+
else if (filter.type === "dateRange") {
|
|
698
|
+
filterBarActions.setDateRange(filter.filterKey, newValue);
|
|
699
|
+
}
|
|
700
|
+
else if (filter.type === "area") {
|
|
701
|
+
filterBarActions.setArea(newValue);
|
|
702
|
+
}
|
|
703
|
+
else if (filter.type === "valueNameArray") {
|
|
704
|
+
filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
|
|
705
|
+
}
|
|
706
|
+
else if (filter.type === "valueName") {
|
|
707
|
+
filterBarActions.setObjectValue(filter.filterKey, newValue);
|
|
708
|
+
}
|
|
709
|
+
else if (filter.type === "minMax") {
|
|
710
|
+
filterBarActions.setMinMaxValue(filter.filterKey, newValue);
|
|
711
|
+
}
|
|
712
|
+
else if (filter.type === "boolean") {
|
|
713
|
+
filterBarActions.setBooleanValue(filter.filterKey, newValue);
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
filterBarActions.setNumberValue(filter.filterKey, newValue);
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
const text = getFilterText();
|
|
720
|
+
const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
|
|
721
|
+
const showDirectly = filter.showDirectly || false;
|
|
722
|
+
return showDirectly ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: filter.component({
|
|
723
|
+
filterDefinition: filter,
|
|
724
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
725
|
+
value: values,
|
|
726
|
+
setValue,
|
|
727
|
+
filterBarActions,
|
|
728
|
+
filterState,
|
|
729
|
+
}) })) : (jsxRuntime.jsx(reactFilterComponents.Filter, { activeLabel: activeFilterText, activeOptionsCount: valuesLength, asIcon: asIcon, className: className, dataTestId: `${filter.filterKey}-filter-button`, isActive: filterBarActions.appliedFilterKeys().includes(filter.filterKey), popoverProps: { placement: visualStyle === "list-item" ? "right-start" : "bottom-start" }, readOnly: readOnly, size: size, title: filter.title, visualStyle: visualStyle, withStickyHeader: true, children: filter.component({
|
|
730
|
+
filterDefinition: filter,
|
|
731
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
732
|
+
value: values,
|
|
733
|
+
setValue,
|
|
734
|
+
filterBarActions,
|
|
735
|
+
filterState,
|
|
736
|
+
}) }));
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Custom React hook for grouping and filtering a list of filters.
|
|
741
|
+
*
|
|
742
|
+
* @param {FilterDefinition[]} filterDefinitions - The list of filter definitions to group and filter.
|
|
743
|
+
* @param {string[]} hiddenFilters - An array of filter keys representing filters to be hidden.
|
|
744
|
+
* @returns {UseGroupFiltersReturnType} An object containing the grouped filters.
|
|
745
|
+
*/
|
|
746
|
+
const useGroupFilters = (filterDefinitions, hiddenFilters) => {
|
|
747
|
+
const [t] = useTranslation();
|
|
748
|
+
const filtersGrouped = react.useMemo(() => {
|
|
749
|
+
const keys = uniqueKeysFromGroups(filterDefinitions);
|
|
750
|
+
return keys
|
|
751
|
+
.map(key => ({
|
|
752
|
+
key,
|
|
753
|
+
title: t(`filtersBar.groups.${key}`),
|
|
754
|
+
filters: filterDefinitions.filter(rawFilter => rawFilter.group === key && hiddenFilters.includes(rawFilter.filterKey) === false),
|
|
755
|
+
}))
|
|
756
|
+
.filter(filter => filter.filters.length > 0);
|
|
757
|
+
}, [filterDefinitions, hiddenFilters, t]);
|
|
758
|
+
return react.useMemo(() => ({ filtersGrouped }), [filtersGrouped]);
|
|
759
|
+
};
|
|
760
|
+
const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* FiltersRenderer renders an array of Filter components from filter definitions
|
|
764
|
+
* It ignores hidden filters.
|
|
765
|
+
*
|
|
766
|
+
* Note: This component does not provide any styling or layout - use a wrapper for proper list styling.
|
|
767
|
+
*
|
|
768
|
+
* @param {object} props - The component props
|
|
769
|
+
* @param {FilterDefinition[]} props.filters - Array of filter definitions to render
|
|
770
|
+
* @param {FilterBarConfig & FilterMapActions & FilterMapGetter} props.filterBarConfig - The filter bar configuration
|
|
771
|
+
* @param {ComponentProps<typeof FilterComponent>["visualStyle"]} [props.visualStyle] - The visual style of the filters
|
|
772
|
+
* @returns {ReactElement[] | null} - Returns an array of Filter components or null
|
|
773
|
+
*/
|
|
774
|
+
const FiltersRenderer = ({ filters, filterBarConfig, visualStyle, }) => {
|
|
775
|
+
return filters.length === 0
|
|
776
|
+
? null
|
|
777
|
+
: filters
|
|
778
|
+
.filter(filter => !filter.isHidden)
|
|
779
|
+
.map(filter => (jsxRuntime.jsx(FilterComponent, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters }, visualStyle: visualStyle }, `filter-${filter.filterKey}`)));
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* FiltersList is a React component that displays a list of filters within a filter bar.
|
|
784
|
+
*
|
|
785
|
+
* @returns {ReactElement} - Returns the FiltersList component.
|
|
786
|
+
*/
|
|
787
|
+
const GroupedFiltersList = ({ filterBarConfig, filtersGrouped, className, dataTestId = "grouped-filters-list", }) => {
|
|
788
|
+
return (jsxRuntime.jsx("div", { className: className, "data-testid": dataTestId, role: "menu", children: filtersGrouped.map((group, idx) => {
|
|
789
|
+
const isLastGroup = idx === filtersGrouped.length - 1;
|
|
790
|
+
return (jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(reactComponents.Text, { className: "text-secondary-400 h-7 px-3 py-2", dataTestId: `${group.key}-group-title`, size: "small", uppercase: true, weight: "bold", children: group.title }), jsxRuntime.jsx("ul", { "aria-labelledby": `${group.key}-group-title`, className: "grid", "data-testid": `${group.key}-group-list`, children: jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: group.filters, visualStyle: "list-item" }) })] }), isLastGroup ? null : jsxRuntime.jsx("div", { className: "bg-secondary-200 h-[1px] w-full", role: "separator" })] }, group.key));
|
|
791
|
+
}) }));
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* ResetFiltersButton is a React component that provides a button for resetting filters.
|
|
796
|
+
*
|
|
797
|
+
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
798
|
+
*/
|
|
799
|
+
const ResetFiltersButton = ({ resetFiltersToInitialState, dataTestId, className, }) => {
|
|
800
|
+
const [t] = useTranslation();
|
|
801
|
+
return (jsxRuntime.jsxs(reactComponents.Button, { className: className, dataTestId: dataTestId ?? "reset-filters-button", onClick: () => {
|
|
802
|
+
resetFiltersToInitialState();
|
|
803
|
+
}, size: "small", variant: "ghost", children: [t("filtersBar.resetFilters"), jsxRuntime.jsx("span", { className: "sr-only", children: "Resets all applied filters" })] }));
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* FilterMenu is a React component that displays a list of filters in a popover menu based on the provided filter bar configuration.
|
|
808
|
+
*
|
|
809
|
+
* @template TFilterBarDefinition - The type representing the filter bar definition.
|
|
810
|
+
* @returns {ReactElement} - Returns the FilterMenu component.
|
|
811
|
+
*/
|
|
812
|
+
const FiltersMenu = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, title, dataTestId = "filters-menu", className, }) => {
|
|
813
|
+
const [t] = useTranslation();
|
|
814
|
+
const { isSm } = reactComponents.useViewportBreakpoints();
|
|
815
|
+
const [showCustomFilters, setShowCustomFilters] = react.useState(false);
|
|
816
|
+
// TODO: Add analytics if event requirements are defined
|
|
817
|
+
// const { logEvent } = useAnalytics(FilterEvents);
|
|
818
|
+
const hideInMenu = react.useMemo(() => {
|
|
819
|
+
return sharedUtils.objectValues(filterBarDefinition)
|
|
820
|
+
.map(filter => {
|
|
821
|
+
const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
|
|
822
|
+
const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true; // TODO: Starred menu concept should be completely removed everywhere
|
|
823
|
+
const showMenuAnywayBecauseFilterHasChanged = filterBarConfig.appliedFilterKeys().includes(filter.filterKey);
|
|
824
|
+
return (!showInFilterBar || !showInStarredMenu) && !showMenuAnywayBecauseFilterHasChanged
|
|
825
|
+
? filter.filterKey
|
|
826
|
+
: null;
|
|
827
|
+
})
|
|
828
|
+
.filter(sharedUtils.truthy);
|
|
829
|
+
}, [filterBarConfig, filterBarDefinition]);
|
|
830
|
+
const { filtersGrouped } = useGroupFilters(sharedUtils.objectValues(filterBarDefinition), [...hideInMenu, ...hiddenFilters]);
|
|
831
|
+
const { appliedFilters, filtersToShow, showDirectlyFilters, hasCustomFields } = react.useMemo(() => {
|
|
832
|
+
const allFilters = filtersGrouped.map(group => group.filters).flat();
|
|
833
|
+
return {
|
|
834
|
+
appliedFilters: allFilters.filter(filter => filterBarConfig.appliedFilterKeys().includes(filter.filterKey)),
|
|
835
|
+
filtersToShow: allFilters.filter(filter => !filter.showDirectly),
|
|
836
|
+
showDirectlyFilters: allFilters.filter(filter => filter.showDirectly),
|
|
837
|
+
hasCustomFields: allFilters.some(filter => filter.group === "CUSTOM_FIELDS"),
|
|
838
|
+
};
|
|
839
|
+
}, [filterBarConfig, filtersGrouped]);
|
|
840
|
+
const [searchResults, searchText, setSearchText] = reactCoreHooks.useTextSearch(filtersToShow, item => [item.title]);
|
|
841
|
+
const { filtersGrouped: searchResultsGrouped } = useGroupFilters(searchResults, []);
|
|
842
|
+
const { filtersGrouped: filtersToShowGrouped } = useGroupFilters(filtersToShow, []);
|
|
843
|
+
const appliedCustomFields = react.useMemo(() => appliedFilters.filter(filter => filter.group === "CUSTOM_FIELDS"), [appliedFilters]);
|
|
844
|
+
return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex items-center gap-2", className), "data-testid": dataTestId, children: [jsxRuntime.jsx(reactComponents.Popover, { onOpenStateChange: open => {
|
|
845
|
+
if (!open) {
|
|
846
|
+
setShowCustomFilters(false);
|
|
847
|
+
setSearchText("");
|
|
848
|
+
}
|
|
849
|
+
}, placement: "bottom-start", children: modalState => {
|
|
850
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { "data-testid": "starred-filters-menu-trigger", id: "starred-filters-menu-trigger", children: jsxRuntime.jsx(reactComponents.Tooltip, { disabled: !compact || modalState.isOpen, label: jsxRuntime.jsx(FilterButtonTooltipLabel, { filterBarConfig: filterBarConfig }), children: jsxRuntime.jsx(reactComponents.Button, { prefix: jsxRuntime.jsx(reactComponents.Icon, { ariaHidden: true, color: filterBarConfig.appliedFilterKeys().length > 0 ? "primary" : undefined, name: "Filter", size: "small" }), size: "small", suffix: compact && filterBarConfig.appliedFilterKeys().length > 0 && isSm ? (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsxs("span", { "aria-hidden": true, children: ["(", filterBarConfig.appliedFilterKeys().length, ")"] }), jsxRuntime.jsxs("span", { className: "sr-only", children: [filterBarConfig.appliedFilterKeys().length, " filters applied"] })] })) : undefined, variant: "secondary", children: jsxRuntime.jsx("span", { className: "hidden sm:block", children: title ?? t("filtersBar.filtersHeading") }) }) }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { cellPadding: 100, children: jsxRuntime.jsxs(reactComponents.Card, { className: "max-h-[min(600px,_calc(100dvh-32px))] w-[300px] overflow-y-hidden", dataTestId: "starred-filters-menu-popover", children: [jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 p-1", children: [jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, dataTestId: "starred-filters-menu-search", fieldSize: "small", id: "search-filters-list", onChange: e => setSearchText(e.currentTarget.value), onClear: () => setSearchText(""), placeholder: t("filtersBar.searchFiltersPlaceholder"), value: searchText }), jsxRuntime.jsxs("div", { className: "flex h-7 items-center justify-between gap-1 px-3", children: [jsxRuntime.jsx(reactComponents.Text, { className: "text-secondary-400", size: "small", children: jsxRuntime.jsx(FiltersAppliedCountLabel, { filterBarConfig: filterBarConfig }) }), filterBarConfig.appliedFilterKeys().length > 0 ? (jsxRuntime.jsx(ResetFiltersButton, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] })] }), jsxRuntime.jsx(Separator, {}), jsxRuntime.jsxs(reactComponents.CardBody, { className: "gap-1 p-1", density: "none", disableGap: true, children: [jsxRuntime.jsx(GroupedFiltersList, { className: "flex flex-col gap-1", filterBarConfig: filterBarConfig, filtersGrouped: searchText
|
|
851
|
+
? searchResultsGrouped
|
|
852
|
+
: showCustomFilters
|
|
853
|
+
? filtersToShowGrouped
|
|
854
|
+
: removeCustomFields(filtersToShowGrouped) }), hasCustomFields && !showCustomFilters && !searchText ? (jsxRuntime.jsx(CustomFieldsHiddenGroup, { appliedCustomFields: appliedCustomFields, filterBarConfig: filterBarConfig, onShow: () => {
|
|
855
|
+
setShowCustomFilters(true);
|
|
856
|
+
} })) : null] })] }) })] }));
|
|
857
|
+
} }), showDirectlyFilters.length > 0 ? (jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, !compact ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [appliedFilters.filter(filter => !filter.showDirectly).length > 0 ? (jsxRuntime.jsx("div", { className: "h-4 w-[1px] bg-slate-300", "data-testid": "applied-filters-buttons" })) : null, jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: appliedFilters }), filterBarConfig.appliedFilterKeys().length > 0 ? (jsxRuntime.jsx(ResetFiltersButton, { resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })) : null] })) : null] }));
|
|
858
|
+
};
|
|
859
|
+
const Separator = () => jsxRuntime.jsx("hr", { className: "border-secondary-200", role: "separator" });
|
|
860
|
+
const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
|
|
861
|
+
const [t] = useTranslation();
|
|
862
|
+
switch (filterBarConfig.appliedFilterKeys().length) {
|
|
863
|
+
case 0:
|
|
864
|
+
return t("filtersBar.appliedFiltersTooltip.none");
|
|
865
|
+
case 1:
|
|
866
|
+
return filterBarConfig.appliedFilterKeys()[0]
|
|
867
|
+
? t("filtersBar.appliedFiltersTooltip.singular", {
|
|
868
|
+
filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilterKeys()[0]),
|
|
869
|
+
})
|
|
870
|
+
: null; // should never happen though
|
|
871
|
+
default:
|
|
872
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [t("filtersBar.appliedFiltersTooltip.plural", { count: filterBarConfig.appliedFilterKeys().length }), jsxRuntime.jsx("ul", { className: "list-inside", children: filterBarConfig.appliedFilterKeys().map((appliedFilterKey, index) => (jsxRuntime.jsx("li", { className: "list-disc", children: filterBarConfig.getFilterTitle(appliedFilterKey) }, index))) })] }));
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
const FiltersAppliedCountLabel = ({ filterBarConfig, }) => {
|
|
876
|
+
const [t] = useTranslation();
|
|
877
|
+
switch (filterBarConfig.appliedFilterKeys().length) {
|
|
878
|
+
case 0:
|
|
879
|
+
return t("filtersBar.appliedFiltersTooltip.none");
|
|
880
|
+
case 1:
|
|
881
|
+
return filterBarConfig.appliedFilterKeys()[0] ? t("filtersMenu.appliedFiltersLabel.singular") : null;
|
|
882
|
+
default:
|
|
883
|
+
return jsxRuntime.jsx(jsxRuntime.Fragment, { children: t("filtersMenu.appliedFiltersLabel.plural", { count: filterBarConfig.appliedFilterKeys().length }) });
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
const CustomFieldsHiddenGroup = ({ appliedCustomFields, filterBarConfig, onShow, }) => {
|
|
887
|
+
const [t] = useTranslation();
|
|
888
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Separator, {}), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(reactComponents.Button, { "aria-controls": "filters-list", className: "text-primary-600 w-full justify-between px-3", onClick: onShow, prefix: jsxRuntime.jsx(reactComponents.Text, { className: "text-secondary-400", size: "small", uppercase: true, weight: "bold", children: t("filtersBar.groups.CUSTOM_FIELDS") }), size: "small", variant: "ghost-neutral", children: t("filtersBar.showAll") }), appliedCustomFields.length > 0 ? (jsxRuntime.jsx("ul", { "aria-label": "Visible custom fields", "data-testid": "applied-custom-fields-list", children: jsxRuntime.jsx(FiltersRenderer, { filterBarConfig: filterBarConfig, filters: appliedCustomFields, visualStyle: "list-item" }) })) : null] })] }));
|
|
889
|
+
};
|
|
890
|
+
const removeCustomFields = (filtersGrouped) => filtersGrouped.filter(group => group.key !== "CUSTOM_FIELDS");
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Filter is a React component that renders a filter element based on the provided filter definition and state.
|
|
894
|
+
*
|
|
895
|
+
* @returns {ReactElement} - Returns the Filter component.
|
|
896
|
+
*/
|
|
897
|
+
const FilterTableComponent = ({ filterKey, filterBarDefinition, filterBarConfig, }) => {
|
|
898
|
+
const ensureFilterKey = react.useCallback((key) => {
|
|
899
|
+
// eslint-disable-next-line local-rules/no-typescript-assertion
|
|
900
|
+
return key;
|
|
901
|
+
}, []);
|
|
902
|
+
const filters = react.useMemo(() => {
|
|
903
|
+
if (Array.isArray(filterKey)) {
|
|
904
|
+
return filterKey.map(key => filterBarDefinition[ensureFilterKey(key)]).filter(Boolean);
|
|
905
|
+
}
|
|
906
|
+
return [];
|
|
907
|
+
}, [filterKey, filterBarDefinition, ensureFilterKey]);
|
|
908
|
+
if (Array.isArray(filterKey)) {
|
|
909
|
+
return (jsxRuntime.jsx(reactComponents.MoreMenu, { customButton: jsxRuntime.jsx(reactComponents.IconButton, { icon: jsxRuntime.jsx(reactComponents.Icon, { color: filterKey.some(key => filterBarConfig.appliedFilterKeys().includes(key)) ? "primary" : undefined, name: "Filter", size: "small" }), onClick: event => event.stopPropagation(), size: "extraSmall", variant: "ghost-neutral" }), children: () => (jsxRuntime.jsx(reactComponents.MenuList, { children: filters.map((value, index) => value && (jsxRuntime.jsx(FilterComponent, { filter: value, filterBarActions: filterBarConfig, filterState: filterBarConfig, visualStyle: "list-item" }, index))) })) }));
|
|
910
|
+
}
|
|
911
|
+
const filter = filterBarDefinition[ensureFilterKey(filterKey)];
|
|
912
|
+
if (!filter) {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
return (jsxRuntime.jsx("div", { onClick: event => event.stopPropagation(), children: jsxRuntime.jsx(FilterComponent, { asIcon: "Filter", filter: filter, filterBarActions: filterBarConfig, filterState: filterBarConfig, size: "extraSmall" }) }));
|
|
630
916
|
};
|
|
631
917
|
|
|
632
918
|
/**
|
|
@@ -854,263 +1140,11 @@ const HierarchicalCheckboxFilter = ({ filterDefinition, filterBarActions, option
|
|
|
854
1140
|
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilterHeader, { ...filterDefinition, ...filterBarActions, filterHasChanges: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), loading: loading, ...searchHeaderProps }), jsxRuntime.jsx("div", { className: "max-h-60 overflow-y-auto", children: jsxRuntime.jsx(FilterResults, { loading: loading, results: filteredOptions, children: results => (jsxRuntime.jsx(jsxRuntime.Fragment, { children: results.map(option => (jsxRuntime.jsx(RenderHierarchicalOption, { cascadeSelection: cascadeSelection, filterBarActions: filterBarActions, filterDefinition: filterDefinition, logEvent: logEvent, option: option, optionsMap: optionsMap, selectedValues: selectedValues, setValue: setValue }, option.key))) })) }) })] }));
|
|
855
1141
|
};
|
|
856
1142
|
|
|
857
|
-
/**
|
|
858
|
-
* Custom React hook for grouping and filtering a list of filters.
|
|
859
|
-
*
|
|
860
|
-
* @param {FilterDefinition[]} filterDefinitions - The list of filter definitions to group and filter.
|
|
861
|
-
* @param {string[]} hiddenFilters - An array of filter keys representing filters to be hidden.
|
|
862
|
-
* @returns {UseGroupFiltersReturnType} An object containing the grouped filters.
|
|
863
|
-
*/
|
|
864
|
-
const useStarredGroupFilters = (filterDefinitions, hiddenFilters) => {
|
|
865
|
-
const [t] = useTranslation();
|
|
866
|
-
const filtersGrouped = react.useMemo(() => {
|
|
867
|
-
const keys = uniqueKeysFromGroups(filterDefinitions);
|
|
868
|
-
return keys
|
|
869
|
-
.map(key => ({
|
|
870
|
-
key,
|
|
871
|
-
title: t(`filtersBar.groups.${key}`),
|
|
872
|
-
filters: filterDefinitions.filter(rawFilter => rawFilter.group === key && hiddenFilters.includes(rawFilter.filterKey) === false),
|
|
873
|
-
}))
|
|
874
|
-
.filter(filter => filter.filters.length > 0);
|
|
875
|
-
}, [filterDefinitions, hiddenFilters, t]);
|
|
876
|
-
return react.useMemo(() => ({ filtersGrouped }), [filtersGrouped]);
|
|
877
|
-
};
|
|
878
|
-
const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
|
|
879
|
-
|
|
880
|
-
/**
|
|
881
|
-
* Returns the two first values, appends counter if more.
|
|
882
|
-
*
|
|
883
|
-
* @param {string[]} [input] Array of values to reduce
|
|
884
|
-
* @returns {*} {string}
|
|
885
|
-
*/
|
|
886
|
-
const reduceFilterText = (input) => {
|
|
887
|
-
if (!Array.isArray(input)) {
|
|
888
|
-
return input;
|
|
889
|
-
}
|
|
890
|
-
// Only first two elements
|
|
891
|
-
const firstTwo = input.slice(0, 2);
|
|
892
|
-
const remainder = input.slice(2, input.length).length;
|
|
893
|
-
const firstTwoJoined = firstTwo.join(", ");
|
|
894
|
-
const remainderStr = remainder > 0 ? `, +${remainder}` : "";
|
|
895
|
-
return firstTwoJoined + remainderStr;
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
/**
|
|
899
|
-
* Filter is a React component that renders a filter element based on the provided filter definition and state.
|
|
900
|
-
*
|
|
901
|
-
* @returns {ReactElement} - Returns the Filter component.
|
|
902
|
-
*/
|
|
903
|
-
const Filter = ({ filter, filterBarActions, filterState, }) => {
|
|
904
|
-
const values = filterBarActions.getValuesByKey(filter.filterKey);
|
|
905
|
-
const getFilterText = () => {
|
|
906
|
-
if (!values) {
|
|
907
|
-
return undefined;
|
|
908
|
-
}
|
|
909
|
-
if (filter.valueAsText) {
|
|
910
|
-
return reduceFilterText(filter.valueAsText(values));
|
|
911
|
-
}
|
|
912
|
-
else if (filter.type === "valueNameArray") {
|
|
913
|
-
return reduceFilterText(values.map(value => value.name));
|
|
914
|
-
}
|
|
915
|
-
else if (filter.type === "valueName") {
|
|
916
|
-
return values.name;
|
|
917
|
-
}
|
|
918
|
-
return values.toString();
|
|
919
|
-
};
|
|
920
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
921
|
-
const setValue = (callback) => {
|
|
922
|
-
const newValue = callback(filterBarActions.getValuesByKey(filter.filterKey));
|
|
923
|
-
if (filter.type === "string") {
|
|
924
|
-
filterBarActions.setStringValue(filter.filterKey, newValue);
|
|
925
|
-
}
|
|
926
|
-
else if (filter.type === "stringArray") {
|
|
927
|
-
filterBarActions.setArrayValue(filter.filterKey, newValue);
|
|
928
|
-
}
|
|
929
|
-
else if (filter.type === "dateRange") {
|
|
930
|
-
filterBarActions.setDateRange(filter.filterKey, newValue);
|
|
931
|
-
}
|
|
932
|
-
else if (filter.type === "area") {
|
|
933
|
-
filterBarActions.setArea(newValue);
|
|
934
|
-
}
|
|
935
|
-
else if (filter.type === "valueNameArray") {
|
|
936
|
-
filterBarActions.setArrayObjectValue(filter.filterKey, newValue);
|
|
937
|
-
}
|
|
938
|
-
else if (filter.type === "valueName") {
|
|
939
|
-
filterBarActions.setObjectValue(filter.filterKey, newValue);
|
|
940
|
-
}
|
|
941
|
-
else if (filter.type === "minMax") {
|
|
942
|
-
filterBarActions.setMinMaxValue(filter.filterKey, newValue);
|
|
943
|
-
}
|
|
944
|
-
else if (filter.type === "boolean") {
|
|
945
|
-
filterBarActions.setBooleanValue(filter.filterKey, newValue);
|
|
946
|
-
}
|
|
947
|
-
else {
|
|
948
|
-
filterBarActions.setNumberValue(filter.filterKey, newValue);
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
const text = getFilterText();
|
|
952
|
-
const activeFilterText = Array.isArray(text) ? text.join(", ") : text;
|
|
953
|
-
const showDirectly = filter.showDirectly || false;
|
|
954
|
-
return showDirectly ? (jsxRuntime.jsx(jsxRuntime.Fragment, { children: filter.component({
|
|
955
|
-
filterDefinition: filter,
|
|
956
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
957
|
-
value: values,
|
|
958
|
-
setValue,
|
|
959
|
-
filterBarActions,
|
|
960
|
-
filterState,
|
|
961
|
-
}) })) : (jsxRuntime.jsx(reactFilterComponents.Filter, { activeLabel: activeFilterText, dataTestId: `${filter.filterKey}-filter-button`, isActive: Boolean(text?.length), popoverProps: { placement: "right-start" }, title: filter.title, withStickyHeader: true, children: filter.component({
|
|
962
|
-
filterDefinition: filter,
|
|
963
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
964
|
-
value: values,
|
|
965
|
-
setValue,
|
|
966
|
-
filterBarActions,
|
|
967
|
-
filterState,
|
|
968
|
-
}) }));
|
|
969
|
-
};
|
|
970
|
-
|
|
971
|
-
/**
|
|
972
|
-
* ResetFiltersButton is a React component that provides a button for resetting filters.
|
|
973
|
-
*
|
|
974
|
-
* @returns {ReactElement | null} The rendered ResetFiltersButton component, or null if no filters have been applied.
|
|
975
|
-
*/
|
|
976
|
-
const ResetFiltersButton = ({ filtersHaveBeenApplied, resetFiltersToInitialState, dataTestId, className, }) => {
|
|
977
|
-
const [t] = useTranslation();
|
|
978
|
-
if (!filtersHaveBeenApplied) {
|
|
979
|
-
return null;
|
|
980
|
-
}
|
|
981
|
-
return (jsxRuntime.jsx(reactComponents.Button, { className: className, dataTestId: dataTestId ?? "reset-filters-button", onClick: () => {
|
|
982
|
-
resetFiltersToInitialState();
|
|
983
|
-
}, size: "small", variant: "ghost-neutral", children: t("filtersBar.resetFilters") }));
|
|
984
|
-
};
|
|
985
|
-
|
|
986
|
-
/**
|
|
987
|
-
* StarredFiltersMenu is a React component that displays a menu of starred filters within a filter bar.
|
|
988
|
-
*
|
|
989
|
-
* @returns {ReactElement} - Returns the StarredFiltersMenu component.
|
|
990
|
-
*/
|
|
991
|
-
const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starredFilterKeys, hiddenFilters = [], className, dataTestId, }) => {
|
|
992
|
-
const [t] = useTranslation();
|
|
993
|
-
const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
|
|
994
|
-
const numberOfShowDirectlyFilters = sharedUtils.objectValues(filterBarDefinition).filter(filter => filter.showDirectly).length; //TODO: this is a bit of a silly workaround to get the count to match without changing any other logic as I am pressed for time...but it works for now.🙈
|
|
995
|
-
const hideInStarredMenu = react.useMemo(() => {
|
|
996
|
-
return sharedUtils.objectValues(filterBarDefinition)
|
|
997
|
-
.map(filter => {
|
|
998
|
-
const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
|
|
999
|
-
const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
|
|
1000
|
-
return filter.showDirectly || !showInStarredMenu || !showInFilterBar ? filter.filterKey : null;
|
|
1001
|
-
})
|
|
1002
|
-
.filter(sharedUtils.truthy);
|
|
1003
|
-
}, [filterBarDefinition]);
|
|
1004
|
-
const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
|
|
1005
|
-
...hideInStarredMenu,
|
|
1006
|
-
...hiddenFilters,
|
|
1007
|
-
]);
|
|
1008
|
-
const hiddenFiltersCount = react.useMemo(() => {
|
|
1009
|
-
const nonHiddenStarredFilterKeys = starredFilterKeys.filter(key => !hideInStarredMenu.includes(key));
|
|
1010
|
-
return (filtersGrouped.map(group => group.filters).flat().length +
|
|
1011
|
-
hiddenFilters.length -
|
|
1012
|
-
nonHiddenStarredFilterKeys.length +
|
|
1013
|
-
numberOfShowDirectlyFilters);
|
|
1014
|
-
}, [filtersGrouped, hiddenFilters, starredFilterKeys, hideInStarredMenu, numberOfShowDirectlyFilters]);
|
|
1015
|
-
const getHiddenFiltersLabel = () => {
|
|
1016
|
-
switch (hiddenFiltersCount) {
|
|
1017
|
-
case 0:
|
|
1018
|
-
return t("filtersBar.hiddenFilters.toggleFilters");
|
|
1019
|
-
case 1:
|
|
1020
|
-
return t("filtersBar.hiddenFilters.singular");
|
|
1021
|
-
default:
|
|
1022
|
-
return t("filtersBar.hiddenFilters.plural", { count: hiddenFiltersCount });
|
|
1023
|
-
}
|
|
1024
|
-
};
|
|
1025
|
-
return (jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.Button, { className: className, dataTestId: dataTestId ?? "starred-filters-menu", size: "small", variant: "ghost", children: getHiddenFiltersLabel() }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx(reactComponents.MenuList, { className: "overflow-hidden", dataTestId: "starred-filters-menu-popover", children: jsxRuntime.jsx("div", { className: "max-h-[55dvh] w-72 overflow-y-auto px-3 pb-2", children: filtersGrouped.map((group, idx) => {
|
|
1026
|
-
const isLast = idx === filtersGrouped.length - 1;
|
|
1027
|
-
return (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx(reactComponents.Text, { className: "pb-1 pt-2", dataTestId: "starred-filters-group-title", size: "small", subtle: true, uppercase: true, children: group.title }), jsxRuntime.jsx("div", { className: "grid", children: group.filters.map(filter => {
|
|
1028
|
-
return !filter.isHidden ? (jsxRuntime.jsx(reactFormComponents.ToggleSwitchOption, { className: reactComponents.cvaInteractableItem({
|
|
1029
|
-
selected: false,
|
|
1030
|
-
cursor: "pointer",
|
|
1031
|
-
focused: "auto",
|
|
1032
|
-
className: "py-1 pl-2 pr-3",
|
|
1033
|
-
}), "data-testid": `${filter.filterKey}-star-filter`, id: `visible-filters-toggle-${filter.filterKey}`, label: filter.title, onChange: toggled => {
|
|
1034
|
-
updateStarredFilters(filter.filterKey);
|
|
1035
|
-
logEvent("Starring Filter - Toggled", {
|
|
1036
|
-
type: filter.filterKey,
|
|
1037
|
-
value: toggled.toString(),
|
|
1038
|
-
});
|
|
1039
|
-
}, showInputFocus: false, size: "medium", toggled: starredFilterKeys.includes(filter.filterKey) }, filter.filterKey)) : null;
|
|
1040
|
-
}) }), !isLast && jsxRuntime.jsx("div", { className: "mt-2 h-[1px] w-full bg-neutral-300" })] }, group.key));
|
|
1041
|
-
}) }) }) })] }));
|
|
1042
|
-
};
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* StarredFilters is a React component that displays a list of starred filters based on the provided filter bar configuration.
|
|
1046
|
-
*
|
|
1047
|
-
* @template TFilterBarDefinition - The type representing the filter bar definition.
|
|
1048
|
-
* @returns {ReactElement} - Returns the StarredFilters component.
|
|
1049
|
-
*/
|
|
1050
|
-
const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, dataTestId, className, title, }) => {
|
|
1051
|
-
const [t] = useTranslation();
|
|
1052
|
-
const { isLg } = reactComponents.useViewportBreakpoints();
|
|
1053
|
-
const isCompactMode = react.useMemo(() => compact ?? !isLg, [compact, isLg]);
|
|
1054
|
-
const hideInMenu = react.useMemo(() => {
|
|
1055
|
-
return sharedUtils.objectValues(filterBarDefinition)
|
|
1056
|
-
.map(filter => {
|
|
1057
|
-
const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
|
|
1058
|
-
const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
|
|
1059
|
-
const showMenuAnywayBecauseFilterHasChanged = filterBarConfig.appliedFilterKeys().includes(filter.filterKey);
|
|
1060
|
-
return (!showInFilterBar || !showInStarredMenu) && !showMenuAnywayBecauseFilterHasChanged
|
|
1061
|
-
? filter.filterKey
|
|
1062
|
-
: null;
|
|
1063
|
-
})
|
|
1064
|
-
.filter(sharedUtils.truthy);
|
|
1065
|
-
}, [filterBarConfig, filterBarDefinition]);
|
|
1066
|
-
const { filtersGrouped } = useStarredGroupFilters(sharedUtils.objectValues(filterBarDefinition), [
|
|
1067
|
-
...hideInMenu,
|
|
1068
|
-
...hiddenFilters,
|
|
1069
|
-
]);
|
|
1070
|
-
const { appliedFilters, filtersToShow, showDirectlyFilters } = react.useMemo(() => {
|
|
1071
|
-
const allFilters = filtersGrouped.map(group => group.filters).flat();
|
|
1072
|
-
const starredFilters = allFilters.filter(filter => {
|
|
1073
|
-
return (filterBarConfig.starredFilterKeys.includes(filter.filterKey) &&
|
|
1074
|
-
!filter.showDirectly);
|
|
1075
|
-
});
|
|
1076
|
-
return {
|
|
1077
|
-
appliedFilters: starredFilters.filter(filter => filterBarConfig.appliedFilterKeys().includes(filter.filterKey)),
|
|
1078
|
-
filtersToShow: starredFilters.filter(filter => !filter.showDirectly),
|
|
1079
|
-
showDirectlyFilters: allFilters.filter(filter => filter.showDirectly),
|
|
1080
|
-
};
|
|
1081
|
-
}, [filterBarConfig, filtersGrouped]);
|
|
1082
|
-
return (jsxRuntime.jsxs("div", { className: tailwindMerge.twMerge("flex flex-wrap items-center gap-2", className), "data-testid": dataTestId, children: [jsxRuntime.jsx(reactComponents.Popover, { placement: "bottom-start", children: modalState => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx("div", { "data-testid": "starred-filters-menu-trigger", children: jsxRuntime.jsxs(reactComponents.Tooltip, { disabled: !isCompactMode || modalState.isOpen, label: jsxRuntime.jsx(FilterButtonTooltipLabel, { filterBarConfig: filterBarConfig }), children: [jsxRuntime.jsx(reactComponents.Button, { className: "@xs:flex hidden", prefix: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilterKeys().length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", suffix: isCompactMode && filterBarConfig.appliedFilterKeys().length > 0
|
|
1083
|
-
? `(${filterBarConfig.appliedFilterKeys().length})`
|
|
1084
|
-
: undefined, variant: "secondary", children: title ? title : t("filtersBar.filtersHeading") }), jsxRuntime.jsx(reactComponents.IconButton, { className: "@xs:hidden", icon: jsxRuntime.jsx(reactComponents.Icon, { color: filterBarConfig.appliedFilterKeys().length > 0 ? "primary" : undefined, name: "Funnel", size: "small" }), size: "small", variant: "secondary" })] }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { cellPadding: 100, children: jsxRuntime.jsxs(reactComponents.Card, { className: "max-h-[min(600px,_calc(100dvh-32px))] overflow-hidden sm:w-[350px]", children: [filtersToShow.length > 0 ? (jsxRuntime.jsx(reactComponents.CardBody, { density: "dense", children: jsxRuntime.jsx("div", { className: "flex h-full min-w-min flex-col gap-2", children: jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: filtersToShow }) }) })) : null, jsxRuntime.jsxs(reactComponents.CardFooter, { className: filtersToShow.length === 0 ? "border-none" : undefined, density: "dense", children: [jsxRuntime.jsx(StarredFiltersMenu, { className: "mr-auto", filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, starredFilterKeys: filterBarConfig.starredFilterKeys, updateStarredFilters: filterBarConfig.updateStarredFilters }), !isCompactMode ? null : (jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys().length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState }))] })] }) })] })) }), showDirectlyFilters.length > 0 ? (jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: showDirectlyFilters })) : null, !isCompactMode ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [appliedFilters.filter(filter => !filter.showDirectly).length > 0 ? (jsxRuntime.jsx("div", { className: "h-4 w-[1px] bg-slate-300" })) : null, jsxRuntime.jsx(FiltersList, { filterBarConfig: filterBarConfig, filters: appliedFilters }), jsxRuntime.jsx(ResetFiltersButton, { filtersHaveBeenApplied: filterBarConfig.appliedFilterKeys().length > 0, resetFiltersToInitialState: filterBarConfig.resetFiltersToInitialState })] })) : null] }));
|
|
1085
|
-
};
|
|
1086
|
-
const FiltersList = ({ filters, filterBarConfig }) => {
|
|
1087
|
-
return filters.length === 0
|
|
1088
|
-
? null
|
|
1089
|
-
: filters.map(filter => {
|
|
1090
|
-
return (jsxRuntime.jsx(Filter, { filter: filter, filterBarActions: filterBarConfig, filterState: { values: filterBarConfig.values, setters: filterBarConfig.setters } }, `filter-${filter.filterKey}`));
|
|
1091
|
-
});
|
|
1092
|
-
};
|
|
1093
|
-
const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
|
|
1094
|
-
const [t] = useTranslation();
|
|
1095
|
-
switch (filterBarConfig.appliedFilterKeys().length) {
|
|
1096
|
-
case 0:
|
|
1097
|
-
return t("filtersBar.appliedFiltersTooltip.none");
|
|
1098
|
-
case 1:
|
|
1099
|
-
return filterBarConfig.appliedFilterKeys()[0]
|
|
1100
|
-
? t("filtersBar.appliedFiltersTooltip.singular", {
|
|
1101
|
-
filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilterKeys()[0]),
|
|
1102
|
-
})
|
|
1103
|
-
: null; // should never happen though
|
|
1104
|
-
default:
|
|
1105
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [t("filtersBar.appliedFiltersTooltip.plural", { count: filterBarConfig.appliedFilterKeys().length }), jsxRuntime.jsx("ul", { className: "list-inside", children: filterBarConfig.appliedFilterKeys().map((appliedFilterKey, index) => (jsxRuntime.jsx("li", { className: "list-disc", children: filterBarConfig.getFilterTitle(appliedFilterKey) }, index))) })] }));
|
|
1106
|
-
}
|
|
1107
|
-
};
|
|
1108
|
-
|
|
1109
1143
|
/**
|
|
1110
1144
|
* The FilterBar component serves as a wrapper for managing filters.
|
|
1111
1145
|
*/
|
|
1112
|
-
const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact, title, }) => {
|
|
1113
|
-
return (jsxRuntime.jsx(
|
|
1146
|
+
const FilterBar = ({ hiddenFilters, className, filterBarDefinition, filterBarConfig, compact = true, title, }) => {
|
|
1147
|
+
return (jsxRuntime.jsx(FiltersMenu, { className: className, compact: compact, dataTestId: `${filterBarConfig.name}-filterbar`, filterBarConfig: filterBarConfig, filterBarDefinition: filterBarDefinition, hiddenFilters: hiddenFilters, title: title }));
|
|
1114
1148
|
};
|
|
1115
1149
|
|
|
1116
1150
|
// Can't import jest.fn so must define a function that does nothing but mimics the jest.fn
|
|
@@ -1932,11 +1966,16 @@ exports.DefaultMinMaxFilter = DefaultMinMaxFilter;
|
|
|
1932
1966
|
exports.DefaultRadioFilter = DefaultRadioFilter;
|
|
1933
1967
|
exports.DynamicFilterList = DynamicFilterList;
|
|
1934
1968
|
exports.FilterBar = FilterBar;
|
|
1969
|
+
exports.FilterComponent = FilterComponent;
|
|
1935
1970
|
exports.FilterEvents = FilterEvents;
|
|
1936
1971
|
exports.FilterHeader = FilterHeader;
|
|
1937
1972
|
exports.FilterResults = FilterResults;
|
|
1973
|
+
exports.FilterTableComponent = FilterTableComponent;
|
|
1974
|
+
exports.FiltersMenu = FiltersMenu;
|
|
1975
|
+
exports.FiltersRenderer = FiltersRenderer;
|
|
1976
|
+
exports.GroupedFiltersList = GroupedFiltersList;
|
|
1938
1977
|
exports.HierarchicalCheckboxFilter = HierarchicalCheckboxFilter;
|
|
1939
|
-
exports.
|
|
1978
|
+
exports.ResetFiltersButton = ResetFiltersButton;
|
|
1940
1979
|
exports.areaFilterGeoJsonGeometrySchema = areaFilterGeoJsonGeometrySchema;
|
|
1941
1980
|
exports.isAreaFilterValue = isAreaFilterValue;
|
|
1942
1981
|
exports.isArrayFilterValue = isArrayFilterValue;
|