diff --git a/client/src/App.tsx b/client/src/App.tsx index f8ff9a5..cf22f6c 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -609,7 +609,7 @@ function App() { } else { content =
Chyba načtení dat
} - return + return
doAddClickFoodChoice(location)}>
@@ -684,7 +684,7 @@ function App() {
} - + {Object.keys(Restaurant).map(key => { const locationKey = key as Restaurant; return food[locationKey] && renderFoodTable(locationKey, food[locationKey]); @@ -699,7 +699,11 @@ function App() { {Object.entries(LunchChoice) .filter(entry => { const locationKey = entry[0] as Restaurant; - return !food[locationKey]?.closed; + // Podniky, pro které server nevrátil menu (dočasně uzavřené), nenabízíme + if (Object.keys(Restaurant).includes(locationKey) && !food?.[locationKey]) { + return false; + } + return !food?.[locationKey]?.closed; }) .map(entry => )} diff --git a/server/changelogs/2026-10-09.json b/server/changelogs/2026-10-09.json index b0d6eba..c8c8269 100644 --- a/server/changelogs/2026-10-09.json +++ b/server/changelogs/2026-10-09.json @@ -1,3 +1,4 @@ [ - "Opraveno načítání jídelního lístku TechTower po změně jejich webu" + "Opraveno načítání jídelního lístku TechTower po změně jejich webu", + "Šenk Šeříková je dočasně skryt, protože je podnik uzavřen" ] diff --git a/server/src/routes/devRoutes.ts b/server/src/routes/devRoutes.ts index 8df7a2f..905011b 100644 --- a/server/src/routes/devRoutes.ts +++ b/server/src/routes/devRoutes.ts @@ -1,12 +1,12 @@ import express, { Request } from "express"; -import { getDateForWeekIndex, getData, getRestaurantMenu, getToday, initIfNeeded } from "../service"; +import { getDateForWeekIndex, getData, isRestaurantDisabled, getActiveRestaurants, getRestaurantMenu, getToday, initIfNeeded } from "../service"; import { formatDate, getDayOfWeekIndex } from "../utils"; import getStorage from "../storage"; import { getWebsocket } from "../websocket"; import { getLogin } from "../auth"; import { parseToken } from "../utils"; import webpush from 'web-push'; -import { ClientData, GroupState, MealSlot, OrderGroupMember, TrackingProvider } from "../../../types/gen/types.gen"; +import { ClientData, GroupState, MealSlot, OrderGroupMember, Restaurant, TrackingProvider } from "../../../types/gen/types.gen"; import { getStores } from "../stores"; import { createGroup } from "../groups"; import { @@ -36,7 +36,7 @@ const LUNCH_CHOICES = [ 'OBJEDNAVAM', 'NEOBEDVAM', 'ROZHODUJI', -]; +].filter(choice => !isRestaurantDisabled(choice as Restaurant)); // Náhodné názvy jídel do poznámek mock objednávek const MOCK_ORDER_FOODS = [ @@ -72,12 +72,7 @@ const MAX_ORDER_MEMBER_COUNT = 50; const MAX_ORDER_AMOUNT = 100000; // Restaurace s menu -const RESTAURANTS_WITH_MENU = [ - 'SLADOVNICKA', - 'TECHTOWER', - 'ZASTAVKAUMICHALA', - 'SENKSERIKOVA', -]; +const RESTAURANTS_WITH_MENU = getActiveRestaurants(); /** * Middleware pro kontrolu DEV režimu @@ -138,7 +133,7 @@ async function generateMockDataForDay(dayIndex: number, requestedCount?: number) }; // Pokud má restaurace menu, vybereme náhodné jídlo - if (RESTAURANTS_WITH_MENU.includes(choice) && menus[choice]?.length) { + if (RESTAURANTS_WITH_MENU.includes(choice as Restaurant) && menus[choice]?.length) { const foods = menus[choice]; // Vybereme náhodné jídlo (ne polévku) const mainFoods = foods.filter((f: any) => !f.isSoup); diff --git a/server/src/routes/foodRoutes.ts b/server/src/routes/foodRoutes.ts index ba0c810..3a48f90 100644 --- a/server/src/routes/foodRoutes.ts +++ b/server/src/routes/foodRoutes.ts @@ -1,6 +1,6 @@ import express, { Request, Response } from "express"; import { getLogin, getTrusted } from "../auth"; -import { addChoice, getDateForWeekIndex, getToday, removeChoice, removeChoices, updateDepartureTime, updateNote, fetchRestaurantWeekMenuData, saveRestaurantWeekMenu, updateBuyer } from "../service"; +import { addChoice, getActiveRestaurants, getDateForWeekIndex, getToday, removeChoice, removeChoices, updateDepartureTime, updateNote, fetchRestaurantWeekMenuData, saveRestaurantWeekMenu, updateBuyer } from "../service"; import { getDayOfWeekIndex, parseToken, getFirstWorkDayOfWeek } from "../utils"; import { getWebsocket } from "../websocket"; import { callNotifikace } from "../notifikace"; @@ -232,8 +232,8 @@ export const refreshMetoda = async (req: Request, res: Response) => { return res.status(400).json({ error: "ještě neumim TODO..." }); } try { - // Pro všechny restaurace refreshni menu na aktuální týden - const restaurants = ["SLADOVNICKA", "TECHTOWER", "ZASTAVKAUMICHALA", "SENKSERIKOVA"] as const; + // Pro všechny aktivní restaurace refreshni menu na aktuální týden + const restaurants = getActiveRestaurants(); const firstDay = getFirstWorkDayOfWeek(getToday()); const results: Record = {}; const successfulRestaurants: string[] = []; diff --git a/server/src/service.ts b/server/src/service.ts index bb75048..b91c78e 100644 --- a/server/src/service.ts +++ b/server/src/service.ts @@ -9,6 +9,24 @@ import { ClientData, DepartureTime, LunchChoice, LunchChoices, MealSlot, PizzaDa const storage = getStorage(); const MENU_PREFIX = 'menu'; +/** + * Dočasně uzavřené podniky – nezobrazují se na hlavní stránce, nelze je zvolit + * a neprovádí se pro ně načítání menu z jejich webu. + */ +export const DISABLED_RESTAURANTS: readonly Restaurant[] = [ + Restaurant.SENKSERIKOVA, // dočasně uzavřen +]; + +/** Vrátí true, pokud je podnik dočasně vyřazen z nabídky. */ +export function isRestaurantDisabled(restaurant: Restaurant | LunchChoice): boolean { + return DISABLED_RESTAURANTS.includes(restaurant as Restaurant); +} + +/** Vrátí podniky s menu, které nejsou dočasně vyřazeny. */ +export function getActiveRestaurants(): Restaurant[] { + return (Object.values(Restaurant) as Restaurant[]).filter(r => !isRestaurantDisabled(r)); +} + function getDataKey(date: Date, slot?: MealSlot): string { const base = formatDate(date); return slot === MealSlot.EXTRA ? `${base}_extra` : base; @@ -55,13 +73,12 @@ export async function getData(date?: Date, slot?: MealSlot): Promise if (slot === MealSlot.EXTRA) { clientData.stores = await getStores(); } else { - clientData.menus = { - SLADOVNICKA: await getRestaurantMenu('SLADOVNICKA', date), - // UMOTLIKU: await getRestaurantMenu('UMOTLIKU', date), - TECHTOWER: await getRestaurantMenu('TECHTOWER', date), - ZASTAVKAUMICHALA: await getRestaurantMenu('ZASTAVKAUMICHALA', date), - SENKSERIKOVA: await getRestaurantMenu('SENKSERIKOVA', date), + // Dočasně vyřazené podniky se do odpovědi vůbec nezahrnují (klient je pak nezobrazí) + const menus: RestaurantDayMenuMap = {}; + for (const restaurant of getActiveRestaurants()) { + menus[restaurant] = await getRestaurantMenu(restaurant, date); } + clientData.menus = menus; // Očistí volby jídel odkazující na indexy, které v aktuálním menu již neexistují. // Pokud se v odpovědi něco změnilo, promítne očistu i do uložených dat (self-heal). if (pruneStaleFoodChoices(clientData.choices, clientData.menus)) { @@ -236,7 +253,8 @@ export async function getRestaurantMenu(restaurant: Restaurant, date?: Date, for const usedDate = date ?? getToday(); const dayOfWeekIndex = getDayOfWeekIndex(usedDate); const now = new Date().getTime(); - if (getIsWeekend(usedDate)) { + // O víkendu a u dočasně vyřazených podniků menu vůbec nenačítáme + if (getIsWeekend(usedDate) || isRestaurantDisabled(restaurant)) { return { lastUpdate: now, closed: true, @@ -468,6 +486,9 @@ export async function addChoice(login: string, trusted: boolean, locationKey: Lu await initIfNeeded(usedDate, slot); let data = await getClientData(usedDate, slot); validateTrusted(data, login, trusted); + if (isRestaurantDisabled(locationKey)) { + throw new Error(`Podnik ${locationKey} je dočasně uzavřen`); + } await validateFoodIndex(locationKey, foodIndex, date); if (!slot || slot === MealSlot.OBED) {