Major UX improvements across multiple pages

Шахматка:
- Date/period picker dropdown on navigation button (choose start date + days window)
- Cancelled bookings fade out with animation after 1 second

Бронирования:
- Clickable column headers with sort asc/desc (Гость, Заезд, Выезд, Статус, Источник, Сумма)

Страница входа:
- Removed role-based account selector — just email + password
- System auto-detects role/hotel from credentials

Настройки:
- New "Бронирование" section with room assignment strategy (spread/together/sequential/manual)
- Notifications: SMTP email config + SMS provider config (SMSC, SMS.ru, МТС, etc.)

Модули:
- Added Housekeeping and Channel Manager as proper modules
- Channel Manager lists: Booking.com, Airbnb, Яндекс Путешествия, Островок, Суточно.ру, OneTwoTrip
- Housekeeping visible to all roles (including housekeeper) via module status
- Sidebar now uses module status to show/hide Уборка and Каналы

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 15:20:05 +03:00
parent 4e615359eb
commit c233590c4b
29 changed files with 2171 additions and 149 deletions

View File

@@ -0,0 +1,109 @@
-- HotelSync Database Schema
-- Migration 001 — Initial Schema
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ── Hotels ─────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS hotels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
plan VARCHAR(20) NOT NULL DEFAULT 'starter'
CHECK (plan IN ('starter', 'pro', 'enterprise')),
timezone VARCHAR(50) NOT NULL DEFAULT 'Europe/Moscow',
currency VARCHAR(3) NOT NULL DEFAULT 'RUB',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Users ──────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
hotel_id UUID REFERENCES hotels(id) ON DELETE SET NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'manager'
CHECK (role IN ('manager', 'housekeeper', 'super_admin')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Rooms ──────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS rooms (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
number VARCHAR(20) NOT NULL,
type VARCHAR(50) NOT NULL,
floor INTEGER NOT NULL DEFAULT 1,
capacity INTEGER NOT NULL DEFAULT 2,
price_per_night DECIMAL(10,2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'clean'
CHECK (status IN ('clean', 'dirty', 'maintenance', 'out_of_order')),
amenities TEXT[] NOT NULL DEFAULT '{}',
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(hotel_id, number)
);
-- ── Bookings ───────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS bookings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
room_id UUID NOT NULL REFERENCES rooms(id),
guest_name VARCHAR(255) NOT NULL,
guest_email VARCHAR(255),
guest_phone VARCHAR(50),
check_in DATE NOT NULL,
check_out DATE NOT NULL,
adults INTEGER NOT NULL DEFAULT 1,
children INTEGER NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'confirmed'
CHECK (status IN ('confirmed', 'checked_in', 'checked_out', 'cancelled', 'no_show')),
source VARCHAR(20) NOT NULL DEFAULT 'direct'
CHECK (source IN ('direct', 'booking_com', 'airbnb', 'expedia', 'vrbo')),
total_amount DECIMAL(10,2),
notes TEXT,
external_id VARCHAR(255),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Housekeeping Tasks ─────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS housekeeping_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
room_id UUID REFERENCES rooms(id) ON DELETE SET NULL,
type VARCHAR(30) NOT NULL DEFAULT 'cleaning'
CHECK (type IN ('cleaning', 'turnover', 'inspection', 'maintenance', 'amenities')),
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'in_progress', 'done', 'skipped')),
priority VARCHAR(10) NOT NULL DEFAULT 'medium'
CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
assignee_id UUID REFERENCES users(id) ON DELETE SET NULL,
notes TEXT,
due_date DATE,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ── Channels ───────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS channels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
hotel_id UUID NOT NULL REFERENCES hotels(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL
CHECK (name IN ('booking_com', 'airbnb', 'expedia', 'vrbo')),
enabled BOOLEAN NOT NULL DEFAULT false,
api_key_encrypted TEXT,
hotel_external_id VARCHAR(255),
last_synced_at TIMESTAMPTZ,
UNIQUE(hotel_id, name)
);
-- ── Indexes ────────────────────────────────────────────────────────────────
CREATE INDEX IF NOT EXISTS idx_bookings_hotel_dates ON bookings(hotel_id, check_in, check_out);
CREATE INDEX IF NOT EXISTS idx_bookings_room ON bookings(room_id);
CREATE INDEX IF NOT EXISTS idx_rooms_hotel ON rooms(hotel_id);
CREATE INDEX IF NOT EXISTS idx_tasks_hotel ON housekeeping_tasks(hotel_id);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_hotel ON users(hotel_id);