From c3ee8b3c906b2234eb3c63ae9b56001b0af3a475 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 4 Jan 2025 19:03:01 -0800 Subject: [PATCH] Convert Cutoff Unmet to TypeScript (cherry picked from commit 45c53bea865447aa543242e64e3d796c93117975) --- frontend/src/App/AppRoutes.tsx | 4 +- frontend/src/App/State/AppState.ts | 6 +- frontend/src/App/State/WantedAppState.ts | 29 ++ frontend/src/Movie/MovieSearchCell.tsx | 2 + .../{getFilterValue.js => getFilterValue.ts} | 9 +- .../src/Wanted/CutoffUnmet/CutoffUnmet.js | 292 -------------- .../src/Wanted/CutoffUnmet/CutoffUnmet.tsx | 358 ++++++++++++++++++ .../CutoffUnmet/CutoffUnmetConnector.js | 188 --------- .../src/Wanted/CutoffUnmet/CutoffUnmetRow.js | 171 --------- .../src/Wanted/CutoffUnmet/CutoffUnmetRow.tsx | 150 ++++++++ 10 files changed, 553 insertions(+), 656 deletions(-) create mode 100644 frontend/src/App/State/WantedAppState.ts rename frontend/src/Utilities/Filter/{getFilterValue.js => getFilterValue.ts} (56%) delete mode 100644 frontend/src/Wanted/CutoffUnmet/CutoffUnmet.js create mode 100644 frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx delete mode 100644 frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js delete mode 100644 frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js create mode 100644 frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.tsx diff --git a/frontend/src/App/AppRoutes.tsx b/frontend/src/App/AppRoutes.tsx index 6f660c045c..81dadc8d01 100644 --- a/frontend/src/App/AppRoutes.tsx +++ b/frontend/src/App/AppRoutes.tsx @@ -32,7 +32,7 @@ import Status from 'System/Status/Status'; import Tasks from 'System/Tasks/Tasks'; import Updates from 'System/Updates/Updates'; import getPathWithUrlBase from 'Utilities/getPathWithUrlBase'; -import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector'; +import CutoffUnmet from 'Wanted/CutoffUnmet/CutoffUnmet'; import MissingConnector from 'Wanted/Missing/MissingConnector'; function RedirectWithUrlBase() { @@ -91,7 +91,7 @@ function AppRoutes() { - + {/* Settings diff --git a/frontend/src/App/State/AppState.ts b/frontend/src/App/State/AppState.ts index 778313e6fe..faaff34086 100644 --- a/frontend/src/App/State/AppState.ts +++ b/frontend/src/App/State/AppState.ts @@ -24,6 +24,7 @@ import RootFolderAppState from './RootFolderAppState'; import SettingsAppState from './SettingsAppState'; import SystemAppState from './SystemAppState'; import TagsAppState from './TagsAppState'; +import WantedAppState from './WantedAppState'; interface FilterBuilderPropOption { id: string; @@ -47,14 +48,14 @@ export interface PropertyFilter { export interface Filter { key: string; label: string | (() => string); - filers: PropertyFilter[]; + filters: PropertyFilter[]; } export interface CustomFilter { id: number; type: string; label: string; - filers: PropertyFilter[]; + filters: PropertyFilter[]; } export interface AppSectionState { @@ -105,6 +106,7 @@ interface AppState { settings: SettingsAppState; system: SystemAppState; tags: TagsAppState; + wanted: WantedAppState; } export default AppState; diff --git a/frontend/src/App/State/WantedAppState.ts b/frontend/src/App/State/WantedAppState.ts new file mode 100644 index 0000000000..ef63a8d38f --- /dev/null +++ b/frontend/src/App/State/WantedAppState.ts @@ -0,0 +1,29 @@ +import AppSectionState, { + AppSectionFilterState, + PagedAppSectionState, + TableAppSectionState, +} from 'App/State/AppSectionState'; +import Movie from 'Movie/Movie'; + +interface WantedMovie extends Movie { + isSaving?: boolean; +} + +interface WantedCutoffUnmetAppState + extends AppSectionState, + AppSectionFilterState, + PagedAppSectionState, + TableAppSectionState {} + +interface WantedMissingAppState + extends AppSectionState, + AppSectionFilterState, + PagedAppSectionState, + TableAppSectionState {} + +interface WantedAppState { + cutoffUnmet: WantedCutoffUnmetAppState; + missing: WantedMissingAppState; +} + +export default WantedAppState; diff --git a/frontend/src/Movie/MovieSearchCell.tsx b/frontend/src/Movie/MovieSearchCell.tsx index c2cc5c0ef8..3abd753625 100644 --- a/frontend/src/Movie/MovieSearchCell.tsx +++ b/frontend/src/Movie/MovieSearchCell.tsx @@ -6,6 +6,7 @@ import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import TableRowCell from 'Components/Table/Cells/TableRowCell'; import useModalOpenState from 'Helpers/Hooks/useModalOpenState'; import { icons } from 'Helpers/Props'; +import { MovieEntity } from 'Movie/useMovie'; import { executeCommand } from 'Store/Actions/commandActions'; import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector'; import translate from 'Utilities/String/translate'; @@ -14,6 +15,7 @@ import styles from './MovieSearchCell.css'; interface MovieSearchCellProps { movieId: number; + movieEntity?: MovieEntity; } function MovieSearchCell({ movieId }: MovieSearchCellProps) { diff --git a/frontend/src/Utilities/Filter/getFilterValue.js b/frontend/src/Utilities/Filter/getFilterValue.ts similarity index 56% rename from frontend/src/Utilities/Filter/getFilterValue.js rename to frontend/src/Utilities/Filter/getFilterValue.ts index 70b0b51f1a..a7de501c9e 100644 --- a/frontend/src/Utilities/Filter/getFilterValue.js +++ b/frontend/src/Utilities/Filter/getFilterValue.ts @@ -1,4 +1,11 @@ -export default function getFilterValue(filters, filterKey, filterValueKey, defaultValue) { +import { Filter } from 'App/State/AppState'; + +export default function getFilterValue( + filters: Filter[], + filterKey: string | number, + filterValueKey: string, + defaultValue: T +) { const filter = filters.find((f) => f.key === filterKey); if (!filter) { diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.js deleted file mode 100644 index be913d7bc6..0000000000 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.js +++ /dev/null @@ -1,292 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import Alert from 'Components/Alert'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import FilterMenu from 'Components/Menu/FilterMenu'; -import ConfirmModal from 'Components/Modal/ConfirmModal'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBody from 'Components/Page/PageContentBody'; -import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; -import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; -import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; -import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; -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 { align, icons, kinds } from 'Helpers/Props'; -import getFilterValue from 'Utilities/Filter/getFilterValue'; -import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; -import translate from 'Utilities/String/translate'; -import getSelectedIds from 'Utilities/Table/getSelectedIds'; -import removeOldSelectedState from 'Utilities/Table/removeOldSelectedState'; -import selectAll from 'Utilities/Table/selectAll'; -import toggleSelected from 'Utilities/Table/toggleSelected'; -import CutoffUnmetRow from './CutoffUnmetRow'; - -function getMonitoredValue(props) { - const { - filters, - selectedFilterKey - } = props; - - return getFilterValue(filters, selectedFilterKey, 'monitored', false); -} - -class CutoffUnmet extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - allSelected: false, - allUnselected: false, - lastToggled: null, - selectedState: {}, - isConfirmSearchAllCutoffUnmetModalOpen: false, - isInteractiveImportModalOpen: false - }; - } - - componentDidUpdate(prevProps) { - if (hasDifferentItems(prevProps.items, this.props.items)) { - this.setState((state) => { - return removeOldSelectedState(state, prevProps.items); - }); - } - } - - // - // Control - - getSelectedIds = () => { - return getSelectedIds(this.state.selectedState); - }; - - // - // Listeners - - onFilterMenuItemPress = (filterKey, filterValue) => { - this.props.onFilterSelect(filterKey, filterValue); - }; - - onSelectAllChange = ({ value }) => { - this.setState(selectAll(this.state.selectedState, value)); - }; - - onSelectedChange = ({ id, value, shiftKey = false }) => { - this.setState((state) => { - return toggleSelected(state, this.props.items, id, value, shiftKey); - }); - }; - - onSearchSelectedPress = () => { - const selected = this.getSelectedIds(); - - this.props.onSearchSelectedPress(selected); - }; - - onToggleSelectedPress = () => { - const movieIds = this.getSelectedIds(); - - this.props.batchToggleCutoffUnmetMovies({ - movieIds, - monitored: !getMonitoredValue(this.props) - }); - }; - - onSearchAllCutoffUnmetPress = () => { - this.setState({ isConfirmSearchAllCutoffUnmetModalOpen: true }); - }; - - onSearchAllCutoffUnmetConfirmed = () => { - const { - selectedFilterKey, - onSearchAllCutoffUnmetPress - } = this.props; - - // TODO: Custom filters will need to check whether there is a monitored - // filter once implemented. - - onSearchAllCutoffUnmetPress(selectedFilterKey === 'monitored'); - this.setState({ isConfirmSearchAllCutoffUnmetModalOpen: false }); - }; - - onConfirmSearchAllCutoffUnmetModalClose = () => { - this.setState({ isConfirmSearchAllCutoffUnmetModalOpen: false }); - }; - - // - // Render - - render() { - const { - isFetching, - isPopulated, - error, - items, - selectedFilterKey, - filters, - columns, - totalRecords, - isSearchingForCutoffUnmetMovies, - isSaving, - onFilterSelect, - ...otherProps - } = this.props; - - const { - allSelected, - allUnselected, - selectedState, - isConfirmSearchAllCutoffUnmetModalOpen - } = this.state; - - const itemsSelected = !!this.getSelectedIds().length; - const isShowingMonitored = getMonitoredValue(this.props); - - return ( - - - - - - - - - - - - - - - - - - - - - { - isFetching && !isPopulated && - - } - - { - !isFetching && error && - - {translate('CutoffUnmetLoadError')} - - } - - { - isPopulated && !error && !items.length && - - {translate('CutoffUnmetNoItems')} - - } - - { - isPopulated && !error && !!items.length && -
- - - { - items.map((item) => { - return ( - - ); - }) - } - -
- - - - -
- {translate('SearchForCutoffUnmetMoviesConfirmationCount', { totalRecords })} -
-
- {translate('MassSearchCancelWarning')} -
-
- } - confirmLabel={translate('Search')} - onConfirm={this.onSearchAllCutoffUnmetConfirmed} - onCancel={this.onConfirmSearchAllCutoffUnmetModalClose} - /> - - } -
-
- ); - } -} - -CutoffUnmet.propTypes = { - isFetching: PropTypes.bool.isRequired, - isPopulated: PropTypes.bool.isRequired, - error: PropTypes.object, - items: PropTypes.arrayOf(PropTypes.object).isRequired, - selectedFilterKey: PropTypes.string.isRequired, - filters: PropTypes.arrayOf(PropTypes.object).isRequired, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - totalRecords: PropTypes.number, - isSearchingForCutoffUnmetMovies: PropTypes.bool.isRequired, - isSaving: PropTypes.bool.isRequired, - onFilterSelect: PropTypes.func.isRequired, - onSearchSelectedPress: PropTypes.func.isRequired, - batchToggleCutoffUnmetMovies: PropTypes.func.isRequired, - onSearchAllCutoffUnmetPress: PropTypes.func.isRequired -}; - -export default CutoffUnmet; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx b/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx new file mode 100644 index 0000000000..88e94c442a --- /dev/null +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmet.tsx @@ -0,0 +1,358 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import AppState, { Filter } from 'App/State/AppState'; +import * as commandNames from 'Commands/commandNames'; +import Alert from 'Components/Alert'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import FilterMenu from 'Components/Menu/FilterMenu'; +import ConfirmModal from 'Components/Modal/ConfirmModal'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBody from 'Components/Page/PageContentBody'; +import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; +import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; +import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; +import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; +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 useCurrentPage from 'Helpers/Hooks/useCurrentPage'; +import useSelectState from 'Helpers/Hooks/useSelectState'; +import { align, icons, kinds } from 'Helpers/Props'; +import { executeCommand } from 'Store/Actions/commandActions'; +import { + batchToggleCutoffUnmetMovies, + clearCutoffUnmet, + fetchCutoffUnmet, + gotoCutoffUnmetPage, + setCutoffUnmetFilter, + setCutoffUnmetSort, + setCutoffUnmetTableOption, +} from 'Store/Actions/wantedActions'; +import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; +import { CheckInputChanged } from 'typings/inputs'; +import { SelectStateInputProps } from 'typings/props'; +import { TableOptionsChangePayload } from 'typings/Table'; +import getFilterValue from 'Utilities/Filter/getFilterValue'; +import { + registerPagePopulator, + unregisterPagePopulator, +} from 'Utilities/pagePopulator'; +import translate from 'Utilities/String/translate'; +import getSelectedIds from 'Utilities/Table/getSelectedIds'; +import CutoffUnmetRow from './CutoffUnmetRow'; + +function getMonitoredValue( + filters: Filter[], + selectedFilterKey: string +): boolean { + return !!getFilterValue(filters, selectedFilterKey, 'monitored', false); +} + +function CutoffUnmet() { + const dispatch = useDispatch(); + const requestCurrentPage = useCurrentPage(); + + const { + isFetching, + isPopulated, + error, + items, + columns, + selectedFilterKey, + filters, + sortKey, + sortDirection, + page, + pageSize, + totalPages, + totalRecords = 0, + } = useSelector((state: AppState) => state.wanted.cutoffUnmet); + + const isSearchingForAllMovies = useSelector( + createCommandExecutingSelector(commandNames.CUTOFF_UNMET_MOVIES_SEARCH) + ); + const isSearchingForSelectedMovies = useSelector( + createCommandExecutingSelector(commandNames.MOVIE_SEARCH) + ); + + const [selectState, setSelectState] = useSelectState(); + const { allSelected, allUnselected, selectedState } = selectState; + + const [isConfirmSearchAllModalOpen, setIsConfirmSearchAllModalOpen] = + useState(false); + + const { + handleFirstPagePress, + handlePreviousPagePress, + handleNextPagePress, + handleLastPagePress, + handlePageSelect, + } = usePaging({ + page, + totalPages, + gotoPage: gotoCutoffUnmetPage, + }); + + const selectedIds = useMemo(() => { + return getSelectedIds(selectedState); + }, [selectedState]); + + const isSaving = useMemo(() => { + return items.filter((m) => m.isSaving).length > 1; + }, [items]); + + const itemsSelected = !!selectedIds.length; + const isShowingMonitored = getMonitoredValue(filters, selectedFilterKey); + const isSearchingForMovies = + isSearchingForAllMovies || isSearchingForSelectedMovies; + + const handleSelectAllChange = useCallback( + ({ value }: CheckInputChanged) => { + setSelectState({ type: value ? 'selectAll' : 'unselectAll', items }); + }, + [items, setSelectState] + ); + + const handleSelectedChange = useCallback( + ({ id, value, shiftKey = false }: SelectStateInputProps) => { + setSelectState({ + type: 'toggleSelected', + items, + id, + isSelected: value, + shiftKey, + }); + }, + [items, setSelectState] + ); + + const handleSearchSelectedPress = useCallback(() => { + dispatch( + executeCommand({ + name: commandNames.MOVIE_SEARCH, + movieIds: selectedIds, + commandFinished: () => { + dispatch(fetchCutoffUnmet()); + }, + }) + ); + }, [selectedIds, dispatch]); + + const handleSearchAllPress = useCallback(() => { + setIsConfirmSearchAllModalOpen(true); + }, []); + + const handleConfirmSearchAllCutoffUnmetModalClose = useCallback(() => { + setIsConfirmSearchAllModalOpen(false); + }, []); + + const handleSearchAllCutoffUnmetConfirmed = useCallback(() => { + dispatch( + executeCommand({ + name: commandNames.CUTOFF_UNMET_MOVIES_SEARCH, + commandFinished: () => { + dispatch(fetchCutoffUnmet()); + }, + }) + ); + + setIsConfirmSearchAllModalOpen(false); + }, [dispatch]); + + const handleToggleSelectedPress = useCallback(() => { + dispatch( + batchToggleCutoffUnmetMovies({ + movieIds: selectedIds, + monitored: !isShowingMonitored, + }) + ); + }, [isShowingMonitored, selectedIds, dispatch]); + + const handleFilterSelect = useCallback( + (filterKey: number | string) => { + dispatch(setCutoffUnmetFilter({ selectedFilterKey: filterKey })); + }, + [dispatch] + ); + + const handleSortPress = useCallback( + (sortKey: string) => { + dispatch(setCutoffUnmetSort({ sortKey })); + }, + [dispatch] + ); + + const handleTableOptionChange = useCallback( + (payload: TableOptionsChangePayload) => { + dispatch(setCutoffUnmetTableOption(payload)); + + if (payload.pageSize) { + dispatch(gotoCutoffUnmetPage({ page: 1 })); + } + }, + [dispatch] + ); + + useEffect(() => { + if (requestCurrentPage) { + dispatch(fetchCutoffUnmet()); + } else { + dispatch(gotoCutoffUnmetPage({ page: 1 })); + } + + return () => { + dispatch(clearCutoffUnmet()); + }; + }, [requestCurrentPage, dispatch]); + + useEffect(() => { + const repopulate = () => { + dispatch(fetchCutoffUnmet()); + }; + + registerPagePopulator(repopulate, [ + 'movieUpdated', + 'movieFileUpdated', + 'movieFileDeleted', + ]); + + return () => { + unregisterPagePopulator(repopulate); + }; + }, [dispatch]); + + return ( + + + + + + + + + + + + + + + + + + + + + {isFetching && !isPopulated ? : null} + + {!isFetching && error ? ( + {translate('CutoffUnmetLoadError')} + ) : null} + + {isPopulated && !error && !items.length ? ( + {translate('CutoffUnmetNoItems')} + ) : null} + + {isPopulated && !error && !!items.length ? ( +
+ + + {items.map((item) => { + return ( + + ); + })} + +
+ + + + +
+ {translate('SearchForCutoffUnmetMoviesConfirmationCount', { + totalRecords, + })} +
+
{translate('MassSearchCancelWarning')}
+
+ } + confirmLabel={translate('Search')} + onConfirm={handleSearchAllCutoffUnmetConfirmed} + onCancel={handleConfirmSearchAllCutoffUnmetModalClose} + /> + + ) : null} +
+
+ ); +} + +export default CutoffUnmet; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js deleted file mode 100644 index 410b1a9339..0000000000 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js +++ /dev/null @@ -1,188 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import * as commandNames from 'Commands/commandNames'; -import withCurrentPage from 'Components/withCurrentPage'; -import { executeCommand } from 'Store/Actions/commandActions'; -import { clearMovieFiles, fetchMovieFiles } from 'Store/Actions/movieFileActions'; -import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions'; -import * as wantedActions from 'Store/Actions/wantedActions'; -import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; -import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; -import selectUniqueIds from 'Utilities/Object/selectUniqueIds'; -import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator'; -import CutoffUnmet from './CutoffUnmet'; - -function createMapStateToProps() { - return createSelector( - (state) => state.wanted.cutoffUnmet, - createCommandExecutingSelector(commandNames.CUTOFF_UNMET_MOVIES_SEARCH), - createCommandExecutingSelector(commandNames.MOVIE_SEARCH), - (cutoffUnmet, isSearchingForCutoffUnmetMovies, isSearchingForSelectedCutoffUnmetMovies) => { - return { - isSearchingForCutoffUnmetMovies: isSearchingForCutoffUnmetMovies || isSearchingForSelectedCutoffUnmetMovies, - isSaving: cutoffUnmet.items.filter((m) => m.isSaving).length > 1, - ...cutoffUnmet - }; - } - ); -} - -const mapDispatchToProps = { - ...wantedActions, - executeCommand, - fetchQueueDetails, - clearQueueDetails, - fetchMovieFiles, - clearMovieFiles -}; - -class CutoffUnmetConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - const { - useCurrentPage, - fetchCutoffUnmet, - gotoCutoffUnmetFirstPage - } = this.props; - - registerPagePopulator(this.repopulate, ['movieUpdated', 'movieFileUpdated', 'movieFileDeleted']); - - if (useCurrentPage) { - fetchCutoffUnmet(); - } else { - gotoCutoffUnmetFirstPage(); - } - } - - componentDidUpdate(prevProps) { - if (hasDifferentItems(prevProps.items, this.props.items)) { - const movieIds = selectUniqueIds(this.props.items, 'id'); - const movieFileIds = selectUniqueIds(this.props.items, 'movieFileId'); - - this.props.fetchQueueDetails({ movieIds }); - - if (movieFileIds.length) { - this.props.fetchMovieFiles({ movieFileIds }); - } - } - } - - componentWillUnmount() { - unregisterPagePopulator(this.repopulate); - this.props.clearCutoffUnmet(); - this.props.clearQueueDetails(); - this.props.clearMovieFiles(); - } - - // - // Control - - repopulate = () => { - this.props.fetchCutoffUnmet(); - }; - // - // Listeners - - onFirstPagePress = () => { - this.props.gotoCutoffUnmetFirstPage(); - }; - - onPreviousPagePress = () => { - this.props.gotoCutoffUnmetPreviousPage(); - }; - - onNextPagePress = () => { - this.props.gotoCutoffUnmetNextPage(); - }; - - onLastPagePress = () => { - this.props.gotoCutoffUnmetLastPage(); - }; - - onPageSelect = (page) => { - this.props.gotoCutoffUnmetPage({ page }); - }; - - onSortPress = (sortKey) => { - this.props.setCutoffUnmetSort({ sortKey }); - }; - - onFilterSelect = (selectedFilterKey) => { - this.props.setCutoffUnmetFilter({ selectedFilterKey }); - }; - - onTableOptionChange = (payload) => { - this.props.setCutoffUnmetTableOption(payload); - - if (payload.pageSize) { - this.props.gotoCutoffUnmetFirstPage(); - } - }; - - onSearchSelectedPress = (selected) => { - this.props.executeCommand({ - name: commandNames.MOVIE_SEARCH, - movieIds: selected, - commandFinished: this.repopulate - }); - }; - - onSearchAllCutoffUnmetPress = (monitored) => { - this.props.executeCommand({ - name: commandNames.CUTOFF_UNMET_MOVIES_SEARCH, - monitored, - commandFinished: this.repopulate - }); - }; - - // - // Render - - render() { - return ( - - ); - } -} - -CutoffUnmetConnector.propTypes = { - useCurrentPage: PropTypes.bool.isRequired, - items: PropTypes.arrayOf(PropTypes.object).isRequired, - fetchCutoffUnmet: PropTypes.func.isRequired, - gotoCutoffUnmetFirstPage: PropTypes.func.isRequired, - gotoCutoffUnmetPreviousPage: PropTypes.func.isRequired, - gotoCutoffUnmetNextPage: PropTypes.func.isRequired, - gotoCutoffUnmetLastPage: PropTypes.func.isRequired, - gotoCutoffUnmetPage: PropTypes.func.isRequired, - setCutoffUnmetSort: PropTypes.func.isRequired, - setCutoffUnmetFilter: PropTypes.func.isRequired, - setCutoffUnmetTableOption: PropTypes.func.isRequired, - clearCutoffUnmet: PropTypes.func.isRequired, - executeCommand: PropTypes.func.isRequired, - fetchQueueDetails: PropTypes.func.isRequired, - clearQueueDetails: PropTypes.func.isRequired, - fetchMovieFiles: PropTypes.func.isRequired, - clearMovieFiles: PropTypes.func.isRequired -}; - -export default withCurrentPage( - connect(createMapStateToProps, mapDispatchToProps)(CutoffUnmetConnector) -); diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js deleted file mode 100644 index 99673c5039..0000000000 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js +++ /dev/null @@ -1,171 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell'; -import TableRowCell from 'Components/Table/Cells/TableRowCell'; -import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; -import TableRow from 'Components/Table/TableRow'; -import movieEntities from 'Movie/movieEntities'; -import MovieSearchCell from 'Movie/MovieSearchCell'; -import MovieStatus from 'Movie/MovieStatus'; -import MovieTitleLink from 'Movie/MovieTitleLink'; -import MovieFileLanguages from 'MovieFile/MovieFileLanguages'; -import styles from './CutoffUnmetRow.css'; - -function CutoffUnmetRow(props) { - const { - id, - movieFileId, - year, - title, - titleSlug, - inCinemas, - digitalRelease, - physicalRelease, - lastSearchTime, - isSelected, - columns, - onSelectedChange - } = props; - - return ( - - - - { - columns.map((column) => { - const { - name, - isVisible - } = column; - - if (!isVisible) { - return null; - } - - if (name === 'movieMetadata.sortTitle') { - return ( - - - - ); - } - - if (name === 'movieMetadata.year') { - return ( - - {year} - - ); - } - - if (name === 'movieMetadata.inCinemas') { - return ( - - ); - } - - if (name === 'movieMetadata.digitalRelease') { - return ( - - ); - } - - if (name === 'movieMetadata.physicalRelease') { - return ( - - ); - } - - if (name === 'languages') { - return ( - - - - ); - } - - if (name === 'movies.lastSearchTime') { - return ( - - ); - } - - if (name === 'status') { - return ( - - - - ); - } - - if (name === 'actions') { - return ( - - ); - } - - return null; - }) - } - - ); -} - -CutoffUnmetRow.propTypes = { - id: PropTypes.number.isRequired, - movieFileId: PropTypes.number, - title: PropTypes.string.isRequired, - year: PropTypes.number.isRequired, - lastSearchTime: PropTypes.string, - titleSlug: PropTypes.string.isRequired, - inCinemas: PropTypes.string, - digitalRelease: PropTypes.string, - physicalRelease: PropTypes.string, - isSelected: PropTypes.bool, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - onSelectedChange: PropTypes.func.isRequired -}; - -export default CutoffUnmetRow; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.tsx b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.tsx new file mode 100644 index 0000000000..40480b8c4a --- /dev/null +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.tsx @@ -0,0 +1,150 @@ +import React from 'react'; +import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell'; +import TableRowCell from 'Components/Table/Cells/TableRowCell'; +import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; +import Column from 'Components/Table/Column'; +import TableRow from 'Components/Table/TableRow'; +import MovieSearchCell from 'Movie/MovieSearchCell'; +import MovieStatus from 'Movie/MovieStatus'; +import MovieTitleLink from 'Movie/MovieTitleLink'; +import MovieFileLanguages from 'MovieFile/MovieFileLanguages'; +import { SelectStateInputProps } from 'typings/props'; +import styles from './CutoffUnmetRow.css'; + +interface CutoffUnmetRowProps { + id: number; + movieFileId?: number; + inCinemas?: string; + digitalRelease?: string; + physicalRelease?: string; + lastSearchTime?: string; + title: string; + year: number; + titleSlug: string; + isSelected?: boolean; + columns: Column[]; + onSelectedChange: (options: SelectStateInputProps) => void; +} + +function CutoffUnmetRow({ + id, + movieFileId, + inCinemas, + digitalRelease, + physicalRelease, + lastSearchTime, + title, + year, + titleSlug, + isSelected, + columns, + onSelectedChange, +}: CutoffUnmetRowProps) { + if (!movieFileId) { + return null; + } + + return ( + + + + {columns.map((column) => { + const { name, isVisible } = column; + + if (!isVisible) { + return null; + } + + if (name === 'movieMetadata.sortTitle') { + return ( + + + + ); + } + + if (name === 'movieMetadata.year') { + return {year}; + } + + if (name === 'movieMetadata.inCinemas') { + return ( + + ); + } + + if (name === 'movieMetadata.digitalRelease') { + return ( + + ); + } + + if (name === 'movieMetadata.physicalRelease') { + return ( + + ); + } + + if (name === 'languages') { + return ( + + + + ); + } + + if (name === 'movies.lastSearchTime') { + return ( + + ); + } + + if (name === 'status') { + return ( + + + + ); + } + + if (name === 'actions') { + return ( + + ); + } + + return null; + })} + + ); +} + +export default CutoffUnmetRow;