Commit Graph

349 Commits

Author SHA1 Message Date
3ed3156fe8 feat: move amenities directory to RoomCategoriesPage + cdn.hotelsync.ru for uploads
- Remove "Справочник удобств" block from RoomsPage, add to RoomCategoriesPage
- Fix RoomsPage imports (add Pencil back, remove unused amenity icons)
- Update hint in CategoryForm to point to current page
- Upload route now returns cdn.hotelsync.ru URLs instead of api.hotelsync.ru/uploads/
- Nginx config for cdn.hotelsync.ru (serves /opt/hotelsync/uploads directly)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 12:31:08 +03:00
3ef7f8265a feat: real file upload to /opt/hotelsync/uploads + serve via /uploads/ on API 2026-03-23 12:23:13 +03:00
5540d0cdfa fix: category form — async save with error display, drag-and-drop photos, 20MB body limit 2026-03-23 12:17:53 +03:00
1f761e65cf feat: room category — remove hardcoded types, show 'Без категории' with link when none exist 2026-03-23 12:08:47 +03:00
04258a4f50 feat: room type dropdown uses categories from API when available 2026-03-23 12:02:32 +03:00
c36d94e22a feat: show occupied slots in new rental step-1 modal 2026-03-23 11:54:52 +03:00
d54ea551b4 fix: RoomsPage load categories from API instead of mock 2026-03-23 11:45:40 +03:00
5c40b154d0 feat: categories + tariffs — migration, API routes, frontend integration 2026-03-23 11:42:29 +03:00
b494224af0 feat: BookingsPage rental tab — connect to API (replace mock data) 2026-03-23 11:19:04 +03:00
2475c76d45 feat: RentalBookingModal 2-column layout
Left col: existing bookings list, full-day toggle, time selectors
Right col: guest autocomplete, phone, link-to-room, notes, price+payment block
Modal widened to max-w-2xl; payment section unified with BookingModal style

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:38:25 +03:00
7c65bebc22 feat: rental tab 2-column layout + payment section
Left col: object picker, date, full-day toggle, time selectors
Right col: guest autocomplete, phone, notes, total + payment block
Payment: Наличные/Терминал toggle buttons, оплачено input, 'Всю сумму' shortcut, debt indicator; paidAmount saved to booking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:31:16 +03:00
b1b6854663 feat: single ФИО field with autocomplete replaces 3 separate name inputs
- BookingModal (room tab): replace Фамилия/Имя/Отчество grid with single 'ФИО *' input; parseFullName() splits by spaces into last/first/middle; DB search via api.guests.list (debounced 300ms); dropdown shows current bookings + DB guests with phone/email autofill
- BookingDetailPanel: merge Фамилия (with autocomplete) + Имя + Отчество into single ФИО * input; existing acQuery/acResults infrastructure reused; parseFio() updates all 3 bgForm fields on every keystroke; openEditForm initializes ФИО from full name
- RentalBookingModal + BookingModal rental tab: include middleName when displaying DB guest results and when setting guestName on selection; update type to include middleName field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:22:46 +03:00
e403689522 fix: toggle CSS + guest autocomplete in BookingModal rental tab
- Toggle: add type=button, overflow-hidden, p-0, explicit left-[2px] + translate-x-[18px] pattern for reliable knob positioning across all toggles
- Guest field in BookingModal rental tab: add debounced DB search (api.guests.list) + suggestions dropdown (same as RentalBookingModal)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 01:08:25 +03:00
f21c5d5054 fix: rental calendar cells cap at 3 bookings + show +N overflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 23:08:25 +03:00
db05898c08 feat: 15-minute rental slots + compact calendar cell display
- Rental bookings now use minutes since midnight (start_hour/end_hour
  store e.g. 495 for 8:15, 570 for 9:30) — DB migration 017
- Buffer applied in exact minutes (no hour ceiling): 15min buffer after
  9:00 booking → next slot available at 9:15
- Calendar cell: removed working-hours line (8:00–22:00), now shows
  colored dot + compact time per booking (8–10, 8:15–9:15)
