mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #4711 from grafixeyehero/Add-filter-mui-components
Add Filter setting components.
This commit is contained in:
commit
f340f7a192
14 changed files with 1395 additions and 8 deletions
457
src/apps/experimental/components/library/filter/FilterButton.tsx
Normal file
457
src/apps/experimental/components/library/filter/FilterButton.tsx
Normal file
|
@ -0,0 +1,457 @@
|
|||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp';
|
||||
import Box from '@mui/material/Box';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import Popover from '@mui/material/Popover';
|
||||
import MuiAccordion, { AccordionProps } from '@mui/material/Accordion';
|
||||
import MuiAccordionDetails from '@mui/material/AccordionDetails';
|
||||
import MuiAccordionSummary, {
|
||||
AccordionSummaryProps
|
||||
} from '@mui/material/AccordionSummary';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { useGetQueryFiltersLegacy, useGetStudios } from 'hooks/useFetchItems';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
import FiltersFeatures from './FiltersFeatures';
|
||||
import FiltersGenres from './FiltersGenres';
|
||||
import FiltersOfficialRatings from './FiltersOfficialRatings';
|
||||
import FiltersEpisodesStatus from './FiltersEpisodesStatus';
|
||||
import FiltersSeriesStatus from './FiltersSeriesStatus';
|
||||
import FiltersStatus from './FiltersStatus';
|
||||
import FiltersStudios from './FiltersStudios';
|
||||
import FiltersTags from './FiltersTags';
|
||||
import FiltersVideoTypes from './FiltersVideoTypes';
|
||||
import FiltersYears from './FiltersYears';
|
||||
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
import { LibraryTab } from 'types/libraryTab';
|
||||
|
||||
const Accordion = styled((props: AccordionProps) => (
|
||||
<MuiAccordion
|
||||
disableGutters
|
||||
elevation={0}
|
||||
TransitionProps={{ unmountOnExit: true }}
|
||||
square
|
||||
{...props}
|
||||
/>
|
||||
))(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0
|
||||
},
|
||||
'&:before': {
|
||||
display: 'none'
|
||||
}
|
||||
}));
|
||||
|
||||
const AccordionSummary = styled((props: AccordionSummaryProps) => (
|
||||
<MuiAccordionSummary
|
||||
expandIcon={<ArrowForwardIosSharpIcon sx={{ fontSize: '0.9rem' }} />}
|
||||
{...props}
|
||||
/>
|
||||
))(({ theme }) => ({
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark' ?
|
||||
'rgba(255, 255, 255, .05)' :
|
||||
'rgba(0, 0, 0, .03)',
|
||||
'& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': {
|
||||
transform: 'rotate(90deg)'
|
||||
},
|
||||
'& .MuiAccordionSummary-content': {
|
||||
marginLeft: theme.spacing(1)
|
||||
}
|
||||
}));
|
||||
|
||||
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
borderTop: '1px solid rgba(0, 0, 0, .125)'
|
||||
}));
|
||||
|
||||
interface FilterButtonProps {
|
||||
parentId: string | null | undefined;
|
||||
itemType: BaseItemKind;
|
||||
viewType: LibraryTab;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<
|
||||
React.SetStateAction<LibraryViewSettings>
|
||||
>;
|
||||
}
|
||||
|
||||
const FilterButton: FC<FilterButtonProps> = ({
|
||||
parentId,
|
||||
itemType,
|
||||
viewType,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const [expanded, setExpanded] = React.useState<string | false>(false);
|
||||
const open = Boolean(anchorEl);
|
||||
const id = open ? 'filter-popover' : undefined;
|
||||
|
||||
const { data } = useGetQueryFiltersLegacy(parentId, itemType);
|
||||
const { data: studios } = useGetStudios(parentId, itemType);
|
||||
|
||||
const handleChange =
|
||||
(panel: string) =>
|
||||
(event: React.SyntheticEvent, newExpanded: boolean) => {
|
||||
setExpanded(newExpanded ? panel : false);
|
||||
};
|
||||
|
||||
const handleClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setAnchorEl(null);
|
||||
}, []);
|
||||
|
||||
const isFiltersLegacyEnabled = () => {
|
||||
return (
|
||||
viewType === LibraryTab.Movies
|
||||
|| viewType === LibraryTab.Series
|
||||
|| viewType === LibraryTab.Albums
|
||||
|| viewType === LibraryTab.AlbumArtists
|
||||
|| viewType === LibraryTab.Artists
|
||||
|| viewType === LibraryTab.Songs
|
||||
|| viewType === LibraryTab.Episodes
|
||||
);
|
||||
};
|
||||
|
||||
const isFiltersStudiosEnabled = () => {
|
||||
return (
|
||||
viewType === LibraryTab.Movies
|
||||
|| viewType === LibraryTab.Series
|
||||
);
|
||||
};
|
||||
|
||||
const isFiltersFeaturesEnabled = () => {
|
||||
return (
|
||||
viewType === LibraryTab.Movies
|
||||
|| viewType === LibraryTab.Series
|
||||
|| viewType === LibraryTab.Episodes
|
||||
);
|
||||
};
|
||||
|
||||
const isFiltersVideoTypesEnabled = () => {
|
||||
return (
|
||||
viewType === LibraryTab.Movies
|
||||
|| viewType === LibraryTab.Episodes
|
||||
);
|
||||
};
|
||||
|
||||
const isFiltersSeriesStatusEnabled = () => {
|
||||
return viewType === LibraryTab.Series;
|
||||
};
|
||||
|
||||
const isFiltersEpisodesStatusEnabled = () => {
|
||||
return viewType === LibraryTab.Episodes;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<IconButton
|
||||
title={globalize.translate('Filter')}
|
||||
sx={{ ml: 2 }}
|
||||
aria-describedby={id}
|
||||
className='paper-icon-button-light btnShuffle autoSize'
|
||||
onClick={handleClick}
|
||||
>
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center'
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
maxHeight: '50%',
|
||||
width: 250
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersStatus'}
|
||||
onChange={handleChange('filtersStatus')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersStatus-content'
|
||||
id='filtersStatus-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Filters')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersStatus
|
||||
viewType={viewType}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={setLibraryViewSettings}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
{isFiltersSeriesStatusEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersSeriesStatus'}
|
||||
onChange={handleChange('filtersSeriesStatus')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersSeriesStatus-content'
|
||||
id='filtersSeriesStatus-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('HeaderSeriesStatus')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersSeriesStatus
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
{isFiltersEpisodesStatusEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersEpisodesStatus'}
|
||||
onChange={handleChange('filtersEpisodesStatus')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersEpisodesStatus-content'
|
||||
id='filtersEpisodesStatus-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('HeaderEpisodesStatus')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersEpisodesStatus
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
{isFiltersFeaturesEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersFeatures'}
|
||||
onChange={handleChange('filtersFeatures')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersFeatures-content'
|
||||
id='filtersFeatures-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Features')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersFeatures
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isFiltersVideoTypesEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersVideoTypes'}
|
||||
onChange={handleChange('filtersVideoTypes')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersVideoTypes-content'
|
||||
id='filtersVideoTypes-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('HeaderVideoType')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersVideoTypes
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isFiltersLegacyEnabled() && (
|
||||
<>
|
||||
{data?.Genres && data?.Genres?.length > 0 && (
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersGenres'}
|
||||
onChange={handleChange('filtersGenres')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersGenres-content'
|
||||
id='filtersGenres-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Genres')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersGenres
|
||||
filters={data}
|
||||
libraryViewSettings={
|
||||
libraryViewSettings
|
||||
}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{data?.OfficialRatings
|
||||
&& data?.OfficialRatings?.length > 0 && (
|
||||
<Accordion
|
||||
expanded={
|
||||
expanded === 'filtersOfficialRatings'
|
||||
}
|
||||
onChange={handleChange(
|
||||
'filtersOfficialRatings'
|
||||
)}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersOfficialRatings-content'
|
||||
id='filtersOfficialRatings-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate(
|
||||
'HeaderParentalRatings'
|
||||
)}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersOfficialRatings
|
||||
filters={data}
|
||||
libraryViewSettings={
|
||||
libraryViewSettings
|
||||
}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{data?.Tags && data?.Tags.length > 0 && (
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersTags'}
|
||||
onChange={handleChange('filtersTags')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersTags-content'
|
||||
id='filtersTags-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Tags')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersTags
|
||||
filters={data}
|
||||
libraryViewSettings={
|
||||
libraryViewSettings
|
||||
}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{data?.Years && data?.Years?.length > 0 && (
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersYears'}
|
||||
onChange={handleChange('filtersYears')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersYears-content'
|
||||
id='filtersYears-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('HeaderYears')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersYears
|
||||
filters={data}
|
||||
libraryViewSettings={
|
||||
libraryViewSettings
|
||||
}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isFiltersStudiosEnabled() && (
|
||||
<>
|
||||
<Accordion
|
||||
expanded={expanded === 'filtersStudios'}
|
||||
onChange={handleChange('filtersStudios')}
|
||||
>
|
||||
<AccordionSummary
|
||||
aria-controls='filtersStudios-content'
|
||||
id='filtersStudios-header'
|
||||
>
|
||||
<Typography>
|
||||
{globalize.translate('Studios')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<FiltersStudios
|
||||
filters={studios}
|
||||
libraryViewSettings={libraryViewSettings}
|
||||
setLibraryViewSettings={
|
||||
setLibraryViewSettings
|
||||
}
|
||||
/>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterButton;
|
|
@ -0,0 +1,75 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
const episodesStatusOptions = [
|
||||
{ label: 'OptionSpecialEpisode', value: 'ParentIndexNumber' },
|
||||
{ label: 'OptionMissingEpisode', value: 'IsMissing' },
|
||||
{ label: 'OptionUnairedEpisode', value: 'IsUnaired' }
|
||||
];
|
||||
|
||||
interface FiltersEpisodesStatusProps {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersEpisodesStatus: FC<FiltersEpisodesStatusProps> = ({
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersEpisodesStatusChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.EpisodesStatus;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, EpisodesStatus: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
EpisodesStatus: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.EpisodesStatus]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{episodesStatusOptions.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.EpisodesStatus?.includes(
|
||||
String( filter.value)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersEpisodesStatusChange}
|
||||
value={String(filter.value)}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate(filter.label)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersEpisodesStatus;
|
|
@ -0,0 +1,81 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
const featuresOptions = [
|
||||
{ label: 'Subtitles', value: 'HasSubtitles' },
|
||||
{ label: 'Trailers', value: 'HasTrailer' },
|
||||
{ label: 'Extras', value: 'HasSpecialFeature' },
|
||||
{ label: 'ThemeSongs', value: 'HasThemeSong' },
|
||||
{ label: 'ThemeVideos', value: 'HasThemeVideo' }
|
||||
];
|
||||
|
||||
interface FiltersFeaturesProps {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<
|
||||
React.SetStateAction<LibraryViewSettings>
|
||||
>;
|
||||
}
|
||||
|
||||
const FiltersFeatures: FC<FiltersFeaturesProps> = ({
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersFeaturesChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue =
|
||||
libraryViewSettings?.Filters?.Features;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Features: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Features: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Features]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{featuresOptions
|
||||
.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Features?.includes(
|
||||
String(filter.value)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersFeaturesChange}
|
||||
value={String(filter.value)}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate(filter.label)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersFeatures;
|
|
@ -0,0 +1,71 @@
|
|||
import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersGenresProps {
|
||||
filters?: QueryFiltersLegacy;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersGenres: FC<FiltersGenresProps> = ({
|
||||
filters,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersGenresChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.Genres;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Genres: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Genres: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Genres]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filters?.Genres?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Genres?.includes(
|
||||
String(filter)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersGenresChange}
|
||||
value={String(filter)}
|
||||
/>
|
||||
}
|
||||
label={filter}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersGenres;
|
|
@ -0,0 +1,71 @@
|
|||
import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersOfficialRatingsProps {
|
||||
filters?: QueryFiltersLegacy;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersOfficialRatings: FC<FiltersOfficialRatingsProps> = ({
|
||||
filters,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersOfficialRatingsChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.OfficialRatings;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, OfficialRatings: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
OfficialRatings: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.OfficialRatings]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filters?.OfficialRatings?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.OfficialRatings?.includes(
|
||||
String(filter)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersOfficialRatingsChange}
|
||||
value={String(filter)}
|
||||
/>
|
||||
}
|
||||
label={filter}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersOfficialRatings;
|
|
@ -0,0 +1,74 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
import { SeriesStatus } from '@jellyfin/sdk/lib/generated-client';
|
||||
|
||||
const statusFiltersOptions = [
|
||||
{ label: 'Continuing', value: SeriesStatus.Continuing },
|
||||
{ label: 'Ended', value: SeriesStatus.Ended },
|
||||
{ label: 'Unreleased', value: SeriesStatus.Unreleased }
|
||||
];
|
||||
|
||||
interface FiltersSeriesStatusProps {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersSeriesStatus: FC<FiltersSeriesStatusProps> = ({
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersSeriesStatusChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = event.target.value as SeriesStatus;
|
||||
const existingValue = libraryViewSettings?.Filters?.SeriesStatus;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: SeriesStatus) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, SeriesStatus: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
SeriesStatus: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.SeriesStatus]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{statusFiltersOptions.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.SeriesStatus?.includes( filter.value)
|
||||
}
|
||||
onChange={onFiltersSeriesStatusChange}
|
||||
value={filter.value}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate(filter.label)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersSeriesStatus;
|
|
@ -0,0 +1,97 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
import { ItemFilter } from '@jellyfin/sdk/lib/generated-client';
|
||||
import { LibraryTab } from 'types/libraryTab';
|
||||
|
||||
const statusFiltersOptions = [
|
||||
{ label: 'Played', value: ItemFilter.IsPlayed },
|
||||
{ label: 'Unplayed', value: ItemFilter.IsUnplayed },
|
||||
{ label: 'Favorite', value: ItemFilter.IsFavorite },
|
||||
{ label: 'ContinueWatching', value: ItemFilter.IsResumable }
|
||||
];
|
||||
|
||||
interface FiltersStatusProps {
|
||||
viewType: LibraryTab;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersStatus: FC<FiltersStatusProps> = ({
|
||||
viewType,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersStatusChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = event.target.value as ItemFilter;
|
||||
const existingValue = libraryViewSettings?.Filters?.Status;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: ItemFilter) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Status: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Status: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Status]
|
||||
);
|
||||
|
||||
const getVisibleFiltersStatus = () => {
|
||||
const visibleFiltersStatus: ItemFilter[] = [ItemFilter.IsFavorite];
|
||||
|
||||
if (
|
||||
viewType !== LibraryTab.Albums
|
||||
&& viewType !== LibraryTab.Artists
|
||||
&& viewType !== LibraryTab.AlbumArtists
|
||||
&& viewType !== LibraryTab.Songs
|
||||
) {
|
||||
visibleFiltersStatus.push(ItemFilter.IsUnplayed);
|
||||
visibleFiltersStatus.push(ItemFilter.IsPlayed);
|
||||
visibleFiltersStatus.push(ItemFilter.IsResumable);
|
||||
}
|
||||
|
||||
return visibleFiltersStatus;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{statusFiltersOptions
|
||||
.filter((filter) => getVisibleFiltersStatus().includes(filter.value))
|
||||
.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Status?.includes(filter.value)
|
||||
}
|
||||
onChange={onFiltersStatusChange}
|
||||
value={filter.value}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate(filter.label)}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersStatus;
|
|
@ -0,0 +1,71 @@
|
|||
import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersStudiosProps {
|
||||
filters?: BaseItemDtoQueryResult;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersStudios: FC<FiltersStudiosProps> = ({
|
||||
filters,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersStudiosChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.StudioIds;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, StudioIds: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
StudioIds: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.StudioIds]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filters?.Items?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.Id}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.StudioIds?.includes(
|
||||
String(filter.Id)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersStudiosChange}
|
||||
value={String(filter.Id)}
|
||||
/>
|
||||
}
|
||||
label={filter.Name}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersStudios;
|
|
@ -0,0 +1,71 @@
|
|||
import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersTagsProps {
|
||||
filters?: QueryFiltersLegacy;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersTags: FC<FiltersTagsProps> = ({
|
||||
filters,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersTagsChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = String(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.Tags;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: string) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Tags: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Tags: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Tags]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filters?.Tags?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Tags?.includes(
|
||||
String(filter)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersTagsChange}
|
||||
value={String(filter)}
|
||||
/>
|
||||
}
|
||||
label={filter}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersTags;
|
|
@ -0,0 +1,131 @@
|
|||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
import { VideoType } from '@jellyfin/sdk/lib/generated-client';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
const videoTypesOptions = [
|
||||
{ label: 'DVD', value: VideoType.Dvd },
|
||||
{ label: 'Blu-ray', value: VideoType.BluRay },
|
||||
{ label: 'ISO', value: VideoType.Iso }
|
||||
];
|
||||
|
||||
interface FiltersVideoTypesProps {
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersVideoTypes: FC<FiltersVideoTypesProps> = ({
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersVideoTypesChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = event.target.value as VideoType;
|
||||
const existingValue = libraryViewSettings?.Filters?.VideoTypes;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: VideoType) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, VideoTypes: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
VideoTypes: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.VideoTypes]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const name = event.target.name;
|
||||
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
[name]: event.target.checked
|
||||
}));
|
||||
},
|
||||
[setLibraryViewSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.IsSD}
|
||||
onChange={handleChange}
|
||||
name='IsSD'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('SD')}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={libraryViewSettings.IsHD}
|
||||
onChange={handleChange}
|
||||
name='IsHD'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('HD')}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
libraryViewSettings.Is4K
|
||||
}
|
||||
onChange={handleChange}
|
||||
name='Is4K'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('4K')}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
libraryViewSettings.Is3D
|
||||
}
|
||||
onChange={handleChange}
|
||||
name='Is3D'
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('3D')}
|
||||
/>
|
||||
{videoTypesOptions
|
||||
.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter.value}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.VideoTypes?.includes(filter.value)
|
||||
}
|
||||
onChange={onFiltersVideoTypesChange}
|
||||
value={filter.value}
|
||||
/>
|
||||
}
|
||||
label={filter.label}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersVideoTypes;
|
|
@ -0,0 +1,71 @@
|
|||
import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { LibraryViewSettings } from 'types/library';
|
||||
|
||||
interface FiltersYearsProps {
|
||||
filters?: QueryFiltersLegacy;
|
||||
libraryViewSettings: LibraryViewSettings;
|
||||
setLibraryViewSettings: React.Dispatch<React.SetStateAction<LibraryViewSettings>>;
|
||||
}
|
||||
|
||||
const FiltersYears: FC<FiltersYearsProps> = ({
|
||||
filters,
|
||||
libraryViewSettings,
|
||||
setLibraryViewSettings
|
||||
}) => {
|
||||
const onFiltersYearsChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
const value = Number(event.target.value);
|
||||
const existingValue = libraryViewSettings?.Filters?.Years;
|
||||
|
||||
if (existingValue?.includes(value)) {
|
||||
const newValue = existingValue?.filter(
|
||||
(prevState: number) => prevState !== value
|
||||
);
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: { ...prevState.Filters, Years: newValue }
|
||||
}));
|
||||
} else {
|
||||
setLibraryViewSettings((prevState) => ({
|
||||
...prevState,
|
||||
StartIndex: 0,
|
||||
Filters: {
|
||||
...prevState.Filters,
|
||||
Years: [...(existingValue ?? []), value]
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setLibraryViewSettings, libraryViewSettings?.Filters?.Years]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
{filters?.Years?.map((filter) => (
|
||||
<FormControlLabel
|
||||
key={filter}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={
|
||||
!!libraryViewSettings?.Filters?.Years?.includes(
|
||||
Number(filter)
|
||||
)
|
||||
}
|
||||
onChange={onFiltersYearsChange}
|
||||
value={String(filter)}
|
||||
/>
|
||||
}
|
||||
label={filter}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default FiltersYears;
|
Loading…
Add table
Add a link
Reference in a new issue