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:
@@ -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>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm font-medium text-slate-700 dark:text-slate-300 capitalize">
|
<span className="text-sm font-medium text-slate-700 dark:text-slate-300 capitalize">
|
||||||
{format(startDate, 'LLLL yyyy', { locale: ru })}
|
{format(startDate, 'LLLL yyyy', { locale: ru })}
|
||||||
</span>
|
</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">
|
||||||
|
|||||||
Reference in New Issue
Block a user