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

74
backend/src/app.ts Normal file
View File

@@ -0,0 +1,74 @@
import Fastify, { FastifyRequest, FastifyReply } from 'fastify'
import jwt from '@fastify/jwt'
import cookie from '@fastify/cookie'
import cors from '@fastify/cors'
import helmet from '@fastify/helmet'
import rateLimit from '@fastify/rate-limit'
import { config } from './config'
import './types' // side-effect: augments fastify types
import authRoutes from './routes/auth'
import hotelsRoutes from './routes/hotels'
import roomsRoutes from './routes/rooms'
import bookingsRoutes from './routes/bookings'
import housekeepingRoutes from './routes/housekeeping'
import channelsRoutes from './routes/channels'
import usersRoutes from './routes/users'
export async function buildApp() {
const fastify = Fastify({
logger: {
level: config.nodeEnv === 'production' ? 'warn' : 'info',
...(config.nodeEnv !== 'production' && {
transport: { target: 'pino-pretty', options: { colorize: true } },
}),
},
})
// ── Security ───────────────────────────────────────────────────────────────
await fastify.register(helmet, { contentSecurityPolicy: false })
await fastify.register(cors, {
origin: config.cors.origins,
credentials: true,
methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
})
await fastify.register(rateLimit, {
max: 200,
timeWindow: '1 minute',
})
// ── Cookies & JWT ──────────────────────────────────────────────────────────
await fastify.register(cookie)
await fastify.register(jwt, {
secret: config.jwt.secret,
})
// Authenticate decorator used as onRequest hook in routes
fastify.decorate(
'authenticate',
async (request: FastifyRequest, reply: FastifyReply) => {
try {
await request.jwtVerify()
} catch (err) {
reply.code(401).send({ error: 'Unauthorized' })
}
},
)
// ── Health check ───────────────────────────────────────────────────────────
fastify.get('/health', async () => ({ status: 'ok', ts: new Date().toISOString() }))
// ── Routes ─────────────────────────────────────────────────────────────────
await fastify.register(authRoutes)
await fastify.register(hotelsRoutes)
await fastify.register(roomsRoutes)
await fastify.register(bookingsRoutes)
await fastify.register(housekeepingRoutes)
await fastify.register(channelsRoutes)
await fastify.register(usersRoutes)
return fastify
}