diff --git a/client/package.json b/client/package.json index 1a9231f..16c6555 100644 --- a/client/package.json +++ b/client/package.json @@ -21,6 +21,7 @@ "react-bootstrap": "^2.7.2", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "react-select-search": "^4.1.6", "socket.io-client": "^4.6.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -48,5 +49,6 @@ "last 1 firefox version", "last 1 safari version" ] - } + }, + "devDependencies": {} } diff --git a/client/src/Api.ts b/client/src/Api.ts index 75b1954..2f02b4d 100644 --- a/client/src/Api.ts +++ b/client/src/Api.ts @@ -38,12 +38,12 @@ export const getPizzy = async () => { return await api.get('/api/pizza'); } -export const createPizzaDay = async () => { - return await api.post('/api/createPizzaDay', {}); +export const createPizzaDay = async (creator) => { + return await api.post('/api/createPizzaDay', JSON.stringify({ creator })); } -export const deletePizzaDay = async () => { - return await api.post('/api/deletePizzaDay', {}); +export const deletePizzaDay = async (login) => { + return await api.post('/api/deletePizzaDay', JSON.stringify({ login })); } export const updateChoice = async (name: string, choice: number | null) => { diff --git a/client/src/App.tsx b/client/src/App.tsx index 0723d30..0df4048 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,15 +1,20 @@ -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import 'bootstrap/dist/css/bootstrap.min.css'; import { EVENT_DISCONNECT, EVENT_MESSAGE, SocketContext } from './context/socket'; -import { getData, getFood, updateChoice } from './Api'; +import { createPizzaDay, deletePizzaDay, getData, getFood, getPizzy, updateChoice } from './Api'; import { useAuth } from './context/auth'; import Login from './Login'; -import { Locations, ClientData } from './Types'; -import { Alert, Col, Form, Row, Table } from 'react-bootstrap'; +import { Locations, ClientData, Pizza } from './Types'; +import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap'; import Header from './components/Header'; import { icon } from '@fortawesome/fontawesome-svg-core/import.macro' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import PizzaOrderList from './components/PizzaOrderList'; +import SelectSearch from 'react-select-search'; +import 'react-select-search/style.css'; import './App.css'; +import { SelectSearchOption } from 'react-select-search'; + const EVENT_CONNECT = "connect" @@ -18,15 +23,15 @@ function App() { const [isConnected, setIsConnected] = useState(false); const [data, setData] = useState(); const [food, setFood] = useState(); - // const [pizzy, setPizzy] = useState(); + const [pizzy, setPizzy] = useState(); const socket = useContext(SocketContext); const choiceRef = useRef(null); // Prvotní načtení aktuálního stavu useEffect(() => { - // getPizzy().then(pizzy => { - // setPizzy(pizzy); - // }); + getPizzy().then(pizzy => { + setPizzy(pizzy); + }); getData().then(data => { setData(data); }) @@ -88,6 +93,31 @@ function App() { } } + const pizzaSuggestions = useMemo(() => { + if (!pizzy) { + return []; + } + const suggestions: SelectSearchOption[] = []; + pizzy.forEach((pizza, index) => { + pizza.sizes.forEach((size, sizeIndex) => { + const name = `${pizza.name}, ${size.size} (${size.price} Kč)`; + const value = `${index}|${sizeIndex}`; + suggestions.push({ name, value }); + }) + }) + return suggestions; + }, [pizzy]); + + const handlePizzaChange = (value) => { + console.log("Pizza vybrána", value); + if (pizzy) { + const s = value.split('|'); + const pizza = pizzy[Number.parseInt(s[0])]; + const size = pizza.sizes[Number.parseInt(s[1])]; + console.log("Vybraná pizza a velikost", pizza, size); + } + } + const renderFoodTable = (name, food) => { return

{name}

