diff --git a/src/pages/DepositHistoryPage.tsx b/src/pages/DepositHistoryPage.tsx index 7489e88..47fe734 100644 --- a/src/pages/DepositHistoryPage.tsx +++ b/src/pages/DepositHistoryPage.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react' -import { Loader2, ShieldCheck, CreditCard, Banknote, RefreshCw } from 'lucide-react' +import { Loader2, ShieldCheck, CreditCard, Banknote, RefreshCw, ArrowLeft } from 'lucide-react' +import { Link } from 'react-router-dom' import { api, type BookingDeposit } from '../lib/api' import { useAuth } from '../contexts/AuthContext' import { cn, formatCurrency } from '../lib/utils' @@ -40,14 +41,17 @@ export function DepositHistoryPage() { return (
-

- - История депозитов -

- diff --git a/src/pages/DepositSettingsPage.tsx b/src/pages/DepositSettingsPage.tsx index f6ad359..b533269 100644 --- a/src/pages/DepositSettingsPage.tsx +++ b/src/pages/DepositSettingsPage.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from 'react' -import { Save, Eye, EyeOff, Loader2, ShieldCheck, Printer, History, Info } from 'lucide-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 } from '../lib/api' +import { api, type DepositSettings, type DepositPreset } from '../lib/api' import { useAuth } from '../contexts/AuthContext' -import { cn } from '../lib/utils' +import { cn, formatCurrency } from '../lib/utils' function Toggle({ on, onChange }: { on: boolean; onChange: () => void }) { return ( @@ -33,24 +33,33 @@ export function DepositSettingsPage() { 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 - api.deposits.getSettings(slug) - .then(s => { - setSettings(s) - setIsEnabled(s.isEnabled) - setAmount(String(s.amount)) - setShopId(s.yookassaShopId ?? '') - setSecretKey(s.yookassaSecretKey ?? '') - }) - .catch(() => { - // No settings yet — defaults - setIsEnabled(false) - setAmount('5000') - setShopId('') - setSecretKey('') - }) - .finally(() => setLoading(false)) + Promise.all([ + api.deposits.getSettings(slug), + api.deposits.getPresets(slug).catch(() => []), + ]).then(([s, p]) => { + setSettings(s) + setIsEnabled(s.isEnabled) + setAmount(String(s.amount)) + setShopId(s.yookassaShopId ?? '') + setSecretKey(s.yookassaSecretKey ?? '') + setPresets(p) + }).catch(() => { + setIsEnabled(false) + setAmount('5000') + setShopId('') + setSecretKey('') + }).finally(() => setLoading(false)) }, [slug]) const handleSave = async () => { @@ -75,6 +84,47 @@ export function DepositSettingsPage() { } } + 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 (
@@ -95,10 +145,7 @@ export function DepositSettingsPage() { Настройте предоплату или залог при заселении гостей.

- + История платежей @@ -110,8 +157,8 @@ export function DepositSettingsPage() {
)} + {/* Main settings card */}
- {/* Enable toggle */}

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

@@ -125,66 +172,47 @@ export function DepositSettingsPage() { {isEnabled && ( <>
- {/* Amount */}
setAmount(e.target.value)} + type="number" min="0" step="100" + value={amount} onChange={e => setAmount(e.target.value)} + onFocus={e => e.target.select()} className="input w-48" />
- {/* YooKassa section */}

ЮКасса (опционально)

- Укажите реквизиты ЮКасса для создания холда (предавторизации) оплаты. Без этих данных доступен только наличный депозит. + Укажите реквизиты ЮКасса для создания холда (предавторизации) оплаты.

-
- setShopId(e.target.value)} - placeholder="123456" - className="input w-full" - /> + setShopId(e.target.value)} placeholder="123456" className="input w-full" />
-
setSecretKey(e.target.value)} + value={secretKey} onChange={e => setSecretKey(e.target.value)} placeholder={settings?.yookassaSecretKey ? '••••••••' : 'live_xxxx...'} className="input w-full pr-10" /> -
{settings?.yookassaSecretKey && ( -

- Ключ уже сохранён. Оставьте поле пустым, чтобы не менять. -

+

Ключ уже сохранён. Оставьте поле пустым, чтобы не менять.

)}
- {/* Webhook notice */} {shopId && (
@@ -192,7 +220,7 @@ export function DepositSettingsPage() { Настройте webhook в ЮКасса

- Чтобы статус депозита обновлялся автоматически после оплаты, добавьте URL уведомлений в личном кабинете ЮКасса (Настройки → HTTP-уведомления): + Чтобы статус депозита обновлялся автоматически, добавьте URL в личном кабинете ЮКасса (Настройки → HTTP-уведомления):

https://api.hotelsync.ru/api/webhooks/yookassa @@ -204,29 +232,20 @@ export function DepositSettingsPage() {
)} - {/* QR code section — only when shopId is configured */} {shopId && (

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

- Распечатайте и разместите на стойке ресепшена. Гость сканирует код и оплачивает депозит картой. + Распечатайте и разместите на стойке ресепшена.

- +

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

- @@ -234,16 +253,8 @@ export function DepositSettingsPage() { @@ -254,18 +265,103 @@ export function DepositSettingsPage() {
- - {saved && ( - Настройки сохранены - )} + {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()} + /> + + +
+
+ )}
) }