New: Special Seasons filtering for Wanted Missing

This commit is contained in:
Bogdan 2025-05-17 17:29:00 +03:00 committed by Mark McDowall
parent 24d780b77f
commit e8e31def9b
8 changed files with 84 additions and 17 deletions

View file

@ -20,6 +20,7 @@ import TablePager from 'Components/Table/TablePager';
import Episode from 'Episode/Episode';
import { useToggleEpisodesMonitored } from 'Episode/useEpisode';
import { Filter } from 'Filters/Filter';
import { useCustomFiltersList } from 'Filters/useCustomFilters';
import { align, icons, kinds } from 'Helpers/Props';
import { SortDirection } from 'Helpers/Props/sortDirections';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
@ -32,6 +33,7 @@ import {
unregisterPagePopulator,
} from 'Utilities/pagePopulator';
import translate from 'Utilities/String/translate';
import MissingFilterModal from './MissingFilterModal';
import {
setMissingOption,
setMissingOptions,
@ -39,7 +41,7 @@ import {
useMissingOptions,
} from './missingOptionsStore';
import MissingRow from './MissingRow';
import useMissing, { FILTERS } from './useMissing';
import useMissing, { FILTERS, useFilters } from './useMissing';
function getMonitoredValue(
filters: Filter[],
@ -66,6 +68,9 @@ function MissingContent() {
const { columns, pageSize, sortKey, sortDirection, selectedFilterKey } =
useMissingOptions();
const filters = useFilters();
const customFilters = useCustomFiltersList('wanted.missing');
const isSearchingForAllEpisodes = useCommandExecuting(
CommandNames.MissingEpisodeSearch
);
@ -257,8 +262,9 @@ function MissingContent() {
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={FILTERS}
customFilters={[]}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={MissingFilterModal}
onFilterSelect={handleFilterSelect}
/>
</PageToolbarSection>

View file

@ -0,0 +1,26 @@
import React, { useCallback } from 'react';
import { SetFilter } from 'Components/Filter/Filter';
import FilterModal, { FilterModalProps } from 'Components/Filter/FilterModal';
import Episode from 'Episode/Episode';
import { setMissingOption } from './missingOptionsStore';
import useMissing, { FILTER_BUILDER } from './useMissing';
type MissingFilterModalProps = FilterModalProps<Episode>;
export default function MissingFilterModal(props: MissingFilterModalProps) {
const { records } = useMissing();
const dispatchSetFilter = useCallback(({ selectedFilterKey }: SetFilter) => {
setMissingOption('selectedFilterKey', selectedFilterKey);
}, []);
return (
<FilterModal
{...props}
sectionItems={records}
filterBuilderProps={FILTER_BUILDER}
customFilterType="wanted.missing"
dispatchSetFilter={dispatchSetFilter}
/>
);
}

View file

@ -1,10 +1,13 @@
import { keepPreviousData } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import Episode from 'Episode/Episode';
import { setEpisodeQueryKey } from 'Episode/useEpisode';
import { Filter } from 'Filters/Filter';
import { Filter, FilterBuilderProp } from 'Filters/Filter';
import { useCustomFiltersList } from 'Filters/useCustomFilters';
import usePage from 'Helpers/Hooks/usePage';
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
import { filterBuilderValueTypes } from 'Helpers/Props';
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
import translate from 'Utilities/String/translate';
import { useMissingOptions } from './missingOptionsStore';
@ -31,20 +34,49 @@ export const FILTERS: Filter[] = [
},
],
},
{
key: 'excludeSpecials',
label: () => translate('ExcludeSpecials'),
filters: [
{
key: 'includeSpecials',
value: [false],
type: 'equal',
},
],
},
];
export const FILTER_BUILDER: FilterBuilderProp<Episode>[] = [
{
name: 'monitored',
label: () => translate('Monitored'),
type: 'exact',
valueType: filterBuilderValueTypes.BOOL,
},
{
name: 'includeSpecials',
label: () => translate('IncludeSpecials'),
type: 'equal',
valueType: filterBuilderValueTypes.BOOL,
},
];
const useMissing = () => {
const { page, goToPage } = usePage('missing');
const { pageSize, selectedFilterKey, sortKey, sortDirection } =
useMissingOptions();
const customFilters = useCustomFiltersList('wanted.missing');
const filters = useMemo(() => {
return findSelectedFilters(selectedFilterKey, FILTERS, customFilters);
}, [selectedFilterKey, customFilters]);
const { isPlaceholderData, queryKey, ...query } = usePagedApiQuery<Episode>({
path: '/wanted/missing',
page,
pageSize,
queryParams: {
monitored: selectedFilterKey === 'monitored',
},
filters,
sortKey,
sortDirection,
queryOptions: {
@ -67,3 +99,7 @@ const useMissing = () => {
};
export default useMissing;
export const useFilters = () => {
return FILTERS;
};

View file

@ -151,7 +151,7 @@ public void Execute(MissingEpisodeSearchCommand message)
pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Series.Monitored == false);
}
episodes = _episodeService.EpisodesWithoutFiles(pagingSpec).Records.ToList();
episodes = _episodeService.EpisodesWithoutFiles(pagingSpec, true).Records.ToList();
}
var queue = GetQueuedEpisodeIds();

View file

@ -708,6 +708,7 @@
"Events": "Events",
"Example": "Example",
"Exception": "Exception",
"ExcludeSpecials": "Exclude Specials",
"ExcludeUnknownSeriesItems": "Exclude Unknown Series Items",
"ExcludedReleaseProfile": "Excluded Release Profile",
"ExcludedReleaseProfiles": "Excluded Release Profiles",

View file

@ -27,7 +27,7 @@ public interface IEpisodeService
List<Episode> GetEpisodesBySeason(int seriesId, int seasonNumber);
List<Episode> GetEpisodesBySceneSeason(int seriesId, int sceneSeasonNumber);
List<Episode> EpisodesWithFiles(int seriesId);
PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec);
PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec, bool includeSpecials);
List<Episode> GetEpisodesByFileId(int episodeFileId);
void UpdateEpisode(Episode episode);
void SetEpisodeMonitored(int episodeId, bool monitored);
@ -158,11 +158,9 @@ public List<Episode> EpisodesWithFiles(int seriesId)
return _episodeRepository.EpisodesWithFiles(seriesId);
}
public PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec)
public PagingSpec<Episode> EpisodesWithoutFiles(PagingSpec<Episode> pagingSpec, bool includeSpecials)
{
var episodeResult = _episodeRepository.EpisodesWithoutFiles(pagingSpec, true);
return episodeResult;
return _episodeRepository.EpisodesWithoutFiles(pagingSpec, includeSpecials);
}
public List<Episode> GetEpisodesByFileId(int episodeFileId)

