Přechody mezi stavy Pizza Day

This commit is contained in:
Martin Berka 2023-06-09 20:58:37 +02:00
parent d5d0b88d3c
commit dfce063de7
8 changed files with 249 additions and 20 deletions

View File

@ -38,6 +38,22 @@ export const deletePizzaDay = async (login) => {
return await api.post<any, any>('/api/deletePizzaDay', JSON.stringify({ login }));
}
export const lockPizzaDay = async (login) => {
return await api.post<any, any>('/api/lockPizzaDay', JSON.stringify({ login }));
}
export const unlockPizzaDay = async (login) => {
return await api.post<any, any>('/api/unlockPizzaDay', JSON.stringify({ login }));
}
export const finishOrder = async (login) => {
return await api.post<any, any>('/api/finishOrder', JSON.stringify({ login }));
}
export const finishDelivery = async (login) => {
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ login }));
}
export const updateChoice = async (name: string, choice: number | null) => {
return await api.post<any, any>('/api/updateChoice', JSON.stringify({ name, choice }));
}

View File

@ -1,10 +1,10 @@
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 { addPizza, createPizzaDay, deletePizzaDay, getData, getFood, getPizzy, removePizza, updateChoice } from './Api';
import { addPizza, createPizzaDay, deletePizzaDay, finishDelivery, finishOrder, getData, getFood, getPizzy, lockPizzaDay, removePizza, unlockPizzaDay, updateChoice } from './Api';
import { useAuth } from './context/auth';
import Login from './Login';
import { Locations, ClientData, Pizza, PizzaOrder } from './Types';
import { Locations, ClientData, Pizza, PizzaOrder, State } from './Types';
import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap';
import Header from './components/Header';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
@ -125,6 +125,30 @@ function App() {
}
}
const addToCart = async () => {
// TODO aktuálně nefunkční - nedokážeme poslat PHPSESSIONID cookie
// if (data?.pizzaDay?.orders) {
// for (const order of data?.pizzaDay?.orders) {
// for (const pizzaOrder of order.pizzaList) {
// const url = 'https://www.pizzachefie.cz/pridat.html';
// const payload = new URLSearchParams();
// payload.append('varId', pizzaOrder.varId.toString());
// await fetch(url, {
// method: "POST",
// mode: "no-cors",
// cache: "no-cache",
// credentials: "same-origin",
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// },
// body: payload,
// })
// }
// }
// // TODO otevřít košík v nové záložce
// }
}
const renderFoodTable = (name, food) => {
return <Col md={12} lg={4}>
<h3>{name}</h3>
@ -150,6 +174,8 @@ function App() {
return <div>Načítám data...</div>
}
const noOrders = data?.pizzaDay?.orders?.length == 0;
return (
<>
<Header />
@ -158,10 +184,7 @@ function App() {
<Alert variant={'primary'}>
Poslední změny:
<ul>
<li>Možnost odhlášení pomocí menu</li>
<li>Přihlásit se lze i stiskem Enter</li>
<li>Základ pro pizza day - našeptávač pizz z Pizza Chefie, možnost naklikat si objednávku</li>
<li>Možnost uložit si číslo účtu pro budoucí generování QR kódů</li>
<li>Přechody mezi stavy Pizza Day: Vytvořeno -&gt; Uzamčeno -&gt; Objednáno -&gt; Doručeno</li>
</ul>
</Alert>
<h1 className='title'>Dnes je {data.date}</h1>
@ -220,11 +243,54 @@ function App() {
{data.pizzaDay &&
<div>
<div style={{ textAlign: 'center' }}>
<p>Pizza Day je založen uživatelem {data.pizzaDay.creator}</p>
{
data.pizzaDay.creator === auth.login && <Button className='danger mb-3' onClick={async () => {
await deletePizzaDay(auth.login);
}}>Smazat Pizza day</Button>
data.pizzaDay.state === State.CREATED && data.pizzaDay.creator === auth.login &&
<div>
<p>Pizza Day je založen a spravován uživatelem {data.pizzaDay.creator}.<br />Můžete upravovat své objednávky.</p>
<Button className='danger mb-3' title="Smaže kompletně pizza day, včetně dosud zadaných objednávek." onClick={async () => {
await deletePizzaDay(auth.login);
}}>Smazat Pizza day</Button>
<Button className='mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze uzamknout - neexistuje žádná objednávka" : "Zamezí přidávat/odebírat objednávky. Použij před samotným objednáním, aby již nemohlo docházet ke změnám."} disabled={noOrders} onClick={async () => {
await lockPizzaDay(auth.login);
}}>Uzamknout</Button>
</div>
}
{
data.pizzaDay.state === State.LOCKED && data.pizzaDay.creator === auth.login &&
<div>
<p>Objednávky jsou uzamčeny uživatelem {data.pizzaDay.creator}</p>
<Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => {
await unlockPizzaDay(auth.login);
}}>Odemknout</Button>
{/* <Button className='danger mb-3' style={{ marginLeft: '20px' }} onClick={async () => {
await addToCart();
}}>Přidat vše do košíku</Button> */}
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze objednat - neexistuje žádná objednávka" : "Použij po objednání. Objednávky zůstanou zamčeny."} disabled={noOrders} onClick={async () => {
await finishOrder(auth.login);
}}>Objednáno</Button>
</div>
}
{
data.pizzaDay.state === State.ORDERED &&
<div>
<p>Pizzy byly objednány uživatelem {data.pizzaDay.creator}</p>
{data.pizzaDay.creator === auth.login &&
<div>
<Button className='danger mb-3' title="Vrátí stav do předchozího kroku (před objednáním)." onClick={async () => {
await lockPizzaDay(auth.login);
}}>Vrátit do "uzamčeno"</Button>
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => {
await finishDelivery(auth.login);
}}>Doručeno</Button>
</div>
}
</div>
}
{
data.pizzaDay.state === State.DELIVERED &&
<div>
<p>Pizzy byly doručeny.</p>
</div>
}
</div>
<SelectSearch
@ -232,8 +298,9 @@ function App() {
options={pizzaSuggestions}
placeholder='Vyhledat pizzu...'
onChange={handlePizzaChange}
disabled={data.pizzaDay.state !== State.CREATED}
/>
<PizzaOrderList orders={data.pizzaDay.orders} onDelete={handlePizzaDelete} />
<PizzaOrderList state={data.pizzaDay.state} orders={data.pizzaDay.orders} onDelete={handlePizzaDelete} />
</div>
}
</div>

