Commit Graph

344 Commits

Author SHA1 Message Date
f5ade5cb53 Fix: reservation_id must be string not number (NetUP API) 2026-03-19 12:34:54 +03:00
c656d0c6ae Log NetUP request/response body; fix reservation_id to be numeric
- reservation_id converted to integer (NetUP likely requires number not UUID string)
- Log requestBody sent to NetUP and responseBody received from NetUP
- Push event rows now expandable: click to see URL, request JSON, NetUP error response
- Same for checkout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:29:45 +03:00
d8f560ae12 Fix TS type: status literal union for PushEvent 2026-03-19 12:23:58 +03:00
44fa9d24f1 Improve NetUP diagnostics: push event log + test check-in button
- notifyNetupCheckin/Checkout now log every attempt (ok/error/skipped)
  with room number, NetUP room, URL, HTTP status, error message
- Push log exposed via GET /netup/log alongside pull request log
- New POST /netup/test-checkin: manually trigger a test check-in from UI
- Diagnostics tab split into two sections:
  - Outgoing (push): table of check-in/check-out events with status dots
  - Incoming (pull): NetUP poll requests (if TravelLine integration works)
- Test button picks first mapped room and fires a real check-in call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:19:48 +03:00
142be49211 Add Diagnostics tab to TV Welcome: view NetUP poll requests in UI
- Rename pull endpoint /travelline → /netup-pms (cleaner branding)
- Add GET/DELETE /api/hotels/:slug/netup/log endpoints
- TvWelcomePage: new 'Диагностика' tab shows incoming NetUP requests
  with method, URL, timestamp list + detail panel (headers, query, body)
- Remove 'TravelLine' wording from UI, replace with 'Pull-интеграция'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 19:08:57 +03:00
8350309645 Add TravelLine pull integration for NetUP: capture endpoint + tl_token
- New /travelline route: accepts all NetUP polls, logs them to in-memory
  ring buffer (GET /travelline/_log to inspect), responds with active
  reservations in a generic PMS format
- Migration 007: adds tl_token column to netup_settings
- PATCH netup/settings now saves tl_token; GET returns it
- TvWelcomePage: new TravelLine section with read-only API URL + copy
  button and token input field

Setup in NetUP: Integration type = TravelLine,
API URL = https://api.hotelsync.ru/travelline, Token = <tl_token>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 18:57:10 +03:00
05d0c94a9a Fix NetUP settings not loading: snake_case → camelCase (transformKeys)
req() auto-converts server_url→serverUrl, default_language→defaultLanguage,
netup_room_number→netupRoomNumber. Fixed both type declarations and component reads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 18:39:01 +03:00
cc5f9c18d3 Fix NetUP API path: /mw/a → /mw/api (actual server URL from spec)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 18:27:55 +03:00
61d25628ae Add NetUP IPTV integration module (TV Welcome settings page)
- DB: migrations/002_netup.sql — netup_settings + netup_room_mapping tables
- Backend: routes/netup.ts — GET/PATCH settings, POST test, GET/POST room
  mappings, POST send TV message; notifyNetupCheckin/Checkout helpers
- Backend: bookings PATCH — fire-and-forget NetUP check-in/out on status change
- Frontend: TvWelcomePage — connection settings tab + room mapping tab
- Frontend: sidebarItem added to tv-welcome module (shows in sidebar menu)
- Frontend: ModulesPage Settings button navigates to /tv-welcome
- Frontend: BookingModal — "Сообщение гостю на TV" panel for existing bookings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 18:18:41 +03:00
bc12f8aabc Fix WS offline on first login: sync token between api.ts and AuthContext
Two root causes fixed:
1. api.ts saveToken() updated only localStorage, not React state — WS kept
   reconnecting with the expired token. Now dispatches hotelsync:token-updated
   event; AuthContext listens and updates session state, triggering WS reconnect
   with the fresh token.
