Skip to content
Snippets Groups Projects
MensaplanPanel.tsx 6.64 KiB
Newer Older
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, Record, Fish, ForkKnife} 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(`/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 key={dish.name} 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, index) => (
                    <Icon key={Icon.name + index} 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"}>
              <ForkKnife size={48} className={"mb-3"}/>
              <p className={"text-center text-zinc-400"}>
                Es werden keine Gerichte in dieser Kategorie angeboten
              </p>
            </div>
          </div>
        )}
      </PanelContent>
    </PanelWrapper>
export default MensaplanPanel;

function toYYYYMMDD(input: Date): string {
  return `${input.getFullYear().toString().padStart(4, "20")}-${(input.getMonth() + 1).toString().padStart(2, "0")}-${input.getDate().toString().padStart(2, "0")}`
}

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
    case "W":
      // Wild
      return Bone
    case "F":
      // Fisch (geraten)
      return Fish