Skip to content
Snippets Groups Projects
MensaplanPanel.tsx 6.42 KiB
Newer Older
  • Learn to ignore specific revisions
  • import React, {useEffect, useRef, useState} from 'react';
    import PanelWrapper from "../../meta/PanelWrapper";
    import PanelTitle from "../../meta/PanelTitle";
    import PanelContent from "../../meta/PanelContent";
    import {CanteenAPIResponse} from "./types/canteenAPI";
    import {Leaf, Plant, Bone, Warning, Record} from "@phosphor-icons/react";
    
    type Dish = {
      name: string,
      details: string,
      typeIcons: React.FC<any>[]
    }
    
    type MensaPanelDefinition = {
      canteenId: number,
      closingTime: {
        hours: number,
        minutes: number
      }
    }
    
    const MensaplanPanel = (props: {definition: MensaPanelDefinition}) => {
      const menus = useRef<Dish[]>([]);
      const specials = useRef<Dish[]>([]);
      const [relativeDay, setRelativeDay] = useState<string>("heute");
      const [groupName, setGroupName] = useState<string>("Menüs")
      const cycle = useRef<number>(0);
      const [dishes, setDishes] = useState<Dish[]>([]);
    
      useEffect(() => {
        const update = async () => {
    
          try {
            // Determine day to fetch for
            const now = new Date();
            let fetchFor: string;
    
            if (
              now.getHours() > props.definition.closingTime.hours || (
                now.getHours() === props.definition.closingTime.hours &&
                now.getMinutes() > props.definition.closingTime.minutes
              )
            ) {
              // After closing, fetch for next day
              setRelativeDay("morgen");
              const tomorrow = new Date(now.setTime(now.getTime() + 24 * 60 * 60 * 1000));
              fetchFor = toYYYYMMDD(tomorrow);
            } else {
              // otherwise, fetch for today
              setRelativeDay("heute");
              fetchFor = toYYYYMMDD(now);
            }
    
            // Request the API
            const request = await fetch(`https://infoscreen.oh14.de/canteen-menu/v3/canteens/${props.definition.canteenId}/${fetchFor}`);
    
            if (!(request.status === 200)) {
              menus.current = [];
              specials.current = [];
              return;
            }
    
            const data = await request.json() as CanteenAPIResponse;
    
            console.log(data);
    
            const old_menus_count = menus.current.length;
            const old_specials_count = menus.current.length;
    
            // ToDo: This needs to be cleaned up!
            menus.current = data
              .filter(d => d.counter !== "Beilagen")
              .filter(d => d.counter !== "Aktionsteller")
              .sort((a, b) => {
                return a.position - b.position
              })
              .map(d => ({
                name: (d.title.de
                  .split(" | ")
                  .at(0) ?? "Name nicht bekannt")
                  .replace(" nach Wahl", ""),
                details: d.title.de
                  .split(" | ")
                  .slice(1, -1)
                  .join(", ")
                  .replace(" nach Wahl", ""),
                typeIcons: d.type.map(typeToIcon).filter(i => i !== null) as unknown as React.FC<any>[]
              }))
    
            specials.current = data
              .filter(d => d.counter === "Aktionsteller")
              .sort((a, b) => {
                return a.position - b.position
              })
              .map(d => ({
                name: (d.title.de
                  .split(" | ")
                  .at(0) ?? "Name nicht bekannt")
                  .replace(" nach Wahl", ""),
                details: d.title.de
                  .split(" | ")
                  .slice(1, -1)
                  .join(", ")
                  .replace(" nach Wahl", ""),
                typeIcons: d.type.map(typeToIcon).filter(i => i !== null) as unknown as React.FC<any>[]
              }))
    
            // If the count of menus and specials changed, reset the cycler
            if (menus.current.length !== old_menus_count || specials.current.length !== old_specials_count) {
              setDishes(menus.current);
              cycle.current = 0;
              setGroupName("Menüs")
            }
    
          catch (e) {
            console.warn("MensaPlan not showing data because", e);
    
            menus.current = [];
            specials.current = [];
          }
        }
    
        update();
        const interval = setInterval(update, 1 * 60 * 60 * 1000);
    
        return () => {
          clearInterval(interval);
        }
    
        // eslint-disable-next-line react-hooks/exhaustive-deps
    
      }, []);
    
      useEffect(() => {
        const update = async () => {
          switch (cycle.current % 2) {
            case 0:
              setGroupName("Menüs");
              setDishes(menus.current);
              break;
            case 1:
              setGroupName("Aktionsteller");
              setDishes(specials.current);
              break;
          }
    
          cycle.current = (cycle.current + 1) % 2;
        }
    
        update();
        const interval = setInterval(update, 20 * 1000);
    
        return () => {
          clearInterval(interval);
        }
      }, []);
    
        <PanelWrapper>
          <PanelTitle title={"Mensaplan für " + relativeDay} info={groupName} />
          <PanelContent>
            <div className={"flex flex-col gap-4"}>
              {dishes.map(dish => (
                <div className={"flex flex-row items-center gap-4"}>
                    {/* Fixme: This shifts the name out of line if there is more than one */}
                    <div className={"flex flex-row gap-2 mr-2"}>
                      {dish.typeIcons.map(Icon => (
                        <Icon size={32} className={"text-zinc-400"}/>
                      ))}
                    </div>
    
                    <h3 className={"text-xl leading-tight"}>
                      {dish.name}
                    </h3>
                    <p className={"text-sm text-zinc-300 leading-tight"}>
                      {dish.details}
                    </p>
                </div>
              ))}
            </div>
    
            {dishes.length === 0 && (
              <div className={"h-full w-full flex justify-center items-center"}>
                <div className={"mb-10 flex flex-col items-center"}>
                  <Warning size={48} className={"mb-3"}/>
                  <p className={"text-center text-zinc-400"}>
                    Aktuell sind keine Mensaplan-Daten verfügbar.
                  </p>
                </div>
              </div>
            )}
          </PanelContent>
        </PanelWrapper>
    
    export default MensaplanPanel;
    
    
    function toYYYYMMDD(input: Date): string {
      return `${input.getFullYear()}-${input.getMonth() + 1}-${input.getDate()}`
    }
    
    function typeToIcon(type: string): React.FC | null {
      switch (type) {
        case "N":
          // Vegan
          return Plant
        case "V":
          // Vegetarisch
          return Leaf
        case "G":
          // Geflügel
          return Bone
        case "K":
          // Kosher
          // ToDo: Work out proper icon
          return Record
        case "S":
          // Schwein
          return Bone
        case "R":
          // Rind (geraten)
          return Bone
        default:
          return null
      }
    }