feat: adds status filter for tv shows (#796)

re #605

Co-authored-by: JoaquinOlivero <joaquin.olivero@hotmail.com>
This commit is contained in:
Joaquin Olivero
2024-08-19 15:37:04 -03:00
committed by GitHub
parent 80f63017ac
commit cfd1bc2535
7 changed files with 118 additions and 0 deletions

View File

@@ -4862,6 +4862,11 @@ paths:
schema: schema:
type: string type: string
example: 8|9 example: 8|9
- in: query
name: status
schema:
type: string
example: 3|4
responses: responses:
'200': '200':
description: Results description: Results

View File

@@ -95,6 +95,7 @@ interface DiscoverTvOptions {
sortBy?: SortOptions; sortBy?: SortOptions;
watchRegion?: string; watchRegion?: string;
watchProviders?: string; watchProviders?: string;
withStatus?: string; // Returning Series: 0 Planned: 1 In Production: 2 Ended: 3 Cancelled: 4 Pilot: 5
} }
class TheMovieDb extends ExternalAPI { class TheMovieDb extends ExternalAPI {
@@ -523,6 +524,7 @@ class TheMovieDb extends ExternalAPI {
voteCountLte, voteCountLte,
watchProviders, watchProviders,
watchRegion, watchRegion,
withStatus,
}: DiscoverTvOptions = {}): Promise<TmdbSearchTvResponse> => { }: DiscoverTvOptions = {}): Promise<TmdbSearchTvResponse> => {
try { try {
const defaultFutureDate = new Date( const defaultFutureDate = new Date(
@@ -570,6 +572,7 @@ class TheMovieDb extends ExternalAPI {
'vote_count.lte': voteCountLte || '', 'vote_count.lte': voteCountLte || '',
with_watch_providers: watchProviders || '', with_watch_providers: watchProviders || '',
watch_region: watchRegion || '', watch_region: watchRegion || '',
with_status: withStatus || '',
}); });
return data; return data;

View File

@@ -71,6 +71,7 @@ const QueryFilterOptions = z.object({
network: z.coerce.string().optional(), network: z.coerce.string().optional(),
watchProviders: z.coerce.string().optional(), watchProviders: z.coerce.string().optional(),
watchRegion: z.coerce.string().optional(), watchRegion: z.coerce.string().optional(),
status: z.coerce.string().optional(),
}); });
export type FilterOptions = z.infer<typeof QueryFilterOptions>; export type FilterOptions = z.infer<typeof QueryFilterOptions>;
@@ -385,6 +386,7 @@ discoverRoutes.get('/tv', async (req, res, next) => {
voteCountLte: query.voteCountLte, voteCountLte: query.voteCountLte,
watchProviders: query.watchProviders, watchProviders: query.watchProviders,
watchRegion: query.watchRegion, watchRegion: query.watchRegion,
withStatus: query.status,
}); });
const media = await Media.getRelatedMedia( const media = await Media.getRelatedMedia(

View File

@@ -8,6 +8,7 @@ import {
CompanySelector, CompanySelector,
GenreSelector, GenreSelector,
KeywordSelector, KeywordSelector,
StatusSelector,
WatchProviderSelector, WatchProviderSelector,
} from '@app/components/Selector'; } from '@app/components/Selector';
import useSettings from '@app/hooks/useSettings'; import useSettings from '@app/hooks/useSettings';
@@ -40,6 +41,7 @@ const messages = defineMessages('components.Discover.FilterSlideover', {
runtime: 'Runtime', runtime: 'Runtime',
streamingservices: 'Streaming Services', streamingservices: 'Streaming Services',
voteCount: 'Number of votes between {minValue} and {maxValue}', voteCount: 'Number of votes between {minValue} and {maxValue}',
status: 'Status',
}); });
type FilterSlideoverProps = { type FilterSlideoverProps = {
@@ -150,6 +152,23 @@ const FilterSlideover = ({
updateQueryParams('genre', value?.map((v) => v.value).join(',')); updateQueryParams('genre', value?.map((v) => v.value).join(','));
}} }}
/> />
{type === 'tv' && (
<>
<span className="text-lg font-semibold">
{intl.formatMessage(messages.status)}
</span>
<StatusSelector
defaultValue={currentFilters.status}
isMulti
onChange={(value) => {
updateQueryParams(
'status',
value?.map((v) => v.value).join('|')
);
}}
/>
</>
)}
<span className="text-lg font-semibold"> <span className="text-lg font-semibold">
{intl.formatMessage(messages.keywords)} {intl.formatMessage(messages.keywords)}
</span> </span>

View File

@@ -108,6 +108,7 @@ export const QueryFilterOptions = z.object({
voteCountGte: z.string().optional(), voteCountGte: z.string().optional(),
watchRegion: z.string().optional(), watchRegion: z.string().optional(),
watchProviders: z.string().optional(), watchProviders: z.string().optional(),
status: z.string().optional(),
}); });
export type FilterOptions = z.infer<typeof QueryFilterOptions>; export type FilterOptions = z.infer<typeof QueryFilterOptions>;
@@ -147,6 +148,10 @@ export const prepareFilterValues = (
filterValues.genre = values.genre; filterValues.genre = values.genre;
} }
if (values.status) {
filterValues.status = values.status;
}
if (values.keywords) { if (values.keywords) {
filterValues.keywords = values.keywords; filterValues.keywords = values.keywords;
} }

