import axios from 'axios';
import moment from 'moment';
+import PropTypes from 'prop-types';
import React from 'react';
-import { Container } from 'react-bootstrap';
+import { Alert, Container } from 'react-bootstrap';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
+import toastr from 'toastr';
import CanonicalLinks from '../common/CanonicalLinks';
import ErrorBoundary from '../common/ErrorBoundary';
+import Filter from '../episodes/Filter';
import List from '../episodes/List';
+import RestreamDialog from '../episodes/RestreamDialog';
+import { withUser } from '../../helpers/UserContext';
-const Schedule = () => {
- const [ahead, setAhead] = React.useState(6);
- const [behind, setBehind] = React.useState(0);
+const Schedule = ({ user }) => {
+ const [ahead] = React.useState(14);
+ const [behind] = React.useState(0);
const [episodes, setEpisodes] = React.useState([]);
+ const [filter, setFilter] = React.useState({});
+ const [restreamChannel, setRestreamChannel] = React.useState(null);
+ const [restreamEpisode, setRestreamEpisode] = React.useState(null);
+ const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
const { t } = useTranslation();
- const fetchEpisodes = React.useCallback((controller, ahead, behind) => {
+ React.useEffect(() => {
+ const savedFilter = localStorage.getItem('episodes.filter.schedule');
+ if (savedFilter) {
+ setFilter(JSON.parse(savedFilter));
+ } else {
+ setFilter(filter => filter ? {} : filter);
+ }
+ }, []);
+
+ const updateFilter = React.useCallback(newFilter => {
+ localStorage.setItem('episodes.filter.schedule', JSON.stringify(newFilter));
+ setFilter(newFilter);
+ }, []);
+
+ const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
axios.get(`/api/episodes`, {
signal: controller.signal,
params: {
after: moment().startOf('day').subtract(behind, 'days').toISOString(),
before: moment().startOf('day').add(ahead + 1, 'days').toISOString(),
+ ...filter,
},
}).then(response => {
setEpisodes(response.data || []);
console.error(e);
}
});
- }, [setEpisodes]);
+ }, []);
+
+ const onAddRestream = React.useCallback(episode => {
+ setRestreamEpisode(episode);
+ setShowRestreamDialog(true);
+ }, []);
+
+ const onAddRestreamSubmit = React.useCallback(async values => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${values.episode_id}/add-restream`, values);
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ toastr.success(t('episodes.restreamDialog.addSuccess'));
+ } catch (e) {
+ toastr.error(t('episodes.restreamDialog.addError'));
+ throw e;
+ }
+ setRestreamEpisode(null);
+ setShowRestreamDialog(false);
+ }, []);
+
+ const onRemoveRestream = React.useCallback(async (episode, channel) => {
+ try {
+ const response = await axios.post(
+ `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
+ const newEpisode = response.data;
+ setEpisodes(episodes => episodes.map(episode =>
+ episode.id === newEpisode.id ? {
+ ...episode,
+ ...newEpisode,
+ } : episode
+ ));
+ toastr.success(t('episodes.restreamDialog.removeSuccess'));
+ setRestreamChannel(null);
+ setRestreamEpisode(null);
+ setShowRestreamDialog(false);
+ } catch (e) {
+ toastr.error(t('episodes.restreamDialog.removeError'));
+ }
+ }, []);
+
+ const onEditRestream = React.useCallback((episode, channel) => {
+ setRestreamChannel(channel);
+ setRestreamEpisode(episode);
+ setShowRestreamDialog(true);
+ }, []);
+
+ const onHideRestreamDialog = React.useCallback(() => {
+ setShowRestreamDialog(false);
+ setRestreamChannel(null);
+ setRestreamEpisode(null);
+ }, []);
React.useEffect(() => {
const controller = new AbortController();
- fetchEpisodes(controller, ahead, behind);
+ fetchEpisodes(controller, ahead, behind, filter);
const timer = setInterval(() => {
- fetchEpisodes(controller, ahead, behind);
- }, 5 * 60 * 1000);
+ fetchEpisodes(controller, ahead, behind, filter);
+ }, 1.5 * 60 * 1000);
return () => {
controller.abort();
clearInterval(timer);
};
- }, [ahead, behind, fetchEpisodes]);
+ }, [ahead, behind, fetchEpisodes, filter]);
return <Container>
<Helmet>
<meta name="description" content={t('schedule.description')} />
</Helmet>
<CanonicalLinks base="/schedule" />
- <h1>{t('schedule.heading')}</h1>
+ <div className="d-flex align-items-start justify-content-between">
+ <h1>{t('schedule.heading')}</h1>
+ <div className="ms-3 mt-5">
+ <Filter filter={filter} setFilter={updateFilter} />
+ </div>
+ </div>
<ErrorBoundary>
- <List episodes={episodes} />
+ {episodes.length ?
+ <List
+ episodes={episodes}
+ onAddRestream={onAddRestream}
+ onEditRestream={onEditRestream}
+ />
+ :
+ <Alert variant="info">
+ {t('episodes.empty')}
+ </Alert>
+ }
</ErrorBoundary>
+ <RestreamDialog
+ channel={restreamChannel}
+ episode={restreamEpisode}
+ onRemoveRestream={onRemoveRestream}
+ onHide={onHideRestreamDialog}
+ onSubmit={onAddRestreamSubmit}
+ show={showRestreamDialog}
+ />
</Container>;
};
-export default Schedule;
+Schedule.propTypes = {
+ user: PropTypes.shape({
+ }),
+};
+
+export default withUser(Schedule);