]> git.localhorst.tv Git - alttp.git/commitdiff
schedule time control
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 11 Jul 2025 13:12:26 +0000 (15:12 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 11 Jul 2025 13:12:26 +0000 (15:12 +0200)
resources/js/components/episodes/Filter.jsx
resources/js/helpers/Episode.js
resources/js/i18n/de.js
resources/js/i18n/en.js
resources/js/pages/Schedule.jsx

index fb4e7e6c0b8cf79e2286450d88f7b20e49e91b49..96d7754a6b202cbc6a18f93a0174f3237fb5f42b 100644 (file)
@@ -1,14 +1,23 @@
 import PropTypes from 'prop-types';
 import React from 'react';
-import { Button } from 'react-bootstrap';
+import { Button, Col, Form, Row } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
 
 import Icon from '../common/Icon';
 import {
+       getFilterLookahead,
+       getFilterLookbehind,
+       getFilterReverse,
        invertEventFilter,
        invertGameFilter,
        isEventSelected,
        isGameSelected,
+       resetEventFilter,
+       resetGameFilter,
+       resetTimeFilter,
+       setFilterLookahead,
+       setFilterLookbehind,
+       setFilterReverse,
        toggleEventFilter,
        toggleGameFilter,
 } from '../../helpers/Episode';
@@ -20,6 +29,10 @@ const Filter = ({ events, filter, games, setFilter }) => {
                setFilter(invertEventFilter(filter));
        }, [filter, setFilter]);
 
+       const resetEvents = React.useCallback(() => {
+               setFilter(resetEventFilter(filter));
+       }, [filter, setFilter]);
+
        const toggleEvent = React.useCallback(event => {
                setFilter(toggleEventFilter(events, filter, event));
        }, [events, filter, setFilter]);
@@ -28,23 +41,53 @@ const Filter = ({ events, filter, games, setFilter }) => {
                setFilter(invertGameFilter(filter));
        }, [filter, setFilter]);
 
+       const resetGames = React.useCallback(() => {
+               setFilter(resetGameFilter(filter));
+       }, [filter, setFilter]);
+
        const toggleGame = React.useCallback(game => {
                setFilter(toggleGameFilter(games, filter, game));
        }, [games, filter, setFilter]);
 
