From 7e0eb67b886621ee1be6961825ba9a0e10d17427 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 11 Jul 2025 15:12:26 +0200 Subject: [PATCH] schedule time control --- resources/js/components/episodes/Filter.jsx | 132 +++++++++++++++++--- resources/js/helpers/Episode.js | 66 ++++++++++ resources/js/i18n/de.js | 6 + resources/js/i18n/en.js | 6 + resources/js/pages/Schedule.jsx | 18 +-- 5 files changed, 199 insertions(+), 29 deletions(-) diff --git a/resources/js/components/episodes/Filter.jsx b/resources/js/components/episodes/Filter.jsx index fb4e7e6..96d7754 100644 --- a/resources/js/components/episodes/Filter.jsx +++ b/resources/js/components/episodes/Filter.jsx @@ -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

{t('schedule.filter')}

{events?.length ? <>

{t('schedule.filters.event')}

- +
+ + +
{events.map(event => @@ -63,14 +106,24 @@ const Filter = ({ events, filter, games, setFilter }) => { {games?.length ? <>

{t('schedule.filters.game')}

- +
+ + +
{games.map(game => @@ -85,6 +138,51 @@ const Filter = ({ events, filter, games, setFilter }) => { )}
: null} +
+

{t('schedule.filters.time')}

+ +
+ + + + {t('schedule.lookahead')} + setLookahead(e.target.value)} + type="number" + value={getFilterLookahead(filter)} + /> + + + + + {t('schedule.lookbehind')} + setLookbehind(e.target.value)} + type="number" + value={getFilterLookbehind(filter)} + /> + + + + + {t('schedule.direction')} + setReverse(e.target.value)} + value={getFilterReverse(filter)} + > + + + + + +
; }; diff --git a/resources/js/helpers/Episode.js b/resources/js/helpers/Episode.js index ca4e001..c18b68e 100644 --- a/resources/js/helpers/Episode.js +++ b/resources/js/helpers/Episode.js @@ -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, + }; +}; diff --git a/resources/js/i18n/de.js b/resources/js/i18n/de.js index 77b5626..ea11081 100644 --- a/resources/js/i18n/de.js +++ b/resources/js/i18n/de.js @@ -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: { diff --git a/resources/js/i18n/en.js b/resources/js/i18n/en.js index ebab681..f0c0015 100644 --- a/resources/js/i18n/en.js +++ b/resources/js/i18n/en.js @@ -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: { diff --git a/resources/js/pages/Schedule.jsx b/resources/js/pages/Schedule.jsx index b688fe8..fde4c96 100644 --- a/resources/js/pages/Schedule.jsx +++ b/resources/js/pages/Schedule.jsx @@ -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); -- 2.39.5