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:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { BedDouble, Users, Wifi, Plus, Search, Loader2, Pencil, Layers } from 'lucide-react'
|
||||
import { BedDouble, Users, Wifi, Plus, Search, Loader2, Pencil, Layers, Wrench } from 'lucide-react'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { api } from '../lib/api'
|
||||
import type { Room, RoomCategory } from '../types'
|
||||
@@ -7,6 +7,8 @@ import { cn, ROOM_STATUS_COLORS, ROOM_STATUS_LABELS, HK_STATUS_COLORS, HK_STATUS
|
||||
import { Badge } from '../components/ui/Badge'
|
||||
import { RoomModal } from '../components/rooms/RoomModal'
|
||||
import { BulkRoomModal, type BulkRoomData } from '../components/rooms/BulkRoomModal'
|
||||
import { RoomContextMenu } from '../components/rooms/RoomContextMenu'
|
||||
import type { RoomStatus, HousekeepingStatus } from '../types'
|
||||
|
||||
export function RoomsPage() {
|
||||
const { user } = useAuth()
|
||||
@@ -69,6 +71,16 @@ export function RoomsPage() {
|
||||
}
|
||||
|
||||
const [saveError, setSaveError] = useState<string | null>(null)
|
||||
const [ctxMenu, setCtxMenu] = useState<{ room: Room; x: number; y: number } | null>(null)
|
||||
|
||||
const handleRoomQuickUpdate = async (roomId: string, patch: Partial<Room>) => {
|
||||
try {
|
||||
const updated = await api.rooms.update(slug, roomId, patch)
|
||||
setRooms(prev => prev.map(r => r.id === updated.id ? updated : r))
|
||||
} catch (err) {
|
||||
console.error('Failed to update room:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleBulkSave = async (bulkRooms: BulkRoomData[]) => {
|
||||
const created: Room[] = []
|
||||
@@ -213,7 +225,12 @@ export function RoomsPage() {
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{filtered.map(room => (
|
||||
<RoomCard key={room.id} room={room} onEdit={() => openEdit(room)} />
|
||||
<RoomCard
|
||||
key={room.id}
|
||||
room={room}
|
||||
onEdit={() => openEdit(room)}
|
||||
onContextMenu={(e) => { e.preventDefault(); setCtxMenu({ room, x: e.clientX, y: e.clientY }) }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -239,19 +256,41 @@ export function RoomsPage() {
|
||||
onClose={() => setBulkOpen(false)}
|
||||
onSave={handleBulkSave}
|
||||
/>
|
||||
|
||||
{ctxMenu && (
|
||||
<RoomContextMenu
|
||||
room={ctxMenu.room}
|
||||
x={ctxMenu.x}
|
||||
y={ctxMenu.y}
|
||||
onClose={() => setCtxMenu(null)}
|
||||
onStatusChange={(id, status: RoomStatus) => handleRoomQuickUpdate(id, { status })}
|
||||
onHkStatusChange={(id, status: HousekeepingStatus) => handleRoomQuickUpdate(id, { housekeepingStatus: status })}
|
||||
onEdit={(room) => openEdit(room)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function RoomCard({ room, onEdit }: { room: Room; onEdit: () => void }) {
|
||||
function RoomCard({ room, onEdit, onContextMenu }: { room: Room; onEdit: () => void; onContextMenu: (e: React.MouseEvent) => void }) {
|
||||
return (
|
||||
<div className="card p-4 hover:shadow-card-hover transition-shadow cursor-pointer group relative">
|
||||
<div
|
||||
className="card p-4 hover:shadow-card-hover transition-shadow cursor-pointer group relative"
|
||||
onContextMenu={onContextMenu}
|
||||
>
|
||||
<button
|
||||
onClick={e => { e.stopPropagation(); onEdit() }}
|
||||
className="absolute top-3 right-3 p-1.5 rounded-lg bg-white dark:bg-slate-700 border border-slate-200 dark:border-slate-600 text-slate-500 opacity-0 group-hover:opacity-100 transition-opacity shadow-sm hover:text-brand-600"
|
||||
>
|
||||
<Pencil size={13} />
|
||||
</button>
|
||||
{room.status === 'maintenance' && (
|
||||
<div className="absolute top-3 left-3">
|
||||
<span className="flex items-center gap-1 text-[10px] font-medium text-orange-600 dark:text-orange-400 bg-orange-50 dark:bg-orange-900/20 px-1.5 py-0.5 rounded-md border border-orange-200 dark:border-orange-800">
|
||||
<Wrench size={10} />ремонт
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user