diff --git a/src/components/bookings/BookingDetailPanel.tsx b/src/components/bookings/BookingDetailPanel.tsx index dd5b132..95a1964 100644 --- a/src/components/bookings/BookingDetailPanel.tsx +++ b/src/components/bookings/BookingDetailPanel.tsx @@ -250,6 +250,26 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on const [conflictResolveRoomId, setConflictResolveRoomId] = useState('') const canEditDates = booking.status === 'confirmed' || booking.status === 'inquiry' + const canRelocate = booking.status === 'checked_in' + + const [relocating, setRelocating] = useState(false) + const [relocateRoomId, setRelocateRoomId] = useState(booking.roomId) + + const freeRoomsForRelocate = (rooms ?? []).filter(r => + r.id !== booking.roomId && + !(allBookings ?? []).some(b => + b.id !== booking.id && + b.roomId === r.id && + b.status !== 'cancelled' && b.status !== 'no_show' && b.status !== 'checked_out' && + b.checkIn < booking.checkOut && b.checkOut > booking.checkIn, + ), + ) + + const handleRelocate = () => { + if (!relocateRoomId || relocateRoomId === booking.roomId) return + onUpdate(booking.id, { roomId: relocateRoomId }) + setRelocating(false) + } const findConflict = (ci: string, co: string, rid: string): Booking | null => (allBookings ?? []).find(b => @@ -405,13 +425,66 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
{room && (
-

Номер

+
+

Номер

+ {canRelocate && !relocating && ( + + )} +

№{room.number} — {room.type}

Этаж {room.floor} · {room.bedType} bed

+ + {/* Форма переселения */} + {relocating && ( +
+

Переселить гостя в другой номер

+ {freeRoomsForRelocate.length > 0 ? ( + <> + +
+ + +
+ + ) : ( +
+ Нет свободных номеров на эти даты + +
+ )} +
+ )}
)} diff --git a/src/components/calendar/BookingCalendar.tsx b/src/components/calendar/BookingCalendar.tsx index 2b3e51c..135f4da 100644 --- a/src/components/calendar/BookingCalendar.tsx +++ b/src/components/calendar/BookingCalendar.tsx @@ -84,6 +84,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook // Drag-to-move booking state const [movingBooking, setMovingBooking] = useState(null) const [moveTargetRoomId, setMoveTargetRoomId] = useState(null) + const [ghostPos, setGhostPos] = useState<{ x: number; y: number } | null>(null) const movingBookingRef = useRef(null) const didDragRef = useRef(false) movingBookingRef.current = movingBooking @@ -486,9 +487,11 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook dragging = true setMovingBooking(booking) setMoveTargetRoomId(booking.roomId) + document.body.style.cursor = 'grabbing' } return } + setGhostPos({ x: ev.clientX, y: ev.clientY }) const el = document.elementFromPoint(ev.clientX, ev.clientY) const row = el?.closest('[data-room-id]') as HTMLElement | null if (row?.dataset.roomId) setMoveTargetRoomId(row.dataset.roomId) @@ -496,6 +499,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook const onUp = (ev: MouseEvent) => { window.removeEventListener('mousemove', onMove) window.removeEventListener('mouseup', onUp) + document.body.style.cursor = '' if (dragging) { const el = document.elementFromPoint(ev.clientX, ev.clientY) const row = el?.closest('[data-room-id]') as HTMLElement | null @@ -506,6 +510,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook } setMovingBooking(null) setMoveTargetRoomId(null) + setGhostPos(null) } } window.addEventListener('mousemove', onMove) @@ -741,6 +746,19 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook /> )} + {/* Drag ghost element */} + {movingBooking && ghostPos && ( +
+ {movingBooking.guestName} +
+ )} + {/* Rental booking modal */} {rentalModal && (