- Time selects show HH:MM format for 15-min intervals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 23:02:24 +03:00
370fe8a9a9 feat: move rental module to main nav under Цены и тарифы
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:46:39 +03:00
20582581be fix: sync endHour state when effectiveStartHour shifts past it
When existing bookings block the initial startHour, effectiveStartHour
snaps to the first available slot. Added useLayoutEffect to update
endHour state immediately so start and end times are never equal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:37:49 +03:00
fe4b9aa6eb feat: connect RentalPage to API (objects + bookings persisted in DB)
- Add createObject/updateObject/deleteObject to api.rental
- Add RentalObjectPayload interface
- RentalPage now loads objects and bookings from API on mount
- Save/delete object operations call the API instead of only local state
- Save booking operations call the API (create or update)
- bufferMinutes from DB is now correctly passed to booking modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:23:16 +03:00
3961585196 fix: rental booking buffer applied to end times + phone mask
- getMaxEndHour now subtracts breakHours from next booking's start,
  so you can't book right up to a slot that needs buffer time before it
- effectiveStartHour moved before availableEndHours so equal start/end
  times are no longer possible
- maskPhone (+7 (XXX) XXX-XX-XX) applied to phone field in RentalBookingModal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:16:12 +03:00
31858ca5cc feat: room/rental tabs in booking modal + smart time slot filtering
BookingModal:
- Add 'Номер' / 'Аренда объекта' tabs when rental objects exist
- Rental tab: object picker, date, full-day toggle, conflict-aware time dropdowns, guest/phone fields, total amount

RentalBookingModal:
- Filter start times: exclude hours inside booked slots + bufferMinutes gap
- Filter end times: cap at next booking's start hour
- Disable 'Весь день' if any timed bookings exist for the day
- Show 'Все слоты заняты' when no start hours are available
- Show buffer duration in time section label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 22:07:40 +03:00
5cab0ce8da fix: checkout form defaults to planned checkOut date, not today
Previously earlyOutDate defaulted to today, which caused bookings checked
out on their first day (checkIn == today) to become 0-night invisible blocks
in the calendar. Now defaults to the planned checkOut date so the bar stays
visible; manager explicitly sets an earlier date for early departure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:39:24 +03:00
c0a086a7b8 fix: normalize checkIn/checkOut dates with .slice(0,10) before comparison
PostgreSQL DATE columns serialize as ISO strings with time component
(2026-03-21T00:00:00.000Z), causing string comparison with yyyy-MM-dd
to incorrectly show the early check-in banner for same-day bookings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:30:02 +03:00
edd493e24e feat: early check-in / late checkout fees with room-level configuration
- DB migration 016: add early_checkin_fee, late_checkout_fee to rooms table
- Backend rooms: support new fee columns in POST/PATCH
- Backend hotel-settings GET: also returns check_in_time, check_out_time
- Room type + RoomPayload: add earlyCheckinFee, lateCheckoutFee fields
- RoomModal: add fee amount inputs in pricing section
- SettingsPage: add toggles for early_checkin_enabled / late_checkout_enabled
- BookingDetailPanel: on check-in shows fee warning + adds payment record if
  current time is before checkInTime and fee is set on the room; same logic
  for check-out after checkOutTime

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:24:36 +03:00
9f1dd0c8c9 fix: block 'Заселить' button for future bookings (checkIn > today)
Future bookings can only be checked in via the early check-in flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:15:51 +03:00
2c458dfc09 feat: early checkout with refund calculation for any checked_in booking
Replaces simple 'Выселить' button with a checkout form:
- Date picker (min=checkIn, max=original checkOut)
- If leaving early: shows refund nights, refund amount, and payment method selector
- Adds negative payment record to local payment history
- On confirm: sets status=checked_out and updates checkOut date

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:11:29 +03:00
b111cc0898 fix: early check-in checkout date capped at original planned checkout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 19:07:14 +03:00
6dbff276c8 feat: early check-in for guests who arrive before booking date
Show amber banner on confirmed bookings where check-in is in the future.
Opens an inline form to check in today: checks if current room is free,
otherwise shows available rooms with price difference. Allows overriding
checkout date. On confirm, sets status=checked_in and check-in=today.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 18:49:23 +03:00
b831106ae2 Rental modal: require phone for new guests, auto-create in guests DB
- Track guestIsKnown: true when selected from dropdown, false when typed manually
- If guest is new (typed manually): phone field is required (red border + hint)
- On save: if new guest + slug available, call api.guests.create before saving rental
- Button shows 'Сохранение...' while saving, disabled during async flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:02:59 +03:00
033882790d Rental guest search: show phone number as subtitle in suggestions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:49:44 +03:00
dbccc59dfc Fix: rental booking save (date comparison), add DB guest search in rental modal
- BookingCalendar: normalize date comparison with .slice(0,10) to fix PostgreSQL DATE ISO format mismatch
- CalendarPage: show alert on rental booking save failure
- RentalBookingModal: add debounced DB guest search (api.guests.list) with 300ms delay
- RentalBookingModal: combine booking suggestions + DB results in unified dropdown
- Pass slug prop to RentalBookingModal for API access

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:45:37 +03:00
68c9caf46f RentalBookingModal: guest search autocomplete + link to room booking
- Guest name field shows dropdown with matching guests from all bookings
- Checked-in guests (🏠) shown first, then confirmed (📅)
- Selecting a guest auto-fills name and phone
- New 'Привязать к номеру' dropdown to link rental to a room booking
- Selecting a linked room auto-fills guest name if empty
- Pass bookings + rooms props from BookingCalendar to modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 15:56:13 +03:00
b48e50e62b Rental: connect to API, improve cell UI; remove price from room labels
Backend:
- Migration 015_rental.sql: rental_objects + rental_bookings tables, seed demo data
- New routes: GET/POST/PATCH/DELETE rental-objects and rental-bookings per hotel
- Register rentalRoutes in app.ts

