From 1efe2b8f7dc98f50d0e2b58a8af1b358520eea80 Mon Sep 17 00:00:00 2001 From: Martin Berka Date: Thu, 7 May 2026 09:09:47 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20potvrzen=C3=AD=20o=20=C3=BAhrad=C4=9B?= =?UTF-8?q?=20objedn=C3=A1vky?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/OrderGroupsPage.tsx | 5 ++++- server/src/groups.ts | 11 +++++++++++ server/src/pizza.ts | 5 ++++- server/src/routes/pizzaDayRoutes.ts | 7 ++++++- types/schemas/_index.yml | 3 +++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/client/src/pages/OrderGroupsPage.tsx b/client/src/pages/OrderGroupsPage.tsx index 2d0fef9..bb9e98f 100644 --- a/client/src/pages/OrderGroupsPage.tsx +++ b/client/src/pages/OrderGroupsPage.tsx @@ -2,7 +2,7 @@ import { useContext, useEffect, useRef, useState } from 'react'; import { Alert, Badge, Button, Card, Form, Modal, Table } from 'react-bootstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTrashCan } from '@fortawesome/free-regular-svg-icons'; -import { faBasketShopping, faGear, faLock, faLockOpen, faSearch, faUserPlus } from '@fortawesome/free-solid-svg-icons'; +import { faBasketShopping, faCircleCheck, faGear, faLock, faLockOpen, faSearch, faUserPlus } from '@fortawesome/free-solid-svg-icons'; import { ClientData, GroupState, MealSlot, OrderGroup, OrderGroupMember, getData, createGroup, deleteGroup, addGroupMember, removeGroupMember, updateGroupMember, setGroupState, updateGroupTimes, @@ -306,6 +306,9 @@ export default function OrderGroupsPage() { {memberLogin === group.creatorLogin && ( )} + {member.paid && ( + + )} diff --git a/server/src/groups.ts b/server/src/groups.ts index 94ca5d8..18495a0 100644 --- a/server/src/groups.ts +++ b/server/src/groups.ts @@ -131,11 +131,22 @@ export async function setGroupState(login: string, groupId: string, newState: Gr await removePendingQrsByGroupId(memberLogins, groupId); group.orderedAt = undefined; group.deliveryAt = undefined; + for (const ml of memberLogins) { + group.members[ml] = { ...group.members[ml], paid: undefined }; + } } group.state = newState; return saveExtraData(data, date); } +export async function markGroupMemberPaid(login: string, groupId: string, date?: Date): Promise { + const data = await getExtraData(date); + const group = findGroup(data, groupId); + if (!group || !group.members[login]) return null; + group.members[login] = { ...group.members[login], paid: true }; + return saveExtraData(data, date); +} + export async function updateGroupTimes(login: string, groupId: string, orderedAt?: string, deliveryAt?: string, date?: Date): Promise { const data = await getExtraData(date); const group = findGroup(data, groupId); diff --git a/server/src/pizza.ts b/server/src/pizza.ts index 4a580c2..f044d86 100644 --- a/server/src/pizza.ts +++ b/server/src/pizza.ts @@ -449,12 +449,15 @@ export async function getPendingQrs(login: string): Promise { /** * Označí QR kód jako uhrazený (odstraní ho ze seznamu nevyřízených). + * Vrátí odstraněný QR kód, pokud byl nalezen. */ -export async function dismissPendingQr(login: string, id: string): Promise { +export async function dismissPendingQr(login: string, id: string): Promise { const key = getPendingQrKey(login); const existing = await storage.getData(key) ?? []; + const dismissed = existing.find(qr => qr.id === id); const filtered = existing.filter(qr => qr.id !== id); await storage.setData(key, filtered); + return dismissed; } /** diff --git a/server/src/routes/pizzaDayRoutes.ts b/server/src/routes/pizzaDayRoutes.ts index e6182a5..757e370 100644 --- a/server/src/routes/pizzaDayRoutes.ts +++ b/server/src/routes/pizzaDayRoutes.ts @@ -1,6 +1,7 @@ import express, { Request } from "express"; import { getLogin } from "../auth"; import { createPizzaDay, deletePizzaDay, getPizzaList, getSalatList, addPizzaOrder, addSalatOrder, removePizzaOrder, lockPizzaDay, unlockPizzaDay, finishPizzaOrder, finishPizzaDelivery, updatePizzaDayNote, updatePizzaFee, dismissPendingQr } from "../pizza"; +import { markGroupMemberPaid } from "../groups"; import { parseToken } from "../utils"; import { getWebsocket } from "../websocket"; import { AddPizzaData, DismissQrData, FinishDeliveryData, RemovePizzaData, UpdatePizzaDayNoteData, UpdatePizzaFeeData } from "../../../types"; @@ -132,7 +133,11 @@ router.post("/dismissQr", async (req: Request<{}, any, DismissQrData["body"]>, r return res.status(400).json({ error: "Nebyl předán identifikátor QR kódu" }); } try { - await dismissPendingQr(login, req.body.id); + const dismissed = await dismissPendingQr(login, req.body.id); + if (dismissed?.groupId) { + const updatedExtra = await markGroupMemberPaid(login, dismissed.groupId); + if (updatedExtra) getWebsocket().emit("message", updatedExtra); + } res.status(200).json({}); } catch (e: any) { next(e) } }); diff --git a/types/schemas/_index.yml b/types/schemas/_index.yml index 95dd880..bd077ec 100644 --- a/types/schemas/_index.yml +++ b/types/schemas/_index.yml @@ -715,6 +715,9 @@ OrderGroupMember: surchargeAmount: description: Výše příplatku v Kč type: number + paid: + description: Příznak, zda člen uhradil svůj podíl objednávky + type: boolean OrderGroup: description: Skupina uživatelů objednávajících z jednoho místa