Commit Graph

207 Commits

Author SHA1 Message Date
babd6e950e Fix: add passport_issued_by/issue_date to guests table for full autocomplete
- Migration 014: add passport_issued_by, passport_issue_date to guests table
- guests.ts: include new fields in SELECT, INSERT, PATCH + encrypt/decrypt
- booking-guests.ts: sync issued_by/issue_date back to guests on update
- GuestApiType: add passportIssuedBy, passportIssueDate fields
- selectAcGuest: fill passport_issued_by and passport_issue_date from guest profile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:47:12 +03:00
887c362c18 Fix: widen passport columns to TEXT for encrypted values
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:28:52 +03:00
8282155237 Add AES-256-GCM encryption for sensitive personal data fields
- New backend/src/lib/crypto.ts: encrypt/decrypt (AES-256-GCM) + passportToken (HMAC-SHA256)
- Migration 012: birth_date/passport_issue_date → TEXT, add passport_search_token indexes
- guests.ts: encrypt passport_series/number/birth_date on write, decrypt on read,
  passport search via HMAC token (?passport= param) instead of plaintext LIKE
- booking-guests.ts: encrypt all sensitive fields, search token for passport lookup
- api.ts: guests.list() supports passport= param for exact passport lookup
- BookingDetailPanel: use passport param for passport-based autocomplete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:19:27 +03:00
aa4a1fdb39 LoginPage: remove demo accounts hint, add terms agreement checkbox on register
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:01:55 +03:00
84cfca500c BookingModal: replace guestName with FIO+phone fields, remove passport tab
- Add lastName/firstName/middleName/phone fields to booking form
- Remove 'Документы гостя' tab entirely from BookingModal
- Auto-parse existing guestName into FIO parts when editing
- Add guestPhone to Booking type and propagate through CalendarPage/BookingsPage API calls
- Add middle_name to guests table (migration 011)
- Update guests backend route to include middle_name in all queries
- Add middleName to GuestApiType in api.ts
- Fix selectAcGuest in BookingDetailPanel to also fill middle_name
- Auto-open guest form pre-filled with booking FIO when no guests exist
- Add passport series+number lookup in BookingDetailPanel guest form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 21:15:37 +03:00
873a9a4fc4 Add multi-guest management: per-booking roster, docs, guest autocomplete
- Migration 010: booking_guests table (roster per booking with passport data)
- Backend: /bookings/:id/guests CRUD — auto-links/creates guest profiles by passport
- Backend: /hotel-settings GET/PATCH for key-value settings (require_guest_docs)
- BookingDetailPanel: Гости tab with multi-guest list, inline add/edit form,
  guest autocomplete (debounced search in guests table), child/main badges,
  passport fields for adults, scan stub, require-docs warning
- SettingsPage: toggle "Обязательное заполнение документов гостей"
- Pass slug prop through CalendarPage → BookingCalendar → BookingDetailPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 20:53:07 +03:00
de4fb2126b Add Guests module: API integration + passport identification
- Migration 009: add passport_series, passport_number, rating to guests table
- Backend route /api/hotels/:slug/guests: list, get (with history), create/upsert by passport, update, delete
- Frontend GuestsPage: loads from API, passport tab in modal, add guest form, search by passport
- api.ts: add guests API methods and GuestApiType

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:03:04 +03:00
3da2d5e0ab Fix drag-to-move and WS keepalive
drag-to-move: use window-level mousemove/mouseup listeners for reliable
drag detection — React synthetic events on container were unreliable

WS keepalive: server sends ping JSON every 25s so nginx proxy_read_timeout
never fires; client ignores pings (not broadcast to peers anymore)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:44:12 +03:00
14c5327870 Fix booking click broken: restore onClick, fix drag vs click detection
Removed onClick broke the detail panel. Now:
- onClick restored on booking block (opens panel on simple click)
- didDragRef flag prevents panel opening after a drag completes
- handleMouseUp no longer calls setSelectedBooking (onClick handles it)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:35:58 +03:00
26412a04dd Fix WS offline: nginx WebSocket headers + keepalive ping + drag-to-move fix
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>
2026-03-19 13:24:37 +03:00
3602ea7318 Calendar: hide cancelled bookings, drag-to-move rooms, allow booking through checked-out
- 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>
2026-03-19 13:09:13 +03:00
ed397dbcd9 Remove pull integration; persist push log to DB (last 50 per hotel)
- 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>
2026-03-19 12:55:39 +03:00
32115399ee Fix reservation_id: numeric string via hex→decimal UUID conversion 2026-03-19 12:40:52 +03:00
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