import { useState, useEffect, useCallback, useRef } from 'react' import { BookingCalendar } from '../components/calendar/BookingCalendar' import { RENTAL_OBJECTS, MOCK_RENTAL_BOOKINGS } from '../data/rentalData' import { useModules } from '../contexts/ModulesContext' import { useAuth } from '../contexts/AuthContext' import { api } from '../lib/api' import { useHotelSocket } from '../hooks/useHotelSocket' import type { WsMessage } from '../hooks/useHotelSocket' import type { Room, Booking } from '../types' import type { RentalBooking } from '../data/rentalData' export type BookingLock = { checkIn: string; checkOut: string; lockedBy: string } export function CalendarPage() { const { user, session } = useAuth() const slug = user?.hotelSlug ?? '' const { statuses } = useModules() const isRentalActive = statuses['rental'] === 'active' || statuses['rental'] === 'trial' const [rooms, setRooms] = useState([]) const [bookings, setBookings] = useState([]) const [fadingBookings, setFadingBookings] = useState>(new Set()) const [rentalBookings, setRentalBookings] = useState(MOCK_RENTAL_BOOKINGS) const [locks, setLocks] = useState>(new Map()) useEffect(() => { if (!slug) return Promise.all([ api.rooms.list(slug), api.bookings.list(slug), ]).then(([r, b]) => { setRooms(r) setBookings(b) }).catch(console.error) }, [slug]) const handleWsMessage = useCallback((msg: WsMessage) => { if (msg.type === 'lock') { setLocks(prev => new Map(prev).set(msg.roomId, { checkIn: msg.checkIn, checkOut: msg.checkOut, lockedBy: msg.lockedBy })) } else if (msg.type === 'unlock') { setLocks(prev => { const n = new Map(prev); n.delete(msg.roomId); return n }) } else if (msg.type === 'booking:created') { setBookings(prev => prev.find(b => b.id === msg.booking.id) ? prev : [...prev, msg.booking]) setLocks(prev => { const n = new Map(prev); n.delete(msg.booking.roomId); return n }) } else if (msg.type === 'booking:updated') { setBookings(prev => prev.map(b => b.id === msg.booking.id ? msg.booking : b)) } else if (msg.type === 'booking:deleted') { setBookings(prev => prev.filter(b => b.id !== msg.bookingId)) } }, []) const { send, connected } = useHotelSocket({ slug, token: session?.token ?? null, onMessage: handleWsMessage }) // Clear stale locks on WS reconnect (may have missed unlock/booking:created while disconnected) const prevConnected = useRef(false) useEffect(() => { if (connected && !prevConnected.current) { setLocks(new Map()) } prevConnected.current = connected }, [connected]) const handleCreate = async (data: Partial) => { try { const created = await api.bookings.create(slug, { roomId: data.roomId, guestName: data.guestName, guestEmail: data.guestEmail, checkIn: data.checkIn, checkOut: data.checkOut, adults: data.adults, children: data.children, status: data.status, source: data.source, totalAmount: data.totalAmount, paidAmount: data.paidAmount, notes: data.notes, }) setBookings(prev => [...prev, created]) send({ type: 'booking:created', booking: created }) if (data.roomId) send({ type: 'unlock', roomId: data.roomId }) } catch (err) { console.error('Failed to create booking:', err) const msg = err instanceof Error ? err.message : 'Ошибка создания бронирования' alert(msg) } } const handleUpdate = async (id: string, data: Partial) => { try { const updated = await api.bookings.update(slug, id, { guestName: data.guestName, guestEmail: data.guestEmail, checkIn: data.checkIn, checkOut: data.checkOut, adults: data.adults, children: data.children, status: data.status, source: data.source, totalAmount: data.totalAmount, notes: data.notes, }) setBookings(prev => prev.map(b => b.id === id ? updated : b)) send({ type: 'booking:updated', booking: updated }) if (data.status === 'cancelled') { setFadingBookings(prev => new Set([...prev, id])) setTimeout(() => { setBookings(prev => prev.filter(b => b.id !== id)) setFadingBookings(prev => { const n = new Set(prev); n.delete(id); return n }) }, 900) } } catch (err) { console.error('Failed to update booking:', err) } } const handleBulkUpdate = async (updates: Array<{ id: string; data: Partial }>) => { try { const results = await Promise.all( updates.map(u => api.bookings.update(slug, u.id, { status: u.data.status, checkIn: u.data.checkIn, checkOut: u.data.checkOut, roomId: u.data.roomId, })), ) setBookings(prev => { let next = [...prev] results.forEach(r => { next = next.map(b => b.id === r.id ? r : b) }) return next }) results.forEach(r => send({ type: 'booking:updated', booking: r })) } catch (err) { console.error('Failed to bulk update bookings:', err) } } const handleDraftStart = useCallback((roomId: string, checkIn: string, checkOut: string) => { send({ type: 'lock', roomId, checkIn, checkOut, lockedBy: user?.name ?? 'Менеджер' }) }, [send, user?.name]) const handleDraftCancel = useCallback((roomId: string) => { send({ type: 'unlock', roomId }) }, [send]) return (
setRentalBookings(prev => [...prev, b]) : undefined} locks={locks} onDraftStart={handleDraftStart} onDraftCancel={handleDraftCancel} wsConnected={connected} />
) }