View File

@@ -33,6 +33,13 @@ const messages = defineMessages('components.Selector', {
nooptions: 'No results.', nooptions: 'No results.',
showmore: 'Show More', showmore: 'Show More',
showless: 'Show Less', showless: 'Show Less',
searchStatus: 'Select status...',
returningSeries: 'Returning Series',
planned: 'Planned',
inProduction: 'In Production',
ended: 'Ended',
canceled: 'Canceled',
pilot: 'Pilot',
}); });
type SingleVal = { type SingleVal = {
@@ -204,6 +211,75 @@ export const GenreSelector = ({
); );
}; };
export const StatusSelector = ({
isMulti,
defaultValue,
onChange,
}: BaseSelectorMultiProps | BaseSelectorSingleProps) => {
const intl = useIntl();
const [defaultDataValue, setDefaultDataValue] = useState<
{ label: string; value: number }[] | null
>(null);
const options = useMemo(
() => [
{ name: intl.formatMessage(messages.returningSeries), id: 0 },
{ name: intl.formatMessage(messages.planned), id: 1 },
{ name: intl.formatMessage(messages.inProduction), id: 2 },
{ name: intl.formatMessage(messages.ended), id: 3 },
{ name: intl.formatMessage(messages.canceled), id: 4 },
{ name: intl.formatMessage(messages.pilot), id: 5 },
],
[intl]
);
useEffect(() => {
const loadDefaultStatus = async (): Promise<void> => {
if (!defaultValue) {
return;
}
const statuses = defaultValue.split('|');
const statusData = options
.filter((opt) => statuses.find((s) => Number(s) === opt.id))
.map((o) => ({
label: o.name,
value: o.id,
}));
setDefaultDataValue(statusData);
};
loadDefaultStatus();
}, [defaultValue, options]);
const loadStatusOptions = async () => {
return options
.map((result) => ({
label: result.name,
value: result.id,
}))
.filter(({ label }) => label.toLowerCase());
};
return (
<AsyncSelect
key={`status-select-${defaultDataValue}`}
className="react-select-container"
classNamePrefix="react-select"
defaultValue={isMulti ? defaultDataValue : defaultDataValue?.[0]}
defaultOptions
isMulti={isMulti}
loadOptions={loadStatusOptions}
placeholder={intl.formatMessage(messages.searchStatus)}
onChange={(value) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChange(value as any);
}}
/>
);
};
export const KeywordSelector = ({ export const KeywordSelector = ({
isMulti, isMulti,
defaultValue, defaultValue,

View File

@@ -73,6 +73,7 @@
"components.Discover.FilterSlideover.releaseDate": "Release Date", "components.Discover.FilterSlideover.releaseDate": "Release Date",
"components.Discover.FilterSlideover.runtime": "Runtime", "components.Discover.FilterSlideover.runtime": "Runtime",
"components.Discover.FilterSlideover.runtimeText": "{minValue}-{maxValue} minute runtime", "components.Discover.FilterSlideover.runtimeText": "{minValue}-{maxValue} minute runtime",
"components.Discover.FilterSlideover.status": "Status",
"components.Discover.FilterSlideover.streamingservices": "Streaming Services", "components.Discover.FilterSlideover.streamingservices": "Streaming Services",
"components.Discover.FilterSlideover.studio": "Studio", "components.Discover.FilterSlideover.studio": "Studio",
"components.Discover.FilterSlideover.tmdbuserscore": "TMDB User Score", "components.Discover.FilterSlideover.tmdbuserscore": "TMDB User Score",
@@ -555,9 +556,16 @@
"components.ResetPassword.validationpasswordrequired": "You must provide a password", "components.ResetPassword.validationpasswordrequired": "You must provide a password",
"components.Search.search": "Search", "components.Search.search": "Search",
"components.Search.searchresults": "Search Results", "components.Search.searchresults": "Search Results",
"components.Selector.canceled": "Canceled",
"components.Selector.ended": "Ended",
"components.Selector.inProduction": "In Production",
"components.Selector.nooptions": "No results.", "components.Selector.nooptions": "No results.",
"components.Selector.pilot": "Pilot",
"components.Selector.planned": "Planned",
"components.Selector.returningSeries": "Returning Series",
"components.Selector.searchGenres": "Select genres…", "components.Selector.searchGenres": "Select genres…",
"components.Selector.searchKeywords": "Search keywords…", "components.Selector.searchKeywords": "Search keywords…",
"components.Selector.searchStatus": "Select status...",
"components.Selector.searchStudios": "Search studios…", "components.Selector.searchStudios": "Search studios…",
"components.Selector.showless": "Show Less", "components.Selector.showless": "Show Less",
"components.Selector.showmore": "Show More", "components.Selector.showmore": "Show More",