Commit Graph

159 Commits

Author SHA1 Message Date
113971f854 feat: wire up loyalty + chat — register routes, API types, connect LoyaltyPage, add ChatWidget to layout 2026-03-26 10:15:29 +03:00
5d5efecd9e feat: staff work schedule — weekly grid for all roles, shift assignment, day off marking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 01:18:02 +03:00
18358b64b3 feat: role-based sidebar menu visibility — each role sees only permitted sections 2026-03-26 00:52:29 +03:00
588ecb0f2a feat: hourly pricing + category base prices in booking modal + availability grid
- Add hourly_price column to rate_overrides, base_price/allow_hourly/hourly_base_price to room_categories (migration 033)
- Update categories and rate-overrides backend routes to handle new fields
- AvailabilityPage: hourly/nightly mode toggle, period prices auto-applied to grid with 'П' indicator, confirmation dialog when overriding period prices
- BookingModal: nightly/hourly toggle at top, pricing hierarchy (override → category base → room rate)
- RoomCategoriesPage: base_price/allow_hourly/hourly_base_price fields in category form; inline rooms panel
- CalendarPage + BookingCalendar: pass hourlyPriceOverrides and categoryBasePrices down to BookingModal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 00:05:14 +03:00
b3d143f1dd feat: 3 improvements — hourly toggle position, category rooms panel, period prices in grid
1. BookingModal: move Посуточно/Почасово toggle to top (before dates)
   when room has hourly rate enabled
2. RoomCategoriesPage: Номера > button now expands inline panel
   showing rooms belonging to that category
3. AvailabilityPage:
   - Period prices auto-applied to grid (base → periods → overrides),
     cells from periods show violet П badge
   - П legend item added
   - Hourly rate settings shown in Periods tab for hourly-enabled rooms

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 20:32:32 +03:00
0e3a7347e0 fix: allow cross-origin image loading + hide legend on small screens
- Set crossOriginResourcePolicy: cross-origin in helmet so browsers
  can load uploaded images from api.hotelsync.ru when app runs on
  app.hotelsync.ru (was blocked by same-origin CORP header)
- Hide booking status legend squares below lg breakpoint (hidden lg:flex)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 20:08:48 +03:00
018b119772 fix: broadcast on task create (realtime), context menu position recalc, CDN_HOST in deploy.sh 2026-03-24 19:57:12 +03:00
0aaf26cbd1 fix: photo URLs (CDN_HOST), booking conflict excludes checked_out, sync label, context menu overflow 2026-03-24 19:46:45 +03:00
9a2e45b4c4 fix: 8 improvements — calendar overlaps, legend, nav, tech tasks photos/sort/modal, hk history active maintenance 2026-03-24 19:32:46 +03:00
7fc59bcb81 feat: show 🔍 проверка label in calendar for rooms awaiting inspection after cleaning 2026-03-24 12:12:05 +03:00
aeef924542 fix: rename urgent → Экстренно! in calendar tech task form + room closure warning
- RoomContextMenu: separate TECH_PRIORITY_ITEMS with 'Экстренно!' label for urgent
- Tech task form uses 3-column grid (all priorities visible at once)
- Red highlight when Экстренно! is selected
- Show "🔒 Номер будет закрыт для бронирования" warning under priority selector

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:59:52 +03:00
c83a9ca714 feat: task problem/solution split + maintenance icons on rooms/calendar
- TechnicalPage: redesign task cards with separate "Задача" and "Решение" sections
  - "Задача": problem description + problem photos (editable before done)
  - "Решение": technician resolution comment + resolution photos (green section)
  - Resolution comment in CompletionModal now saves to resolution_notes field
  - Resolution photos save to resolution_photos (no longer mixed with problem photos)
- RoomModal history tab: maintenance entries show separate problem/resolution sections
- Backend: add resolution_notes, resolution_photos to PATCH allowed fields
- Migration 029: resolution_notes TEXT + resolution_photos TEXT[] columns
- RoomsPage: load active maintenance tasks, show 🔧 неисправность badge on room cards
- CalendarPage: load active maintenance tasks, show 🔧 неиспр. label in calendar row
- BookingCalendar: activeMaintenance prop, WS handler updates maintenance set on task changes
- CalendarPage WS: handle housekeeping_task_created to update activeMaintenance set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:45:24 +03:00
14f0b8e0f2 feat: room history tab + tech task resolution comment
- RoomModal: new "История" tab showing all housekeeping and maintenance tasks for the room
  - Groups tasks by date (newest first), expandable cards
  - Shows: assignee, start time, end time, duration (started_at → completed_at)
  - For maintenance: description, problem/solution photos with lightbox viewer
  - Timing grid: start / completion timestamps
