Add Availability Calendar, Modules system, and route refactoring

- Add AvailabilityPage with price grid (categories × dates), drag-select,
  channel prices, EditPanel, PeriodModal, and rate periods tab
- Add ModulesContext with localStorage persistence and dynamic sidebar items
- Add ModulesPage with WiFi Auth, Payments, TV Welcome, Smart Locks,
  OLAP Reports, Website Builder, Booking Widget modules
- Add ReportsPage (OLAP analytics with KPI cards, charts, tables)
- Add WebsitePage and BookingWidgetPage placeholders
- Remove hotel slug from URL routing; hotel context from JWT
- Add Доступность nav item in Sidebar under Управление
- Fix LoginPage redirects and HotelSync branding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 14:43:18 +03:00
parent 63c9ae574f
commit b277e3e38d
14 changed files with 1869 additions and 78 deletions

117
src/data/ratesData.ts Normal file
View File

@@ -0,0 +1,117 @@
import { addDays, format, getDay } from 'date-fns'
export interface RoomCategory {
id: string
name: string
color: string // tailwind bg class
textColor: string // tailwind text class
basePrice: number
extraPersonPrice: number
maxPersons: number
}
export interface ChannelRate {
channelId: string
price: number // цена для этого канала
}
export interface PriceCell {
price: number
extraPerson: number
minNights: number
channelPrices: Record<string, number> // channelId -> price
closed: boolean // закрыто для продажи
}
export interface RatePeriod {
id: string
name: string
startDate: string
endDate: string
notes?: string
categoryPrices: Record<string, number> // categoryId -> price
channelMarkup: Record<string, number> // channelId -> multiplier (1.15 = +15%)
extraPersonPrice: number
minNights: number
daysOfWeek?: number[] // если задано — применяется только для этих дней (0=вс,1=пн...)
}
export const RATE_CHANNELS = [
{ id: 'direct', name: 'Прямые', icon: '🏨', color: 'text-brand-600' },
{ id: 'booking_com', name: 'Booking.com', icon: '🔵', color: 'text-blue-600' },
{ id: 'airbnb', name: 'Airbnb', icon: '🔴', color: 'text-rose-600' },
{ id: 'expedia', name: 'Expedia', icon: '🟡', color: 'text-amber-600' },
]
export const ROOM_CATEGORIES: RoomCategory[] = [
{ id: 'standard', name: 'Стандарт', color: 'bg-slate-500', textColor: 'text-slate-600', basePrice: 3500, extraPersonPrice: 800, maxPersons: 2 },
{ id: 'deluxe', name: 'Делюкс', color: 'bg-brand-500', textColor: 'text-brand-600', basePrice: 5200, extraPersonPrice: 1000, maxPersons: 3 },
{ id: 'suite', name: 'Сюит', color: 'bg-violet-500', textColor: 'text-violet-600', basePrice: 8900, extraPersonPrice: 1500, maxPersons: 4 },
{ id: 'junior_suite', name: 'Джуниор Сюит', color: 'bg-cyan-500', textColor: 'text-cyan-600', basePrice: 6800, extraPersonPrice: 1200, maxPersons: 4 },
{ id: 'penthouse', name: 'Пентхаус', color: 'bg-amber-500', textColor: 'text-amber-600', basePrice: 18000, extraPersonPrice: 2500, maxPersons: 6 },
]
// Дефолтные мультипликаторы каналов (markup к базовой цене)
export const DEFAULT_CHANNEL_MARKUP: Record<string, number> = {
direct: 1.00,
booking_com: 1.00, // net price — Booking забирает свою комиссию сверху
airbnb: 1.05,
expedia: 1.03,
}
// Генерируем начальную сетку цен на 60 дней
export function buildInitialPriceGrid(): Record<string, Record<string, PriceCell>> {
const result: Record<string, Record<string, PriceCell>> = {}
const today = new Date()
for (const cat of ROOM_CATEGORIES) {
result[cat.id] = {}
for (let i = 0; i < 60; i++) {
const d = addDays(today, i)
const dateStr = format(d, 'yyyy-MM-dd')
const dow = getDay(d)
const isWeekend = dow === 5 || dow === 6
const basePrice = Math.round(cat.basePrice * (isWeekend ? 1.25 : 1.0))
const channelPrices: Record<string, number> = {}
for (const ch of RATE_CHANNELS) {
channelPrices[ch.id] = Math.round(basePrice * DEFAULT_CHANNEL_MARKUP[ch.id])
}
result[cat.id][dateStr] = {
price: basePrice,
extraPerson: cat.extraPersonPrice,
minNights: 1,
channelPrices,
closed: false,
}
}
}
return result
}
// Демо-периоды
export const DEMO_PERIODS: RatePeriod[] = [
{
id: 'p1',
name: 'Праздники 8 марта',
startDate: '2026-03-06',
endDate: '2026-03-10',
categoryPrices: { standard: 4500, deluxe: 6500, suite: 11000, junior_suite: 8500, penthouse: 22000 },
channelMarkup: { direct: 1.0, booking_com: 1.0, airbnb: 1.05, expedia: 1.03 },
extraPersonPrice: 1200,
minNights: 2,
notes: 'Повышенный спрос на праздники',
},
{
id: 'p2',
name: 'Майские праздники',
startDate: '2026-04-30',
endDate: '2026-05-10',
categoryPrices: { standard: 5000, deluxe: 7200, suite: 12500, junior_suite: 9800, penthouse: 25000 },
channelMarkup: { direct: 1.0, booking_com: 1.0, airbnb: 1.08, expedia: 1.05 },
extraPersonPrice: 1500,
minNights: 3,
notes: 'Минимальный заезд 3 ночи',
},
]