diff --git a/frontend/src/Activity/Blocklist/Blocklist.tsx b/frontend/src/Activity/Blocklist/Blocklist.tsx index c60dd9c93..7528b66a4 100644 --- a/frontend/src/Activity/Blocklist/Blocklist.tsx +++ b/frontend/src/Activity/Blocklist/Blocklist.tsx @@ -17,6 +17,7 @@ import TableBody from 'Components/Table/TableBody'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import TablePager from 'Components/Table/TablePager'; 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'; @@ -31,6 +32,7 @@ import translate from 'Utilities/String/translate'; import BlocklistFilterModal from './BlocklistFilterModal'; import { setBlocklistOption, + setBlocklistSort, useBlocklistOptions, } from './blocklistOptionsStore'; import BlocklistRow from './BlocklistRow'; @@ -129,9 +131,15 @@ function BlocklistContent() { [] ); - const handleSortPress = useCallback((sortKey: string) => { - setBlocklistOption('sortKey', sortKey); - }, []); + const handleSortPress = useCallback( + (sortKey: string, sortDirection?: SortDirection) => { + setBlocklistSort({ + sortKey, + sortDirection, + }); + }, + [] + ); const handleTableOptionChange = useCallback( (payload: TableOptionsChangePayload) => { diff --git a/frontend/src/Activity/Blocklist/blocklistOptionsStore.ts b/frontend/src/Activity/Blocklist/blocklistOptionsStore.ts index 75631b9fe..a832e3ce3 100644 --- a/frontend/src/Activity/Blocklist/blocklistOptionsStore.ts +++ b/frontend/src/Activity/Blocklist/blocklistOptionsStore.ts @@ -6,7 +6,7 @@ import translate from 'Utilities/String/translate'; export type BlocklistOptions = PageableOptions; -const { useOptions, useOption, setOptions, setOption } = +const { useOptions, useOption, setOptions, setOption, setSort } = createOptionsStore('blocklist_options', () => { return { pageSize: 20, @@ -69,3 +69,4 @@ export const useBlocklistOptions = useOptions; export const setBlocklistOptions = setOptions; export const useBlocklistOption = useOption; export const setBlocklistOption = setOption; +export const setBlocklistSort = setSort; diff --git a/frontend/src/Activity/History/History.tsx b/frontend/src/Activity/History/History.tsx index 3f5f5899c..608f07a6e 100644 --- a/frontend/src/Activity/History/History.tsx +++ b/frontend/src/Activity/History/History.tsx @@ -1,9 +1,5 @@ import React, { useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { - setQueueOption, - setQueueOptions, -} from 'Activity/Queue/queueOptionsStore'; import Alert from 'Components/Alert'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import FilterMenu from 'Components/Menu/FilterMenu'; @@ -19,6 +15,7 @@ import TablePager from 'Components/Table/TablePager'; import createEpisodesFetchingSelector from 'Episode/createEpisodesFetchingSelector'; 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 { clearEpisodeFiles } from 'Store/Actions/episodeFileActions'; import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector'; @@ -31,7 +28,12 @@ import { } from 'Utilities/pagePopulator'; import translate from 'Utilities/String/translate'; import HistoryFilterModal from './HistoryFilterModal'; -import { useHistoryOptions } from './historyOptionsStore'; +import { + setHistoryOption, + setHistoryOptions, + setHistorySort, + useHistoryOptions, +} from './historyOptionsStore'; import HistoryRow from './HistoryRow'; import useHistory, { useFilters } from './useHistory'; @@ -67,18 +69,24 @@ function History() { const handleFilterSelect = useCallback( (selectedFilterKey: string | number) => { - setQueueOption('selectedFilterKey', selectedFilterKey); + setHistoryOption('selectedFilterKey', selectedFilterKey); }, [] ); - const handleSortPress = useCallback((sortKey: string) => { - setQueueOption('sortKey', sortKey); - }, []); + const handleSortPress = useCallback( + (sortKey: string, sortDirection?: SortDirection) => { + setHistorySort({ + sortKey, + sortDirection, + }); + }, + [] + ); const handleTableOptionChange = useCallback( (payload: TableOptionsChangePayload) => { - setQueueOptions(payload); + setHistoryOptions(payload); if (payload.pageSize) { goToPage(1); diff --git a/frontend/src/Activity/History/historyOptionsStore.ts b/frontend/src/Activity/History/historyOptionsStore.ts index 251db2d33..9f49b32ee 100644 --- a/frontend/src/Activity/History/historyOptionsStore.ts +++ b/frontend/src/Activity/History/historyOptionsStore.ts @@ -9,7 +9,7 @@ import translate from 'Utilities/String/translate'; export type HistoryOptions = PageableOptions; -const { useOptions, useOption, setOptions, setOption } = +const { useOptions, useOption, setOptions, setOption, setSort } = createOptionsStore('history_options', () => { return { includeUnknownSeriesItems: true, @@ -107,3 +107,4 @@ export const useHistoryOptions = useOptions; export const setHistoryOptions = setOptions; export const useHistoryOption = useOption; export const setHistoryOption = setOption; +export const setHistorySort = setSort; diff --git a/frontend/src/Activity/Queue/Queue.tsx b/frontend/src/Activity/Queue/Queue.tsx index 833c1bb4d..dc5a5a057 100644 --- a/frontend/src/Activity/Queue/Queue.tsx +++ b/frontend/src/Activity/Queue/Queue.tsx @@ -24,6 +24,7 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions import TablePager from 'Components/Table/TablePager'; import createEpisodesFetchingSelector from 'Episode/createEpisodesFetchingSelector'; 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'; @@ -42,6 +43,7 @@ import QueueOptions from './QueueOptions'; import { setQueueOption, setQueueOptions, + setQueueSort, useQueueOptions, } from './queueOptionsStore'; import QueueRow from './QueueRow'; @@ -164,9 +166,15 @@ function QueueContent() { [] ); - const handleSortPress = useCallback((sortKey: string) => { - setQueueOption('sortKey', sortKey); - }, []); + const handleSortPress = useCallback( + (sortKey: string, sortDirection?: SortDirection) => { + setQueueSort({ + sortKey, + sortDirection, + }); + }, + [] + ); const handleTableOptionChange = useCallback( (payload: TableOptionsChangePayload) => { diff --git a/frontend/src/Activity/Queue/queueOptionsStore.ts b/frontend/src/Activity/Queue/queueOptionsStore.ts index 284cee7bf..b440cf7fa 100644 --- a/frontend/src/Activity/Queue/queueOptionsStore.ts +++ b/frontend/src/Activity/Queue/queueOptionsStore.ts @@ -17,7 +17,7 @@ export interface QueueOptions extends PageableOptions { removalOptions: QueueRemovalOptions; } -const { useOptions, useOption, setOptions, setOption } = +const { useOptions, useOption, setOptions, setOption, setSort } = createOptionsStore('queue_options', () => { return { includeUnknownSeriesItems: true, @@ -158,3 +158,4 @@ export const useQueueOptions = useOptions; export const setQueueOptions = setOptions; export const useQueueOption = useOption; export const setQueueOption = setOption; +export const setQueueSort = setSort; diff --git a/frontend/src/Helpers/Hooks/useOptionsStore.ts b/frontend/src/Helpers/Hooks/useOptionsStore.ts index 9f4285298..c9a6b768b 100644 --- a/frontend/src/Helpers/Hooks/useOptionsStore.ts +++ b/frontend/src/Helpers/Hooks/useOptionsStore.ts @@ -8,9 +8,12 @@ type TSettingsWithoutColumns = object; interface TSettingsWithColumns { columns: Column[]; + selectedFilterKey: string | number; + sortKey: string; + sortDirection: SortDirection; } -type TSettingd = TSettingsWithoutColumns | TSettingsWithColumns; +type TSettings = TSettingsWithoutColumns | TSettingsWithColumns; export interface PageableOptions { pageSize: number; @@ -25,7 +28,7 @@ export type OptionChanged = { value: T[keyof T]; }; -export const createOptionsStore = ( +export const createOptionsStore = ( name: string, state: StateCreator, options: Omit, 'name' | 'storage'> = {} @@ -52,6 +55,10 @@ export const createOptionsStore = ( }; const setOptions = (options: Partial) => { + if ('sortKey' in options || 'sortDirection' in options) { + throw new Error('Use setSort to set sortKey and sortDirection'); + } + store.setState((state) => ({ ...state, ...options, @@ -59,12 +66,47 @@ export const createOptionsStore = ( }; const setOption = (key: K, value: T[K]) => { + if (key === 'sortKey' || key === 'sortDirection') { + throw new Error('Use setSort to set sortKey and sortDirection'); + } + store.setState((state) => ({ ...state, [key]: value, })); }; + const setSort = ({ + sortKey, + sortDirection, + }: { + sortKey: string; + sortDirection: SortDirection | undefined; + }) => { + // @ts-expect-error - Cannot verify if T has sortKey and sortDirection + store.setState((state) => { + if ('sortKey' in state === false || 'sortDirection' in state === false) { + return state; + } + + let newSortDirection = sortDirection; + + if (sortDirection == null) { + if (state.sortKey === sortKey) { + newSortDirection = + state.sortDirection === 'ascending' ? 'descending' : 'ascending'; + } else { + newSortDirection = state.sortDirection; + } + } + + return { + sortKey, + sortDirection: newSortDirection, + }; + }); + }; + return { store, useOptions, @@ -73,10 +115,11 @@ export const createOptionsStore = ( getOption, setOptions, setOption, + setSort, }; }; -const merge = ( +const merge = ( persistedState: unknown, currentState: T ) => { diff --git a/frontend/src/System/Events/LogsTable.tsx b/frontend/src/System/Events/LogsTable.tsx index 4d7183e46..8d5a5511c 100644 --- a/frontend/src/System/Events/LogsTable.tsx +++ b/frontend/src/System/Events/LogsTable.tsx @@ -14,6 +14,7 @@ import TableBody from 'Components/Table/TableBody'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import TablePager from 'Components/Table/TablePager'; import { align, icons, kinds } from 'Helpers/Props'; +import { SortDirection } from 'Helpers/Props/sortDirections'; import { executeCommand } from 'Store/Actions/commandActions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import { TableOptionsChangePayload } from 'typings/Table'; @@ -21,6 +22,7 @@ import translate from 'Utilities/String/translate'; import { setEventOption, setEventOptions, + setEventSort, useEventOptions, } from './eventOptionsStore'; import LogsTableRow from './LogsTableRow'; @@ -56,9 +58,15 @@ function LogsTable() { [] ); - const handleSortPress = useCallback((sortKey: string) => { - setEventOption('sortKey', sortKey); - }, []); + const handleSortPress = useCallback( + (sortKey: string, sortDirection?: SortDirection) => { + setEventSort({ + sortKey, + sortDirection, + }); + }, + [] + ); const handleTableOptionChange = useCallback( (payload: TableOptionsChangePayload) => { diff --git a/frontend/src/System/Events/eventOptionsStore.tsx b/frontend/src/System/Events/eventOptionsStore.tsx index fc6307280..762844632 100644 --- a/frontend/src/System/Events/eventOptionsStore.tsx +++ b/frontend/src/System/Events/eventOptionsStore.tsx @@ -6,9 +6,8 @@ import translate from 'Utilities/String/translate'; export type EventOptions = PageableOptions; -const { useOptions, setOptions, setOption } = createOptionsStore( - 'event_options', - () => { +const { useOptions, setOptions, setOption, setSort } = + createOptionsStore('event_options', () => { return { pageSize: 50, selectedFilterKey: 'all', @@ -52,9 +51,9 @@ const { useOptions, setOptions, setOption } = createOptionsStore( }, ], }; - } -); + }); export const useEventOptions = useOptions; export const setEventOptions = setOptions; export const setEventOption = setOption; +export const setEventSort = setSort; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx b/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx index 76a2a2ca2..de1761d41 100644 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx @@ -21,6 +21,7 @@ import TablePager from 'Components/Table/TablePager'; import Episode from 'Episode/Episode'; import { useToggleEpisodesMonitored } from 'Episode/useEpisode'; import { align, icons, kinds } from 'Helpers/Props'; +import { SortDirection } from 'Helpers/Props/sortDirections'; import { executeCommand } from 'Store/Actions/commandActions'; import { fetchEpisodeFiles } from 'Store/Actions/episodeFileActions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; @@ -36,6 +37,7 @@ import translate from 'Utilities/String/translate'; import { setCutoffUnmetOption, setCutoffUnmetOptions, + setCutoffUnmetSort, useCutoffUnmetOptions, } from './cutoffUnmetOptionsStore'; import CutoffUnmetRow from './CutoffUnmetRow'; @@ -156,9 +158,15 @@ function CutoffUnmetContent() { setCutoffUnmetOption('selectedFilterKey', filterKey); }, []); - const handleSortPress = useCallback((sortKey: string) => { - setCutoffUnmetOption('sortKey', sortKey); - }, []); + const handleSortPress = useCallback( + (sortKey: string, sortDirection?: SortDirection) => { + setCutoffUnmetSort({ + sortKey, + sortDirection, + }); + }, + [] + ); const handleTableOptionChange = useCallback( (payload: TableOptionsChangePayload) => { diff --git a/frontend/src/Wanted/CutoffUnmet/cutoffUnmetOptionsStore.ts b/frontend/src/Wanted/CutoffUnmet/cutoffUnmetOptionsStore.ts index 79c725d19..4ddcd7e7a 100644 --- a/frontend/src/Wanted/CutoffUnmet/cutoffUnmetOptionsStore.ts +++ b/frontend/src/Wanted/CutoffUnmet/cutoffUnmetOptionsStore.ts @@ -4,7 +4,7 @@ import { } from 'Helpers/Hooks/useOptionsStore'; import translate from 'Utilities/String/translate'; -const { useOptions, useOption, setOptions, setOption } = +const { useOptions, useOption, setOptions, setOption, setSort } = createOptionsStore('cutoffUnmet_options', () => { return { pageSize: 20, @@ -65,3 +65,4 @@ export const useCutoffUnmetOptions = useOptions; export const setCutoffUnmetOptions = setOptions; export const useCutoffUnmetOption = useOption; export const setCutoffUnmetOption = setOption; +export const setCutoffUnmetSort = setSort; diff --git a/frontend/src/Wanted/Missing/Missing.tsx b/frontend/src/Wanted/Missing/Missing.tsx index ad39333e3..252833ce5 100644 --- a/frontend/src/Wanted/Missing/Missing.tsx +++ b/frontend/src/Wanted/Missing/Missing.tsx @@ -21,6 +21,7 @@ import TablePager from 'Components/Table/TablePager'; import Episode from 'Episode/Episode'; import { useToggleEpisodesMonitored } from 'Episode/useEpisode'; import { align, icons, kinds } from 'Helpers/Props'; +import { SortDirection } from 'Helpers/Props/sortDirections'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; import { executeCommand } from 'Store/Actions/commandActions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; @@ -36,6 +37,7 @@ import translate from 'Utilities/String/translate'; import { setMissingOption, setMissingOptions, + setMissingSort, useMissingOptions, } from './missingOptionsStore'; import MissingRow from './MissingRow'; @@ -163,9 +165,15 @@ function MissingContent() { setMissingOption('selectedFilterKey', filterKey); }, []); - const handleSortPress = useCallback((sortKey: string) => { - setMissingOption('sortKey', sortKey); - }, []); + const handleSortPress = useCallback( + (sortKey: string, sortDirection?: SortDirection) => { + setMissingSort({ + sortKey, + sortDirection, + }); + }, + [] + ); const handleTableOptionChange = useCallback( (payload: TableOptionsChangePayload) => { diff --git a/frontend/src/Wanted/Missing/missingOptionsStore.ts b/frontend/src/Wanted/Missing/missingOptionsStore.ts index e6dae041e..139f747e8 100644 --- a/frontend/src/Wanted/Missing/missingOptionsStore.ts +++ b/frontend/src/Wanted/Missing/missingOptionsStore.ts @@ -4,7 +4,7 @@ import { } from 'Helpers/Hooks/useOptionsStore'; import translate from 'Utilities/String/translate'; -const { useOptions, useOption, setOptions, setOption } = +const { useOptions, useOption, setOptions, setOption, setSort } = createOptionsStore('missing_options', () => { return { pageSize: 20, @@ -60,3 +60,4 @@ export const useMissingOptions = useOptions; export const setMissingOptions = setOptions; export const useMissingOption = useOption; export const setMissingOption = setOption; +export const setMissingSort = setSort;