@@ -113,8 +143,6 @@ function App() { return
Načítám data...
} - // const pizzaDayExists = data?.state > 0; - return ( <>
@@ -168,26 +196,31 @@ function App() { :
Zatím nikdo nehlasoval...
} + {/* {!data.pizzaDay && +
+

Pro dnešní den není aktuálně založen Pizza day.

+ +
+ } + {data.pizzaDay &&
+

Pizza Day je založen uživatelem {data.pizzaDay.creator}

+ { + data.pizzaDay.creator === auth.login && + } + + +
} */} } - {/* {!pizzaDayExists && -
-

Pro dnešní den není aktuálně založen Pizza day.

- -
- } - {pizzaDayExists &&
- - -
} */} - {/* */} ); diff --git a/client/src/Types.tsx b/client/src/Types.tsx index 9ae4a6f..f971e06 100644 --- a/client/src/Types.tsx +++ b/client/src/Types.tsx @@ -1,12 +1,20 @@ // TODO všechno v tomto souboru jsou duplicity se serverem, ale aktuálně nevím jaký je nejlepší způsob jejich sdílení +export interface PizzaSize { + size: string, // velikost pizzy, např. "30cm" + pizzaPrice: number, // cena samotné pizzy + boxPrice: number, // cena krabice + price: number, // celková cena (pizza + krabice) +} + /** Jedna konkrétní pizza */ export interface Pizza { name: string, // název pizzy - size: number, // velikost pizzy v cm - price: number, // cena pizzy v Kč, včetně krabice + ingredients: string[], // seznam ingrediencí + sizes: PizzaSize[], // dostupné velikosti pizzy } +/** Jedna objednávka v rámci Pizza day */ export interface Order { customer: string, // název člověka pizzaList: Pizza[], // seznam objednaných pizz @@ -17,10 +25,18 @@ export interface Choices { [location: string]: string[], } +/** Údaje o Pizza day. */ +export interface PizzaDay { + state: State, + creator: string, + orders: Order[] +} + export interface ClientData { date: string, // dnešní datum pro zobrazení isWeekend: boolean, // příznak zda je dnešní den víkend choices: Choices, // seznam voleb + pizzaDay?: PizzaDay, // údaje o pizza day, pokud je pro dnešek založen } export enum Locations { @@ -31,4 +47,10 @@ export enum Locations { VLASTNI = 'Mám vlastní', OBJEDNAVAM = 'Objednávám', NEOBEDVAM = 'Neobědvám', +} + +export enum State { + NOT_CREATED, // Pizza day nebyl založen + CREATED, // Pizza day je založen + LOCKED // Objednávky uzamčeny } \ No newline at end of file diff --git a/client/src/components/OrderList.tsx b/client/src/components/PizzaOrderList.tsx similarity index 75% rename from client/src/components/OrderList.tsx rename to client/src/components/PizzaOrderList.tsx index 1503b71..d40bc1c 100644 --- a/client/src/components/OrderList.tsx +++ b/client/src/components/PizzaOrderList.tsx @@ -1,7 +1,8 @@ +import React from "react"; import { Table } from "react-bootstrap"; import { Order } from "../Types"; -export default function OrderList({ orders }: { orders: Order[] }) { +export default function PizzaOrderList({ orders }: { orders: Order[] }) { return @@ -12,7 +13,7 @@ export default function OrderList({ orders }: { orders: Order[] }) { {orders.map(order => - + )} diff --git a/client/yarn.lock b/client/yarn.lock index 55ec4d8..70f2208 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -7767,6 +7767,11 @@ react-scripts@5.0.1: optionalDependencies: fsevents "^2.3.2" +react-select-search@^4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/react-select-search/-/react-select-search-4.1.6.tgz#4c3165e02007518726e004267fd1168e0076061d" + integrity sha512-BJMf11Ux0hqn6Z3BqRwceXdwjdF+dnpDsYGGehDPB/nZv+Dse7wdPUMqLSCVDyrH5y3xFu7r6IlZ6dj78291vA== + react-transition-group@^4.4.2: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" diff --git a/server/src/chefie.ts b/server/src/chefie.ts index c701099..363d912 100644 --- a/server/src/chefie.ts +++ b/server/src/chefie.ts @@ -67,11 +67,9 @@ const downloadPizzy = async () => { const sizes: PizzaSize[] = []; const a = $('.varianty > li > a', pizzaHtml); a.each((i, elm) => { - // TODO nedoděláno - // const size = $('span', elm).text(); - // const priceKc = $(elm).text().split(size).pop().trim(); - // const price = Number.parseInt(priceKc.split(" Kč")[0]); - // sizes.push({ size: size, pizzaPrice: price, boxPrice: boxPrices[size], price: price + boxPrices[size] }); + const size = $($(elm).contents().get(0)).text().trim(); + const price = Number.parseInt($($(elm).contents().get(1)).text().trim().split(" Kč")[0]); + sizes.push({ size: size, pizzaPrice: price, boxPrice: boxPrices[size], price: price + boxPrices[size] }); }) result.push({ name: name, diff --git a/server/src/index.ts b/server/src/index.ts index 076325d..e01028d 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -3,7 +3,7 @@ import { Server } from "socket.io"; import bodyParser from "body-parser"; import { fetchPizzy } from "./chefie"; import cors from 'cors'; -import { getData, updateChoice } from "./service"; +import { createPizzaDay, deletePizzaDay, getData, updateChoice } from "./service"; import dotenv from 'dotenv'; import path from 'path'; import { fetchMenus } from "./restaurants"; @@ -42,23 +42,30 @@ app.get("/api/food", (req, res) => { /** Vrátí seznam dostupných pizz. */ app.get("/api/pizza", (req, res) => { fetchPizzy().then(pizzaList => { - console.log("Výsledek", pizzaList); + // console.log("Výsledek", pizzaList); res.status(200).json(pizzaList); }); }); // /** Založí pizza day pro aktuální den, za předpokladu že dosud neexistuje. */ -// app.post("/api/createPizzaDay", (req, res) => { -// const data = createPizzaDay(); -// res.status(200).json(data); -// io.emit("message", data); -// }); +app.post("/api/createPizzaDay", (req, res) => { + console.log("Založení pizza day", req) // TODO smazat + if (!req.body?.creator) { + throw Error("Nebyl předán název zakládajícího"); + } + const data = createPizzaDay(req.body.creator); + res.status(200).json(data); + io.emit("message", data); +}); // /** Smaže pizza day pro aktuální den, za předpokladu že existuje. */ -// app.post("/api/deletePizzaDay", (req, res) => { -// deletePizzaDay(); -// io.emit("message", getData()); -// }); +app.post("/api/deletePizzaDay", (req, res) => { + if (!req.body?.login) { + throw Error("Nebyl předán login uživatele"); + } + deletePizzaDay(req.body.login); + io.emit("message", getData()); +}); app.post("/api/updateChoice", (req, res) => { console.log("Změna výběru", req.body); diff --git a/server/src/service.ts b/server/src/service.ts index 6a22881..f9fe66f 100644 --- a/server/src/service.ts +++ b/server/src/service.ts @@ -1,36 +1,8 @@ -import { ClientData, Locations } from "./types"; +import { ClientData, Locations, State } from "./types"; import { db } from "./database"; import { getHumanDate, getIsWeekend } from "./utils"; import { formatDate } from "./utils"; -// /** Jedna konkrétní pizza */ -// interface Pizza { -// name: string, // název pizzy -// size: number, // velikost pizzy v cm -// price: number, // cena pizzy v Kč, včetně krabice -// } - -// /** Objednávka jednoho člověka */ -// interface Order { -// customer: string, // název člověka -// pizzaList: Pizza[], // seznam objednaných pizz -// totalPrice: number, // celková cena všech objednaných pizz a krabic -// } - -// /** Stav pizza dne. */ -// enum State { -// NOT_CREATED, // Pizza day nebyl založen -// CREATED, // Pizza day je založen -// LOCKED // Objednávky uzamčeny -// } - -// /** Veškerá data pro zobrazení na klientovi */ -// interface ClientData { -// date: string, // dnešní datum pro zobrazení -// state: State, // stav pizza dne -// orders?: Order[], // seznam objednávek, pokud není vyplněno, není založen pizza day -// } - /** Vrátí dnešní datum, případně fiktivní datum pro účely vývoje a testování. */ function getToday(): Date { if (process.env.MOCK_DATA) { @@ -52,29 +24,35 @@ export function getData(): ClientData { return data; } -// /** -// * Vytvoří pizza day pro aktuální den a vrátí data pro klienta. -// */ -// export function createPizzaDay(): ClientData { -// const today = getDate(); -// if (db.has(today)) { -// throw Error("Pizza day pro dnešní den již existuje"); -// } -// const data = { date: getTodayString(), state: State.CREATED, orders: [] }; -// db.set(today, data); -// return data; -// } +/** + * Vytvoří pizza day pro aktuální den a vrátí data pro klienta. + */ +export function createPizzaDay(creator: string): ClientData { + initIfNeeded(); + const today = formatDate(getToday()); + const clientData: ClientData = db.get(today); + if (clientData.pizzaDay) { + throw Error("Pizza day pro dnešní den již existuje"); + } + const data: ClientData = { pizzaDay: { state: State.CREATED, creator, orders: [] }, ...clientData }; + db.set(today, data); + return data; +} -// /** -// * Smaže pizza day pro aktuální den. -// */ -// export function deletePizzaDay() { -// const today = getDate(); -// if (!db.has(today)) { -// throw Error("Pizza day pro dnešní den neexistuje"); -// } -// db.delete(today); -// } +/** + * Smaže pizza day pro aktuální den. + */ +export function deletePizzaDay(login: string) { + const today = formatDate(getToday()); + const clientData: ClientData = db.get(today); + if (!clientData.pizzaDay) { + throw Error("Pizza day pro dnešní den neexistuje"); + } + if (clientData.pizzaDay.creator !== login) { + throw Error("Login uživatele se neshoduje se zakladatelem Pizza Day"); + } + db.delete(today); +} export function initIfNeeded() { const today = formatDate(getToday()); diff --git a/server/src/types.ts b/server/src/types.ts index 69b7bfd..bb59fec 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -2,10 +2,39 @@ export interface Choices { [location: string]: string[], } +// /** Jedna konkrétní pizza */ +interface Pizza { + name: string, // název pizzy + size: number, // velikost pizzy v cm + price: number, // cena pizzy v Kč, včetně krabice +} + +// /** Objednávka jednoho člověka */ +interface Order { + customer: string, // název člověka + pizzaList: Pizza[], // seznam objednaných pizz + totalPrice: number, // celková cena všech objednaných pizz a krabic +} + +// /** Stav pizza dne. */ +export enum State { + NOT_CREATED, // Pizza day nebyl založen + CREATED, // Pizza day je založen + LOCKED // Objednávky uzamčeny +} + +// /** Veškerá data pro zobrazení na klientovi */ +interface PizzaDay { + state: State, // stav pizza dne + creator: string, // jméno zakladatele + orders: Order[], // seznam objednávek, pokud není vyplněno, není založen pizza day +} + export interface ClientData { date: string, // dnešní datum pro zobrazení isWeekend: boolean, // příznak, zda je dnes víkend choices: Choices, // seznam voleb + pizzaDay?: PizzaDay, // pizza day pro dnešní den, pokud existuje } export enum Locations {
{order.pizzaList[0].name}, {order.pizzaList[0].size}{order.pizzaList[0].name} {order.customer} {order.totalPrice}