feat: přehlednější zobrazení QR kódů, oprava zobrazení na stránce objednání
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 30s
CI / Build client (push) Successful in 37s
CI / Playwright E2E tests (push) Successful in 1m20s
CI / Build and push Docker image (push) Successful in 46s
CI / Notify (push) Successful in 3s
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 30s
CI / Build client (push) Successful in 37s
CI / Playwright E2E tests (push) Successful in 1m20s
CI / Build and push Docker image (push) Successful in 46s
CI / Notify (push) Successful in 3s
This commit is contained in:
@@ -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<Props>) {
|
||||
const [dismissQrId, setDismissQrId] = useState<string | null>(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<Set<string>>(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) => (
|
||||
<div key={qr.id} className='qr-code mb-3'>
|
||||
<p>
|
||||
<strong>{formatDateString(qr.date)}</strong> — {qr.creator} ({qr.totalPrice / 100} Kč)
|
||||
{qr.purpose && <><br /><span className="text-muted">{qr.purpose}</span></>}
|
||||
</p>
|
||||
<img src={`/api/qr?login=${login}&id=${qr.id}`} alt='QR kód' />
|
||||
<div className='mt-2'>
|
||||
<Button variant="success" onClick={() => setDismissQrId(qr.id)}>
|
||||
Zaplatil jsem
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='pizza-section fade-in mt-4'>
|
||||
<h3>Nevyřízené platby</h3>
|
||||
<p>Máte neuhrazené platby.</p>
|
||||
{pendingQrs.map(qr => (
|
||||
<div key={qr.id} className='qr-code mb-3'>
|
||||
<p>
|
||||
<strong>{formatDateString(qr.date)}</strong> — {qr.creator} ({qr.totalPrice / 100} Kč)
|
||||
{qr.purpose && <><br /><span className="text-muted">{qr.purpose}</span></>}
|
||||
</p>
|
||||
<img src={`/api/qr?login=${login}&id=${qr.id}`} alt='QR kód' />
|
||||
<div className='mt-2'>
|
||||
<Button variant="success" onClick={() => setDismissQrId(qr.id)}>
|
||||
Zaplatil jsem
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<p>
|
||||
Máte neuhrazené platby.{' '}
|
||||
<Button variant="link" className="p-0 align-baseline" onClick={() => setModalOpen(true)}>
|
||||
Zobrazit QR kódy
|
||||
</Button>
|
||||
</p>
|
||||
{pendingQrs.map(renderQr)}
|
||||
</div>
|
||||
<Modal show={modalOpen} onHide={() => setModalOpen(false)} centered scrollable>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Nevyřízené platby</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>Máte neuhrazené platby. Naskenujte QR kód pro zaplacení.</p>
|
||||
{pendingQrs.map(renderQr)}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={() => setModalOpen(false)}>
|
||||
Zavřít
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
<ConfirmModal
|
||||
isOpen={dismissQrId !== null}
|
||||
title="Potvrzení platby"
|
||||
|
||||
@@ -152,12 +152,24 @@ export default function OrderGroupsPage() {
|
||||
return () => { 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:<login>)
|
||||
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) => {
|
||||
|
||||
Reference in New Issue
Block a user