diff --git a/src/components/rooms/RoomModal.tsx b/src/components/rooms/RoomModal.tsx index e37fa5d..52831c4 100644 --- a/src/components/rooms/RoomModal.tsx +++ b/src/components/rooms/RoomModal.tsx @@ -1,8 +1,8 @@ import { useState, useRef } from 'react' import { Modal } from '../ui/Modal' import { cn } from '../../lib/utils' -import type { Room, RoomStatus, HousekeepingStatus, BedType } from '../../types' -import { ImagePlus, X as XIcon, ChevronLeft, ChevronRight } from 'lucide-react' +import type { Room, RoomStatus, HousekeepingStatus, BedType, BedItem, BedItemType, ExtraPlace, ChildPolicy } from '../../types' +import { ImagePlus, X as XIcon, ChevronLeft, ChevronRight, Plus, Minus, Baby } from 'lucide-react' import { useAmenities } from '../../contexts/AmenitiesContext' const ROOM_TYPES = ['Стандарт', 'Делюкс', 'Полулюкс', 'Люкс', 'Пентхаус', 'Апартаменты', 'Другой'] @@ -29,8 +29,19 @@ const HK_STATUSES: { value: HousekeepingStatus; label: string }[] = [ { value: 'inspect', label: 'Проверка' }, ] +const BED_ITEM_TYPES: { value: BedItemType; label: string; icon: string }[] = [ + { value: 'single', label: 'Кровать 1-сп.', icon: '🛏' }, + { value: 'double', label: 'Кровать 2-сп.', icon: '🛏' }, + { value: 'queen', label: 'Queen-кровать', icon: '🛏' }, + { value: 'king', label: 'King-кровать', icon: '🛏' }, + { value: 'twin', label: 'Две кровати', icon: '🛏' }, + { value: 'sofa', label: 'Диван', icon: '🛋' }, + { value: 'bunk', label: 'Двухъярусная', icon: '🛏' }, + { value: 'cot', label: 'Раскладушка', icon: '🪑' }, +] + // ── Tab type ─────────────────────────────────────────────────────────────────── -type ModalTab = 'main' | 'description' | 'photos' +type ModalTab = 'main' | 'places' | 'description' | 'photos' interface RoomModalProps { open: boolean @@ -67,6 +78,22 @@ export function RoomModal({ open, room, categories = [], onClose, onSave }: Room const [photoIdx, setPhotoIdx] = useState(0) const fileInputRef = useRef(null) + // ── Beds constructor ────────────────────────────────────────────────────── + const [beds, setBeds] = useState( + room?.beds ?? [{ type: 'double', count: 1 }] + ) + const [extraPlace, setExtraPlace] = useState( + room?.extraPlace ?? { enabled: false, count: 1, price: 1500 } + ) + const [childPolicy, setChildPolicy] = useState( + room?.childPolicy ?? { enabled: false, freeUnderAge: 12, chargeFromAge: 12 } + ) + + const addBed = () => setBeds(prev => [...prev, { type: 'single', count: 1 }]) + const removeBed = (i: number) => setBeds(prev => prev.filter((_, idx) => idx !== i)) + const updateBed = (i: number, patch: Partial) => + setBeds(prev => prev.map((b, idx) => idx === i ? { ...b, ...patch } : b)) + const set = (k: K, v: typeof form[K]) => setForm(prev => ({ ...prev, [k]: v })) @@ -116,11 +143,15 @@ export function RoomModal({ open, room, categories = [], onClose, onSave }: Room categoryId: form.categoryId || undefined, description: form.description || undefined, photos: photos.length > 0 ? photos : undefined, + beds: beds.length > 0 ? beds : undefined, + extraPlace: extraPlace.enabled ? extraPlace : undefined, + childPolicy: childPolicy.enabled ? childPolicy : undefined, }) } const TABS: { key: ModalTab; label: string }[] = [ { key: 'main', label: 'Основное' }, + { key: 'places', label: 'Места' }, { key: 'description', label: 'Описание' }, { key: 'photos', label: `Фото${photos.length > 0 ? ` (${photos.length})` : ''}` }, ] @@ -285,6 +316,194 @@ export function RoomModal({ open, room, categories = [], onClose, onSave }: Room )} + {/* ── Places tab ── */} + {tab === 'places' && ( +
+ + {/* Beds constructor */} +
+
+

Спальные места

+

Кровати, диваны и другая мебель для сна

+
+
+ {beds.map((bed, i) => ( +
+ + {BED_ITEM_TYPES.find(b => b.value === bed.type)?.icon ?? '🛏'} + + + {/* Count stepper */} +
+ + {bed.count} + +
+ +
+ ))} + +
+
+ + {/* Extra places */} +
+
+
+

Дополнительные места

+

Раскладные кровати, кушетки за доп. плату

+
+ +
+ {extraPlace.enabled && ( +
+
+ +
+ + {extraPlace.count} + +
+
+
+ + setExtraPlace(p => ({ ...p, price: parseInt(e.target.value) || 0 }))} + /> +
+
+ )} +
+ + {/* Child policy */} +
+
+
+ +
+

Политика для детей

+

Возраст бесплатного проживания

+
+
+ +
+ {childPolicy.enabled ? ( +
+
+ 🆓 +
+

Бесплатно до

+
+
+ + + до {childPolicy.freeUnderAge} лет + + +
+
+ +
+ 💰 +
+

Доп. место от

+ {extraPlace.enabled && ( +

{extraPlace.price} ₽/ночь

+ )} + {!extraPlace.enabled && ( +

Включите «Доп. места» и укажите цену

+ )} +
+
+ + + от {childPolicy.chargeFromAge} лет + + +
+
+ +

+ Дети до {childPolicy.freeUnderAge} лет — бесплатно без доп. места. + Дети от {childPolicy.chargeFromAge} лет — оплачивают дополнительное место. +

+
+ ) : ( +
+

Стандартные условия, все гости оплачиваются

+
+ )} +
+ +
+ )} + {/* ── Description tab ── */} {tab === 'description' && (
diff --git a/src/types/index.ts b/src/types/index.ts index 5e51223..d2c986e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -42,6 +42,25 @@ export type RoomStatus = 'available' | 'occupied' | 'maintenance' | 'blocked' export type HousekeepingStatus = 'clean' | 'dirty' | 'cleaning' | 'inspect' export type BedType = 'single' | 'double' | 'queen' | 'king' | 'twin' +export type BedItemType = 'single' | 'double' | 'queen' | 'king' | 'twin' | 'sofa' | 'bunk' | 'cot' + +export interface BedItem { + type: BedItemType + count: number +} + +export interface ExtraPlace { + enabled: boolean + count: number // max extra places available + price: number // price per extra place per night +} + +export interface ChildPolicy { + enabled: boolean + freeUnderAge: number // children strictly under this age stay free + chargeFromAge: number // children this age and above pay for extra place +} + export interface RoomCategory { id: string hotelId: string @@ -71,6 +90,9 @@ export interface Room { description?: string photos?: string[] categoryId?: string + beds?: BedItem[] + extraPlace?: ExtraPlace + childPolicy?: ChildPolicy } export interface DocumentTemplate {