- {
- items.map((item, index) => {
- const momentDate = moment(item.sortDate);
- const showDate = index === 0 ||
- !moment(items[index - 1].sortDate).isSame(momentDate, 'day');
-
- return (
-
- );
- })
- }
-
- );
-}
-
-Agenda.propTypes = {
- items: PropTypes.arrayOf(PropTypes.object).isRequired,
- start: PropTypes.string.isRequired,
- end: PropTypes.string.isRequired
-};
-
-export default Agenda;
diff --git a/frontend/src/Calendar/Agenda/Agenda.tsx b/frontend/src/Calendar/Agenda/Agenda.tsx
new file mode 100644
index 0000000000..a4856d2927
--- /dev/null
+++ b/frontend/src/Calendar/Agenda/Agenda.tsx
@@ -0,0 +1,81 @@
+import moment from 'moment';
+import React, { useMemo } from 'react';
+import { useSelector } from 'react-redux';
+import AppState from 'App/State/AppState';
+import Movie from 'Movie/Movie';
+import AgendaEvent from './AgendaEvent';
+import styles from './Agenda.css';
+
+interface AgendaMovie extends Movie {
+ sortDate: moment.Moment;
+}
+
+function Agenda() {
+ const { start, end, items } = useSelector(
+ (state: AppState) => state.calendar
+ );
+
+ const events = useMemo(() => {
+ const result = items.map((item): AgendaMovie => {
+ const { inCinemas, digitalRelease, physicalRelease } = item;
+
+ const dates = [];
+
+ if (inCinemas) {
+ const inCinemasMoment = moment(inCinemas);
+
+ if (inCinemasMoment.isAfter(start) && inCinemasMoment.isBefore(end)) {
+ dates.push(inCinemasMoment);
+ }
+ }
+
+ if (digitalRelease) {
+ const digitalReleaseMoment = moment(digitalRelease);
+
+ if (
+ digitalReleaseMoment.isAfter(start) &&
+ digitalReleaseMoment.isBefore(end)
+ ) {
+ dates.push(digitalReleaseMoment);
+ }
+ }
+
+ if (physicalRelease) {
+ const physicalReleaseMoment = moment(physicalRelease);
+
+ if (
+ physicalReleaseMoment.isAfter(start) &&
+ physicalReleaseMoment.isBefore(end)
+ ) {
+ dates.push(physicalReleaseMoment);
+ }
+ }
+
+ const sortDate = moment.min(...dates);
+
+ return {
+ ...item,
+ sortDate,
+ };
+ });
+
+ result.sort((a, b) => (a.sortDate > b.sortDate ? 1 : -1));
+
+ return result;
+ }, [items, start, end]);
+
+ return (
+
+ {events.map((item, index) => {
+ const momentDate = moment(item.sortDate);
+ const showDate =
+ index === 0 ||
+ !moment(events[index - 1].sortDate).isSame(momentDate, 'day');
+
+ return
;
+ })}
+
+ );
+}
+
+export default Agenda;
diff --git a/frontend/src/Calendar/Agenda/AgendaConnector.js b/frontend/src/Calendar/Agenda/AgendaConnector.js
deleted file mode 100644
index b6f2388736..0000000000
--- a/frontend/src/Calendar/Agenda/AgendaConnector.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-import Agenda from './Agenda';
-
-function createMapStateToProps() {
- return createSelector(
- (state) => state.calendar,
- (calendar) => {
- return calendar;
- }
- );
-}
-
-export default connect(createMapStateToProps)(Agenda);
diff --git a/frontend/src/Calendar/Agenda/AgendaEvent.css b/frontend/src/Calendar/Agenda/AgendaEvent.css
index 28de0b1ca5..8d954fa715 100644
--- a/frontend/src/Calendar/Agenda/AgendaEvent.css
+++ b/frontend/src/Calendar/Agenda/AgendaEvent.css
@@ -53,6 +53,13 @@
margin-right: 10px;
}
+.releaseIcon {
+ margin-right: 20px;
+ width: 25px;
+ cursor: default;
+ pointer-events: all;
+}
+
.statusIcon {
margin-left: 3px;
cursor: default;
@@ -107,8 +114,3 @@
flex: 0 0 100%;
}
}
-
-.releaseIcon {
- margin-right: 20px;
- width: 25px;
-}
diff --git a/frontend/src/Calendar/Agenda/AgendaEvent.js b/frontend/src/Calendar/Agenda/AgendaEvent.js
deleted file mode 100644
index 3d31371c9d..0000000000
--- a/frontend/src/Calendar/Agenda/AgendaEvent.js
+++ /dev/null
@@ -1,190 +0,0 @@
-import classNames from 'classnames';
-import moment from 'moment';
-import PropTypes from 'prop-types';
-import React, { Component } from 'react';
-import CalendarEventQueueDetails from 'Calendar/Events/CalendarEventQueueDetails';
-import getStatusStyle from 'Calendar/getStatusStyle';
-import Icon from 'Components/Icon';
-import Link from 'Components/Link/Link';
-import { icons, kinds } from 'Helpers/Props';
-import translate from 'Utilities/String/translate';
-import styles from './AgendaEvent.css';
-
-class AgendaEvent extends Component {
- //
- // Lifecycle
-
- constructor(props, context) {
- super(props, context);
-
- this.state = {
- isDetailsModalOpen: false
- };
- }
-
- //
- // Listeners
-
- onPress = () => {
- this.setState({ isDetailsModalOpen: true });
- };
-
- onDetailsModalClose = () => {
- this.setState({ isDetailsModalOpen: false });
- };
-
- //
- // Render
-
- render() {
- const {
- movieFile,
- title,
- titleSlug,
- genres,
- isAvailable,
- inCinemas,
- digitalRelease,
- physicalRelease,
- monitored,
- hasFile,
- grabbed,
- queueItem,
- showDate,
- showMovieInformation,
- showCutoffUnmetIcon,
- longDateFormat,
- colorImpairedMode,
- cinemaDateParsed,
- digitalDateParsed,
- physicalDateParsed,
- sortDate
- } = this.props;
-
- let startTime = null;
- let releaseIcon = null;
-
- if (physicalDateParsed === sortDate) {
- startTime = physicalRelease;
- releaseIcon = icons.DISC;
- }
-
- if (digitalDateParsed === sortDate) {
- startTime = digitalRelease;
- releaseIcon = icons.MOVIE_FILE;
- }
-
- if (cinemaDateParsed === sortDate) {
- startTime = inCinemas;
- releaseIcon = icons.IN_CINEMAS;
- }
-
- startTime = moment(startTime);
- const downloading = !!(queueItem || grabbed);
- const isMonitored = monitored;
- const statusStyle = getStatusStyle(hasFile, downloading, isMonitored, isAvailable);
- const joinedGenres = genres.slice(0, 2).join(', ');
- const link = `/movie/${titleSlug}`;
-
- return (
-
- {
- isFetching && !isPopulated &&
-
- }
-
- {
- !isFetching && !!error &&
-
{translate('CalendarLoadError')}
- }
-
- {
- !error && isPopulated && view === calendarViews.AGENDA &&
-
- }
-
- {
- !error && isPopulated && view !== calendarViews.AGENDA &&
-
-
-
-
-
- }
-
- );
- }
-}
-
-Calendar.propTypes = {
- isFetching: PropTypes.bool.isRequired,
- isPopulated: PropTypes.bool.isRequired,
- error: PropTypes.object,
- view: PropTypes.string.isRequired
-};
-
-export default Calendar;
diff --git a/frontend/src/Calendar/Calendar.tsx b/frontend/src/Calendar/Calendar.tsx
new file mode 100644
index 0000000000..dca63c9c07
--- /dev/null
+++ b/frontend/src/Calendar/Calendar.tsx
@@ -0,0 +1,164 @@
+import React, { useCallback, useEffect, useRef } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import AppState from 'App/State/AppState';
+import * as commandNames from 'Commands/commandNames';
+import Alert from 'Components/Alert';
+import LoadingIndicator from 'Components/Loading/LoadingIndicator';
+import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
+import usePrevious from 'Helpers/Hooks/usePrevious';
+import { kinds } from 'Helpers/Props';
+import Movie from 'Movie/Movie';
+import {
+ clearCalendar,
+ fetchCalendar,
+ gotoCalendarToday,
+} from 'Store/Actions/calendarActions';
+import {
+ clearMovieFiles,
+ fetchMovieFiles,
+} from 'Store/Actions/movieFileActions';
+import {
+ clearQueueDetails,
+ fetchQueueDetails,
+} from 'Store/Actions/queueActions';
+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 translate from 'Utilities/String/translate';
+import Agenda from './Agenda/Agenda';
+import CalendarDays from './Day/CalendarDays';
+import DaysOfWeek from './Day/DaysOfWeek';
+import CalendarHeader from './Header/CalendarHeader';
+import styles from './Calendar.css';
+
+const UPDATE_DELAY = 3600000; // 1 hour
+
+function Calendar() {
+ const dispatch = useDispatch();
+ const requestCurrentPage = useCurrentPage();
+ const updateTimeout = useRef