nginx: add /ws location with Upgrade/Connection headers and 1h timeout
useHotelSocket: ping every 30s to prevent idle timeout, read fresh token
from localStorage on reconnect (handles 1h JWT expiry gracefully)
BookingCalendar: use elementFromPoint for reliable drag-to-move target
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Filter cancelled bookings from calendar grid (they no longer appear as red blocks)
- Remove cancelled from legend
- Drag-to-move: left-click drag a booking block to a different room row; click without dragging still opens the detail panel
- checked_out bookings are now semi-transparent and pointer-events-none so clicking through them creates a new booking
- Conflict detection in BookingDetailPanel now excludes checked_out bookings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove TravelLine/netup-pms pull route (travelline.ts unused)
- Remove pull integration section from TvWelcomePage (token field, API URL, etc.)
- Migration 008: netup_push_log table (hotel_id, action, status, request/response body)
- recordPush() now writes to DB in addition to in-memory buffer
- getLog reads from DB so logs survive server restarts
- clearLog deletes from DB
- Limit enforced: 50 rows per hotel (oldest auto-deleted)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
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>
- 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>