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>
This commit is contained in:
@@ -4,11 +4,12 @@ import { addDays, format, startOfDay, differenceInDays, parseISO, isToday } from
|
||||
import { ru } from 'date-fns/locale'
|
||||
import { ChevronLeft, ChevronRight, Plus, CalendarDays, ChevronDown, AlignJustify, Clock } from 'lucide-react'
|
||||
import { cn, BOOKING_STATUS_COLORS, BOOKING_STATUS_LABELS, SOURCE_LABELS } from '../../lib/utils'
|
||||
import type { Room, Booking, DraftBooking } from '../../types'
|
||||
import type { Room, Booking, DraftBooking, RoomStatus, HousekeepingStatus } from '../../types'
|
||||
import type { RentalObject, RentalBooking } from '../../data/rentalData'
|
||||
import { BookingModal } from '../bookings/BookingModal'
|
||||
import { BookingDetailPanel } from '../bookings/BookingDetailPanel'
|
||||
import { RentalBookingModal } from '../rental/RentalBookingModal'
|
||||
import { RoomContextMenu } from '../rooms/RoomContextMenu'
|
||||
|
||||
const CELL_WIDTH = 52
|
||||
const ROW_HEIGHT = 56
|
||||
@@ -25,6 +26,7 @@ interface BookingCalendarProps {
|
||||
onBookingCreate: (b: Partial<Booking>) => void
|
||||
onBookingUpdate: (id: string, b: Partial<Booking>) => void
|
||||
onBookingBulkUpdate?: (updates: Array<{ id: string; data: Partial<Booking> }>) => void
|
||||
onRoomUpdate?: (roomId: string, patch: Partial<Room>) => void
|
||||
fadingBookingIds?: Set<string>
|
||||
rentalObjects?: RentalObject[]
|
||||
rentalBookings?: RentalBooking[]
|
||||
@@ -56,10 +58,26 @@ function getRoomTypeColor(type: string): string {
|
||||
return map[type] ?? 'bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300'
|
||||
}
|
||||
|
||||
export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBookingUpdate, onBookingBulkUpdate, fadingBookingIds, rentalObjects, rentalBookings, onRentalBookingCreate, locks = new Map(), onDraftStart, onDraftCancel, wsConnected }: BookingCalendarProps) {
|
||||
export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBookingUpdate, onBookingBulkUpdate, onRoomUpdate, fadingBookingIds, rentalObjects, rentalBookings, onRentalBookingCreate, locks = new Map(), onDraftStart, onDraftCancel, wsConnected }: BookingCalendarProps) {
|
||||
const [startDate, setStartDate] = useState(() => startOfDay(new Date()))
|
||||
const [visibleDays, setVisibleDays] = useState(DAYS_VISIBLE)
|
||||
|
||||
// Room context menu
|
||||
const [ctxMenu, setCtxMenu] = useState<{ room: Room; x: number; y: number } | null>(null)
|
||||
|
||||
const handleRoomContextMenu = (e: React.MouseEvent, room: Room) => {
|
||||
e.preventDefault()
|
||||
setCtxMenu({ room, x: e.clientX, y: e.clientY })
|
||||
}
|
||||
|
||||
const handleCtxStatusChange = (roomId: string, status: RoomStatus) => {
|
||||
onRoomUpdate?.(roomId, { status })
|
||||
}
|
||||
|
||||
const handleCtxHkChange = (roomId: string, status: HousekeepingStatus) => {
|
||||
onRoomUpdate?.(roomId, { housekeepingStatus: status })
|
||||
}
|
||||
|
||||
// Date picker state
|
||||
const [showNavPicker, setShowNavPicker] = useState(false)
|
||||
const [pickerDateInput, setPickerDateInput] = useState(format(new Date(), 'yyyy-MM-dd'))
|
||||
@@ -429,14 +447,21 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
|
||||
>
|
||||
{/* Room label */}
|
||||
<div
|
||||
className={cn('shrink-0 sticky left-0 z-10 bg-white dark:bg-slate-800 border-r border-slate-200 dark:border-slate-700 flex items-center gap-2 group-hover:bg-slate-50 dark:group-hover:bg-slate-800', isMobile ? 'px-2' : 'px-3')}
|
||||
className={cn('shrink-0 sticky left-0 z-10 bg-white dark:bg-slate-800 border-r border-slate-200 dark:border-slate-700 flex items-center gap-2 group-hover:bg-slate-50 dark:group-hover:bg-slate-800 cursor-context-menu select-none', isMobile ? 'px-2' : 'px-3')}
|
||||
style={{ width: labelWidth }}
|
||||
onContextMenu={e => handleRoomContextMenu(e, room)}
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-sm font-bold text-slate-900 dark:text-slate-100">{room.number}</span>
|
||||
{room.name && !isMobile && <span className="text-xs text-slate-500 dark:text-slate-400">{room.name}</span>}
|
||||
</div>
|
||||
{room.status === 'maintenance' && (
|
||||
<span className="text-[10px] text-orange-500 font-medium leading-none">🔧 ремонт</span>
|
||||
)}
|
||||
{room.status === 'blocked' && (
|
||||
<span className="text-[10px] text-slate-400 font-medium leading-none">🔒 закрыт</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -804,6 +829,17 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{ctxMenu && (
|
||||
<RoomContextMenu
|
||||
room={ctxMenu.room}
|
||||
x={ctxMenu.x}
|
||||
y={ctxMenu.y}
|
||||
onClose={() => setCtxMenu(null)}
|
||||
onStatusChange={handleCtxStatusChange}
|
||||
onHkStatusChange={handleCtxHkChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user