Přenos změn

This commit is contained in:
Martin Berka 2023-06-20 08:52:05 +02:00
parent 47fbe4173d
commit e8d7e3fd9e
6 changed files with 165 additions and 42 deletions

View File

@ -1,4 +1,4 @@
import { PizzaOrder } from "./Types";
import { Locations, PizzaOrder } from "./Types";
import { getBaseUrl } from "./Utils";
async function request<TResponse>(
@ -58,7 +58,7 @@ export const finishDelivery = async (login, bankAccount, bankAccountHolder) => {
return await api.post<any, any>('/api/finishDelivery', JSON.stringify({ login, bankAccount, bankAccountHolder }));
}
export const updateChoice = async (name: string, choice: number | null) => {
export const updateChoice = async (name: string, choice?: Locations | null) => {
return await api.post<any, any>('/api/updateChoice', JSON.stringify({ name, choice }));
}
@ -72,4 +72,8 @@ export const removePizza = async (login: string, pizzaOrder: PizzaOrder) => {
export const updateNote = async (login: string, note?: string) => {
return await api.post<any, any>('/api/updateNote', JSON.stringify({ login, note }));
}
export const updateFoodChoice = async (login: string, choice: Locations, foodName?: string) => {
return await api.post<any, any>('/api/updateFoodChoice', JSON.stringify({ login, choice, foodName }));
}

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, finishDelivery, finishOrder, getData, getFood, getPizzy, getQrUrl, lockPizzaDay, removePizza, unlockPizzaDay, updateChoice, updateNote } from './Api';
import { addPizza, createPizzaDay, deletePizzaDay, finishDelivery, finishOrder, getData, getFood, getPizzy, getQrUrl, lockPizzaDay, removePizza, unlockPizzaDay, updateChoice, updateFoodChoice, updateNote } from './Api';
import { useAuth } from './context/auth';
import Login from './Login';
import { Locations, ClientData, Pizza, PizzaOrder, State, Order, Food, Restaurants } from './Types';
import { Locations, ClientData, Pizza, PizzaOrder, State, Order, Food, Restaurants, FoodChoice } from './Types';
import { Alert, Button, Col, Form, Row, Table } from 'react-bootstrap';
import Header from './components/Header';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
@ -30,6 +30,8 @@ function App() {
const socket = useContext(SocketContext);
const choiceRef = useRef<HTMLSelectElement>(null);
const poznamkaRef = useRef<HTMLInputElement>(null);
const [foodSelection, setFoodSelection] = useState<Food[]>();
const foodChoiceRef = useRef<HTMLSelectElement>(null);
// Prvotní načtení aktuálního stavu
useEffect(() => {
@ -73,8 +75,8 @@ function App() {
// TODO tohle občas náhodně nezafunguje, nutno přepsat, viz https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780
if (data?.choices && choiceRef.current) {
for (let entry of Object.entries(data.choices)) {
if (entry[1].includes(auth.login)) {
choiceRef.current.value = Object.values(Locations)[entry[0]]
if (entry[1].find(ch => ch.login === auth.login)) {
choiceRef.current.value = entry[0];
}
}
}
@ -88,10 +90,64 @@ function App() {
}
}, [auth?.login, data?.pizzaDay?.orders])
const locationToRestaurant = (location: Locations): Restaurants | undefined => {
switch (location) {
case Locations.SLADOVNICKA:
return Restaurants.SLADOVNICKA;
case Locations.UMOTLIKU:
return Restaurants.UMOTLIKU;
case Locations.TECHTOWER:
return Restaurants.TECHTOWER;
}
}
const renderLocation = (location: Locations) => {
switch (location) {
case Locations.SLADOVNICKA:
return 'Sladovnická';
case Locations.UMOTLIKU:
return 'U Motlíků';
case Locations.TECHTOWER:
return 'TechTower';
case Locations.SPSE:
return 'SPŠE';
case Locations.PIZZA:
return 'Pizza day';
case Locations.OBJEDNAVAM:
return 'Objednávám (mimo pizza day)';
case Locations.NEOBEDVAM:
return 'Vlastní/Home office/Neobědvám';
}
}
const restaurantToLocation = (restaurant: Restaurants): Locations | undefined => {
switch (restaurant) {
case Restaurants.SLADOVNICKA:
return Locations.SLADOVNICKA;
case Restaurants.UMOTLIKU:
return Locations.UMOTLIKU;
case Restaurants.TECHTOWER:
return Locations.TECHTOWER;
}
}
const changeChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
const index = Object.values(Locations).indexOf(event.target.value as unknown as Locations);
const choice: Locations | undefined = Locations[event.target.value];
console.log("Vybráno", choice); // TODO smazat
if (auth?.login) {
await updateChoice(auth.login, index > -1 ? index : null);
await updateChoice(auth.login, choice);
// if (index >= 0) {
// const choice = Object.values(Locations)[index];
// if ([Locations.SLADOVNICKA, Locations.UMOTLIKU, Locations.TECHTOWER].includes(choice)) {
// const restaurant = locationToRestaurant(choice);
// if (restaurant) {
// const selection = food?.[restaurant];
// console.log("Choice and restaurant", choice, restaurant);
// setFoodSelection(selection);
// }
// }
} else {
setFoodSelection(undefined);
}
}
@ -104,6 +160,19 @@ function App() {
}
}
const changeFoodChoice = async (event: React.ChangeEvent<HTMLSelectElement>) => {
console.log("Přidání jídla", choiceRef.current?.value, event.target.value);
let key: string | undefined = undefined;
// TODO pokračovat
// if (choiceRef.current?.value) {
// const location = choiceRef.current.value as Locations;
// key = Object.keys(Locations)[Object.values(Locations).indexOf(location)];
// }
// if (auth?.login) {
// await updateFoodChoice(auth.login, key, event.target.value);
// }
}
const pizzaSuggestions = useMemo(() => {
if (!pizzy) {
return [];
@ -222,27 +291,32 @@ function App() {
<p>Jak to dnes vidíš s obědem?</p>
<Form.Select ref={choiceRef} onChange={changeChoice}>
<option></option>
<option value={Locations.SLADOVNICKA}>Sladovnická</option>
<option value={Locations.UMOTLIKU}>U Motlíků</option>
<option value={Locations.TECHTOWER}>TechTower</option>
<option value={Locations.SPSE}>SPŠE</option>
<option value={Locations.PIZZA}>Pizza day</option>
<option value={Locations.OBJEDNAVAM}>Budu objednávat (mimo pizzu)</option>
<option value={Locations.NEOBEDVAM}>Mám vlastní/neobědvám</option>
{Object.values(Locations).map(key => <option key={key} value={key}>{renderLocation(key)}</option>)}
</Form.Select>
<p style={{ fontSize: "12px", marginTop: "5px" }}>
Aktuálně je možné vybrat pouze jednu variantu.
</p>
{foodSelection &&
<>
<p>Na jaké hlavní jídlo?</p>
<Form.Select ref={foodChoiceRef} onChange={changeFoodChoice}>
<option></option>
{foodSelection?.filter(food => !food.isSoup).map(food =>
<option key={food.name} value={food.name}>{food.name}</option>
)}
</Form.Select>
</>
}
{Object.keys(data.choices).length > 0 ?
<Table striped bordered hover className='results-table mt-5'>
<tbody>
{Object.keys(data.choices).map((key: string, index: number) =>
<tr key={index}>
<td>{Object.values(Locations)[Number(key)]}</td>
<td>{renderLocation(Locations[key])}</td>
<td>
<ul>
{data.choices[Number(key)].map((p: string, index: number) =>
<li key={index}>{p} {p === auth.login && <FontAwesomeIcon onClick={() => {
{data.choices[key].map((p: FoodChoice, index: number) =>
<li key={index}>{p.login} {p.login === auth.login && <FontAwesomeIcon onClick={() => {
removeChoice(key);
}} title='Odstranit' className='trash-icon' icon={faTrashCan} />}</li>
)}

View File

@ -32,8 +32,13 @@ export interface Order {
note?: string, // volitelná poznámka uživatele k objednávce
}
export interface FoodChoice {
login: string, // jméno uživatele
foodName?: string, // název vybraného jídla
}
export interface Choices {
[location: string]: string[],
[location: string]: FoodChoice[],
}
/** Údaje o Pizza day. */
@ -51,13 +56,13 @@ export interface ClientData {
}
export enum Locations {
SLADOVNICKA = 'Sladovnická',
UMOTLIKU = 'U Motlíků',
TECHTOWER = 'TechTower',
SPSE = 'SPŠE',
PIZZA = 'Pizza day',
OBJEDNAVAM = 'Budu objednávat',
NEOBEDVAM = 'Mám vlastní/neobědvám',
SLADOVNICKA = 'SLADOVNICKA',
UMOTLIKU = 'UMOTLIKU',
TECHTOWER = 'TECHTOWER',
SPSE = 'SPSE',
PIZZA = 'PIZZA',
OBJEDNAVAM = 'OBJEDNAVAM',
NEOBEDVAM = 'NEOBEDVAM',
}
/** Jídlo z obědového menu restaurace. */

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, finishPizzaDelivery, finishPizzaOrder, getData, lockPizzaDay, removePizzaOrder, unlockPizzaDay, updateChoice, updateNote } from "./service";
import { addPizzaOrder, createPizzaDay, deletePizzaDay, finishPizzaDelivery, finishPizzaOrder, getData, lockPizzaDay, removePizzaOrder, unlockPizzaDay, updateChoice, updateFoodChoice, updateNote } from "./service";
import dotenv from 'dotenv';
import path from 'path';
import { getMenuSladovnicka, getMenuTechTower, getMenuUMotliku } from "./restaurants";
@ -179,6 +179,18 @@ app.post("/api/updateNote", (req, res) => {
res.status(200).json(data);
});
app.post("/api/updateFoodChoice", (req, res) => {
if (!req.body.login) {
throw Error("Nebyl předán login");
}
if (!req.body.choice) {
throw Error("Nebyla předána vybraná restaurace");
}
const data = updateFoodChoice(req.body.login, req.body.choice, req.body.food);
io.emit("message", data);
res.status(200).json(data);
});
io.on("connection", (socket) => {
console.log(`New client connected: ${socket.id}`);

View File

@ -240,11 +240,13 @@ export function initIfNeeded() {
export function removeChoice(login: string, data: ClientData) {
for (let key of Object.keys(data.choices)) {
if (data.choices[key] && data.choices[key].includes(login)) {
const index = data.choices[key].indexOf(login);
data.choices[key].splice(index, 1);
if (data.choices[key].length == 0) {
delete data.choices[key];
if (data.choices[key]) {
const index = data.choices[key].findIndex(choice => choice.login === login);
if (index >= 0) {
data.choices[key].splice(index, 1);
if (data.choices[key].length == 0) {
delete data.choices[key];
}
}
}
}
@ -256,11 +258,11 @@ export function updateChoice(login: string, choice: Locations | null) {
const today = formatDate(getToday());
let data: ClientData = db.get(today);
data = removeChoice(login, data);
if (choice !== null) {
if (choice) {
if (!data.choices?.[choice]) {
data.choices[choice] = [];
}
data.choices[choice].push(login);
data.choices[choice].push({ login });
}
db.set(today, data);
return data;
@ -282,4 +284,24 @@ export function updateNote(login: string, note?: string) {
myOrder.note = note;
db.set(today, clientData);
return clientData;
}
export function updateFoodChoice(login: string, location: Locations, foodName?: string) {
const today = formatDate(getToday());
let clientData: ClientData = db.get(today);
// TODO smazat
console.log("Location", location);
console.log("Choices", clientData.choices);
const myChoice = clientData.choices[location].find(o => o.login === login);
if (!myChoice) {
throw Error("V datech nebyla nalezena volba pro uživatele " + login);
}
if (foodName) {
myChoice.foodName = foodName;
} else {
delete myChoice.foodName;
}
db.set(today, clientData);
return clientData;
}

View File

@ -1,7 +1,13 @@
import exp from "constants";
/** Výběr jídla z restaurace jednoho uživatele. */
export interface FoodChoice {
login: string, // jméno uživatele
foodName?: string, // název vybraného jídla
}
export interface Choices {
[location: string]: string[],
[location: string]: FoodChoice[],
}
/** Velikost konkrétní pizzy */
@ -77,13 +83,13 @@ export enum Restaurants {
}
export enum Locations {
SLADOVNICKA = 'Sladovnická',
UMOTLIKU = 'U Motlíků',
TECHTOWER = 'TechTower',
SPSE = 'SPŠE',
PIZZA = 'Pizza day',
OBJEDNAVAM = 'Budu objednávat',
NEOBEDVAM = 'Mám vlastní/neobědvám',
SLADOVNICKA = 'SLADOVNICKA',
UMOTLIKU = 'UMOTLIKU',
TECHTOWER = 'TECHTOWER',
SPSE = 'SPSE',
PIZZA = 'PIZZA',
OBJEDNAVAM = 'OBJEDNAVAM',
NEOBEDVAM = 'NEOBEDVAM',
}
export enum UdalostEnum {