diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index bbacc2f..9410618 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -7,7 +7,8 @@ import FeaturesVotingModal from "./modals/FeaturesVotingModal"; import PizzaCalculatorModal from "./modals/PizzaCalculatorModal"; import { useNavigate } from "react-router"; import { STATS_URL } from "../AppRoutes"; -import { FeatureRequest, getVotes, updateVote } from "../../../types"; +import { FeatureRequest, getVotes, refreshMenu, Restaurant, updateVote } from "../../../types"; +import RefreshMenuModal from "./modals/RefreshMenuModal"; export default function Header() { const auth = useAuth(); @@ -16,6 +17,7 @@ export default function Header() { const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [votingModalOpen, setVotingModalOpen] = useState(false); const [pizzaModalOpen, setPizzaModalOpen] = useState(false); + const [refreshModalOpen, setRefreshModalOpen] = useState(false); const [featureVotes, setFeatureVotes] = useState([]); useEffect(() => { @@ -38,6 +40,10 @@ export default function Header() { setPizzaModalOpen(false); } + const closeRefreshModal = () => { + setRefreshModalOpen(false); + } + const isValidInteger = (str: string) => { str = str.trim(); if (!str) { @@ -107,6 +113,12 @@ export default function Header() { setFeatureVotes(votes); } + const handleRefreshMenu = async (restaurants: Restaurant[]) => { + if (restaurants.length > 0) { + await refreshMenu({ body: restaurants }); + } + } + return Luncher @@ -117,6 +129,7 @@ export default function Header() { setVotingModalOpen(true)}>Hlasovat o nových funkcích setPizzaModalOpen(true)}>Pizza kalkulačka navigate(STATS_URL)}>Statistiky + setRefreshModalOpen(true)}>Přenačíst menu Odhlásit se @@ -125,5 +138,6 @@ export default function Header() { + } \ No newline at end of file diff --git a/client/src/components/modals/RefreshMenuModal.tsx b/client/src/components/modals/RefreshMenuModal.tsx new file mode 100644 index 0000000..fc81343 --- /dev/null +++ b/client/src/components/modals/RefreshMenuModal.tsx @@ -0,0 +1,53 @@ +import { Modal, Button, Form } from "react-bootstrap" +import { Restaurant } from "../../../../types"; +import { getRestaurantName } from "../../enums"; +import { useState } from "react"; + +type Props = { + isOpen: boolean, + onClose: () => void, + onSubmit: (restaurants: Restaurant[]) => void, +} + +/** Modální dialog pro přenačtení menu jednotlivých podniků. */ +export default function RefreshMenuModal({ isOpen, onClose, onSubmit }: Readonly) { + + const [restaurants, setRestaurants] = useState([]); + + const handleChange = (e: React.ChangeEvent) => { + if (e.currentTarget.checked) { + setRestaurants([...restaurants, e.currentTarget.value as Restaurant]); + } else { + setRestaurants(restaurants.filter(restaurant => restaurant !== e.currentTarget.value as Restaurant)); + } + } + + return + + + Vyberte podniky k přenačtení menu +

Menu lze přenačíst nejdříve 15 minut od poslední aktualizace

+
+
+ + {(Object.keys(Restaurant) as Array).map(key => { + return + })} + + + + + +
+} \ No newline at end of file diff --git a/server/src/routes/foodRoutes.ts b/server/src/routes/foodRoutes.ts index d042cb2..553f44d 100644 --- a/server/src/routes/foodRoutes.ts +++ b/server/src/routes/foodRoutes.ts @@ -1,11 +1,14 @@ import express, { Request } from "express"; import { getLogin, getTrusted } from "../auth"; -import { addChoice, getDateForWeekIndex, getToday, removeChoice, removeChoices, updateDepartureTime, updateNote } from "../service"; +import { addChoice, getDateForWeekIndex, getRestaurantMenu, getToday, removeChoice, removeChoices, updateDepartureTime, updateNote } from "../service"; import { getDayOfWeekIndex, parseToken } from "../utils"; import { getWebsocket } from "../websocket"; import { callNotifikace } from "../notifikace"; import { AddChoiceData, ChangeDepartureTimeData, RemoveChoiceData, RemoveChoicesData, UdalostEnum, UpdateNoteData } from "../../../types/gen/types.gen"; +/** Po jak dlouhé době (v minutách) lze provést nové načtení menu. */ +const MENU_REFRESH_INTERVAL = 15; + /** * Ověří a vrátí index dne v týdnu z požadavku, za předpokladu, že byl předán, a je zároveň * roven nebo vyšší indexu dnešního dne. @@ -141,4 +144,25 @@ router.post("/jdemeObed", async (req, res, next) => { } catch (e: any) { next(e) } }); +router.post("/refreshMenu", async (req, res, next) => { + if (!req.body || !Array.isArray(req.body)) { + return res.status(400).json({ error: "Neplatný požadavek" }); + } + try { + const now = new Date(); + for (const restaurant of req.body) { + // TODO tohle je technicky špatně, protože pokud aktuálně jídla načtená nejsou, tak je toto volání načte a následně je to načte znovu kvůli force! + const menu = await getRestaurantMenu(restaurant); + if (menu.lastUpdate != null) { + const minutes = (now.getTime() - menu.lastUpdate) / 1000 / 60; + if (minutes < MENU_REFRESH_INTERVAL) { + throw Error(`Podnik ${restaurant} byl přenačtený před ${Math.round(minutes)} minutami. Nové přenačtení lze provést nejdříve za ${Math.round(MENU_REFRESH_INTERVAL - minutes)} minut.`); + } + } + await getRestaurantMenu(restaurant, undefined, true); + } + res.status(200).json({}); + } catch (e: any) { next(e) } +}); + export default router; \ No newline at end of file diff --git a/server/src/service.ts b/server/src/service.ts index 2bf8dd9..8292218 100644 --- a/server/src/service.ts +++ b/server/src/service.ts @@ -78,13 +78,13 @@ async function getMenu(date: Date): Promise { // TODO přesun do restaurants.ts /** * Vrátí menu dané restaurace pro předaný den. - * Pokud neexistuje, provede stažení menu pro příslušný týden a uložení do DB. + * Pokud neexistuje nebo je nastaven příznak force, provede stažení menu pro příslušný týden a uložení do DB. * * @param restaurant restaurace * @param date datum, ke kterému získat menu - * @param mock příznak, zda chceme pouze mock data + * @param force Příznak, zda znovu získat aktuální menu i v případě, že je již načteno. Pokud není předán, provede se načtení pouze v případě, že menu aktuálně nemáme. Pokud je true, provede nové načtení. Pokud je false, neprovede se nové načtení ani v případě, že menu aktuálně nemáme. */ -export async function getRestaurantMenu(restaurant: Restaurant, date?: Date): Promise { +export async function getRestaurantMenu(restaurant: Restaurant, date?: Date, force?: boolean): Promise { const usedDate = date ?? getToday(); const dayOfWeekIndex = getDayOfWeekIndex(usedDate); const now = new Date().getTime(); @@ -112,7 +112,8 @@ export async function getRestaurantMenu(restaurant: Restaurant, date?: Date): Pr }; } } - if (!weekMenu[dayOfWeekIndex][restaurant]?.food?.length) { + + if ((!weekMenu[dayOfWeekIndex][restaurant]?.food?.length && force === undefined) || force) { const firstDay = getFirstWorkDayOfWeek(usedDate); const mock = process.env.MOCK_DATA === 'true'; switch (restaurant) { diff --git a/types/api.yml b/types/api.yml index 8311627..acf566b 100644 --- a/types/api.yml +++ b/types/api.yml @@ -26,6 +26,8 @@ paths: $ref: "./paths/food/changeDepartureTime.yml" /food/jdemeObed: $ref: "./paths/food/jdemeObed.yml" + /food/refreshMenu: + $ref: "./paths/food/refreshMenu.yml" # Pizza day (/api/pizzaDay) /pizzaDay/create: diff --git a/types/paths/food/refreshMenu.yml b/types/paths/food/refreshMenu.yml new file mode 100644 index 0000000..d9162f9 --- /dev/null +++ b/types/paths/food/refreshMenu.yml @@ -0,0 +1,15 @@ +post: + operationId: refreshMenu + summary: Přenačtení menu vybraných podniků + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + $ref: "../../schemas/_index.yml#/Restaurant" + + responses: + "200": + description: Menu bylo přenačteno