- Backend GET /housekeeping: add room_id filter param
- TechnicalPage CompletionModal: add resolution comment field saved to task notes
- api.ts: housekeeping.list() accepts roomId param

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:32:11 +03:00
2ebef940b9 feat: cleaning tooltip, report photos, fix tech task creation, completion photos
- Calendar: hover tooltip on '🧹 убирается' shows who is cleaning (assignee name)
  fetched from active HK tasks on mount, updated via WS housekeeping_updated events
- HousekeepingPage: 'Сообщить о неисправности' now creates a real maintenance task
  in the DB (category=maintenance) + sends WS so TechnicalPage receives it instantly;
  photo upload added to the report form
- RoomContextMenu: photo upload added to tech task creation form (camera button,
  preview thumbnails); photos passed through to API on task creation
- TechnicalPage: selecting 'Выполнено' now opens CompletionModal with photo upload;
  technician can attach completion photos before confirming; if require_completion_photo
  setting is on — at least one photo is required
- Settings (HousekeepingPage plans tab): new toggle 'Обязательное фото при завершении
  тех. задачи' saved to housekeeping_settings.require_completion_photo (migration 027)
- Backend: housekeeping-settings updated with require_completion_photo field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 11:07:28 +03:00
e51d776b3a feat: photos for tech tasks, 3 priorities, remove cleaning status from context menu
- Remove 'Убирается' (cleaning) from manually selectable HK statuses in calendar context menu — it now only shows automatically when a task is in_progress
- Reduce priorities from 4 to 3: remove 'high', keep urgent/medium/low across context menu, tech tasks modal, and HousekeepingTask type
- Add photo upload to technical tasks: upload via camera button per task, display thumbnails with hover-to-remove, stored as TEXT[] in DB (migration 026)
- Fix HousekeepingPage WS handler to filter by category — no longer adds maintenance tasks to housekeeping board
- Add 'tasks' folder support to upload route

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 10:38:51 +03:00
3bb7262b59 feat: add category field to housekeeping tasks + TechnicalPage for maintenance tasks
- Add migration 025 to add `category` column (housekeeping/maintenance) to housekeeping_tasks
- Backend: filter by ?category= in GET, store category in POST, allow patching category
- API client: HkPayload + housekeeping.list() now support category param
- HousekeepingPage: filters by category=housekeeping so maintenance tasks don't appear
- RoomContextMenu: priority sub-form for dirty/cleaning status, tech task creation inline form
- BookingCalendar: accepts onMaintenanceTaskCreate prop, passes priority to onRoomUpdate
- CalendarPage: handleRoomUpdate creates task for dirty+cleaning (with priority), new handleMaintenanceTaskCreate for category=maintenance tasks
- TechnicalPage: new page for viewing/creating/updating maintenance tasks
- App.tsx: /technical route added
- Sidebar: Тех. задачи nav item added under Управление

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:28:42 +03:00
b631a281d2 fix: normalizeRoom converts NUMERIC baseRate string to number in api.ts
PostgreSQL NUMERIC columns are returned as strings by node-postgres.
Added normalizeRoom() that coerces baseRate/hourlyRate/floor to JS numbers
before they reach components, eliminating string concatenation bugs like
"300005000.005000.00" in price calculations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 22:48:51 +03:00
9af11df1b0 feat: housekeeping automation — auto-task on checkout + room status on done
Backend:
- Migration 023: housekeeping_settings table (checkout_auto, checkout_priority, inspection_after_clean)
- New route: GET/PATCH /api/hotels/:slug/housekeeping-settings
- bookings.ts: on checked_out → auto-create turnover task + mark room dirty + broadcast WS
- housekeeping.ts: on task done → update room status (inspect|clean per setting) + broadcast WS
- ws.ts: export broadcast() for use in other routes

