diff --git a/client/src/App.tsx b/client/src/App.tsx index ae23bb5..5deb500 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -18,7 +18,7 @@ import Loader from './components/Loader'; import { getHumanDateTime, isInTheFuture } from './Utils'; import NoteModal from './components/modals/NoteModal'; import { useEasterEgg } from './context/eggs'; -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, setBuyer } 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, setBuyer, dismissQr } from '../../types'; import { getLunchChoiceName } from './enums'; // import FallingLeaves, { LEAF_PRESETS, LEAF_COLOR_THEMES } from './FallingLeaves'; // import './FallingLeaves.scss'; @@ -729,6 +729,32 @@ function App() { } + {data.pendingQrs && data.pendingQrs.length > 0 && +
+

Nevyřízené platby

+

Máte neuhrazené QR kódy z předchozích Pizza day.

+ {data.pendingQrs.map(qr => ( +
+

+ {qr.date} — {qr.creator} ({qr.totalPrice} Kč) +

+ QR kód +
+ +
+
+ ))} +
+ } {/* { // Na víkendu zobrazíme pátek místo hlášky "Užívejte víkend" date = getDateForWeekIndex(4); } - res.status(200).json(await getData(date)); + const data = await getData(date); + // Připojíme nevyřízené QR kódy pro přihlášeného uživatele + try { + const login = getLogin(parseToken(req)); + const pendingQrs = await getPendingQrs(login); + if (pendingQrs.length > 0) { + data.pendingQrs = pendingQrs; + } + } catch { + // Token nemusí být validní, ignorujeme + } + res.status(200).json(data); }); // Ostatní routes diff --git a/server/src/pizza.ts b/server/src/pizza.ts index f1c7d48..8ffdf69 100644 --- a/server/src/pizza.ts +++ b/server/src/pizza.ts @@ -4,9 +4,10 @@ import { generateQr } from "./qr"; import getStorage from "./storage"; import { downloadPizzy } from "./chefie"; import { getClientData, getToday, initIfNeeded } from "./service"; -import { Pizza, ClientData, PizzaDayState, PizzaSize, PizzaOrder, PizzaVariant, UdalostEnum } from "../../types/gen/types.gen"; +import { Pizza, ClientData, PizzaDayState, PizzaSize, PizzaOrder, PizzaVariant, UdalostEnum, PendingQr } from "../../types/gen/types.gen"; const storage = getStorage(); +const PENDING_QR_PREFIX = 'pending_qr'; /** * Vrátí seznam dostupných pizz pro dnešní den. @@ -241,6 +242,12 @@ export async function finishPizzaDelivery(login: string, bankAccount?: string, b let message = order.pizzaList!.map(pizza => `Pizza ${pizza.name} (${pizza.size})`).join(', '); await generateQr(order.customer, bankAccount, bankAccountHolder, order.totalPrice, message); order.hasQr = true; + // Uložíme nevyřízený QR kód pro persistentní zobrazení + await addPendingQr(order.customer, { + date: today, + creator: login, + totalPrice: order.totalPrice, + }); } } } @@ -307,4 +314,41 @@ export async function updatePizzaFee(login: string, targetLogin: string, text?: targetOrder.totalPrice = targetOrder.pizzaList.reduce((price, pizzaOrder) => price + pizzaOrder.price, 0) + (targetOrder.fee?.price ?? 0); await storage.setData(today, clientData); return clientData; +} + +/** + * Vrátí klíč pro uložení nevyřízených QR kódů uživatele. + */ +function getPendingQrKey(login: string): string { + return `${PENDING_QR_PREFIX}_${login}`; +} + +/** + * Přidá nevyřízený QR kód pro uživatele. + */ +async function addPendingQr(login: string, pendingQr: PendingQr): Promise { + const key = getPendingQrKey(login); + const existing = await storage.getData(key) ?? []; + // Nepřidáváme duplicity pro stejný den + if (!existing.some(qr => qr.date === pendingQr.date)) { + existing.push(pendingQr); + await storage.setData(key, existing); + } +} + +/** + * Vrátí nevyřízené QR kódy pro uživatele. + */ +export async function getPendingQrs(login: string): Promise { + return await storage.getData(getPendingQrKey(login)) ?? []; +} + +/** + * Označí QR kód pro daný den jako uhrazený (odstraní ho ze seznamu nevyřízených). + */ +export async function dismissPendingQr(login: string, date: string): Promise { + const key = getPendingQrKey(login); + const existing = await storage.getData(key) ?? []; + const filtered = existing.filter(qr => qr.date !== date); + await storage.setData(key, filtered); } \ No newline at end of file diff --git a/server/src/routes/pizzaDayRoutes.ts b/server/src/routes/pizzaDayRoutes.ts index 944518e..82a26c5 100644 --- a/server/src/routes/pizzaDayRoutes.ts +++ b/server/src/routes/pizzaDayRoutes.ts @@ -1,9 +1,9 @@ import express, { Request } from "express"; import { getLogin } from "../auth"; -import { createPizzaDay, deletePizzaDay, getPizzaList, addPizzaOrder, removePizzaOrder, lockPizzaDay, unlockPizzaDay, finishPizzaOrder, finishPizzaDelivery, updatePizzaDayNote, updatePizzaFee } from "../pizza"; +import { createPizzaDay, deletePizzaDay, getPizzaList, addPizzaOrder, removePizzaOrder, lockPizzaDay, unlockPizzaDay, finishPizzaOrder, finishPizzaDelivery, updatePizzaDayNote, updatePizzaFee, dismissPendingQr } from "../pizza"; import { parseToken } from "../utils"; import { getWebsocket } from "../websocket"; -import { AddPizzaData, FinishDeliveryData, RemovePizzaData, UpdatePizzaDayNoteData, UpdatePizzaFeeData } from "../../../types"; +import { AddPizzaData, DismissQrData, FinishDeliveryData, RemovePizzaData, UpdatePizzaDayNoteData, UpdatePizzaFeeData } from "../../../types"; const router = express.Router(); @@ -109,4 +109,16 @@ router.post("/updatePizzaFee", async (req: Request<{}, any, UpdatePizzaFeeData[" } catch (e: any) { next(e) } }); +/** Označí QR kód jako uhrazený. */ +router.post("/dismissQr", async (req: Request<{}, any, DismissQrData["body"]>, res, next) => { + const login = getLogin(parseToken(req)); + if (!req.body.date) { + return res.status(400).json({ error: "Nebyl předán datum" }); + } + try { + await dismissPendingQr(login, req.body.date); + res.status(200).json({}); + } catch (e: any) { next(e) } +}); + export default router; \ No newline at end of file