diff --git a/client/src/App.tsx b/client/src/App.tsx index 2b79a3a..8d122b1 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,7 +1,6 @@ import React, { useContext, useEffect, useMemo, useRef, useState, useCallback } from 'react'; import 'bootstrap/dist/css/bootstrap.min.css'; import { EVENT_DISCONNECT, EVENT_MESSAGE, SocketContext } from './context/socket'; -import { addPizza, createPizzaDay, deletePizzaDay, finishDelivery, finishOrder, lockPizzaDay, removePizza, unlockPizzaDay, updatePizzaDayNote } from './api/PizzaDayApi'; import { useAuth } from './context/auth'; import Login from './Login'; import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap'; @@ -16,15 +15,13 @@ import { useSettings } from './context/settings'; import Footer from './components/Footer'; import { faChainBroken, faChevronLeft, faChevronRight, faGear, faSatelliteDish, faSearch } from '@fortawesome/free-solid-svg-icons'; import Loader from './components/Loader'; -import { getData, errorHandler, getQrUrl } from './api/Api'; -import { addChoice, removeChoices, removeChoice, changeDepartureTime, jdemeObed, updateNote } from './api/FoodApi'; import { getHumanDateTime, isInTheFuture } from './Utils'; import NoteModal from './components/modals/NoteModal'; import { useEasterEgg } from './context/eggs'; -import { getImage } from './api/EasterEggApi'; import { Link } from 'react-router'; import { STATS_URL } from './AppRoutes'; -import { ClientData, Food, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, UserLunchChoice, PizzaVariant } from '../../types'; +import { ClientData, Food, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, UserLunchChoice, PizzaVariant, getData, getEasterEggImage, addPizza, removePizza, updatePizzaDayNote, createPizzaDay, deletePizzaDay, lockPizzaDay, unlockPizzaDay, finishOrder, finishDelivery, addChoice, jdemeObed, removeChoices, removeChoice, updateNote, changeDepartureTime } from '../../types'; +import { getLunchChoiceName } from './enums'; const EVENT_CONNECT = "connect" @@ -68,7 +65,8 @@ function App() { if (!auth?.login) { return } - getData().then(data => { + getData().then(response => { + const data = response.data if (data) { setData(data); setDayIndex(data.dayIndex); @@ -85,9 +83,12 @@ function App() { if (!auth?.login) { return } - getData(dayIndex).then((data: ClientData) => { + getData({ query: { dayIndex: dayIndex } }).then(response => { + const data = response.data; setData(data); - setFood(data.menus); + if (data) { + setFood(data.menus); + } }).catch(e => { setFailure(true); }) @@ -142,10 +143,10 @@ function App() { useEffect(() => { if (choiceRef?.current?.value && choiceRef.current.value !== "") { - const locationKey = choiceRef.current.value as keyof typeof LunchChoice; + const locationKey = choiceRef.current.value as LunchChoice; const restaurantKey = Object.keys(Restaurant).indexOf(locationKey); if (restaurantKey > -1 && food) { - const restaurant = Object.keys(Restaurant)[restaurantKey] as keyof typeof Restaurant; + const restaurant = Object.keys(Restaurant)[restaurantKey] as Restaurant; setFoodChoiceList(food[restaurant]?.food); setClosed(food[restaurant]?.closed ?? false); } else { @@ -177,9 +178,9 @@ function App() { // Stažení a nastavení easter egg obrázku useEffect(() => { if (auth?.login && easterEgg?.url && !eggImage) { - getImage(easterEgg.url).then(data => { - if (data) { - setEggImage(data); + getEasterEggImage({ path: { url: easterEgg.url } }).then(response => { + if (response.data) { + setEggImage(response.data); // Smazání obrázku z DOMu po animaci setTimeout(() => { if (eggRef?.current) { @@ -191,18 +192,18 @@ function App() { } }, [auth?.login, easterEgg?.duration, easterEgg?.url, eggImage]); - const doAddClickFoodChoice = async (location: keyof typeof LunchChoice, foodIndex?: number) => { + const doAddClickFoodChoice = async (location: LunchChoice, foodIndex?: number) => { if (document.getSelection()?.type !== 'Range') { // pouze pokud se nejedná o výběr textu if (auth?.login) { - await errorHandler(() => addChoice(location, foodIndex, dayIndex)); + await addChoice({ body: { locationKey: location, foodIndex, dayIndex } }); } } } const doAddChoice = async (event: React.ChangeEvent) => { - const locationKey = event.target.value as keyof typeof LunchChoice; + const locationKey = event.target.value as LunchChoice; if (auth?.login) { - await errorHandler(() => addChoice(locationKey, undefined, dayIndex)); + await addChoice({ body: { locationKey, dayIndex } }); if (foodChoiceRef.current?.value) { foodChoiceRef.current.value = ""; } @@ -217,16 +218,16 @@ function App() { const doAddFoodChoice = async (event: React.ChangeEvent) => { if (event.target.value && foodChoiceList?.length && choiceRef.current?.value) { - const locationKey = choiceRef.current.value as keyof typeof LunchChoice; + const locationKey = choiceRef.current.value as LunchChoice; if (auth?.login) { - await errorHandler(() => addChoice(locationKey, Number(event.target.value), dayIndex)); + await addChoice({ body: { locationKey, foodIndex: Number(event.target.value), dayIndex } }); } } } - const doRemoveChoices = async (locationKey: keyof typeof LunchChoice) => { + const doRemoveChoices = async (locationKey: LunchChoice) => { if (auth?.login) { - await errorHandler(() => removeChoices(locationKey, dayIndex)); + await removeChoices({ body: { locationKey, dayIndex } }); // Vyresetujeme výběr, aby bylo jasné pro který případně vybíráme jídlo if (choiceRef?.current?.value) { choiceRef.current.value = ""; @@ -237,9 +238,9 @@ function App() { } } - const doRemoveFoodChoice = async (locationKey: keyof typeof LunchChoice, foodIndex: number) => { + const doRemoveFoodChoice = async (locationKey: LunchChoice, foodIndex: number) => { if (auth?.login) { - await errorHandler(() => removeChoice(locationKey, foodIndex, dayIndex)); + await removeChoice({ body: { locationKey, foodIndex, dayIndex } }); if (choiceRef?.current?.value) { choiceRef.current.value = ""; } @@ -251,7 +252,7 @@ function App() { const saveNote = async (note?: string) => { if (auth?.login) { - await errorHandler(() => updateNote(note, dayIndex)); + await updateNote({ body: { note, dayIndex } }); setNoteModalOpen(false); } } @@ -281,12 +282,12 @@ function App() { const s = value.split('|'); const pizzaIndex = Number.parseInt(s[0]); const pizzaSizeIndex = Number.parseInt(s[1]); - await addPizza(pizzaIndex, pizzaSizeIndex); + await addPizza({ body: { pizzaIndex, pizzaSizeIndex } }); } } const handlePizzaDelete = async (pizzaOrder: PizzaVariant) => { - await removePizza(pizzaOrder); + await removePizza({ body: { pizzaOrder } }); } const handlePizzaPoznamkaChange = async () => { @@ -294,7 +295,7 @@ function App() { alert("Poznámka může mít maximálně 70 znaků"); return; } - updatePizzaDayNote(pizzaPoznamkaRef.current?.value); + updatePizzaDayNote({ body: { note: pizzaPoznamkaRef.current?.value } }); } // const addToCart = async () => { @@ -323,7 +324,7 @@ function App() { const handleChangeDepartureTime = async (event: React.ChangeEvent) => { if (foodChoiceList?.length && choiceRef.current?.value) { - await changeDepartureTime(event.target.value, dayIndex); + await changeDepartureTime({ body: { time: event.target.value as DepartureTime, dayIndex } }); } } @@ -341,7 +342,7 @@ function App() { } } - const renderFoodTable = (location: keyof typeof Restaurant, menu: RestaurantDayMenu) => { + const renderFoodTable = (location: Restaurant, menu: RestaurantDayMenu) => { let content; if (menu?.closed) { content =

