From a9709a944fa398b417a42ed2ca78781e9a497bf5 Mon Sep 17 00:00:00 2001 From: Martin Berka Date: Mon, 4 Aug 2025 17:30:04 +0200 Subject: [PATCH] =?UTF-8?q?=C3=9Aprava=20pro=20novou=20podobu=20str=C3=A1n?= =?UTF-8?q?ek=20Sladovnick=C3=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/App.tsx | 1 + server/src/restaurants.ts | 114 ++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 65 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 829ed7b..1eed593 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -415,6 +415,7 @@ function App() { Poslední změny: {dayIndex != null && diff --git a/server/src/restaurants.ts b/server/src/restaurants.ts index efb39d4..9d6f681 100644 --- a/server/src/restaurants.ts +++ b/server/src/restaurants.ts @@ -23,7 +23,7 @@ const SOUP_NAMES = [ const DAYS_IN_WEEK = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota', 'neděle']; // URL na týdenní menu jednotlivých restaurací -const SLADOVNICKA_URL = 'https://sladovnicka.unasplzenchutna.cz/cz/denni-nabidka'; +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'; @@ -78,81 +78,65 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f const html = await getHtml(SLADOVNICKA_URL); const $ = load(html); - const list = $('ul.tab-links').children(); + const menuContentElements = $('#daily-menu-content-list').children('[id^="daily-menu-content-"]'); + // Prozatím předpokládáme, že budou mít vždy elementy pro všech 5 dní v týdnu, i pokud bude zavřeno + if (menuContentElements.length < 5) { + throw Error("Neočekávaný počet dní v menu Sladovnické: " + menuContentElements.length + ", očekáváno 5 (možná je některý den zavřeno?)"); + } + const result: Food[][] = []; for (let dayIndex = 0; dayIndex < 5; dayIndex++) { - const currentDate = new Date(firstDayOfWeek); - currentDate.setDate(firstDayOfWeek.getDate() + dayIndex); - const searchedDayText = `${currentDate.getDate()}.${currentDate.getMonth() + 1}.${capitalize(DAYS_IN_WEEK[dayIndex])}`; - // Najdeme index pro vstupní datum (např. při svátcích bude posunutý) - // TODO validovat, že vstupní datum je v aktuálním týdnu - // TODO tenhle způsob je zbytečně komplikovaný - stačilo by hledat rovnou v div.tab-content, protože každý den tam má datum taky (akorát je print-only) - let index = undefined; - list.each((i, dayRow) => { - const rowText = $(dayRow).first().text().trim(); - if (rowText === searchedDayText) { - index = i; - } - }) - if (index === undefined) { - // Pravděpodobně svátek, nebo je zavřeno - result[dayIndex] = [{ - amount: undefined, - name: "Pro daný den nebyla nalezena denní nabídka", - price: "", - isSoup: false, - }]; - continue; + const dayChildren = $(menuContentElements[dayIndex]).children(); + // Prozatím předpokládáme, že budou mít vždy polévku a hlavní jídla + if (dayChildren.length < 2) { + throw Error("Neočekávaný počet children v menu Sladovnické pro den " + dayIndex + ": " + dayChildren.length + ", očekávány alespoň 2 (polévka a hlavní jídlo)"); } - // Dle dohledaného indexu najdeme správný tabpanel - const rows = $('div.tab-content').children(); - if (index >= rows.length) { - throw Error("V HTML nebyl nalezen řádek menu pro index " + index); - } - const tabPanel = $(rows.get(index)); - - // Opětovná validace, že daný tabpanel je pro vstupní datum - const headers = tabPanel.find('h2'); - if (headers.length !== 3) { - throw Error("Neočekávaný počet elementů h2 v menu pro datum " + searchedDayText + ", očekávány 3, ale nalezeno bylo " + headers.length); - } - const dayText = $(headers.get(0)).text().trim(); - if (dayText !== searchedDayText) { - throw Error("Neočekávaný datum na řádce nalezeného dne: '" + dayText + "', ale očekáváno bylo '" + searchedDayText + "'"); - } - - // V tabpanelu očekáváme dvě tabulky - pro polévku a pro hlavní jídlo - const tables = tabPanel.find('table'); - if (tables.length !== 2) { - throw Error("Neočekávaný počet tabulek na řádce nalezeného dne: " + tables.length + ", ale očekávány byly 2"); - } - const currentDayFood: Food[] = []; - // Polévka - div -> table -> tbody -> tr -> 3x td - const soupCells = $(tables.get(0)).children().first().children().first().children(); + // Parsování polévky + const soupElement = dayChildren.get(0); + const soupTable = $(soupElement).find('table tbody tr'); + const soupCells = soupTable.children('td'); if (soupCells.length !== 3) { throw Error("Neočekávaný počet buněk v tabulce polévky: " + soupCells.length + ", ale očekávány byly 3"); } + + const soupAmount = sanitizeText($(soupCells.get(0)).text()); + const soupName = sanitizeText($(soupCells.get(1)).text()); + const soupPrice = sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0')); + + // Parsování hlavních jídel + const mainCourseElement = dayChildren.get(1); + const mainCourseTable = $(mainCourseElement).find('table tbody'); + const mainCourseRows = mainCourseTable.children('tr'); + + const currentDayFood: Food[] = []; + + // Přidáme polévku do seznamu jídel currentDayFood.push({ - amount: sanitizeText($(soupCells.get(0)).text()), - name: sanitizeText($(soupCells.get(1)).text()), - price: sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0')), + amount: soupAmount, + name: soupName, + price: soupPrice, isSoup: true, }); - // Hlavní jídla - div -> table -> tbody -> 3x tr - const mainCourseRows = $(tables.get(1)).children().first().children(); - mainCourseRows.each((i, foodRow) => { - const foodCells = $(foodRow).children(); - if (foodCells.length !== 3) { - throw Error("Neočekávaný počet buněk v řádku jídla: " + foodCells.length + ", ale očekávány byly 3"); + + // Projdeme všechny řádky hlavních jídel + mainCourseRows.each((i, row) => { + const cells = $(row).children('td'); + const amount = sanitizeText($(cells.get(0)).text()); + const name = sanitizeText($(cells.get(1)).text()); + const price = sanitizeText($(cells.get(2)).text().replace(' ', '\xA0')); + + // Přeskočíme prázdné řádky (první řádek může být prázdný) + if (name.trim().length > 0) { + currentDayFood.push({ + amount, + name, + price, + isSoup: false, + }); } - currentDayFood.push({ - amount: sanitizeText($(foodCells.get(0)).text()), - name: sanitizeText($(foodCells.get(1)).text()), - price: sanitizeText($(foodCells.get(2)).text().replace(' ', '\xA0')), - isSoup: false, - }); - }) + }); + result[dayIndex] = currentDayFood; } return result;