mirror of
https://github.com/Sonarr/Sonarr
synced 2025-12-15 21:03:56 +01:00
Use react-query for custom filters
This commit is contained in:
parent
9db17883df
commit
645f20dc1b
44 changed files with 245 additions and 183 deletions
|
|
@ -16,10 +16,10 @@ import Table from 'Components/Table/Table';
|
|||
import TableBody from 'Components/Table/TableBody';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import BlockListModel from 'typings/Blocklist';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
|
|
@ -61,7 +61,7 @@ function BlocklistContent() {
|
|||
const filters = useFilters();
|
||||
const { isRemoving, removeBlocklistItems } = useRemoveBlocklistItems();
|
||||
|
||||
const customFilters = useSelector(createCustomFiltersSelector('blocklist'));
|
||||
const customFilters = useCustomFiltersList('blocklist');
|
||||
const isClearingBlocklistExecuting = useSelector(
|
||||
createCommandExecutingSelector(commandNames.CLEAR_BLOCKLIST)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { keepPreviousData, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CustomFilter, Filter, FilterBuilderProp } from 'App/State/AppState';
|
||||
import { Filter, FilterBuilderProp } from 'Filters/Filter';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import usePage from 'Helpers/Hooks/usePage';
|
||||
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
|
||||
import { filterBuilderValueTypes } from 'Helpers/Props';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import Blocklist from 'typings/Blocklist';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
|
@ -43,9 +42,7 @@ const useBlocklist = () => {
|
|||
const { page, goToPage } = usePage('blocklist');
|
||||
const { pageSize, selectedFilterKey, sortKey, sortDirection } =
|
||||
useBlocklistOptions();
|
||||
const customFilters = useSelector(
|
||||
createCustomFiltersSelector('blocklist')
|
||||
) as CustomFilter[];
|
||||
const customFilters = useCustomFiltersList('blocklist');
|
||||
|
||||
const filters = useMemo(() => {
|
||||
return findSelectedFilters(selectedFilterKey, FILTERS, customFilters);
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ import TableBody from 'Components/Table/TableBody';
|
|||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import createEpisodesFetchingSelector from 'Episode/createEpisodesFetchingSelector';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import { clearEpisodes, fetchEpisodes } from 'Store/Actions/episodeActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import HistoryItem from 'typings/History';
|
||||
import { TableOptionsChangePayload } from 'typings/Table';
|
||||
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
|
|
@ -59,7 +59,7 @@ function History() {
|
|||
|
||||
const { isEpisodesFetching, isEpisodesPopulated, episodesError } =
|
||||
useSelector(createEpisodesFetchingSelector());
|
||||
const customFilters = useSelector(createCustomFiltersSelector('history'));
|
||||
const customFilters = useCustomFiltersList('history');
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isFetchingAny = isLoading || isEpisodesFetching;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { keepPreviousData, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CustomFilter, Filter, FilterBuilderProp } from 'App/State/AppState';
|
||||
import { Filter, FilterBuilderProp } from 'Filters/Filter';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import usePage from 'Helpers/Hooks/usePage';
|
||||
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
|
||||
import { filterBuilderValueTypes } from 'Helpers/Props';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import History from 'typings/History';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
|
@ -117,9 +116,7 @@ const useHistory = () => {
|
|||
const { page, goToPage } = usePage('history');
|
||||
const { pageSize, selectedFilterKey, sortKey, sortDirection } =
|
||||
useHistoryOptions();
|
||||
const customFilters = useSelector(
|
||||
createCustomFiltersSelector('history')
|
||||
) as CustomFilter[];
|
||||
const customFilters = useCustomFiltersList('history');
|
||||
|
||||
const filters = useMemo(() => {
|
||||
return findSelectedFilters(selectedFilterKey, FILTERS, customFilters);
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@ import TableBody from 'Components/Table/TableBody';
|
|||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TablePager from 'Components/Table/TablePager';
|
||||
import createEpisodesFetchingSelector from 'Episode/createEpisodesFetchingSelector';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { clearEpisodes, fetchEpisodes } from 'Store/Actions/episodeActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import { CheckInputChanged } from 'typings/inputs';
|
||||
import QueueModel from 'typings/Queue';
|
||||
|
|
@ -81,7 +81,7 @@ function QueueContent() {
|
|||
const { count } = useQueueStatus();
|
||||
const { isEpisodesFetching, isEpisodesPopulated, episodesError } =
|
||||
useSelector(createEpisodesFetchingSelector());
|
||||
const customFilters = useSelector(createCustomFiltersSelector('queue'));
|
||||
const customFilters = useCustomFiltersList('queue');
|
||||
|
||||
const isRefreshMonitoredDownloadsExecuting = useSelector(
|
||||
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { keepPreviousData, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CustomFilter, Filter, FilterBuilderProp } from 'App/State/AppState';
|
||||
import { Filter, FilterBuilderProp } from 'Filters/Filter';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import usePage from 'Helpers/Hooks/usePage';
|
||||
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
|
||||
import { filterBuilderValueTypes } from 'Helpers/Props';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import Queue from 'typings/Queue';
|
||||
import getQueryString from 'Utilities/Fetch/getQueryString';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
|
|
@ -67,9 +66,7 @@ const useQueue = () => {
|
|||
sortKey,
|
||||
sortDirection,
|
||||
} = useQueueOptions();
|
||||
const customFilters = useSelector(
|
||||
createCustomFiltersSelector('queue')
|
||||
) as CustomFilter[];
|
||||
const customFilters = useCustomFiltersList('queue');
|
||||
|
||||
const filters = useMemo(() => {
|
||||
return findSelectedFilters(selectedFilterKey, FILTERS, customFilters);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Column from 'Components/Table/Column';
|
||||
import { Filter, FilterBuilderProp } from 'Filters/Filter';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import { ValidationFailure } from 'typings/pending';
|
||||
import { Filter, FilterBuilderProp } from './AppState';
|
||||
|
||||
export interface Error {
|
||||
status?: number;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import ModelBase from 'App/ModelBase';
|
||||
import { FilterBuilderTypes } from 'Helpers/Props/filterBuilderTypes';
|
||||
import { DateFilterValue, FilterType } from 'Helpers/Props/filterTypes';
|
||||
import { Error } from './AppSectionState';
|
||||
import BlocklistAppState from './BlocklistAppState';
|
||||
import CaptchaAppState from './CaptchaAppState';
|
||||
import CommandAppState from './CommandAppState';
|
||||
import CustomFiltersAppState from './CustomFiltersAppState';
|
||||
import EpisodesAppState from './EpisodesAppState';
|
||||
import HistoryAppState, { SeriesHistoryAppState } from './HistoryAppState';
|
||||
import ImportSeriesAppState from './ImportSeriesAppState';
|
||||
|
|
@ -18,38 +14,6 @@ import ProviderOptionsAppState from './ProviderOptionsAppState';
|
|||
import SeriesAppState, { SeriesIndexAppState } from './SeriesAppState';
|
||||
import SettingsAppState from './SettingsAppState';
|
||||
|
||||
export interface FilterBuilderPropOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface FilterBuilderProp<T> {
|
||||
name: string;
|
||||
label: string | (() => string);
|
||||
type: FilterBuilderTypes;
|
||||
valueType?: string;
|
||||
optionsSelector?: (items: T[]) => FilterBuilderPropOption[];
|
||||
}
|
||||
|
||||
// TODO: Make generic so key can be keyof T
|
||||
export interface PropertyFilter {
|
||||
key: string;
|
||||
value: string | string[] | number[] | boolean[] | DateFilterValue;
|
||||
type: FilterType;
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
key: string;
|
||||
label: string | (() => string);
|
||||
filters: PropertyFilter[];
|
||||
}
|
||||
|
||||
export interface CustomFilter extends ModelBase {
|
||||
type: string;
|
||||
label: string;
|
||||
filters: PropertyFilter[];
|
||||
}
|
||||
|
||||
export interface AppSectionState {
|
||||
isUpdated: boolean;
|
||||
isConnected: boolean;
|
||||
|
|
@ -77,7 +41,6 @@ interface AppState {
|
|||
blocklist: BlocklistAppState;
|
||||
captcha: CaptchaAppState;
|
||||
commands: CommandAppState;
|
||||
customFilters: CustomFiltersAppState;
|
||||
episodeHistory: HistoryAppState;
|
||||
episodes: EpisodesAppState;
|
||||
episodesSelection: EpisodesAppState;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { CustomFilter } from './AppState';
|
||||
|
||||
interface ClientSideCollectionAppState {
|
||||
totalItems: number;
|
||||
customFilters: CustomFilter[];
|
||||
}
|
||||
|
||||
export default ClientSideCollectionAppState;
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import AppSectionState, {
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import { CustomFilter } from './AppState';
|
||||
|
||||
interface CustomFiltersAppState
|
||||
extends AppSectionState<CustomFilter>,
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState {}
|
||||
|
||||
export default CustomFiltersAppState;
|
||||
|
|
@ -3,9 +3,9 @@ import AppSectionState, {
|
|||
AppSectionSaveState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import Column from 'Components/Table/Column';
|
||||
import { Filter, FilterBuilderProp } from 'Filters/Filter';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import Series from 'Series/Series';
|
||||
import { Filter, FilterBuilderProp } from './AppState';
|
||||
|
||||
export interface SeriesIndexAppState {
|
||||
sortKey: string;
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import Episode from 'Episode/Episode';
|
||||
import EpisodeFileProvider from 'EpisodeFile/EpisodeFileProvider';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import useMeasure from 'Helpers/Hooks/useMeasure';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import NoSeries from 'Series/NoSeries';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import createSeriesCountSelector from 'Store/Selectors/createSeriesCountSelector';
|
||||
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
|
||||
|
|
@ -53,7 +53,7 @@ function CalendarPage() {
|
|||
const isRssSyncExecuting = useSelector(
|
||||
createCommandExecutingSelector(commandNames.RSS_SYNC)
|
||||
);
|
||||
const customFilters = useSelector(createCustomFiltersSelector('calendar'));
|
||||
const customFilters = useCustomFiltersList('calendar');
|
||||
const hasSeries = !!useSelector(createSeriesCountSelector());
|
||||
|
||||
const [pageContentRef, { width }] = useMeasure();
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ import moment from 'moment';
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { create } from 'zustand';
|
||||
import AppState, { Filter, FilterBuilderProp } from 'App/State/AppState';
|
||||
import AppState from 'App/State/AppState';
|
||||
import Command from 'Commands/Command';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import { setEpisodeQueryKey } from 'Episode/useEpisode';
|
||||
import { Filter, FilterBuilderProp } from 'Filters/Filter';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import useApiQuery from 'Helpers/Hooks/useApiQuery';
|
||||
import { filterBuilderValueTypes } from 'Helpers/Props';
|
||||
import { executeCommandHelper } from 'Store/Actions/commandActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import { CalendarItem } from 'typings/Calendar';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
|
@ -81,7 +82,7 @@ const useCalendar = () => {
|
|||
const time = useCalendarTime();
|
||||
const selectedFilterKey = useCalendarOption('selectedFilterKey');
|
||||
const view = useCalendarOption('view');
|
||||
const customFilters = useSelector(createCustomFiltersSelector('calendar'));
|
||||
const customFilters = useCustomFiltersList('calendar');
|
||||
|
||||
const { start, end } = useMemo(() => {
|
||||
return getPopulatableRange(dates[0], dates[dates.length - 1], view);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { FilterBuilderPropOption } from 'App/State/AppState';
|
||||
import { FilterBuilderPropOption } from 'Filters/Filter';
|
||||
import { filterBuilderTypes } from 'Helpers/Props';
|
||||
import * as filterTypes from 'Helpers/Props/filterTypes';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import { maxBy } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import AppState, {
|
||||
CustomFilter,
|
||||
FilterBuilderProp,
|
||||
PropertyFilter,
|
||||
} from 'App/State/AppState';
|
||||
import FormInputGroup, {
|
||||
ValidationMessage,
|
||||
} from 'Components/Form/FormInputGroup';
|
||||
|
|
@ -15,9 +9,14 @@ import ModalBody from 'Components/Modal/ModalBody';
|
|||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import {
|
||||
CustomFilter,
|
||||
FilterBuilderProp,
|
||||
PropertyFilter,
|
||||
} from 'Filters/Filter';
|
||||
import { useSaveCustomFilter } from 'Filters/useCustomFilters';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import { saveCustomFilter } from 'Store/Actions/customFilterActions';
|
||||
import { InputChanged } from 'typings/inputs';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import FilterBuilderRow from './FilterBuilderRow';
|
||||
|
|
@ -50,10 +49,8 @@ function FilterBuilderModalContent<T>({
|
|||
onCancelPress,
|
||||
onModalClose,
|
||||
}: FilterBuilderModalContentProps<T>) {
|
||||
const dispatch = useDispatch();
|
||||
const { isSaving, saveError } = useSelector(
|
||||
(state: AppState) => state.customFilters
|
||||
);
|
||||
const { newCustomFilter, saveCustomFilter, isSaving, saveError } =
|
||||
useSaveCustomFilter(id);
|
||||
|
||||
const { initialLabel, initialFilters } = useMemo(() => {
|
||||
if (id) {
|
||||
|
|
@ -121,13 +118,15 @@ function FilterBuilderModalContent<T>({
|
|||
return;
|
||||
}
|
||||
|
||||
dispatch(saveCustomFilter({ id, type: customFilterType, label, filters }));
|
||||
}, [id, customFilterType, label, filters, dispatch]);
|
||||
saveCustomFilter({ type: customFilterType, label, filters });
|
||||
}, [customFilterType, label, filters, saveCustomFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wasSaving && !isSaving && !saveError) {
|
||||
if (id) {
|
||||
dispatchSetFilter({ selectedFilterKey: id });
|
||||
} else if (newCustomFilter) {
|
||||
dispatchSetFilter({ selectedFilterKey: newCustomFilter.id });
|
||||
} else {
|
||||
const last = maxBy(customFilters, 'id');
|
||||
|
||||
|
|
@ -141,6 +140,7 @@ function FilterBuilderModalContent<T>({
|
|||
}, [
|
||||
id,
|
||||
customFilters,
|
||||
newCustomFilter,
|
||||
isSaving,
|
||||
wasSaving,
|
||||
saveError,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { FilterBuilderProp, PropertyFilter } from 'App/State/AppState';
|
||||
import SelectInput from 'Components/Form/SelectInput';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import { FilterBuilderProp, PropertyFilter } from 'Filters/Filter';
|
||||
import { filterBuilderValueTypes, icons } from 'Helpers/Props';
|
||||
import {
|
||||
FilterBuilderTypes,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { FilterBuilderProp } from 'App/State/AppState';
|
||||
import TagInput, { TagBase } from 'Components/Form/Tag/TagInput';
|
||||
import { FilterBuilderProp } from 'Filters/Filter';
|
||||
import {
|
||||
filterBuilderTypes,
|
||||
filterBuilderValueTypes,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Error } from 'App/State/AppSectionState';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import { useDeleteCustomFilter } from 'Filters/useCustomFilters';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { deleteCustomFilter } from 'Store/Actions/customFilterActions';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './CustomFilter.css';
|
||||
|
||||
interface CustomFilterProps {
|
||||
id: number;
|
||||
label: string;
|
||||
isDeleting: boolean;
|
||||
deleteError?: Error;
|
||||
dispatchSetFilter: (payload: { selectedFilterKey: string | number }) => void;
|
||||
onEditPress: (id: number) => void;
|
||||
}
|
||||
|
|
@ -21,11 +18,11 @@ interface CustomFilterProps {
|
|||
function CustomFilter({
|
||||
id,
|
||||
label,
|
||||
isDeleting,
|
||||
deleteError,
|
||||
dispatchSetFilter,
|
||||
onEditPress,
|
||||
}: CustomFilterProps) {
|
||||
const { deleteCustomFilter, isDeleting, deleteError } =
|
||||
useDeleteCustomFilter(id);
|
||||
const dispatch = useDispatch();
|
||||
const wasDeleting = usePrevious(isDeleting);
|
||||
const [isDeletingInternal, setIsDeletingInternal] = useState(false);
|
||||
|
|
@ -37,8 +34,8 @@ function CustomFilter({
|
|||
const handleRemovePress = useCallback(() => {
|
||||
setIsDeletingInternal(true);
|
||||
|
||||
dispatch(deleteCustomFilter({ id }));
|
||||
}, [id, dispatch]);
|
||||
deleteCustomFilter();
|
||||
}, [deleteCustomFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (wasDeleting && !isDeleting && isDeletingInternal && deleteError) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import AppState, {
|
||||
CustomFilter as CustomFilterModel,
|
||||
} from 'App/State/AppState';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
import { CustomFilter as CustomFilterModel } from 'Filters/Filter';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import CustomFilter from './CustomFilter';
|
||||
import styles from './CustomFiltersModalContent.css';
|
||||
|
|
@ -28,23 +24,17 @@ function CustomFiltersModalContent({
|
|||
onEditCustomFilter,
|
||||
onModalClose,
|
||||
}: CustomFiltersModalContentProps) {
|
||||
const { isDeleting, deleteError } = useSelector(
|
||||
(state: AppState) => state.customFilters
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('CustomFilters')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{customFilters.sort(sortByProp('label')).map((customFilter) => {
|
||||
{customFilters.map((customFilter) => {
|
||||
return (
|
||||
<CustomFilter
|
||||
key={customFilter.id}
|
||||
id={customFilter.id}
|
||||
label={customFilter.label}
|
||||
isDeleting={isDeleting}
|
||||
deleteError={deleteError}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
onEditPress={onEditCustomFilter}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { CustomFilter, FilterBuilderProp } from 'App/State/AppState';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import { CustomFilter, FilterBuilderProp } from 'Filters/Filter';
|
||||
import FilterBuilderModalContent from './Builder/FilterBuilderModalContent';
|
||||
import CustomFiltersModalContent from './CustomFilters/CustomFiltersModalContent';
|
||||
import { SetFilter } from './Filter';
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ import Icon, { IconKind, IconName } from 'Components/Icon';
|
|||
import SpinnerButton, {
|
||||
SpinnerButtonProps,
|
||||
} from 'Components/Link/SpinnerButton';
|
||||
import { getValidationFailures } from 'Helpers/Hooks/useApiMutation';
|
||||
import usePrevious from 'Helpers/Hooks/usePrevious';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { ValidationFailure } from 'typings/pending';
|
||||
import { ApiError } from 'Utilities/Fetch/fetchJson';
|
||||
import styles from './SpinnerErrorButton.css';
|
||||
|
||||
function getTestResult(error: Error | string | undefined) {
|
||||
function getTestResult(error: ApiError | Error | string | undefined | null) {
|
||||
if (!error) {
|
||||
return {
|
||||
wasSuccessful: true,
|
||||
|
|
@ -18,7 +20,7 @@ function getTestResult(error: Error | string | undefined) {
|
|||
};
|
||||
}
|
||||
|
||||
if (typeof error === 'string' || error.status !== 400) {
|
||||
if (typeof error === 'string') {
|
||||
return {
|
||||
wasSuccessful: false,
|
||||
hasWarning: false,
|
||||
|
|
@ -26,31 +28,63 @@ function getTestResult(error: Error | string | undefined) {
|
|||
};
|
||||
}
|
||||
|
||||
const failures = error.responseJSON as ValidationFailure[];
|
||||
if ('status' in error) {
|
||||
if (error.status !== 400) {
|
||||
return {
|
||||
wasSuccessful: false,
|
||||
hasWarning: false,
|
||||
hasError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const { hasError, hasWarning } = failures.reduce(
|
||||
(acc, failure) => {
|
||||
if (failure.isWarning) {
|
||||
acc.hasWarning = true;
|
||||
} else {
|
||||
acc.hasError = true;
|
||||
}
|
||||
const failures = error.responseJSON as ValidationFailure[];
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ hasWarning: false, hasError: false }
|
||||
);
|
||||
const { hasError, hasWarning } = failures.reduce(
|
||||
(acc, failure) => {
|
||||
if (failure.isWarning) {
|
||||
acc.hasWarning = true;
|
||||
} else {
|
||||
acc.hasError = true;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ hasWarning: false, hasError: false }
|
||||
);
|
||||
|
||||
return {
|
||||
wasSuccessful: false,
|
||||
hasWarning,
|
||||
hasError,
|
||||
};
|
||||
} else if ('statusCode' in error) {
|
||||
if (error.statusCode !== 400 || error.statusBody == null) {
|
||||
return {
|
||||
wasSuccessful: false,
|
||||
hasWarning: false,
|
||||
hasError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const failures = getValidationFailures(error);
|
||||
|
||||
return {
|
||||
wasSuccessful: false,
|
||||
hasWarning: failures.warnings.length > 0,
|
||||
hasError: failures.errors.length > 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
wasSuccessful: false,
|
||||
hasWarning,
|
||||
hasError,
|
||||
hasWarning: false,
|
||||
hasError: true,
|
||||
};
|
||||
}
|
||||
|
||||
interface SpinnerErrorButtonProps extends SpinnerButtonProps {
|
||||
isSpinning: boolean;
|
||||
error?: Error | string;
|
||||
error?: ApiError | Error | string | null;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { CustomFilter, Filter } from 'App/State/AppState';
|
||||
import { CustomFilter, Filter } from 'Filters/Filter';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import FilterMenuContent from './FilterMenuContent';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { CustomFilter, Filter } from 'App/State/AppState';
|
||||
import { CustomFilter, Filter } from 'Filters/Filter';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import FilterMenuItem from './FilterMenuItem';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ interface ErrorPageProps {
|
|||
isLocalStorageSupported: boolean;
|
||||
translationsError?: Error;
|
||||
seriesError?: Error;
|
||||
customFiltersError?: Error;
|
||||
customFiltersError: ApiError | null;
|
||||
tagsError: ApiError | null;
|
||||
qualityProfilesError?: Error;
|
||||
uiSettingsError?: Error;
|
||||
|
|
|
|||
34
frontend/src/Filters/Filter.ts
Normal file
34
frontend/src/Filters/Filter.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import ModelBase from 'App/ModelBase';
|
||||
import { FilterBuilderTypes } from 'Helpers/Props/filterBuilderTypes';
|
||||
import { DateFilterValue, FilterType } from 'Helpers/Props/filterTypes';
|
||||
|
||||
export interface FilterBuilderPropOption {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface FilterBuilderProp<T> {
|
||||
name: string;
|
||||
label: string | (() => string);
|
||||
type: FilterBuilderTypes;
|
||||
valueType?: string;
|
||||
optionsSelector?: (items: T[]) => FilterBuilderPropOption[];
|
||||
}
|
||||
|
||||
export interface PropertyFilter {
|
||||
key: string;
|
||||
value: string | string[] | number[] | boolean[] | DateFilterValue;
|
||||
type: FilterType;
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
key: string;
|
||||
label: string | (() => string);
|
||||
filters: PropertyFilter[];
|
||||
}
|
||||
|
||||
export interface CustomFilter extends ModelBase {
|
||||
type: string;
|
||||
label: string;
|
||||
filters: PropertyFilter[];
|
||||
}
|
||||
73
frontend/src/Filters/useCustomFilters.ts
Normal file
73
frontend/src/Filters/useCustomFilters.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import useApiQuery from 'Helpers/Hooks/useApiQuery';
|
||||
import { sortByProp } from 'Utilities/Array/sortByProp';
|
||||
import { CustomFilter } from './Filter';
|
||||
|
||||
const DEFAULT_CUSTOM_FILTERS: CustomFilter[] = [];
|
||||
|
||||
const useCustomFilters = () => {
|
||||
const result = useApiQuery<CustomFilter[]>({
|
||||
path: '/customFilter',
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
data: result.data ?? DEFAULT_CUSTOM_FILTERS,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCustomFilters;
|
||||
|
||||
export const useCustomFiltersList = (type: string) => {
|
||||
const { data } = useCustomFilters();
|
||||
|
||||
return useMemo(() => {
|
||||
return data.filter((cf) => cf.type === type).sort(sortByProp('label'));
|
||||
}, [data, type]);
|
||||
};
|
||||
|
||||
export const useSaveCustomFilter = (id: number | null) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate, isPending, error, data } = useApiMutation<
|
||||
CustomFilter,
|
||||
Partial<CustomFilter>
|
||||
>({
|
||||
path: id === null ? '/customFilter' : `/customFilter/${id}`,
|
||||
method: id === null ? 'POST' : 'PUT',
|
||||
mutationOptions: {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/customFilter'] });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
saveCustomFilter: mutate,
|
||||
isSaving: isPending,
|
||||
saveError: error,
|
||||
newCustomFilter: data,
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteCustomFilter = (id: number) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate, isPending, error } = useApiMutation<void, void>({
|
||||
path: `/customFilter/${id}`,
|
||||
method: 'DELETE',
|
||||
mutationOptions: {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['/customFilter'] });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
deleteCustomFilter: mutate,
|
||||
isDeleting: isPending,
|
||||
deleteError: error,
|
||||
};
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@ import { useEffect, useMemo } from 'react';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import useCustomFilters from 'Filters/useCustomFilters';
|
||||
import { fetchTranslations } from 'Store/Actions/appActions';
|
||||
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
|
||||
import { fetchSeries } from 'Store/Actions/seriesActions';
|
||||
|
|
@ -17,15 +18,16 @@ import useTags from 'Tags/useTags';
|
|||
import { ApiError } from 'Utilities/Fetch/fetchJson';
|
||||
|
||||
const createErrorsSelector = ({
|
||||
customFiltersError,
|
||||
systemStatusError,
|
||||
tagsError,
|
||||
}: {
|
||||
customFiltersError: ApiError | null;
|
||||
systemStatusError: ApiError | null;
|
||||
tagsError: ApiError | null;
|
||||
}) =>
|
||||
createSelector(
|
||||
(state: AppState) => state.series.error,
|
||||
(state: AppState) => state.customFilters.error,
|
||||
(state: AppState) => state.settings.ui.error,
|
||||
(state: AppState) => state.settings.qualityProfiles.error,
|
||||
(state: AppState) => state.settings.languages.error,
|
||||
|
|
@ -34,7 +36,6 @@ const createErrorsSelector = ({
|
|||
(state: AppState) => state.app.translations.error,
|
||||
(
|
||||
seriesError,
|
||||
customFiltersError,
|
||||
uiSettingsError,
|
||||
qualityProfilesError,
|
||||
languagesError,
|
||||
|
|
@ -43,8 +44,8 @@ const createErrorsSelector = ({
|
|||
translationsError
|
||||
) => {
|
||||
const hasError = !!(
|
||||
seriesError ||
|
||||
customFiltersError ||
|
||||
seriesError ||
|
||||
uiSettingsError ||
|
||||
qualityProfilesError ||
|
||||
languagesError ||
|
||||
|
|
@ -75,6 +76,10 @@ const createErrorsSelector = ({
|
|||
|
||||
const useAppPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { isFetched: isCustomFiltersFetched, error: customFiltersError } =
|
||||
useCustomFilters();
|
||||
|
||||
const { isFetched: isSystemStatusFetched, error: systemStatusError } =
|
||||
useSystemStatus();
|
||||
|
||||
|
|
@ -83,7 +88,6 @@ const useAppPage = () => {
|
|||
const isAppStatePopulated = useSelector(
|
||||
(state: AppState) =>
|
||||
state.series.isPopulated &&
|
||||
state.customFilters.isPopulated &&
|
||||
state.settings.ui.isPopulated &&
|
||||
state.settings.qualityProfiles.isPopulated &&
|
||||
state.settings.languages.isPopulated &&
|
||||
|
|
@ -93,10 +97,13 @@ const useAppPage = () => {
|
|||
);
|
||||
|
||||
const isPopulated =
|
||||
isAppStatePopulated && isSystemStatusFetched && isTagsFetched;
|
||||
isAppStatePopulated &&
|
||||
isCustomFiltersFetched &&
|
||||
isSystemStatusFetched &&
|
||||
isTagsFetched;
|
||||
|
||||
const { hasError, errors } = useSelector(
|
||||
createErrorsSelector({ systemStatusError, tagsError })
|
||||
createErrorsSelector({ customFiltersError, systemStatusError, tagsError })
|
||||
);
|
||||
|
||||
const isLocalStorageSupported = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { keepPreviousData, useQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { PropertyFilter } from 'App/State/AppState';
|
||||
import { PropertyFilter } from 'Filters/Filter';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import fetchJson from 'Utilities/Fetch/fetchJson';
|
||||
import getQueryPath from 'Utilities/Fetch/getQueryPath';
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import PageMenuButton from 'Components/Menu/PageMenuButton';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import { align, kinds } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import InteractiveSearchFilterModal from './InteractiveSearchFilterModal';
|
||||
|
|
@ -25,7 +24,7 @@ interface InteractiveSearchProps {
|
|||
}
|
||||
|
||||
function InteractiveSearch({ type, searchPayload }: InteractiveSearchProps) {
|
||||
const customFilters = useSelector(createCustomFiltersSelector('releases'));
|
||||
const customFilters = useCustomFiltersList('releases');
|
||||
const { columns } = useReleaseOptions();
|
||||
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { create } from 'zustand';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import { Filter, FilterBuilderProp } from 'App/State/AppState';
|
||||
import { FilterBuilderTag } from 'Components/Filter/Builder/FilterBuilderRowValue';
|
||||
import type DownloadProtocol from 'DownloadClient/DownloadProtocol';
|
||||
import { Filter, FilterBuilderProp } from 'Filters/Filter';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import useApiMutation from 'Helpers/Hooks/useApiMutation';
|
||||
import useApiQuery from 'Helpers/Hooks/useApiQuery';
|
||||
import { applySort } from 'Helpers/Hooks/useOptionsStore';
|
||||
|
|
@ -15,7 +15,6 @@ import { SortDirection } from 'Helpers/Props/sortDirections';
|
|||
import Language from 'Language/Language';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import { AlternateTitle } from 'Series/Series';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import Rejection from 'typings/Rejection';
|
||||
import sortByProp from 'Utilities/Array/sortByProp';
|
||||
|
|
@ -379,7 +378,7 @@ const DEFAULT_RELEASES: Release[] = [];
|
|||
const THIRTY_MINUTES = 30 * 60 * 1000;
|
||||
|
||||
const useReleases = (payload: InteractiveSearchPayload) => {
|
||||
const customFilters = useSelector(createCustomFiltersSelector('releases'));
|
||||
const customFilters = useCustomFiltersList('releases');
|
||||
const { episodeSelectedFilterKey, seasonSelectedFilterKey } =
|
||||
useReleaseOptions();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { CustomFilter, Filter } from 'App/State/AppState';
|
||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||
import { CustomFilter, Filter } from 'Filters/Filter';
|
||||
import SeriesIndexFilterModal from 'Series/Index/SeriesIndexFilterModal';
|
||||
|
||||
interface SeriesIndexFilterMenuProps {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { useCustomFiltersList } from 'Filters/useCustomFilters';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import { DESCENDING } from 'Helpers/Props/sortDirections';
|
||||
import ParseToolbarButton from 'Parse/ParseToolbarButton';
|
||||
|
|
@ -83,13 +84,14 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => {
|
|||
columns,
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
view,
|
||||
}: SeriesAppState & SeriesIndexAppState & ClientSideCollectionAppState =
|
||||
useSelector(createSeriesClientSideCollectionItemsSelector('seriesIndex'));
|
||||
|
||||
const customFilters = useCustomFiltersList('series');
|
||||
|
||||
const isRssSyncExecuting = useSelector(
|
||||
createCommandExecutingSelector(RSS_SYNC)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import * as app from './appActions';
|
||||
import * as captcha from './captchaActions';
|
||||
import * as commands from './commandActions';
|
||||
import * as customFilters from './customFilterActions';
|
||||
import * as episodes from './episodeActions';
|
||||
import * as episodeHistory from './episodeHistoryActions';
|
||||
import * as episodeSelection from './episodeSelectionActions';
|
||||
|
|
@ -20,7 +19,6 @@ export default [
|
|||
app,
|
||||
captcha,
|
||||
commands,
|
||||
customFilters,
|
||||
episodes,
|
||||
episodeHistory,
|
||||
episodeSelection,
|
||||
|
|
|
|||
|
|
@ -109,24 +109,12 @@ function sort(items, state) {
|
|||
return _.orderBy(items, clauses, orders);
|
||||
}
|
||||
|
||||
export function createCustomFiltersSelector(type, alternateType) {
|
||||
return createSelector(
|
||||
(state) => state.customFilters.items,
|
||||
(customFilters) => {
|
||||
return customFilters.filter((customFilter) => {
|
||||
return customFilter.type === type || customFilter.type === alternateType;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createClientSideCollectionSelector(section, uiSection) {
|
||||
return createSelector(
|
||||
(state) => _.get(state, section),
|
||||
(state) => _.get(state, uiSection),
|
||||
createCustomFiltersSelector(section, uiSection),
|
||||
(sectionState, uiSectionState = {}, customFilters) => {
|
||||
const state = Object.assign({}, sectionState, uiSectionState, { customFilters });
|
||||
(sectionState, uiSectionState = {}) => {
|
||||
const state = Object.assign({}, sectionState, uiSectionState);
|
||||
|
||||
const filtered = filter(state.items, state);
|
||||
const sorted = sort(filtered, state);
|
||||
|
|
@ -134,7 +122,6 @@ function createClientSideCollectionSelector(section, uiSection) {
|
|||
return {
|
||||
...sectionState,
|
||||
...uiSectionState,
|
||||
customFilters,
|
||||
items: sorted,
|
||||
totalItems: state.items.length
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { keepPreviousData } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { Filter } from 'App/State/AppState';
|
||||
import { Filter } from 'Filters/Filter';
|
||||
import usePage from 'Helpers/Hooks/usePage';
|
||||
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
|
||||
import LogEvent from 'typings/LogEvent';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { PropertyFilter } from 'App/State/AppState';
|
||||
import { PropertyFilter } from 'Filters/Filter';
|
||||
|
||||
export interface QueryParams {
|
||||
[key: string]:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import { CustomFilter, Filter } from 'App/State/AppState';
|
||||
import { CustomFilter, Filter } from 'Filters/Filter';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import { FilterType } from 'Helpers/Props/filterTypes';
|
||||
import getFilterTypePredicate from 'Helpers/Props/getFilterTypePredicate';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CustomFilter, Filter } from 'App/State/AppState';
|
||||
import { CustomFilter, Filter } from 'Filters/Filter';
|
||||
|
||||
export default function findSelectedFilters(
|
||||
selectedFilterKey: string | number,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Filter } from 'App/State/AppState';
|
||||
import { Filter } from 'Filters/Filter';
|
||||
|
||||
export default function getFilterValue<T>(
|
||||
filters: Filter[],
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import React, {
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import QueueDetailsProvider from 'Activity/Queue/Details/QueueDetailsProvider';
|
||||
import { SelectProvider, useSelect } from 'App/Select/SelectContext';
|
||||
import { Filter } from 'App/State/AppState';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
|
|
@ -27,6 +26,7 @@ import TablePager from 'Components/Table/TablePager';
|
|||
import Episode from 'Episode/Episode';
|
||||
import { useToggleEpisodesMonitored } from 'Episode/useEpisode';
|
||||
import EpisodeFileProvider from 'EpisodeFile/EpisodeFileProvider';
|
||||
import { Filter } from 'Filters/Filter';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { keepPreviousData } from '@tanstack/react-query';
|
||||
import { useEffect } from 'react';
|
||||
import { Filter } from 'App/State/AppState';
|
||||
import Episode from 'Episode/Episode';
|
||||
import { setEpisodeQueryKey } from 'Episode/useEpisode';
|
||||
import { Filter } from 'Filters/Filter';
|
||||
import usePage from 'Helpers/Hooks/usePage';
|
||||
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import QueueDetailsProvider from 'Activity/Queue/Details/QueueDetailsProvider';
|
||||
import { SelectProvider, useSelect } from 'App/Select/SelectContext';
|
||||
import { Filter } from 'App/State/AppState';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import Alert from 'Components/Alert';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
|
|
@ -20,6 +19,7 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
|
|||
import TablePager from 'Components/Table/TablePager';
|
||||
import Episode from 'Episode/Episode';
|
||||
import { useToggleEpisodesMonitored } from 'Episode/useEpisode';
|
||||
import { Filter } from 'Filters/Filter';
|
||||
import { align, icons, kinds } from 'Helpers/Props';
|
||||
import { SortDirection } from 'Helpers/Props/sortDirections';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { keepPreviousData } from '@tanstack/react-query';
|
||||
import { useEffect } from 'react';
|
||||
import { Filter } from 'App/State/AppState';
|
||||
import Episode from 'Episode/Episode';
|
||||
import { setEpisodeQueryKey } from 'Episode/useEpisode';
|
||||
import { Filter } from 'Filters/Filter';
|
||||
import usePage from 'Helpers/Hooks/usePage';
|
||||
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
|
||||
import translate from 'Utilities/String/translate';
|
||||
|
|
|
|||
|
|
@ -46,8 +46,10 @@ public ActionResult<CustomFilterResource> UpdateCustomFilter([FromBody] CustomFi
|
|||
}
|
||||
|
||||
[RestDeleteById]
|
||||
public void DeleteCustomResource(int id)
|
||||
public ActionResult DeleteCustomResource(int id)
|
||||
{
|
||||
_customFilterService.Delete(id);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue