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