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