Zavřeno

@@ -363,7 +364,7 @@ function App() { content =

Chyba načtení dat

} return -

doAddClickFoodChoice(location)}>{LunchChoice[location]}

+

doAddClickFoodChoice(location)}>{getLunchChoiceName(location)}

{menu?.lastUpdate && Poslední aktualizace: {getHumanDateTime(new Date(menu.lastUpdate))}} {content} @@ -439,10 +440,10 @@ function App() { {Object.entries(LunchChoice) .filter(entry => { - const locationKey = entry[0] as keyof typeof Restaurant; + const locationKey = entry[0] as Restaurant; return !food[locationKey]?.closed; }) - .map(entry => )} + .map(entry => )} Je možné vybrat jen jednu možnost. Výběr jiné odstraní předchozí. {foodChoiceList && !closed && <> @@ -466,8 +467,8 @@ function App() { {Object.keys(data.choices).map(key => { - const locationKey = key as keyof typeof LunchChoice; - const locationName = LunchChoice[locationKey]; + const locationKey = key as LunchChoice; + const locationName = getLunchChoiceName(locationKey); const loginObject = data.choices[locationKey]; if (!loginObject) { return; @@ -500,13 +501,13 @@ function App() { setNoteModalOpen(true); }} title='Upravit poznámku' className='action-icon' icon={faNoteSticky} />} {login === auth.login && canChangeChoice && { - doRemoveChoices(key as keyof typeof LunchChoice); + doRemoveChoices(key as LunchChoice); }} title={`Odstranit volbu ${locationName}, včetně případných zvolených jídel`} className='action-icon' icon={faTrashCan} />} {userChoices?.length && food ?
    {userChoices?.map(foodIndex => { - const restaurantKey = key as keyof typeof Restaurant; + const restaurantKey = key as Restaurant; const foodName = food[restaurantKey]?.food?.[foodIndex].name; return
  • {foodName} @@ -601,7 +602,7 @@ function App() { await lockPizzaDay(); }}>Vrátit do "uzamčeno" } @@ -643,7 +644,7 @@ function App() { data.pizzaDay.state === PizzaDayState.DELIVERED && myOrder?.hasQr ?

    QR platba

    - QR kód + QR kód
    : null } diff --git a/client/src/Login.tsx b/client/src/Login.tsx index af769a4..39d23e1 100644 --- a/client/src/Login.tsx +++ b/client/src/Login.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useRef } from 'react'; import { Button } from 'react-bootstrap'; import { useAuth } from './context/auth'; -import { login } from './api/Api'; +import { login } from '../../types'; import './Login.css'; /** @@ -14,9 +14,10 @@ export default function Login() { useEffect(() => { if (auth && !auth.login) { // Vyzkoušíme přihlášení "naprázdno", pokud projde, přihlásili nás trusted headers - login().then(token => { + login().then(response => { + const token = response.data; if (token) { - auth?.setToken(token); + auth?.setToken(token as unknown as string); // TODO vyřešit, API definice je špatně, je to skutečně string } }).catch(error => { // nezajímá nás @@ -27,9 +28,9 @@ export default function Login() { const doLogin = useCallback(async () => { const length = loginRef?.current?.value.length && loginRef.current.value.replace(/\s/g, '').length if (length) { - const token = await login(loginRef.current?.value); - if (token) { - auth?.setToken(token); + const response = await login({ body: { login: loginRef.current?.value } }); + if (response.data) { + auth?.setToken(response.data as unknown as string); // TODO vyřešit } } }, [auth]); diff --git a/client/src/api/Api.ts b/client/src/api/Api.ts deleted file mode 100644 index 0bed18f..0000000 --- a/client/src/api/Api.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { toast } from "react-toastify"; -import { getToken } from "../Utils"; -import { ClientData } from "../../../types"; - -/** - * Wrapper pro volání API, u kterých chceme automaticky zobrazit toaster s chybou ze serveru. - * - * @param apiFunction volaná API funkce - */ -export function errorHandler(apiFunction: () => Promise): Promise { - return new Promise((resolve, reject) => { - apiFunction().then((result) => { - resolve(result); - }).catch(e => { - toast.error(e.message, { theme: "colored" }); - }); - }); -} - -async function request( - url: string, - config: RequestInit = {} -): Promise { - config.headers = config?.headers ? new Headers(config.headers) : new Headers(); - config.headers.set("Authorization", `Bearer ${getToken()}`); - try { - const response = await fetch(url, config); - if (!response.ok) { - // TODO tohle je blbě, jelikož automaticky očekáváme, že v případě chyby přijde vždy JSON, což není pravda - const json = await response.json(); - // Vyhodíme samotnou hlášku z odpovědi, odchytí si jí errorHandler - throw new Error(json.error); - } - const contentType = response.headers.get("content-type"); - if (contentType && contentType.indexOf("application/json") !== -1) { - return response.json() as TResponse; - } else { - return response.text() as TResponse; - } - } catch (e) { - return Promise.reject(e); - } -} - -async function blobRequest( - url: string, - config: RequestInit = {} -): Promise { - config.headers = config?.headers ? new Headers(config.headers) : new Headers(); - config.headers.set("Authorization", `Bearer ${getToken()}`); - try { - const response = await fetch(url, config); - if (!response.ok) { - const json = await response.json(); - // Vyhodíme samotnou hlášku z odpovědi, odchytí si jí errorHandler - throw new Error(json.error); - } - return response.blob() - } catch (e) { - return Promise.reject(e); - } -} - -export const api = { - get: (url: string) => request(url), - blobGet: (url: string) => blobRequest(url), - post: (url: string, body?: TBody) => request(url, { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' } }), -} - -export const getQrUrl = (login: string) => { - return `/api/qr?login=${login}`; -} - -export const getData = async (dayIndex?: number): Promise => { - let url = '/api/data'; - if (dayIndex != null) { - url += '?dayIndex=' + dayIndex; - } - return await api.get(url); -} - -export const login = async (login?: string) => { - return await api.post('/api/login', { login }); -} diff --git a/client/src/api/EasterEggApi.ts b/client/src/api/EasterEggApi.ts deleted file mode 100644 index eed4197..0000000 --- a/client/src/api/EasterEggApi.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { EasterEgg } from "../../../types"; -import { api } from "./Api"; - -const EASTER_EGGS_API_PREFIX = '/api/easterEggs'; - -export const getEasterEgg = async (): Promise => { - return await api.get(`${EASTER_EGGS_API_PREFIX}`); -} - -export const getImage = async (url: string) => { - return await api.blobGet(`${EASTER_EGGS_API_PREFIX}/${url}`); -} \ No newline at end of file diff --git a/client/src/api/FoodApi.ts b/client/src/api/FoodApi.ts deleted file mode 100644 index d80bf8c..0000000 --- a/client/src/api/FoodApi.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { AddChoiceRequest, ChangeDepartureTimeRequest, LunchChoice, RemoveChoiceRequest, RemoveChoicesRequest, UpdateNoteRequest } from "../../../types"; -import { api } from "./Api"; - -const FOOD_API_PREFIX = '/api/food'; - -export const addChoice = async (locationKey: keyof typeof LunchChoice, foodIndex?: number, dayIndex?: number) => { - return await api.post(`${FOOD_API_PREFIX}/addChoice`, { locationKey, foodIndex, dayIndex }); -} - -export const removeChoices = async (locationKey: keyof typeof LunchChoice, dayIndex?: number) => { - return await api.post(`${FOOD_API_PREFIX}/removeChoices`, { locationKey, dayIndex }); -} - -export const removeChoice = async (locationKey: keyof typeof LunchChoice, foodIndex: number, dayIndex?: number) => { - return await api.post(`${FOOD_API_PREFIX}/removeChoice`, { locationKey, foodIndex, dayIndex }); -} - -export const updateNote = async (note?: string, dayIndex?: number) => { - return await api.post(`${FOOD_API_PREFIX}/updateNote`, { note, dayIndex }); -} - -export const changeDepartureTime = async (time: string, dayIndex?: number) => { - return await api.post(`${FOOD_API_PREFIX}/changeDepartureTime`, { time, dayIndex }); -} - -export const jdemeObed = async () => { - return await api.post(`${FOOD_API_PREFIX}/jdemeObed`); -} diff --git a/client/src/api/PizzaDayApi.ts b/client/src/api/PizzaDayApi.ts deleted file mode 100644 index 3a39ea6..0000000 --- a/client/src/api/PizzaDayApi.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AddPizzaRequest, FinishDeliveryRequest, PizzaVariant, RemovePizzaRequest, UpdatePizzaDayNoteRequest, UpdatePizzaFeeRequest } from "../../../types"; -import { api } from "./Api"; - -const PIZZADAY_API_PREFIX = '/api/pizzaDay'; - -export const createPizzaDay = async () => { - return await api.post(`${PIZZADAY_API_PREFIX}/create`); -} - -export const deletePizzaDay = async () => { - return await api.post(`${PIZZADAY_API_PREFIX}/delete`); -} - -export const lockPizzaDay = async () => { - return await api.post(`${PIZZADAY_API_PREFIX}/lock`); -} - -export const unlockPizzaDay = async () => { - return await api.post(`${PIZZADAY_API_PREFIX}/unlock`); -} - -export const finishOrder = async () => { - return await api.post(`${PIZZADAY_API_PREFIX}/finishOrder`); -} - -export const finishDelivery = async (bankAccount?: string, bankAccountHolder?: string) => { - return await api.post(`${PIZZADAY_API_PREFIX}/finishDelivery`, { bankAccount, bankAccountHolder }); -} - -export const addPizza = async (pizzaIndex: number, pizzaSizeIndex: number) => { - return await api.post(`${PIZZADAY_API_PREFIX}/add`, { pizzaIndex, pizzaSizeIndex }); -} - -export const removePizza = async (pizzaOrder: PizzaVariant) => { - return await api.post(`${PIZZADAY_API_PREFIX}/remove`, { pizzaOrder }); -} - -export const updatePizzaDayNote = async (note?: string) => { - return await api.post(`${PIZZADAY_API_PREFIX}/updatePizzaDayNote`, { note }); -} - -export const updatePizzaFee = async (login: string, text?: string, price?: number) => { - return await api.post(`${PIZZADAY_API_PREFIX}/updatePizzaFee`, { login, text, price }); -} diff --git a/client/src/api/StatsApi.ts b/client/src/api/StatsApi.ts deleted file mode 100644 index 03a3a71..0000000 --- a/client/src/api/StatsApi.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { WeeklyStats } from "../../../types"; -import { api } from "./Api"; - -const STATS_API_PREFIX = '/api/stats'; - -export const getStats = async (startDate: string, endDate: string) => { - return await api.get(`${STATS_API_PREFIX}?startDate=${startDate}&endDate=${endDate}`); -} diff --git a/client/src/api/VotingApi.ts b/client/src/api/VotingApi.ts deleted file mode 100644 index a1f7341..0000000 --- a/client/src/api/VotingApi.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { FeatureRequest, UpdateFeatureVoteRequest } from "../../../types"; -import { api } from "./Api"; - -const VOTING_API_PREFIX = '/api/voting'; - -export const getFeatureVotes = async () => { - return await api.get(`${VOTING_API_PREFIX}/getVotes`); -} - -export const updateFeatureVote = async (option: FeatureRequest, active: boolean) => { - return await api.post(`${VOTING_API_PREFIX}/updateVote`, { option, active }); -} \ No newline at end of file diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 4fcbbbb..bbacc2f 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -4,12 +4,10 @@ import { useAuth } from "../context/auth"; import SettingsModal from "./modals/SettingsModal"; import { useSettings } from "../context/settings"; import FeaturesVotingModal from "./modals/FeaturesVotingModal"; -import { errorHandler } from "../api/Api"; -import { getFeatureVotes, updateFeatureVote } from "../api/VotingApi"; import PizzaCalculatorModal from "./modals/PizzaCalculatorModal"; import { useNavigate } from "react-router"; import { STATS_URL } from "../AppRoutes"; -import { FeatureRequest } from "../../../types"; +import { FeatureRequest, getVotes, updateVote } from "../../../types"; export default function Header() { const auth = useAuth(); @@ -18,12 +16,12 @@ export default function Header() { const [settingsModalOpen, setSettingsModalOpen] = useState(false); const [votingModalOpen, setVotingModalOpen] = useState(false); const [pizzaModalOpen, setPizzaModalOpen] = useState(false); - const [featureVotes, setFeatureVotes] = useState([]); + const [featureVotes, setFeatureVotes] = useState([]); useEffect(() => { if (auth?.login) { - getFeatureVotes().then(votes => { - setFeatureVotes(votes); + getVotes().then(response => { + setFeatureVotes(response.data); }) } }, [auth?.login]); @@ -99,8 +97,8 @@ export default function Header() { } const saveFeatureVote = async (option: FeatureRequest, active: boolean) => { - await errorHandler(() => updateFeatureVote(option, active)); - const votes = [...featureVotes]; + await updateVote({ body: { option, active } }); + const votes = [...featureVotes || []]; if (active) { votes.push(option); } else { diff --git a/client/src/components/PizzaOrderList.tsx b/client/src/components/PizzaOrderList.tsx index 3d313e2..cbc9cc7 100644 --- a/client/src/components/PizzaOrderList.tsx +++ b/client/src/components/PizzaOrderList.tsx @@ -1,7 +1,6 @@ import { Table } from "react-bootstrap"; import PizzaOrderRow from "./PizzaOrderRow"; -import { updatePizzaFee } from "../api/PizzaDayApi"; -import { PizzaDayState, PizzaOrder, PizzaVariant } from "../../../types"; +import { PizzaDayState, PizzaOrder, PizzaVariant, updatePizzaFee } from "../../../types"; type Props = { state: PizzaDayState, @@ -12,7 +11,7 @@ type Props = { export default function PizzaOrderList({ state, orders, onDelete, creator }: Readonly) { const saveFees = async (customer: string, text?: string, price?: number) => { - await updatePizzaFee(customer, text, price); + await updatePizzaFee({ body: { login: customer, text, price } }); } if (!orders?.length) { diff --git a/client/src/context/eggs.tsx b/client/src/context/eggs.tsx index dd1f750..6ecc21f 100644 --- a/client/src/context/eggs.tsx +++ b/client/src/context/eggs.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from "react"; -import { getEasterEgg } from "../api/EasterEggApi"; import { AuthContextProps } from "./auth"; -import { EasterEgg } from "../../../types"; +import { EasterEgg, getEasterEgg } from "../../../types"; export const useEasterEgg = (auth?: AuthContextProps | null): [EasterEgg | undefined, boolean] => { const [result, setResult] = useState(); @@ -11,7 +10,7 @@ export const useEasterEgg = (auth?: AuthContextProps | null): [EasterEgg | undef async function fetchEasterEgg() { if (auth?.login) { setLoading(true); - const egg = await getEasterEgg(); + const egg = (await getEasterEgg())?.data; setResult(egg); setLoading(false); } diff --git a/client/src/enums.ts b/client/src/enums.ts new file mode 100644 index 0000000..161f4c6 --- /dev/null +++ b/client/src/enums.ts @@ -0,0 +1,41 @@ +import { LunchChoice, Restaurant } from "../../types"; + +export function getRestaurantName(restaurant: Restaurant) { + switch (restaurant) { + case Restaurant.SLADOVNICKA: + return "Sladovnická"; + case Restaurant.TECHTOWER: + return "TechTower"; + case Restaurant.ZASTAVKAUMICHALA: + return "Zastávka u Michala"; + case Restaurant.SENKSERIKOVA: + return "Šenk Šeříková"; + default: + return restaurant; + } +} + +export function getLunchChoiceName(location: LunchChoice) { + switch (location) { + case Restaurant.SLADOVNICKA: + return "Sladovnická"; + case Restaurant.TECHTOWER: + return "TechTower"; + case Restaurant.ZASTAVKAUMICHALA: + return "Zastávka u Michala"; + case Restaurant.SENKSERIKOVA: + return "Šenk Šeříková"; + case LunchChoice.SPSE: + return "SPŠE"; + case LunchChoice.PIZZA: + return "Pizza day"; + case LunchChoice.OBJEDNAVAM: + return "Budu objednávat"; + case LunchChoice.NEOBEDVAM: + return "Mám vlastní/neobědvám"; + case LunchChoice.ROZHODUJI: + return "Rozhoduji se"; + default: + return location; + } +} \ No newline at end of file diff --git a/client/src/index.tsx b/client/src/index.tsx index 3073e6d..3f8e9d7 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -5,6 +5,24 @@ import 'react-toastify/dist/ReactToastify.css'; import './index.css'; import AppRoutes from './AppRoutes'; import { BrowserRouter } from 'react-router'; +import { client } from '../../types/gen/client.gen'; +import { getToken } from './Utils'; +import { toast } from 'react-toastify'; + +client.setConfig({ + auth: () => getToken(), + baseUrl: '/api', // openapi-ts si to z nějakého důvodu neumí převzít z api.yml +}); + +// Interceptor na vyhození toasteru při chybě +client.interceptors.response.use(async response => { + // TODO opravit - login je zatím výjimka, voláme ho "naprázdno" abychom zjistili, zda nás nepřihlásily trusted headers + if (!response.ok && response.url.indexOf("/login") == -1) { + const json = await response.json(); + toast.error(json.error, { theme: "colored" }); + } + return response; +}); const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement diff --git a/client/src/pages/StatsPage.tsx b/client/src/pages/StatsPage.tsx index 0b589ea..e202f89 100644 --- a/client/src/pages/StatsPage.tsx +++ b/client/src/pages/StatsPage.tsx @@ -4,12 +4,12 @@ import Header from "../components/Header"; import { useAuth } from "../context/auth"; import Login from "../Login"; import { formatDate, getFirstWorkDayOfWeek, getHumanDate, getLastWorkDayOfWeek } from "../Utils"; -import { getStats } from "../api/StatsApi"; -import { WeeklyStats, LunchChoice } from "../../../types"; +import { WeeklyStats, LunchChoice, getStats } from "../../../types"; import Loader from "../components/Loader"; import { faChevronLeft, faChevronRight, faGear } from "@fortawesome/free-solid-svg-icons"; import { Legend, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { getLunchChoiceName } from "../enums"; import './StatsPage.scss'; const CHART_WIDTH = 1400; @@ -43,14 +43,15 @@ export default function StatsPage() { // Přenačtení pro zvolený týden useEffect(() => { if (dateRange) { - getStats(formatDate(dateRange[0]), formatDate(dateRange[1])).then(setData); + getStats({ query: { startDate: formatDate(dateRange[0]), endDate: formatDate(dateRange[1]) } }).then(response => { + setData(response.data); + }); } }, [dateRange]); const renderLine = (location: LunchChoice) => { const index = Object.values(LunchChoice).indexOf(location); - const key = Object.keys(LunchChoice)[index]; - return data.locations[key] ?? 0} stroke={COLORS[index]} strokeWidth={STROKE_WIDTH} /> + return data.locations[location] ?? 0} stroke={COLORS[index]} strokeWidth={STROKE_WIDTH} /> } const handlePreviousWeek = () => { diff --git a/server/src/service.ts b/server/src/service.ts index a0d4eeb..0a4d024 100644 --- a/server/src/service.ts +++ b/server/src/service.ts @@ -84,7 +84,7 @@ async function getMenu(date: Date): Promise { * @param date datum, ke kterému získat menu * @param mock příznak, zda chceme pouze mock data */ -export async function getRestaurantMenu(restaurant: keyof typeof Restaurant, date?: Date): Promise { +export async function getRestaurantMenu(restaurant: Restaurant, date?: Date): Promise { const usedDate = date ?? getToday(); const dayOfWeekIndex = getDayOfWeekIndex(usedDate); const now = new Date().getTime(); @@ -210,7 +210,7 @@ export async function initIfNeeded(date?: Date) { * @param date datum, ke kterému se volba vztahuje * @returns */ -export async function removeChoices(login: string, trusted: boolean, locationKey: keyof typeof LunchChoice, date?: Date) { +export async function removeChoices(login: string, trusted: boolean, locationKey: LunchChoice, date?: Date) { const selectedDay = formatDate(date ?? getToday()); let data = await getClientData(date); validateTrusted(data, login, trusted); @@ -237,7 +237,7 @@ export async function removeChoices(login: string, trusted: boolean, locationKey * @param date datum, ke kterému se volba vztahuje * @returns */ -export async function removeChoice(login: string, trusted: boolean, locationKey: keyof typeof LunchChoice, foodIndex: number, date?: Date) { +export async function removeChoice(login: string, trusted: boolean, locationKey: LunchChoice, foodIndex: number, date?: Date) { const selectedDay = formatDate(date ?? getToday()); let data = await getClientData(date); validateTrusted(data, login, trusted); @@ -260,11 +260,11 @@ export async function removeChoice(login: string, trusted: boolean, locationKey: * @param date datum, ke kterému se volby vztahují * @param ignoredLocationKey volba, která nebude odstraněna, pokud existuje */ -async function removeChoiceIfPresent(login: string, date?: Date, ignoredLocationKey?: keyof typeof LunchChoice) { +async function removeChoiceIfPresent(login: string, date?: Date, ignoredLocationKey?: LunchChoice) { const usedDate = date ?? getToday(); let data = await getClientData(usedDate); for (const key of Object.keys(data.choices)) { - const locationKey = key as keyof typeof LunchChoice; + const locationKey = key as LunchChoice; if (ignoredLocationKey != null && ignoredLocationKey == locationKey) { continue; } @@ -312,7 +312,7 @@ function validateTrusted(data: ClientData, login: string, trusted: boolean) { * @param date datum, ke kterému se volba vztahuje * @returns aktuální data */ -export async function addChoice(login: string, trusted: boolean, locationKey: keyof typeof LunchChoice, foodIndex?: number, date?: Date) { +export async function addChoice(login: string, trusted: boolean, locationKey: LunchChoice, foodIndex?: number, date?: Date) { const usedDate = date ?? getToday(); await initIfNeeded(usedDate); let data = await getClientData(usedDate); @@ -353,7 +353,7 @@ export async function addChoice(login: string, trusted: boolean, locationKey: ke * @param foodIndex index jídla pro danou lokalitu * @param date datum, pro které je validace prováděna */ -async function validateFoodIndex(locationKey: keyof typeof LunchChoice, foodIndex?: number, date?: Date) { +async function validateFoodIndex(locationKey: LunchChoice, foodIndex?: number, date?: Date) { if (foodIndex != null) { if (typeof foodIndex !== 'number') { throw Error(`Neplatný index ${foodIndex} typu ${typeof foodIndex}`); @@ -365,7 +365,7 @@ async function validateFoodIndex(locationKey: keyof typeof LunchChoice, foodInde throw Error(`Neplatný index ${foodIndex} pro lokalitu ${locationKey} nepodporující indexy`); } const usedDate = date ?? getToday(); - const menu = await getRestaurantMenu(locationKey as keyof typeof Restaurant, usedDate); + const menu = await getRestaurantMenu(locationKey as Restaurant, usedDate); if (menu.food?.length && foodIndex > (menu.food.length - 1)) { throw new Error(`Neplatný index ${foodIndex} pro lokalitu ${locationKey}`); } diff --git a/server/src/stats.ts b/server/src/stats.ts index 34507f6..d4ac3f7 100644 --- a/server/src/stats.ts +++ b/server/src/stats.ts @@ -39,7 +39,7 @@ export async function getStats(startDate: string, endDate: string): Promise