mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-03 05:09:43 -05:00
feat: adds status filter for tv shows (#796)
re #605 Co-authored-by: JoaquinOlivero <joaquin.olivero@hotmail.com>
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user