add reset button to list filters

This commit is contained in:
jeffvli
2026-01-17 17:43:55 -08:00
parent 5b519320c2
commit 9b97a3fa61
11 changed files with 180 additions and 105 deletions
@@ -15,6 +15,7 @@ import {
} from '/@/renderer/features/shared/components/multi-select-rows';
import { useCurrentServerId } from '/@/renderer/store';
import { useAppStore, useAppStoreActions } from '/@/renderer/store/app.store';
import { Button } from '/@/shared/components/button/button';
import { Divider } from '/@/shared/components/divider/divider';
import { Group } from '/@/shared/components/group/group';
import { VirtualMultiSelect } from '/@/shared/components/multi-select/virtual-multi-select';
@@ -44,6 +45,7 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
const isGenrePage = customFilters?.genreIds !== undefined;
const {
clear,
query,
setAlbumArtist,
setCompilation,
@@ -297,10 +299,10 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
<Stack px="md" py="md">
{yesNoFilter.map((filter) => (
<YesNoSelect
defaultValue={filter.value ? filter.value.toString() : undefined}
key={`jf-filter-${filter.label}`}
label={filter.label}
onChange={(e) => filter.onChange(e ? e === 'true' : undefined)}
value={filter.value ? filter.value.toString() : undefined}
/>
))}
{!disableArtistFilter && (
@@ -338,35 +340,39 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
<Divider my="md" />
<Group grow>
<NumberInput
defaultValue={query.minYear ?? undefined}
hideControls={false}
label={t('filter.fromYear', { postProcess: 'sentenceCase' })}
max={2300}
min={1700}
onChange={(e) => debouncedHandleMinYearFilter(e)}
required={!!query.minYear}
value={query.minYear ?? undefined}
/>
<NumberInput
defaultValue={query.maxYear ?? undefined}
hideControls={false}
label={t('filter.toYear', { postProcess: 'sentenceCase' })}
max={2300}
min={1700}
onChange={(e) => debouncedHandleMaxYearFilter(e)}
required={!!query.minYear}
value={query.maxYear ?? undefined}
/>
</Group>
{tagsQuery.data?.boolTags && tagsQuery.data.boolTags.length > 0 && (
<MultiSelectWithInvalidData
clearable
data={tagsQuery.data.boolTags}
defaultValue={query._custom?.[tagsQuery.data.boolTags.join('|')] || []}
label={t('common.tags', { postProcess: 'sentenceCase' })}
onChange={handleTagFilter}
searchable
value={query._custom?.[tagsQuery.data.boolTags.join('|')] || []}
width={250}
/>
)}
<Divider my="md" />
<Button fullWidth onClick={clear} variant="subtle">
{t('common.reset', { postProcess: 'sentenceCase' })}
</Button>
</Stack>
);
};
@@ -17,6 +17,7 @@ import { useCurrentServer, useCurrentServerId } from '/@/renderer/store';
import { useAppStore, useAppStoreActions } from '/@/renderer/store/app.store';
import { titleCase } from '/@/renderer/utils';
import { NDSongQueryFieldsLabelMap } from '/@/shared/api/navidrome/navidrome-types';
import { Button } from '/@/shared/components/button/button';
import { Divider } from '/@/shared/components/divider/divider';
import { Group } from '/@/shared/components/group/group';
import { VirtualMultiSelect } from '/@/shared/components/multi-select/virtual-multi-select';
@@ -45,6 +46,7 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
const isGenrePage = customFilters?.genreIds !== undefined;
const {
clear,
query,
setAlbumArtist,
setCompilation,
@@ -285,11 +287,11 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
</Text>
<SegmentedControl
data={segmentedControlData}
defaultValue={booleanToSegmentValue(query.favorite)}
onChange={(value) => {
setFavorite(segmentValueToBoolean(value));
}}
size="sm"
value={booleanToSegmentValue(query.favorite)}
w="100%"
/>
</Stack>
@@ -299,18 +301,18 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
</Text>
<SegmentedControl
data={segmentedControlData}
defaultValue={booleanToSegmentValue(query.compilation)}
onChange={(value) => {
setCompilation(segmentValueToBoolean(value));
}}
size="sm"
value={booleanToSegmentValue(query.compilation)}
w="100%"
/>
</Stack>
{toggleFilters.map((filter) => (
<Group justify="space-between" key={`nd-filter-${filter.label}`}>
<Text>{filter.label}</Text>
<Switch defaultChecked={filter?.value ?? false} onChange={filter.onChange} />
<Switch checked={filter?.value ?? false} onChange={filter.onChange} />
</Group>
))}
{!disableArtistFilter && (
@@ -346,15 +348,19 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
)}
<Divider my="md" />
<NumberInput
defaultValue={query.minYear ?? undefined}
hideControls={false}
label={t('common.year', { postProcess: 'titleCase' })}
max={5000}
min={0}
onChange={(e) => debouncedHandleYearFilter(e)}
value={query.minYear ?? undefined}
/>
<Divider my="md" />
<TagFilters />
<Divider my="md" />
<Button fullWidth onClick={clear} variant="subtle">
{t('common.reset', { postProcess: 'sentenceCase' })}
</Button>
</Stack>
);
};
@@ -377,7 +383,7 @@ const TagFilterItem = ({ label, onChange, options, tagValue, value }: TagFilterI
[options],
);
const defaultValue = useMemo(() => {
const currentValue = useMemo(() => {
if (!value) return [];
return Array.isArray(value) ? value : [value];
}, [value]);
@@ -397,12 +403,12 @@ const TagFilterItem = ({ label, onChange, options, tagValue, value }: TagFilterI
<MultiSelectWithInvalidData
clearable
data={selectData}
defaultValue={defaultValue}
key={tagValue}
label={label}
limit={100}
onChange={handleChange}
searchable
value={currentValue}
/>
);
};
@@ -13,6 +13,7 @@ import {
} from '/@/renderer/features/shared/components/multi-select-rows';
import { useCurrentServerId } from '/@/renderer/store';
import { useAppStore, useAppStoreActions } from '/@/renderer/store/app.store';
import { Button } from '/@/shared/components/button/button';
import { Divider } from '/@/shared/components/divider/divider';
import { Group } from '/@/shared/components/group/group';
import { VirtualMultiSelect } from '/@/shared/components/multi-select/virtual-multi-select';
@@ -37,7 +38,7 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
const isGenrePage = customFilters?.genreIds !== undefined;
const { query, setAlbumArtist, setFavorite, setGenreId, setMaxYear, setMinYear } =
const { clear, query, setAlbumArtist, setFavorite, setGenreId, setMaxYear, setMinYear } =
useAlbumListFilters();
const albumArtistListQuery = useSuspenseQuery(
@@ -215,7 +216,7 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
{toggleFilters.map((filter) => (
<Group justify="space-between" key={`ss-filter-${filter.label}`}>
<Text>{filter.label}</Text>
<Switch defaultChecked={filter.value ?? false} onChange={filter.onChange} />
<Switch checked={filter.value ?? false} onChange={filter.onChange} />
</Group>
))}
{!disableArtistFilter && (
@@ -251,24 +252,28 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
<Divider my="md" />
<Group grow>
<NumberInput
defaultValue={query.minYear ?? undefined}
disabled={Boolean(query.genreIds && query.genreIds.length > 0)}
hideControls={false}
label={t('filter.fromYear', { postProcess: 'sentenceCase' })}
max={5000}
min={0}
onChange={(e) => debouncedHandleMinYearFilter(e)}
value={query.minYear ?? undefined}
/>
<NumberInput
defaultValue={query.maxYear ?? undefined}
disabled={Boolean(query.genreIds && query.genreIds.length > 0)}
hideControls={false}
label={t('filter.toYear', { postProcess: 'sentenceCase' })}
max={5000}
min={0}
onChange={(e) => debouncedHandleMaxYearFilter(e)}
value={query.maxYear ?? undefined}
/>
</Group>
<Divider my="md" />
<Button fullWidth onClick={clear} variant="subtle">
{t('common.reset', { postProcess: 'sentenceCase' })}
</Button>
</Stack>
);
};
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useCallback, useMemo } from 'react';
import { useSearchParams } from 'react-router';
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
@@ -10,6 +10,7 @@ import {
parseBooleanParam,
parseCustomFiltersParam,
parseIntParam,
setMultipleSearchParams,
setSearchParam,
} from '/@/renderer/utils/query-params';
import { AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
@@ -18,12 +19,9 @@ import { ItemListKey } from '/@/shared/types/types';
export const useAlbumListFilters = (listKey?: ItemListKey) => {
const resolvedListKey = listKey ?? ItemListKey.ALBUM;
const { setSortBy, sortBy } = useSortByFilter<AlbumListSort>(
AlbumListSort.NAME,
resolvedListKey,
);
const { sortBy } = useSortByFilter<AlbumListSort>(AlbumListSort.NAME, resolvedListKey);
const { setSortOrder, sortOrder } = useSortOrderFilter(SortOrder.ASC, resolvedListKey);
const { sortOrder } = useSortOrderFilter(SortOrder.ASC, resolvedListKey);
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
@@ -74,12 +72,6 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
[searchParams],
);
// Use a ref to track the latest custom filters to avoid stale state during batched updates
const customRef = useRef<null | Record<string, any> | undefined>(custom);
useEffect(() => {
customRef.current = custom;
}, [custom]);
const setGenreId = useCallback(
(value: null | string[]) => {
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.ALBUM.GENRE_ID, value), {
@@ -184,32 +176,27 @@ export const useAlbumListFilters = (listKey?: ItemListKey) => {
);
const clear = useCallback(() => {
setAlbumArtist(null);
setCompilation(null);
setCustom(null);
setFavorite(null);
setGenreId(null);
setHasRating(null);
setMaxYear(null);
setMinYear(null);
setRecentlyPlayed(null);
setSearchTerm(null);
setSortBy(AlbumListSort.NAME);
setSortOrder(SortOrder.ASC);
}, [
setAlbumArtist,
setCompilation,
setCustom,
setFavorite,
setGenreId,
setHasRating,
setMaxYear,
setMinYear,
setRecentlyPlayed,
setSearchTerm,
setSortBy,
setSortOrder,
]);
setSearchParams(
(prev) =>
setMultipleSearchParams(
prev,
{
[FILTER_KEYS.ALBUM._CUSTOM]: null,
[FILTER_KEYS.ALBUM.ARTIST_IDS]: null,
[FILTER_KEYS.ALBUM.COMPILATION]: null,
[FILTER_KEYS.ALBUM.FAVORITE]: null,
[FILTER_KEYS.ALBUM.GENRE_ID]: null,
[FILTER_KEYS.ALBUM.HAS_RATING]: null,
[FILTER_KEYS.ALBUM.MAX_YEAR]: null,
[FILTER_KEYS.ALBUM.MIN_YEAR]: null,
[FILTER_KEYS.ALBUM.RECENTLY_PLAYED]: null,
[FILTER_KEYS.SHARED.SEARCH_TERM]: null,
},
new Set([FILTER_KEYS.ALBUM._CUSTOM]),
),
{ replace: true },
);
}, [setSearchParams]);
const query = useMemo(
() => ({