2. AuthContext mount effect could race with login(): if refresh failed while
   login() was concurrently setting a new token, catch() called setSession(null)
   and wiped the fresh session. Fixed with functional setSession updater that
   checks current state before clearing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 17:37:36 +03:00
cbd85c5c7e Clear stale WS locks on reconnect to fix phantom overlays
When WS reconnects after a disconnect, the browser may have missed unlock/booking:created
messages, leaving gray striped overlays stuck forever. Clearing all locks on reconnect
ensures stale state is removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 20:13:10 +03:00
bda0cbb7d5 Improve email deliverability: plain text, contrast fix, Content-Language, List-Unsubscribe
- Add plain text (text:) version to all emails — helps SpamAssassin scoring
- Add lang="ru" to <html> and Content-Language: ru header — signals intentional Cyrillic
- Fix HTML_FONT_LOW_CONTRAST: change #94a3b8 to #475569 / #334155 for note text
- Add List-Unsubscribe header (reduces spam score)
- Refactor: extract baseHtml() helper to avoid duplication

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 20:01:25 +03:00
5e0c388b3d Add nginx config with /ws WebSocket location block
Keeps nginx config in repo so WS upgrade headers are not lost on server changes.
The /ws block must appear before location / in api.hotelsync.ru server block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:58:20 +03:00
684073c4ad Fix booking creation: localStorage token, conflict detection, paid_amount
- Fix api.ts getToken/saveToken to read from localStorage (matching AuthContext) — was reading sessionStorage causing all API requests to fail auth until refresh
- Fix session cleanup on 401 to use localStorage
- Add isConflict/availableRooms helpers to BookingModal; show amber warning when selected room is occupied with clickable free room suggestions
- Pass bookings prop to BookingModal via BookingCalendar
- Add paid_amount to backend POST /bookings INSERT
- Show alert() when booking creation fails so user sees the error
- Pass paidAmount in CalendarPage handleCreate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:48:37 +03:00
e59bc752b0 Add password letter requirement + labeled strength UI + auto-capitalize name fields
- Reject pure-numeric passwords (must contain ≥1 letter) in RegisterForm, ResetPasswordPage, and backend register/reset-password handlers
- Replace bare strength bars with labeled 2×2 grid: ≥8 символов, Содержит букву, Заглавная буква, Цифра
- Auto-capitalize first letter of each word in "Название отеля" and "Контактное лицо" fields (autoCapitalize="words" + JS handler)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:35:54 +03:00
1fd12a6dbb Fix null token in session: refresh on mount if token missing
If localStorage session has user but no token (corrupted/migrated session),
attempt a token refresh via httpOnly cookie. If refresh fails, clear session
and force re-login. Prevents WebSocket 'missing token' skip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:19:19 +03:00
fce15a918e Fix WS debug: console.debug → console.log, log slug/token values
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:11:36 +03:00
e3f37744b0 Improve login UX for unconfirmed email + resend confirmation
- LoginForm: detect 403 separately, show amber warning with email address
- Add 'Resend confirmation email' button (calls POST /api/auth/resend-confirmation)
- Backend: POST /api/auth/resend-confirmation endpoint (generates new token, resends email)
- api.ts: add resendConfirmation method

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:05:55 +03:00
352d22a101 Add WS reconnect logic + connection indicator in calendar
- useHotelSocket: reconnect with exponential backoff (up to 30s), return connected state, log errors to console
- BookingCalendar: show green/gray dot + Online/Offline label in toolbar
- CalendarPage: pass wsConnected prop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:51:32 +03:00
02c3b7452a Fix login page UX + add password reset flow
- Move Field component outside RegisterForm — fixes focus loss on every keystroke
- Remove setError('') at submit start — fixes error "flash" that looked like page reload
- Clear errors onChange instead, so error persists until user starts correcting
- Add 'Forgot password?' link in LoginForm
- Add ForgotPasswordForm (email input, success state)
- Add ResetPasswordPage at /reset-password?token=...
- Backend: POST /api/auth/forgot-password — generates 1h token, sends email
- Backend: POST /api/auth/reset-password — validates token, updates password hash
- Backend: migration 006 — reset_token/reset_token_expires columns
- Backend: sendPasswordResetEmail in email.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:43:05 +03:00
42dc4518e2 Switch session storage from sessionStorage to localStorage
sessionStorage is per-tab — new tabs had no session, so WebSocket
didn't connect and real-time lock/booking sync didn't work.
localStorage shares session across tabs in the same browser.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:29:14 +03:00
dd9a2a3da9 Add auto theme mode (light/dark/auto by time of day)
- ThemeContext: add 'auto' mode that switches light 7:00-22:00, dark otherwise, re-checks every minute
- Topbar: cycle through 3 modes on click, show Monitor icon for auto mode
- Settings → Внешний вид: 3-button grid with split preview for auto

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:22:37 +03:00
7b045b38a6 Fix ws.ts: use SocketStream from @fastify/websocket v8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:16:12 +03:00
d3045194a6 Downgrade @fastify/websocket to v8 (Fastify 4 compatible)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:13:34 +03:00
25b1a96740 Fix TypeScript: add @types/ws and RawData type for WebSocket message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:08:14 +03:00
3506105fc1 Add real-time booking collaboration via WebSocket
- Backend: /ws relay endpoint (@fastify/websocket)
- Frontend: useHotelSocket hook with lock/unlock/booking events
- Calendar: lock overlay with diagonal stripes shows other manager editing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:05:09 +03:00
1d24e5a4c4 Add delete room button in RoomModal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:02:08 +03:00
791d8637f9 Fix migration 005: add phone column to users table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 17:35:16 +03:00
152a8c0463 Add hotel registration with email confirmation (nodemailer, confirm-email endpoint)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 17:30:07 +03:00
c06f4bf553 Fix login spinner stuck on API error (add try/catch in handleSubmit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 17:04:05 +03:00
56f4d997a2 Show API save errors in RoomModal footer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 16:55:08 +03:00
55a34dc0dc Connect ChannelsPage, FloorMapPage, SettingsPage, UsersPage to real API
- ChannelsPage: load from API, toggle/sync call real endpoints; normalize
  enabled→isEnabled, lastSyncedAt→lastSyncAt, add displayName/mappings defaults
- FloorMapPage: load rooms+bookings from API; create booking via API
- SettingsPage: load hotel via GET /api/hotels/:slug; save general section
  via PATCH (name, address, timezone, currency, check_in/out times)
- UsersPage: load users from API; create/update/delete via API;
  map backend User (name/role) → StaffUser (firstName/lastName/StaffRole)
- api.ts: add hotels.get/update, users.delete, channel normalization,
  HotelPayload/toHotelPayload
- types/index.ts: extend Hotel with phone/checkInTime/checkOutTime/optional fields;
  add User.createdAt/updatedAt
- backend/routes/hotels.ts: extend PATCH to allow address/phone/check_in_time/check_out_time
- backend/migrations/004_hotels_contacts.sql: add address/phone/check_in/out_time to hotels

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:21:45 +03:00
57162c55c6 Fix calendar rental objects label: compact price, fixed row height
- Abbreviate price to ₽/д (fits in label width without wrapping)
- Rental rows always use normal height (56px min) regardless of compact mode
- Whitespace-nowrap on price line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 14:05:35 +03:00
40e4c04f44 Add API rebuild to deploy workflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:59:29 +03:00
cc1ce4d7f6 Fix build errors: mockData priority 'normal'→'medium', add vite/client types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:55:02 +03:00
728b20417a Connect frontend to real API — rooms, bookings, housekeeping, calendar
- src/lib/api.ts: central API client with JWT auto-refresh, snake_case→camelCase transform
- src/contexts/AuthContext.tsx: real login via POST /api/auth/login
- src/pages/RoomsPage.tsx: load rooms from API, create/update via API
- src/pages/BookingsPage.tsx: load bookings + rooms from API
- src/pages/HousekeepingPage.tsx: load today's tasks from API, update status via API
- src/pages/CalendarPage.tsx: load rooms + bookings from API
- src/types/index.ts: fix HousekeepingTask.priority to match DB (medium/urgent)
- backend/src/routes/rooms.ts: update to use new column names (max_guests, base_rate) + all new fields
- backend/src/routes/bookings.ts: update price_per_night→base_rate, add paid_amount to PATCH
- backend/migrations/003_fix_constraints.sql: fix rooms.status values, add paid_amount, inquiry status, other source
- public/robots.txt + index.html: noindex for SPA inner pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:49:30 +03:00
17dbf629a2 Add docs, robots.txt and fix migration syntax
- backend/README.md: full API and DB documentation
- public/robots.txt: block all pages except root for SEO
- index.html: add noindex meta tag for inner SPA pages
- 002_extend.sql: fix RENAME COLUMN syntax (remove unsupported IF EXISTS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:33:27 +03:00
ef1603d896 Add migration 002_extend.sql — extended database schema
Covers all entities present in the UI:
- Hotels: additional fields (address, phone, check_in/out times, is_active)
- room_categories table
- rooms: beds JSONB, extra_place, child_policy, housekeeping_status, allow_hourly; rename capacity→max_guests, price_per_night→base_rate
- guests table with loyalty points/tiers
- bookings: guest_id, payment_status, meal_plan, timestamps
- tariffs, discounts, loyalty_rules, loyalty_tiers
- reviews, maintenance_tasks
- pos_categories, pos_items, pos_orders
- document_templates, hotel_settings (key-value JSONB)
- All necessary indexes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:28:54 +03:00
a8d181eb18 Add beds constructor, extra places and child policy to room modal
- New types: BedItem, ExtraPlace, ChildPolicy on Room interface
- New 'Места' tab in RoomModal with 3 sections:
  · Beds constructor: add/remove/count any combo of bed types (1-sp, 2-sp,
    queen, king, twin, sofa, bunk, cot) with emoji icons and ±1 steppers
  · Extra places: toggle + count (max 5) + price per night
  · Child policy: toggle + free-under-age stepper + charge-from-age stepper
    with green/amber visual cards and summary text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:22:05 +03:00
93e9202149 Fix white hover highlight on room label in dark theme
group-hover:bg-slate-750 doesn't exist in Tailwind — replaced with bg-slate-800
so dark mode hover stays dark instead of flashing white.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 01:40:40 +03:00
efa1f1aeef Fix nginx config: cp.hotelsync.ru routes to hotelsync-cp container
The actual nginx config lives at /opt/hotelsync/nginx/conf.d/ (outside git).
It used container names (hotelsync-*) not docker-compose service aliases.
- cp.hotelsync.ru: hotelsync-frontend:80 → hotelsync-cp:80
- app/guest: restored hotelsync-frontend:80 (was broken by accidental sed)
- deploy.yml: added step to sync this file from repo to /opt/hotelsync/nginx/conf.d/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 01:35:40 +03:00
a66c1f6035 Fix: add --network-alias to docker run in deploy workflow
Containers restarted via docker run lose their docker-compose service name
aliases. Added --network-alias frontend/cp so nginx can resolve them by
service name. Also reload nginx after each deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 01:13:44 +03:00
526a9eefcf Separate CP admin panel into independent Vite project at /cp/
- New /cp/ Vite project with own package.json, Dockerfile, tailwind config
- Migrated CpLoginPage, CpLayout, AdminDashboard → cp/src/ (self-contained)
- cp/src/lib/utils.ts has only what CP needs (cn, PLAN_LABELS, PLAN_COLORS)
- cp/src/data/mockData.ts has Hotel/User types and mock data
- cp/ builds to nginx static container (hotelsync-cp)
- docker-compose.yml: added cp service
- nginx: added cp.hotelsync.ru → hotelsync-cp:80 (SSL block ready for certbot)
- deploy.yml: builds hotelsync-cp:latest and restarts container on push
- Cleaned src/App.tsx: removed IS_CP branch, CpLoginPage/CpLayout/AdminDashboard imports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 01:03:28 +03:00
5429b272f9 Fix mobile UI bugs from screenshots
- Notifications panel: fixed positioning on mobile (full-width below topbar,
  no more overflow behind calendar)
- BookingModal: two-column layout stacks vertically on mobile (flex-col sm:flex-row)
- TariffsPage cards: price + actions merged into right column, no text overlap
- DiscountsPage cards: same fix, price text reduced to text-lg
- ReviewsPage: action buttons moved inside content div (no longer overlap guest name)
- RoomCategoriesPage: edit/delete buttons stack as column, 'Номера' text hidden on mobile
- PosPage: show desktop-only message on mobile instead of broken layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 00:47:24 +03:00
eeadbf0f5a Simplify hotel registration form to single step
Remove legal entity section (ОПФ, ИНН, КПП), two-step flow,
and confirm password. Keep: hotel name, address, contact person,
email, phone, password.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 00:32:29 +03:00
2fc66d1e84 Fix mobile responsiveness across all pages
- PosPage: rooms list becomes horizontal scroll row on mobile instead of w-56 sidebar
- AvailabilityPage: edit panel stacks below grid on mobile (was fixed w-80 side panel)
- UsersPage roles tab: sidebar stacks above permissions on mobile
- All page containers: p-6 → p-4 md:p-6 (TariffsPage, DiscountsPage, MaintenancePage, ReportsPage, DynamicPricingPage, GuestsPage, LoyaltyPage)
- Stats grids: lg:grid-cols-4 → md:grid-cols-4 (GuestsPage, TariffsPage, DiscountsPage, MaintenancePage, ReportsPage, LoyaltyPage)
- Modal form grids: grid-cols-3 → grid-cols-1 sm:grid-cols-3, grid-cols-5 → grid-cols-3 sm:grid-cols-5, grid-cols-4 → grid-cols-2 sm:grid-cols-4
- PosPage modals: fixed w-80/w-96 → responsive w-[calc(100vw-2rem)] sm:w-80/96

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 00:27:43 +03:00
91e58ff994 Fix infinite redirect loop: super_admin no longer redirects to /admin
The /admin route was removed when CP was moved to cp.hotelsync.ru.
Logged-in super_admin sessions were causing a redirect loop to a
non-existent route, resulting in a blank page on app.hotelsync.ru.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 00:16:29 +03:00
e9830975ab Add separate CP admin panel at cp.hotelsync.ru
- CpLoginPage: dark themed login page with email/password auth, only for cp subdomain
- CpLayout: separate dark sidebar layout with Shield icon, nav for Обзор/Отели/Пользователи/Тарифы/Мониторинг/Настройки
- App.tsx: detect hostname — cp.hotelsync.ru renders CP app, app.hotelsync.ru renders PMS (no /admin route)
- CpGuard: protects CP routes, redirects to /login if not authenticated
- Admin panel completely removed from app.hotelsync.ru

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 20:58:03 +03:00
15c3471e7f Fix mobile responsiveness across all pages
- BookingsPage: hide Номер/Выезд/Источник columns on small screens, show nights inline on mobile
- GuestsPage: stats grid 2→4 cols (grid-cols-2 lg:grid-cols-4), table columns hidden on mobile (md/lg breakpoints), GuestModal stats 2→4 cols on sm+
- BookingDetailPanel: full-width on mobile (w-full sm:w-[400px])
- Topbar: notifications panel full-width on mobile (w-[calc(100vw-1rem)] sm:w-96)
- LoyaltyPage, MaintenancePage, DiscountsPage, TariffsPage: stats grids grid-cols-2 lg:grid-cols-4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 20:51:56 +03:00
e81de90855 Move guest links to guest.hotelsync.ru subdomain
/review/:slug and /room-service/:slug guest pages now link to
guest.hotelsync.ru instead of app.hotelsync.ru

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 20:29:14 +03:00