View File

@ -1,6 +1,7 @@
// 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 {
varId: number, // unikátní ID varianty pizzy
size: string, // velikost pizzy, např. "30cm"
pizzaPrice: number, // cena samotné pizzy
boxPrice: number, // cena krabice
@ -16,6 +17,7 @@ export interface Pizza {
/** Objednávka jedné konkrétní pizzy */
export interface PizzaOrder {
varId: number, // unikátní ID varianty pizzy
name: string, // název pizzy
size: string, // velikost pizzy jako string (30cm)
price: number, // cena pizzy v Kč, včetně krabice
@ -59,5 +61,7 @@ export enum Locations {
export enum State {
NOT_CREATED, // Pizza day nebyl založen
CREATED, // Pizza day je založen
LOCKED // Objednávky uzamčeny
LOCKED, // Objednávky uzamčeny
ORDERED, // Objednáno
DELIVERED, // Doručeno
}

View File

@ -1,19 +1,23 @@
import React from "react";
import { Table } from "react-bootstrap";
import { Order, PizzaOrder } from "../Types";
import { Order, PizzaOrder, State } from "../Types";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTrashCan } from "@fortawesome/free-regular-svg-icons";
import { useAuth } from "../context/auth";
export default function PizzaOrderList({ orders, onDelete }: { orders: Order[], onDelete: (pizzaOrder: PizzaOrder) => void }) {
export default function PizzaOrderList({ state, orders, onDelete }: { state: State, orders: Order[], onDelete: (pizzaOrder: PizzaOrder) => void }) {
const auth = useAuth();
if (!orders?.length) {
return <p><i>Zatím žádné objednávky...</i></p>
}
return <Table className="mt-3" striped bordered hover>
<thead>
<tr>
<th>Jméno</th>
<th>Objednávka</th>
<th>Celkem</th>
<th>Cena</th>
</tr>
</thead>
<tbody>
@ -22,13 +26,13 @@ export default function PizzaOrderList({ orders, onDelete }: { orders: Order[],
<td>{order.pizzaList.map<React.ReactNode>((pizzaOrder, index) =>
<span key={index}>
{`${pizzaOrder.name}, ${pizzaOrder.size} (${pizzaOrder.price} Kč)`}
{auth?.login === order.customer &&
{auth?.login === order.customer && state === State.CREATED &&
<FontAwesomeIcon onClick={() => {
onDelete(pizzaOrder);
}} title='Odstranit' className='trash-icon' icon={faTrashCan} />
}
</span>)
.reduce((prev, curr) => [prev, <br />, curr])}
.reduce((prev, curr, index) => [prev, <br key={`br-${index}`} />, curr])}
</td>
<td>{order.totalPrice} </td>
</tr>)}

View File

@ -5,6 +5,7 @@ import fs from 'fs';
import axios from 'axios';
type PizzaSize = {
varId: number,
size: string,
pizzaPrice: number,
boxPrice: number,
@ -67,9 +68,10 @@ const downloadPizzy = async () => {
const sizes: PizzaSize[] = [];
const a = $('.varianty > li > a', pizzaHtml);
a.each((i, elm) => {
const varId = Number.parseInt(elm.attribs.href.split('?varianta=')[1].trim());
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] });
sizes.push({ varId, size, pizzaPrice: price, boxPrice: boxPrices[size], price: price + boxPrices[size] });
})
result.push({
name: name,

View File

@ -3,7 +3,7 @@ import { Server } from "socket.io";
import bodyParser from "body-parser";
import { fetchPizzy } from "./chefie";
import cors from 'cors';
import { addPizzaOrder, createPizzaDay, deletePizzaDay, getData, removePizzaOrder, updateChoice } from "./service";
import { addPizzaOrder, createPizzaDay, deletePizzaDay, finishPizzaDelivery, finishPizzaOrder, getData, lockPizzaDay, removePizzaOrder, unlockPizzaDay, updateChoice } from "./service";
import dotenv from 'dotenv';
import path from 'path';
import { fetchMenus } from "./restaurants";
@ -103,6 +103,42 @@ app.post("/api/removePizza", (req, res) => {
res.status(200).json({});
});
app.post("/api/lockPizzaDay", (req, res) => {
if (!req.body?.login) {
throw Error("Nebyl předán login");
}
const data = lockPizzaDay(req.body.login);
io.emit("message", data);
res.status(200).json({});
});
app.post("/api/unlockPizzaDay", (req, res) => {
if (!req.body?.login) {
throw Error("Nebyl předán login");
}
const data = unlockPizzaDay(req.body.login);
io.emit("message", data);
res.status(200).json({});
});
app.post("/api/finishOrder", (req, res) => {
if (!req.body?.login) {
throw Error("Nebyl předán login");
}
const data = finishPizzaOrder(req.body.login);
io.emit("message", data);
res.status(200).json({});
});
app.post("/api/finishDelivery", (req, res) => {
if (!req.body?.login) {
throw Error("Nebyl předán login");
}
const data = finishPizzaDelivery(req.body.login);
io.emit("message", data);
res.status(200).json({});
});
app.post("/api/updateChoice", (req, res) => {
if (!req.body.hasOwnProperty('name')) {
res.status(400).json({});

View File

@ -69,6 +69,9 @@ export function addPizzaOrder(login: string, pizza: Pizza, size: PizzaSize) {
if (!clientData.pizzaDay) {
throw Error("Pizza day pro dnešní den neexistuje");
}
if (clientData.pizzaDay.state !== PizzaDayState.CREATED) {
throw Error("Pizza day není ve stavu " + PizzaDayState.CREATED);
}
let order: Order | undefined = clientData.pizzaDay.orders.find(o => o.customer === login);
if (!order) {
order = {
@ -79,6 +82,7 @@ export function addPizzaOrder(login: string, pizza: Pizza, size: PizzaSize) {
clientData.pizzaDay.orders.push(order);
}
const pizzaOrder: PizzaOrder = {
varId: size.varId,
name: pizza.name,
size: size.size,
price: size.price,
@ -120,6 +124,98 @@ export function removePizzaOrder(login: string, pizzaOrder: PizzaOrder) {
return clientData;
}
/**
* Uzamkne možnost editovat objednávky pizzy.
*
* @param login login uživatele
* @returns aktuální data pro uživatele
*/
export function lockPizzaDay(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("Pizza day není spravován uživatelem " + login);
}
if (clientData.pizzaDay.state !== PizzaDayState.CREATED && clientData.pizzaDay.state !== PizzaDayState.ORDERED) {
throw Error("Pizza day není ve stavu " + PizzaDayState.CREATED + " nebo " + PizzaDayState.ORDERED);
}
clientData.pizzaDay.state = PizzaDayState.LOCKED;
db.set(today, clientData);
return clientData;
}
/**
* Odekmne možnost editovat objednávky pizzy.
*
* @param login login uživatele
* @returns aktuální data pro uživatele
*/
export function unlockPizzaDay(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("Pizza day není spravován uživatelem " + login);
}
if (clientData.pizzaDay.state !== PizzaDayState.LOCKED) {
throw Error("Pizza day není ve stavu " + PizzaDayState.LOCKED);
}
clientData.pizzaDay.state = PizzaDayState.CREATED;
db.set(today, clientData);
return clientData;
}
/**
* Nastaví stav pizza day na "pizzy objednány".
*
* @param login login uživatele
* @returns aktuální data pro uživatele
*/
export function finishPizzaOrder(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("Pizza day není spravován uživatelem " + login);
}
if (clientData.pizzaDay.state !== PizzaDayState.LOCKED) {
throw Error("Pizza day není ve stavu " + PizzaDayState.LOCKED);
}
clientData.pizzaDay.state = PizzaDayState.ORDERED;
db.set(today, clientData);
return clientData;
}
/**
* Nastaví stav pizza day na "pizzy doručeny".
*
* @param login login uživatele
* @returns aktuální data pro uživatele
*/
export function finishPizzaDelivery(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("Pizza day není spravován uživatelem " + login);
}
if (clientData.pizzaDay.state !== PizzaDayState.ORDERED) {
throw Error("Pizza day není ve stavu " + PizzaDayState.ORDERED);
}
clientData.pizzaDay.state = PizzaDayState.DELIVERED;
db.set(today, clientData);
return clientData;
}
export function initIfNeeded() {
const today = formatDate(getToday());
if (!db.has(today)) {

View File

@ -4,6 +4,7 @@ export interface Choices {
/** Velikost konkrétní pizzy */
export interface PizzaSize {
varId: number, // unikátní ID varianty pizzy
size: string, // velikost pizzy, např. "30cm"
pizzaPrice: number, // cena samotné pizzy
boxPrice: number, // cena krabice
@ -19,6 +20,7 @@ export interface Pizza {
/** Objednávka jedné konkrétní pizzy */
export interface PizzaOrder {
varId: number, // unikátní ID varianty pizzy
name: string, // název pizzy
size: string, // velikost pizzy jako string (30cm)
price: number, // cena pizzy v Kč, včetně krabice
@ -35,7 +37,9 @@ export interface Order {
export enum PizzaDayState {
NOT_CREATED, // Pizza day nebyl založen
CREATED, // Pizza day je založen
LOCKED // Objednávky uzamčeny
LOCKED, // Objednávky uzamčeny
ORDERED, // Pizzy objednány
DELIVERED // Pizzy doručeny
}
/** Informace o pizza day pro dnešní den */