Parsování jídel na celý týden
This commit is contained in:
		
							parent
							
								
									74893c38eb
								
							
						
					
					
						commit
						ca9a7c5c23
					
				| @ -14,7 +14,7 @@ import './App.css'; | |||||||
| import { SelectSearchOption } from 'react-select-search'; | import { SelectSearchOption } from 'react-select-search'; | ||||||
| import { faCircleCheck, faTrashCan } from '@fortawesome/free-regular-svg-icons'; | import { faCircleCheck, faTrashCan } from '@fortawesome/free-regular-svg-icons'; | ||||||
| import { useBank } from './context/bank'; | import { useBank } from './context/bank'; | ||||||
| import { ClientData, Restaurants, Food, Order, Locations, PizzaOrder, PizzaDayState, FoodChoices, Menu } from './types'; | import { ClientData, Restaurants, Food, Order, Locations, PizzaOrder, PizzaDayState, FoodChoices, DayMenu } from './types'; | ||||||
| import Footer from './components/Footer'; | import Footer from './components/Footer'; | ||||||
| import { faChainBroken, faChevronLeft, faChevronRight, faGear, faSatelliteDish, faSearch } from '@fortawesome/free-solid-svg-icons'; | import { faChainBroken, faChevronLeft, faChevronRight, faGear, faSatelliteDish, faSearch } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import Loader from './components/Loader'; | import Loader from './components/Loader'; | ||||||
| @ -28,7 +28,7 @@ function App() { | |||||||
|   const bank = useBank(); |   const bank = useBank(); | ||||||
|   const [isConnected, setIsConnected] = useState<boolean>(false); |   const [isConnected, setIsConnected] = useState<boolean>(false); | ||||||
|   const [data, setData] = useState<ClientData>(); |   const [data, setData] = useState<ClientData>(); | ||||||
|   const [food, setFood] = useState<{ [key in Restaurants]?: Menu }>(); |   const [food, setFood] = useState<{ [key in Restaurants]?: DayMenu }>(); | ||||||
|   const [myOrder, setMyOrder] = useState<Order>(); |   const [myOrder, setMyOrder] = useState<Order>(); | ||||||
|   const [foodChoiceList, setFoodChoiceList] = useState<Food[]>(); |   const [foodChoiceList, setFoodChoiceList] = useState<Food[]>(); | ||||||
|   const [closed, setClosed] = useState<boolean>(false); |   const [closed, setClosed] = useState<boolean>(false); | ||||||
| @ -288,7 +288,7 @@ function App() { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const renderFoodTable = (name: string, menu: Menu) => { |   const renderFoodTable = (name: string, menu: DayMenu) => { | ||||||
|     let content; |     let content; | ||||||
|     if (menu?.closed) { |     if (menu?.closed) { | ||||||
|       content = <h3>Zavřeno</h3> |       content = <h3>Zavřeno</h3> | ||||||
| @ -352,13 +352,7 @@ function App() { | |||||||
|           <Alert variant={'primary'}> |           <Alert variant={'primary'}> | ||||||
|             Poslední změny: |             Poslední změny: | ||||||
|             <ul> |             <ul> | ||||||
|               <li>Oprava generování QR kódů pro Pizza day</li> |               <li>Parsování jídelních lístků na celý týden</li> | ||||||
|               <li>Serverová validace času odchodu</li> |  | ||||||
|               <li>Loader při zakládání Pizza day</li> |  | ||||||
|               <li>Možnost ručního zadání příplatku k Pizza day objednávkám</li> |  | ||||||
|               <li>Vylepšená detekce uzavření pro podniky Sladovnická a TechTower</li> |  | ||||||
|               <li>Úprava zvýraznění aktuálního dne</li> |  | ||||||
|               <li>Možnost hlasování o nových funkcích</li> |  | ||||||
|             </ul> |             </ul> | ||||||
|           </Alert> |           </Alert> | ||||||
|           {dayIndex != null && |           {dayIndex != null && | ||||||
|  | |||||||
| @ -1124,16 +1124,16 @@ export const getTodayMock = () => { | |||||||
|     return '2023-05-31'; // středa
 |     return '2023-05-31'; // středa
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const getMenuSladovnickaMock = (date: Date) => { | export const getMenuSladovnickaMock = () => { | ||||||
|     return MOCK_DATA['sladovnicka'][getDayOfWeekIndex(date)]; |     return MOCK_DATA['sladovnicka']; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const getMenuUMotlikuMock = (date: Date) => { | export const getMenuUMotlikuMock = () => { | ||||||
|     return MOCK_DATA['uMotliku'][getDayOfWeekIndex(date)]; |     return MOCK_DATA['uMotliku']; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const getMenuTechTowerMock = (date: Date) => { | export const getMenuTechTowerMock = () => { | ||||||
|     return MOCK_DATA['techTower'][getDayOfWeekIndex(date)]; |     return MOCK_DATA['techTower']; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const getPizzaListMock = () => { | export const getPizzaListMock = () => { | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { formatDate } from "./utils"; | import { formatDate } from "./utils"; | ||||||
| import { callNotifikace } from "./notifikace"; | import { callNotifikace } from "./notifikace"; | ||||||
| import { generateQr } from "./qr"; | import { generateQr } from "./qr"; | ||||||
| import { ClientData, PizzaDayState, UdalostEnum, Pizza, PizzaSize, Order, PizzaOrder } from "../../types"; | import { ClientData, PizzaDayState, UdalostEnum, Pizza, PizzaSize, Order, PizzaOrder, DayData } from "../../types"; | ||||||
| import getStorage from "./storage"; | import getStorage from "./storage"; | ||||||
| import { downloadPizzy } from "./chefie"; | import { downloadPizzy } from "./chefie"; | ||||||
| import { getToday, initIfNeeded } from "./service"; | import { getToday, initIfNeeded } from "./service"; | ||||||
| @ -15,7 +15,7 @@ const storage = getStorage(); | |||||||
| export async function getPizzaList(): Promise<Pizza[] | undefined> { | export async function getPizzaList(): Promise<Pizza[] | undefined> { | ||||||
|     await initIfNeeded(); |     await initIfNeeded(); | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     let clientData: ClientData = await storage.getData(today); |     let clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaList) { |     if (!clientData.pizzaList) { | ||||||
|         const mock = process.env.MOCK_DATA === 'true'; |         const mock = process.env.MOCK_DATA === 'true'; | ||||||
|         clientData = await savePizzaList(await downloadPizzy(mock)); |         clientData = await savePizzaList(await downloadPizzy(mock)); | ||||||
| @ -31,7 +31,7 @@ export async function getPizzaList(): Promise<Pizza[] | undefined> { | |||||||
| export async function savePizzaList(pizzaList: Pizza[]): Promise<ClientData> { | export async function savePizzaList(pizzaList: Pizza[]): Promise<ClientData> { | ||||||
|     await initIfNeeded(); |     await initIfNeeded(); | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     clientData.pizzaList = pizzaList; |     clientData.pizzaList = pizzaList; | ||||||
|     clientData.pizzaListLastUpdate = new Date(); |     clientData.pizzaListLastUpdate = new Date(); | ||||||
|     await storage.setData(today, clientData); |     await storage.setData(today, clientData); | ||||||
| @ -44,7 +44,7 @@ export async function savePizzaList(pizzaList: Pizza[]): Promise<ClientData> { | |||||||
| export async function createPizzaDay(creator: string): Promise<ClientData> { | export async function createPizzaDay(creator: string): Promise<ClientData> { | ||||||
|     await initIfNeeded(); |     await initIfNeeded(); | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     if (clientData.pizzaDay) { |     if (clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den již existuje"); |         throw Error("Pizza day pro dnešní den již existuje"); | ||||||
|     } |     } | ||||||
| @ -61,7 +61,7 @@ export async function createPizzaDay(creator: string): Promise<ClientData> { | |||||||
|  */ |  */ | ||||||
| export async function deletePizzaDay(login: string): Promise<ClientData> { | export async function deletePizzaDay(login: string): Promise<ClientData> { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
| @ -82,7 +82,7 @@ export async function deletePizzaDay(login: string): Promise<ClientData> { | |||||||
|  */ |  */ | ||||||
| export async function addPizzaOrder(login: string, pizza: Pizza, size: PizzaSize) { | export async function addPizzaOrder(login: string, pizza: Pizza, size: PizzaSize) { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
| @ -118,7 +118,7 @@ export async function addPizzaOrder(login: string, pizza: Pizza, size: PizzaSize | |||||||
|  */ |  */ | ||||||
| export async function removePizzaOrder(login: string, pizzaOrder: PizzaOrder) { | export async function removePizzaOrder(login: string, pizzaOrder: PizzaOrder) { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
| @ -149,7 +149,7 @@ export async function removePizzaOrder(login: string, pizzaOrder: PizzaOrder) { | |||||||
|  */ |  */ | ||||||
| export async function lockPizzaDay(login: string) { | export async function lockPizzaDay(login: string) { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
| @ -172,7 +172,7 @@ export async function lockPizzaDay(login: string) { | |||||||
|  */ |  */ | ||||||
| export async function unlockPizzaDay(login: string) { | export async function unlockPizzaDay(login: string) { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
| @ -195,7 +195,7 @@ export async function unlockPizzaDay(login: string) { | |||||||
|  */ |  */ | ||||||
| export async function finishPizzaOrder(login: string) { | export async function finishPizzaOrder(login: string) { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
| @ -220,7 +220,7 @@ export async function finishPizzaOrder(login: string) { | |||||||
|  */ |  */ | ||||||
| export async function finishPizzaDelivery(login: string, bankAccount?: string, bankAccountHolder?: string) { | export async function finishPizzaDelivery(login: string, bankAccount?: string, bankAccountHolder?: string) { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     const clientData: ClientData = await storage.getData(today); |     const clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
| @ -255,7 +255,7 @@ export async function finishPizzaDelivery(login: string, bankAccount?: string, b | |||||||
|  */ |  */ | ||||||
| export async function updatePizzaDayNote(login: string, note?: string) { | export async function updatePizzaDayNote(login: string, note?: string) { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     let clientData: ClientData = await storage.getData(today); |     let clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
| @ -282,7 +282,7 @@ export async function updatePizzaDayNote(login: string, note?: string) { | |||||||
|  */ |  */ | ||||||
| export async function updatePizzaFee(login: string, targetLogin: string, text?: string, price?: number) { | export async function updatePizzaFee(login: string, targetLogin: string, text?: string, price?: number) { | ||||||
|     const today = formatDate(getToday()); |     const today = formatDate(getToday()); | ||||||
|     let clientData: ClientData = await storage.getData(today); |     let clientData: DayData = await storage.getData(today); | ||||||
|     if (!clientData.pizzaDay) { |     if (!clientData.pizzaDay) { | ||||||
|         throw Error("Pizza day pro dnešní den neexistuje"); |         throw Error("Pizza day pro dnešní den neexistuje"); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ import axios from "axios"; | |||||||
| import { load } from 'cheerio'; | import { load } from 'cheerio'; | ||||||
| import { Food } from "../../types"; | import { Food } from "../../types"; | ||||||
| import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock } from "./mock"; | import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock } from "./mock"; | ||||||
| import { getDayOfWeekIndex } from "./utils"; |  | ||||||
| 
 | 
 | ||||||
| // Fráze v názvech jídel, které naznačují že se jedná o polévku
 | // Fráze v názvech jídel, které naznačují že se jedná o polévku
 | ||||||
| const SOUP_NAMES = ['polévka', 'česnečka', 'česnekový krém', 'cibulačka', 'vývar'] | const SOUP_NAMES = ['polévka', 'česnečka', 'česnekový krém', 'cibulačka', 'vývar'] | ||||||
| @ -48,27 +47,29 @@ const getHtml = async (url: string): Promise<any> => { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Získá obědovou nabídku Sladovnické pro předané datum. |  * Získá obědovou nabídku Sladovnické pro jeden týden. | ||||||
|  *  |  *  | ||||||
|  * @param date datum, pro které získat menu |  * @param firstDayOfWeek první den v týdnu, pro který získat menu | ||||||
|  * @param mock zda vrátit mock data |  * @param mock zda vrátit mock data | ||||||
|  * @returns seznam jídel pro dané datum |  * @returns seznam jídel pro daný týden | ||||||
|  */ |  */ | ||||||
| export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean = false): Promise<Food[]> => { | export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => { | ||||||
|     if (mock) { |     if (mock) { | ||||||
|         return getMenuSladovnickaMock(date); |         return getMenuSladovnickaMock(); | ||||||
|     } |  | ||||||
|     const todayDayIndex = getDayOfWeekIndex(date); |  | ||||||
|     if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
 |  | ||||||
|         return []; |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     const html = await getHtml(SLADOVNICKA_URL); |     const html = await getHtml(SLADOVNICKA_URL); | ||||||
|     const $ = load(html); |     const $ = load(html); | ||||||
|  | 
 | ||||||
|  |     const list = $('ul.tab-links').children(); | ||||||
|  |     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ý)
 |         // 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 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)
 |         // 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)
 | ||||||
|     const list = $('ul.tab-links').children(); |  | ||||||
|     const searchedDayText = `${date.getDate()}.${date.getMonth() + 1}.${capitalize(DAYS_IN_WEEK[todayDayIndex])}`; |  | ||||||
|         let index = undefined; |         let index = undefined; | ||||||
|         list.each((i, dayRow) => { |         list.each((i, dayRow) => { | ||||||
|             const rowText = $(dayRow).first().text().trim(); |             const rowText = $(dayRow).first().text().trim(); | ||||||
| @ -79,12 +80,13 @@ export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean | |||||||
|         }) |         }) | ||||||
|         if (index === undefined) { |         if (index === undefined) { | ||||||
|             // Pravděpodobně svátek, nebo je zavřeno
 |             // Pravděpodobně svátek, nebo je zavřeno
 | ||||||
|         return [{ |             result[dayIndex] = [{ | ||||||
|                 amount: undefined, |                 amount: undefined, | ||||||
|                 name: "Pro daný den nebyla nalezena denní nabídka", |                 name: "Pro daný den nebyla nalezena denní nabídka", | ||||||
|                 price: "", |                 price: "", | ||||||
|                 isSoup: false, |                 isSoup: false, | ||||||
|             }]; |             }]; | ||||||
|  |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Dle dohledaného indexu najdeme správný tabpanel
 |         // Dle dohledaného indexu najdeme správný tabpanel
 | ||||||
| @ -109,13 +111,13 @@ export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean | |||||||
|         if (tables.length !== 2) { |         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"); |             throw Error("Neočekávaný počet tabulek na řádce nalezeného dne: " + tables.length + ", ale očekávány byly 2"); | ||||||
|         } |         } | ||||||
|     const results: Food[] = []; |         const currentDayFood: Food[] = []; | ||||||
|         // Polévka - div -> table -> tbody -> tr -> 3x td
 |         // Polévka - div -> table -> tbody -> tr -> 3x td
 | ||||||
|         const soupCells = $(tables.get(0)).children().first().children().first().children(); |         const soupCells = $(tables.get(0)).children().first().children().first().children(); | ||||||
|         if (soupCells.length !== 3) { |         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"); |             throw Error("Neočekávaný počet buněk v tabulce polévky: " + soupCells.length + ", ale očekávány byly 3"); | ||||||
|         } |         } | ||||||
|     results.push({ |         currentDayFood.push({ | ||||||
|             amount: sanitizeText($(soupCells.get(0)).text()), |             amount: sanitizeText($(soupCells.get(0)).text()), | ||||||
|             name: sanitizeText($(soupCells.get(1)).text()), |             name: sanitizeText($(soupCells.get(1)).text()), | ||||||
|             price: sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0')), |             price: sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0')), | ||||||
| @ -123,60 +125,61 @@ export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean | |||||||
|         }); |         }); | ||||||
|         // Hlavní jídla - div -> table -> tbody -> 3x tr
 |         // Hlavní jídla - div -> table -> tbody -> 3x tr
 | ||||||
|         const mainCourseRows = $(tables.get(1)).children().first().children(); |         const mainCourseRows = $(tables.get(1)).children().first().children(); | ||||||
|     // Záměrně zakomentováno - občas je ve Sladovnické jídel méně
 |  | ||||||
|     // if (mainCourseRows.length !== 3) {
 |  | ||||||
|     //     throw Error("Neočekávaný počet řádek jídel: " + mainCourseRows.length + ", ale očekávány byly 3");
 |  | ||||||
|     // }
 |  | ||||||
|         mainCourseRows.each((i, foodRow) => { |         mainCourseRows.each((i, foodRow) => { | ||||||
|             const foodCells = $(foodRow).children(); |             const foodCells = $(foodRow).children(); | ||||||
|             if (foodCells.length !== 3) { |             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"); |                 throw Error("Neočekávaný počet buněk v řádku jídla: " + foodCells.length + ", ale očekávány byly 3"); | ||||||
|             } |             } | ||||||
|         results.push({ |             currentDayFood.push({ | ||||||
|                 amount: sanitizeText($(foodCells.get(0)).text()), |                 amount: sanitizeText($(foodCells.get(0)).text()), | ||||||
|                 name: sanitizeText($(foodCells.get(1)).text()), |                 name: sanitizeText($(foodCells.get(1)).text()), | ||||||
|                 price: sanitizeText($(foodCells.get(2)).text().replace(' ', '\xA0')), |                 price: sanitizeText($(foodCells.get(2)).text().replace(' ', '\xA0')), | ||||||
|                 isSoup: false, |                 isSoup: false, | ||||||
|             }); |             }); | ||||||
|         }) |         }) | ||||||
|     return results; |         result[index] = currentDayFood; | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Získá obědovou nabídku restaurace U Motlíků pro předané datum. |  * Získá obědovou nabídku restaurace U Motlíků pro jeden týden. | ||||||
|  *  |  *  | ||||||
|  * @param date datum, pro které získat menu |  * @param firstDayOfWeek první den v týdnu, pro který získat menu | ||||||
|  * @param mock zda vrátit mock data |  * @param mock zda vrátit mock data | ||||||
|  * @returns seznam jídel pro dané datum |  * @returns seznam jídel pro dané datum | ||||||
|  */ |  */ | ||||||
| export const getMenuUMotliku = async (date: Date = new Date(), mock: boolean = false): Promise<Food[]> => { | export const getMenuUMotliku = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => { | ||||||
|     if (mock) { |     if (mock) { | ||||||
|         return getMenuUMotlikuMock(date); |         return getMenuUMotlikuMock(); | ||||||
|     } |  | ||||||
|     const todayDayIndex = getDayOfWeekIndex(date); |  | ||||||
|     if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
 |  | ||||||
|         return []; |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     const html = await getHtml(U_MOTLIKU_URL); |     const html = await getHtml(U_MOTLIKU_URL); | ||||||
|     const $ = load(html); |     const $ = load(html); | ||||||
|  | 
 | ||||||
|     const table = $('table.table.table-hover.Xtable-striped').first(); |     const table = $('table.table.table-hover.Xtable-striped').first(); | ||||||
|     const body = table.children().first(); |     const body = table.children().first(); | ||||||
|     const rows = body.children(); |     const rows = body.children(); | ||||||
|     const results: Food[] = []; | 
 | ||||||
|  |     const result: Food[][] = []; | ||||||
|  |     for (let dayIndex = 0; dayIndex < 5; dayIndex++) { | ||||||
|  |         if (!(dayIndex in result)) { | ||||||
|  |             result[dayIndex] = []; | ||||||
|  |         } | ||||||
|         let parsing = false; |         let parsing = false; | ||||||
|         let isSoup = false; |         let isSoup = false; | ||||||
|         rows.each((i, row) => { |         rows.each((i, row) => { | ||||||
|             const firstChild = $(row).children().get(0); |             const firstChild = $(row).children().get(0); | ||||||
|             if (firstChild?.name == 'th') { |             if (firstChild?.name == 'th') { | ||||||
|                 const childText = $(firstChild).text(); |                 const childText = $(firstChild).text(); | ||||||
|             if (capitalize(DAYS_IN_WEEK[todayDayIndex]) === childText) { // Našli jsme dnešek
 |                 if (capitalize(DAYS_IN_WEEK[dayIndex]) === childText) { | ||||||
|                     parsing = true; |                     parsing = true; | ||||||
|                 } else if (parsing) { |                 } else if (parsing) { | ||||||
|                     // Narazili jsme na další den - konec parsování
 |                     // Narazili jsme na další den - konec parsování
 | ||||||
|                     parsing = false; |                     parsing = false; | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|         } else if (parsing) { // Jsme aktuálně na dnešním dni
 |             } else if (parsing) { | ||||||
|                 const children = $(row).children(); |                 const children = $(row).children(); | ||||||
|                 if (children.length === 1) { // Nadpis "Polévka" nebo "Hlavní jídlo"
 |                 if (children.length === 1) { // Nadpis "Polévka" nebo "Hlavní jídlo"
 | ||||||
|                     const foodType = children.first().text(); |                     const foodType = children.first().text(); | ||||||
| @ -194,7 +197,7 @@ export const getMenuUMotliku = async (date: Date = new Date(), mock: boolean = f | |||||||
|                     const amount = sanitizeText($(children.get(0)).text()); |                     const amount = sanitizeText($(children.get(0)).text()); | ||||||
|                     const name = sanitizeText($(children.get(1)).text()); |                     const name = sanitizeText($(children.get(1)).text()); | ||||||
|                     const price = sanitizeText($(children.get(2)).text()).replace(',-', '').replace(' ', '\xA0'); |                     const price = sanitizeText($(children.get(2)).text()).replace(',-', '').replace(' ', '\xA0'); | ||||||
|                 results.push({ |                     result[dayIndex].push({ | ||||||
|                         amount, |                         amount, | ||||||
|                         name, |                         name, | ||||||
|                         price, |                         price, | ||||||
| @ -203,26 +206,25 @@ export const getMenuUMotliku = async (date: Date = new Date(), mock: boolean = f | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|     return results; |     } | ||||||
|  |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Získá obědovou nabídku TechTower pro předané datum. |  * Získá obědovou nabídku TechTower pro jeden týden. | ||||||
|  *  |  *  | ||||||
|  * @param date datum, pro které získat menu |  * @param firstDayOfWeek první den v týdnu, pro který získat menu | ||||||
|  * @param mock zda vrátit mock data |  * @param mock zda vrátit mock data | ||||||
|  * @returns seznam jídel pro dané datum |  * @returns seznam jídel pro dané datum | ||||||
|  */ |  */ | ||||||
| export const getMenuTechTower = async (date: Date = new Date(), mock: boolean = false) => { | export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => { | ||||||
|     if (mock) { |     if (mock) { | ||||||
|         return getMenuTechTowerMock(date); |         return getMenuTechTowerMock(); | ||||||
|     } |  | ||||||
|     const todayDayIndex = getDayOfWeekIndex(date); |  | ||||||
|     if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
 |  | ||||||
|         return []; |  | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     const html = await getHtml(TECHTOWER_URL); |     const html = await getHtml(TECHTOWER_URL); | ||||||
|     const $ = load(html); |     const $ = load(html); | ||||||
|  | 
 | ||||||
|     const fonts = $('font.wsw-41'); |     const fonts = $('font.wsw-41'); | ||||||
|     let font = undefined; |     let font = undefined; | ||||||
|     fonts.each((i, f) => { |     fonts.each((i, f) => { | ||||||
| @ -236,11 +238,16 @@ export const getMenuTechTower = async (date: Date = new Date(), mock: boolean = | |||||||
|     // TODO validovat, že v textu nalezeného <font> je rozsah, do kterého spadá vstupní datum
 |     // TODO validovat, že v textu nalezeného <font> je rozsah, do kterého spadá vstupní datum
 | ||||||
|     const siblings = $(font).parent().parent().siblings(); |     const siblings = $(font).parent().parent().siblings(); | ||||||
|     let parsing = false; |     let parsing = false; | ||||||
|     const results: Food[] = []; |     const result: Food[][] = []; | ||||||
|  |     // TODO toto je kvůli poslednímu "línému" refaktoru neoptimální, stačilo by to projít jedním cyklem 
 | ||||||
|  |     for (let dayIndex = 0; dayIndex < 5; dayIndex++) { | ||||||
|  |         if (!(dayIndex in result)) { | ||||||
|  |             result[dayIndex] = []; | ||||||
|  |         } | ||||||
|         for (let i = 0; i < siblings.length; i++) { |         for (let i = 0; i < siblings.length; i++) { | ||||||
|             const text = $(siblings.get(i)).text().trim().replace('\t', '').replace('\n', ' '); |             const text = $(siblings.get(i)).text().trim().replace('\t', '').replace('\n', ' '); | ||||||
|             if (DAYS_IN_WEEK.includes(text)) { |             if (DAYS_IN_WEEK.includes(text)) { | ||||||
|             if (text === DAYS_IN_WEEK[todayDayIndex]) { |                 if (text === DAYS_IN_WEEK[dayIndex]) { | ||||||
|                     // Našli jsme dnešní den, odtud začínáme parsovat jídla
 |                     // Našli jsme dnešní den, odtud začínáme parsovat jídla
 | ||||||
|                     parsing = true; |                     parsing = true; | ||||||
|                     continue |                     continue | ||||||
| @ -261,7 +268,7 @@ export const getMenuTechTower = async (date: Date = new Date(), mock: boolean = | |||||||
|                     price = `${split.slice(1)[0]}\xA0Kč` |                     price = `${split.slice(1)[0]}\xA0Kč` | ||||||
|                     name = split[0] |                     name = split[0] | ||||||
|                 } |                 } | ||||||
|             results.push({ |                 result[dayIndex].push({ | ||||||
|                     amount: '-', |                     amount: '-', | ||||||
|                     name, |                     name, | ||||||
|                     price, |                     price, | ||||||
| @ -269,5 +276,6 @@ export const getMenuTechTower = async (date: Date = new Date(), mock: boolean = | |||||||
|                 }) |                 }) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     return results; |     } | ||||||
|  |     return result; | ||||||
| } | } | ||||||
| @ -1,10 +1,11 @@ | |||||||
| import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getHumanDate, getHumanTime, getIsWeekend } from "./utils"; | import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getHumanDate, getHumanTime, getIsWeekend, getLastWorkDayOfWeek, getWeekNumber } from "./utils"; | ||||||
| import { ClientData, Locations, Restaurants, Menu, DepartureTime } from "../../types"; | import { ClientData, Locations, Restaurants, DayMenu, DepartureTime, DayData, WeekMenu } from "../../types"; | ||||||
| import getStorage from "./storage"; | import getStorage from "./storage"; | ||||||
| import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants"; | import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants"; | ||||||
| import { getTodayMock } from "./mock"; | import { getTodayMock } from "./mock"; | ||||||
| 
 | 
 | ||||||
| const storage = getStorage(); | const storage = getStorage(); | ||||||
|  | const MENU_PREFIX = 'menu'; | ||||||
| 
 | 
 | ||||||
| /** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */ | /** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */ | ||||||
| export function getToday(): Date { | export function getToday(): Date { | ||||||
| @ -34,7 +35,7 @@ function getEmptyData(date?: Date): ClientData { | |||||||
|         isWeekend: getIsWeekend(usedDate), |         isWeekend: getIsWeekend(usedDate), | ||||||
|         weekIndex: getDayOfWeekIndex(usedDate), |         weekIndex: getDayOfWeekIndex(usedDate), | ||||||
|         choices: {}, |         choices: {}, | ||||||
|         departureTimes: Object.values(DepartureTime), |         departureTimes: Object.values(DepartureTime), // TODO tohle zmizí, bude se přidávat do dat dynamicky
 | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -42,73 +43,112 @@ function getEmptyData(date?: Date): ClientData { | |||||||
|  * Vrátí veškerá klientská data pro předaný den, nebo aktuální den, pokud není předán. |  * Vrátí veškerá klientská data pro předaný den, nebo aktuální den, pokud není předán. | ||||||
|  */ |  */ | ||||||
| export async function getData(date?: Date): Promise<ClientData> { | export async function getData(date?: Date): Promise<ClientData> { | ||||||
|     const dateString = formatDate(date ?? getToday()); |     const targetDate = date ?? getToday(); | ||||||
|     const data: ClientData = await storage.getData(dateString) || getEmptyData(date); |     const dateString = formatDate(targetDate); | ||||||
|     data.todayWeekIndex = getDayOfWeekIndex(getToday()); |     const data: DayData = await storage.getData(dateString) || getEmptyData(date); | ||||||
|     // Dotažení jídel, pokud je ještě nemáme
 |     const clientData: ClientData = { ...data }; | ||||||
|     if (!data.menus) { |     clientData.todayWeekIndex = getDayOfWeekIndex(getToday()); | ||||||
|         data.menus = { |     clientData.menus = { | ||||||
|             [Restaurants.SLADOVNICKA]: await getRestaurantMenu(Restaurants.SLADOVNICKA, date ?? getToday()), |         [Restaurants.SLADOVNICKA]: await getRestaurantMenu(Restaurants.SLADOVNICKA, targetDate), | ||||||
|             [Restaurants.UMOTLIKU]: await getRestaurantMenu(Restaurants.UMOTLIKU, date ?? getToday()), |         [Restaurants.UMOTLIKU]: await getRestaurantMenu(Restaurants.UMOTLIKU, targetDate), | ||||||
|             [Restaurants.TECHTOWER]: await getRestaurantMenu(Restaurants.TECHTOWER, date ?? getToday()), |         [Restaurants.TECHTOWER]: await getRestaurantMenu(Restaurants.TECHTOWER, targetDate), | ||||||
|     } |     } | ||||||
|         await storage.setData(dateString, data); |     return clientData; | ||||||
|     } | } | ||||||
|     return data; | 
 | ||||||
|  | /** | ||||||
|  |  * Vrátí klíč, pod kterým je uloženo menu pro předané datum. | ||||||
|  |  * | ||||||
|  |  * @param date datum | ||||||
|  |  * @returns databázový klíč | ||||||
|  |  */ | ||||||
|  | function getMenuKey(date: Date) { | ||||||
|  |     const weekNumber = getWeekNumber(date); | ||||||
|  |     return `${MENU_PREFIX}_${date.getFullYear()}_${weekNumber}`; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Vrátí menu restaurací pro předané datum, pokud již existují. | ||||||
|  |  *  | ||||||
|  |  * @param date datum | ||||||
|  |  * @returns menu restaurací pro předané datum | ||||||
|  |  */ | ||||||
|  | async function getMenu(date: Date): Promise<WeekMenu | undefined> { | ||||||
|  |     return await storage.getData(getMenuKey(date)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO přesun do restaurants.ts
 | // TODO přesun do restaurants.ts
 | ||||||
| /** | /** | ||||||
|  * Vrátí menu dané restaurace pro předaný den. Pokud neexistuje, provede jeho stažení a uložení do DB. |  * 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. | ||||||
|  *  |  *  | ||||||
|  * @param restaurant restaurace |  * @param restaurant restaurace | ||||||
|  * @param date datum |  * @param date datum, ke kterému získat menu | ||||||
|  * @param mock příznak, zda chceme pouze mock data |  * @param mock příznak, zda chceme pouze mock data | ||||||
|  */ |  */ | ||||||
| export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): Promise<Menu> { | export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): Promise<DayMenu> { | ||||||
|     await initIfNeeded(date); |     const usedDate = date ?? getToday(); | ||||||
|     const selectedDay = formatDate(date ?? getToday()); |     const dayOfWeekIndex = getDayOfWeekIndex(usedDate); | ||||||
|     const clientData: ClientData = await storage.getData(selectedDay); | 
 | ||||||
|     if (!clientData.menus) { |     let menus = await getMenu(usedDate); | ||||||
|         clientData.menus = {}; |     if (menus == null) { | ||||||
|         storage.setData(selectedDay, clientData); |         menus = []; | ||||||
|     } |     } | ||||||
|     if (!clientData.menus[restaurant]) { |     for (let i = 0; i < 5; i++) { | ||||||
|         clientData.menus[restaurant] = { |         if (menus[i] == null) { | ||||||
|  |             menus[i] = {}; | ||||||
|  |         } | ||||||
|  |         if (menus[i][restaurant] == null) { | ||||||
|  |             menus[i][restaurant] = { | ||||||
|                 lastUpdate: getHumanTime(new Date()), |                 lastUpdate: getHumanTime(new Date()), | ||||||
|                 closed: false, |                 closed: false, | ||||||
|                 food: [], |                 food: [], | ||||||
|             }; |             }; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     if (!menus[dayOfWeekIndex][restaurant]?.food?.length) { | ||||||
|  |         const firstDay = getFirstWorkDayOfWeek(usedDate); | ||||||
|         const mock = process.env.MOCK_DATA === 'true'; |         const mock = process.env.MOCK_DATA === 'true'; | ||||||
|         switch (restaurant) { |         switch (restaurant) { | ||||||
|             case Restaurants.SLADOVNICKA: |             case Restaurants.SLADOVNICKA: | ||||||
|                 const sladovnickaFood = await getMenuSladovnicka(date, mock); |                 const sladovnickaFood = await getMenuSladovnicka(firstDay, mock); | ||||||
|                 clientData.menus[restaurant]!.food = sladovnickaFood; |                 for (let i = 0; i < sladovnickaFood.length; i++) { | ||||||
|  |                     menus[i][restaurant]!.food = sladovnickaFood[i]; | ||||||
|                     // Velice chatrný a nespolehlivý způsob detekce uzavření...
 |                     // Velice chatrný a nespolehlivý způsob detekce uzavření...
 | ||||||
|                 if (sladovnickaFood.length === 1 && sladovnickaFood[0].name.toLowerCase() === 'pro daný den nebyla nalezena denní nabídka') { |                     if (sladovnickaFood[i].length === 1 && sladovnickaFood[i][0].name.toLowerCase() === 'pro daný den nebyla nalezena denní nabídka') { | ||||||
|                     clientData.menus[restaurant]!.closed = true; |                         menus[i][restaurant]!.closed = true; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case Restaurants.UMOTLIKU: |             case Restaurants.UMOTLIKU: | ||||||
|                 const uMotlikuFood = await getMenuUMotliku(date, mock); |                 const uMotlikuFood = await getMenuUMotliku(firstDay, mock); | ||||||
|                 clientData.menus[restaurant]!.food = uMotlikuFood; |                 for (let i = 0; i < uMotlikuFood.length; i++) { | ||||||
|                 if (uMotlikuFood.length === 1 && uMotlikuFood[0].name.toLowerCase() === 'zavřeno') { |                     menus[i][restaurant]!.food = uMotlikuFood[i]; | ||||||
|                     clientData.menus[restaurant]!.closed = true; |                     if (uMotlikuFood[i].length === 1 && uMotlikuFood[i][0].name.toLowerCase() === 'zavřeno') { | ||||||
|  |                         menus[i][restaurant]!.closed = true; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case Restaurants.TECHTOWER: |             case Restaurants.TECHTOWER: | ||||||
|                 const techTowerFood = await getMenuTechTower(date, mock); |                 const techTowerFood = await getMenuTechTower(firstDay, mock); | ||||||
|                 clientData.menus[restaurant]!.food = techTowerFood; |                 for (let i = 0; i < techTowerFood.length; i++) { | ||||||
|                 if (techTowerFood.length === 1 && techTowerFood[0].name.toLowerCase() === 'svátek') { |                     menus[i][restaurant]!.food = techTowerFood[i]; | ||||||
|                     clientData.menus[restaurant]!.closed = true; |                     if (techTowerFood[i].length === 1 && techTowerFood[i][0].name.toLowerCase() === 'svátek') { | ||||||
|  |                         menus[i][restaurant]!.closed = true; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|         storage.setData(selectedDay, clientData); |         await storage.setData(getMenuKey(usedDate), menus); | ||||||
|     } |     } | ||||||
|     return clientData.menus[restaurant]!; |     return menus[dayOfWeekIndex][restaurant]!; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Inicializuje výchozí data pro předané datum, nebo dnešek, pokud není datum předáno. | ||||||
|  |  *  | ||||||
|  |  * @param date datum | ||||||
|  |  */ | ||||||
| export async function initIfNeeded(date?: Date) { | export async function initIfNeeded(date?: Date) { | ||||||
|     const usedDate = formatDate(date ?? getToday()); |     const usedDate = formatDate(date ?? getToday()); | ||||||
|     const hasData = await storage.hasData(usedDate); |     const hasData = await storage.hasData(usedDate); | ||||||
| @ -128,7 +168,7 @@ export async function initIfNeeded(date?: Date) { | |||||||
|  */ |  */ | ||||||
| export async function removeChoices(login: string, trusted: boolean, location: Locations, date?: Date) { | export async function removeChoices(login: string, trusted: boolean, location: Locations, date?: Date) { | ||||||
|     const selectedDay = formatDate(date ?? getToday()); |     const selectedDay = formatDate(date ?? getToday()); | ||||||
|     let data: ClientData = await storage.getData(selectedDay); |     let data: DayData = await storage.getData(selectedDay); | ||||||
|     validateTrusted(data, login, trusted); |     validateTrusted(data, login, trusted); | ||||||
|     if (location in data.choices) { |     if (location in data.choices) { | ||||||
|         if (login in data.choices[location]) { |         if (login in data.choices[location]) { | ||||||
| @ -155,7 +195,7 @@ export async function removeChoices(login: string, trusted: boolean, location: L | |||||||
|  */ |  */ | ||||||
| export async function removeChoice(login: string, trusted: boolean, location: Locations, foodIndex: number, date?: Date) { | export async function removeChoice(login: string, trusted: boolean, location: Locations, foodIndex: number, date?: Date) { | ||||||
|     const selectedDay = formatDate(date ?? getToday()); |     const selectedDay = formatDate(date ?? getToday()); | ||||||
|     let data: ClientData = await storage.getData(selectedDay); |     let data: DayData = await storage.getData(selectedDay); | ||||||
|     validateTrusted(data, login, trusted); |     validateTrusted(data, login, trusted); | ||||||
|     if (location in data.choices) { |     if (location in data.choices) { | ||||||
|         if (login in data.choices[location]) { |         if (login in data.choices[location]) { | ||||||
| @ -175,7 +215,7 @@ export async function removeChoice(login: string, trusted: boolean, location: Lo | |||||||
|  * @param login login uživatele |  * @param login login uživatele | ||||||
|  */ |  */ | ||||||
| async function removeChoiceIfPresent(login: string, date: string) { | async function removeChoiceIfPresent(login: string, date: string) { | ||||||
|     let data: ClientData = await storage.getData(date); |     let data: DayData = await storage.getData(date); | ||||||
|     for (const key of Object.keys(data.choices)) { |     for (const key of Object.keys(data.choices)) { | ||||||
|         if (login in data.choices[key]) { |         if (login in data.choices[key]) { | ||||||
|             delete data.choices[key][login]; |             delete data.choices[key][login]; | ||||||
| @ -222,9 +262,10 @@ function validateTrusted(data: ClientData, login: string, trusted: boolean) { | |||||||
|  * @returns aktuální data |  * @returns aktuální data | ||||||
|  */ |  */ | ||||||
| export async function addChoice(login: string, trusted: boolean, location: Locations, foodIndex?: number, date?: Date) { | export async function addChoice(login: string, trusted: boolean, location: Locations, foodIndex?: number, date?: Date) { | ||||||
|     await initIfNeeded(); |     const usedDate = date ?? getToday(); | ||||||
|     const selectedDate = formatDate(date ?? getToday()); |     await initIfNeeded(usedDate); | ||||||
|     let data: ClientData = await storage.getData(selectedDate); |     const selectedDate = formatDate(usedDate); | ||||||
|  |     let data: DayData = await storage.getData(selectedDate); | ||||||
|     validateTrusted(data, login, trusted); |     validateTrusted(data, login, trusted); | ||||||
|     // Pokud měníme pouze lokaci, mažeme případné předchozí
 |     // Pokud měníme pouze lokaci, mažeme případné předchozí
 | ||||||
|     if (foodIndex == null) { |     if (foodIndex == null) { | ||||||
| @ -255,7 +296,7 @@ export async function addChoice(login: string, trusted: boolean, location: Locat | |||||||
|  */ |  */ | ||||||
| export async function updateDepartureTime(login: string, time?: string, date?: Date) { | export async function updateDepartureTime(login: string, time?: string, date?: Date) { | ||||||
|     const selectedDate = formatDate(date ?? getToday()); |     const selectedDate = formatDate(date ?? getToday()); | ||||||
|     let clientData: ClientData = await storage.getData(selectedDate); |     let clientData: DayData = await storage.getData(selectedDate); | ||||||
|     const found = Object.values(clientData.choices).find(location => login in location); |     const found = Object.values(clientData.choices).find(location => login in location); | ||||||
|     // TODO validace, že se jedná o restauraci
 |     // TODO validace, že se jedná o restauraci
 | ||||||
|     if (found) { |     if (found) { | ||||||
|  | |||||||
| @ -39,6 +39,29 @@ export function getIsWeekend(date: Date) { | |||||||
|     return index == 5 || index == 6; |     return index == 5 || index == 6; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** Vrátí první pracovní den v týdnu předaného data. */ | ||||||
|  | export function getFirstWorkDayOfWeek(date: Date) { | ||||||
|  |     const firstDay = new Date(date.getTime()); | ||||||
|  |     firstDay.setDate(date.getDate() - getDayOfWeekIndex(date)); | ||||||
|  |     return firstDay; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** Vrátí poslední pracovní den v týdnu předaného data. */ | ||||||
|  | export function getLastWorkDayOfWeek(date: Date) { | ||||||
|  |     const lastDay = new Date(date.getTime()); | ||||||
|  |     lastDay.setDate(date.getDate() + (4 - getDayOfWeekIndex(date))); | ||||||
|  |     return lastDay; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** Vrátí pořadové číslo týdne předaného data v roce dle ISO 8601. */ | ||||||
|  | export function getWeekNumber(inputDate: Date) { | ||||||
|  |     var date = new Date(inputDate.getTime()); | ||||||
|  |     date.setHours(0, 0, 0, 0); | ||||||
|  |     date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); | ||||||
|  |     var week1 = new Date(date.getFullYear(), 0, 4); | ||||||
|  |     return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Vrátí JWT token z hlaviček, pokud ho obsahují. |  * Vrátí JWT token z hlaviček, pokud ho obsahují. | ||||||
|  *  |  *  | ||||||
|  | |||||||
| @ -67,24 +67,36 @@ interface PizzaDay { | |||||||
|     orders: Order[], // seznam objednávek jednotlivých lidí
 |     orders: Order[], // seznam objednávek jednotlivých lidí
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** Veškerá data pro zobrazení na klientovi */ | /** Týdenní menu jednotlivých restaurací. */ | ||||||
| export interface ClientData { | export interface WeekMenu { | ||||||
|     date: string, // datum vybraného dne pro zobrazení
 |     [dayIndex: number]: { | ||||||
|     isWeekend: boolean, // příznak, zda je zvolené datum víkend
 |         [restaurant in Restaurants]?: DayMenu | ||||||
|     weekIndex: number, // index zvoleného dne v týdnu (0-6)
 |     } | ||||||
|     todayWeekIndex?: number, // index dnešního dne v týdnu (0-6)
 | } | ||||||
|     choices: Choices, // seznam voleb
 | 
 | ||||||
|  | /** Data vztahující se k jednomu konkrétnímu dni. */ | ||||||
|  | export interface DayData { | ||||||
|  |     date: string, // datum dne
 | ||||||
|  |     isWeekend: boolean, // příznak, zda je datum víkend
 | ||||||
|  |     weekIndex: number, // index dne v týdnu (0-6)
 | ||||||
|  |     choices: Choices, // seznam voleb uživatelů
 | ||||||
|  |     // TODO smazat
 | ||||||
|     departureTimes: DepartureTime[], // seznam možných časů odchodu
 |     departureTimes: DepartureTime[], // seznam možných časů odchodu
 | ||||||
|     menus?: { [restaurant in Restaurants]?: Menu }, // menu jednotlivých restaurací
 |     menus?: { [restaurant in Restaurants]?: DayMenu }, // menu jednotlivých restaurací
 | ||||||
|     pizzaDay?: PizzaDay, // pizza day pro dnešní den, pokud existuje
 |     pizzaDay?: PizzaDay, // pizza day pro dnešní den, pokud existuje
 | ||||||
|     pizzaList?: Pizza[], // seznam dostupných pizz pro dnešní den
 |     pizzaList?: Pizza[], // seznam dostupných pizz pro dnešní den
 | ||||||
|     pizzaListLastUpdate?: Date, // datum a čas poslední aktualizace pizz
 |     pizzaListLastUpdate?: Date, // datum a čas poslední aktualizace pizz
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** Nabídka jídel jednoho podniku. */ | /** Veškerá data pro zobrazení na klientovi. */ | ||||||
| export interface Menu { | export interface ClientData extends DayData { | ||||||
|  |     todayWeekIndex?: number, // index dnešního dne v týdnu (0-6)
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** Nabídka jídel jednoho podniku pro jeden konkrétní den. */ | ||||||
|  | export interface DayMenu { | ||||||
|     lastUpdate: string, // human-readable čas poslední aktualizace menu
 |     lastUpdate: string, // human-readable čas poslední aktualizace menu
 | ||||||
|     closed: boolean, // příznak, zda je daný podnik aktuálně zavřený
 |     closed: boolean, // příznak, zda je daný podnik v tento den zavřený
 | ||||||
|     food: Food[], // seznam jídel v menu
 |     food: Food[], // seznam jídel v menu
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user