feat: hourly pricing + category base prices in booking modal + availability grid

- Add hourly_price column to rate_overrides, base_price/allow_hourly/hourly_base_price to room_categories (migration 033)
- Update categories and rate-overrides backend routes to handle new fields
- AvailabilityPage: hourly/nightly mode toggle, period prices auto-applied to grid with 'П' indicator, confirmation dialog when overriding period prices
- BookingModal: nightly/hourly toggle at top, pricing hierarchy (override → category base → room rate)
- RoomCategoriesPage: base_price/allow_hourly/hourly_base_price fields in category form; inline rooms panel
- CalendarPage + BookingCalendar: pass hourlyPriceOverrides and categoryBasePrices down to BookingModal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 00:05:14 +03:00
parent b3d143f1dd
commit 588ecb0f2a
11 changed files with 232 additions and 38 deletions

View File

@@ -25,6 +25,8 @@ export function CalendarPage() {
const [rentalBookings, setRentalBookings] = useState<RentalBookingApi[]>([])
const [locks, setLocks] = useState<Map<string, BookingLock>>(new Map())
const [priceOverrides, setPriceOverrides] = useState<Record<string, Record<string, number>>>({})
const [hourlyPriceOverrides, setHourlyPriceOverrides] = useState<Record<string, Record<string, number>>>({})
const [categoryBasePrices, setCategoryBasePrices] = useState<Record<string, { nightlyBase: number; hourlyBase: number }>>({})
// roomId → assigneeName for "убирается" tooltip
const [cleaningAssignees, setCleaningAssignees] = useState<Record<string, string>>({})
// Set of room IDs with active maintenance tasks
@@ -36,21 +38,36 @@ export function CalendarPage() {
api.rooms.list(slug),
api.bookings.list(slug),
api.rateOverrides.list(slug).catch(() => []),
api.categories.list(slug).catch(() => []),
]
if (isRentalActive) {
fetches.push(api.rental.listObjects(slug))
fetches.push(api.rental.listBookings(slug))
}
Promise.all(fetches).then(([r, b, overrides, ro, rb]) => {
Promise.all(fetches).then(([r, b, overridesRaw, catsRaw, ro, rb]) => {
setRooms(r as Room[])
setBookings(b as Booking[])
// Build categoryId → date → price lookup
const overrides = overridesRaw as import('../lib/api').RateOverrideApi[]
const map: Record<string, Record<string, number>> = {}
for (const o of overrides as import('../lib/api').RateOverrideApi[]) {
const hourlyMap: Record<string, Record<string, number>> = {}
for (const o of overrides) {
if (!map[o.categoryId]) map[o.categoryId] = {}
map[o.categoryId][o.date] = o.price
if (o.hourlyPrice != null && o.hourlyPrice > 0) {
if (!hourlyMap[o.categoryId]) hourlyMap[o.categoryId] = {}
hourlyMap[o.categoryId][o.date] = o.hourlyPrice
}
}
setPriceOverrides(map)
setHourlyPriceOverrides(hourlyMap)
// Build category base prices
const cats = catsRaw as import('../lib/api').CategoryApi[]
const catBases: Record<string, { nightlyBase: number; hourlyBase: number }> = {}
for (const cat of cats) {
catBases[cat.id] = { nightlyBase: cat.base_price ?? 0, hourlyBase: cat.hourly_base_price ?? 0 }
}
setCategoryBasePrices(catBases)
if (isRentalActive) {
setRentalObjects(ro as RentalObjectApi[])
setRentalBookings(rb as RentalBookingApi[])
@@ -296,6 +313,8 @@ export function CalendarPage() {
onDraftCancel={handleDraftCancel}
wsConnected={connected}
priceOverrides={priceOverrides}
hourlyPriceOverrides={hourlyPriceOverrides}
categoryBasePrices={categoryBasePrices}
/>
</div>
</div>