import { useState } from 'react' import { format } from 'date-fns' import { Star, Banknote, CreditCard, AlertCircle, Map, Plus, Trash2, IdCard, CalendarDays, Tag, ScanLine, Minus, BedDouble } from 'lucide-react' import { MOCK_DISCOUNTS } from '../../pages/DiscountsPage' import type { Discount } from '../../pages/DiscountsPage' import { Modal } from '../ui/Modal' import { cn, BOOKING_STATUS_LABELS } from '../../lib/utils' import type { Booking, DraftBooking, Room, BookingStatus } from '../../types' import { FloorMapModal } from '../floormap/FloorMapModal' type DocType = 'rf_passport' | 'foreign_passport' | 'other' interface PassportData { docType: DocType lastName: string firstName: string patronymic: string birthDate: string birthPlace: string nationality: string series: string number: string issuedBy: string issuedDate: string divisionCode: string registrationAddress: string } const EMPTY_PASSPORT: PassportData = { docType: 'rf_passport', lastName: '', firstName: '', patronymic: '', birthDate: '', birthPlace: '', nationality: 'Россия', series: '', number: '', issuedBy: '', issuedDate: '', divisionCode: '', registrationAddress: '', } const DOC_TYPES: { id: DocType; label: string }[] = [ { id: 'rf_passport', label: 'Паспорт РФ' }, { id: 'foreign_passport', label: 'Загранпаспорт' }, { id: 'other', label: 'Иной документ' }, ] const GUEST_TAGS = ['VIP', 'Постоянный гость', 'Корпоративный', 'Медовый месяц', 'День рождения', 'Особые пожелания'] const ADDITIONAL_SERVICES = [ { id: 'breakfast', label: 'Завтрак', price: 800 }, { id: 'transfer', label: 'Трансфер', price: 2500 }, { id: 'parking', label: 'Парковка', price: 500 }, { id: 'laundry', label: 'Стирка', price: 600 }, { id: 'minibar', label: 'Мини-бар', price: 1500 }, { id: 'excursion', label: 'Экскурсия', price: 3000 }, ] interface AddedService { id: string; label: string; price: number; qty: number } const HOURS = Array.from({ length: 24 }, (_, i) => i) interface BookingModalProps { open: boolean draft: DraftBooking rooms: Room[] bookings?: Booking[] onClose: () => void onSave: (data: Partial) => void existing?: Booking } function isConflict(bookings: Booking[], roomId: string, checkIn: string, checkOut: string, excludeId?: string) { if (!checkIn || !checkOut || checkIn >= checkOut) return false return bookings.some(b => b.roomId === roomId && b.id !== excludeId && b.status !== 'cancelled' && b.status !== 'no_show' && b.checkIn < checkOut && b.checkOut > checkIn, ) } function availableRooms(rooms: Room[], bookings: Booking[], checkIn: string, checkOut: string, excludeId?: string) { return rooms.filter(r => !isConflict(bookings, r.id, checkIn, checkOut, excludeId)) } export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSave, existing }: BookingModalProps) { const [form, setForm] = useState({ roomId: existing?.roomId ?? draft.roomId, guestName: existing?.guestName ?? '', guestEmail:existing?.guestEmail ?? '', checkIn: existing?.checkIn ?? draft.checkIn, checkOut: existing?.checkOut ?? draft.checkOut, adults: existing?.adults ?? 2, children: existing?.children ?? 0, status: (existing?.status ?? 'confirmed') as BookingStatus, notes: existing?.notes ?? '', }) const [guestTags, setGuestTags] = useState([]) const [paymentMethod, setPaymentMethod] = useState<'cash' | 'terminal' | null>(null) const [paidAmount, setPaidAmount] = useState(existing?.paidAmount ?? 0) const [showFloorMap, setShowFloorMap] = useState(false) const [addedServices, setAddedServices] = useState([]) const [selectedServiceId, setSelectedServiceId] = useState(ADDITIONAL_SERVICES[0].id) const [activeTab, setActiveTab] = useState<'booking' | 'passport'>('booking') const [passport, setPassport] = useState(EMPTY_PASSPORT) const [selectedDiscountId, setSelectedDiscountId] = useState('') const activeDiscounts = MOCK_DISCOUNTS.filter(d => d.isActive) const selectedDiscount: Discount | undefined = activeDiscounts.find(d => d.id === selectedDiscountId) const setP = (k: K, v: PassportData[K]) => setPassport(prev => ({ ...prev, [k]: v })) const passportFilled = !!(passport.lastName || passport.number) // Hourly booking state const [isHourly, setIsHourly] = useState(false) const [hourlyDate, setHourlyDate] = useState(draft.checkIn) const [startHour, setStartHour] = useState(10) const [endHour, setEndHour] = useState(12) // Extra beds const [extraBeds, setExtraBeds] = useState(0) const EXTRA_BED_PRICE = 1500 const room = rooms.find(r => r.id === form.roomId) const nights = form.checkIn && form.checkOut ? Math.max(0, (new Date(form.checkOut).getTime() - new Date(form.checkIn).getTime()) / 86400000) : 0 const hourlyHours = Math.max(0, endHour - startHour) const roomBaseTotal = isHourly && room?.allowHourly ? (room.hourlyRate ?? 0) * hourlyHours : (room?.baseRate ?? 0) * nights const discountAmount = selectedDiscount ? selectedDiscount.valueType === 'percent' ? Math.round(roomBaseTotal * Math.min(100, selectedDiscount.value) / 100) : Math.min(roomBaseTotal, selectedDiscount.value) : 0 const roomTotal = Math.max(0, roomBaseTotal - discountAmount) const servicesTotal = addedServices.reduce((s, sv) => s + sv.price * sv.qty, 0) const extraBedsTotal = extraBeds * EXTRA_BED_PRICE * (isHourly ? 1 : Math.max(1, nights)) const total = roomTotal + servicesTotal + extraBedsTotal const debt = Math.max(0, total - paidAmount) const addService = () => { const svc = ADDITIONAL_SERVICES.find(s => s.id === selectedServiceId) if (!svc) return setAddedServices(prev => { const ex = prev.find(s => s.id === svc.id) if (ex) return prev.map(s => s.id === svc.id ? { ...s, qty: s.qty + 1 } : s) return [...prev, { ...svc, qty: 1 }] }) } const removeService = (id: string) => setAddedServices(prev => prev.filter(s => s.id !== id)) const isFutureBooking = form.checkIn > format(new Date(), 'yyyy-MM-dd') const set = (k: K, v: typeof form[K]) => setForm(prev => ({ ...prev, [k]: v })) const toggleTag = (tag: string) => setGuestTags(prev => prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag]) const handleSave = () => { if (!form.guestName) return if (isHourly && room?.allowHourly) { if (!hourlyDate) return } else { if (!form.checkIn || !form.checkOut) return } const hourlyPrefix = isHourly && room?.allowHourly ? `[Почасово: ${String(startHour).padStart(2, '0')}:00–${String(endHour).padStart(2, '0')}:00] ` : '' const extraBedsPrefix = extraBeds > 0 ? `[Доп.места: ${extraBeds}] ` : '' const checkIn = isHourly && room?.allowHourly ? hourlyDate : form.checkIn const checkOut = isHourly && room?.allowHourly ? hourlyDate : form.checkOut onSave({ ...form, source: 'direct', checkIn, checkOut, notes: hourlyPrefix + extraBedsPrefix + form.notes, totalAmount: total, paidAmount, id: existing?.id ?? `b-${Date.now()}`, hotelId: 'hotel-1', guestId: existing?.guestId ?? `g-${Date.now()}`, createdAt: existing?.createdAt ?? format(new Date(), 'yyyy-MM-dd'), }) } const showHourlyTab = room?.allowHourly === true return ( <> } > {/* ── Tabs ── */}
{([ { id: 'booking' as const, label: 'Бронирование', icon: CalendarDays }, { id: 'passport' as const, label: 'Документы гостя', icon: IdCard, badge: passportFilled }, ]).map(t => ( ))}
{/* ── BOOKING TAB ── */} {activeTab === 'booking' && (
{/* ── LEFT COLUMN ── */}
{/* Room */}
{/* Conflict warning */} {!isHourly && form.roomId && form.checkIn && form.checkOut && form.checkIn < form.checkOut && isConflict(bookings, form.roomId, form.checkIn, form.checkOut, existing?.id) && (() => { const free = availableRooms(rooms, bookings, form.checkIn, form.checkOut, existing?.id) return (
Номер занят на эти даты.
{free.length > 0 ? (
Свободны: {free.map(r => ( ))}
) : (

Нет свободных номеров на эти даты.

)}
) })() }
{/* Guest name + email */}
set('guestName', e.target.value)} />
set('guestEmail', e.target.value)} />
{/* Hourly / Daily toggle */} {showHourlyTab && (
)} {/* Dates */} {isHourly && room?.allowHourly ? (
setHourlyDate(e.target.value)} />
) : (
set('checkIn', e.target.value)} />
set('checkOut', e.target.value)} />
)} {/* Adults + Children + Status */}
set('adults', parseInt(e.target.value) || 1)} />
set('children', parseInt(e.target.value) || 0)} />
{/* Extra beds */}

Доп. места

{EXTRA_BED_PRICE.toLocaleString('ru-RU')} ₽/место{!isHourly && nights > 0 ? '/ночь' : ''}

{extraBeds > 0 && ( +{extraBedsTotal.toLocaleString('ru-RU')} ₽ )} {extraBeds}
{/* Guest tags */}
{GUEST_TAGS.map(tag => { const isVip = tag === 'VIP' const selected = guestTags.includes(tag) return ( ) })}
{/* ── RIGHT COLUMN ── */}
{/* Payment */} {total > 0 && (

Оплата

{/* Discount */}
{selectedDiscount && (

−{discountAmount.toLocaleString('ru-RU')} ₽ {selectedDiscount.note ? ` · ${selectedDiscount.note}` : ''}

)}
{/* Additional services */}
{addedServices.length > 0 && (
{addedServices.map(s => (
{s.label} × {s.qty}
{(s.price * s.qty).toLocaleString('ru-RU')} ₽
))}
)}
setPaidAmount(Math.min(total, Math.max(0, parseInt(e.target.value) || 0)))} />
)}
{/* ── BOTTOM ROW: Notes + Summary ── */}