Frontend:
- HousekeepingPage: load/save settings to API, live Loader on save button
- useHotelSocket: add housekeeping WS message types
- HousekeepingPage: subscribe to WS — new tasks appear instantly without refresh
- BookingModal: show total section when room + nights selected (not just when total > 0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 22:27:18 +03:00
ce20c6545d fix: robust night price calculation in BookingModal + clear test DB data
- Use parseISO instead of new Date() for safer date parsing
- Use Math.floor(nights) to avoid float iteration issues
- Type-check override price before using (typeof p === 'number')
- Label now shows actual sum from overrides instead of baseRate × nights

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:54:17 +03:00
15936753b9 feat: use availability rate overrides in calendar tooltip and booking modal
- Load rate_overrides in CalendarPage, pass as priceOverrides to BookingCalendar
- Tooltip shows override price for the hovered date (falls back to baseRate)
- BookingModal sums per-night prices from overrides for the stay period
- Room baseRate now only used as initial default for availability grid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:35:13 +03:00
8312d2372e fix: maintenance overlay + context menu UX improvements
- Calendar overlay: normalize date strings (slice 0-10) to handle full ISO timestamps from Postgres; fix off-by-one (to = exclusive); stronger stripe visibility; guard against vFrom >= vTo
- Context menu: dateFrom defaults to today, indefinite defaults to false (both date inputs shown immediately)
- DateRangeForm: extracted DateInput component with CalendarDays icon that triggers native date picker
- HK status order: clean → inspect → cleaning → dirty

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:03:17 +03:00
c3a30be74e feat: calendar hover price tooltip + AvailabilityPage API integration
- Calendar cells: hold mouse 600ms to show price tooltip for that date/room (baseRate + hourly rate if enabled)
- AvailabilityPage: load real categories from API, derive basePrice from room baseRates, fall back to room types if no categories
- Rate periods: persisted to DB via new /api/hotels/:slug/rate-periods endpoint
- New backend migration 021_rate_periods.sql + rate-periods route
- Added api.ratePeriods.{list,create,update,delete} to frontend API client

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:05:12 +03:00
63da3d21cc feat: maintenance/blocked date ranges with booking overlap warning
- DB migration 020: add maintenance_from, maintenance_to to rooms
- Room type + RoomPayload: maintenanceFrom, maintenanceTo fields
- Context menu: clicking "На ремонт"/"Закрыт" expands inline date picker
  (С / По + "Без срока" checkbox), confirms with "Применить" button
- Calendar: stripe overlay covers only the maintenance date range
  (full row if no dates set, partial if date range specified)
- Booking creation: if drag-selected dates overlap with maintenance period,
  shows warning dialog with "Отмена" / "Всё равно забронировать"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:49:19 +03:00
4a39d5f731 fix: context menu reposition after mount to prevent viewport overflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:41:56 +03:00
7bcc1ca0ab feat: visual overlay for maintenance/blocked rooms in calendar
- Diagonal stripe overlay on date cells: orange tint for maintenance,
  grey for blocked
- Room number and name dimmed when maintenance/blocked
- Show 🧹 dirty/cleaning indicator under room number (when not in maintenance)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:38:36 +03:00
fd50e02be0 feat: right-click context menu for room status + bed type capacity
- Add RoomContextMenu component (portal, Escape/click-outside to close)
  - Change room status: available / maintenance / blocked
  - Change housekeeping status: clean / dirty / cleaning / inspect
  - Open room edit (optional)
- Calendar: right-click on room label → context menu; onRoomUpdate prop
  wired to CalendarPage (calls api.rooms.update); show 🔧/🔒 badge on label
- Rooms page: right-click on any room card → same context menu;
  show orange "ремонт" badge on maintenance rooms
- BedTypesContext: add capacity field (max guests per bed type)
- BedTypes reference UI: add capacity input to add/edit form; show "N чел."

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:30:14 +03:00
8c6cdfb1c7 feat: configurable bed types via BedTypesContext
- Add BedTypesContext with default types + add/remove/update operations
- Replace hardcoded BED_ITEM_TYPES array in RoomModal with useBedTypes()
- Add "Справочник спальных мест" section to RoomCategoriesPage
  (expandable, same UX as amenities: add icon+label, rename, delete)
- BedItem.type changed from union to string for dynamic support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 14:20:09 +03:00
3e9622d281 feat: bulk room creation + category filter on rooms page
- Add BulkRoomModal: set number range (from→to), category, bed type,
  max guests, base rate, amenities; auto-derives floor from room number;
  preview shows new vs skipped (already existing) rooms
- Add category filter chips on Rooms page (alongside existing floor filter)
- Add "Групповое добавление" button next to "Добавить номер"
- Wire onDelete prop to RoomModal on rooms page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:57:31 +03:00
eea76657e1 fix: smart next-free room number suggestion
- Placeholder shows next free after global max (e.g. 204 if 203 is max)
- Error message suggests next free starting from typed+1:
  type 101 (taken) → suggests 102
  type 201 (taken) → suggests 202
  type 1011 (taken) → suggests 1012
- Suggestion is a clickable link that auto-fills the field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:51:10 +03:00
4ea6dcc368 fix: calendar room label dedup + room number validation
- BookingCalendar: remove type badge below room number (already shown in group header)
- RoomModal: validate number against existingNumbers before save
  - Red border + error message if number taken
  - Placeholder shows next free number (e.g. if 101,102 exist → placeholder 103)
  - Save button disabled while number is taken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:44:33 +03:00
31d135563f fix: cdn.hotelsync.ru nginx config + RoomModal key for state reset
- Add cdn.hotelsync.ru server block to nginx config (serves /var/www/uploads)
- Add cdn.hotelsync.ru to HTTP redirect server_name
- RoomModal: add key={editingRoom?.id ?? 'new'} to force state reinit on room change
- RoomModal: remove categories[0] fallback for categoryId (show 'Без категории' instead)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 13:14:20 +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
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
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