]> git.localhorst.tv Git - alttp.git/blobdiff - resources/js/components/pages/Schedule.js
collapse schedule filter
[alttp.git] / resources / js / components / pages / Schedule.js
index 5f82bfe3347d322d0178b86da342209a2d8129b1..ffbb69ef611f8deffaeb39ae09af27d0483e5c97 100644 (file)
@@ -1,27 +1,56 @@
 import axios from 'axios';
 import moment from 'moment';
+import PropTypes from 'prop-types';
 import React from 'react';
-import { Container } from 'react-bootstrap';
+import { Alert, Button, 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 Icon from '../common/Icon';
+import ApplyDialog from '../episodes/ApplyDialog';
+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 [applyAs, setApplyAs] = React.useState('commentary');
+       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 [showApplyDialog, setShowApplyDialog] = React.useState(false);
+       const [showRestreamDialog, setShowRestreamDialog] = React.useState(false);
+       const [showFilter, setShowFilter] = 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 || []);
@@ -30,19 +59,169 @@ const Schedule = () => {
                                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 editRestream = React.useCallback(async values => {
+               try {
+                       const response = await axios.post(
+                               `/api/episodes/${values.episode_id}/edit-restream`, values);
+                       const newEpisode = response.data;
+                       setEpisodes(episodes => episodes.map(episode =>
+                               episode.id === newEpisode.id ? {
+                                       ...episode,
+                                       ...newEpisode,
+                               } : episode
+                       ));
+                       setRestreamEpisode(episode => ({
+                               ...episode,
+                               ...newEpisode,
+                       }));
+                       const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
+                       setRestreamChannel(channel => ({
+                               ...channel,
+                               ...newChannel,
+                       }));
+                       toastr.success(t('episodes.restreamDialog.editSuccess'));
+               } catch (e) {
+                       toastr.error(t('episodes.restreamDialog.editError'));
+               }
+       }, []);
+
+       const manageCrew = React.useCallback(async values => {
+               try {
+                       const response = await axios.post(
+                               `/api/episodes/${values.episode_id}/crew-manage`, values);
+                       const newEpisode = response.data;
+                       setEpisodes(episodes => episodes.map(episode =>
+                               episode.id === newEpisode.id ? {
+                                       ...episode,
+                                       ...newEpisode,
+                               } : episode
+                       ));
+                       setRestreamEpisode(episode => ({
+                               ...episode,
+                               ...newEpisode,
+                       }));
+                       const newChannel = newEpisode.channels.find(c => c.id === values.channel_id);
+                       setRestreamChannel(channel => ({
+                               ...channel,
+                               ...newChannel,
+                       }));
+                       toastr.success(t('episodes.restreamDialog.crewSuccess'));
+               } catch (e) {
+                       toastr.error(t('episodes.restreamDialog.crewError'));
+               }
+       }, []);
+
+       const onHideRestreamDialog = React.useCallback(() => {
+               setShowRestreamDialog(false);
+               setRestreamChannel(null);
+               setRestreamEpisode(null);
+       }, []);
+
+       const onApply = React.useCallback((episode, as) => {
+               setShowApplyDialog(true);
+               setRestreamEpisode(episode);
+               setApplyAs(as);
+       }, []);
+
+       const onSubmitApplyDialog = React.useCallback(async values => {
+               try {
+                       const response = await axios.post(
+                               `/api/episodes/${values.episode_id}/crew-signup`, values);
+                       const newEpisode = response.data;
+                       setEpisodes(episodes => episodes.map(episode =>
+                               episode.id === newEpisode.id ? {
+                                       ...episode,
+                                       ...newEpisode,
+                               } : episode
+                       ));
+                       toastr.success(t('episodes.applyDialog.applySuccess'));
+               } catch (e) {
+                       toastr.error(t('episodes.applyDialog.applyError'));
+                       throw e;
+               }
+               setRestreamEpisode(null);
+               setShowApplyDialog(false);
+       }, []);
+
+       const onHideApplyDialog = React.useCallback(() => {
+               setShowApplyDialog(false);
+               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]);
+       }, [ahead, behind, fetchEpisodes, filter]);
+
+       const toggleFilter = React.useCallback(() => {
+               setShowFilter(show => !show);
+       }, []);
+
+       const filterButtonVariant = React.useMemo(() => {
+               const outline = showFilter ? '' : 'outline-';
+               const filterActive = filter && filter.event && filter.event.length;
+               return `${outline}${filterActive ? 'info' : 'secondary'}`;
+       }, [filter, showFilter]);
 
        return <Container>
                <Helmet>
@@ -50,11 +229,60 @@ const Schedule = () => {
                        <meta name="description" content={t('schedule.description')} />
                </Helmet>
                <CanonicalLinks base="/schedule" />
-               <h1>{t('schedule.heading')}</h1>
+               <div className="d-flex align-items-end justify-content-between">
+                       <h1 className="mb-0">{t('schedule.heading')}</h1>
+                       <Button
+                               onClick={toggleFilter}
+                               title={t('button.filter')}
+                               variant={filterButtonVariant}
+                       >
+                               <Icon.FILTER title="" />
+                       </Button>
+               </div>
+               {showFilter ?
+                       <div className="my-2">
+                               <Filter filter={filter} setFilter={updateFilter} />
+                       </div>
+               : null}
                <ErrorBoundary>
-                       <List episodes={episodes} />
+                       {episodes.length ?
+                               <List
+                                       episodes={episodes}
+                                       onAddRestream={onAddRestream}
+                                       onApply={onApply}
+                                       onEditRestream={onEditRestream}
+                               />
+                       :
+                               <Alert variant="info">
+                                       {t('episodes.empty')}
+                               </Alert>
+                       }
                </ErrorBoundary>
+               {user ? <>
+                       <ApplyDialog
+                               as={applyAs}
+                               episode={restreamEpisode}
+                               onHide={onHideApplyDialog}
+                               onSubmit={onSubmitApplyDialog}
+                               show={showApplyDialog}
+                       />
+                       <RestreamDialog
+                               channel={restreamChannel}
+                               editRestream={editRestream}
+                               episode={restreamEpisode}
+                               manageCrew={manageCrew}
+                               onRemoveRestream={onRemoveRestream}
+                               onHide={onHideRestreamDialog}
+                               onSubmit={onAddRestreamSubmit}
+                               show={showRestreamDialog}
+                       />
+               </> : null}
        </Container>;
 };
 
-export default Schedule;
+Schedule.propTypes = {
+       user: PropTypes.shape({
+       }),
+};
+
+export default withUser(Schedule);