From 0fd14828107d08474c5b8ee8a35f5faf28e0ec11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20H=C3=A1jek?= Date: Tue, 14 Jan 2025 23:45:06 +0100 Subject: [PATCH] =?UTF-8?q?P=C5=99id=C3=A1n=C3=AD=20restaurace=20Zast?= =?UTF-8?q?=C3=A1vka=20u=20Michala?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.tsx | 3 +- server/src/mock.ts | 101 +++++++++++++++++++++++++++++++++++++- server/src/restaurants.ts | 57 ++++++++++++++++++++- server/src/service.ts | 18 ++++++- server/src/utils.ts | 12 +++-- types/Types.ts | 1 + 6 files changed, 180 insertions(+), 12 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index bad6994..6ab5167 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -351,7 +351,7 @@ function App() { } else { content =

Chyba načtení dat

} - return + return

{name}

{menu?.lastUpdate && Poslední aktualizace: {getHumanDateTime(new Date(menu.lastUpdate))}} {content} @@ -417,6 +417,7 @@ function App() { {food[Restaurants.SLADOVNICKA] && renderFoodTable('Sladovnická', food[Restaurants.SLADOVNICKA])} {/* {food[Restaurants.UMOTLIKU] && renderFoodTable('U Motlíků', food[Restaurants.UMOTLIKU])} */} {food[Restaurants.TECHTOWER] && renderFoodTable('TechTower', food[Restaurants.TECHTOWER])} + {food[Restaurants.ZASTAVKAUMICHALA] && renderFoodTable('Zastávka u Michala', food[Restaurants.ZASTAVKAUMICHALA])}
diff --git a/server/src/mock.ts b/server/src/mock.ts index 0db8d04..961f724 100644 --- a/server/src/mock.ts +++ b/server/src/mock.ts @@ -427,6 +427,96 @@ const MOCK_DATA = { isSoup: false, } ] + ], + 'zastavkaUmichala': [ + [ + { + amount: "-", + name: "Fazolačka s klobásou & zakysačkou", + price: "39\xA0Kč", + isSoup: true, + }, + { + amount: "-", + name: "Zeleninová musaka – lilek, cuketa, tomatové sugo & sýrový bešamel", + price: "135\xA0Kč", + isSoup: false, + }, + { + amount: "-", + name: "Slovácké strapačky s uzenou slaninou, zelím, mletým pepřem & sekanou petrželkou", + price: "140\xA0Kč", + isSoup: false, + }, + { + amount: "-", + name: "Hovězí guláš s vejcem, zeleninovou garniturkou & žemlovými knedlíky", + price: "145\xA0Kč", + isSoup: false, + }, + { + amount: "-", + name: "Kuřecí roláda s kaštanovou nádivkou, demi-glace & smetanovou bramborovou kaší", + price: "150\xA0Kč", + isSoup: false, + } + ], + [ + { + amount: "-", + name: "Hovězí vývar se zeleninou a játrovou rýží", + price: "39\xA0Kč", + isSoup: true, + }, + { + amount: "-", + name: "Pečené vepřové koleno, křen, hořčice, chléb", + price: "320\xA0Kč", + isSoup: false, + } + ], + [ + { + amount: "-", + name: "Zeleninová polévka s kuskusem", + price: "39\xA0Kč", + isSoup: true, + }, + { + amount: "-", + name: "Poutine (trhané vepřové, hranolky, sýr, čalamáda, pikantní omáčka)", + price: "190\xA0Kč", + isSoup: false, + } + ], + [ + { + amount: "-", + name: "Hrachová polévka s uzeninou", + price: "39\xA0Kč", + isSoup: true, + }, + { + amount: "-", + name: "Vepřový řízek z kotlety, domácí bramborový salát", + price: "170\xA0Kč", + isSoup: false, + } + ], + [ + { + amount: "-", + name: "Cibulačka se sýrem", + price: "39\xA0Kč", + isSoup: true, + }, + { + amount: "-", + name: "Burger z Chuck rollu, hranolky, tatarská omáčka", + price: "200\xA0Kč", + isSoup: false, + } + ], ] } @@ -1180,8 +1270,11 @@ const MOCK_PIZZA_LIST = [ } ] -export const getTodayMock = () => { - return '2023-05-31'; // středa +/** + * Funkce vrací mock datu ve formátu YYYY-MM-DD + */ +export const getTodayMock = (): Date => { + return new Date('2025-01-10'); // pátek } export const getMenuSladovnickaMock = () => { @@ -1196,6 +1289,10 @@ export const getMenuTechTowerMock = () => { return MOCK_DATA['techTower']; } +export const getMenuZastavkaUmichalaMock = () => { + return MOCK_DATA['zastavkaUmichala']; +} + export const getPizzaListMock = () => { return MOCK_PIZZA_LIST; } \ No newline at end of file diff --git a/server/src/restaurants.ts b/server/src/restaurants.ts index 00de3d1..fafd1ae 100644 --- a/server/src/restaurants.ts +++ b/server/src/restaurants.ts @@ -1,7 +1,8 @@ import axios from "axios"; import { load } from 'cheerio'; import { Food } from "../../types"; -import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock } from "./mock"; +import {getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock, getMenuZastavkaUmichalaMock} from "./mock"; +import {formatDate, getDayOfWeekIndex, getIsWeekend} from "./utils"; // Fráze v názvech jídel, které naznačují že se jedná o polévku const SOUP_NAMES = [ @@ -25,6 +26,8 @@ const DAYS_IN_WEEK = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', ' const SLADOVNICKA_URL = 'https://sladovnicka.unasplzenchutna.cz/cz/denni-nabidka'; const U_MOTLIKU_URL = 'https://www.umotliku.cz/menu'; const TECHTOWER_URL = 'https://www.equifarm.cz/restaurace-techtower'; +const ZASTAVKAUMICHALA_URL = 'https://www.zastavkaumichala.cz'; +const SENKSERIKOVA_URL = 'https://www.menicka.cz/6561-pivovarsky-senk-serikova.html'; /** * Vrátí true, pokud předaný text obsahuje některé ze slov, které naznačuje, že se jedná o polévku. @@ -326,4 +329,54 @@ export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = fal } } return result; -} \ No newline at end of file +} + +/** + * Získá obědovou nabídku ZastavkaUmichala pro jeden týden. + * + * @param firstDayOfWeek první den v týdnu, pro který získat menu + * @param mock zda vrátit mock data + * @returns seznam jídel pro dané datum + */ +export const getMenuZastavkaUmichala = async (firstDayOfWeek: Date, mock: boolean = false): Promise => { + if (mock) { + return getMenuZastavkaUmichalaMock(); + } + + const nowDate = new Date().getDate(); + const result: Food[][] = []; + for (let dayIndex = 0; dayIndex < 5; dayIndex++) { + const currentDate = new Date(firstDayOfWeek); + currentDate.setDate(firstDayOfWeek.getDate() + dayIndex); + + // if (currentDate < now) { + if (currentDate.getDate() !== nowDate) { + result[dayIndex] = [{ + amount: undefined, + name: "Pro tento den není uveřejněna nabídka jídel", + price: "", + isSoup: false, + }]; + continue; + } else { + // let dateString = formatDate(currentDate, 'DD.MM.YYYY'); + // const html = await getHtml(ZASTAVKAUMICHALA_URL + '/?do=dailyMenu-changeDate&dailyMenu-dateString=' + dateString); + const html = await getHtml(ZASTAVKAUMICHALA_URL); + const $ = load(html); + + // const row = $($('.foodsList li')[0]).text(); + + const currentDayFood: Food[] = []; + $('.foodsList li').each((index, element) => { + currentDayFood.push({ + amount: '-', + name: sanitizeText($(element).contents().not('span').text()), + price: sanitizeText($(element).find('span').text()), + isSoup: (index === 0), + }); + }); + result[dayIndex] = currentDayFood; + } + } + return result; +} diff --git a/server/src/service.ts b/server/src/service.ts index eb0fcb6..0daa62f 100644 --- a/server/src/service.ts +++ b/server/src/service.ts @@ -1,7 +1,7 @@ import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getHumanDate, getIsWeekend, getWeekNumber } from "./utils"; import { ClientData, Restaurants, DayMenu, DepartureTime, DayData, WeekMenu, LocationKey } from "../../types"; import getStorage from "./storage"; -import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants"; +import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku, getMenuZastavkaUmichala } from "./restaurants"; import { getTodayMock } from "./mock"; const storage = getStorage(); @@ -10,7 +10,7 @@ const MENU_PREFIX = 'menu'; /** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */ export function getToday(): Date { if (process.env.MOCK_DATA === 'true') { - return new Date(getTodayMock()); + return getTodayMock(); } return new Date(); } @@ -61,6 +61,7 @@ export async function getData(date?: Date): Promise { [Restaurants.SLADOVNICKA]: await getRestaurantMenu(Restaurants.SLADOVNICKA, date), // [Restaurants.UMOTLIKU]: await getRestaurantMenu(Restaurants.UMOTLIKU, date), [Restaurants.TECHTOWER]: await getRestaurantMenu(Restaurants.TECHTOWER, date), + [Restaurants.ZASTAVKAUMICHALA]: await getRestaurantMenu(Restaurants.ZASTAVKAUMICHALA, date), } clientData = await addVolatileData(clientData); return clientData; @@ -168,6 +169,19 @@ export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): P } catch (e: any) { console.error("Selhalo načtení jídel pro podnik TechTower", e); } + case Restaurants.ZASTAVKAUMICHALA: + try { + const zastavkaUmichalaFood = await getMenuZastavkaUmichala(firstDay, mock); + for (let i = 0; i < zastavkaUmichalaFood.length; i++) { + menus[i][restaurant]!.food = zastavkaUmichalaFood[i]; + if (zastavkaUmichalaFood[i]?.length === 1 && zastavkaUmichalaFood[i][0].name === 'Pro tento den není uveřejněna nabídka jídel.') { + menus[i][restaurant]!.closed = true; + } + } + break; + } catch (e: any) { + console.error("Selhalo načtení jídel pro podnik Zastávka u Michala", e); + } } await storage.setData(getMenuKey(usedDate), menus); } diff --git a/server/src/utils.ts b/server/src/utils.ts index 98d031e..e8e8e35 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -1,11 +1,13 @@ import { Choices, LocationKey } from "../../types"; /** Vrátí datum v ISO formátu. */ -export function formatDate(date: Date) { - let currentDay = String(date.getDate()).padStart(2, '0'); - let currentMonth = String(date.getMonth() + 1).padStart(2, "0"); - let currentYear = date.getFullYear(); - return `${currentYear}-${currentMonth}-${currentDay}`; +export function formatDate(date: Date, format?: string) { + let day = String(date.getDate()).padStart(2, '0'); + let month = String(date.getMonth() + 1).padStart(2, "0"); + let year = String(date.getFullYear()); + + const f = (format === undefined) ? 'YYYY-MM-DD' : format; + return f.replace('DD', day).replace('MM', month).replace('YYYY', year); } /** Vrátí human-readable reprezentaci předaného data pro zobrazení. */ diff --git a/types/Types.ts b/types/Types.ts index d9764e2..bcd3b8b 100644 --- a/types/Types.ts +++ b/types/Types.ts @@ -3,6 +3,7 @@ export enum Restaurants { SLADOVNICKA = 'sladovnicka', // UMOTLIKU = 'uMotliku', TECHTOWER = 'techTower', + ZASTAVKAUMICHALA = 'zastavkaUmichala', } export type FoodChoices = {