import { useState, useEffect, useCallback } from "react"; import { Modal, Button, Form, Table, Alert } from "react-bootstrap"; import { generateQr, LunchChoice, LocationLunchChoicesMap, RestaurantDayMenu, QrRecipient } from "../../../../types"; import { parsePriceCzk } from "../../utils/parsePrice"; type DinerEntry = { login: string; selectedFoods: number[]; baseAmount: number; baseAmountParseFailed: boolean; surchargeText: string; surchargeAmount: string; included: boolean; }; type Props = { isOpen: boolean; onClose: () => void; locationKey: LunchChoice; locationName: string; locationChoices: LocationLunchChoicesMap; menu: RestaurantDayMenu | undefined; payerLogin: string; bankAccount: string; bankAccountHolder: string; }; function sanitizeAmount(value: string): string { return value.replace(/[^0-9.,]/g, '').replace(',', '.'); } function parseAmount(s: string): number | null { if (!s || s.trim().length === 0) return null; const n = parseFloat(s); if (isNaN(n) || n < 0) return null; const parts = s.split('.'); if (parts.length === 2 && parts[1].length > 2) return null; return Math.round(n * 100) / 100; } export default function PayForAllModal({ isOpen, onClose, locationName, locationChoices, menu, payerLogin, bankAccount, bankAccountHolder }: Readonly) { const [diners, setDiners] = useState([]); const [tipTotal, setTipTotal] = useState(''); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); const hasMenu = !!menu; useEffect(() => { if (!isOpen) return; const entries: DinerEntry[] = Object.entries(locationChoices).map(([login, choice]) => { const selectedFoods = choice.selectedFoods ?? []; let baseAmount = 0; let baseAmountParseFailed = false; if (menu) { for (const idx of selectedFoods) { const price = parsePriceCzk(menu.food?.[idx]?.price); if (price === null) { baseAmountParseFailed = true; } else { baseAmount += price; } } } return { login, selectedFoods, baseAmount, baseAmountParseFailed, surchargeText: '', surchargeAmount: '', included: login !== payerLogin, }; }); setDiners(entries); setTipTotal(''); setError(null); setSuccess(false); }, [isOpen, locationChoices, menu, payerLogin]); const includedDiners = diners.filter(d => d.included && d.login !== payerLogin); const tipPerPerson = (() => { if (includedDiners.length === 0) return 0; const tip = parseAmount(tipTotal); if (tip === null || tip === 0) return 0; return Math.round((tip / includedDiners.length) * 100) / 100; })(); const getTotal = (d: DinerEntry): number => { const surcharge = parseAmount(d.surchargeAmount) ?? 0; const tip = d.included && d.login !== payerLogin ? tipPerPerson : 0; return Math.round((d.baseAmount + surcharge + tip) * 100) / 100; }; const handleInclude = useCallback((login: string, checked: boolean) => { setDiners(prev => prev.map(d => d.login === login ? { ...d, included: checked } : d)); }, []); const handleSurchargeText = useCallback((login: string, value: string) => { setDiners(prev => prev.map(d => d.login === login ? { ...d, surchargeText: value } : d)); }, []); const handleSurchargeAmount = useCallback((login: string, value: string) => { setDiners(prev => prev.map(d => d.login === login ? { ...d, surchargeAmount: sanitizeAmount(value) } : d)); }, []); const handleGenerate = async () => { setError(null); const recipients: QrRecipient[] = []; for (const d of diners) { if (!d.included || d.login === payerLogin) continue; const total = getTotal(d); if (total <= 0) { setError(`Celková částka pro ${d.login} musí být kladná`); return; } const amountStr = total.toString(); if (amountStr.includes('.') && amountStr.split('.')[1].length > 2) { setError(`Částka pro ${d.login} má více než 2 desetinná místa`); return; } const foods = d.selectedFoods.map(i => menu?.food?.[i]?.name).filter(Boolean).join(', '); const purposeBase = `Oběd ${locationName}${foods ? ` — ${foods}` : ''}`; recipients.push({ login: d.login, purpose: purposeBase.substring(0, 60), amount: total, }); } if (recipients.length === 0) { setError("Nebyl vybrán žádný příjemce"); return; } setLoading(true); try { const response = await generateQr({ body: { recipients, bankAccount, bankAccountHolder }, }); if (response.error) { setError((response.error as any).error || 'Nastala chyba při generování QR kódů'); } else { setSuccess(true); setTimeout(() => onClose(), 2000); } } catch (e: any) { setError(e.message || 'Nastala chyba při generování QR kódů'); } finally { setLoading(false); } }; const anyParseFailed = diners.some(d => d.baseAmountParseFailed && d.included); return (

Zaplatit za všechny — {locationName}

{success ? ( QR kódy byly úspěšně vygenerovány! Uživatelé je uvidí v sekci „Nevyřízené platby". ) : ( <>

Zaplatili jste za skupinu v restauraci. Nastavte příplatky a dýško, poté vygenerujte QR kódy pro ostatní.

{!hasMenu && ( Pro tuto skupinu nejsou k dispozici ceny jídel — vyplňte příplatky ručně. )} {anyParseFailed && ( U některých jídel se nepodařilo načíst cenu — doplňte ji ručně v sloupci Příplatek. )} {error && ( setError(null)} dismissible> {error} )} {diners.map(d => { const isPayer = d.login === payerLogin; const foodNames = d.selectedFoods.map(i => menu?.food?.[i]?.name).filter(Boolean).join(', '); const total = getTotal(d); return ( ); })}
Strávník Jídla Příplatek Dýško Celkem
{isPayer ? ( plátce ) : ( handleInclude(d.login, e.target.checked)} /> )} {d.login} {foodNames || } {hasMenu && d.baseAmount > 0 && ({d.baseAmount} Kč)} {d.baseAmountParseFailed && } {!isPayer && (
handleSurchargeText(d.login, e.target.value)} disabled={!d.included} size="sm" onKeyDown={e => e.stopPropagation()} /> handleSurchargeAmount(d.login, e.target.value)} disabled={!d.included} size="sm" style={{ width: 70 }} onKeyDown={e => e.stopPropagation()} />
)}
{!isPayer && d.included ? `${tipPerPerson} Kč` : '—'} {!isPayer ? `${total} Kč` : '—'}
setTipTotal(sanitizeAmount(e.target.value))} size="sm" style={{ width: 100 }} onKeyDown={e => e.stopPropagation()} /> {includedDiners.length > 0 && tipPerPerson > 0 ? `(${tipPerPerson} Kč / osoba)` : ''}
)}
{!success && ( <> Příjemci: {includedDiners.length} )} {success && ( )}
); }