Calendar: add drag ghost element + grabbing cursor; BookingDetail: add Relocate room for checked_in guests

This commit is contained in:
2026-03-21 00:37:26 +03:00
parent 75d7d6aa08
commit 3e4a2a3cd2
3 changed files with 93 additions and 2 deletions

View File

@@ -250,6 +250,26 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
const [conflictResolveRoomId, setConflictResolveRoomId] = useState('')
const canEditDates = booking.status === 'confirmed' || booking.status === 'inquiry'
const canRelocate = booking.status === 'checked_in'
const [relocating, setRelocating] = useState(false)
const [relocateRoomId, setRelocateRoomId] = useState(booking.roomId)
const freeRoomsForRelocate = (rooms ?? []).filter(r =>
r.id !== booking.roomId &&
!(allBookings ?? []).some(b =>
b.id !== booking.id &&
b.roomId === r.id &&
b.status !== 'cancelled' && b.status !== 'no_show' && b.status !== 'checked_out' &&
b.checkIn < booking.checkOut && b.checkOut > booking.checkIn,
),
)
const handleRelocate = () => {
if (!relocateRoomId || relocateRoomId === booking.roomId) return
onUpdate(booking.id, { roomId: relocateRoomId })
setRelocating(false)
}
const findConflict = (ci: string, co: string, rid: string): Booking | null =>
(allBookings ?? []).find(b =>
@@ -405,13 +425,66 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
<div className="space-y-4">
{room && (
<div className="rounded-xl bg-slate-50 dark:bg-slate-700/40 p-3">
<p className="text-xs text-slate-500 dark:text-slate-400 font-medium mb-1">Номер</p>
<div className="flex items-start justify-between mb-1">
<p className="text-xs text-slate-500 dark:text-slate-400 font-medium">Номер</p>
{canRelocate && !relocating && (
<button
onClick={() => { setRelocating(true); setRelocateRoomId('') }}
className="flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400 hover:text-amber-700 dark:hover:text-amber-300 font-medium"
>
<Pencil size={11} /> Переселить
</button>
)}
</div>
<p className="font-semibold text-slate-900 dark:text-slate-100">
{room.number} {room.type}
</p>
<p className="text-sm text-slate-500 dark:text-slate-400">
Этаж {room.floor} · {room.bedType} bed
</p>
{/* Форма переселения */}
{relocating && (
<div className="mt-3 space-y-2 pt-3 border-t border-slate-200 dark:border-slate-600">
<p className="text-xs font-medium text-amber-700 dark:text-amber-400">Переселить гостя в другой номер</p>
{freeRoomsForRelocate.length > 0 ? (
<>
<select
className="input text-sm"
value={relocateRoomId}
onChange={e => setRelocateRoomId(e.target.value)}
>
<option value=""> выберите номер </option>
{freeRoomsForRelocate.map(r => (
<option key={r.id} value={r.id}>
{r.number} {r.type} ({r.baseRate.toLocaleString('ru-RU')} /ночь)
</option>
))}
</select>
<div className="flex gap-2">
<button
onClick={handleRelocate}
disabled={!relocateRoomId}
className="flex-1 py-1.5 rounded-lg bg-amber-500 hover:bg-amber-600 disabled:opacity-50 text-white text-xs font-semibold transition-colors"
>
Переселить
</button>
<button
onClick={() => setRelocating(false)}
className="px-3 py-1.5 rounded-lg border border-slate-200 dark:border-slate-600 text-xs text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors"
>
Отмена
</button>
</div>
</>
) : (
<div className="text-xs text-slate-500 dark:text-slate-400">
Нет свободных номеров на эти даты
<button onClick={() => setRelocating(false)} className="ml-2 text-brand-600 dark:text-brand-400 underline">Закрыть</button>
</div>
)}
</div>
)}
</div>
)}