diff --git a/src/components/calendar/BookingCalendar.tsx b/src/components/calendar/BookingCalendar.tsx index d460093..f2ef444 100644 --- a/src/components/calendar/BookingCalendar.tsx +++ b/src/components/calendar/BookingCalendar.tsx @@ -793,6 +793,8 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook existingBookings={(rentalBookings ?? []).filter( b => b.objectId === rentalModal.obj.id && b.date === rentalModal.date, )} + bookings={bookings} + rooms={rooms} onClose={() => setRentalModal(null)} onSave={(b) => { onRentalBookingCreate?.(b) diff --git a/src/components/rental/RentalBookingModal.tsx b/src/components/rental/RentalBookingModal.tsx index 611809a..fefd14e 100644 --- a/src/components/rental/RentalBookingModal.tsx +++ b/src/components/rental/RentalBookingModal.tsx @@ -1,13 +1,16 @@ -import { useState } from 'react' -import { X, Clock, CalendarDays, User, Phone, AlertCircle, Banknote, CreditCard, Building2 } from 'lucide-react' +import { useState, useRef, useEffect } from 'react' +import { X, Clock, CalendarDays, User, Phone, AlertCircle, Banknote, CreditCard, Building2, Link2 } from 'lucide-react' import { cn } from '../../lib/utils' import type { RentalObject, RentalBooking } from '../../data/rentalData' +import type { Booking, Room } from '../../types' interface RentalBookingModalProps { obj: RentalObject - date: string // 'yyyy-MM-dd' + date: string existingBookings: RentalBooking[] - editBooking?: RentalBooking // pass to edit an existing booking + editBooking?: RentalBooking + bookings?: Booking[] // all room bookings (for guest search + link) + rooms?: Room[] // room list (for displaying room numbers) onClose: () => void onSave: (b: RentalBooking) => void } @@ -20,7 +23,9 @@ function formatHour(h: number): string { return `${h}:00` } -export function RentalBookingModal({ obj, date, existingBookings, editBooking, onClose, onSave }: RentalBookingModalProps) { +export function RentalBookingModal({ + obj, date, existingBookings, editBooking, bookings = [], rooms = [], onClose, onSave, +}: RentalBookingModalProps) { const isEdit = !!editBooking const [isFullDay, setIsFullDay] = useState(editBooking?.isFullDay ?? false) const [startHour, setStartHour] = useState(editBooking?.startHour ?? obj.openHour) @@ -30,15 +35,52 @@ export function RentalBookingModal({ obj, date, existingBookings, editBooking, o const [notes, setNotes] = useState(editBooking?.notes ?? '') const [paidAmount, setPaidAmount] = useState(editBooking?.paidAmount ? String(editBooking.paidAmount) : '') const [payMethod, setPayMethod] = useState<'cash' | 'card' | 'transfer'>('cash') + const [linkedBookingId, setLinkedBookingId] = useState(editBooking?.linkedRoomId ?? '') + + // Guest search + const [showSuggestions, setShowSuggestions] = useState(false) + const nameRef = useRef(null) + + // Bookings with guest info — checked_in first, then confirmed + const checkedIn = bookings.filter(b => b.status === 'checked_in') + const confirmed = bookings.filter(b => b.status === 'confirmed') + const guestSuggestions = [...checkedIn, ...confirmed] + + const filtered = guestName.trim().length > 0 + ? guestSuggestions.filter(b => + b.guestName.toLowerCase().includes(guestName.toLowerCase()), + ) + : guestSuggestions + + // Room label helper + const roomLabel = (b: Booking) => { + const r = rooms.find(r => r.id === b.roomId) + return r ? `№${r.number}` : '' + } + + // Close suggestions on outside click + useEffect(() => { + const handler = (e: MouseEvent) => { + if (nameRef.current && !nameRef.current.contains(e.target as Node)) { + setShowSuggestions(false) + } + } + document.addEventListener('mousedown', handler) + return () => document.removeEventListener('mousedown', handler) + }, []) + + const selectGuest = (b: Booking) => { + setGuestName(b.guestName) + setGuestPhone(b.guestPhone ?? '') + setLinkedBookingId(b.id) + setShowSuggestions(false) + } const hours = isFullDay ? obj.closeHour - obj.openHour : Math.max(0, endHour - startHour) - const totalAmount = isFullDay - ? obj.pricePerDay - : hours * obj.pricePerHour - + const totalAmount = isFullDay ? obj.pricePerDay : hours * obj.pricePerHour const maxHoursOk = !obj.maxHoursPerSlot || hours <= obj.maxHoursPerSlot const otherBookings = existingBookings.filter(b => b.id !== editBooking?.id) @@ -52,6 +94,7 @@ export function RentalBookingModal({ obj, date, existingBookings, editBooking, o const handleSave = () => { if (!canSave) return + const linked = bookings.find(b => b.id === linkedBookingId) onSave({ id: editBooking?.id ?? `rb-${Date.now()}`, objectId: obj.id, @@ -61,6 +104,7 @@ export function RentalBookingModal({ obj, date, existingBookings, editBooking, o endHour: isFullDay ? obj.closeHour : endHour, guestName: guestName.trim(), guestPhone: guestPhone.trim(), + linkedRoomId: (linked?.roomId ?? linkedBookingId) || undefined, notes: notes.trim() || undefined, totalAmount, paidAmount: parseFloat(paidAmount) || 0, @@ -68,7 +112,12 @@ export function RentalBookingModal({ obj, date, existingBookings, editBooking, o }) } - const displayDate = new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long', weekday: 'short' }).format(new Date(date)) + const displayDate = new Intl.DateTimeFormat('ru-RU', { + day: 'numeric', month: 'long', weekday: 'short', + }).format(new Date(date)) + + // For link dropdown — show checked-in first, label includes room number + const linkableBookings = [...checkedIn, ...confirmed.filter(b => !checkedIn.includes(b))] return (
@@ -80,9 +129,7 @@ export function RentalBookingModal({ obj, date, existingBookings, editBooking, o

{obj.name}

{displayDate}

- +
@@ -116,7 +163,7 @@ export function RentalBookingModal({ obj, date, existingBookings, editBooking, o
- {obj.maxHoursPerSlot && !maxHoursOk && ( -

- Максимальное время бронирования: {obj.maxHoursPerSlot} ч -

+

Максимум: {obj.maxHoursPerSlot} ч

)} {isTimeConflict && ( -

- Выбранное время пересекается с существующей бронью -

+

Время пересекается с существующей бронью

)} )} - {/* Guest info */} + {/* Guest search */}
-
- +
+ setGuestName(e.target.value)} + onChange={e => { setGuestName(e.target.value); setShowSuggestions(true) }} + onFocus={() => setShowSuggestions(true)} + autoComplete="off" /> + {/* Suggestions dropdown */} + {showSuggestions && filtered.length > 0 && ( +
+ {filtered.slice(0, 10).map(b => { + const rLabel = roomLabel(b) + const isCheckedIn = b.status === 'checked_in' + return ( + + ) + })} +
+ )}
+
+ + {/* Link to room */} +
+ + +
+