diff --git a/src/components/bookings/BookingModal.tsx b/src/components/bookings/BookingModal.tsx
index 7f3e447..9fdd762 100644
--- a/src/components/bookings/BookingModal.tsx
+++ b/src/components/bookings/BookingModal.tsx
@@ -51,6 +51,17 @@ function availableRooms(rooms: Room[], bookings: Booking[], checkIn: string, che
return rooms.filter(r => !isConflict(bookings, r.id, checkIn, checkOut, excludeId))
}
+// Возвращает уникальные категории (типы) номеров с количеством свободных
+function getRoomCategories(rooms: Room[], bookings: Booking[], checkIn: string, checkOut: string, excludeId?: string) {
+ const types = Array.from(new Set(rooms.map(r => r.type)))
+ return types.map(type => {
+ const roomsOfType = rooms.filter(r => r.type === type)
+ const freeRooms = roomsOfType.filter(r => !isConflict(bookings, r.id, checkIn, checkOut, excludeId))
+ const minRate = Math.min(...roomsOfType.map(r => r.baseRate))
+ return { type, total: roomsOfType.length, free: freeRooms.length, minRate, rooms: freeRooms, allRooms: roomsOfType }
+ })
+}
+
export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSave, existing }: BookingModalProps) {
const { statuses } = useModules()
const { user } = useAuth()
@@ -80,8 +91,16 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
}
const _nameParts = (existing?.guestName ?? '').split(' ')
+
+ // Определяем начальную категорию из существующего бронирования или первого доступного номера
+ const _initRoomId = existing?.roomId ?? draft.roomId
+ const _initCategory = rooms.find(r => r.id === _initRoomId)?.type ?? rooms[0]?.type ?? ''
+
+ const [selectedCategory, setSelectedCategory] = useState(_initCategory)
+ const [pickSpecificRoom, setPickSpecificRoom] = useState(!!existing?.roomId)
+
const [form, setForm] = useState({
- roomId: existing?.roomId ?? draft.roomId,
+ roomId: _initRoomId,
lastName: _nameParts[0] ?? '',
firstName: _nameParts[1] ?? '',
middleName: _nameParts.slice(2).join(' '),
@@ -115,6 +134,34 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
const EXTRA_BED_PRICE = 1500
const room = rooms.find(r => r.id === form.roomId)
+
+ // Категории и доступность
+ const categories = getRoomCategories(rooms, bookings, form.checkIn, form.checkOut, existing?.id)
+ const currentCategory = categories.find(c => c.type === selectedCategory)
+ const categoryAvailableRooms = currentCategory
+ ? (pickSpecificRoom ? currentCategory.rooms : currentCategory.allRooms)
+ : []
+
+ // При смене категории или дат (если не выбран конкретный номер) — авто-выбираем первый свободный
+ const handleCategorySelect = (type: string) => {
+ setSelectedCategory(type)
+ if (!pickSpecificRoom) {
+ const cat = getRoomCategories(rooms, bookings, form.checkIn, form.checkOut, existing?.id).find(c => c.type === type)
+ const first = cat?.rooms[0] ?? cat?.allRooms[0]
+ if (first) set('roomId', first.id)
+ }
+ }
+
+ const handlePickSpecificToggle = (on: boolean) => {
+ setPickSpecificRoom(on)
+ if (!on) {
+ // Возвращаемся к первому свободному в категории
+ const cat = getRoomCategories(rooms, bookings, form.checkIn, form.checkOut, existing?.id).find(c => c.type === selectedCategory)
+ const first = cat?.rooms[0] ?? cat?.allRooms[0]
+ if (first) set('roomId', first.id)
+ }
+ }
+
const nights = form.checkIn && form.checkOut
? Math.max(0, (new Date(form.checkOut).getTime() - new Date(form.checkIn).getTime()) / 86400000)
: 0
@@ -214,263 +261,266 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
{/* ── LEFT COLUMN ── */}
-
- {/* Room */}
+
+
+ {/* 1. Даты заезда / выезда */}
-
-
-
-
-
- {/* 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 => (
-
- ))}
-
- ) : (
-
Нет свободных номеров на эти даты.
- )}
-
- )
- })()
- }
-
-
- {/* ФИО гостя */}
-
- {/* Телефон + Email */}
-
-
- {/* Hourly / Daily toggle */}
- {showHourlyTab && (
-
-
-
-
- )}
-
- {/* Dates */}
- {isHourly && room?.allowHourly ? (
-
-
-
+
+ {isHourly && room?.allowHourly ? (
+
setHourlyDate(e.target.value)}
/>
+
+
+
+
+
+
+
+
+
+
+ ) : (
-
- ) : (
-
- )}
+ )}
+
- {/* Adults + Children + Status */}
+ {/* 2. Категория номера */}
+
+
+
+
+
+
+ {categories.map(cat => {
+ const isSelected = selectedCategory === cat.type
+ const hasAvailable = cat.free > 0
+ const datesSet = !!(form.checkIn && form.checkOut && form.checkIn < form.checkOut)
+ return (
+
+ )
+ })}
+
+
+ {/* Переключатель конкретного номера */}
+ {selectedCategory && (
+
+
+
+ )}
+
+ {/* Выпадающий список конкретных номеров */}
+ {pickSpecificRoom && selectedCategory && (
+
+ {categoryAvailableRooms.length > 0 ? (
+
+ ) : (
+
+
+ Нет свободных номеров в этой категории на выбранные даты
+
+ )}
+
+ )}
+
+ {/* Почасово / посуточно — только если у выбранного номера есть почасовая ставка */}
+ {showHourlyTab && (
+
+
+
+
+ )}
+
+
+ {/* 3. ФИО гостя */}
+
+
+ {/* 4. Взрослые / Дети / Статус */}
)}
- {/* Extra beds */}
-
+ {/* Доп. места */}
+
@@ -541,22 +587,18 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
+ >
{extraBeds}
+ >
- {/* Guest tags */}
+ {/* Статус гостя (теги) */}