379 Commits

Author SHA1 Message Date
31de5be873 feat: review form logo/header image customizer, fix unread msg job startup
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
- ReviewsPage: logo upload (circle/square), header bg image, show-email toggle
- GuestReviewQrPage/GuestReviewPage: render logo image, header bg image, logo shape
- Backend: return review_logo_url, review_logo_shape, review_header_image_url, show_email from both public review endpoints
- jobs.ts: run unread messages job 20s after startup (was only on interval)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 23:09:43 +03:00
9bda48c020 feat: guest email field on QR review form for reply
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:43:49 +03:00
8ab71b1e45 feat: email field for QR review replies, internal note if no email
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:37:22 +03:00
d83ffcb3c7 fix: notification links use app routes without slug prefix
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:31:20 +03:00
bb401095dc fix: add qr to reviews source constraint + notif type fix
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:26:20 +03:00
6165e4c67c fix: stabilize star rating layout — reserve label height, use inline scale
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:21:52 +03:00
d1948f95b9 fix: add missing notif types to frontend (new_booking, room_service, new_review, unread_messages)
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:16:12 +03:00
76101e8811 fix: skip macOS ._* artifact files in migrations runner
Some checks failed
Deploy to Production / deploy (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:08:24 +03:00
6d07632f01 feat: notification settings per role + review/unread-msg notifications
- Add notificationSettings JSONB to role_permissions (migration 089)
- sendPushForNotification() — pushes only to users with the notif type enabled
- reviews.ts — push + in-app on new direct and QR reviews
- room-service.ts — use sendPushForNotification('room_service_order')
- publicWidget.ts — push + in-app on new online booking
- jobs.ts — runUnreadMessagesJob() every 15 min, deduped by in-process set
- UsersPage: NOTIFICATION_GROUPS UI in roles tab with per-type toggles
- api.ts / RolePermissionsContext: notificationSettings in types and save()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 16:43:39 +03:00
1975663cdd feat: full Room Service backend integration
- Migrations 086-088: room_service_settings, menu_items, orders+order_items
- Backend: /api/hotels/:slug/room-service/* routes (settings, menu CRUD,
  orders CRUD, public config, public order status)
- push.ts: sendPushToHotel() — push to all hotel staff on new order
- On new order: in-app notification + push to hotel staff
- api.ts: RoomService types + roomService API methods
- RoomServicePage: replaced mocked state with real API, 30s polling,
  add/edit/delete menu items modal, real advance/cancel order
- GuestRoomServicePage: fetches real menu + config, uses hotel payment
  methods, submits real order, polls status every 15s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:44:28 +03:00
0e2180e3bc feat: configurable source→platform map, custom location QR codes
- Settings tab: replace static source map rows with editable table
  (add/delete mappings, persisted as review_source_map in hotel_settings)
- QR tab: add custom location QR codes (Ресепшн, Ресторан, etc.)
  not tied to a room, stored as review_custom_locations
- Backend: GET /api/public/review/:token reads review_source_map from
  hotel_settings instead of hardcoded SOURCE_KEYWORD mapping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:44:07 +03:00
47215c7d64 fix: reviews — correct pending/archive logic, rejected_at field, stats, reply email
- Migration 085: add rejected_at to reviews
- pending = !isPublic && !reply && !rejectedAt
- archived = isPublic || reply || rejectedAt
- Stats: avgRating/NPS from all reviews; 'В архиве' = archived count
- sendReply: no longer sets isPublic=true on negative reviews
- reject: sets rejected_at=NOW() (distinct from pending)
- restore: clears rejected_at (now actually works)
- Badge 'Отклонён' for rejected reviews, opacity-60

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:33:01 +03:00
89e919bd8d feat: reply email to guest, QR code reviews per room
- PATCH reviews/:id now sends email to guest when manager adds reply
  (sendReviewReplyEmail: hotel-branded, shows original rating, manager's text)
- New public endpoints: GET/POST /api/public/review-qr/:slug/:room
  (anonymous QR review, no token/booking required, source='qr')
- New page GuestReviewQrPage at /review-qr/:slug/:room
- ReviewsPage: new QR-коды tab — grid of QR codes per room with download SVG + print

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:23:54 +03:00
67c47a6d01 fix: review email — hotel-branded header, proper first name, 30d guard, brand color
- Email no longer shows HotelSync branding, shows hotel name instead
- Greeting uses second word of guest_name as first name (Фамилия Имя format)
- Footer link changed to hotelsync.ru
- Brand color from hotel settings applied to email button/header
- Job: only process bookings checked out within last 30 days (prevents bulk send on first activation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:14:22 +03:00
2de9b9e27d fix: ReviewsPage use useAuth for slug; reviews redirect by booking source
- Replace useParams with useAuth().user.hotelSlug in ReviewsPage
- Backend: filter platforms by booking source when review_redirect_source=true
  (booking_com→Booking.com, yandex_travel→Яндекс, airbnb, expedia, vrbo)
- If source has matching platform — show only that one; otherwise all platforms

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:21:26 +03:00
6ba591928a fix: use request.user.sub instead of .id in users route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:07:16 +03:00
1ab2751854 feat: reviews module — email request after checkout, API, job
- Migration 084: add review_token, review_email_sent_at, review_retry_sent_at to bookings
- Backend route /api/hotels/:slug/reviews (GET list, PATCH reply/status)
- Backend public routes /api/public/review/:token (GET config, POST submit)
- email.ts: sendReviewRequestEmail template
- jobs.ts: runReviewRequestJob — sends email N hours after checkout, retry after 24h
- app.ts: register reviewsRoutes
- api.ts: add ReviewApi, PublicReviewConfig types + reviews/publicReview methods
- ReviewsPage: connect to real API, load/save settings, remove SMS option
- GuestReviewPage: use :token param, fetch config from API, submit to API
- App.tsx: route /review/:slug → /review/:token

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:04:59 +03:00
696cf4d38c fix: refresh token re-reads role from DB; remove staff stats cards
- /api/auth/refresh now fetches current role from DB instead of using
  stale Redis payload — role changes now take effect on next page refresh
  without requiring re-login. Also deactivates token if user.active=false.
- Removed role-count stat cards from staff page (user request)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 20:17:09 +03:00
a05bc3c48e feat: staff invite flow — email invitation instead of manual password
- POST /api/hotels/:slug/users: password now optional
  - with password → create active user (email_confirmed=true)
  - without password → create inactive user, send invite email with 7-day token
- POST /api/auth/accept-invite: validates token, sets password, activates
  account, returns JWT for auto-login
- Migration 083: invite_token + invite_expires columns on users
- email.ts: sendInviteEmail() with branded HTML template
- AcceptInvitePage at /invite/:token — set password form, auto-login on success
- AuthContext: loginWithToken() for programmatic session set
- UserModal: password field optional for new users, hint about invite email

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 20:11:53 +03:00
153e1d72d6 fix: role-permissions GET/PUT return camelCase (isSystem, roleKey, homePage)
Backend was returning raw Postgres snake_case column names; frontend
SavedRolePermission interface expected camelCase. This caused isSystem
to always be undefined → all roles treated as custom in UserModal.
Also adds home_page to the SELECT so homePage is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 20:04:01 +03:00
9e1eff30a6 fix: persist custom roles immediately, show them in user modal
- createRole() now calls api.rolePermissions.save() on creation so the
  role survives a page refresh (was only in local state before)
- UserModal now renders custom roles from savedPermissions context
  alongside the system role buttons
- mapRole() passes custom_* role keys through unchanged
- handleSave falls back to u.role for custom roles (no backendRoleMap entry)
- Table badge handles missing ROLE_META entry for custom roles
- DB migration 082: removes users.role CHECK constraint so custom role
  keys can be stored in the users table
- Backend POST/PATCH: allow custom_* roles through role allowlist checks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 19:57:59 +03:00
a701366216 feat: route permission guards, home page per role — RolePermissionsContext, PermissionGuard, App routes, DB migration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:50:22 +03:00
80429cd52a feat: home page per role — selector in RolesTab, saved to DB and applied on login
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 10:42:05 +03:00
d26ad30433 fix: edit user modal empty fields (key prop); refactor: sidebar concept A — by work role
Sidebar: 6 groups by daily work role instead of mixed functionality
- Фронт-деск: calendar, bookings, guests, pos, room-service, reviews
- Номерной фонд: rooms, categories, availability, floor-map, housekeeping, tech tasks
- Продажи: tariffs, pricing, discounts, rental, channels, website, widget
- Аналитика: reports, loyalty
- Персонал: users, schedule, documents
- Настройки: settings, modules, billing, equipment, wifi, ttlock, sub-settings
Removed: separate 'Разработчикам' group (API docs moved to Настройки)
Removed: stale localStorage key sidebarGroups_v2 → v3
Fixed: dynamic modules (pos, room-service, etc) placed in logical groups instead of dumped in 'Сервис'
Fixed: UserModal key prop — form now resets correctly when switching between employees

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:26:03 +03:00
512e3c6659 feat: full role permissions system — new modules, API persistence, sidebar context
- Backend: migration 080_role_permissions table (hotel-scoped, upsert)
- Backend: routes GET/PUT/DELETE /api/hotels/:slug/role-permissions
- Frontend: RolePermissionsContext — loads saved perms from API, provides can()
- Frontend: Sidebar uses context can() instead of hardcoded ROLE_PERMS
- Frontend: fixed module ID→permKey mapping (room-service, olap-reports, website-builder)
- Frontend: Documents page added to Управление nav (was missing)
- Frontend: equipment/wifi/ttlock get own permission keys (not bundled under 'settings')
- Frontend: floor_map gets own permission key (not bundled under 'rooms')
- Frontend: new module group 'Технологии': wifi, equipment, ttlock, floor_map
- Frontend: 'schedule' added to Администрирование module group
- Updated INITIAL_ROLE_PERMISSIONS: receptionist+availability+reports+documents+website, accountant+documents, technician+floor_map+equipment+ttlock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:16:15 +03:00
ea58cffa88 fix: use correct super_admin role in group ownership transfer check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:50:07 +03:00
18a6359adb feat: group chat owner protection — prevent removing creator, transfer ownership before leaving
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:47:18 +03:00
9d12e157b1 fix: notifications — unified dark style, centered at bottom 2026-04-17 11:30:46 +03:00
e6d84e6fd7 fix: push notifications — urlBase64ToUint8Array bug, add PushBanner prompt 2026-04-15 12:24:18 +03:00
1641b14f29 feat: PWA push notifications — service worker, VAPID, subscription management
- Add web-push VAPID backend with push_subscriptions table (migration 078)
- Backend push module with sendPushToUser/sendPushToRoom helpers
- Push routes: GET vapid-key, POST subscribe, DELETE unsubscribe
- Trigger push on new chat messages in direct/group rooms
- PWA manifest, service worker, and icons (72–512px)
- Frontend push lib: SW registration, subscribe/unsubscribe helpers
- Push API in api.ts, SW registered in main.tsx
- usePushSubscription hook for auto-subscribe on permission grant
- PushPermissionRow component in ChatWidget settings view

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 12:14:03 +03:00
756a71bf2f fix: chat — show group avatar in room list 2026-04-15 11:56:01 +03:00
e6caf10a98 feat: chat — group settings (rename, photo, add/remove members), fix mention underline style 2026-04-15 11:32:52 +03:00
2cdc44b495 fix: chat — group creation (snake_case member_ids), readable mention color 2026-04-14 17:54:05 +03:00
18e1eb86af fix: chat — group members endpoint (all roles), mention first-name only, restore direct-chat button 2026-04-14 17:44:33 +03:00
8353eb7c7f feat: chat — group chat creation (create-group view, backend endpoint, group room display) 2026-04-14 17:36:04 +03:00
b91a75c89f fix: chat — 4 fixes for mentions, role display, header, close-on-outside
1. Role tooltip is now absolute-positioned (no layout impact on bubble width)
2. Outside-click uses overlay div instead of document mousedown (fixes mention dropdown closing chat)
3. Header: role shown in small white/50 text, separate from green 'в сети'
4. @mentions: highlighted in messages (yellow for self, bold for others); bypass mute if mentioned

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 17:09:31 +03:00
cf552d4b40 feat: chat — role on hover, role in direct header, @mention dropdown
- MessageBubble: role hidden by default, appears on hover of sender name (group/name CSS)
- Direct chat header: shows other user's role (from otherUserRole field added to backend)
- @mention: typing @ triggers filtered user dropdown, click inserts @Name into message
- Hint text updated: '@ — упомянуть'
- Backend: other_user_role added to rooms query

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:42:32 +03:00
e7ad9862ba feat: chat pagination — infinite scroll / load older messages
- Backend: cursor-based pagination via ?before=<ISO timestamp>
- Frontend: auto-load when scrolling to top (<60px), spinner + manual 'Загрузить ещё' button
- Poll merge: preserves older history when new messages arrive from polling
- hasMoreMsgs flag: shown only when exactly 50 msgs returned (more may exist)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:35:38 +03:00
ef1fd4246e feat: chat — smart scroll, sender role, close on outside click, per-room mute
- Scroll: only auto-scroll on poll when near bottom; openRoom uses double-RAF for instant jump
- Sender role: show position (Менеджер / Горничная etc) under name in message bubbles
- Close on outside click: mousedown listener when panel is open
- Per-room mute: ⋮ menu → Отключить/Включить уведомления, BellOff badge on muted rooms, saved to localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:25:24 +03:00
1c3045f083 feat: chat messages — instant scroll, in-room search, room search reset on back
- openRoom: set instantScrollRef before setMessages → scrollTop=scrollHeight synchronously, no visible jump
- In-room search: 🔍 button in messages header, filter messages client-side, empty state
- Room search state reset when opening/closing room

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:13:25 +03:00
58ad4060c1 feat: chat — background badge polling, toast notifications, notification settings
- Room poll now runs always (10s when closed, 5s when open) — badge + sound work when minimized
- Toast pop-up (top-right) on new messages when chat is minimized or room not active
- Auto-dismiss toasts after 4.5s, click to open room
- Settings: popup enable/disable + show/hide message text in notification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:39:23 +03:00
a3db2e9d5a fix: chat settings toggle — dot no longer overflows button bounds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:29:31 +03:00
abf1920e6f feat: chat — ⋮ room menu, typing indicator, online presence, read receipts
- RoomRow: ⋮ button (hover) for pin/unpin context menu
- MessageBubble: right-click context menu with emoji reactions + edit/delete
- Typing indicator: debounced setTyping, animated TypingDots, polling getTyping every 2s
- Online presence: heartbeat setPresence every 30s, green dot on avatars/header
- Read receipts: ✓/✓✓ for own messages in direct rooms via otherUserLastRead
- Migration 075: chat_presence + chat_typing tables
- Context menus clamped to viewport bounds

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:16:35 +03:00
4de9cfa88e fix/feat: chat UX — context menu, sound, pin rooms, settings fix
- Replace hover toolbar with right-click context menu (no overflow issues)
  Messages: emoji reactions row + Edit + Delete (own only)
  Rooms: Pin / Unpin — pinned rooms always appear at top with 📌 icon
- Context menu clamped to viewport bounds, closes on any outside click
- Sound notifications: Web Audio API beep (880Hz, 0.25s) on new messages
  and on new unreads when widget is closed; respects soundEnabled setting
- Settings: added px-4 to sections so toggle is never clipped
  Added hint text about right-click shortcuts
- Pin state stored in localStorage (hotelsync-chat-pins)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:00:30 +03:00
a29f58282f feat: chat — edit/delete messages + emoji reactions
- Migration 074: edited_at, deleted_at columns + chat_reactions table
- Backend: PATCH edit (own only), DELETE soft-delete (own/admin), POST toggle reaction
  fetchMessage helper returns full message with aggregated reactions in one query
- API types: ChatReaction interface, editedAt/deletedAt/reactions in ChatMessage
- ChatWidget: hover toolbar (😊 react · ✏️ edit · 🗑️ delete), inline edit with
  save/cancel (Enter/Esc), reaction emoji picker (8 emojis), reaction pills below
  messages (click to toggle), '· изм.' label on edited messages, soft-deleted
  messages show 'Сообщение удалено' placeholder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:31:17 +03:00
7ccf752429 feat: chat — photo attachments
- Migration 073: attachment_url column in chat_messages
- upload.ts: add 'chat' folder to ALLOWED_FOLDERS
- chat.ts: accept attachment_url in send message, return it in GET messages
- api.ts: attachmentUrl in ChatMessage type, uploadImage() with raw FormData fetch
- ChatWidget: paperclip button, file preview thumbnail with remove, image display
  inline in message bubbles (clickable, opens original), send enabled with image only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:13:07 +03:00
d0dbf33701 fix: chat — VARCHAR(10) overflow for notifications type, skeleton loading
- Migration 072: widen type column to VARCHAR(20), add partial unique index for general rooms
- Backend: replace brittle ON CONFLICT with explicit SELECT-then-INSERT ensureRoom helper
- Frontend: show room skeletons (Общий чат + Уведомления) during load instead of spinner,
  hide '0 чатов' subtitle when list is empty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:03:35 +03:00
3c4c40da44 feat: chat — search, notifications room, settings
- Migration 071: nullable sender_id, is_system flag, notifications room type
- Backend: notifications room auto-created per hotel, search messages endpoint,
  notify endpoint (manager/admin posts system messages), read-only enforcement
- API: ChatSearchResult type, chat.search(), chat.notify(), updated ChatMessage type
- Frontend ChatWidget: search view (people + messages tabs), settings panel
  (notifications visibility toggle, sound toggle), notifications room (bell icon,
  read-only banner, amber styling for system messages), new-chat button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 11:25:40 +03:00
291d45622b fix: widget embed — hide scrollbar in iframe mode 2026-04-12 02:00:31 +03:00
d5240baffc fix: widget modal — explicit dialog height, iframe fills absolutely 2026-04-12 01:52:15 +03:00