Frontend:
- api.ts: add rental.listObjects, listBookings, createBooking, updateBooking, deleteBooking
- CalendarPage: fetch rental objects + bookings from API instead of mock data
- BookingCalendar: rental cell UI redesigned — colored squares + working hours + time slots (matches design spec)
- BookingCalendar: remove base rate price from desktop room label column (irrelevant with dynamic pricing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 15:46:01 +03:00
3f4d0dac4c Calendar: compact mobile layout — narrow left column, hide View btn, move + to month row
- Left column: 80px on mobile (was 160px), hide room name/price/type badge
- Category headers: truncate text, reduce padding on mobile
- Toolbar: hide "Вид" button on mobile (sm:hidden)
- Toolbar: add compact "+" button next to month label on mobile only
- Original "+" button hidden on mobile (hidden sm:inline-flex)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 14:52:32 +03:00
7bd0e038ba Fix: block drag-to-move if target room has conflicting booking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:05:40 +03:00
456a35e00d Revert: drag ghost back to small badge style
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 01:02:43 +03:00
80e3b21827 Fix: drag ghost visual, check-in date auto-fill, backend room_id patch
- BookingCalendar: replace tiny ghost badge with full-size booking block
  (same color, correct width based on nights × cell width, centered on cursor)
- BookingModal: auto-set check-out to check-in+1 day when check-in changes
- Backend PATCH /bookings/🆔 add room_id to allowed fields so drag-to-move saves correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 00:56:49 +03:00
97b2ca1f91 Fix drag drop: use ref for target room ID instead of elementFromPoint in mouseup (stale closure fix) 2026-03-21 00:44:30 +03:00
3e4a2a3cd2 Calendar: add drag ghost element + grabbing cursor; BookingDetail: add Relocate room for checked_in guests 2026-03-21 00:37:26 +03:00
75d7d6aa08 Fix: drag booking passes roomId to API; checked_out rooms available for new booking; remove time-based theme text 2026-03-21 00:22:03 +03:00
fd804926e4 BookingModal: move guest fields to right column; Theme: auto mode follows system prefers-color-scheme 2026-03-21 00:04:24 +03:00
d3581f27f2 BookingModal: move extra beds+tags to right col, payment to full-width bottom; Calendar: fix today booking left edge fill 2026-03-20 23:56:02 +03:00
0464259273 BookingModal: redesign — dates first, category cards, specific room toggle 2026-03-20 23:45:11 +03:00
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