feat: trvalé zobrazení QR kódu do ručního zavření (#31)
QR kódy pro platbu za pizza day jsou nyní zobrazeny persistentně i po následující dny, dokud uživatel nepotvrdí platbu tlačítkem "Zaplatil jsem". Nevyřízené QR kódy jsou uloženy per-user v storage a zobrazeny v sekci "Nevyřízené platby".
This commit is contained in:
@@ -18,7 +18,7 @@ import Loader from './components/Loader';
|
|||||||
import { getHumanDateTime, isInTheFuture } from './Utils';
|
import { getHumanDateTime, isInTheFuture } from './Utils';
|
||||||
import NoteModal from './components/modals/NoteModal';
|
import NoteModal from './components/modals/NoteModal';
|
||||||
import { useEasterEgg } from './context/eggs';
|
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 { getLunchChoiceName } from './enums';
|
||||||
// import FallingLeaves, { LEAF_PRESETS, LEAF_COLOR_THEMES } from './FallingLeaves';
|
// import FallingLeaves, { LEAF_PRESETS, LEAF_COLOR_THEMES } from './FallingLeaves';
|
||||||
// import './FallingLeaves.scss';
|
// import './FallingLeaves.scss';
|
||||||
@@ -729,6 +729,32 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
{data.pendingQrs && data.pendingQrs.length > 0 &&
|
||||||
|
<div className='pizza-section fade-in mt-4'>
|
||||||
|
<h3>Nevyřízené platby</h3>
|
||||||
|
<p>Máte neuhrazené QR kódy z předchozích Pizza day.</p>
|
||||||
|
{data.pendingQrs.map(qr => (
|
||||||
|
<div key={qr.date} className='qr-code mb-3'>
|
||||||
|
<p>
|
||||||
|
<strong>{qr.date}</strong> — {qr.creator} ({qr.totalPrice} Kč)
|
||||||
|
</p>
|
||||||
|
<img src={`/api/qr?login=${auth.login}`} alt='QR kód' />
|
||||||
|
<div className='mt-2'>
|
||||||
|
<Button variant="success" onClick={async () => {
|
||||||
|
await dismissQr({ body: { date: qr.date } });
|
||||||
|
// Přenačteme data pro aktualizaci
|
||||||
|
const response = await getData({ query: { dayIndex } });
|
||||||
|
if (response.data) {
|
||||||
|
setData(response.data);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Zaplatil jsem
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
{/* <FallingLeaves
|
{/* <FallingLeaves
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { getData, getDateForWeekIndex, getToday } from "./service";
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getQr } from "./qr";
|
import { getQr } from "./qr";
|
||||||
import { generateToken, verify } from "./auth";
|
import { generateToken, getLogin, verify } from "./auth";
|
||||||
import { getIsWeekend, InsufficientPermissions } from "./utils";
|
import { getIsWeekend, InsufficientPermissions, parseToken } from "./utils";
|
||||||
|
import { getPendingQrs } from "./pizza";
|
||||||
import { initWebsocket } from "./websocket";
|
import { initWebsocket } from "./websocket";
|
||||||
import pizzaDayRoutes from "./routes/pizzaDayRoutes";
|
import pizzaDayRoutes from "./routes/pizzaDayRoutes";
|
||||||
import foodRoutes, { refreshMetoda } from "./routes/foodRoutes";
|
import foodRoutes, { refreshMetoda } from "./routes/foodRoutes";
|
||||||
@@ -138,7 +139,18 @@ app.get("/api/data", async (req, res) => {
|
|||||||
// Na víkendu zobrazíme pátek místo hlášky "Užívejte víkend"
|
// Na víkendu zobrazíme pátek místo hlášky "Užívejte víkend"
|
||||||
date = getDateForWeekIndex(4);
|
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
|
// Ostatní routes
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { generateQr } from "./qr";
|
|||||||
import getStorage from "./storage";
|
import getStorage from "./storage";
|
||||||
import { downloadPizzy } from "./chefie";
|
import { downloadPizzy } from "./chefie";
|
||||||
import { getClientData, getToday, initIfNeeded } from "./service";
|
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 storage = getStorage();
|
||||||
|
const PENDING_QR_PREFIX = 'pending_qr';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vrátí seznam dostupných pizz pro dnešní den.
|
* 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(', ');
|
let message = order.pizzaList!.map(pizza => `Pizza ${pizza.name} (${pizza.size})`).join(', ');
|
||||||
await generateQr(order.customer, bankAccount, bankAccountHolder, order.totalPrice, message);
|
await generateQr(order.customer, bankAccount, bankAccountHolder, order.totalPrice, message);
|
||||||
order.hasQr = true;
|
order.hasQr = true;
|
||||||
|
// Uložíme nevyřízený QR kód pro persistentní zobrazení
|
||||||
|
await addPendingQr(order.customer, {
|
||||||
|
date: today,
|
||||||
|
creator: login,
|
||||||
|
totalPrice: order.totalPrice,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,3 +315,40 @@ export async function updatePizzaFee(login: string, targetLogin: string, text?:
|
|||||||
await storage.setData(today, clientData);
|
await storage.setData(today, clientData);
|
||||||
return 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<void> {
|
||||||
|
const key = getPendingQrKey(login);
|
||||||
|
const existing = await storage.getData<PendingQr[]>(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<PendingQr[]> {
|
||||||
|
return await storage.getData<PendingQr[]>(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<void> {
|
||||||
|
const key = getPendingQrKey(login);
|
||||||
|
const existing = await storage.getData<PendingQr[]>(key) ?? [];
|
||||||
|
const filtered = existing.filter(qr => qr.date !== date);
|
||||||
|
await storage.setData(key, filtered);
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import express, { Request } from "express";
|
import express, { Request } from "express";
|
||||||
import { getLogin } from "../auth";
|
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 { parseToken } from "../utils";
|
||||||
import { getWebsocket } from "../websocket";
|
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();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -109,4 +109,16 @@ router.post("/updatePizzaFee", async (req: Request<{}, any, UpdatePizzaFeeData["
|
|||||||
} catch (e: any) { next(e) }
|
} 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;
|
export default router;
|
||||||
Reference in New Issue
Block a user