feat: 3 improvements — hourly toggle position, category rooms panel, period prices in grid
1. BookingModal: move Посуточно/Почасово toggle to top (before dates)
when room has hourly rate enabled
2. RoomCategoriesPage: Номера > button now expands inline panel
showing rooms belonging to that category
3. AvailabilityPage:
- Period prices auto-applied to grid (base → periods → overrides),
cells from periods show violet П badge
- П legend item added
- Hourly rate settings shown in Periods tab for hourly-enabled rooms
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import { Plus, Pencil, Trash2, ImagePlus, X as XIcon, Tag, ChevronRight, ChevronDown, ChevronUp, Check, Loader2 } from 'lucide-react'
|
||||
import { Plus, Pencil, Trash2, ImagePlus, X as XIcon, Tag, ChevronRight, ChevronDown, ChevronUp, Check, Loader2, DoorOpen } from 'lucide-react'
|
||||
import { cn } from '../lib/utils'
|
||||
import type { RoomCategory } from '../types'
|
||||
import type { RoomCategory, Room } from '../types'
|
||||
import { useAmenities } from '../contexts/AmenitiesContext'
|
||||
import { useBedTypes } from '../contexts/BedTypesContext'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
@@ -353,6 +353,8 @@ export function RoomCategoriesPage() {
|
||||
|
||||
const [categories, setCategories] = useState<RoomCategory[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [rooms, setRooms] = useState<Room[]>([])
|
||||
const [catRoomsId, setCatRoomsId] = useState<string | null>(null)
|
||||
const [editingCat, setEditingCat] = useState<RoomCategory | undefined>(undefined)
|
||||
const [formOpen, setFormOpen] = useState(false)
|
||||
|
||||
@@ -376,9 +378,13 @@ export function RoomCategoriesPage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!slug) return
|
||||
api.categories.list(slug)
|
||||
.then(rows => setCategories(rows.map(fromApi)))
|
||||
.catch(console.error)
|
||||
Promise.all([
|
||||
api.categories.list(slug),
|
||||
api.rooms.list(slug).catch(() => []),
|
||||
]).then(([rows, roomRows]) => {
|
||||
setCategories(rows.map(fromApi))
|
||||
setRooms(roomRows)
|
||||
}).catch(console.error)
|
||||
.finally(() => setLoading(false))
|
||||
}, [slug])
|
||||
|
||||
@@ -499,14 +505,44 @@ export function RoomCategoriesPage() {
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => openEdit(cat)}
|
||||
className="flex items-center gap-1 px-2.5 py-1.5 rounded-lg bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 text-xs font-medium hover:bg-slate-200 dark:hover:bg-slate-600 transition-colors whitespace-nowrap"
|
||||
onClick={() => setCatRoomsId(catRoomsId === cat.id ? null : cat.id)}
|
||||
className={cn(
|
||||
'flex items-center gap-1 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-colors whitespace-nowrap',
|
||||
catRoomsId === cat.id
|
||||
? 'bg-brand-100 dark:bg-brand-900/30 text-brand-700 dark:text-brand-300'
|
||||
: 'bg-slate-100 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-200 dark:hover:bg-slate-600',
|
||||
)}
|
||||
>
|
||||
<span className="hidden sm:inline">Номера</span>
|
||||
<ChevronRight size={12} />
|
||||
<ChevronRight size={12} className={cn('transition-transform', catRoomsId === cat.id && 'rotate-90')} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rooms list panel */}
|
||||
{catRoomsId === cat.id && (() => {
|
||||
const catRooms = rooms.filter(r => r.categoryId === cat.id)
|
||||
return (
|
||||
<div className="mt-4 pt-4 border-t border-slate-200 dark:border-slate-700">
|
||||
{catRooms.length === 0 ? (
|
||||
<p className="text-sm text-slate-400 flex items-center gap-2">
|
||||
<DoorOpen size={15} />
|
||||
Нет номеров в этой категории
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2">
|
||||
{catRooms.sort((a, b) => a.sortOrder - b.sortOrder).map(r => (
|
||||
<div key={r.id} className="flex items-center gap-2 px-3 py-2 rounded-lg bg-slate-50 dark:bg-slate-700/50 text-sm">
|
||||
<DoorOpen size={13} className="text-slate-400 shrink-0" />
|
||||
<span className="font-medium text-slate-700 dark:text-slate-200">№{r.number}</span>
|
||||
<span className="text-slate-400 text-xs truncate">{r.baseRate.toLocaleString('ru-RU')} ₽</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user