feat: calendar hover price tooltip + AvailabilityPage API integration

- Calendar cells: hold mouse 600ms to show price tooltip for that date/room (baseRate + hourly rate if enabled)
- AvailabilityPage: load real categories from API, derive basePrice from room baseRates, fall back to room types if no categories
- Rate periods: persisted to DB via new /api/hotels/:slug/rate-periods endpoint
- New backend migration 021_rate_periods.sql + rate-periods route
- Added api.ratePeriods.{list,create,update,delete} to frontend API client

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 15:05:12 +03:00
parent 63da3d21cc
commit c3a30be74e
6 changed files with 411 additions and 45 deletions

View File

@@ -395,6 +395,21 @@ export const api = {
req<void>('DELETE', `/api/hotels/${slug}/tariffs/${id}`),
},
// ── Rate Periods ─────────────────────────────────────────────────────────
ratePeriods: {
list: (slug: string) =>
req<RatePeriodApi[]>('GET', `/api/hotels/${slug}/rate-periods`),
create: (slug: string, data: RatePeriodPayload) =>
req<RatePeriodApi>('POST', `/api/hotels/${slug}/rate-periods`, data),
update: (slug: string, id: string, data: Partial<RatePeriodPayload>) =>
req<RatePeriodApi>('PATCH', `/api/hotels/${slug}/rate-periods/${id}`, data),
delete: (slug: string, id: string) =>
req<void>('DELETE', `/api/hotels/${slug}/rate-periods/${id}`),
},
// ── NetUP IPTV ────────────────────────────────────────────────────────────
netup: {
getSettings: (slug: string) =>
@@ -718,6 +733,31 @@ export interface TariffPayload {
is_active?: boolean
}
export interface RatePeriodApi {
id: string
name: string
startDate: string
endDate: string
notes: string | null
categoryPrices: Record<string, number>
channelMarkup: Record<string, number>
extraPersonPrice: number
minNights: number
daysOfWeek: number[] | null
}
export interface RatePeriodPayload {
name: string
start_date: string
end_date: string
notes?: string
category_prices?: Record<string, number>
channel_markup?: Record<string, number>
extra_person_price?: number
min_nights?: number
days_of_week?: number[] | null
}
function toHotelPayload(h: HotelPayload): Record<string, unknown> {
const out: Record<string, unknown> = {}
if (h.name !== undefined) out.name = h.name