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>
DB constraint only allowed pending/in_progress/done/skipped, causing
500 error when trying to cancel a maintenance task.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Tasks now get status 'cancelled' when deleted — they remain in history.
Confirmation dialog updated to reflect this behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- 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>
- Backend POST housekeeping: include photos in INSERT, return task with room JOIN
- Backend PATCH housekeeping: set started_at on in_progress, return task with room JOIN
- WS broadcasts now include room_number/assignee_name from JOIN
- Frontend: normalizeTask() helper maps snake_case WS fields to camelCase
- TechnicalPage: fix room number display (roomNumber || room_number)
- TechnicalPage: show time spent (started_at → completed_at) on task cards
- HousekeepingPage: history tab now includes maintenance reports (no category filter)
- HousekeepingPage: HistoryCard shows photos, notes, time spent, maintenance styling
- Migration 028: started_at TIMESTAMPTZ column on housekeeping_tasks
- types: add startedAt? to HousekeepingTask
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Fix root cause: CalendarPage created tasks with type='regular' which violated DB CHECK constraint (allowed: cleaning/turnover/inspection/maintenance/amenities) → INSERT silently failed. Fixed to type='cleaning'.
- Fix: HousekeepingPage now loads ALL active tasks (status=active = pending+in_progress) without date filter — tasks appear regardless of when they were created
- Backend: support ?status=active filter (translates to IN ('pending','in_progress'))
- Add "История" tab in HousekeepingPage: shows completed tasks for a selected date with prev/next navigation, assignee and completion time displayed
- WS handler: when task becomes done, move it from active list instead of keeping in place
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Context menu (RoomContextMenu):
- Dirty/Cleaning now show priority sub-form (срочно/высокий/средний/низкий)
- New "Тех. задача" section with description text + priority grid
Calendar (CalendarPage):
- handleRoomUpdate: creates task for both 'dirty' AND 'cleaning' status
- New handleMaintenanceTaskCreate for category=maintenance tasks
- Passes priority from context menu to task creation
Backend (housekeeping.ts):
- task status → in_progress: room housekeeping_status = 'cleaning',
broadcasts housekeeping_done with roomStatus='cleaning' so all
connected Calendar clients update in real-time
DB (migration 025):
- category column on housekeeping_tasks (default 'housekeeping')
- HousekeepingPage filters category=housekeeping
- TechnicalPage filters category=maintenance
New TechnicalPage (/technical):
- List/filter/delete maintenance tasks
- Status dropdown per task
- Create modal with room/user/priority/date
Sidebar: "Тех. задачи" nav item added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- Interval reduced from 10m to 1h (min period is 2h — hourly is enough)
- Auto-checkout threshold: check_out date + hotel.check_out_time + N hours
(e.g. checkout Mar 23 + 12:00 + 2h = auto-checkout at 14:00)
- Auto-cancel threshold: check_in date + hotel.check_in_time + N hours
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- notifications table (024 migration) — stores per-hotel notifications
- GET/PATCH/POST/DELETE /api/hotels/:slug/notifications endpoints
- createNotification() helper used by jobs + future hooks
- jobs.ts — background tasks every 10min: auto-cancel no-shows and
auto-checkout overdue stays based on hotel_settings, broadcasts WS
events and creates notifications for each automated action
Frontend:
- CalendarPage: handle housekeeping_done WS → update room status in
calendar in real-time (bug fix)
- CalendarPage: when manually setting room to dirty via context menu,
auto-create housekeeping task and broadcast via WS (bug fix)
- NotificationsContext: replaced mock data with real API integration,
polls every 60s, syncs markRead/delete to backend
- SettingsPage booking section: auto-cancel no-show toggle + hours
selector; auto-checkout toggle + hours selector
- api.ts: notifications API methods
- useHotelSocket: notification:new WS message type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
- 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>
- 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>
- Fix applyPrices: build overrides array before setPrices (updater is async, side-effects inside it are unreliable)
- Add "Без категории" group for rooms not assigned to any category
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
- DELETE /api/upload?url= endpoint removes file from disk (with path traversal protection)
- api.upload.deletePhoto() helper in frontend
- removePhoto() now calls deletePhoto for cdn.hotelsync.ru URLs (best-effort)
- handleDragOver only highlights drop zone for external file drags (not thumbnail DnD)
- <img> in preview gets draggable={false} + pointer-events-none to not interfere with drops
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Thumbnails are draggable — drag one over another to swap positions.
Dragged item shows opacity/scale hint while dragging.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api.upload.photo used wrong localStorage key ('access_token' vs 'hotelsync-session')
- Removed standalone 'Загрузить фото' button — drop zone handles click+drag
- Added '+' thumbnail button to add more photos when gallery is non-empty
- Show spinner inline during upload instead of disabled button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Upload route now accepts ?folder=categories|rooms|hotels|guests
- Files saved to /uploads/{folder}/uuid.ext, served at cdn.hotelsync.ru/{folder}/uuid.ext
- api.upload.photo() accepts optional folder param (default: 'rooms')
- RoomCategoriesPage passes folder='categories' on photo upload
- SSL cert issued and nginx config active for cdn.hotelsync.ru
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>