]> git.localhorst.tv Git - alttp.git/commitdiff
WIP event filter
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Tue, 21 Feb 2023 16:44:22 +0000 (17:44 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Tue, 21 Feb 2023 16:44:22 +0000 (17:44 +0100)
app/Http/Controllers/EventController.php
resources/js/components/episodes/Filter.js [new file with mode: 0644]
resources/js/components/pages/Schedule.js

index eb742399dab1d1ba9dfd1e8c172ed8e483bda502..bfe9efd82eaa854f0f4836637f711264dfb0e379 100644 (file)
@@ -3,13 +3,30 @@
 namespace App\Http\Controllers;
 
 use App\Models\Event;
+use Carbon\Carbon;
 use Illuminate\Http\Request;
 
 class EventController extends Controller
 {
 
        public function search(Request $request) {
+               $validatedData = $request->validate([
+                       'after' => 'nullable|date',
+                       'before' => 'nullable|date',
+               ]);
                $events = Event::where('visible', '=', true);
+               if (isset($validatedData['before'])) {
+                       $events = $events->where(function ($query) use ($validatedData) {
+                               $query->whereNull('start');
+                               $query->orWhere('start', '<', $validatedData['before']);
+                       });
+               }
+               if (isset($validatedData['after'])) {
+                       $events = $events->where(function ($query) use ($validatedData) {
+                               $query->whereNull('end');
+                               $query->orWhere('end', '>', $validatedData['after']);
+                       });
+               }
                return $events->get()->toJson();
        }
 
diff --git a/resources/js/components/episodes/Filter.js b/resources/js/components/episodes/Filter.js
new file mode 100644 (file)
index 0000000..4855a71
--- /dev/null
@@ -0,0 +1,80 @@
+import axios from 'axios';
+import moment from 'moment';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+
+const Filter = ({ filter, setFilter }) => {
+       const [events, setEvents] = React.useState([]);
+
+       React.useEffect(() => {
+               const controller = new AbortController();
+               axios.get(`/api/events`, {
+                       signal: controller.signal,
+                       params: {
+                               after: moment().startOf('day').subtract(7, 'days').toISOString(),
+                               before: moment().startOf('day').add(8, 'days').toISOString(),
+                       },
+               }).then(response => {
+                       const newEvents = response.data || [];
+                       setEvents(newEvents);
+                       setFilter(filter => {
+                               const eventFilter = filter.event || [];
+                               return {
+                                       ...filter,
+                                       event: eventFilter.filter(newEvents.find(id => newEvents.includes(id))),
+                               };
+                       });
+               }).catch(e => {
+                       if (!axios.isCancel(e)) {
+                               console.error(e);
+                       }
+               });
+               return () => {
+                       controller.abort();
+               };
+       }, [setEvents, setFilter]);
+
+       const toggleEvent = React.useCallback(event => {
+               setFilter(filter => {
+                       const eventFilter = filter.event || [];
+                       if (!eventFilter.includes(event.id)) {
+                               return {
+                                       ...filter,
+                                       event: [...eventFilter, event.id],
+                               };
+                       }
+                       return {
+                               ...filter,
+                               event: eventFilter.filter(id => id !== event.id),
+                       };
+               });
+       }, [setFilter]);
+
+       const isEventSelected = React.useCallback(event => {
+               const eventFilter = filter.event || [];
+               return eventFilter.includes(event.id);
+       }, [filter]);
+
+       if (!events || !events.length) return null;
+
+       return <div className="episode-filter">
+               {events.map(event =>
+                       <Button
+                               active={isEventSelected(event)}
+                               key={event.id}
+                               onClick={() => toggleEvent(event)}
+                               variant="outline-secondary"
+                       >
+                               {event.title}
+                       </Button>
+               )}
+       </div>;
+};
+
+Filter.propTypes = {
+       filter: PropTypes.shape(),
+       setFilter: PropTypes.func,
+};
+
+export default Filter;
index c27a384a5fdddc8bbd527abb97e550b5e1057ee1..2e97af9e4437ba6ee49cb5b646a5ff920e482644 100644 (file)
@@ -7,21 +7,38 @@ import { useTranslation } from 'react-i18next';
 
 import CanonicalLinks from '../common/CanonicalLinks';
 import ErrorBoundary from '../common/ErrorBoundary';
+import Filter from '../episodes/Filter';
 import List from '../episodes/List';
 
 const Schedule = () => {
        const [ahead, setAhead] = React.useState(6);
        const [behind, setBehind] = React.useState(0);
        const [episodes, setEpisodes] = React.useState([]);
+       const [filter, setFilter] = React.useState({});
 
        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);
+       }, [setFilter]);
+
+       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 || []);
@@ -34,15 +51,15 @@ const Schedule = () => {
 
        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);
+               }, 3 * 60 * 1000);
                return () => {
                        controller.abort();
                        clearInterval(timer);
                };
-       }, [ahead, behind, fetchEpisodes]);
+       }, [ahead, behind, fetchEpisodes, filter]);
 
        return <Container>
                <Helmet>
@@ -52,6 +69,7 @@ const Schedule = () => {
                <CanonicalLinks base="/schedule" />
                <h1>{t('schedule.heading')}</h1>
                <ErrorBoundary>
+                       <Filter filter={filter} setFilter={updateFilter} />
                        <List episodes={episodes} />
                </ErrorBoundary>
        </Container>;