You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
8.9 KiB
272 lines
8.9 KiB
import Button from '@app/components/Common/Button';
|
|
import MultiRangeSlider from '@app/components/Common/MultiRangeSlider';
|
|
import SlideOver from '@app/components/Common/SlideOver';
|
|
import type { FilterOptions } from '@app/components/Discover/constants';
|
|
import { countActiveFilters } from '@app/components/Discover/constants';
|
|
import LanguageSelector from '@app/components/LanguageSelector';
|
|
import {
|
|
CompanySelector,
|
|
GenreSelector,
|
|
KeywordSelector,
|
|
} from '@app/components/Selector';
|
|
import useSettings from '@app/hooks/useSettings';
|
|
import {
|
|
useBatchUpdateQueryParams,
|
|
useUpdateQueryParams,
|
|
} from '@app/hooks/useUpdateQueryParams';
|
|
import { XCircleIcon } from '@heroicons/react/24/outline';
|
|
import { defineMessages, useIntl } from 'react-intl';
|
|
import Datepicker from 'react-tailwindcss-datepicker-sct';
|
|
|
|
const messages = defineMessages({
|
|
filters: 'Filters',
|
|
activefilters:
|
|
'{count, plural, one {# Active Filter} other {# Active Filters}}',
|
|
releaseDate: 'Release Date',
|
|
firstAirDate: 'First Air Date',
|
|
from: 'From',
|
|
to: 'To',
|
|
studio: 'Studio',
|
|
genres: 'Genres',
|
|
keywords: 'Keywords',
|
|
originalLanguage: 'Original Language',
|
|
runtimeText: '{minValue}-{maxValue} minute runtime',
|
|
ratingText: 'Ratings between {minValue} and {maxValue}',
|
|
clearfilters: 'Clear Active Filters',
|
|
tmdbuserscore: 'TMDB User Score',
|
|
runtime: 'Runtime',
|
|
});
|
|
|
|
type FilterSlideoverProps = {
|
|
show: boolean;
|
|
onClose: () => void;
|
|
type: 'movie' | 'tv';
|
|
currentFilters: FilterOptions;
|
|
};
|
|
|
|
const FilterSlideover = ({
|
|
show,
|
|
onClose,
|
|
type,
|
|
currentFilters,
|
|
}: FilterSlideoverProps) => {
|
|
const intl = useIntl();
|
|
const { currentSettings } = useSettings();
|
|
const updateQueryParams = useUpdateQueryParams({});
|
|
const batchUpdateQueryParams = useBatchUpdateQueryParams({});
|
|
|
|
const dateGte =
|
|
type === 'movie' ? 'primaryReleaseDateGte' : 'firstAirDateGte';
|
|
const dateLte =
|
|
type === 'movie' ? 'primaryReleaseDateLte' : 'firstAirDateLte';
|
|
|
|
return (
|
|
<SlideOver
|
|
show={show}
|
|
title={intl.formatMessage(messages.filters)}
|
|
subText={intl.formatMessage(messages.activefilters, {
|
|
count: countActiveFilters(currentFilters),
|
|
})}
|
|
onClose={() => onClose()}
|
|
>
|
|
<div className="flex flex-col space-y-4">
|
|
<div>
|
|
<div className="mb-2 text-lg font-semibold">
|
|
{intl.formatMessage(
|
|
type === 'movie' ? messages.releaseDate : messages.firstAirDate
|
|
)}
|
|
</div>
|
|
<div className="relative z-40 flex space-x-2">
|
|
<div className="flex flex-col">
|
|
<div className="mb-2">{intl.formatMessage(messages.from)}</div>
|
|
<Datepicker
|
|
primaryColor="indigo"
|
|
value={{
|
|
startDate: currentFilters[dateGte] ?? null,
|
|
endDate: currentFilters[dateGte] ?? null,
|
|
}}
|
|
onChange={(value) => {
|
|
updateQueryParams(
|
|
dateGte,
|
|
value?.startDate ? (value.startDate as string) : undefined
|
|
);
|
|
}}
|
|
inputName="fromdate"
|
|
useRange={false}
|
|
asSingle
|
|
containerClassName="datepicker-wrapper"
|
|
inputClassName="pr-1 sm:pr-4"
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<div className="mb-2">{intl.formatMessage(messages.to)}</div>
|
|
<Datepicker
|
|
primaryColor="indigo"
|
|
value={{
|
|
startDate: currentFilters[dateLte] ?? null,
|
|
endDate: currentFilters[dateLte] ?? null,
|
|
}}
|
|
onChange={(value) => {
|
|
updateQueryParams(
|
|
dateLte,
|
|
value?.startDate ? (value.startDate as string) : undefined
|
|
);
|
|
}}
|
|
inputName="todate"
|
|
useRange={false}
|
|
asSingle
|
|
containerClassName="datepicker-wrapper"
|
|
inputClassName="pr-1 sm:pr-4"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{type === 'movie' && (
|
|
<>
|
|
<span className="text-lg font-semibold">
|
|
{intl.formatMessage(messages.studio)}
|
|
</span>
|
|
<CompanySelector
|
|
defaultValue={currentFilters.studio}
|
|
onChange={(value) => {
|
|
updateQueryParams('studio', value?.value.toString());
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
<span className="text-lg font-semibold">
|
|
{intl.formatMessage(messages.genres)}
|
|
</span>
|
|
<GenreSelector
|
|
type={type}
|
|
defaultValue={currentFilters.genre}
|
|
isMulti
|
|
onChange={(value) => {
|
|
updateQueryParams('genre', value?.map((v) => v.value).join(','));
|
|
}}
|
|
/>
|
|
<span className="text-lg font-semibold">
|
|
{intl.formatMessage(messages.keywords)}
|
|
</span>
|
|
<KeywordSelector
|
|
defaultValue={currentFilters.keywords}
|
|
isMulti
|
|
onChange={(value) => {
|
|
updateQueryParams('keywords', value?.map((v) => v.value).join(','));
|
|
}}
|
|
/>
|
|
<span className="text-lg font-semibold">
|
|
{intl.formatMessage(messages.originalLanguage)}
|
|
</span>
|
|
<LanguageSelector
|
|
value={currentFilters.language}
|
|
serverValue={currentSettings.originalLanguage}
|
|
isUserSettings
|
|
setFieldValue={(_key, value) => {
|
|
updateQueryParams('language', value);
|
|
}}
|
|
/>
|
|
<span className="text-lg font-semibold">
|
|
{intl.formatMessage(messages.runtime)}
|
|
</span>
|
|
<div className="relative z-0">
|
|
<MultiRangeSlider
|
|
min={0}
|
|
max={400}
|
|
onUpdateMin={(min) => {
|
|
updateQueryParams(
|
|
'withRuntimeGte',
|
|
min !== 0 && Number(currentFilters.withRuntimeLte) !== 400
|
|
? min.toString()
|
|
: undefined
|
|
);
|
|
}}
|
|
onUpdateMax={(max) => {
|
|
updateQueryParams(
|
|
'withRuntimeLte',
|
|
max !== 400 && Number(currentFilters.withRuntimeGte) !== 0
|
|
? max.toString()
|
|
: undefined
|
|
);
|
|
}}
|
|
defaultMaxValue={
|
|
currentFilters.withRuntimeLte
|
|
? Number(currentFilters.withRuntimeLte)
|
|
: undefined
|
|
}
|
|
defaultMinValue={
|
|
currentFilters.withRuntimeGte
|
|
? Number(currentFilters.withRuntimeGte)
|
|
: undefined
|
|
}
|
|
subText={intl.formatMessage(messages.runtimeText, {
|
|
minValue: currentFilters.withRuntimeGte ?? 0,
|
|
maxValue: currentFilters.withRuntimeLte ?? 400,
|
|
})}
|
|
/>
|
|
</div>
|
|
<span className="text-lg font-semibold">
|
|
{intl.formatMessage(messages.tmdbuserscore)}
|
|
</span>
|
|
<div className="relative z-0">
|
|
<MultiRangeSlider
|
|
min={1}
|
|
max={10}
|
|
defaultMaxValue={
|
|
currentFilters.voteAverageLte
|
|
? Number(currentFilters.voteAverageLte)
|
|
: undefined
|
|
}
|
|
defaultMinValue={
|
|
currentFilters.voteAverageGte
|
|
? Number(currentFilters.voteAverageGte)
|
|
: undefined
|
|
}
|
|
onUpdateMin={(min) => {
|
|
updateQueryParams(
|
|
'voteAverageGte',
|
|
min !== 1 && Number(currentFilters.voteAverageLte) !== 10
|
|
? min.toString()
|
|
: undefined
|
|
);
|
|
}}
|
|
onUpdateMax={(max) => {
|
|
updateQueryParams(
|
|
'voteAverageLte',
|
|
max !== 10 && Number(currentFilters.voteAverageGte) !== 1
|
|
? max.toString()
|
|
: undefined
|
|
);
|
|
}}
|
|
subText={intl.formatMessage(messages.ratingText, {
|
|
minValue: currentFilters.voteAverageGte ?? 1,
|
|
maxValue: currentFilters.voteAverageLte ?? 10,
|
|
})}
|
|
/>
|
|
</div>
|
|
<div className="pt-4">
|
|
<Button
|
|
className="w-full"
|
|
disabled={Object.keys(currentFilters).length === 0}
|
|
onClick={() => {
|
|
const copyCurrent = Object.assign({}, currentFilters);
|
|
(
|
|
Object.keys(copyCurrent) as (keyof typeof currentFilters)[]
|
|
).forEach((k) => {
|
|
copyCurrent[k] = undefined;
|
|
});
|
|
batchUpdateQueryParams(copyCurrent);
|
|
onClose();
|
|
}}
|
|
>
|
|
<XCircleIcon />
|
|
<span>{intl.formatMessage(messages.clearfilters)}</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</SlideOver>
|
|
);
|
|
};
|
|
|
|
export default FilterSlideover;
|