View file

@ -48,7 +48,7 @@ public PagingResource<EpisodeResource> GetMissingEpisodes([FromQuery] PagingRequ
pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Series.Monitored == false);
}
var resource = pagingSpec.ApplyToPage(_episodeService.EpisodesWithoutFiles, v => MapToResource(v, includeSeries, false, includeImages));
var resource = pagingSpec.ApplyToPage(spec => _episodeService.EpisodesWithoutFiles(spec, true), v => MapToResource(v, includeSeries, false, includeImages));
return resource;
}

View file

@ -24,7 +24,7 @@ public MissingController(IEpisodeService episodeService,
[HttpGet]
[Produces("application/json")]
public PagingResource<EpisodeResource> GetMissingEpisodes([FromQuery] PagingRequestResource paging, bool monitored = true, [FromQuery] MissingSubresource[]? includeSubresources = null)
public PagingResource<EpisodeResource> GetMissingEpisodes([FromQuery] PagingRequestResource paging, bool monitored = true, bool includeSpecials = true, [FromQuery] MissingSubresource[]? includeSubresources = null)
{
var pagingResource = new PagingResource<EpisodeResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<EpisodeResource, Episode>(
@ -49,7 +49,7 @@ public PagingResource<EpisodeResource> GetMissingEpisodes([FromQuery] PagingRequ
var includeSeries = includeSubresources.Contains(MissingSubresource.Series);
var includeImages = includeSubresources.Contains(MissingSubresource.Images);
var resource = pagingSpec.ApplyToPage(_episodeService.EpisodesWithoutFiles, v => MapToResource(v, includeSeries, false, includeImages));
var resource = pagingSpec.ApplyToPage(spec => _episodeService.EpisodesWithoutFiles(spec, includeSpecials), v => MapToResource(v, includeSeries, false, includeImages));
return resource;
}