1 import axios from 'axios';
2 import moment from 'moment';
3 import PropTypes from 'prop-types';
4 import React from 'react';
5 import { Alert, Container } from 'react-bootstrap';
6 import { Helmet } from 'react-helmet';
7 import { useTranslation } from 'react-i18next';
8 import toastr from 'toastr';
10 import CanonicalLinks from '../common/CanonicalLinks';
11 import ErrorBoundary from '../common/ErrorBoundary';
12 import Filter from '../episodes/Filter';
13 import List from '../episodes/List';
14 import RestreamDialog from '../episodes/RestreamDialog';
15 import { withUser } from '../../helpers/UserContext';
17 const Schedule = ({ user }) => {
18 const [ahead] = React.useState(14);
19 const [behind] = React.useState(0);
20 const [episodes, setEpisodes] = React.useState([]);
21 const [filter, setFilter] = React.useState({});
22 const [restreamChannel, setRestreamChannel] = React.useState(null);
23 const [restreamEpisode, setRestreamEpisode] = React.useState(null);
24 const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
26 const { t } = useTranslation();
28 React.useEffect(() => {
29 const savedFilter = localStorage.getItem('episodes.filter.schedule');
31 setFilter(JSON.parse(savedFilter));
33 setFilter(filter => filter ? {} : filter);
37 const updateFilter = React.useCallback(newFilter => {
38 localStorage.setItem('episodes.filter.schedule', JSON.stringify(newFilter));
42 const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
43 axios.get(`/api/episodes`, {
44 signal: controller.signal,
46 after: moment().startOf('day').subtract(behind, 'days').toISOString(),
47 before: moment().startOf('day').add(ahead + 1, 'days').toISOString(),
51 setEpisodes(response.data || []);
53 if (!axios.isCancel(e)) {
59 const onAddRestream = React.useCallback(episode => {
60 setRestreamEpisode(episode);
61 setShowRestreamDialog(true);
64 const onAddRestreamSubmit = React.useCallback(async values => {
66 const response = await axios.post(
67 `/api/episodes/${values.episode_id}/add-restream`, values);
68 const newEpisode = response.data;
69 setEpisodes(episodes => episodes.map(episode =>
70 episode.id === newEpisode.id ? {
75 toastr.success(t('episodes.restreamDialog.addSuccess'));
77 toastr.error(t('episodes.restreamDialog.addError'));
80 setRestreamEpisode(null);
81 setShowRestreamDialog(false);
84 const onRemoveRestream = React.useCallback(async (episode, channel) => {
86 const response = await axios.post(
87 `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
88 const newEpisode = response.data;
89 setEpisodes(episodes => episodes.map(episode =>
90 episode.id === newEpisode.id ? {
95 toastr.success(t('episodes.restreamDialog.removeSuccess'));
96 setRestreamChannel(null);
97 setRestreamEpisode(null);
98 setShowRestreamDialog(false);
100 toastr.error(t('episodes.restreamDialog.removeError'));
104 const onEditRestream = React.useCallback((episode, channel) => {
105 setRestreamChannel(channel);
106 setRestreamEpisode(episode);
107 setShowRestreamDialog(true);
110 const onHideRestreamDialog = React.useCallback(() => {
111 setShowRestreamDialog(false);
112 setRestreamChannel(null);
113 setRestreamEpisode(null);
116 React.useEffect(() => {
117 const controller = new AbortController();
118 fetchEpisodes(controller, ahead, behind, filter);
119 const timer = setInterval(() => {
120 fetchEpisodes(controller, ahead, behind, filter);
124 clearInterval(timer);
126 }, [ahead, behind, fetchEpisodes, filter]);
130 <title>{t('schedule.heading')}</title>
131 <meta name="description" content={t('schedule.description')} />
133 <CanonicalLinks base="/schedule" />
134 <div className="d-flex align-items-start justify-content-between">
135 <h1>{t('schedule.heading')}</h1>
136 <div className="ms-3 mt-5">
137 <Filter filter={filter} setFilter={updateFilter} />
144 onAddRestream={onAddRestream}
145 onEditRestream={onEditRestream}
148 <Alert variant="info">
149 {t('episodes.empty')}
154 channel={restreamChannel}
155 episode={restreamEpisode}
156 onRemoveRestream={onRemoveRestream}
157 onHide={onHideRestreamDialog}
158 onSubmit={onAddRestreamSubmit}
159 show={showRestreamDialog}
164 Schedule.propTypes = {
165 user: PropTypes.shape({
169 export default withUser(Schedule);