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:
@@ -368,9 +368,11 @@ export function RoomCategoriesPage() {
|
||||
const [showBedTypes, setShowBedTypes] = useState(false)
|
||||
const [newBedLabel, setNewBedLabel] = useState('')
|
||||
const [newBedIcon, setNewBedIcon] = useState('🛏')
|
||||
const [newBedCapacity, setNewBedCapacity] = useState(2)
|
||||
const [editingBed, setEditingBed] = useState<string | null>(null)
|
||||
const [editBedLabel, setEditBedLabel] = useState('')
|
||||
const [editBedIcon, setEditBedIcon] = useState('')
|
||||
const [editBedCapacity, setEditBedCapacity] = useState(2)
|
||||
|
||||
useEffect(() => {
|
||||
if (!slug) return
|
||||
@@ -631,7 +633,7 @@ export function RoomCategoriesPage() {
|
||||
{/* Add new */}
|
||||
<div className="flex gap-2">
|
||||
<select
|
||||
className="input w-16 text-lg px-2 text-center"
|
||||
className="input w-14 text-lg px-2 text-center"
|
||||
value={newBedIcon}
|
||||
onChange={e => setNewBedIcon(e.target.value)}
|
||||
>
|
||||
@@ -642,7 +644,7 @@ export function RoomCategoriesPage() {
|
||||
<input
|
||||
type="text"
|
||||
className="input flex-1 text-sm"
|
||||
placeholder="Название (напр. Водяная кровать, Гамак...)"
|
||||
placeholder="Название (напр. Водяная кровать...)"
|
||||
value={newBedLabel}
|
||||
onChange={e => setNewBedLabel(e.target.value)}
|
||||
onKeyDown={e => {
|
||||
@@ -650,23 +652,28 @@ export function RoomCategoriesPage() {
|
||||
e.preventDefault()
|
||||
const label = newBedLabel.trim()
|
||||
if (!label) return
|
||||
const value = label.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_а-яё]/gi, '')
|
||||
+ '_' + Date.now().toString(36)
|
||||
addBedType({ value, label, icon: newBedIcon })
|
||||
setNewBedLabel('')
|
||||
setNewBedIcon('🛏')
|
||||
const value = label.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_а-яё]/gi, '') + '_' + Date.now().toString(36)
|
||||
addBedType({ value, label, icon: newBedIcon, capacity: newBedCapacity })
|
||||
setNewBedLabel(''); setNewBedIcon('🛏'); setNewBedCapacity(2)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<span className="text-xs text-slate-500 dark:text-slate-400 whitespace-nowrap">мест:</span>
|
||||
<input
|
||||
type="number" min={1} max={10}
|
||||
className="input w-14 text-sm text-center"
|
||||
value={newBedCapacity}
|
||||
onChange={e => setNewBedCapacity(parseInt(e.target.value) || 1)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
const label = newBedLabel.trim()
|
||||
if (!label) return
|
||||
const value = label.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_а-яё]/gi, '')
|
||||
+ '_' + Date.now().toString(36)
|
||||
addBedType({ value, label, icon: newBedIcon })
|
||||
setNewBedLabel('')
|
||||
setNewBedIcon('🛏')
|
||||
const value = label.toLowerCase().replace(/\s+/g, '_').replace(/[^a-z0-9_а-яё]/gi, '') + '_' + Date.now().toString(36)
|
||||
addBedType({ value, label, icon: newBedIcon, capacity: newBedCapacity })
|
||||
setNewBedLabel(''); setNewBedIcon('🛏'); setNewBedCapacity(2)
|
||||
}}
|
||||
disabled={!newBedLabel.trim()}
|
||||
className="btn-primary px-4 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
@@ -699,14 +706,21 @@ export function RoomCategoriesPage() {
|
||||
onChange={e => setEditBedLabel(e.target.value)}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
updateBedType(b.value, { label: editBedLabel, icon: editBedIcon })
|
||||
updateBedType(b.value, { label: editBedLabel, icon: editBedIcon, capacity: editBedCapacity })
|
||||
setEditingBed(null)
|
||||
}
|
||||
if (e.key === 'Escape') setEditingBed(null)
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="number" min={1} max={10}
|
||||
className="input text-xs w-10 py-0.5 h-6 text-center"
|
||||
value={editBedCapacity}
|
||||
onChange={e => setEditBedCapacity(parseInt(e.target.value) || 1)}
|
||||
title="Вместимость"
|
||||
/>
|
||||
<button
|
||||
onClick={() => { updateBedType(b.value, { label: editBedLabel, icon: editBedIcon }); setEditingBed(null) }}
|
||||
onClick={() => { updateBedType(b.value, { label: editBedLabel, icon: editBedIcon, capacity: editBedCapacity }); setEditingBed(null) }}
|
||||
className="text-emerald-500 hover:text-emerald-600 shrink-0"
|
||||
>
|
||||
<Check size={13} />
|
||||
@@ -719,9 +733,10 @@ export function RoomCategoriesPage() {
|
||||
<>
|
||||
<span className="text-base shrink-0">{b.icon}</span>
|
||||
<span className="text-xs text-slate-700 dark:text-slate-300 flex-1 truncate">{b.label}</span>
|
||||
<span className="text-[10px] text-slate-400 shrink-0">{b.capacity} чел.</span>
|
||||
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
||||
<button
|
||||
onClick={() => { setEditingBed(b.value); setEditBedLabel(b.label); setEditBedIcon(b.icon) }}
|
||||
onClick={() => { setEditingBed(b.value); setEditBedLabel(b.label); setEditBedIcon(b.icon); setEditBedCapacity(b.capacity) }}
|
||||
className="p-0.5 rounded hover:bg-slate-100 dark:hover:bg-slate-700 text-slate-400 hover:text-slate-700"
|
||||
>
|
||||
<Pencil size={11} />
|
||||
|
||||
Reference in New Issue
Block a user