@trackunit/filters-filter-bar 1.3.42 → 1.3.45

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 CHANGED
@@ -367,13 +367,13 @@ const DynamicFilterList = ({ rowCount, keyMapper, labelMapper, onChange, checked
367
367
  *
368
368
  * @returns {ReactElement} - Returns the FilterHeader component.
369
369
  */
370
- const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanged, resetIndividualFilterToInitialState, onResetFilter, loading = false, children, className, dataTestId, }) => {
370
+ const FilterHeader = ({ filterKey, title, searchEnabled, searchProps, filterHasChanges, resetIndividualFilterToInitialState, onResetFilter, loading = false, children, className, dataTestId, }) => {
371
371
  const [t] = useTranslation();
372
372
  const handleResetFilter = () => {
373
373
  resetIndividualFilterToInitialState(filterKey);
374
374
  onResetFilter?.();
375
375
  };
376
- return (jsxRuntime.jsxs(reactFilterComponents.FilterHeader, { className: className, dataTestId: dataTestId ?? `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), showReset: filterHasChanged(filterKey), title: title, children: [searchEnabled ? (jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value), onKeyDown: e => {
376
+ return (jsxRuntime.jsxs(reactFilterComponents.FilterHeader, { className: className, dataTestId: dataTestId ?? `${filterKey}-filter-header`, loading: loading, onReset: handleResetFilter, resetLabel: t("filtersBar.resetFilter"), showReset: filterHasChanges, title: title, children: [searchEnabled ? (jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, fieldSize: "small", id: `${filterKey}-search`, onChange: e => searchProps.onChange(e.currentTarget.value), onKeyDown: e => {
377
377
  if (e.key === "Enter" && searchProps.onEnter) {
378
378
  searchProps.onEnter(searchProps.value);
379
379
  }
@@ -502,7 +502,7 @@ const DefaultCheckboxFilter = ({ filterDefinition, filterBarActions, options, lo
502
502
  setMultipleValues(selectValues);
503
503
  }
504
504
  };
505
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilterHeader, { ...filterDefinition, ...filterBarActions, loading: loading, searchEnabled: true, searchProps: {
505
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilterHeader, { ...filterDefinition, ...filterBarActions, filterHasChanges: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), loading: loading, searchEnabled: true, searchProps: {
506
506
  value: customSearch?.value ?? searchText,
507
507
  onChange: customSearch?.onChange ?? setSearchText,
508
508
  count: undefinedCount ? filteredOptions.length - 1 : filteredOptions.length,
@@ -587,8 +587,8 @@ const DefaultMinMaxFilter = ({ filterDefinition, filterName, value, setValue, fi
587
587
  }, [value]);
588
588
  const { logEvent } = reactCoreHooks.useAnalytics(FilterEvents);
589
589
  const handleApply = () => {
590
- const realMinValue = minValue === 0 ? undefined : minValue ?? undefined;
591
- const realMaxValue = maxValue === 0 ? undefined : maxValue ?? undefined;
590
+ const realMinValue = minValue === 0 ? undefined : (minValue ?? undefined);
591
+ const realMaxValue = maxValue === 0 ? undefined : (maxValue ?? undefined);
592
592
  logEvent("Filters Applied - V2", {
593
593
  type: filterName ?? `${stringTs.capitalize(filterDefinition.filterKey)}Filter`,
594
594
  value: JSON.stringify({ min: realMinValue, max: realMaxValue }),
@@ -597,7 +597,7 @@ const DefaultMinMaxFilter = ({ filterDefinition, filterName, value, setValue, fi
597
597
  return realMinValue || realMaxValue ? { min: realMinValue, max: realMaxValue } : {};
598
598
  });
599
599
  };
600
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactFilterComponents.FilterHeader, { onReset: () => filterBarActions.resetIndividualFilterToInitialState(filterDefinition.filterKey), resetLabel: t("filtersBar.resetFilter"), showReset: filterBarActions.filterHasChanged(filterDefinition.filterKey), title: filterDefinition.title }), jsxRuntime.jsxs(reactFilterComponents.FilterBody, { children: [jsxRuntime.jsxs("div", { className: "flex gap-4 px-1", children: [jsxRuntime.jsx(reactFormComponents.NumberField, { addonAfter: unit, className: "w-40", label: t("filtersBar.defaultMinMaxFilters.min"), max: filterDefinition.type === "minMax" ? filterDefinition.maximumNumber : undefined, min: filterDefinition.type === "minMax" ? filterDefinition.minimumNumber : undefined, onChange: e => setMinValue(e.target.value === "" ? undefined : Number(e.target.value)), value: minValue ?? "" }), jsxRuntime.jsx(reactFormComponents.NumberField, { addonAfter: unit, className: "w-40", label: t("filtersBar.defaultMinMaxFilters.max"), max: filterDefinition.type === "minMax" ? filterDefinition.maximumNumber : undefined, min: filterDefinition.type === "minMax" ? filterDefinition.minimumNumber : undefined, onChange: e => setMaxValue(e.target.value === "" ? undefined : Number(e.target.value)), value: maxValue ?? "" })] }), jsxRuntime.jsx(reactFilterComponents.FilterFooter, { children: jsxRuntime.jsx(reactComponents.Button, { onClick: handleApply, size: "small", variant: "ghost", children: t("filtersBar.defaultMinMaxFilters.apply") }) })] })] }));
600
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactFilterComponents.FilterHeader, { onReset: () => filterBarActions.resetIndividualFilterToInitialState(filterDefinition.filterKey), resetLabel: t("filtersBar.resetFilter"), showReset: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), title: filterDefinition.title }), jsxRuntime.jsxs(reactFilterComponents.FilterBody, { children: [jsxRuntime.jsxs("div", { className: "flex gap-4 px-1", children: [jsxRuntime.jsx(reactFormComponents.NumberField, { addonAfter: unit, className: "w-40", label: t("filtersBar.defaultMinMaxFilters.min"), max: filterDefinition.type === "minMax" ? filterDefinition.maximumNumber : undefined, min: filterDefinition.type === "minMax" ? filterDefinition.minimumNumber : undefined, onChange: e => setMinValue(e.target.value === "" ? undefined : Number(e.target.value)), value: minValue ?? "" }), jsxRuntime.jsx(reactFormComponents.NumberField, { addonAfter: unit, className: "w-40", label: t("filtersBar.defaultMinMaxFilters.max"), max: filterDefinition.type === "minMax" ? filterDefinition.maximumNumber : undefined, min: filterDefinition.type === "minMax" ? filterDefinition.minimumNumber : undefined, onChange: e => setMaxValue(e.target.value === "" ? undefined : Number(e.target.value)), value: maxValue ?? "" })] }), jsxRuntime.jsx(reactFilterComponents.FilterFooter, { children: jsxRuntime.jsx(reactComponents.Button, { onClick: handleApply, size: "small", variant: "ghost", children: t("filtersBar.defaultMinMaxFilters.apply") }) })] })] }));
601
601
  };
602
602
 
603
603
  /**
@@ -619,7 +619,7 @@ const DefaultRadioFilter = ({ filterDefinition, filterBarActions, options, loadi
619
619
  }
620
620
  };
621
621
  const selectedRadioId = filteredOptions.find(option => filterBarActions.objectIncludesValue(filterDefinition.filterKey, option.key));
622
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilterHeader, { ...filterBarActions, ...filterDefinition, loading: loading, searchEnabled: true, searchProps: {
622
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(FilterHeader, { ...filterBarActions, ...filterDefinition, filterHasChanges: filterBarActions.appliedFilterKeys().includes(filterDefinition.filterKey), loading: loading, searchEnabled: true, searchProps: {
623
623
  value: customSearch?.value ?? searchText,
624
624
  onChange: customSearch?.onChange ?? setSearchText,
625
625
  count: filteredOptions.length,
@@ -647,7 +647,7 @@ const useStarredGroupFilters = (filterDefinitions, hiddenFilters) => {
647
647
  }))
648
648
  .filter(filter => filter.filters.length > 0);
649
649
  }, [filterDefinitions, hiddenFilters, t]);
650
- return { filtersGrouped };
650
+ return react.useMemo(() => ({ filtersGrouped }), [filtersGrouped]);
651
651
  };
652
652
  const uniqueKeysFromGroups = (filters) => [...new Set(filters.map(filter => filter.group))];
653
653
 
@@ -779,11 +779,13 @@ const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starred
779
779
  ...hideInStarredMenu,
780
780
  ...hiddenFilters,
781
781
  ]);
782
- const nonHiddenStarredFilterKeys = starredFilterKeys.filter(key => !hideInStarredMenu.includes(key));
783
- const hiddenFiltersCount = filtersGrouped.map(group => group.filters).flat().length +
784
- hiddenFilters.length -
785
- nonHiddenStarredFilterKeys.length +
786
- numberOfShowDirectlyFilters;
782
+ const hiddenFiltersCount = react.useMemo(() => {
783
+ const nonHiddenStarredFilterKeys = starredFilterKeys.filter(key => !hideInStarredMenu.includes(key));
784
+ return (filtersGrouped.map(group => group.filters).flat().length +
785
+ hiddenFilters.length -
786
+ nonHiddenStarredFilterKeys.length +
787
+ numberOfShowDirectlyFilters);
788
+ }, [filtersGrouped, hiddenFilters, starredFilterKeys, hideInStarredMenu, numberOfShowDirectlyFilters]);
787
789
  const getHiddenFiltersLabel = () => {
788
790
  switch (hiddenFiltersCount) {
789
791
  case 0:
@@ -822,13 +824,13 @@ const StarredFiltersMenu = ({ filterBarDefinition, updateStarredFilters, starred
822
824
  const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters = [], compact, dataTestId, className, }) => {
823
825
  const [t] = useTranslation();
824
826
  const { isLg } = reactComponents.useViewportBreakpoints();
825
- const isCompactMode = compact ?? !isLg;
827
+ const isCompactMode = react.useMemo(() => compact ?? !isLg, [compact, isLg]);
826
828
  const hideInMenu = react.useMemo(() => {
827
829
  return sharedUtils.objectValues(filterBarDefinition)
828
830
  .map(filter => {
829
831
  const showInFilterBar = filter.showInFilterBar ? filter.showInFilterBar() : true;
830
832
  const showInStarredMenu = filter.showInStarredMenu ? filter.showInStarredMenu() : true;
831
- const showMenuAnywayBecauseFilterHasChanged = filterBarConfig.filterHasChanged(filter.filterKey);
833
+ const showMenuAnywayBecauseFilterHasChanged = filterBarConfig.appliedFilterKeys().includes(filter.filterKey);
832
834
  return (!showInFilterBar || !showInStarredMenu) && !showMenuAnywayBecauseFilterHasChanged
833
835
  ? filter.filterKey
834
836
  : null;
@@ -839,17 +841,21 @@ const StarredFilters = ({ filterBarDefinition, filterBarConfig, hiddenFilters =
839
841
  ...hideInMenu,
840
842
  ...hiddenFilters,
841
843
  ]);
842
- const allFilters = filtersGrouped.map(group => group.filters).flat();
843
- const starredFilters = allFilters.filter(filter => {
844
- return (filterBarConfig.starredFilterKeys.includes(filter.filterKey) &&
845
- !filter.showDirectly);
846
- });
847
- const appliedFilters = starredFilters.filter(filter => filterBarConfig.appliedFilterKeys.includes(filter.filterKey));
848
- const filtersToShow = starredFilters.filter(filter => !filter.showDirectly);
849
- const showDirectlyFilters = allFilters.filter(filter => filter.showDirectly);
850
- 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
851
- ? `(${filterBarConfig.appliedFilterKeys.length})`
852
- : undefined, variant: "secondary", children: 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] }));
844
+ const { appliedFilters, filtersToShow, showDirectlyFilters } = react.useMemo(() => {
845
+ const allFilters = filtersGrouped.map(group => group.filters).flat();
846
+ const starredFilters = allFilters.filter(filter => {
847
+ return (filterBarConfig.starredFilterKeys.includes(filter.filterKey) &&
848
+ !filter.showDirectly);
849
+ });
850
+ return {
851
+ appliedFilters: starredFilters.filter(filter => filterBarConfig.appliedFilterKeys().includes(filter.filterKey)),
852
+ filtersToShow: starredFilters.filter(filter => !filter.showDirectly),
853
+ showDirectlyFilters: allFilters.filter(filter => filter.showDirectly),
854
+ };
855
+ }, [filterBarConfig, filtersGrouped]);
856
+ 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
857
+ ? `(${filterBarConfig.appliedFilterKeys().length})`
858
+ : undefined, variant: "secondary", children: 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] }));
853
859
  };
854
860
  const FiltersList = ({ filters, filterBarConfig }) => {
855
861
  return filters.length === 0
@@ -860,17 +866,17 @@ const FiltersList = ({ filters, filterBarConfig }) => {
860
866
  };
861
867
  const FilterButtonTooltipLabel = ({ filterBarConfig, }) => {
862
868
  const [t] = useTranslation();
863
- switch (filterBarConfig.appliedFilterKeys.length) {
869
+ switch (filterBarConfig.appliedFilterKeys().length) {
864
870
  case 0:
865
871
  return t("filtersBar.appliedFiltersTooltip.none");
866
872
  case 1:
867
- return filterBarConfig.appliedFilterKeys[0]
873
+ return filterBarConfig.appliedFilterKeys()[0]
868
874
  ? t("filtersBar.appliedFiltersTooltip.singular", {
869
- filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilterKeys[0]),
875
+ filterName: filterBarConfig.getFilterTitle(filterBarConfig.appliedFilterKeys()[0]),
870
876
  })
871
877
  : null; // should never happen though
872
878
  default:
873
- 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))) })] }));
879
+ 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))) })] }));
874
880
  }
875
881
  };
876
882
 
@@ -887,11 +893,11 @@ const doNothing = (args) => { };
887
893
  const mockFilterBar = {
888
894
  filterBarConfig: {
889
895
  isFilterIncludedByKey: doNothing,
890
- appliedFilterKeys: [],
896
+ appliedFilterKeys: () => [],
891
897
  arrayIncludesValue: doNothing,
892
898
  getFilterTitle: doNothing,
893
899
  getFilterBarName: doNothing,
894
- initialState: { filtered: { customerType: [] }, empty: { customerType: [] } },
900
+ initialState: { customerType: [] },
895
901
  name: "test",
896
902
  objectArrayIncludesValue: doNothing,
897
903
  resetFiltersToInitialState: doNothing,
@@ -908,7 +914,6 @@ const mockFilterBar = {
908
914
  toggleArrayObjectValue: doNothing,
909
915
  toggleArrayValue: doNothing,
910
916
  values: { customerType: [] },
911
- filterHasChanged: doNothing,
912
917
  starredFilterKeys: [],
913
918
  getValuesByKey: doNothing,
914
919
  setters: {
@@ -919,10 +924,35 @@ const mockFilterBar = {
919
924
  setObjectValue: doNothing,
920
925
  toggleObjectValue: doNothing,
921
926
  },
922
- dataLoaded: doNothing(),
923
927
  filterBarDefinition: {},
928
+ name: "test",
929
+ onValuesChange: doNothing,
924
930
  };
925
931
 
932
+ /**
933
+ *
934
+ */
935
+ const createFilterSetters = (mainFilters, setValue) => mainFilters.reduce((prev, curr) => {
936
+ const key = curr.filterKey;
937
+ return {
938
+ ...prev,
939
+ [`set${stringTs.capitalize(key)}`]: (callback) => setValue(key, callback),
940
+ };
941
+ // eslint-disable-next-line local-rules/no-typescript-assertion
942
+ }, {});
943
+
944
+ /**
945
+ *
946
+ */
947
+ const createFilterValues = (mainFilters, useDefaultValues = false) => mainFilters.reduce((prev, curr) => {
948
+ const key = curr.filterKey;
949
+ const type = curr.type;
950
+ return {
951
+ ...prev,
952
+ [key]: useDefaultValues ? (curr.defaultValue ?? getInitialValueFromType(type)) : getInitialValueFromType(type),
953
+ };
954
+ }, {});
955
+
926
956
  /**
927
957
  * A helper function that returns a default value based on the filter type.
928
958
  *
@@ -955,253 +985,40 @@ const getInitialValueFromType = (type) => {
955
985
  * @template TFilterBarDefinition - The type representing the filter bar definition.
956
986
  * @returns {FilterBarConfig<TFilterBarDefinition>} - Returns an initial filter bar configuration object.
957
987
  */
958
- const createInitialState = (name, mainFilters, initialState, setValue) => {
988
+ const createInitialState = ({ name, mainFilters, setValue, }) => {
959
989
  const defaultStarredKeys = mainFilters
960
990
  .filter(f => f.default)
991
+ // eslint-disable-next-line local-rules/no-typescript-assertion
961
992
  .map(f => f.filterKey);
962
- const values = mainFilters.reduce((prev, curr) => {
963
- const key = curr.filterKey;
964
- const type = curr.type;
965
- return {
966
- ...prev,
967
- [key]:
968
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
969
- "filtered" in initialState && initialState.filtered[key]
970
- ? initialState.filtered[key]
971
- : (curr.defaultValue ?? getInitialValueFromType(type)),
972
- };
973
- }, {});
974
- const setters = mainFilters.reduce((prev, curr) => {
975
- const key = curr.filterKey;
976
- return {
977
- ...prev,
978
- [`set${stringTs.capitalize(key)}`]: (callback) => setValue(key, callback),
979
- };
980
- }, {});
993
+ const values = createFilterValues(mainFilters, true);
994
+ const initialState = createFilterValues(mainFilters);
995
+ const setters = createFilterSetters(mainFilters, setValue);
981
996
  return {
982
997
  name,
983
- initialState: { filtered: values, empty: "empty" in initialState ? initialState.empty : {} },
984
998
  starredFilterKeys: defaultStarredKeys,
985
999
  values,
986
1000
  setters,
1001
+ initialState,
987
1002
  };
988
1003
  };
989
1004
 
990
- const areaFilterGeoJsonGeometrySchema = zod.z.union([geoJsonUtils.geoJsonPolygonSchema, geoJsonUtils.geoJsonMultiPolygonSchema]);
991
-
992
- const hasValue = (value) => {
993
- if (value === undefined || value === null) {
994
- return false;
995
- }
996
- // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
997
- if (typeof value === "object" && Object.keys(value).length === 0) {
998
- return false;
999
- }
1000
- return !(Array.isArray(value) && value.length === 0);
1001
- };
1002
- const isNotRightType = (filterDefinition, foundFilter) => {
1003
- return ((filterDefinition.type === "valueNameArray" && !isValueNameArray(foundFilter)) ||
1004
- (filterDefinition.type === "valueName" && !isValueName(foundFilter)) ||
1005
- (filterDefinition.type === "stringArray" && !isStringArrayFilterValue(foundFilter)) ||
1006
- (filterDefinition.type === "dateRange" && !isDateRangeValue(foundFilter)) ||
1007
- (filterDefinition.type === "area" && !isAreaFilterValue(foundFilter)) ||
1008
- (filterDefinition.type === "minMax" && !isMinMaxFilterValue(foundFilter)) ||
1009
- (filterDefinition.type === "boolean" && !isBooleanValue(foundFilter)) ||
1010
- (filterDefinition.type === "string" && typeof foundFilter !== "string") ||
1011
- (filterDefinition.type === "number" && typeof foundFilter !== "number"));
1012
- };
1013
- /**
1014
- *
1015
- */
1016
- const isMinMaxFilterValue = (value) => {
1017
- return value
1018
- ? // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1019
- typeof value === "object" && (Object.keys(value).includes("min") || Object.keys(value).includes("max"))
1020
- : false;
1021
- };
1022
- /**
1023
- *
1024
- */
1025
- const isDateRangeValue = (value) => {
1026
- return value
1027
- ? // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1028
- typeof value === "object" && (Object.keys(value).includes("from") || Object.keys(value).includes("to"))
1029
- : false;
1030
- };
1031
- /**
1032
- * {
1033
- type: "Polygon";
1034
- coordinates: [number, number][][];
1035
- }
1036
- */
1037
- const isAreaFilterValue = (value) => {
1038
- return areaFilterGeoJsonGeometrySchema.safeParse(value).success;
1039
- };
1040
- /**
1041
- *
1042
- */
1043
- const isArrayFilterValue = (value) => {
1044
- return Array.isArray(value);
1045
- };
1046
- /**
1047
- *
1048
- */
1049
- const isStringArrayFilterValue = (value) => {
1050
- return isArrayFilterValue(value) && value.every(item => typeof item === "string");
1051
- };
1052
- /**
1053
- *
1054
- */
1055
- const isBooleanValue = (value) => {
1056
- // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1057
- return value ? typeof value === "object" && Object.keys(value).includes("booleanValue") : false;
1058
- };
1059
- /**
1060
- * Type guard to check if a value is a single ValueName object
1061
- */
1062
- const isValueName = (value) => {
1063
- return (typeof value === "object" &&
1064
- value !== null &&
1065
- // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1066
- Object.keys(value).includes("name") &&
1067
- // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1068
- Object.keys(value).includes("value"));
1069
- };
1070
- /**
1071
- * Type guard to check if a value is an array of ValueName objects
1072
- */
1073
- const isValueNameArray = (value) => {
1074
- return isArrayFilterValue(value) && value.every(isValueName);
1075
- };
1076
- /**
1077
- * Validates a filter configuration against filter definitions.
1078
- *
1079
- * @template TFilterBarDefinition - The type of the filter bar definition.
1080
- * @param {FilterBarConfig<TFilterBarDefinition>} filter - The filter configuration to validate.
1081
- * @param {FilterDefinition[]} filterDefinitions - An array of filter definitions to validate against.
1082
- * @returns {boolean} - Returns `true` if the filter configuration is valid, otherwise `false`.
1083
- */
1084
- const validateFilter = (filter, filterDefinitions) => {
1085
- const stateKeys = [];
1086
- let inBadState = false;
1087
- // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1088
- for (const key of Object.keys(filter?.values || {})) {
1089
- if (filterDefinitions.find(filterDefinition => filterDefinition.filterKey === key)) {
1090
- stateKeys.push(key);
1091
- }
1092
- else {
1093
- inBadState = true;
1094
- }
1095
- }
1096
- filterDefinitions.forEach(filterDefinition => {
1097
- const foundFilter = filter?.values && filter.values[filterDefinition.filterKey];
1098
- if (filter) {
1099
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1100
- if (foundFilter && hasValue(foundFilter) && isNotRightType(filterDefinition, foundFilter)) {
1101
- inBadState = true;
1102
- }
1103
- }
1104
- else {
1105
- inBadState = true;
1106
- }
1107
- });
1108
- stateKeys.sort((a, b) => a.localeCompare(b));
1109
- const filterKeysNotEqual = !isEqual(stateKeys, filterDefinitions.map(f => f.filterKey).sort((a, b) => a.localeCompare(b)));
1110
- return !(inBadState || filterKeysNotEqual);
1111
- };
1112
-
1113
1005
  /**
1114
- * Custom hook for managing a filter bar's state and actions.
1006
+ * Custom hook for managing a filter bar's actions .
1115
1007
  *
1116
1008
  * @template TFilterBarDefinition - A generic type for the filter bar definition.
1117
1009
  * @returns {object} An object containing filter bar configuration and actions.
1118
1010
  */
1119
- const useFilterBar = ({ name, onValuesChange, filterBarDefinition, loadAsync, initialState, }) => {
1120
- const [asyncLoadedFilterBarDefinitions, setAsyncLoadedFilterBarDefinitions] = react.useState();
1121
- const internalFilterBarDefinitions = react.useMemo(() => asyncLoadedFilterBarDefinitions ?? filterBarDefinition, [filterBarDefinition, asyncLoadedFilterBarDefinitions]);
1122
- const { clientSideUserId } = reactCoreHooks.useCurrentUser();
1123
- const setValue = react.useCallback((key, callback) => {
1124
- setFilterBarConfig(prevState => {
1125
- return {
1126
- ...prevState,
1127
- values: {
1128
- ...prevState.values,
1129
- [key]: callback(prevState.values[key]),
1130
- },
1131
- };
1132
- });
1133
- }, []);
1134
- const [initialStoredFilters] = react.useState(() => localStorage.getItem(`filter-${name}-${clientSideUserId}`) || "{}");
1135
- const loadData = react.useCallback((updatedFilterDefinitionsValues) => {
1136
- let initialFilterBarConfig;
1137
- const storedFilters = initialStoredFilters;
1138
- if (storedFilters && storedFilters !== "undefined") {
1139
- const loadedFilterBarConfig = JSON.parse(storedFilters);
1140
- if (validateFilter(loadedFilterBarConfig, updatedFilterDefinitionsValues)) {
1141
- initialFilterBarConfig = {
1142
- ...loadedFilterBarConfig,
1143
- initialState: initialState || loadedFilterBarConfig.initialState,
1144
- };
1145
- }
1146
- }
1147
- //WHY WE NEED THIS?
1148
- //For filters that are not visible, and we want to set the default value to the initial state.
1149
- //To do this for a changing default value as in customers and sites we would need to recreate the initialFilterBarConfig every time the default value changes.
1150
- //This mean that filterbars that have this functionality wouldn't be able to save the state of the filterbar.
1151
- //Another option would be to create a new filterbar for each customer or site. This also has its drawbacks. Would raise it with the frontend community.
1152
- const hasNonVisibleDefaultValues = updatedFilterDefinitionsValues.some(value => value.showInStarredMenu &&
1153
- !value.showInStarredMenu() &&
1154
- value.showInFilterBar &&
1155
- !value.showInFilterBar() &&
1156
- value.defaultValue?.toString &&
1157
- value.defaultValue.toString().length > 0);
1158
- if (initialFilterBarConfig === undefined || hasNonVisibleDefaultValues) {
1159
- initialFilterBarConfig = createInitialState(name, updatedFilterDefinitionsValues, initialState || {}, setValue);
1160
- }
1161
- // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1162
- Object.keys(initialFilterBarConfig.values).forEach(key => {
1163
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1164
- initialFilterBarConfig.setters[`set${stringTs.capitalize(key)}`] = (callback) => setValue(key, callback);
1165
- });
1166
- return initialFilterBarConfig;
1167
- }, [initialState, name, setValue, initialStoredFilters]);
1168
- const dataLoaded = react.useCallback((loadedFilterDefinitionsValues) => {
1169
- if (!loadAsync) {
1170
- throw new Error("You must pass in loadAsync to useFilterBar when loading filter data asynchronously");
1171
- }
1172
- setAsyncLoadedFilterBarDefinitions(loadedFilterDefinitionsValues);
1173
- setFilterBarConfig(prev => loadData(sharedUtils.objectValues(loadedFilterDefinitionsValues)));
1174
- }, [loadAsync, loadData]);
1175
- const [filterBarConfig, setFilterBarConfig] = react.useState(() => {
1176
- let initialFilterBarConfig;
1177
- if (!loadAsync) {
1178
- initialFilterBarConfig = loadData(sharedUtils.objectValues(internalFilterBarDefinitions));
1179
- }
1180
- else {
1181
- initialFilterBarConfig = createInitialState(name, sharedUtils.objectValues(internalFilterBarDefinitions), initialState || {}, setValue);
1182
- // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1183
- Object.keys(initialFilterBarConfig.values).forEach(key => {
1184
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1185
- initialFilterBarConfig.setters[`set${stringTs.capitalize(key)}`] = (callback) => setValue(key, callback);
1186
- });
1187
- }
1188
- return initialFilterBarConfig;
1189
- });
1190
- react.useEffect(() => {
1191
- onValuesChange?.(filterBarConfig.values);
1192
- }, [filterBarConfig.values, filterBarConfig, onValuesChange]);
1193
- react.useEffect(() => {
1194
- localStorage.setItem(`filter-${name}-${clientSideUserId}`, JSON.stringify(filterBarConfig));
1195
- }, [filterBarConfig, name, clientSideUserId]);
1011
+ const useFilterBarActions = ({ name, filterBarConfig, filterBarDefinition, setFilterBarConfig, setValue, initialState, }) => {
1196
1012
  const filterMapGetter = react.useMemo(() => {
1197
1013
  return {
1198
1014
  getFilterBarName: () => {
1199
1015
  return filterBarConfig.name;
1200
1016
  },
1201
1017
  getFilterTitle(key) {
1202
- return internalFilterBarDefinitions[key]?.title ?? key;
1018
+ return filterBarDefinition[key]?.title ?? key;
1203
1019
  },
1204
1020
  arrayIncludesValue(key, value) {
1021
+ // eslint-disable-next-line local-rules/no-typescript-assertion
1205
1022
  const filter = filterBarConfig.values[key];
1206
1023
  return filter?.includes(value) || false;
1207
1024
  },
@@ -1212,58 +1029,37 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, loadAsync, in
1212
1029
  const values = filterBarConfig.values[key];
1213
1030
  return Boolean(values);
1214
1031
  },
1215
- get appliedFilterKeys() {
1216
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1217
- const initialStateFilteredValues = JSON.parse(JSON.stringify(filterBarConfig.initialState?.filtered || {}));
1218
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1219
- const initialStateEmptyValues = JSON.parse(JSON.stringify(filterBarConfig.initialState?.empty || {}));
1220
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1221
- const currentFilters = JSON.parse(JSON.stringify(filterBarConfig.values || {}));
1222
- return sharedUtils.objectKeys(currentFilters)
1032
+ appliedFilterKeys() {
1033
+ const initialStateEmptyValues = JSON.parse(JSON.stringify(initialState ? initialState : {}));
1034
+ const currentFilters = JSON.parse(JSON.stringify(filterBarConfig.values));
1035
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1036
+ return Object.keys(currentFilters)
1223
1037
  .filter(filterKey => {
1224
- const isFilterValueEqualToInitialStateValue = dequal.dequal(currentFilters[filterKey], initialStateFilteredValues[filterKey]);
1225
- const emptyStateValue = initialStateEmptyValues[filterKey];
1226
- // If we passed an initialState's empty state, we have to compare whether this field is different
1227
- // from the empty state. If the field is different from the empty state, it means that it is an active filter.
1228
- if (emptyStateValue) {
1229
- const isFilterValueEqualToEmptyStateValue = dequal.dequal(currentFilters[filterKey], emptyStateValue);
1230
- return !isFilterValueEqualToEmptyStateValue;
1231
- }
1232
- // Otherwise, we need to check whether our filter's field value equals the initial state's field value
1233
- // The initialState value is created based on the `initialState` passed to this hook, and some magic
1234
- // done in the `createInitialState` function.
1038
+ const isFilterValueEqualToInitialStateValue = dequal.dequal(currentFilters[filterKey], initialStateEmptyValues[filterKey]);
1235
1039
  return !isFilterValueEqualToInitialStateValue;
1236
1040
  })
1237
1041
  .map(key => String(key));
1238
1042
  },
1239
- filterHasChanged(key) {
1240
- const initialStateFilteredValue = JSON.parse(
1241
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1242
- JSON.stringify(filterBarConfig.initialState?.filtered?.[key] || {}));
1243
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1244
- const currentFilter = JSON.parse(JSON.stringify(filterBarConfig.values[key] || {}));
1245
- return !dequal.dequal(currentFilter, initialStateFilteredValue);
1246
- },
1247
1043
  objectArrayIncludesValue(key, value) {
1044
+ // eslint-disable-next-line local-rules/no-typescript-assertion
1248
1045
  const filter = filterBarConfig.values[key];
1249
1046
  return filter?.find(f => f.value === value) !== undefined || false;
1250
1047
  },
1251
1048
  objectIncludesValue(key, value) {
1049
+ // eslint-disable-next-line local-rules/no-typescript-assertion
1252
1050
  const filter = filterBarConfig.values[key];
1253
1051
  return filter?.value === value || false;
1254
1052
  },
1255
1053
  };
1256
- }, [
1257
- filterBarConfig.initialState.empty,
1258
- filterBarConfig.initialState.filtered,
1259
- filterBarConfig.name,
1260
- filterBarConfig.values,
1261
- internalFilterBarDefinitions,
1262
- ]);
1054
+ }, [filterBarDefinition, filterBarConfig.name, filterBarConfig.values, initialState]);
1263
1055
  const filterMapActions = react.useMemo(() => {
1264
1056
  // Reset an individual filter to its initial state
1265
1057
  const resetIndividualFilterToInitialState = (key) => {
1266
- const tmpInitialState = createInitialState(name, sharedUtils.objectValues(internalFilterBarDefinitions), initialState || {}, setValue);
1058
+ const tmpInitialState = createInitialState({
1059
+ name,
1060
+ mainFilters: sharedUtils.objectValues(filterBarDefinition),
1061
+ setValue,
1062
+ });
1267
1063
  setFilterBarConfig(prevState => {
1268
1064
  return {
1269
1065
  ...prevState,
@@ -1476,20 +1272,315 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, loadAsync, in
1476
1272
  setFilterBarConfig(prevState => {
1477
1273
  return {
1478
1274
  ...prevState,
1479
- values: createInitialState(name, sharedUtils.objectValues(internalFilterBarDefinitions), initialState || {}, setValue).values,
1275
+ values: createInitialState({
1276
+ name,
1277
+ mainFilters: sharedUtils.objectValues(filterBarDefinition),
1278
+ setValue,
1279
+ }).values,
1480
1280
  };
1481
1281
  });
1482
1282
  },
1483
1283
  resetIndividualFilterToInitialState,
1484
1284
  };
1485
- }, [initialState, name, setValue, internalFilterBarDefinitions]);
1285
+ }, [name, setFilterBarConfig, setValue, filterBarDefinition]);
1286
+ return react.useMemo(() => ({ filterMapGetter, filterMapActions }), [filterMapGetter, filterMapActions]);
1287
+ };
1288
+
1289
+ const areaFilterGeoJsonGeometrySchema = zod.z.union([geoJsonUtils.geoJsonPolygonSchema, geoJsonUtils.geoJsonMultiPolygonSchema]);
1290
+
1291
+ const hasValue = (value) => {
1292
+ if (value === undefined || value === null) {
1293
+ return false;
1294
+ }
1295
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1296
+ if (typeof value === "object" && Object.keys(value).length === 0) {
1297
+ return false;
1298
+ }
1299
+ return !(Array.isArray(value) && value.length === 0);
1300
+ };
1301
+ const isNotRightType = (filterDefinition, foundFilter) => {
1302
+ return ((filterDefinition.type === "valueNameArray" && !isValueNameArray(foundFilter)) ||
1303
+ (filterDefinition.type === "valueName" && !isValueName(foundFilter)) ||
1304
+ (filterDefinition.type === "stringArray" && !isStringArrayFilterValue(foundFilter)) ||
1305
+ (filterDefinition.type === "dateRange" && !isDateRangeValue(foundFilter)) ||
1306
+ (filterDefinition.type === "area" && !isAreaFilterValue(foundFilter)) ||
1307
+ (filterDefinition.type === "minMax" && !isMinMaxFilterValue(foundFilter)) ||
1308
+ (filterDefinition.type === "boolean" && !isBooleanValue(foundFilter)) ||
1309
+ (filterDefinition.type === "string" && typeof foundFilter !== "string") ||
1310
+ (filterDefinition.type === "number" && typeof foundFilter !== "number"));
1311
+ };
1312
+ /**
1313
+ *
1314
+ */
1315
+ const isMinMaxFilterValue = (value) => {
1316
+ return value
1317
+ ? // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1318
+ typeof value === "object" && (Object.keys(value).includes("min") || Object.keys(value).includes("max"))
1319
+ : false;
1320
+ };
1321
+ /**
1322
+ *
1323
+ */
1324
+ const isDateRangeValue = (value) => {
1325
+ return value
1326
+ ? // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1327
+ typeof value === "object" && (Object.keys(value).includes("from") || Object.keys(value).includes("to"))
1328
+ : false;
1329
+ };
1330
+ /**
1331
+ * {
1332
+ type: "Polygon";
1333
+ coordinates: [number, number][][];
1334
+ }
1335
+ */
1336
+ const isAreaFilterValue = (value) => {
1337
+ return areaFilterGeoJsonGeometrySchema.safeParse(value).success;
1338
+ };
1339
+ /**
1340
+ *
1341
+ */
1342
+ const isArrayFilterValue = (value) => {
1343
+ return Array.isArray(value);
1344
+ };
1345
+ /**
1346
+ *
1347
+ */
1348
+ const isStringArrayFilterValue = (value) => {
1349
+ return isArrayFilterValue(value) && value.every(item => typeof item === "string");
1350
+ };
1351
+ /**
1352
+ *
1353
+ */
1354
+ const isBooleanValue = (value) => {
1355
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1356
+ return value ? typeof value === "object" && Object.keys(value).includes("booleanValue") : false;
1357
+ };
1358
+ /**
1359
+ * Type guard to check if a value is a single ValueName object
1360
+ */
1361
+ const isValueName = (value) => {
1362
+ return (typeof value === "object" &&
1363
+ value !== null &&
1364
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1365
+ Object.keys(value).includes("name") &&
1366
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1367
+ Object.keys(value).includes("value"));
1368
+ };
1369
+ /**
1370
+ * Type guard to check if a value is an array of ValueName objects
1371
+ */
1372
+ const isValueNameArray = (value) => {
1373
+ return isArrayFilterValue(value) && value.every(isValueName);
1374
+ };
1375
+ /**
1376
+ * Validates a filter configuration against filter definitions.
1377
+ *
1378
+ * @template TFilterBarDefinition - The type of the filter bar definition.
1379
+ * @param {FilterBarConfig<TFilterBarDefinition>} filter - The filter configuration to validate.
1380
+ * @param {FilterDefinition[]} filterDefinitions - An array of filter definitions to validate against.
1381
+ * @returns {boolean} - Returns `true` if the filter configuration is valid, otherwise `false`.
1382
+ */
1383
+ const validateFilter = ({ values, starredFilterKeys, filterDefinitions, }) => {
1384
+ const stateKeys = [];
1385
+ let inBadState = false;
1386
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1387
+ for (const key of Object.keys(values)) {
1388
+ if (filterDefinitions.find(filterDefinition => filterDefinition.filterKey === key)) {
1389
+ stateKeys.push(key);
1390
+ }
1391
+ else {
1392
+ inBadState = true;
1393
+ }
1394
+ }
1395
+ filterDefinitions.forEach(filterDefinition => {
1396
+ const foundFilter = values[filterDefinition.filterKey];
1397
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1398
+ if (foundFilter && hasValue(foundFilter) && isNotRightType(filterDefinition, foundFilter)) {
1399
+ inBadState = true;
1400
+ }
1401
+ });
1402
+ if (starredFilterKeys.length > 0) {
1403
+ const allKeys = filterDefinitions.map(f => f.filterKey);
1404
+ const filteredStarredFilterKeys = starredFilterKeys.filter(key => allKeys.includes(key));
1405
+ if (filteredStarredFilterKeys.length !== starredFilterKeys.length) {
1406
+ inBadState = true;
1407
+ }
1408
+ }
1409
+ stateKeys.sort((a, b) => a.localeCompare(b));
1410
+ const filterKeysNotEqual = !isEqual(stateKeys, filterDefinitions.map(f => f.filterKey).sort((a, b) => a.localeCompare(b)));
1411
+ return !(inBadState || filterKeysNotEqual);
1412
+ };
1413
+
1414
+ const getPersistenceKey = (name, clientSideUserId) => `filter-${name}-${clientSideUserId}`;
1415
+ /**
1416
+ * Custom hook for managing the persistence of filter bar configurations.
1417
+ *
1418
+ * @template TFilterBarDefinition - The type of the filter bar definition.
1419
+ * @param {FilterBarPersistenceProps<TFilterBarDefinition>} props - The props for the filter bar persistence.
1420
+ * @returns { object } An object containing the loadData and saveData functions.
1421
+ */
1422
+ const useFilterBarPersistence = ({ name, setValue, }) => {
1423
+ const { clientSideUserId } = reactCoreHooks.useCurrentUser();
1424
+ const [initialStoredFilters] = react.useState(() => localStorage.getItem(getPersistenceKey(name, clientSideUserId)) || "{}");
1425
+ const saveData = react.useCallback((filterBarConfig) => {
1426
+ const toPersist = {
1427
+ values: filterBarConfig.values ?? {},
1428
+ starredFilterKeys: filterBarConfig.starredFilterKeys ?? [],
1429
+ };
1430
+ localStorage.setItem(getPersistenceKey(name, clientSideUserId), JSON.stringify(toPersist));
1431
+ }, [name, clientSideUserId]);
1432
+ const loadData = react.useCallback((updatedFilterDefinitionsValues) => {
1433
+ let initialFilterBarConfig;
1434
+ const storedFilters = initialStoredFilters;
1435
+ const hasNonVisibleDefaultValues = updatedFilterDefinitionsValues.some(value => value.showInStarredMenu &&
1436
+ !value.showInStarredMenu() &&
1437
+ value.showInFilterBar &&
1438
+ !value.showInFilterBar() &&
1439
+ value.defaultValue?.toString &&
1440
+ value.defaultValue.toString().length > 0);
1441
+ const initialStateValues = createInitialState({
1442
+ name,
1443
+ mainFilters: updatedFilterDefinitionsValues,
1444
+ setValue,
1445
+ });
1446
+ if (storedFilters && storedFilters !== "undefined") {
1447
+ const loadedFilterBarConfigValues = JSON.parse(storedFilters);
1448
+ if (!loadedFilterBarConfigValues.values) {
1449
+ loadedFilterBarConfigValues.values = {};
1450
+ }
1451
+ if (!loadedFilterBarConfigValues.starredFilterKeys) {
1452
+ loadedFilterBarConfigValues.starredFilterKeys = [];
1453
+ }
1454
+ if (validateFilter({
1455
+ values: loadedFilterBarConfigValues.values,
1456
+ starredFilterKeys: loadedFilterBarConfigValues.starredFilterKeys,
1457
+ filterDefinitions: updatedFilterDefinitionsValues,
1458
+ })) {
1459
+ initialFilterBarConfig = {
1460
+ values: loadedFilterBarConfigValues.values,
1461
+ name: name,
1462
+ starredFilterKeys: loadedFilterBarConfigValues.starredFilterKeys,
1463
+ setters: initialStateValues.setters,
1464
+ initialState: initialStateValues.initialState,
1465
+ };
1466
+ }
1467
+ }
1468
+ if (initialFilterBarConfig === undefined || hasNonVisibleDefaultValues) {
1469
+ initialFilterBarConfig = initialStateValues;
1470
+ }
1471
+ // eslint-disable-next-line no-autofix/local-rules/prefer-custom-object-keys
1472
+ Object.keys(initialFilterBarConfig.values).forEach(key => {
1473
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1474
+ initialFilterBarConfig.setters[`set${stringTs.capitalize(key)}`] = (callback) => setValue(key, callback);
1475
+ });
1476
+ return initialFilterBarConfig;
1477
+ }, [name, setValue, initialStoredFilters]);
1478
+ return react.useMemo(() => ({ loadData, saveData }), [loadData, saveData]);
1479
+ };
1480
+
1481
+ /**
1482
+ * Generic hook for setting the value of a filter bar.
1483
+ *
1484
+ * @template TFilterBarDefinition - The type of the filter bar definition.
1485
+ * @returns {object} An object containing the setValue function.
1486
+ */
1487
+ const useGenericSetValue = () => {
1488
+ const setValue = react.useCallback((setFilterBarConfig, key, callback) => {
1489
+ setFilterBarConfig(prevState => {
1490
+ return {
1491
+ ...prevState,
1492
+ values: {
1493
+ ...prevState.values,
1494
+ [key]: callback(prevState.values[key]),
1495
+ },
1496
+ };
1497
+ });
1498
+ }, []);
1499
+ return react.useMemo(() => ({ setValue }), [setValue]);
1500
+ };
1501
+
1502
+ /**
1503
+ * Custom hook for managing a filter bar's state and actions.
1504
+ *
1505
+ * @template TFilterBarDefinition - A generic type for the filter bar definition.
1506
+ * @returns {object} An object containing filter bar configuration and actions.
1507
+ */
1508
+ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, }) => {
1509
+ const { setValue } = useGenericSetValue();
1510
+ const { loadData, saveData } = useFilterBarPersistence({
1511
+ name,
1512
+ setValue: (key, callback) => setValue(setFilterBarConfig, key, callback),
1513
+ });
1514
+ const [filterBarConfig, setFilterBarConfig] = react.useState(() => {
1515
+ return loadData(sharedUtils.objectValues(filterBarDefinition));
1516
+ });
1517
+ const { filterMapActions, filterMapGetter } = useFilterBarActions({
1518
+ name,
1519
+ filterBarConfig,
1520
+ filterBarDefinition,
1521
+ initialState: filterBarConfig.initialState,
1522
+ setFilterBarConfig,
1523
+ setValue: (key, callback) => setValue(setFilterBarConfig, key, callback),
1524
+ });
1525
+ react.useEffect(() => {
1526
+ onValuesChange?.(filterBarConfig.values);
1527
+ saveData(filterBarConfig);
1528
+ }, [filterBarConfig.values, filterBarConfig, onValuesChange, saveData]);
1529
+ return react.useMemo(() => {
1530
+ return {
1531
+ filterBarConfig: { ...filterBarConfig, ...filterMapActions, ...filterMapGetter },
1532
+ filterBarDefinition,
1533
+ name,
1534
+ onValuesChange,
1535
+ };
1536
+ }, [filterBarConfig, filterMapActions, filterMapGetter, filterBarDefinition, name, onValuesChange]);
1537
+ };
1538
+
1539
+ /**
1540
+ * Custom hook for managing a filter bar's state and actions.
1541
+ *
1542
+ * @template TFilterBarDefinition - A generic type for the filter bar definition.
1543
+ * @returns {object} An object containing filter bar configuration and actions.
1544
+ */
1545
+ const useFilterBarAsync = ({ name, onValuesChange, filterBarDefinition, }) => {
1546
+ const [isDataLoaded, setIsDataLoaded] = react.useState(false);
1547
+ const [asyncLoadedFilterBarDefinitions, setAsyncLoadedFilterBarDefinitions] = react.useState();
1548
+ const internalFilterBarDefinitions = react.useMemo(() => asyncLoadedFilterBarDefinitions ?? filterBarDefinition, [filterBarDefinition, asyncLoadedFilterBarDefinitions]);
1549
+ const { setValue } = useGenericSetValue();
1550
+ const { loadData, saveData } = useFilterBarPersistence({
1551
+ name,
1552
+ setValue: (key, callback) => setValue(setFilterBarConfig, key, callback),
1553
+ });
1554
+ const [filterBarConfig, setFilterBarConfig] = react.useState(() => {
1555
+ return loadData(sharedUtils.objectValues(internalFilterBarDefinitions));
1556
+ });
1557
+ const { filterMapActions, filterMapGetter } = useFilterBarActions({
1558
+ name,
1559
+ filterBarConfig,
1560
+ filterBarDefinition,
1561
+ initialState: filterBarConfig.initialState,
1562
+ setFilterBarConfig,
1563
+ setValue: (key, callback) => setValue(setFilterBarConfig, key, callback),
1564
+ });
1565
+ const dataLoaded = react.useCallback((loadedFilterDefinitionsValues) => {
1566
+ setIsDataLoaded(true);
1567
+ setAsyncLoadedFilterBarDefinitions(loadedFilterDefinitionsValues);
1568
+ setFilterBarConfig(_ => loadData(sharedUtils.objectValues(loadedFilterDefinitionsValues)));
1569
+ }, [loadData]);
1570
+ react.useEffect(() => {
1571
+ if (isDataLoaded) {
1572
+ onValuesChange?.(filterBarConfig.values);
1573
+ saveData(filterBarConfig);
1574
+ }
1575
+ }, [filterBarConfig.values, filterBarConfig, onValuesChange, saveData, isDataLoaded]);
1486
1576
  return react.useMemo(() => {
1487
1577
  return {
1488
1578
  filterBarConfig: { ...filterBarConfig, ...filterMapActions, ...filterMapGetter },
1489
1579
  filterBarDefinition: internalFilterBarDefinitions,
1490
1580
  dataLoaded,
1581
+ name,
1491
1582
  };
1492
- }, [filterBarConfig, filterMapActions, filterMapGetter, internalFilterBarDefinitions, dataLoaded]);
1583
+ }, [filterBarConfig, filterMapActions, filterMapGetter, internalFilterBarDefinitions, dataLoaded, name]);
1493
1584
  };
1494
1585
 
1495
1586
  /**
@@ -1506,47 +1597,43 @@ const useFilterBar = ({ name, onValuesChange, filterBarDefinition, loadAsync, in
1506
1597
  */
1507
1598
  const useSearchParamAsFilter = ({ searchParamName, filterName, search, zodSchema, errorHandler, }) => {
1508
1599
  return react.useMemo(() => {
1509
- if (sharedUtils.objectKeys(search).includes(searchParamName)) {
1510
- const foundParam = search[searchParamName];
1511
- try {
1512
- let jsonParsed;
1600
+ if (!sharedUtils.objectKeys(search).includes(searchParamName)) {
1601
+ return null;
1602
+ }
1603
+ const foundParam = search[searchParamName];
1604
+ try {
1605
+ const getJsonParsedVal = () => {
1513
1606
  if (zodSchema._def.typeName === "ZodString") {
1514
- jsonParsed = foundParam ? foundParam + "" : foundParam;
1607
+ return foundParam ? foundParam + "" : foundParam;
1515
1608
  }
1516
- else {
1517
- if (typeof search === "string" && typeof foundParam === "string") {
1518
- jsonParsed = JSON.parse(foundParam);
1519
- }
1520
- else {
1521
- jsonParsed = foundParam;
1522
- }
1523
- }
1524
- const zodParsed = zodSchema.safeParse(jsonParsed);
1525
- if (zodParsed.success) {
1526
- return zodParsed.data;
1609
+ if (typeof search === "string" && typeof foundParam === "string") {
1610
+ return JSON.parse(foundParam);
1527
1611
  }
1528
- else {
1529
- captureUrlParseException(errorHandler, {
1530
- filterName,
1531
- param: typeof foundParam === "string" ? foundParam : JSON.stringify(foundParam),
1532
- jsonParsed: jsonParsed,
1533
- zodParseError: zodParsed.error,
1534
- });
1535
- }
1536
- }
1537
- catch (e) {
1538
- captureUrlParseException(errorHandler, {
1539
- filterName,
1540
- param: typeof foundParam === "string" ? foundParam : JSON.stringify(foundParam),
1541
- jsonParsed: "parse json error",
1542
- zodParseError: null,
1543
- });
1612
+ return foundParam;
1613
+ };
1614
+ const jsonParsed = getJsonParsedVal();
1615
+ const zodParsed = zodSchema.safeParse(jsonParsed);
1616
+ if (zodParsed.success) {
1617
+ return zodParsed.data;
1544
1618
  }
1619
+ captureUrlParseException(errorHandler, {
1620
+ filterName,
1621
+ param: typeof foundParam === "string" ? foundParam : JSON.stringify(foundParam),
1622
+ jsonParsed: jsonParsed,
1623
+ zodParseError: zodParsed.error,
1624
+ });
1625
+ }
1626
+ catch (e) {
1627
+ captureUrlParseException(errorHandler, {
1628
+ filterName,
1629
+ param: typeof foundParam === "string" ? foundParam : JSON.stringify(foundParam),
1630
+ jsonParsed: "parse json error",
1631
+ zodParseError: null,
1632
+ });
1545
1633
  }
1546
- return null;
1547
1634
  }, [search, searchParamName, zodSchema, errorHandler, filterName]);
1548
1635
  };
1549
- const captureUrlParseException = (errorHandler, { filterName, param, jsonParsed: parsed, }) => errorHandler.captureException(new Error(JSON.stringify({
1636
+ const captureUrlParseException = (errorHandler, { filterName, param, jsonParsed: parsed }) => errorHandler.captureException(new Error(JSON.stringify({
1550
1637
  info: `Received invalid values for ${filterName} from URL query params. Can't set ${filterName} filter with this. Please fix the URL query params (or schema).`,
1551
1638
  param,
1552
1639
  parsed,
@@ -1596,5 +1683,6 @@ exports.mergeFilters = mergeFilters;
1596
1683
  exports.mockFilterBar = mockFilterBar;
1597
1684
  exports.toggleFilterValue = toggleFilterValue;
1598
1685
  exports.useFilterBar = useFilterBar;
1686
+ exports.useFilterBarAsync = useFilterBarAsync;
1599
1687
  exports.useSearchParamAsFilter = useSearchParamAsFilter;
1600
1688
  exports.validateFilter = validateFilter;