1 Commits

Author SHA1 Message Date
bc039a361d Magic bullshit 2025-01-09 00:35:47 +01:00
9 changed files with 113 additions and 308 deletions

View File

@@ -14,13 +14,13 @@ import './App.scss';
import { SelectSearchOption } from 'react-select-search'; import { SelectSearchOption } from 'react-select-search';
import { faCircleCheck, faNoteSticky, faTrashCan } from '@fortawesome/free-regular-svg-icons'; import { faCircleCheck, faNoteSticky, faTrashCan } from '@fortawesome/free-regular-svg-icons';
import { useSettings } from './context/settings'; import { useSettings } from './context/settings';
import { ClientData, Restaurants, Food, Order, Locations, PizzaOrder, PizzaDayState, FoodChoices, DayMenu, DepartureTime, LocationKey } from './types'; import { ClientData, Restaurants, Food, Order, Locations, PizzaOrder, PizzaDayState, FoodChoices, DayMenu, DepartureTime } 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';
import { getData, errorHandler, getQrUrl } from './api/Api'; import { getData, errorHandler, getQrUrl } from './api/Api';
import { addChoice, removeChoices, removeChoice, changeDepartureTime, jdemeObed, updateNote } from './api/FoodApi'; import { addChoice, removeChoices, removeChoice, changeDepartureTime, jdemeObed, updateNote } from './api/FoodApi';
import { getHumanDateTime, isInTheFuture } from './Utils'; import { getHumanDateTime } from './Utils';
import NoteModal from './components/modals/NoteModal'; import NoteModal from './components/modals/NoteModal';
import { useEasterEgg } from './context/eggs'; import { useEasterEgg } from './context/eggs';
import { getImage } from './api/EasterEggApi'; import { getImage } from './api/EasterEggApi';
@@ -141,8 +141,11 @@ function App() {
useEffect(() => { useEffect(() => {
if (choiceRef?.current?.value && choiceRef.current.value !== "") { if (choiceRef?.current?.value && choiceRef.current.value !== "") {
const locationKey = choiceRef.current.value as LocationKey; // TODO: wtf, cos pil, když jsi tohle psal?
const restaurantKey = Object.keys(Restaurants).indexOf(locationKey); const key = choiceRef?.current?.value;
const locationIndex = Object.keys(Locations).indexOf(key as unknown as Locations);
const locationsKey = Object.keys(Locations)[locationIndex];
const restaurantKey = Object.keys(Restaurants).indexOf(locationsKey);
if (restaurantKey > -1 && food) { if (restaurantKey > -1 && food) {
const restaurant = Object.values(Restaurants)[restaurantKey]; const restaurant = Object.values(Restaurants)[restaurantKey];
setFoodChoiceList(food[restaurant]?.food); setFoodChoiceList(food[restaurant]?.food);
@@ -191,7 +194,7 @@ function App() {
}, [auth?.login, easterEgg?.duration, easterEgg?.url, eggImage]); }, [auth?.login, easterEgg?.duration, easterEgg?.url, eggImage]);
const doAddChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => { const doAddChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
const locationKey = event.target.value as LocationKey; const locationKey = event.target.value as Locations;
if (auth?.login) { if (auth?.login) {
await errorHandler(() => addChoice(locationKey, undefined, dayIndex)); await errorHandler(() => addChoice(locationKey, undefined, dayIndex));
if (foodChoiceRef.current?.value) { if (foodChoiceRef.current?.value) {
@@ -208,14 +211,14 @@ function App() {
const doAddFoodChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => { const doAddFoodChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
if (event.target.value && foodChoiceList?.length && choiceRef.current?.value) { if (event.target.value && foodChoiceList?.length && choiceRef.current?.value) {
const locationKey = choiceRef.current.value as LocationKey; const locationKey = choiceRef.current.value as Locations;
if (auth?.login) { if (auth?.login) {
await errorHandler(() => addChoice(locationKey, Number(event.target.value), dayIndex)); await errorHandler(() => addChoice(locationKey, Number(event.target.value), dayIndex));
} }
} }
} }
const doRemoveChoices = async (locationKey: LocationKey) => { const doRemoveChoices = async (locationKey: Locations) => {
if (auth?.login) { if (auth?.login) {
await errorHandler(() => removeChoices(locationKey, dayIndex)); await errorHandler(() => removeChoices(locationKey, dayIndex));
// Vyresetujeme výběr, aby bylo jasné pro který případně vybíráme jídlo // Vyresetujeme výběr, aby bylo jasné pro který případně vybíráme jídlo
@@ -228,7 +231,7 @@ function App() {
} }
} }
const doRemoveFoodChoice = async (locationKey: LocationKey, foodIndex: number) => { const doRemoveFoodChoice = async (locationKey: Locations, foodIndex: number) => {
if (auth?.login) { if (auth?.login) {
await errorHandler(() => removeChoice(locationKey, foodIndex, dayIndex)); await errorHandler(() => removeChoice(locationKey, foodIndex, dayIndex));
if (choiceRef?.current?.value) { if (choiceRef?.current?.value) {
@@ -351,7 +354,7 @@ function App() {
} else { } else {
content = <h3>Chyba načtení dat</h3> content = <h3>Chyba načtení dat</h3>
} }
return <Col md={12} lg={4} className='mt-3'> return <Col md={12} lg={6} className='mt-3'>
<h3>{name}</h3> <h3>{name}</h3>
{menu?.lastUpdate && <small>Poslední aktualizace: {getHumanDateTime(new Date(menu.lastUpdate))}</small>} {menu?.lastUpdate && <small>Poslední aktualizace: {getHumanDateTime(new Date(menu.lastUpdate))}</small>}
{content} {content}
@@ -417,7 +420,6 @@ function App() {
{food[Restaurants.SLADOVNICKA] && renderFoodTable('Sladovnická', food[Restaurants.SLADOVNICKA])} {food[Restaurants.SLADOVNICKA] && renderFoodTable('Sladovnická', food[Restaurants.SLADOVNICKA])}
{/* {food[Restaurants.UMOTLIKU] && renderFoodTable('U Motlíků', food[Restaurants.UMOTLIKU])} */} {/* {food[Restaurants.UMOTLIKU] && renderFoodTable('U Motlíků', food[Restaurants.UMOTLIKU])} */}
{food[Restaurants.TECHTOWER] && renderFoodTable('TechTower', food[Restaurants.TECHTOWER])} {food[Restaurants.TECHTOWER] && renderFoodTable('TechTower', food[Restaurants.TECHTOWER])}
{food[Restaurants.ZASTAVKAUMICHALA] && renderFoodTable('Zastávka u Michala', food[Restaurants.ZASTAVKAUMICHALA])}
</Row> </Row>
<div className='content-wrapper'> <div className='content-wrapper'>
<div className='content'> <div className='content'>
@@ -427,8 +429,11 @@ function App() {
<option></option> <option></option>
{Object.entries(Locations) {Object.entries(Locations)
.filter(entry => { .filter(entry => {
const locationKey = entry[0] as LocationKey; // TODO: wtf, cos pil, když jsi tohle psal? v2
const restaurantKey = Object.keys(Restaurants).indexOf(locationKey); const key = entry[0];
const locationIndex = Object.keys(Locations).indexOf(key as unknown as Locations);
const locationsKey = Object.keys(Locations)[locationIndex];
const restaurantKey = Object.keys(Restaurants).indexOf(locationsKey);
const v = Object.values(Restaurants)[restaurantKey]; const v = Object.values(Restaurants)[restaurantKey];
return v == null || !food[v]?.closed; return v == null || !food[v]?.closed;
}) })
@@ -446,9 +451,7 @@ function App() {
<p style={{ marginTop: "10px" }}>V kolik hodin preferuješ odchod?</p> <p style={{ marginTop: "10px" }}>V kolik hodin preferuješ odchod?</p>
<Form.Select ref={departureChoiceRef} onChange={handleChangeDepartureTime}> <Form.Select ref={departureChoiceRef} onChange={handleChangeDepartureTime}>
<option></option> <option></option>
{Object.values(DepartureTime) {Object.values(DepartureTime).map(time => <option key={time} value={time}>{time}</option>)}
.filter(time => isInTheFuture(time))
.map(time => <option key={time} value={time}>{time}</option>)}
</Form.Select> </Form.Select>
</>} </>}
</>} </>}
@@ -456,13 +459,18 @@ function App() {
<Table bordered className='mt-5'> <Table bordered className='mt-5'>
<tbody> <tbody>
{Object.keys(data.choices).map(key => { {Object.keys(data.choices).map(key => {
const locationKey = key as LocationKey; const locationKey = key as keyof typeof Locations;
console.log("Mapuji location key", locationKey);
const locationName = Locations[locationKey]; const locationName = Locations[locationKey];
console.log("Location name", locationName);
console.log("Obsah data.choices", data.choices);
const loginObject = data.choices[locationKey]; const loginObject = data.choices[locationKey];
// TODO toto je hovnokód, mělo by to být napsané tak, aby si na to TypeScript nemohl stěžovat
if (!loginObject) { if (!loginObject) {
return; return;
} }
const locationLoginList = Object.entries(loginObject); const locationLoginList = Object.entries(loginObject);
console.log("Entries", locationLoginList);
return ( return (
<tr key={key}> <tr key={key}>
<td>{locationName}</td> <td>{locationName}</td>
@@ -486,20 +494,20 @@ function App() {
setNoteModalOpen(true); setNoteModalOpen(true);
}} title='Upravit poznámku' className='action-icon' icon={faNoteSticky} />} }} title='Upravit poznámku' className='action-icon' icon={faNoteSticky} />}
{login === auth.login && canChangeChoice && <FontAwesomeIcon onClick={() => { {login === auth.login && canChangeChoice && <FontAwesomeIcon onClick={() => {
doRemoveChoices(key as LocationKey); doRemoveChoices(key as Locations); // TODO dořešit
}} title={`Odstranit volbu ${locationName}, včetně případných zvolených jídel`} className='action-icon' icon={faTrashCan} />} }} title={`Odstranit volbu ${locationName}, včetně případných zvolených jídel`} className='action-icon' icon={faTrashCan} />}
</td> </td>
{userChoices?.length && food ? <td> {userChoices?.length && food ? <td>
<ul> <ul>
{userChoices?.map(foodIndex => { {userChoices?.map(foodIndex => {
// TODO narovnat, tohle je zbytečně složité const locationsKey = Object.keys(Locations)[Number(key)]
const restaurantKey = Object.keys(Restaurants).indexOf(key); const restaurantKey = Object.keys(Restaurants).indexOf(locationsKey);
const restaurant = Object.values(Restaurants)[restaurantKey]; const restaurant = Object.values(Restaurants)[restaurantKey];
const foodName = food[restaurant]?.food[foodIndex].name; const foodName = food[restaurant]?.food[foodIndex].name;
return <li key={foodIndex}> return <li key={foodIndex}>
{foodName} {foodName}
{login === auth.login && canChangeChoice && <FontAwesomeIcon onClick={() => { {login === auth.login && canChangeChoice && <FontAwesomeIcon onClick={() => {
doRemoveFoodChoice(key as LocationKey, foodIndex); doRemoveFoodChoice(key as Locations, foodIndex); // TODO dořešit
}} title={`Odstranit ${foodName}`} className='action-icon' icon={faTrashCan} />} }} title={`Odstranit ${foodName}`} className='action-icon' icon={faTrashCan} />}
</li> </li>
})} })}

View File

@@ -1,5 +1,3 @@
import {DepartureTime} from "../../types";
const TOKEN_KEY = "token"; const TOKEN_KEY = "token";
/** /**
@@ -45,15 +43,3 @@ export function getHumanDateTime(datetime: Date) {
return `${day}.${month}.${year} ${hours}:${minutes}`; return `${day}.${month}.${year} ${hours}:${minutes}`;
} }
} }
/**
* Vrátí true, pokud je předaný čas větší než aktuální čas.
*/
export function isInTheFuture(time: DepartureTime) {
const now = new Date();
const currentHours = now.getHours();
const currentMinutes = now.getMinutes();
const [hours, minutes] = time.split(':').map(Number);
return hours > currentHours || (hours === currentHours && minutes > currentMinutes);
}

View File

@@ -1,17 +1,17 @@
import { AddChoiceRequest, ChangeDepartureTimeRequest, LocationKey, RemoveChoiceRequest, RemoveChoicesRequest, UpdateNoteRequest } from "../types"; import { AddChoiceRequest, ChangeDepartureTimeRequest, Locations, RemoveChoiceRequest, RemoveChoicesRequest, UpdateNoteRequest } from "../types";
import { api } from "./Api"; import { api } from "./Api";
const FOOD_API_PREFIX = '/api/food'; const FOOD_API_PREFIX = '/api/food';
export const addChoice = async (locationKey: LocationKey, foodIndex?: number, dayIndex?: number) => { export const addChoice = async (locationKey: keyof typeof Locations, foodIndex?: number, dayIndex?: number) => {
return await api.post<AddChoiceRequest, void>(`${FOOD_API_PREFIX}/addChoice`, { locationKey, foodIndex, dayIndex }); return await api.post<AddChoiceRequest, void>(`${FOOD_API_PREFIX}/addChoice`, { locationKey, foodIndex, dayIndex });
} }
export const removeChoices = async (locationKey: LocationKey, dayIndex?: number) => { export const removeChoices = async (locationKey: keyof typeof Locations, dayIndex?: number) => {
return await api.post<RemoveChoicesRequest, void>(`${FOOD_API_PREFIX}/removeChoices`, { locationKey, dayIndex }); return await api.post<RemoveChoicesRequest, void>(`${FOOD_API_PREFIX}/removeChoices`, { locationKey, dayIndex });
} }
export const removeChoice = async (locationKey: LocationKey, foodIndex: number, dayIndex?: number) => { export const removeChoice = async (locationKey: keyof typeof Locations, foodIndex: number, dayIndex?: number) => {
return await api.post<RemoveChoiceRequest, void>(`${FOOD_API_PREFIX}/removeChoice`, { locationKey, foodIndex, dayIndex }); return await api.post<RemoveChoiceRequest, void>(`${FOOD_API_PREFIX}/removeChoice`, { locationKey, foodIndex, dayIndex });
} }

View File

@@ -1,7 +1,9 @@
import { getDayOfWeekIndex } from "./utils";
// Mockovací data pro podporované podniky, na jeden týden // Mockovací data pro podporované podniky, na jeden týden
const MOCK_DATA = { const MOCK_DATA = {
'sladovnicka': { 'sladovnicka': [
'MONDAY': [ [
{ {
amount: "0,25l", amount: "0,25l",
name: "Kulajda", name: "Kulajda",
@@ -27,7 +29,7 @@ const MOCK_DATA = {
isSoup: false, isSoup: false,
} }
], ],
'TUESDAY': [ [
{ {
amount: "0,25l", amount: "0,25l",
name: "Hovězí vývar s kapáním", name: "Hovězí vývar s kapáním",
@@ -53,7 +55,7 @@ const MOCK_DATA = {
isSoup: false, isSoup: false,
} }
], ],
'WEDNESDAY': [ [
{ {
amount: "0,25l", amount: "0,25l",
name: "Zelná polévka s klobásou", name: "Zelná polévka s klobásou",
@@ -79,7 +81,7 @@ const MOCK_DATA = {
isSoup: false, isSoup: false,
} }
], ],
'THURSDAY': [ [
{ {
amount: "0,25l", amount: "0,25l",
name: "Kuřecí vývar s nudlemi", name: "Kuřecí vývar s nudlemi",
@@ -105,7 +107,7 @@ const MOCK_DATA = {
isSoup: false, isSoup: false,
} }
], ],
'FRIDAY': [ [
{ {
amount: "0,25l", amount: "0,25l",
name: "Dršťková polévka", name: "Dršťková polévka",
@@ -131,7 +133,7 @@ const MOCK_DATA = {
isSoup: false, isSoup: false,
} }
] ]
}, ],
'uMotliku': [ 'uMotliku': [
[ [
{ {
@@ -425,96 +427,6 @@ const MOCK_DATA = {
isSoup: false, 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,
}
],
] ]
} }
@@ -1268,11 +1180,8 @@ const MOCK_PIZZA_LIST = [
} }
] ]
/** export const getTodayMock = () => {
* Funkce vrací mock datu ve formátu YYYY-MM-DD return '2023-05-31'; // středa
*/
export const getTodayMock = (): Date => {
return new Date('2025-01-08'); // pátek
} }
export const getMenuSladovnickaMock = () => { export const getMenuSladovnickaMock = () => {
@@ -1287,10 +1196,6 @@ export const getMenuTechTowerMock = () => {
return MOCK_DATA['techTower']; return MOCK_DATA['techTower'];
} }
export const getMenuZastavkaUmichalaMock = () => {
return MOCK_DATA['zastavkaUmichala'];
}
export const getPizzaListMock = () => { export const getPizzaListMock = () => {
return MOCK_PIZZA_LIST; return MOCK_PIZZA_LIST;
} }

View File

@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
import { load } from 'cheerio'; import { load } from 'cheerio';
import { DayOfWeek, DayOfWeekEnum, DayOfWeekIndex, Food, RestaurantWeeklyMenu } from "../../types"; import { Food } from "../../types";
import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock, getMenuZastavkaUmichalaMock } from "./mock"; import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock } from "./mock";
// 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 = [ const SOUP_NAMES = [
@@ -13,9 +13,9 @@ const SOUP_NAMES = [
'fazolová', 'fazolová',
'cuketový krém', 'cuketový krém',
'boršč', 'boršč',
'slepičí s ', 'slepičí s',
'zeleninová s ', 'zeleninová s',
'hovězí s ', 'hovězí s',
'kachní kaldoun', 'kachní kaldoun',
'dršťková' 'dršťková'
]; ];
@@ -25,8 +25,6 @@ const DAYS_IN_WEEK = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', '
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 U_MOTLIKU_URL = 'https://www.umotliku.cz/menu';
const TECHTOWER_URL = 'https://www.equifarm.cz/restaurace-techtower'; 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. * Vrátí true, pokud předaný text obsahuje některé ze slov, které naznačuje, že se jedná o polévku.
@@ -69,7 +67,7 @@ const getHtml = async (url: string): Promise<any> => {
* @param mock zda vrátit mock data * @param mock zda vrátit mock data
* @returns seznam jídel pro daný týden * @returns seznam jídel pro daný týden
*/ */
export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = false): Promise<RestaurantWeeklyMenu> => { export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
if (mock) { if (mock) {
return getMenuSladovnickaMock(); return getMenuSladovnickaMock();
} }
@@ -78,8 +76,7 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f
const $ = load(html); const $ = load(html);
const list = $('ul.tab-links').children(); const list = $('ul.tab-links').children();
const result: RestaurantWeeklyMenu = {}; const result: Food[][] = [];
// TODO upravit až bude enum
for (let dayIndex = 0; dayIndex < 5; dayIndex++) { for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
const currentDate = new Date(firstDayOfWeek); const currentDate = new Date(firstDayOfWeek);
currentDate.setDate(firstDayOfWeek.getDate() + dayIndex); currentDate.setDate(firstDayOfWeek.getDate() + dayIndex);
@@ -97,8 +94,7 @@ export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = f
}) })
if (index === undefined) { if (index === undefined) {
// Pravděpodobně svátek, nebo je zavřeno // Pravděpodobně svátek, nebo je zavřeno
const index: number = Object.keys(DayOfWeekEnum).indexOf('Casual'); // 1 result[dayIndex] = [{
result[dayIndex as DayOfWeekEnum] = [{
amount: undefined, amount: undefined,
name: "Pro daný den nebyla nalezena denní nabídka", name: "Pro daný den nebyla nalezena denní nabídka",
price: "", price: "",
@@ -330,54 +326,4 @@ export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = fal
} }
} }
return result; return result;
} }
/**
* 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<Food[][]> => {
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;
}

View File

@@ -1,7 +1,7 @@
import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getHumanDate, getIsWeekend, getWeekNumber } from "./utils"; import { InsufficientPermissions, formatDate, getDayOfWeekIndex, getFirstWorkDayOfWeek, getHumanDate, getHumanTime, getIsWeekend, getLastWorkDayOfWeek, getWeekNumber } from "./utils";
import { ClientData, Restaurants, RestaurantDailyMenu, DepartureTime, DayData, WeekMenu, LocationKey, DayOfWeekIndex, daysOfWeeksIndices, DayOfWeekEnum, DayOfWeek } from "../../types"; import { ClientData, Locations, Restaurants, DayMenu, DepartureTime, DayData, WeekMenu } from "../../types";
import getStorage from "./storage"; import getStorage from "./storage";
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku, getMenuZastavkaUmichala } from "./restaurants"; import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
import { getTodayMock } from "./mock"; import { getTodayMock } from "./mock";
const storage = getStorage(); 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í. */ /** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */
export function getToday(): Date { export function getToday(): Date {
if (process.env.MOCK_DATA === 'true') { if (process.env.MOCK_DATA === 'true') {
return getTodayMock(); return new Date(getTodayMock());
} }
return new Date(); return new Date();
} }
@@ -61,7 +61,6 @@ export async function getData(date?: Date): Promise<ClientData> {
[Restaurants.SLADOVNICKA]: await getRestaurantMenu(Restaurants.SLADOVNICKA, date), [Restaurants.SLADOVNICKA]: await getRestaurantMenu(Restaurants.SLADOVNICKA, date),
// [Restaurants.UMOTLIKU]: await getRestaurantMenu(Restaurants.UMOTLIKU, date), // [Restaurants.UMOTLIKU]: await getRestaurantMenu(Restaurants.UMOTLIKU, date),
[Restaurants.TECHTOWER]: await getRestaurantMenu(Restaurants.TECHTOWER, date), [Restaurants.TECHTOWER]: await getRestaurantMenu(Restaurants.TECHTOWER, date),
[Restaurants.ZASTAVKAUMICHALA]: await getRestaurantMenu(Restaurants.ZASTAVKAUMICHALA, date),
} }
clientData = await addVolatileData(clientData); clientData = await addVolatileData(clientData);
return clientData; return clientData;
@@ -97,7 +96,7 @@ async function getMenu(date: Date): Promise<WeekMenu | undefined> {
* @param date datum, ke kterému získat menu * @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<RestaurantDailyMenu> { export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): Promise<DayMenu> {
const usedDate = date ?? getToday(); const usedDate = date ?? getToday();
const dayOfWeekIndex = getDayOfWeekIndex(usedDate); const dayOfWeekIndex = getDayOfWeekIndex(usedDate);
const now = new Date().getTime(); const now = new Date().getTime();
@@ -111,9 +110,9 @@ export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): P
let menus = await getMenu(usedDate); let menus = await getMenu(usedDate);
if (menus == null) { if (menus == null) {
menus = {}; menus = [];
} }
daysOfWeeksIndices.forEach(i => { for (let i = 0; i < 5; i++) {
if (menus[i] == null) { if (menus[i] == null) {
menus[i] = {}; menus[i] = {};
} }
@@ -124,9 +123,6 @@ export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): P
food: [], food: [],
}; };
} }
})
if (!menus[dayOfWeekIndex]) {
menus[dayOfWeekIndex] = {};
} }
if (!menus[dayOfWeekIndex][restaurant]?.food?.length) { if (!menus[dayOfWeekIndex][restaurant]?.food?.length) {
const firstDay = getFirstWorkDayOfWeek(usedDate); const firstDay = getFirstWorkDayOfWeek(usedDate);
@@ -134,15 +130,7 @@ export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): P
switch (restaurant) { switch (restaurant) {
case Restaurants.SLADOVNICKA: case Restaurants.SLADOVNICKA:
try { try {
// TODO tady jsme v háji, protože z následujících metod vracíme arbitrárně dlouhé pole (musíme vracet omezené na maximálně 0-7 prvků)
const sladovnickaFood = await getMenuSladovnicka(firstDay, mock); const sladovnickaFood = await getMenuSladovnicka(firstDay, mock);
for (const i in DayOfWeekEnum) {
menus[i][restaurant]!.food = sladovnickaFood[i];
// Velice chatrný a nespolehlivý způsob detekce uzavření...
if (sladovnickaFood[i].length === 1 && sladovnickaFood[i][0].name.toLowerCase() === 'pro daný den nebyla nalezena denní nabídka') {
menus[i][restaurant]!.closed = true;
}
}
for (let i = 0; i < sladovnickaFood.length; i++) { for (let i = 0; i < sladovnickaFood.length; i++) {
menus[i][restaurant]!.food = sladovnickaFood[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í...
@@ -180,19 +168,6 @@ export async function getRestaurantMenu(restaurant: Restaurants, date?: Date): P
} catch (e: any) { } catch (e: any) {
console.error("Selhalo načtení jídel pro podnik TechTower", e); 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); await storage.setData(getMenuKey(usedDate), menus);
} }
@@ -221,7 +196,7 @@ export async function initIfNeeded(date?: Date) {
* @param date datum, ke kterému se volba vztahuje * @param date datum, ke kterému se volba vztahuje
* @returns * @returns
*/ */
export async function removeChoices(login: string, trusted: boolean, locationKey: LocationKey, date?: Date) { export async function removeChoices(login: string, trusted: boolean, locationKey: keyof typeof Locations, date?: Date) {
const selectedDay = formatDate(date ?? getToday()); const selectedDay = formatDate(date ?? getToday());
let data: DayData = await storage.getData(selectedDay); let data: DayData = await storage.getData(selectedDay);
validateTrusted(data, login, trusted); validateTrusted(data, login, trusted);
@@ -248,7 +223,7 @@ export async function removeChoices(login: string, trusted: boolean, locationKey
* @param date datum, ke kterému se volba vztahuje * @param date datum, ke kterému se volba vztahuje
* @returns * @returns
*/ */
export async function removeChoice(login: string, trusted: boolean, locationKey: LocationKey, foodIndex: number, date?: Date) { export async function removeChoice(login: string, trusted: boolean, locationKey: keyof typeof Locations, foodIndex: number, date?: Date) {
const selectedDay = formatDate(date ?? getToday()); const selectedDay = formatDate(date ?? getToday());
let data: DayData = await storage.getData(selectedDay); let data: DayData = await storage.getData(selectedDay);
validateTrusted(data, login, trusted); validateTrusted(data, login, trusted);
@@ -272,7 +247,7 @@ export async function removeChoice(login: string, trusted: boolean, locationKey:
async function removeChoiceIfPresent(login: string, date: string) { async function removeChoiceIfPresent(login: string, date: string) {
let data: DayData = 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)) {
const locationKey = key as LocationKey; const locationKey = key as keyof typeof Locations;
if (data.choices[locationKey] && login in data.choices[locationKey]) { if (data.choices[locationKey] && login in data.choices[locationKey]) {
delete data.choices[locationKey][login]; delete data.choices[locationKey][login];
if (Object.keys(data.choices[locationKey]).length === 0) { if (Object.keys(data.choices[locationKey]).length === 0) {
@@ -317,7 +292,7 @@ function validateTrusted(data: ClientData, login: string, trusted: boolean) {
* @param date datum, ke kterému se volba vztahuje * @param date datum, ke kterému se volba vztahuje
* @returns aktuální data * @returns aktuální data
*/ */
export async function addChoice(login: string, trusted: boolean, locationKey: LocationKey, foodIndex?: number, date?: Date) { export async function addChoice(login: string, trusted: boolean, locationKey: keyof typeof Locations, foodIndex?: number, date?: Date) {
const usedDate = date ?? getToday(); const usedDate = date ?? getToday();
await initIfNeeded(usedDate); await initIfNeeded(usedDate);
const selectedDate = formatDate(usedDate); const selectedDate = formatDate(usedDate);
@@ -327,22 +302,29 @@ export async function addChoice(login: string, trusted: boolean, locationKey: Lo
if (foodIndex == null) { if (foodIndex == null) {
data = await removeChoiceIfPresent(login, selectedDate); data = await removeChoiceIfPresent(login, selectedDate);
} }
// TODO vytáhnout inicializaci "prázdné struktury" do vlastní funkce if (!data.choices) {
if (!(data.choices[locationKey])) { console.log("Klíč", Locations[locationKey]); // TODO smazat
data.choices[locationKey] = {} data.choices = {
} [Locations[locationKey]]: {}
if (!(login in data.choices[locationKey])) {
if (!data.choices[locationKey]) {
data.choices[locationKey] = {}
} }
data.choices[locationKey][login] = {
trusted,
options: []
};
}
if (foodIndex != null && !data.choices[locationKey][login].options.includes(foodIndex)) {
data.choices[locationKey][login].options.push(foodIndex);
} }
console.log("Máme choices", data.choices);
console.log("Hodnota locationKey", locationKey);
// if (!(data.choices[locationKey])) {
// data?.choices[locationKey] = {}
// }
// if (!(login in data.choices[locationKey])) {
// if (!data.choices[locationKey]) {
// data.choices[locationKey] = {}
// }
// data.choices[locationKey][login] = {
// trusted,
// options: []
// };
// }
// if (foodIndex != null && !data.choices[locationKey][login].options.includes(foodIndex)) {
// data.choices[locationKey][login].options.push(foodIndex);
// }
await storage.setData(selectedDate, data); await storage.setData(selectedDate, data);
return data; return data;
} }

View File

@@ -1,13 +1,11 @@
import { Choices, DayOfWeekIndex, LocationKey } from "../../types"; import { Choices, Locations } from "../../types";
/** Vrátí datum v ISO formátu. */ /** Vrátí datum v ISO formátu. */
export function formatDate(date: Date, format?: string) { export function formatDate(date: Date) {
let day = String(date.getDate()).padStart(2, '0'); let currentDay = String(date.getDate()).padStart(2, '0');
let month = String(date.getMonth() + 1).padStart(2, "0"); let currentMonth = String(date.getMonth() + 1).padStart(2, "0");
let year = String(date.getFullYear()); let currentYear = date.getFullYear();
return `${currentYear}-${currentMonth}-${currentDay}`;
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í. */ /** Vrátí human-readable reprezentaci předaného data pro zobrazení. */
@@ -32,9 +30,9 @@ export function getHumanTime(time: Date) {
* @param date datum * @param date datum
* @returns index dne v týdnu * @returns index dne v týdnu
*/ */
export const getDayOfWeekIndex = (date: Date): DayOfWeekIndex => { export const getDayOfWeekIndex = (date: Date) => {
// https://stackoverflow.com/a/4467559 // https://stackoverflow.com/a/4467559
return ((((date.getDay() - 1) % 7) + 7) % 7) as DayOfWeekIndex; return (((date.getDay() - 1) % 7) + 7) % 7;
} }
/** Vrátí true, pokud je předané datum o víkendu. */ /** Vrátí true, pokud je předané datum o víkendu. */
@@ -116,16 +114,18 @@ export const getUsersByLocation = (choices: Choices, login: string): string[] =>
const result: string[] = []; const result: string[] = [];
for (const location of Object.entries(choices)) { for (const location of Object.entries(choices)) {
const locationKey = location[0] as LocationKey; const locationKey = location[0];
const locationValue = location[1]; const locationValue = location[1];
if (locationValue[login]) { console.log("locationKey", locationKey);
for (const username in choices[locationKey]) { console.log("locationValue", locationValue);
if (choices[locationKey].hasOwnProperty(username)) { // if (locationValues[login]) {
result.push(username); // for (const username in choices[locationKey]) {
} // if (choices[locationKey].hasOwnProperty(username)) {
} // result.push(username);
break; // }
} // }
// break;
// }
} }
return result; return result;

View File

@@ -1,7 +1,7 @@
import { FeatureRequest, LocationKey, PizzaOrder } from "./Types"; import { FeatureRequest, Locations, PizzaOrder } from "./Types";
export type ILocationKey = { export type ILocationKey = {
locationKey: LocationKey, locationKey: keyof typeof Locations,
} }
export type IDayIndex = { export type IDayIndex = {

View File

@@ -3,7 +3,6 @@ export enum Restaurants {
SLADOVNICKA = 'sladovnicka', SLADOVNICKA = 'sladovnicka',
// UMOTLIKU = 'uMotliku', // UMOTLIKU = 'uMotliku',
TECHTOWER = 'techTower', TECHTOWER = 'techTower',
ZASTAVKAUMICHALA = 'zastavkaUmichala',
} }
export type FoodChoices = { export type FoodChoices = {
@@ -13,9 +12,8 @@ export type FoodChoices = {
note?: string, note?: string,
} }
// TODO okomentovat / rozdělit
export type Choices = { export type Choices = {
[location in LocationKey]?: { [location in keyof typeof Locations]?: {
[login: string]: FoodChoices [login: string]: FoodChoices
} }
} }
@@ -70,36 +68,20 @@ interface PizzaDay {
orders: Order[], // seznam objednávek jednotlivých lidí orders: Order[], // seznam objednávek jednotlivých lidí
} }
/** Index dne v týdnu (0 = pondělí, 6 = neděle) */
// TODO tohle by měl být (seřazený) enum MONDAY-SUNDAY, ne číslo
export const daysOfWeeksIndices = [0, 1, 2, 3, 4, 5, 6] as const;
export type DayOfWeekIndex = typeof daysOfWeeksIndices[number]
const daysOfWeek = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'] as const;
export type DayOfWeek = typeof daysOfWeek[number];
/** Denní menu všech dostupných podniků. */
export type DailyMenu = {
[restaurant in Restaurants]?: RestaurantDailyMenu
}
/** Týdenní menu jednotlivých restaurací. */ /** Týdenní menu jednotlivých restaurací. */
export type WeekMenu = { export type WeekMenu = {
[dayIndex in DayOfWeek]?: DailyMenu [dayIndex: number]: {
} [restaurant in Restaurants]?: DayMenu
}
/** Týdenní menu jedné restaurace. */
export type RestaurantWeeklyMenu = {
[key in DayOfWeek]?: Food[]
} }
/** Data vztahující se k jednomu konkrétnímu dni. */ /** Data vztahující se k jednomu konkrétnímu dni. */
export type DayData = { export type DayData = {
date: string, // datum dne date: string, // datum dne
isWeekend: boolean, // příznak, zda je datum víkend isWeekend: boolean, // příznak, zda je datum víkend
weekIndex: DayOfWeekIndex, // index dne v týdnu (0-6) weekIndex: number, // index dne v týdnu (0-6)
choices: Choices, // seznam voleb uživatelů choices: Choices, // seznam voleb uživatelů
menus?: { [restaurant in Restaurants]?: RestaurantDailyMenu }, // 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
@@ -107,11 +89,11 @@ export type DayData = {
/** Veškerá data pro zobrazení na klientovi. */ /** Veškerá data pro zobrazení na klientovi. */
export type ClientData = DayData & { export type ClientData = DayData & {
todayWeekIndex?: DayOfWeekIndex, // index dnešního dne v týdnu (0-6) todayWeekIndex?: number, // index dnešního dne v týdnu (0-6)
} }
/** Nabídka jídel jednoho podniku pro jeden konkrétní den. */ /** Nabídka jídel jednoho podniku pro jeden konkrétní den. */
export type RestaurantDailyMenu = { export type DayMenu = {
lastUpdate: number, // UNIX timestamp poslední aktualizace menu lastUpdate: number, // UNIX timestamp poslední aktualizace menu
closed: boolean, // příznak, zda je daný podnik v tento den 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
@@ -130,7 +112,6 @@ export enum Locations {
SLADOVNICKA = 'Sladovnická', SLADOVNICKA = 'Sladovnická',
// UMOTLIKU = 'U Motlíků', // UMOTLIKU = 'U Motlíků',
TECHTOWER = 'TechTower', TECHTOWER = 'TechTower',
ZASTAVKAUMICHALA = 'Zastávka u Michala',
SPSE = 'SPŠE', SPSE = 'SPŠE',
PIZZA = 'Pizza day', PIZZA = 'Pizza day',
OBJEDNAVAM = 'Budu objednávat', OBJEDNAVAM = 'Budu objednávat',
@@ -138,9 +119,6 @@ export enum Locations {
ROZHODUJI = 'Rozhoduji se', ROZHODUJI = 'Rozhoduji se',
} }
// TODO totéž
export type LocationKey = keyof typeof Locations;
export enum UdalostEnum { export enum UdalostEnum {
ZAHAJENA_PIZZA = "Zahájen pizza day", ZAHAJENA_PIZZA = "Zahájen pizza day",
OBJEDNANA_PIZZA = "Objednána pizza", OBJEDNANA_PIZZA = "Objednána pizza",