From 16255ed53f387d60962cd41fac37ff5335eeaabd Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 30 Sep 2025 21:12:38 -0700 Subject: [PATCH] Use react-query for History UI --- .../History/Details/HistoryDetailsModal.tsx | 35 +- frontend/src/Activity/History/History.tsx | 118 +++---- .../Activity/History/HistoryFilterModal.tsx | 39 +-- frontend/src/Activity/History/HistoryRow.tsx | 29 +- .../Activity/History/historyOptionsStore.ts | 109 ++++++ frontend/src/Activity/History/useHistory.ts | 186 ++++++++++ frontend/src/App/State/AppState.ts | 1 - frontend/src/Helpers/Hooks/usePage.ts | 2 + frontend/src/Store/Actions/historyActions.js | 327 ------------------ frontend/src/Store/Actions/index.js | 2 - 10 files changed, 377 insertions(+), 471 deletions(-) create mode 100644 frontend/src/Activity/History/historyOptionsStore.ts create mode 100644 frontend/src/Activity/History/useHistory.ts delete mode 100644 frontend/src/Store/Actions/historyActions.js diff --git a/frontend/src/Activity/History/Details/HistoryDetailsModal.tsx b/frontend/src/Activity/History/Details/HistoryDetailsModal.tsx index 8134a9736..f9eefb650 100644 --- a/frontend/src/Activity/History/Details/HistoryDetailsModal.tsx +++ b/frontend/src/Activity/History/Details/HistoryDetailsModal.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import Button from 'Components/Link/Button'; import SpinnerButton from 'Components/Link/SpinnerButton'; import Modal from 'Components/Modal/Modal'; @@ -9,6 +9,7 @@ import ModalHeader from 'Components/Modal/ModalHeader'; import { kinds } from 'Helpers/Props'; import { HistoryData, HistoryEventType } from 'typings/History'; import translate from 'Utilities/String/translate'; +import { useMarkAsFailed } from '../useHistory'; import HistoryDetails from './HistoryDetails'; import styles from './HistoryDetailsModal.css'; @@ -33,26 +34,32 @@ function getHeaderTitle(eventType: HistoryEventType) { interface HistoryDetailsModalProps { isOpen: boolean; + id: number; eventType: HistoryEventType; sourceTitle: string; data: HistoryData; downloadId?: string; - isMarkingAsFailed: boolean; - onMarkAsFailedPress: () => void; onModalClose: () => void; } function HistoryDetailsModal(props: HistoryDetailsModalProps) { - const { - isOpen, - eventType, - sourceTitle, - data, - downloadId, - isMarkingAsFailed = false, - onMarkAsFailedPress, - onModalClose, - } = props; + const { isOpen, id, eventType, sourceTitle, data, downloadId, onModalClose } = + props; + + const { markAsFailed, isMarkingAsFailed, markAsFailedError } = + useMarkAsFailed(id); + + const wasMarkingAsFailed = useRef(isMarkingAsFailed); + + const handleMarkAsFailedPress = useCallback(() => { + markAsFailed(); + }, [markAsFailed]); + + useEffect(() => { + if (wasMarkingAsFailed && !isMarkingAsFailed && !markAsFailedError) { + onModalClose(); + } + }, [wasMarkingAsFailed, isMarkingAsFailed, markAsFailedError, onModalClose]); return ( @@ -74,7 +81,7 @@ function HistoryDetailsModal(props: HistoryDetailsModalProps) { className={styles.markAsFailedButton} kind={kinds.DANGER} isSpinning={isMarkingAsFailed} - onPress={onMarkAsFailedPress} + onPress={handleMarkAsFailedPress} > {translate('MarkAsFailed')} diff --git a/frontend/src/Activity/History/History.tsx b/frontend/src/Activity/History/History.tsx index e153ffa1e..3f5f5899c 100644 --- a/frontend/src/Activity/History/History.tsx +++ b/frontend/src/Activity/History/History.tsx @@ -1,6 +1,9 @@ import React, { useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import AppState from 'App/State/AppState'; +import { + setQueueOption, + setQueueOptions, +} from 'Activity/Queue/queueOptionsStore'; import Alert from 'Components/Alert'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import FilterMenu from 'Components/Menu/FilterMenu'; @@ -13,20 +16,11 @@ 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 usePaging from 'Components/Table/usePaging'; import createEpisodesFetchingSelector from 'Episode/createEpisodesFetchingSelector'; import useCurrentPage from 'Helpers/Hooks/useCurrentPage'; import { align, icons, kinds } from 'Helpers/Props'; import { clearEpisodes, fetchEpisodes } from 'Store/Actions/episodeActions'; import { clearEpisodeFiles } from 'Store/Actions/episodeFileActions'; -import { - clearHistory, - fetchHistory, - gotoHistoryPage, - setHistoryFilter, - setHistorySort, - setHistoryTableOption, -} from 'Store/Actions/historyActions'; import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector'; import HistoryItem from 'typings/History'; import { TableOptionsChangePayload } from 'typings/Table'; @@ -37,100 +31,90 @@ import { } from 'Utilities/pagePopulator'; import translate from 'Utilities/String/translate'; import HistoryFilterModal from './HistoryFilterModal'; +import { useHistoryOptions } from './historyOptionsStore'; import HistoryRow from './HistoryRow'; +import useHistory, { useFilters } from './useHistory'; function History() { - const requestCurrentPage = useCurrentPage(); - const { - isFetching, - isPopulated, - error, - items, - columns, - selectedFilterKey, - filters, - sortKey, - sortDirection, - page, - pageSize, + records, totalPages, totalRecords, - } = useSelector((state: AppState) => state.history); + error, + isFetching, + isFetched, + isLoading, + page, + goToPage, + refetch, + } = useHistory(); + + const { columns, pageSize, sortKey, sortDirection, selectedFilterKey } = + useHistoryOptions(); + + const filters = useFilters(); + + const requestCurrentPage = useCurrentPage(); const { isEpisodesFetching, isEpisodesPopulated, episodesError } = useSelector(createEpisodesFetchingSelector()); const customFilters = useSelector(createCustomFiltersSelector('history')); const dispatch = useDispatch(); - const isFetchingAny = isFetching || isEpisodesFetching; - const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length); + const isFetchingAny = isLoading || isEpisodesFetching; + const isAllPopulated = isFetched && (isEpisodesPopulated || !records.length); const hasError = error || episodesError; - const { - handleFirstPagePress, - handlePreviousPagePress, - handleNextPagePress, - handleLastPagePress, - handlePageSelect, - } = usePaging({ - page, - totalPages, - gotoPage: gotoHistoryPage, - }); - const handleFilterSelect = useCallback( (selectedFilterKey: string | number) => { - dispatch(setHistoryFilter({ selectedFilterKey })); + setQueueOption('selectedFilterKey', selectedFilterKey); }, - [dispatch] + [] ); - const handleSortPress = useCallback( - (sortKey: string) => { - dispatch(setHistorySort({ sortKey })); - }, - [dispatch] - ); + const handleSortPress = useCallback((sortKey: string) => { + setQueueOption('sortKey', sortKey); + }, []); const handleTableOptionChange = useCallback( (payload: TableOptionsChangePayload) => { - dispatch(setHistoryTableOption(payload)); + setQueueOptions(payload); if (payload.pageSize) { - dispatch(gotoHistoryPage({ page: 1 })); + goToPage(1); } }, - [dispatch] + [goToPage] ); - useEffect(() => { - if (requestCurrentPage) { - dispatch(fetchHistory()); - } else { - dispatch(gotoHistoryPage({ page: 1 })); - } + const handleRefreshPress = useCallback(() => { + goToPage(1); + refetch(); + }, [goToPage, refetch]); + useEffect(() => { return () => { - dispatch(clearHistory()); dispatch(clearEpisodes()); dispatch(clearEpisodeFiles()); }; }, [requestCurrentPage, dispatch]); useEffect(() => { - const episodeIds = selectUniqueIds(items, 'episodeId'); + const episodeIds = selectUniqueIds( + records, + 'episodeId' + ); if (episodeIds.length) { dispatch(fetchEpisodes({ episodeIds })); } else { dispatch(clearEpisodes()); } - }, [items, dispatch]); + }, [records, dispatch]); useEffect(() => { const repopulate = () => { - dispatch(fetchHistory()); + refetch(); }; registerPagePopulator(repopulate); @@ -138,7 +122,7 @@ function History() { return () => { unregisterPagePopulator(repopulate); }; - }, [dispatch]); + }, [refetch]); return ( @@ -148,7 +132,7 @@ function History() { label={translate('Refresh')} iconName={icons.REFRESH} isSpinning={isFetching} - onPress={handleFirstPagePress} + onPress={handleRefreshPress} /> @@ -186,12 +170,12 @@ function History() { // If history isPopulated and it's empty show no history found and don't // wait for the episodes to populate because they are never coming. - isPopulated && !hasError && !items.length ? ( + isFetched && !hasError && !records.length ? ( {translate('NoHistoryFound')} ) : null } - {isAllPopulated && !hasError && items.length ? ( + {isAllPopulated && !hasError && records.length ? (
- {items.map((item) => { + {records.map((item) => { return ( ); @@ -215,11 +199,7 @@ function History() { totalPages={totalPages} totalRecords={totalRecords} isFetching={isFetching} - onFirstPagePress={handleFirstPagePress} - onPreviousPagePress={handlePreviousPagePress} - onNextPagePress={handleNextPagePress} - onLastPagePress={handleLastPagePress} - onPageSelect={handlePageSelect} + onPageSelect={goToPage} /> ) : null} diff --git a/frontend/src/Activity/History/HistoryFilterModal.tsx b/frontend/src/Activity/History/HistoryFilterModal.tsx index 34ab3f3a9..3da6bb265 100644 --- a/frontend/src/Activity/History/HistoryFilterModal.tsx +++ b/frontend/src/Activity/History/HistoryFilterModal.tsx @@ -1,48 +1,25 @@ import React, { useCallback } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { createSelector } from 'reselect'; -import AppState from 'App/State/AppState'; import FilterModal, { FilterModalProps } from 'Components/Filter/FilterModal'; -import { setHistoryFilter } from 'Store/Actions/historyActions'; - -function createHistorySelector() { - return createSelector( - (state: AppState) => state.history.items, - (queueItems) => { - return queueItems; - } - ); -} - -function createFilterBuilderPropsSelector() { - return createSelector( - (state: AppState) => state.history.filterBuilderProps, - (filterBuilderProps) => { - return filterBuilderProps; - } - ); -} +import { setHistoryOption } from './historyOptionsStore'; +import useHistory, { FILTER_BUILDER } from './useHistory'; type HistoryFilterModalProps = FilterModalProps; export default function HistoryFilterModal(props: HistoryFilterModalProps) { - const sectionItems = useSelector(createHistorySelector()); - const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); - - const dispatch = useDispatch(); + const { records } = useHistory(); const dispatchSetFilter = useCallback( - (payload: { selectedFilterKey: string | number }) => { - dispatch(setHistoryFilter(payload)); + ({ selectedFilterKey }: { selectedFilterKey: string | number }) => { + setHistoryOption('selectedFilterKey', selectedFilterKey); }, - [dispatch] + [] ); return ( diff --git a/frontend/src/Activity/History/HistoryRow.tsx b/frontend/src/Activity/History/HistoryRow.tsx index d1ba279dc..ad61701ab 100644 --- a/frontend/src/Activity/History/HistoryRow.tsx +++ b/frontend/src/Activity/History/HistoryRow.tsx @@ -1,5 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; +import React, { useCallback, useState } from 'react'; import IconButton from 'Components/Link/IconButton'; import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; @@ -13,13 +12,11 @@ import EpisodeQuality from 'Episode/EpisodeQuality'; import EpisodeTitleLink from 'Episode/EpisodeTitleLink'; import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber'; import useEpisode from 'Episode/useEpisode'; -import usePrevious from 'Helpers/Hooks/usePrevious'; import { icons, tooltipPositions } from 'Helpers/Props'; import Language from 'Language/Language'; import { QualityModel } from 'Quality/Quality'; import SeriesTitleLink from 'Series/SeriesTitleLink'; import useSeries from 'Series/useSeries'; -import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions'; import CustomFormat from 'typings/CustomFormat'; import { HistoryData, HistoryEventType } from 'typings/History'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore'; @@ -61,13 +58,9 @@ function HistoryRow(props: HistoryRowProps) { date, data, downloadId, - isMarkingAsFailed = false, - markAsFailedError, columns, } = props; - const wasMarkingAsFailed = usePrevious(isMarkingAsFailed); - const dispatch = useDispatch(); const series = useSeries(seriesId); const episode = useEpisode(episodeId, 'episodes'); @@ -81,23 +74,6 @@ function HistoryRow(props: HistoryRowProps) { setIsDetailsModalOpen(false); }, [setIsDetailsModalOpen]); - const handleMarkAsFailedPress = useCallback(() => { - dispatch(markAsFailed({ id })); - }, [id, dispatch]); - - useEffect(() => { - if (wasMarkingAsFailed && !isMarkingAsFailed && !markAsFailedError) { - setIsDetailsModalOpen(false); - dispatch(fetchHistory()); - } - }, [ - wasMarkingAsFailed, - isMarkingAsFailed, - markAsFailedError, - setIsDetailsModalOpen, - dispatch, - ]); - if (!series || !episode) { return null; } @@ -254,13 +230,12 @@ function HistoryRow(props: HistoryRowProps) { })} diff --git a/frontend/src/Activity/History/historyOptionsStore.ts b/frontend/src/Activity/History/historyOptionsStore.ts new file mode 100644 index 000000000..251db2d33 --- /dev/null +++ b/frontend/src/Activity/History/historyOptionsStore.ts @@ -0,0 +1,109 @@ +import React from 'react'; +import Icon from 'Components/Icon'; +import { + createOptionsStore, + PageableOptions, +} from 'Helpers/Hooks/useOptionsStore'; +import { icons } from 'Helpers/Props'; +import translate from 'Utilities/String/translate'; + +export type HistoryOptions = PageableOptions; + +const { useOptions, useOption, setOptions, setOption } = + createOptionsStore('history_options', () => { + return { + includeUnknownSeriesItems: true, + pageSize: 20, + selectedFilterKey: 'all', + sortKey: 'time', + sortDirection: 'descending', + columns: [ + { + name: 'eventType', + label: '', + columnLabel: () => translate('EventType'), + isVisible: true, + isModifiable: false, + }, + { + name: 'series.sortTitle', + label: () => translate('Series'), + isSortable: true, + isVisible: true, + }, + { + name: 'episode', + label: () => translate('Episode'), + isVisible: true, + }, + { + name: 'episodes.title', + label: () => translate('EpisodeTitle'), + isVisible: true, + }, + { + name: 'languages', + label: () => translate('Languages'), + isVisible: false, + }, + { + name: 'quality', + label: () => translate('Quality'), + isVisible: true, + }, + { + name: 'customFormats', + label: () => translate('Formats'), + isSortable: false, + isVisible: true, + }, + { + name: 'date', + label: () => translate('Date'), + isSortable: true, + isVisible: true, + }, + { + name: 'downloadClient', + label: () => translate('DownloadClient'), + isVisible: false, + }, + { + name: 'indexer', + label: () => translate('Indexer'), + isVisible: false, + }, + { + name: 'releaseGroup', + label: () => translate('ReleaseGroup'), + isVisible: false, + }, + { + name: 'sourceTitle', + label: () => translate('SourceTitle'), + isVisible: false, + }, + { + name: 'customFormatScore', + columnLabel: () => translate('CustomFormatScore'), + label: React.createElement(Icon, { + name: icons.SCORE, + title: () => translate('CustomFormatScore'), + }), + isVisible: false, + }, + { + name: 'details', + label: '', + columnLabel: () => translate('Details'), + isVisible: true, + isModifiable: false, + }, + ], + }; + }); + +export const useHistoryOptions = useOptions; +export const setHistoryOptions = setOptions; +export const useHistoryOption = useOption; +export const setHistoryOption = setOption; diff --git a/frontend/src/Activity/History/useHistory.ts b/frontend/src/Activity/History/useHistory.ts new file mode 100644 index 000000000..94be4913d --- /dev/null +++ b/frontend/src/Activity/History/useHistory.ts @@ -0,0 +1,186 @@ +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 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'; +import { useHistoryOptions } from './historyOptionsStore'; + +export const FILTERS: Filter[] = [ + { + key: 'all', + label: () => translate('All'), + filters: [], + }, + { + key: 'grabbed', + label: () => translate('Grabbed'), + filters: [ + { + key: 'eventType', + value: '1', + type: 'equal', + }, + ], + }, + { + key: 'imported', + label: () => translate('Imported'), + filters: [ + { + key: 'eventType', + value: '3', + type: 'equal', + }, + ], + }, + { + key: 'failed', + label: () => translate('Failed'), + filters: [ + { + key: 'eventType', + value: '4', + type: 'equal', + }, + ], + }, + { + key: 'deleted', + label: () => translate('Deleted'), + filters: [ + { + key: 'eventType', + value: '5', + type: 'equal', + }, + ], + }, + { + key: 'renamed', + label: () => translate('Renamed'), + filters: [ + { + key: 'eventType', + value: '6', + type: 'equal', + }, + ], + }, + { + key: 'ignored', + label: () => translate('Ignored'), + filters: [ + { + key: 'eventType', + value: '7', + type: 'equal', + }, + ], + }, +]; + +export const FILTER_BUILDER: FilterBuilderProp[] = [ + { + name: 'eventType', + label: () => translate('EventType'), + type: 'equal', + valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE, + }, + { + name: 'seriesIds', + label: () => translate('Series'), + type: 'equal', + valueType: filterBuilderValueTypes.SERIES, + }, + { + name: 'quality', + label: () => translate('Quality'), + type: 'equal', + valueType: filterBuilderValueTypes.QUALITY, + }, + { + name: 'languages', + label: () => translate('Languages'), + type: 'contains', + valueType: filterBuilderValueTypes.LANGUAGE, + }, +]; + +const useHistory = () => { + const { page, goToPage } = usePage('history'); + const { pageSize, selectedFilterKey, sortKey, sortDirection } = + useHistoryOptions(); + const customFilters = useSelector( + createCustomFiltersSelector('history') + ) as CustomFilter[]; + + const filters = useMemo(() => { + return findSelectedFilters(selectedFilterKey, FILTERS, customFilters); + }, [selectedFilterKey, customFilters]); + + const { refetch, ...query } = usePagedApiQuery({ + path: '/history', + page, + pageSize, + filters, + sortKey, + sortDirection, + queryOptions: { + placeholderData: keepPreviousData, + }, + }); + + const handleGoToPage = useCallback( + (page: number) => { + goToPage(page); + }, + [goToPage] + ); + + return { + ...query, + goToPage: handleGoToPage, + page, + refetch, + }; +}; + +export default useHistory; + +export const useFilters = () => { + return FILTERS; +}; + +export const useMarkAsFailed = (id: number) => { + const queryClient = useQueryClient(); + const [error, setError] = useState(null); + + const { mutate, isPending } = useApiMutation({ + path: `/history/failed/${id}`, + method: 'POST', + mutationOptions: { + onMutate: () => { + setError(null); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['/history'] }); + }, + onError: () => { + setError('Error marking history item as failed'); + }, + }, + }); + + return { + markAsFailed: mutate, + isMarkingAsFailed: isPending, + markAsFailedError: error, + }; +}; diff --git a/frontend/src/App/State/AppState.ts b/frontend/src/App/State/AppState.ts index 3af3eb1d9..1b37add0f 100644 --- a/frontend/src/App/State/AppState.ts +++ b/frontend/src/App/State/AppState.ts @@ -90,7 +90,6 @@ interface AppState { episodeHistory: HistoryAppState; episodes: EpisodesAppState; episodesSelection: EpisodesAppState; - history: HistoryAppState; importSeries: ImportSeriesAppState; interactiveImport: InteractiveImportAppState; oAuth: OAuthAppState; diff --git a/frontend/src/Helpers/Hooks/usePage.ts b/frontend/src/Helpers/Hooks/usePage.ts index a0cd86027..84b552632 100644 --- a/frontend/src/Helpers/Hooks/usePage.ts +++ b/frontend/src/Helpers/Hooks/usePage.ts @@ -5,12 +5,14 @@ import { create } from 'zustand'; interface PageStore { blocklist: number; events: number; + history: number; queue: number; } const pageStore = create(() => ({ blocklist: 1, events: 1, + history: 1, queue: 1, })); diff --git a/frontend/src/Store/Actions/historyActions.js b/frontend/src/Store/Actions/historyActions.js deleted file mode 100644 index 45d858249..000000000 --- a/frontend/src/Store/Actions/historyActions.js +++ /dev/null @@ -1,327 +0,0 @@ -import React from 'react'; -import { createAction } from 'redux-actions'; -import Icon from 'Components/Icon'; -import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, icons, sortDirections } from 'Helpers/Props'; -import { createThunk, handleThunks } from 'Store/thunks'; -import createAjaxRequest from 'Utilities/createAjaxRequest'; -import serverSideCollectionHandlers from 'Utilities/State/serverSideCollectionHandlers'; -import translate from 'Utilities/String/translate'; -import { updateItem } from './baseActions'; -import createHandleActions from './Creators/createHandleActions'; -import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers'; -import createClearReducer from './Creators/Reducers/createClearReducer'; -import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer'; - -// -// Variables - -export const section = 'history'; - -// -// State - -export const defaultState = { - isFetching: false, - isPopulated: false, - error: null, - pageSize: 20, - sortKey: 'date', - sortDirection: sortDirections.DESCENDING, - items: [], - - columns: [ - { - name: 'eventType', - columnLabel: () => translate('EventType'), - isVisible: true, - isModifiable: false - }, - { - name: 'series.sortTitle', - label: () => translate('Series'), - isSortable: true, - isVisible: true - }, - { - name: 'episode', - label: () => translate('Episode'), - isVisible: true - }, - { - name: 'episodes.title', - label: () => translate('EpisodeTitle'), - isVisible: true - }, - { - name: 'languages', - label: () => translate('Languages'), - isVisible: false - }, - { - name: 'quality', - label: () => translate('Quality'), - isVisible: true - }, - { - name: 'customFormats', - label: () => translate('Formats'), - isSortable: false, - isVisible: true - }, - { - name: 'date', - label: () => translate('Date'), - isSortable: true, - isVisible: true - }, - { - name: 'downloadClient', - label: () => translate('DownloadClient'), - isVisible: false - }, - { - name: 'indexer', - label: () => translate('Indexer'), - isVisible: false - }, - { - name: 'releaseGroup', - label: () => translate('ReleaseGroup'), - isVisible: false - }, - { - name: 'sourceTitle', - label: () => translate('SourceTitle'), - isVisible: false - }, - { - name: 'customFormatScore', - columnLabel: () => translate('CustomFormatScore'), - label: React.createElement(Icon, { - name: icons.SCORE, - title: () => translate('CustomFormatScore') - }), - isVisible: false - }, - { - name: 'details', - columnLabel: () => translate('Details'), - isVisible: true, - isModifiable: false - } - ], - - selectedFilterKey: 'all', - - filters: [ - { - key: 'all', - label: () => translate('All'), - filters: [] - }, - { - key: 'grabbed', - label: () => translate('Grabbed'), - filters: [ - { - key: 'eventType', - value: '1', - type: filterTypes.EQUAL - } - ] - }, - { - key: 'imported', - label: () => translate('Imported'), - filters: [ - { - key: 'eventType', - value: '3', - type: filterTypes.EQUAL - } - ] - }, - { - key: 'failed', - label: () => translate('Failed'), - filters: [ - { - key: 'eventType', - value: '4', - type: filterTypes.EQUAL - } - ] - }, - { - key: 'deleted', - label: () => translate('Deleted'), - filters: [ - { - key: 'eventType', - value: '5', - type: filterTypes.EQUAL - } - ] - }, - { - key: 'renamed', - label: () => translate('Renamed'), - filters: [ - { - key: 'eventType', - value: '6', - type: filterTypes.EQUAL - } - ] - }, - { - key: 'ignored', - label: () => translate('Ignored'), - filters: [ - { - key: 'eventType', - value: '7', - type: filterTypes.EQUAL - } - ] - } - ], - - filterBuilderProps: [ - { - name: 'eventType', - label: () => translate('EventType'), - type: filterBuilderTypes.EQUAL, - valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE - }, - { - name: 'seriesIds', - label: () => translate('Series'), - type: filterBuilderTypes.EQUAL, - valueType: filterBuilderValueTypes.SERIES - }, - { - name: 'quality', - label: () => translate('Quality'), - type: filterBuilderTypes.EQUAL, - valueType: filterBuilderValueTypes.QUALITY - }, - { - name: 'languages', - label: () => translate('Languages'), - type: filterBuilderTypes.CONTAINS, - valueType: filterBuilderValueTypes.LANGUAGE - } - ] - -}; - -export const persistState = [ - 'history.pageSize', - 'history.sortKey', - 'history.sortDirection', - 'history.selectedFilterKey', - 'history.columns' -]; - -// -// Actions Types - -export const FETCH_HISTORY = 'history/fetchHistory'; -export const GOTO_FIRST_HISTORY_PAGE = 'history/gotoHistoryFirstPage'; -export const GOTO_PREVIOUS_HISTORY_PAGE = 'history/gotoHistoryPreviousPage'; -export const GOTO_NEXT_HISTORY_PAGE = 'history/gotoHistoryNextPage'; -export const GOTO_LAST_HISTORY_PAGE = 'history/gotoHistoryLastPage'; -export const GOTO_HISTORY_PAGE = 'history/gotoHistoryPage'; -export const SET_HISTORY_SORT = 'history/setHistorySort'; -export const SET_HISTORY_FILTER = 'history/setHistoryFilter'; -export const SET_HISTORY_TABLE_OPTION = 'history/setHistoryTableOption'; -export const CLEAR_HISTORY = 'history/clearHistory'; -export const MARK_AS_FAILED = 'history/markAsFailed'; - -// -// Action Creators - -export const fetchHistory = createThunk(FETCH_HISTORY); -export const gotoHistoryFirstPage = createThunk(GOTO_FIRST_HISTORY_PAGE); -export const gotoHistoryPreviousPage = createThunk(GOTO_PREVIOUS_HISTORY_PAGE); -export const gotoHistoryNextPage = createThunk(GOTO_NEXT_HISTORY_PAGE); -export const gotoHistoryLastPage = createThunk(GOTO_LAST_HISTORY_PAGE); -export const gotoHistoryPage = createThunk(GOTO_HISTORY_PAGE); -export const setHistorySort = createThunk(SET_HISTORY_SORT); -export const setHistoryFilter = createThunk(SET_HISTORY_FILTER); -export const setHistoryTableOption = createAction(SET_HISTORY_TABLE_OPTION); -export const clearHistory = createAction(CLEAR_HISTORY); -export const markAsFailed = createThunk(MARK_AS_FAILED); - -// -// Action Handlers - -export const actionHandlers = handleThunks({ - ...createServerSideCollectionHandlers( - section, - '/history', - fetchHistory, - { - [serverSideCollectionHandlers.FETCH]: FETCH_HISTORY, - [serverSideCollectionHandlers.FIRST_PAGE]: GOTO_FIRST_HISTORY_PAGE, - [serverSideCollectionHandlers.PREVIOUS_PAGE]: GOTO_PREVIOUS_HISTORY_PAGE, - [serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_HISTORY_PAGE, - [serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_HISTORY_PAGE, - [serverSideCollectionHandlers.EXACT_PAGE]: GOTO_HISTORY_PAGE, - [serverSideCollectionHandlers.SORT]: SET_HISTORY_SORT, - [serverSideCollectionHandlers.FILTER]: SET_HISTORY_FILTER - }), - - [MARK_AS_FAILED]: function(getState, payload, dispatch) { - const id = payload.id; - - dispatch(updateItem({ - section, - id, - isMarkingAsFailed: true - })); - - const promise = createAjaxRequest({ - url: `/history/failed/${id}`, - method: 'POST', - dataType: 'json' - }).request; - - promise.done(() => { - dispatch(updateItem({ - section, - id, - isMarkingAsFailed: false, - markAsFailedError: null - })); - }); - - promise.fail((xhr) => { - dispatch(updateItem({ - section, - id, - isMarkingAsFailed: false, - markAsFailedError: xhr - })); - }); - } -}); - -// -// Reducers - -export const reducers = createHandleActions({ - - [SET_HISTORY_TABLE_OPTION]: createSetTableOptionReducer(section), - - [CLEAR_HISTORY]: createClearReducer(section, { - isFetching: false, - isPopulated: false, - error: null, - items: [], - totalPages: 0, - totalRecords: 0 - }) - -}, defaultState, section); diff --git a/frontend/src/Store/Actions/index.js b/frontend/src/Store/Actions/index.js index 7c9d34919..25d3bef5b 100644 --- a/frontend/src/Store/Actions/index.js +++ b/frontend/src/Store/Actions/index.js @@ -7,7 +7,6 @@ import * as episodes from './episodeActions'; import * as episodeFiles from './episodeFileActions'; import * as episodeHistory from './episodeHistoryActions'; import * as episodeSelection from './episodeSelectionActions'; -import * as history from './historyActions'; import * as importSeries from './importSeriesActions'; import * as interactiveImportActions from './interactiveImportActions'; import * as oAuth from './oAuthActions'; @@ -35,7 +34,6 @@ export default [ episodeFiles, episodeHistory, episodeSelection, - history, importSeries, interactiveImportActions, oAuth,