From 9152425d2b809ffb66f1950dfc8fed4dad9c6342 Mon Sep 17 00:00:00 2001 From: Martin Berka Date: Thu, 11 Jun 2026 11:45:24 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20p=C5=99ehledn=C4=9Bj=C5=A1=C3=AD=20zobr?= =?UTF-8?q?azen=C3=AD=20QR=20k=C3=B3d=C5=AF,=20oprava=20zobrazen=C3=AD=20n?= =?UTF-8?q?a=20str=C3=A1nce=20objedn=C3=A1n=C3=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/PendingPayments.tsx | 78 ++++++++++++++++++----- client/src/pages/OrderGroupsPage.tsx | 18 +++++- 2 files changed, 76 insertions(+), 20 deletions(-) diff --git a/client/src/components/PendingPayments.tsx b/client/src/components/PendingPayments.tsx index a8fc8ad..be86240 100644 --- a/client/src/components/PendingPayments.tsx +++ b/client/src/components/PendingPayments.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; -import { Button } from 'react-bootstrap'; +import { useEffect, useRef, useState } from 'react'; +import { Button, Modal } from 'react-bootstrap'; import { PendingQr, dismissQr } from '../../../types'; import { formatDateString } from '../Utils'; import ConfirmModal from './modals/ConfirmModal'; @@ -13,31 +13,75 @@ type Props = { // Sekce "Nevyřízené platby" – zobrazí QR kódy neuhrazených plateb přihlášeného uživatele // včetně tlačítka "Zaplatil jsem" a potvrzovacího dialogu. Sdíleno hlavní stránkou i stránkou objednávek. +// Při příchodu nových nevyřízených plateb se navíc automaticky otevře modální dialog, +// aby si uživatel QR kódů určitě všiml (často si jich nevšimnou, protože sekce je dole na stránce). export default function PendingPayments({ pendingQrs, login, onDismissed }: Readonly) { const [dismissQrId, setDismissQrId] = useState(null); + const [modalOpen, setModalOpen] = useState(false); + + // ID QR kódů, pro které už byl v rámci tohoto načtení stránky automaticky zobrazen + // modální dialog. Drží se jen v paměti (ne v sessionStorage), takže se při každém + // ručním přenačtení stránky vynuluje a dialog se znovu otevře, dokud uživatel platby + // neuhradí. Zároveň se nepřekrývá při pouhém obnovení dat či příchodu už zobrazeného QR. + const autoShownQrIds = useRef>(new Set()); + + const qrIdsKey = (pendingQrs ?? []).map(qr => qr.id).join(','); + + // Automaticky otevřeme modální dialog, jakmile přijdou nové (dosud nezobrazené) platby. + useEffect(() => { + const ids = (pendingQrs ?? []).map(qr => qr.id); + if (ids.length === 0) return; + const unseen = ids.filter(id => !autoShownQrIds.current.has(id)); + if (unseen.length > 0) { + setModalOpen(true); + unseen.forEach(id => autoShownQrIds.current.add(id)); + } + }, [qrIdsKey, pendingQrs]); if (!pendingQrs || pendingQrs.length === 0) return null; + // Vykreslení jednoho QR kódu i s tlačítkem "Zaplatil jsem" – sdíleno sekcí i modálem. + const renderQr = (qr: PendingQr) => ( +
+

+ {formatDateString(qr.date)} — {qr.creator} ({qr.totalPrice / 100} Kč) + {qr.purpose && <>
{qr.purpose}} +

+ QR kód +
+ +
+
+ ); + return ( <>

Nevyřízené platby

-

Máte neuhrazené platby.

- {pendingQrs.map(qr => ( -
-

- {formatDateString(qr.date)} — {qr.creator} ({qr.totalPrice / 100} Kč) - {qr.purpose && <>
{qr.purpose}} -

- QR kód -
- -
-
- ))} +

+ Máte neuhrazené platby.{' '} + +

+ {pendingQrs.map(renderQr)}
+ setModalOpen(false)} centered scrollable> + + Nevyřízené platby + + +

Máte neuhrazené platby. Naskenujte QR kód pro zaplacení.

+ {pendingQrs.map(renderQr)} +
+ + + +
{ socket.off(EVENT_MESSAGE); socket.off(EVENT_PENDING_QR); }; }, [socket]); + // Připojení do osobní socket místnosti po přihlášení – bez toho nechodí události + // o nových nevyřízených platbách (QR kódy se posílají do místnosti user:) useEffect(() => { - // Po znovupřipojení socketu načteme aktuálně zobrazený den (mohli jsme přijít o živé aktualizace) - const onReconnect = () => fetchData(selectedDateRef.current); + if (auth?.login) { + socket.emit('join', auth.login); + } + }, [auth?.login, socket]); + + useEffect(() => { + // Po znovupřipojení socketu znovu vstoupíme do osobní místnosti a načteme aktuálně + // zobrazený den (mohli jsme přijít o živé aktualizace) + const onReconnect = () => { + if (auth?.login) socket.emit('join', auth.login); + fetchData(selectedDateRef.current); + }; socket.io.on('reconnect', onReconnect); return () => { socket.io.off('reconnect', onReconnect); }; - }, [socket]); + }, [socket, auth?.login]); // Navigace mezi dny pomocí klávesových šipek (←/→), obdobně jako na hlavní stránce const handleKeyDown = useCallback((e: KeyboardEvent) => {