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 ApplyDialog from '../episodes/ApplyDialog';
13 import Filter from '../episodes/Filter';
14 import List from '../episodes/List';
15 import RestreamDialog from '../episodes/RestreamDialog';
16 import { withUser } from '../../helpers/UserContext';
18 const Schedule = ({ user }) => {
19 const [ahead] = React.useState(14);
20 const [applyAs, setApplyAs] = React.useState('commentary');
21 const [behind] = React.useState(0);
22 const [episodes, setEpisodes] = React.useState([]);
23 const [filter, setFilter] = React.useState({});
24 const [restreamChannel, setRestreamChannel] = React.useState(null);
25 const [restreamEpisode, setRestreamEpisode] = React.useState(null);
26 const [showApplyDialog, setShowApplyDialog] = React.useState(false);
27 const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
29 const { t } = useTranslation();
31 React.useEffect(() => {
32 const savedFilter = localStorage.getItem('episodes.filter.schedule');
34 setFilter(JSON.parse(savedFilter));
36 setFilter(filter => filter ? {} : filter);
40 const updateFilter = React.useCallback(newFilter => {
41 localStorage.setItem('episodes.filter.schedule', JSON.stringify(newFilter));
45 const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
46 axios.get(`/api/episodes`, {
47 signal: controller.signal,
49 after: moment().startOf('day').subtract(behind, 'days').toISOString(),
50 before: moment().startOf('day').add(ahead + 1, 'days').toISOString(),
54 setEpisodes(response.data || []);
56 if (!axios.isCancel(e)) {
62 const onAddRestream = React.useCallback(episode => {
63 setRestreamEpisode(episode);
64 setShowRestreamDialog(true);
67 const onAddRestreamSubmit = React.useCallback(async values => {
69 const response = await axios.post(
70 `/api/episodes/${values.episode_id}/add-restream`, values);
71 const newEpisode = response.data;
72 setEpisodes(episodes => episodes.map(episode =>
73 episode.id === newEpisode.id ? {
78 toastr.success(t('episodes.restreamDialog.addSuccess'));
80 toastr.error(t('episodes.restreamDialog.addError'));
83 setRestreamEpisode(null);
84 setShowRestreamDialog(false);
87 const onRemoveRestream = React.useCallback(async (episode, channel) => {
89 const response = await axios.post(
90 `/api/episodes/${episode.id}/remove-restream`, { channel_id: channel.id });
91 const newEpisode = response.data;
92 setEpisodes(episodes => episodes.map(episode =>
93 episode.id === newEpisode.id ? {
98 toastr.success(t('episodes.restreamDialog.removeSuccess'));
99 setRestreamChannel(null);
100 setRestreamEpisode(null);
101 setShowRestreamDialog(false);
103 toastr.error(t('episodes.restreamDialog.removeError'));
107 const onEditRestream = React.useCallback((episode, channel) => {
108 setRestreamChannel(channel);
109 setRestreamEpisode(episode);
110 setShowRestreamDialog(true);
113 const editRestream = React.useCallback(async values => {
115 const response = await axios.post(
116 `/api/episodes/${values.episode_id}/edit-restream`, values);
117 const newEpisode = response.data;
118 setEpisodes(episodes => episodes.map(episode =>
119 episode.id === newEpisode.id ? {
124 setRestreamEpisode(episode => ({
128 const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
129 setRestreamChannel(channel => ({
133 toastr.success(t('episodes.restreamDialog.editSuccess'));
135 toastr.error(t('episodes.restreamDialog.editError'));
139 const manageCrew = React.useCallback(async values => {
141 const response = await axios.post(
142 `/api/episodes/${values.episode_id}/crew-manage`, values);
143 const newEpisode = response.data;
144 setEpisodes(episodes => episodes.map(episode =>
145 episode.id === newEpisode.id ? {
150 setRestreamEpisode(episode => ({
154 const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
155 setRestreamChannel(channel => ({
159 toastr.success(t('episodes.restreamDialog.crewSuccess'));
161 toastr.error(t('episodes.restreamDialog.crewError'));
165 const onHideRestreamDialog = React.useCallback(() => {
166 setShowRestreamDialog(false);
167 setRestreamChannel(null);
168 setRestreamEpisode(null);
171 const onApply = React.useCallback((episode, as) => {
172 setShowApplyDialog(true);
173 setRestreamEpisode(episode);
177 const onSubmitApplyDialog = React.useCallback(async values => {
179 const response = await axios.post(
180 `/api/episodes/${values.episode_id}/crew-signup`, values);
181 const newEpisode = response.data;
182 setEpisodes(episodes => episodes.map(episode =>
183 episode.id === newEpisode.id ? {
188 toastr.success(t('episodes.applyDialog.applySuccess'));
190 toastr.error(t('episodes.applyDialog.applyError'));
193 setRestreamEpisode(null);
194 setShowApplyDialog(false);
197 const onHideApplyDialog = React.useCallback(() => {
198 setShowApplyDialog(false);
199 setRestreamEpisode(null);
202 React.useEffect(() => {
203 const controller = new AbortController();
204 fetchEpisodes(controller, ahead, behind, filter);
205 const timer = setInterval(() => {
206 fetchEpisodes(controller, ahead, behind, filter);
210 clearInterval(timer);
212 }, [ahead, behind, fetchEpisodes, filter]);
216 <title>{t('schedule.heading')}</title>
217 <meta name="description" content={t('schedule.description')} />
219 <CanonicalLinks base="/schedule" />
220 <div className="d-flex align-items-start justify-content-between">
221 <h1>{t('schedule.heading')}</h1>
222 <div className="ms-3 mt-5">
223 <Filter filter={filter} setFilter={updateFilter} />
230 onAddRestream={onAddRestream}
232 onEditRestream={onEditRestream}
235 <Alert variant="info">
236 {t('episodes.empty')}
243 episode={restreamEpisode}
244 onHide={onHideApplyDialog}
245 onSubmit={onSubmitApplyDialog}
246 show={showApplyDialog}
249 channel={restreamChannel}
250 editRestream={editRestream}
251 episode={restreamEpisode}
252 manageCrew={manageCrew}
253 onRemoveRestream={onRemoveRestream}
254 onHide={onHideRestreamDialog}
255 onSubmit={onAddRestreamSubmit}
256 show={showRestreamDialog}
262 Schedule.propTypes = {
263 user: PropTypes.shape({
267 export default withUser(Schedule);