Calendar: compact mobile layout — narrow left column, hide View btn, move + to month row

- Left column: 80px on mobile (was 160px), hide room name/price/type badge
- Category headers: truncate text, reduce padding on mobile
- Toolbar: hide "Вид" button on mobile (sm:hidden)
- Toolbar: add compact "+" button next to month label on mobile only
- Original "+" button hidden on mobile (hidden sm:inline-flex)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-21 14:52:32 +03:00
parent 7bd0e038ba
commit 3f4d0dac4c

View File

@@ -98,6 +98,15 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
) )
const rowHeight = compact ? ROW_HEIGHT_COMPACT : ROW_HEIGHT const rowHeight = compact ? ROW_HEIGHT_COMPACT : ROW_HEIGHT
// Mobile detection
const [isMobile, setIsMobile] = useState(() => window.innerWidth < 640)
useEffect(() => {
const h = () => setIsMobile(window.innerWidth < 640)
window.addEventListener('resize', h)
return () => window.removeEventListener('resize', h)
}, [])
const labelWidth = isMobile ? 80 : LABEL_WIDTH
// Modals // Modals
const [bookingModalDraft, setBookingModalDraft] = useState<DraftBooking | null>(null) const [bookingModalDraft, setBookingModalDraft] = useState<DraftBooking | null>(null)
const [selectedBooking, setSelectedBooking] = useState<Booking | null>(null) const [selectedBooking, setSelectedBooking] = useState<Booking | null>(null)
@@ -264,9 +273,22 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
<button onClick={() => shiftDays(7)} className="btn-ghost p-2"><ChevronRight size={16} /></button> <button onClick={() => shiftDays(7)} className="btn-ghost p-2"><ChevronRight size={16} /></button>
</div> </div>
<span className="text-sm font-medium text-slate-700 dark:text-slate-300 capitalize"> <div className="flex items-center gap-2">
{format(startDate, 'LLLL yyyy', { locale: ru })} <span className="text-sm font-medium text-slate-700 dark:text-slate-300 capitalize">
</span> {format(startDate, 'LLLL yyyy', { locale: ru })}
</span>
{/* Mobile-only + button next to month name */}
<button
className="sm:hidden btn-primary p-1.5"
onClick={() => {
const today = format(new Date(), 'yyyy-MM-dd')
const tomorrow = format(addDays(new Date(), 1), 'yyyy-MM-dd')
setBookingModalDraft({ roomId: rooms[0]?.id ?? '', checkIn: today, checkOut: tomorrow })
}}
>
<Plus size={15} />
</button>
</div>
<div className="flex-1" /> <div className="flex-1" />
@@ -298,7 +320,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
onClick={() => setCompact(v => { const next = !v; localStorage.setItem('calendarCompact', String(next)); return next })} onClick={() => setCompact(v => { const next = !v; localStorage.setItem('calendarCompact', String(next)); return next })}
title={compact ? 'Обычный вид' : 'Компактный вид'} title={compact ? 'Обычный вид' : 'Компактный вид'}
className={cn( className={cn(
'btn-ghost flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium', 'hidden sm:flex btn-ghost items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium',
compact && 'bg-brand-50 dark:bg-brand-900/20 text-brand-600 dark:text-brand-400', compact && 'bg-brand-50 dark:bg-brand-900/20 text-brand-600 dark:text-brand-400',
)} )}
> >
@@ -312,7 +334,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
const tomorrow = format(addDays(new Date(), 1), 'yyyy-MM-dd') const tomorrow = format(addDays(new Date(), 1), 'yyyy-MM-dd')
setBookingModalDraft({ roomId: rooms[0]?.id ?? '', checkIn: today, checkOut: tomorrow }) setBookingModalDraft({ roomId: rooms[0]?.id ?? '', checkIn: today, checkOut: tomorrow })
}} }}
className="btn-primary" className="hidden sm:inline-flex btn-primary"
> >
<Plus size={15} /> <Plus size={15} />
<span className="hidden sm:inline">Новое бронирование</span> <span className="hidden sm:inline">Новое бронирование</span>
@@ -321,7 +343,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
{/* Grid */} {/* Grid */}
<div className="flex-1 overflow-auto" ref={gridRef}> <div className="flex-1 overflow-auto" ref={gridRef}>
<div style={{ minWidth: LABEL_WIDTH + visibleDays * CELL_WIDTH }}> <div style={{ minWidth: labelWidth + visibleDays * CELL_WIDTH }}>
{/* Date header row */} {/* Date header row */}
<div <div
@@ -330,7 +352,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
> >
<div <div
className="shrink-0 sticky left-0 z-30 bg-white dark:bg-slate-800 border-r border-slate-200 dark:border-slate-700 flex items-center px-3" className="shrink-0 sticky left-0 z-30 bg-white dark:bg-slate-800 border-r border-slate-200 dark:border-slate-700 flex items-center px-3"
style={{ width: LABEL_WIDTH }} style={{ width: labelWidth }}
> >
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Номер</span> <span className="text-xs font-semibold text-slate-500 uppercase tracking-wide">Номер</span>
</div> </div>
@@ -379,10 +401,10 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
style={{ height: 28 }} style={{ height: 28 }}
> >
<div <div
className="shrink-0 sticky left-0 z-10 bg-slate-50 dark:bg-slate-700/40 border-r border-slate-200 dark:border-slate-600 flex items-center px-4" className="shrink-0 sticky left-0 z-10 bg-slate-50 dark:bg-slate-700/40 border-r border-slate-200 dark:border-slate-600 flex items-center px-2 overflow-hidden"
style={{ width: LABEL_WIDTH }} style={{ width: labelWidth }}
> >
<span className={cn('text-xs font-semibold uppercase tracking-wide', getRoomTypeColor(room.type).split(' ').filter(c => c.startsWith('text-')).join(' '))}> <span className={cn('text-xs font-semibold uppercase tracking-wide truncate', getRoomTypeColor(room.type).split(' ').filter(c => c.startsWith('text-')).join(' '))}>
{room.type} {room.type}
</span> </span>
</div> </div>
@@ -407,21 +429,21 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
> >
{/* Room label */} {/* Room label */}
<div <div
className="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 px-3 group-hover:bg-slate-50 dark:group-hover:bg-slate-800" 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')}
style={{ width: LABEL_WIDTH }} style={{ width: labelWidth }}
> >
<div> <div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-sm font-bold text-slate-900 dark:text-slate-100">{room.number}</span> <span className="text-sm font-bold text-slate-900 dark:text-slate-100">{room.number}</span>
{room.name && <span className="text-xs text-slate-500 dark:text-slate-400">{room.name}</span>} {room.name && !isMobile && <span className="text-xs text-slate-500 dark:text-slate-400">{room.name}</span>}
</div> </div>
{!compact && ( {!compact && !isMobile && (
<span className={cn('text-xs px-1.5 py-0.5 rounded-md font-medium', getRoomTypeColor(room.type))}> <span className={cn('text-xs px-1.5 py-0.5 rounded-md font-medium', getRoomTypeColor(room.type))}>
{room.type} {room.type}
</span> </span>
)} )}
</div> </div>
{!compact && ( {!compact && !isMobile && (
<div className="ml-auto text-xs text-slate-400 dark:text-slate-500"> <div className="ml-auto text-xs text-slate-400 dark:text-slate-500">
{room.baseRate.toLocaleString('ru-RU')} {room.baseRate.toLocaleString('ru-RU')}
</div> </div>
@@ -612,7 +634,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
style={{ height: 32 }}> style={{ height: 32 }}>
<div <div
className="shrink-0 sticky left-0 z-10 bg-slate-100 dark:bg-slate-700/60 border-r border-slate-200 dark:border-slate-600 flex items-center px-4" className="shrink-0 sticky left-0 z-10 bg-slate-100 dark:bg-slate-700/60 border-r border-slate-200 dark:border-slate-600 flex items-center px-4"
style={{ width: LABEL_WIDTH }} style={{ width: labelWidth }}
> >
<span className="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide"> <span className="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide">
Аренда объектов Аренда объектов
@@ -635,7 +657,7 @@ export function BookingCalendar({ rooms, bookings, slug, onBookingCreate, onBook
{/* Label */} {/* Label */}
<div <div
className="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 px-3" className="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 px-3"
style={{ width: LABEL_WIDTH }} style={{ width: labelWidth }}
> >
<span className="text-base leading-none shrink-0">{obj.icon}</span> <span className="text-base leading-none shrink-0">{obj.icon}</span>
<div className="min-w-0"> <div className="min-w-0">