mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-11 06:42:38 +02:00
fix list filters
This commit is contained in:
@@ -3,17 +3,19 @@ import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
import { useListContext } from '/@/renderer/context/list-context';
|
||||
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
|
||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||
import { AlbumListFilter, useCurrentServerId } from '/@/renderer/store';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { Divider } from '/@/shared/components/divider/divider';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||
import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
|
||||
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
||||
import {
|
||||
AlbumArtistListSort,
|
||||
GenreListSort,
|
||||
@@ -22,15 +24,17 @@ import {
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
interface JellyfinAlbumFiltersProps {
|
||||
customFilters?: Partial<AlbumListFilter>;
|
||||
disableArtistFilter?: boolean;
|
||||
onFilterChange: (filters: AlbumListFilter) => void;
|
||||
}
|
||||
|
||||
export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const { customFilters } = useListContext();
|
||||
|
||||
const isGenrePage = customFilters?.genreIds !== undefined;
|
||||
|
||||
const {
|
||||
query,
|
||||
setAlbumArtist,
|
||||
@@ -180,46 +184,25 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
|
||||
|
||||
const handleTagFilter = useMemo(
|
||||
() => (e: string[] | undefined) => {
|
||||
setCustom((prev) => {
|
||||
if (!prev) {
|
||||
return e && e.length > 0 ? { [e.join('|')]: e.join('|') } : null;
|
||||
}
|
||||
|
||||
if (!e || e.length === 0) {
|
||||
// Remove all tag-related properties (they use '|' joined keys)
|
||||
const rest = Object.fromEntries(
|
||||
Object.entries(prev).filter(([key]) => !key.includes('|')),
|
||||
);
|
||||
|
||||
return Object.keys(rest).length === 0 ? null : rest;
|
||||
}
|
||||
|
||||
// Remove old tag entries and add new one
|
||||
const rest = Object.fromEntries(
|
||||
Object.entries(prev).filter(([key]) => !key.includes('|')),
|
||||
);
|
||||
const tagKey = e.join('|');
|
||||
|
||||
return {
|
||||
...rest,
|
||||
[tagKey]: tagKey,
|
||||
};
|
||||
});
|
||||
setCustom({ Tags: e?.join('|') ?? null });
|
||||
},
|
||||
[setCustom],
|
||||
);
|
||||
|
||||
const debouncedHandleMinYearFilter = useDebouncedCallback(handleMinYearFilter, 300);
|
||||
const debouncedHandleMaxYearFilter = useDebouncedCallback(handleMaxYearFilter, 300);
|
||||
|
||||
return (
|
||||
<Stack p="0.8rem">
|
||||
<Stack p="md">
|
||||
{yesNoFilter.map((filter) => (
|
||||
<YesNoSelect
|
||||
defaultValue={filter.value ? filter.value.toString() : undefined}
|
||||
key={`jf-filter-${filter.label}`}
|
||||
label={filter.label}
|
||||
onChange={filter.onChange}
|
||||
value={filter.value ?? undefined}
|
||||
onChange={(e) => filter.onChange(e ? e === 'true' : undefined)}
|
||||
/>
|
||||
))}
|
||||
<Divider my="0.5rem" />
|
||||
<Divider my="md" />
|
||||
<Group grow>
|
||||
<NumberInput
|
||||
defaultValue={query.minYear ?? undefined}
|
||||
@@ -227,7 +210,7 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
|
||||
label={t('filter.fromYear', { postProcess: 'sentenceCase' })}
|
||||
max={2300}
|
||||
min={1700}
|
||||
onBlur={(e) => handleMinYearFilter(e.currentTarget.value)}
|
||||
onChange={(e) => debouncedHandleMinYearFilter(e)}
|
||||
required={!!query.minYear}
|
||||
/>
|
||||
<NumberInput
|
||||
@@ -236,23 +219,24 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
|
||||
label={t('filter.toYear', { postProcess: 'sentenceCase' })}
|
||||
max={2300}
|
||||
min={1700}
|
||||
onBlur={(e) => handleMaxYearFilter(e.currentTarget.value)}
|
||||
onChange={(e) => debouncedHandleMaxYearFilter(e)}
|
||||
required={!!query.minYear}
|
||||
/>
|
||||
</Group>
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
data={genreList}
|
||||
defaultValue={query.genreIds ?? undefined}
|
||||
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||
onChange={(e) => handleGenresFilter(e)}
|
||||
searchable
|
||||
/>
|
||||
|
||||
{!isGenrePage && (
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
data={genreList}
|
||||
defaultValue={query.genreIds || []}
|
||||
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
data={selectableAlbumArtists}
|
||||
defaultValue={query.artistIds ?? undefined}
|
||||
defaultValue={query.artistIds || []}
|
||||
disabled={disableArtistFilter}
|
||||
label={t('entity.artist', { count: 2, postProcess: 'sentenceCase' })}
|
||||
limit={300}
|
||||
@@ -264,7 +248,7 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
data={tagsQuery.data.boolTags}
|
||||
defaultValue={query._custom?.[tagsQuery.data.boolTags.join('|')] ?? undefined}
|
||||
defaultValue={query._custom?.[tagsQuery.data.boolTags.join('|')] || []}
|
||||
label={t('common.tags', { postProcess: 'sentenceCase' })}
|
||||
onChange={handleTagFilter}
|
||||
searchable
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ChangeEvent, memo, useMemo } from 'react';
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
MultiSelectWithInvalidData,
|
||||
SelectWithInvalidData,
|
||||
} from '/@/renderer/components/select-with-invalid-data';
|
||||
import { useListContext } from '/@/renderer/context/list-context';
|
||||
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
|
||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||
import { useGenreList } from '/@/renderer/features/genres/api/genres-api';
|
||||
@@ -15,11 +16,12 @@ import { titleCase } from '/@/renderer/utils';
|
||||
import { Divider } from '/@/shared/components/divider/divider';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||
import { Spinner, SpinnerIcon } from '/@/shared/components/spinner/spinner';
|
||||
import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
|
||||
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
||||
import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types';
|
||||
|
||||
interface NavidromeAlbumFiltersProps {
|
||||
@@ -31,6 +33,10 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
|
||||
const server = useCurrentServer();
|
||||
const serverId = server.id;
|
||||
|
||||
const { customFilters } = useListContext();
|
||||
|
||||
const isGenrePage = customFilters?.genreIds !== undefined;
|
||||
|
||||
const {
|
||||
query,
|
||||
setAlbumArtist,
|
||||
@@ -89,10 +95,10 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
|
||||
const recentlyPlayed = e.currentTarget.checked ? true : undefined;
|
||||
setRecentlyPlayed(recentlyPlayed ?? null);
|
||||
},
|
||||
value: query.recentlyPlayed,
|
||||
value: query.isRecentlyPlayed,
|
||||
},
|
||||
],
|
||||
[t, query.hasRating, query.recentlyPlayed, setHasRating, setRecentlyPlayed],
|
||||
[t, query.hasRating, query.isRecentlyPlayed, setHasRating, setRecentlyPlayed],
|
||||
);
|
||||
|
||||
const handleYearFilter = useMemo(
|
||||
@@ -142,43 +148,48 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
|
||||
}));
|
||||
}, [albumArtistListQuery.data?.items]);
|
||||
|
||||
const debouncedHandleYearFilter = useDebouncedCallback(handleYearFilter, 300);
|
||||
|
||||
return (
|
||||
<Stack p="0.8rem">
|
||||
<Stack p="md">
|
||||
{yesNoUndefinedFilters.map((filter) => (
|
||||
<YesNoSelect
|
||||
clearable
|
||||
defaultValue={filter.value ? filter.value.toString() : undefined}
|
||||
key={`nd-filter-${filter.label}`}
|
||||
label={filter.label}
|
||||
onChange={filter.onChange}
|
||||
value={filter.value ?? undefined}
|
||||
onChange={(e) => filter.onChange(e ? e === 'true' : undefined)}
|
||||
/>
|
||||
))}
|
||||
{toggleFilters.map((filter) => (
|
||||
<Group justify="space-between" key={`nd-filter-${filter.label}`}>
|
||||
<Text>{filter.label}</Text>
|
||||
<Switch checked={filter?.value ?? false} onChange={filter.onChange} />
|
||||
<Switch defaultChecked={filter?.value ?? false} onChange={filter.onChange} />
|
||||
</Group>
|
||||
))}
|
||||
<Divider my="0.5rem" />
|
||||
<Divider my="md" />
|
||||
<NumberInput
|
||||
defaultValue={query.minYear ?? undefined}
|
||||
hideControls={false}
|
||||
label={t('common.year', { postProcess: 'titleCase' })}
|
||||
max={5000}
|
||||
min={0}
|
||||
onBlur={(e) => handleYearFilter(e.currentTarget.value)}
|
||||
/>
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
data={genreList}
|
||||
defaultValue={query.genreIds}
|
||||
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||
onChange={(e) => (e && e.length > 0 ? setGenreId(e) : setGenreId(null))}
|
||||
searchable
|
||||
onChange={(e) => debouncedHandleYearFilter(e)}
|
||||
/>
|
||||
{!isGenrePage && (
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
data={genreList}
|
||||
defaultValue={query.genreIds || []}
|
||||
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||
onChange={(e) => (e && e.length > 0 ? setGenreId(e) : setGenreId(null))}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
<SelectWithInvalidData
|
||||
clearable
|
||||
data={selectableAlbumArtists}
|
||||
defaultValue={query.artistIds ? query.artistIds[0] : undefined}
|
||||
defaultValue={query.artistIds?.[0] || undefined}
|
||||
disabled={disableArtistFilter}
|
||||
label={t('entity.artist', { count: 1, postProcess: 'titleCase' })}
|
||||
limit={300}
|
||||
@@ -186,6 +197,7 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
|
||||
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||
searchable
|
||||
/>
|
||||
<Divider my="md" />
|
||||
<TagFilters />
|
||||
</Stack>
|
||||
);
|
||||
@@ -194,38 +206,34 @@ export const NavidromeAlbumFilters = ({ disableArtistFilter }: NavidromeAlbumFil
|
||||
interface TagFilterItemProps {
|
||||
label: string;
|
||||
onChange: (value: null | string) => void;
|
||||
options: string[];
|
||||
options: Array<{ id: string; name: string }>;
|
||||
tagValue: string;
|
||||
value: string | undefined;
|
||||
}
|
||||
|
||||
const TagFilterItem = memo(
|
||||
({ label, onChange, options, tagValue, value }: TagFilterItemProps) => {
|
||||
return (
|
||||
<SelectWithInvalidData
|
||||
clearable
|
||||
data={options}
|
||||
defaultValue={value}
|
||||
key={tagValue}
|
||||
label={label}
|
||||
limit={100}
|
||||
onChange={onChange}
|
||||
searchable
|
||||
/>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
// Only re-render if the specific tag's value or options change
|
||||
// We don't compare onChange since it's a stable wrapper around handleTagFilter
|
||||
// and handleTagFilter itself is memoized and stable
|
||||
return (
|
||||
prevProps.tagValue === nextProps.tagValue &&
|
||||
prevProps.label === nextProps.label &&
|
||||
prevProps.value === nextProps.value &&
|
||||
prevProps.options === nextProps.options
|
||||
);
|
||||
},
|
||||
);
|
||||
const TagFilterItem = ({ label, onChange, options, tagValue, value }: TagFilterItemProps) => {
|
||||
const selectData = useMemo(
|
||||
() =>
|
||||
options.map((option) => ({
|
||||
label: option.name,
|
||||
value: option.id,
|
||||
})),
|
||||
[options],
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectWithInvalidData
|
||||
clearable
|
||||
data={selectData}
|
||||
defaultValue={value}
|
||||
key={tagValue}
|
||||
label={label}
|
||||
limit={100}
|
||||
onChange={onChange}
|
||||
searchable
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
TagFilterItem.displayName = 'TagFilterItem';
|
||||
|
||||
@@ -234,7 +242,7 @@ const TagFilters = () => {
|
||||
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const tagsQuery = useQuery(
|
||||
const tagsQuery = useSuspenseQuery(
|
||||
sharedQueries.tags({
|
||||
options: {
|
||||
gcTime: 1000 * 60 * 60,
|
||||
@@ -249,50 +257,27 @@ const TagFilters = () => {
|
||||
|
||||
const handleTagFilter = useMemo(
|
||||
() => (tag: string, e: null | string) => {
|
||||
setCustom((prev) => {
|
||||
if (!prev) {
|
||||
return e ? { [tag]: e } : null;
|
||||
}
|
||||
|
||||
if (e === null) {
|
||||
const rest = Object.fromEntries(
|
||||
Object.entries(prev).filter(([key]) => key !== tag),
|
||||
);
|
||||
|
||||
return Object.keys(rest).length === 0 ? null : rest;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[tag]: e,
|
||||
};
|
||||
});
|
||||
setCustom({ [tag]: e });
|
||||
},
|
||||
[setCustom],
|
||||
);
|
||||
|
||||
const tags = useMemo(() => {
|
||||
return (
|
||||
tagsQuery.data?.enumTags?.map((tag) => ({
|
||||
label: titleCase(tag.name),
|
||||
options: tag.options,
|
||||
value: tag.name,
|
||||
})) || []
|
||||
);
|
||||
}, [tagsQuery.data?.enumTags]);
|
||||
const results: { label: string; options: { id: string; name: string }[]; value: string }[] =
|
||||
[];
|
||||
|
||||
// Create stable onChange handlers for each tag using useMemo
|
||||
const tagHandlers = useMemo(() => {
|
||||
const handlers = new Map<string, (value: null | string) => void>();
|
||||
tags.forEach((tag) => {
|
||||
handlers.set(tag.value, (value: null | string) => handleTagFilter(tag.value, value));
|
||||
});
|
||||
return handlers;
|
||||
}, [tags, handleTagFilter]);
|
||||
for (const tag of tagsQuery.data?.enumTags || []) {
|
||||
if (!tagsQuery.data?.excluded.album.includes(tag.name)) {
|
||||
results.push({
|
||||
label: titleCase(tag.name),
|
||||
options: tag.options,
|
||||
value: tag.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (tagsQuery.isLoading) {
|
||||
return <Spinner container />;
|
||||
}
|
||||
return results;
|
||||
}, [tagsQuery.data]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -300,7 +285,7 @@ const TagFilters = () => {
|
||||
<TagFilterItem
|
||||
key={tag.value}
|
||||
label={tag.label}
|
||||
onChange={tagHandlers.get(tag.value)!}
|
||||
onChange={(e) => handleTagFilter(tag.value, e)}
|
||||
options={tag.options}
|
||||
tagValue={tag.value}
|
||||
value={query._custom?.[tag.value] as string | undefined}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
import { useListContext } from '/@/renderer/context/list-context';
|
||||
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
|
||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||
import { useGenreList } from '/@/renderer/features/genres/api/genres-api';
|
||||
import { useCurrentServerId } from '/@/renderer/store';
|
||||
import { Divider } from '/@/shared/components/divider/divider';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
@@ -15,7 +16,8 @@ import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { AlbumArtistListSort, GenreListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
||||
import { AlbumArtistListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
|
||||
interface SubsonicAlbumFiltersProps {
|
||||
disableArtistFilter?: boolean;
|
||||
@@ -26,12 +28,16 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
|
||||
|
||||
const serverId = useCurrentServerId();
|
||||
|
||||
const { customFilters } = useListContext();
|
||||
|
||||
const isGenrePage = customFilters?.genreIds !== undefined;
|
||||
|
||||
const { query, setAlbumArtist, setFavorite, setGenreId, setMaxYear, setMinYear } =
|
||||
useAlbumListFilters();
|
||||
|
||||
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
||||
|
||||
const albumArtistListQuery = useQuery(
|
||||
const albumArtistListQuery = useSuspenseQuery(
|
||||
artistsQueries.albumArtistList({
|
||||
options: {
|
||||
gcTime: 1000 * 60 * 2,
|
||||
@@ -64,20 +70,7 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
|
||||
[setAlbumArtist],
|
||||
);
|
||||
|
||||
const genreListQuery = useQuery(
|
||||
genresQueries.list({
|
||||
options: {
|
||||
gcTime: 1000 * 60 * 2,
|
||||
staleTime: 1000 * 60 * 1,
|
||||
},
|
||||
query: {
|
||||
sortBy: GenreListSort.NAME,
|
||||
sortOrder: SortOrder.ASC,
|
||||
startIndex: 0,
|
||||
},
|
||||
serverId,
|
||||
}),
|
||||
);
|
||||
const genreListQuery = useGenreList();
|
||||
|
||||
const genreList = useMemo(() => {
|
||||
if (!genreListQuery?.data) return [];
|
||||
@@ -146,15 +139,18 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
|
||||
[setMaxYear],
|
||||
);
|
||||
|
||||
const debouncedHandleMinYearFilter = useDebouncedCallback(handleMinYearFilter, 300);
|
||||
const debouncedHandleMaxYearFilter = useDebouncedCallback(handleMaxYearFilter, 300);
|
||||
|
||||
return (
|
||||
<Stack p="0.8rem">
|
||||
<Stack p="md">
|
||||
{toggleFilters.map((filter) => (
|
||||
<Group justify="space-between" key={`nd-filter-${filter.label}`}>
|
||||
<Group justify="space-between" key={`ss-filter-${filter.label}`}>
|
||||
<Text>{filter.label}</Text>
|
||||
<Switch checked={filter?.value || false} onChange={filter.onChange} />
|
||||
<Switch defaultChecked={filter.value ?? false} onChange={filter.onChange} />
|
||||
</Group>
|
||||
))}
|
||||
<Divider my="0.5rem" />
|
||||
<Divider my="md" />
|
||||
<Group grow>
|
||||
<NumberInput
|
||||
defaultValue={query.minYear ?? undefined}
|
||||
@@ -163,7 +159,7 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
|
||||
label={t('filter.fromYear', { postProcess: 'sentenceCase' })}
|
||||
max={5000}
|
||||
min={0}
|
||||
onBlur={(e) => handleMinYearFilter(e.currentTarget.value)}
|
||||
onChange={(e) => debouncedHandleMinYearFilter(e)}
|
||||
/>
|
||||
<NumberInput
|
||||
defaultValue={query.maxYear ?? undefined}
|
||||
@@ -172,22 +168,24 @@ export const SubsonicAlbumFilters = ({ disableArtistFilter }: SubsonicAlbumFilte
|
||||
label={t('filter.toYear', { postProcess: 'sentenceCase' })}
|
||||
max={5000}
|
||||
min={0}
|
||||
onBlur={(e) => handleMaxYearFilter(e.currentTarget.value)}
|
||||
onChange={(e) => debouncedHandleMaxYearFilter(e)}
|
||||
/>
|
||||
</Group>
|
||||
<Select
|
||||
clearable
|
||||
data={genreList}
|
||||
defaultValue={query.genreIds?.[0] ?? undefined}
|
||||
disabled={Boolean(query.minYear || query.maxYear)}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||
onChange={(e) => handleGenresFilter(e)}
|
||||
searchable
|
||||
/>
|
||||
{!isGenrePage && (
|
||||
<Select
|
||||
clearable
|
||||
data={genreList}
|
||||
defaultValue={query.genreIds?.[0] ?? undefined}
|
||||
disabled={Boolean(query.minYear || query.maxYear)}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||
onChange={(e) => handleGenresFilter(e)}
|
||||
searchable
|
||||
/>
|
||||
)}
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
data={selectableAlbumArtists}
|
||||
defaultValue={query.artistIds ?? undefined}
|
||||
defaultValue={query.artistIds ?? []}
|
||||
disabled={disableArtistFilter}
|
||||
label={t('entity.artist', { count: 2, postProcess: 'sentenceCase' })}
|
||||
limit={300}
|
||||
|
||||
Reference in New Issue
Block a user