feat: vylepšení funkce objednávání
CI / Generate TypeScript types (push) Successful in 1m24s
CI / Build server (push) Successful in 26s
CI / Build client (push) Successful in 41s
CI / Server unit tests (push) Successful in 3m25s
CI / Playwright E2E tests (push) Has been cancelled
CI / Build and push Docker image (push) Has been cancelled
CI / Notify (push) Has been cancelled
CI / Generate TypeScript types (push) Successful in 1m24s
CI / Build server (push) Successful in 26s
CI / Build client (push) Successful in 41s
CI / Server unit tests (push) Successful in 3m25s
CI / Playwright E2E tests (push) Has been cancelled
CI / Build and push Docker image (push) Has been cancelled
CI / Notify (push) Has been cancelled
This commit is contained in:
+31
-17
@@ -18,6 +18,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import Loader from './components/Loader';
|
||||
import { getHumanDateTime, isInTheFuture, formatDateString } from './Utils';
|
||||
import NoteModal from './components/modals/NoteModal';
|
||||
import ConfirmModal from './components/modals/ConfirmModal';
|
||||
import PayForAllModal from './components/modals/PayForAllModal';
|
||||
import { useEasterEgg } from './context/eggs';
|
||||
import { ClientData, Food, MealSlot, PendingQr, PizzaOrder, DepartureTime, PizzaDayState, Restaurant, RestaurantDayMenu, RestaurantDayMenuMap, LunchChoice, LocationLunchChoicesMap, UserLunchChoice, PizzaVariant, getData, getEasterEggImage, addPizza, removePizza, updatePizzaDayNote, createPizzaDay, deletePizzaDay, lockPizzaDay, unlockPizzaDay, finishOrder, finishDelivery, addChoice, jdemeObed, removeChoices, removeChoice, updateNote, changeDepartureTime, setBuyer, dismissQr, generateQr } from '../../types';
|
||||
@@ -77,6 +78,7 @@ function App() {
|
||||
const [dayIndex, setDayIndex] = useState<number>();
|
||||
const [loadingPizzaDay, setLoadingPizzaDay] = useState<boolean>(false);
|
||||
const [noteModalOpen, setNoteModalOpen] = useState<boolean>(false);
|
||||
const [dismissQrId, setDismissQrId] = useState<string | null>(null);
|
||||
const [payForAllLocationKey, setPayForAllLocationKey] = useState<LunchChoice | null>(null);
|
||||
const [eggImage, setEggImage] = useState<Blob>();
|
||||
const eggRef = useRef<HTMLImageElement>(null);
|
||||
@@ -698,15 +700,15 @@ function App() {
|
||||
{locationPickCount >= 2 && auth.login && loginObject[auth.login] !== undefined
|
||||
&& locationKey !== LunchChoice.PIZZA && locationKey !== LunchChoice.NEOBEDVAM && locationKey !== LunchChoice.ROZHODUJI
|
||||
&& settings?.bankAccount && settings?.holderName && (
|
||||
<span title='Zaplatit za všechny a vygenerovat QR kódy ostatním' className="ms-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faMoneyBillTransfer}
|
||||
onClick={() => setPayForAllLocationKey(locationKey)}
|
||||
className='action-icon'
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
<span title='Zaplatit za všechny a vygenerovat QR kódy ostatním' className="ms-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faMoneyBillTransfer}
|
||||
onClick={() => setPayForAllLocationKey(locationKey)}
|
||||
className='action-icon'
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className='p-0'>
|
||||
<Table className="nested-table">
|
||||
@@ -909,18 +911,12 @@ function App() {
|
||||
{data.pendingQrs.map(qr => (
|
||||
<div key={qr.id} className='qr-code mb-3'>
|
||||
<p>
|
||||
<strong>{formatDateString(qr.date)}</strong> — {qr.creator} ({qr.totalPrice} Kč)
|
||||
<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=${auth.login}&id=${qr.id}`} alt='QR kód' />
|
||||
<div className='mt-2'>
|
||||
<Button variant="success" onClick={async () => {
|
||||
await dismissQr({ body: { id: qr.id } });
|
||||
const response = await getData({ query: { dayIndex } });
|
||||
if (response.data) {
|
||||
setData(response.data);
|
||||
}
|
||||
}}>
|
||||
<Button variant="success" onClick={() => setDismissQrId(qr.id)}>
|
||||
Zaplatil jsem
|
||||
</Button>
|
||||
</div>
|
||||
@@ -936,6 +932,24 @@ function App() {
|
||||
/> */}
|
||||
<Footer />
|
||||
<NoteModal isOpen={noteModalOpen} onClose={() => setNoteModalOpen(false)} onSave={saveNote} />
|
||||
<ConfirmModal
|
||||
isOpen={dismissQrId !== null}
|
||||
title="Potvrzení platby"
|
||||
message="Opravdu jste zaplatili? QR kód bude odstraněn."
|
||||
confirmLabel="Zaplatil jsem"
|
||||
confirmVariant="success"
|
||||
onClose={() => setDismissQrId(null)}
|
||||
onConfirm={async () => {
|
||||
if (!dismissQrId) return;
|
||||
const id = dismissQrId;
|
||||
setDismissQrId(null);
|
||||
await dismissQr({ body: { id } });
|
||||
const response = await getData({ query: { dayIndex } });
|
||||
if (response.data) {
|
||||
setData(response.data);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{payForAllLocationKey && data && (
|
||||
<PayForAllModal
|
||||
isOpen
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Modal, Button } from "react-bootstrap";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
confirmLabel?: string;
|
||||
confirmVariant?: string;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export default function ConfirmModal({ isOpen, title, message, confirmLabel = "Potvrdit", confirmVariant = "primary", onConfirm, onClose }: Readonly<Props>) {
|
||||
return (
|
||||
<Modal show={isOpen} onHide={onClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>{title}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>{message}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={onClose}>Zrušit</Button>
|
||||
<Button variant={confirmVariant} onClick={onConfirm}>{confirmLabel}</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { generateQr, OrderGroup, OrderGroupMember, QrRecipient } from "../../../
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
group: OrderGroup;
|
||||
payerLogin: string;
|
||||
bankAccount: string;
|
||||
@@ -18,7 +19,7 @@ type DinerEntry = {
|
||||
included: boolean;
|
||||
};
|
||||
|
||||
export default function PayForGroupModal({ isOpen, onClose, group, payerLogin, bankAccount, bankAccountHolder, groupId }: Readonly<Props>) {
|
||||
export default function PayForGroupModal({ isOpen, onClose, onSuccess, group, payerLogin, bankAccount, bankAccountHolder, groupId }: Readonly<Props>) {
|
||||
const [diners, setDiners] = useState<DinerEntry[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -73,9 +74,10 @@ export default function PayForGroupModal({ isOpen, onClose, group, payerLogin, b
|
||||
setError(`Celková částka pro ${d.login} musí být kladná`);
|
||||
return;
|
||||
}
|
||||
const note = d.member.note?.trim();
|
||||
recipients.push({
|
||||
login: d.login,
|
||||
purpose: `Objednávka ${group.name}`.substring(0, 60),
|
||||
purpose: note ? note.replace(/[^\x00-\xff*]/g, '').replace(/\*/g, '').substring(0, 60) : `Objednávka ${group.name}`.substring(0, 60),
|
||||
amount: total,
|
||||
});
|
||||
}
|
||||
@@ -94,6 +96,7 @@ export default function PayForGroupModal({ isOpen, onClose, group, payerLogin, b
|
||||
setError((response.error as any).error || 'Nastala chyba při generování QR kódů');
|
||||
} else {
|
||||
setSuccess(true);
|
||||
onSuccess?.();
|
||||
setTimeout(() => onClose(), 2000);
|
||||
}
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -209,7 +209,7 @@ export default function OrderGroupsPage() {
|
||||
)}
|
||||
|
||||
<div className="content-wrapper">
|
||||
<div className="content" style={{ maxWidth: 960 }}>
|
||||
<div className="content" style={{ maxWidth: 1200 }}>
|
||||
{/* Vytvoření nové skupiny */}
|
||||
<div className="choice-section fade-in mb-4">
|
||||
<h5>Vytvořit skupinu</h5>
|
||||
@@ -295,7 +295,7 @@ export default function OrderGroupsPage() {
|
||||
)}
|
||||
{isCreator && isOrdered && (
|
||||
<>
|
||||
{settings?.bankAccount && settings?.holderName && (
|
||||
{settings?.bankAccount && settings?.holderName && !group.qrGenerated && (
|
||||
<Button variant="primary" size="sm" onClick={() => setPayModal(group)}>
|
||||
<FontAwesomeIcon icon={faBasketShopping} className="me-1" />
|
||||
Generovat QR
|
||||
@@ -319,10 +319,10 @@ export default function OrderGroupsPage() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Člen</th>
|
||||
<th style={{ width: 140 }}>Částka (Kč)</th>
|
||||
<th style={{ width: 180 }}>Částka (bez slev)</th>
|
||||
<th style={{ width: 220 }}>Příplatek</th>
|
||||
<th>Poznámka</th>
|
||||
<th style={{ width: 100 }}>Celkem</th>
|
||||
<th style={{ width: 160 }}>Celkem (s poplatky)</th>
|
||||
<th style={{ width: 40 }}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -537,7 +537,6 @@ export default function OrderGroupsPage() {
|
||||
<small className="text-muted">
|
||||
Doručení v: <strong>{group.deliveryAt ?? '—'}</strong>
|
||||
</small>
|
||||
{isCreator && <small className="text-muted fst-italic">(upravit)</small>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -579,6 +578,7 @@ export default function OrderGroupsPage() {
|
||||
<PayForGroupModal
|
||||
isOpen={!!payModal}
|
||||
onClose={() => setPayModal(null)}
|
||||
onSuccess={fetchData}
|
||||
group={payModal}
|
||||
groupId={payModal.id}
|
||||
payerLogin={auth.login}
|
||||
|
||||
Reference in New Issue
Block a user