import { useState, useEffect } from 'react' import { Save, Eye, EyeOff, Loader2, ShieldCheck, Printer, History, Info, Plus, Trash2, Check } from 'lucide-react' import { QRCodeSVG } from 'qrcode.react' import { Link } from 'react-router-dom' import { api, type DepositSettings, type DepositPreset } from '../lib/api' import { useAuth } from '../contexts/AuthContext' import { cn, formatCurrency } from '../lib/utils' function Toggle({ on, onChange }: { on: boolean; onChange: () => void }) { return ( ) } export function DepositSettingsPage() { const { user } = useAuth() const slug = user?.hotelSlug ?? '' const [settings, setSettings] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [saved, setSaved] = useState(false) const [error, setError] = useState(null) const [isEnabled, setIsEnabled] = useState(false) const [amount, setAmount] = useState('5000') const [releaseRequiresCheckout, setReleaseRequiresCheckout] = useState(false) const [longStayFullPayment, setLongStayFullPayment] = useState(false) const [longStayThresholdDays, setLongStayThresholdDays] = useState('7') const [shopId, setShopId] = useState('') const [secretKey, setSecretKey] = useState('') const [showSecret, setShowSecret] = useState(false) // Deduction presets const [presets, setPresets] = useState([]) const [newPresetName, setNewPresetName] = useState('') const [newPresetAmount, setNewPresetAmount] = useState('') const [addingPreset, setAddingPreset] = useState(false) const [editingPreset, setEditingPreset] = useState(null) const [editName, setEditName] = useState('') const [editAmount, setEditAmount] = useState('') useEffect(() => { if (!slug) return Promise.all([ api.deposits.getSettings(slug), api.deposits.getPresets(slug).catch(() => []), ]).then(([s, p]) => { setSettings(s) setIsEnabled(s.isEnabled) setAmount(String(s.amount)) setReleaseRequiresCheckout(s.releaseRequiresCheckout ?? false) setLongStayFullPayment(s.longStayFullPayment ?? false) setLongStayThresholdDays(String(s.longStayThresholdDays ?? 7)) setShopId(s.yookassaShopId ?? '') setSecretKey(s.yookassaSecretKey ?? '') setPresets(p) }).catch(() => { setIsEnabled(false) setAmount('5000') setShopId('') setSecretKey('') }).finally(() => setLoading(false)) }, [slug]) const handleSave = async () => { setSaving(true) setError(null) try { const payload: Record = { is_enabled: isEnabled, amount: parseFloat(amount) || 5000, release_requires_checkout: releaseRequiresCheckout, long_stay_full_payment: longStayFullPayment, long_stay_threshold_days: parseInt(longStayThresholdDays) || 7, } if (shopId) payload.yookassa_shop_id = shopId if (secretKey && secretKey !== '••••••••') payload.yookassa_secret_key = secretKey const updated = await api.deposits.updateSettings(slug, payload) setSettings(updated) setSaved(true) setTimeout(() => setSaved(false), 2000) } catch { setError('Не удалось сохранить настройки') } finally { setSaving(false) } } const handleAddPreset = async () => { const name = newPresetName.trim() const amt = parseFloat(newPresetAmount) if (!name || !amt || amt <= 0) return setAddingPreset(true) try { const p = await api.deposits.createPreset(slug, name, amt) setPresets(prev => [...prev, p]) setNewPresetName('') setNewPresetAmount('') } catch { // ignore } finally { setAddingPreset(false) } } const handleDeletePreset = async (id: string) => { await api.deposits.deletePreset(slug, id).catch(() => {}) setPresets(prev => prev.filter(p => p.id !== id)) } const startEdit = (p: DepositPreset) => { setEditingPreset(p.id) setEditName(p.name) setEditAmount(String(p.amount)) } const saveEdit = async (id: string) => { const name = editName.trim() const amt = parseFloat(editAmount) if (!name || !amt) return try { const updated = await api.deposits.updatePreset(slug, id, { name, amount: amt }) setPresets(prev => prev.map(p => p.id === id ? updated : p)) } catch { // ignore } setEditingPreset(null) } if (loading) { return (
) } return (

Депозит

Настройте предоплату или залог при заселении гостей.

История платежей
{error && (
{error}
)} {/* Main settings card */}

Включить депозит

При включении на странице бронирования появятся кнопки для управления депозитом

setIsEnabled(v => !v)} />
{isEnabled && ( <>
setAmount(e.target.value)} onFocus={e => e.target.select()} className="input w-48" />

Запретить возврат депозита до выезда

Кнопки «Вернуть» и «Списать» будут заблокированы, пока гость не выехал

setReleaseRequiresCheckout(v => !v)} />

Длинная бронь — полная оплата

Для броней длиннее указанного кол-ва ночей снимать депозит сразу (не замораживать). Обход 7-дневного лимита ЮКасса

{longStayFullPayment && (
Порог: setLongStayThresholdDays(e.target.value)} onFocus={e => e.target.select()} className="input w-20 text-sm" /> ночей
)}
setLongStayFullPayment(v => !v)} />

ЮКасса настраивается в одном месте

Реквизиты ЮКасса (Shop ID и секретный ключ) теперь управляются централизованно. Перейдите в Настройки оплаты → Онлайн-оплата и добавьте шлюз один раз — он будет работать для депозита, онлайн-бронирования и Room Service.

QR-код для гостей

Распечатайте и разместите на стойке ресепшена.

https://app.hotelsync.ru/{slug}/pay

)}
{saved && Настройки сохранены}
{/* Deduction presets */} {isEnabled && (

Позиции удержания

Стандартные причины для списания из депозита. При возврате депозита менеджер выбирает из этого списка.

{/* Existing presets */} {presets.length > 0 && (
{presets.map(p => (
{editingPreset === p.id ? ( <> setEditName(e.target.value)} className="input text-sm flex-1" placeholder="Название" onFocus={e => e.target.select()} /> setEditAmount(e.target.value)} onFocus={e => e.target.select()} type="number" min="0" step="100" className="input text-sm w-28 text-right" placeholder="Сумма" /> ) : ( <> startEdit(p)} > {p.name} startEdit(p)}> {formatCurrency(p.amount)} )}
))}
)} {/* Add new preset */}
setNewPresetName(e.target.value)} className="input text-sm flex-1" placeholder="Название (напр. Штраф за курение)" onKeyDown={e => e.key === 'Enter' && handleAddPreset()} /> setNewPresetAmount(e.target.value)} onFocus={e => e.target.select()} type="number" min="0" step="100" className="input text-sm w-28 text-right" placeholder="Сумма" onKeyDown={e => e.key === 'Enter' && handleAddPreset()} />
)}
) }