Skip to content
Snippets Groups Projects
MensaplanPanel.tsx 6.04 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 () => {
          // 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.ok) {
            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)
    
                .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")
          }
        }
    
        update();
        const interval = setInterval(update, 1 * 60 * 60 * 1000);
    
        return () => {
          clearInterval(interval);
        }
      }, []);
    
      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
      }
    }