+       const resetTime = React.useCallback(() => {
+               setFilter(resetTimeFilter(filter));
+       }, [filter, setFilter]);
+
+       const setLookahead = React.useCallback((value) => {
+               setFilter(setFilterLookahead(filter, parseInt(value, 10) || 0));
+       }, [filter, setFilter]);
+
+       const setLookbehind = React.useCallback((value) => {
+               setFilter(setFilterLookbehind(filter, parseInt(value, 10) || 0));
+       }, [filter, setFilter]);
+
+       const setReverse = React.useCallback((value) => {
+               setFilter(setFilterReverse(filter, parseInt(value, 10) || 0));
+       }, [filter, setFilter]);
+
        return <div className="episode-filter my-3">
                <h2>{t('schedule.filter')}</h2>
                {events?.length ? <>
                        <div className="d-flex align-items-start justify-content-start gap-3 mt-3">
                                <h3>{t('schedule.filters.event')}</h3>
-                               <Button
-                                       onClick={invertEvents}
-                                       size="sm"
-                                       title={t('button.invert')}
-                                       variant="outline-secondary"
-                               >
-                                       <Icon.INVERT title="" />
-                               </Button>
+                               <div className="button-bar">
+                                       <Button
+                                               onClick={invertEvents}
+                                               size="sm"
+                                               title={t('button.invert')}
+                                               variant="outline-secondary"
+                                       >
+                                               <Icon.INVERT title="" />
+                                       </Button>
+                                       <Button
+                                               onClick={resetEvents}
+                                               size="sm"
+                                               title={t('button.reset')}
+                                               variant="outline-secondary"
+                                       >
+                                               <Icon.RESET title="" />
+                                       </Button>
+                               </div>
                        </div>
                        <div className="button-bar my-2">
                                {events.map(event =>
@@ -63,14 +106,24 @@ const Filter = ({ events, filter, games, setFilter }) => {
                {games?.length ? <>
                        <div className="d-flex align-items-start justify-content-start gap-3 mt-3">
                                <h3>{t('schedule.filters.game')}</h3>
-                               <Button
-                                       onClick={invertGames}
-                                       size="sm"
-                                       title={t('button.invert')}
-                                       variant="outline-secondary"
-                               >
-                                       <Icon.INVERT title="" />
-                               </Button>
+                               <div className="button-bar">
+                                       <Button
+                                               onClick={invertGames}
+                                               size="sm"
+                                               title={t('button.invert')}
+                                               variant="outline-secondary"
+                                       >
+                                               <Icon.INVERT title="" />
+                                       </Button>
+                                       <Button
+                                               onClick={resetGames}
+                                               size="sm"
+                                               title={t('button.reset')}
+                                               variant="outline-secondary"
+                                       >
+                                               <Icon.RESET title="" />
+                                       </Button>
+                               </div>
                        </div>
                        <div className="button-bar my-2">
                                {games.map(game =>
@@ -85,6 +138,51 @@ const Filter = ({ events, filter, games, setFilter }) => {
                                )}
                        </div>
                </> : null}
+               <div className="d-flex align-items-start justify-content-start gap-3 mt-3">
+                       <h3>{t('schedule.filters.time')}</h3>
+                       <Button
+                               onClick={resetTime}
+                               size="sm"
+                               title={t('button.reset')}
+                               variant="outline-secondary"
+                       >
+                               <Icon.RESET title="" />
+                       </Button>
+               </div>
+               <Row>
+                       <Col sm={4} md={3} lg={2}>
+                               <Form.Group>
+                                       <Form.Label>{t('schedule.lookahead')}</Form.Label>
+                                       <Form.Control
+                                               onChange={(e) => setLookahead(e.target.value)}
+                                               type="number"
+                                               value={getFilterLookahead(filter)}
+                                       />
+                               </Form.Group>
+                       </Col>
+                       <Col sm={4} md={3} lg={2}>
+                               <Form.Group>
+                                       <Form.Label>{t('schedule.lookbehind')}</Form.Label>
+                                       <Form.Control
+                                               onChange={(e) => setLookbehind(e.target.value)}
+                                               type="number"
+                                               value={getFilterLookbehind(filter)}
+                                       />
+                               </Form.Group>
+                       </Col>
+                       <Col sm={4} md={3} lg={2}>
+                               <Form.Group>
+                                       <Form.Label>{t('schedule.direction')}</Form.Label>
+                                       <Form.Select
+                                               onChange={(e) => setReverse(e.target.value)}
+                                               value={getFilterReverse(filter)}
+                                       >
+                                               <option value="0">{t('schedule.forward')}</option>
+                                               <option value="1">{t('schedule.reverse')}</option>
+                                       </Form.Select>
+                               </Form.Group>
+                       </Col>
+               </Row>
        </div>;
 };
 
index ca4e0010bbec35f3a8e32175e25520c8ab0c02ea..c18b68ec9c5e24a3a9e1e130592b56c88613bc8a 100644 (file)
@@ -75,6 +75,12 @@ export const invertEventFilter = (filter) => ({
        eventInvert: filter.eventInvert ? 0 : 1,
 });
 
+export const resetEventFilter = (filter) => ({
+       ...filter,
+       event: [],
+       eventInvert: 0,
+});
+
 export const toggleEventFilter = (events, filter, event) => {
        const eventFilter = filter.event || [];
        if (eventFilter.includes(event.id)) {
@@ -99,6 +105,12 @@ export const invertGameFilter = (filter) => ({
        gameInvert: filter.gameInvert ? 0 : 1,
 });
 
+export const resetGameFilter = (filter) => ({
+       ...filter,
+       game: [],
+       gameInvert: 0,
+});
+
 export const toggleGameFilter = (games, filter, game) => {
        const gameFilter = filter.game || [];
        if (gameFilter.includes(game)) {
@@ -112,3 +124,57 @@ export const toggleGameFilter = (games, filter, game) => {
                game: [...gameFilter, game],
        };
 };
+
+export const getFilterLookahead = (filter, fallback = 14) => {
+       if (!filter) return fallback;
+       if ('lookahead' in filter) {
+               return filter.lookahead;
+       }
+       return fallback;
+};
+
+export const getFilterLookbehind = (filter, fallback = 0) => {
+       if (!filter) return fallback;
+       if ('lookbehind' in filter) {
+               return filter.lookbehind;
+       }
+       return fallback;
+};
+
+export const getFilterReverse = (filter, fallback = 0) => {
+       if (!filter) return fallback;
+       if ('reverse' in filter) {
+               return filter.reverse;
+       }
+       return fallback;
+};
+
+export const setFilterLookahead = (filter, value) => ({
+       ...filter,
+       lookahead: value,
+});
+
+export const setFilterLookbehind = (filter, value) => ({
+       ...filter,
+       lookbehind: value,
+});
+
+export const setFilterReverse = (filter, reverse) => ({
+       ...filter,
+       reverse: reverse ? 1 : 0,
+});
+
+export const resetTimeFilter = (filter) => ({
+       ...filter,
+       lookahead: 14,
+       lookbehind: 0,
+       reverse: 0,
+});
+
+export const getFilterParams = (filter) => {
+       return {
+               after: moment().subtract(2, 'hours').subtract(getFilterLookbehind(filter, 0), 'days').toISOString(),
+               before: moment().add(16, 'hours').add(getFilterLookahead(filter, 14), 'days').toISOString(),
+               ...filter,
+       };
+};
index 77b5626626a8c4b09b961738beb8ab6866a238d9..ea110814d007b146c6884f98623ddd5319412383 100644 (file)
@@ -598,11 +598,14 @@ export default {
                },
                schedule: {
                        description: 'Anstehende Spiele und andere Termine.',
+                       direction: 'Richtung',
                        filter: 'Filter',
                        filters: {
                                event: 'Nach Veranstaltung',
                                game: 'Nach Spiel',
+                               time: 'Zeitraum',
                        },
+                       forward: 'Vorwärts',
                        games: {
                                alttp: 'ALttP',
                                alttpr: 'ALttPR',
@@ -611,6 +614,9 @@ export default {
                                smz3: 'SMZ3',
                        },
                        heading: 'Terminplan',
+                       lookahead: 'Voraus (Tage)',
+                       lookbehind: 'Zurück (Tage)',
+                       reverse: 'Rückwärts',
                        startTime: '{{ date, LT }}',
                },
                search: {
index ebab681c78892b0be0bf1e30def253f422524aa3..f0c00159a80c160787d1bd862c5892d61eaa61d7 100644 (file)
@@ -598,11 +598,14 @@ export default {
                },
                schedule: {
                        description: 'Upcoming matches and other events.',
+                       direction: 'Direction',
                        filter: 'Filter',
                        filters: {
                                event: 'By event',
                                game: 'By game',
+                               time: 'Time frame',
                        },
+                       forward: 'Foreward',
                        games: {
                                alttp: 'ALttP',
                                alttpr: 'ALttPR',
@@ -611,6 +614,9 @@ export default {
                                smz3: 'SMZ3',
                        },
                        heading: 'Schedule',
+                       lookahead: 'Ahead (days)',
+                       lookbehind: 'Behind (days)',
+                       reverse: 'Reverse',
                        startTime: '{{ date, LT }}',
                },
                search: {
index b688fe86891d8230f00a6ede366ff2b48a453f53..fde4c96abdcc4a00fd512c13ed4ab603eb8269ef 100644 (file)
@@ -13,7 +13,7 @@ import ApplyDialog from '../components/episodes/ApplyDialog';
 import Filter from '../components/episodes/Filter';
 import List from '../components/episodes/List';
 import RestreamDialog from '../components/episodes/RestreamDialog';
-import { isFilterActive } from '../helpers/Episode';
+import { getFilterParams, isFilterActive } from '../helpers/Episode';
 import { useUser } from '../hooks/user';
 
 const GAMES = [
@@ -21,9 +21,7 @@ const GAMES = [
 ];
 
 export const Component = () => {
-       const [ahead] = React.useState(14);
        const [applyAs, setApplyAs] = React.useState('commentary');
-       const [behind] = React.useState(0);
        const [episodes, setEpisodes] = React.useState([]);
        const [events, setEvents] = React.useState([]);
        const [filter, setFilter] = React.useState({});
@@ -81,14 +79,10 @@ export const Component = () => {
                setFilter(newFilter);
        }, []);
 
-       const fetchEpisodes = React.useCallback((controller, ahead, behind, filter) => {
+       const fetchEpisodes = React.useCallback((controller, filter) => {
                axios.get(`/api/episodes`, {
                        signal: controller.signal,
-                       params: {
-                               after: moment().subtract(2, 'hours').subtract(behind, 'days').toISOString(),
-                               before: moment().add(16, 'hours').add(ahead, 'days').toISOString(),
-                               ...filter,
-                       },
+                       params: getFilterParams(filter),
                }).then(response => {
                        setEpisodes(response.data || []);
                }).catch(e => {
@@ -240,15 +234,15 @@ export const Component = () => {
 
        React.useEffect(() => {
                const controller = new AbortController();
-               fetchEpisodes(controller, ahead, behind, filter);
+               fetchEpisodes(controller, filter);
                const timer = setInterval(() => {
-                       fetchEpisodes(controller, ahead, behind, filter);
+                       fetchEpisodes(controller, filter);
                }, 1.5 * 60 * 1000);
                return () => {
                        controller.abort();
                        clearInterval(timer);
                };
-       }, [ahead, behind, fetchEpisodes, filter]);
+       }, [fetchEpisodes, filter]);
 
        const toggleFilter = React.useCallback(() => {
                setShowFilter(show => !show);