feat: show occupied slots in new rental step-1 modal

This commit is contained in:
2026-03-23 11:54:52 +03:00
parent d54ea551b4
commit c36d94e22a

View File

@@ -199,6 +199,17 @@ export function BookingsPage() {
)
}
// Step-1 modal: occupied slots for selected object + date
const pickObj = rentalObjects.find(o => o.id === rentalPickObj)
const pickBookings = rentalBookings.filter(
b => b.objectId === rentalPickObj && b.date === rentalPickDate && b.status === 'confirmed',
)
const pickOpenMin = (pickObj?.openHour ?? 8) * 60
const pickCloseMin = (pickObj?.closeHour ?? 22) * 60
const pickTotalMin = pickCloseMin - pickOpenMin
const pickHasFullDay = pickBookings.some(b => b.isFullDay)
const fmtPickMin = (m: number) => `${Math.floor(m / 60)}:${String(m % 60).padStart(2, '0')}`
return (
<div className="p-4 md:p-6 space-y-4">
{/* Header */}
@@ -506,6 +517,63 @@ export function BookingsPage() {
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">Дата</label>
<input type="date" className="input" value={rentalPickDate} onChange={e => setRentalPickDate(e.target.value)} />
</div>
{/* Occupied slots preview */}
{pickObj && (
<div className="space-y-2">
<p className="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wide">
Занятость · {new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long' }).format(new Date(rentalPickDate))}
</p>
{pickHasFullDay ? (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-700">
<span className="w-2 h-2 rounded-full bg-red-500 shrink-0" />
<span className="text-sm text-red-700 dark:text-red-300 font-medium">Весь день занят</span>
</div>
) : pickBookings.length === 0 ? (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 dark:border-emerald-700">
<span className="w-2 h-2 rounded-full bg-emerald-500 shrink-0" />
<span className="text-sm text-emerald-700 dark:text-emerald-300 font-medium">Свободно весь день</span>
</div>
) : (
<div className="space-y-2">
{/* Timeline bar */}
<div className="relative h-5 rounded-md overflow-hidden bg-emerald-100 dark:bg-emerald-900/20">
{pickBookings.filter(b => !b.isFullDay).map(b => {
const left = ((b.startHour - pickOpenMin) / pickTotalMin) * 100
const width = ((b.endHour - b.startHour) / pickTotalMin) * 100
return (
<div
key={b.id}
className="absolute top-0 h-full bg-red-400 dark:bg-red-600 opacity-80"
style={{ left: `${Math.max(0, left)}%`, width: `${Math.min(100 - Math.max(0, left), width)}%` }}
title={`${b.guestName} ${fmtPickMin(b.startHour)}${fmtPickMin(b.endHour)}`}
/>
)
})}
</div>
{/* Hour labels */}
<div className="flex justify-between text-[10px] text-slate-400 dark:text-slate-500">
<span>{fmtPickMin(pickOpenMin)}</span>
<span>{fmtPickMin(Math.round((pickOpenMin + pickCloseMin) / 2))}</span>
<span>{fmtPickMin(pickCloseMin)}</span>
</div>
{/* Booking list */}
<div className="space-y-1 max-h-24 overflow-y-auto">
{pickBookings.filter(b => !b.isFullDay).map(b => (
<div key={b.id} className="flex items-center gap-2 text-xs">
<span className="w-1.5 h-1.5 rounded-full bg-red-400 shrink-0" />
<span className="font-medium text-slate-700 dark:text-slate-300 tabular-nums">
{fmtPickMin(b.startHour)}{fmtPickMin(b.endHour)}
</span>
<span className="text-slate-400 dark:text-slate-500 truncate">{b.guestName}</span>
</div>
))}
</div>
</div>
)}
</div>
)}
<div className="flex gap-2 justify-end">
<button onClick={() => setNewRentalStep('idle')} className="btn-secondary">Отмена</button>
<button