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

View File

@@ -0,0 +1,63 @@
import { CalendarCheck2, Code2, CreditCard, Globe } from 'lucide-react'
export function BookingWidgetPage() {
const snippet = `<script src="https://widget.hotelsync.ru/v1/embed.js"
data-hotel="grand-palace"
data-lang="ru">
</script>`
return (
<div className="p-6 max-w-4xl mx-auto space-y-6">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-indigo-600 flex items-center justify-center shrink-0">
<CalendarCheck2 size={20} className="text-white" />
</div>
<div>
<h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">Онлайн-бронирование</h1>
<p className="text-sm text-slate-500 dark:text-slate-400">Виджет прямых продаж для сайта отеля</p>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-3">
{[
{ label: 'Бронирований через виджет', value: '—', sub: 'нет данных' },
{ label: 'Конверсия', value: '—', sub: 'нет данных' },
{ label: 'Прямая выручка', value: '—', sub: 'нет данных' },
].map(s => (
<div key={s.label} className="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-4 text-center">
<p className="text-2xl font-bold text-slate-400">{s.value}</p>
<p className="text-xs text-slate-500 dark:text-slate-400 mt-1 leading-tight">{s.label}</p>
</div>
))}
</div>
{/* Embed code */}
<div className="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-5 space-y-3">
<div className="flex items-center gap-2">
<Code2 size={16} className="text-indigo-500" />
<h3 className="font-semibold text-slate-900 dark:text-slate-100 text-sm">Код для вставки на сайт</h3>
</div>
<pre className="bg-slate-900 text-emerald-400 text-xs rounded-lg p-4 overflow-x-auto font-mono leading-relaxed">
{snippet}
</pre>
<button className="btn-secondary text-sm">Скопировать код</button>
</div>
{/* Features */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{[
{ icon: CalendarCheck2, title: 'Мгновенное подтверждение', desc: 'Email + SMS гостю' },
{ icon: CreditCard, title: 'Онлайн-оплата', desc: 'Тинькофф, СБП, ЮKassa' },
{ icon: Globe, title: 'Без комиссии OTA', desc: 'Только платёжная комиссия' },
].map(f => (
<div key={f.title} className="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-4 text-center">
<f.icon size={22} className="text-indigo-500 mx-auto mb-2" />
<p className="font-medium text-slate-900 dark:text-slate-100 text-sm">{f.title}</p>
<p className="text-xs text-slate-400 mt-0.5">{f.desc}</p>
</div>
))}
</div>
</div>
)
}