Move cell/range toggle near arrows, add period picker on date button
- Replaced static "Сегодня" button with a dropdown picker (▾) showing current start date and visible days count - Picker lets user choose any start date + days count (14/30/45/60д) - "Сегодня" quick-reset inside picker + "Показать" confirms navigation - Arrows now step by visibleDays instead of fixed 14 - Moved By-cell / By-range toggle into the left nav group (next to arrows) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useState, useRef, useCallback, useMemo } from 'react'
|
||||
import { useState, useRef, useCallback, useMemo, useEffect } from 'react'
|
||||
import { addDays, format, parseISO, isWithinInterval, startOfDay, getDay, isSameDay } from 'date-fns'
|
||||
import { ru } from 'date-fns/locale'
|
||||
import {
|
||||
ChevronLeft, ChevronRight, X, Check, CalendarDays,
|
||||
Plus, Pencil, Trash2, AlertCircle, RefreshCw, MousePointer2, Rows3,
|
||||
Plus, Pencil, Trash2, AlertCircle, RefreshCw, MousePointer2, Rows3, ChevronDown,
|
||||
} from 'lucide-react'
|
||||
import { cn } from '../lib/utils'
|
||||
import {
|
||||
@@ -640,11 +640,26 @@ export function AvailabilityPage() {
|
||||
const [periodModal, setPeriodModal] = useState<RatePeriod | null | 'new'>(null)
|
||||
const [cellEditMode, setCellEditMode] = useState(false)
|
||||
const [selectedCat, setSelectedCat] = useState<string | null>(null)
|
||||
const [visibleDays, setVisibleDays] = useState(45)
|
||||
const [showNavPicker, setShowNavPicker] = useState(false)
|
||||
const [pickerDate, setPickerDate] = useState(format(new Date(), DATE_FMT))
|
||||
const navPickerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!showNavPicker) return
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (navPickerRef.current && !navPickerRef.current.contains(e.target as Node)) {
|
||||
setShowNavPicker(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handler)
|
||||
return () => document.removeEventListener('mousedown', handler)
|
||||
}, [showNavPicker])
|
||||
|
||||
const dates = useMemo(() =>
|
||||
Array.from({ length: DAYS }, (_, i) =>
|
||||
Array.from({ length: visibleDays }, (_, i) =>
|
||||
format(addDays(today, offset + i), DATE_FMT),
|
||||
), [today, offset])
|
||||
), [today, offset, visibleDays])
|
||||
|
||||
// ── Mouse handlers ──
|
||||
const handleCellDown = (date: string, catId: string) => {
|
||||
@@ -741,6 +756,7 @@ export function AvailabilityPage() {
|
||||
{/* ── Top bar ── */}
|
||||
<div className="shrink-0 px-5 py-3 bg-white dark:bg-slate-800 border-b border-slate-200 dark:border-slate-700 flex items-center justify-between gap-4 flex-wrap">
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Tabs */}
|
||||
<div className="flex bg-slate-100 dark:bg-slate-700 rounded-lg p-0.5">
|
||||
{([['grid', 'Сетка цен'], ['periods', 'Периоды']] as const).map(([t, label]) => (
|
||||
<button
|
||||
@@ -760,18 +776,114 @@ export function AvailabilityPage() {
|
||||
|
||||
{tab === 'grid' && (
|
||||
<div className="flex items-center gap-1">
|
||||
<button onClick={() => setOffset(o => o - 14)} className="btn-ghost p-1.5">
|
||||
<button onClick={() => setOffset(o => o - visibleDays)} className="btn-ghost p-1.5">
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
|
||||
{/* Date/period picker trigger */}
|
||||
<div className="relative" ref={navPickerRef}>
|
||||
<button
|
||||
onClick={() => setOffset(0)}
|
||||
className={cn('btn-ghost text-xs px-2 py-1', offset === 0 && 'text-brand-600 font-semibold')}
|
||||
onClick={() => setShowNavPicker(v => !v)}
|
||||
className={cn(
|
||||
'btn-ghost flex items-center gap-1 text-xs px-2.5 py-1.5 rounded-lg',
|
||||
showNavPicker && 'bg-slate-100 dark:bg-slate-700',
|
||||
)}
|
||||
>
|
||||
<CalendarDays size={13} className={offset === 0 ? 'text-brand-600' : 'text-slate-400'} />
|
||||
<span className={cn('font-medium', offset === 0 ? 'text-brand-600' : 'text-slate-600 dark:text-slate-300')}>
|
||||
{offset === 0
|
||||
? 'Сегодня'
|
||||
: format(addDays(today, offset), 'd MMM', { locale: ru })}
|
||||
</span>
|
||||
<span className="text-slate-400">·</span>
|
||||
<span className="text-slate-400">{visibleDays}д</span>
|
||||
<ChevronDown size={12} className="text-slate-400" />
|
||||
</button>
|
||||
|
||||
{showNavPicker && (
|
||||
<div className="absolute top-full left-0 mt-1 z-50 bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 shadow-lg p-4 w-64">
|
||||
{/* Start date */}
|
||||
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">
|
||||
Начало периода
|
||||
</p>
|
||||
<div className="flex gap-2 mb-3">
|
||||
<input
|
||||
type="date"
|
||||
value={pickerDate}
|
||||
onChange={e => setPickerDate(e.target.value)}
|
||||
className="input text-sm py-1.5 flex-1"
|
||||
/>
|
||||
<button
|
||||
onClick={() => {
|
||||
setPickerDate(format(today, DATE_FMT))
|
||||
setOffset(0)
|
||||
}}
|
||||
className="btn-secondary text-xs px-2.5 py-1.5 shrink-0"
|
||||
>
|
||||
Сегодня
|
||||
</button>
|
||||
<button onClick={() => setOffset(o => o + 14)} className="btn-ghost p-1.5">
|
||||
</div>
|
||||
|
||||
{/* Days count */}
|
||||
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">
|
||||
Количество дней
|
||||
</p>
|
||||
<div className="flex gap-1.5 flex-wrap mb-4">
|
||||
{[14, 30, 45, 60].map(d => (
|
||||
<button
|
||||
key={d}
|
||||
onClick={() => setVisibleDays(d)}
|
||||
className={cn(
|
||||
'px-3 py-1 rounded-lg text-xs font-medium border transition-colors',
|
||||
visibleDays === d
|
||||
? 'bg-brand-600 border-brand-600 text-white'
|
||||
: 'bg-white dark:bg-slate-700 border-slate-200 dark:border-slate-600 text-slate-600 dark:text-slate-300 hover:border-brand-400',
|
||||
)}
|
||||
>
|
||||
{d}д
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
const diff = Math.round(
|
||||
(parseISO(pickerDate).getTime() - startOfDay(today).getTime()) / 86400000
|
||||
)
|
||||
setOffset(diff)
|
||||
setShowNavPicker(false)
|
||||
}}
|
||||
className="btn-primary w-full justify-center text-sm py-1.5"
|
||||
>
|
||||
Показать
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button onClick={() => setOffset(o => o + visibleDays)} className="btn-ghost p-1.5">
|
||||
<ChevronRight size={16} />
|
||||
</button>
|
||||
|
||||
{/* Cell / range mode toggle */}
|
||||
<button
|
||||
onClick={() => {
|
||||
setCellEditMode(v => !v)
|
||||
setSelectedCat(null)
|
||||
setSelection(null)
|
||||
setShowEditPanel(false)
|
||||
}}
|
||||
title={cellEditMode ? 'Режим ячейки: редактирует только выбранную категорию' : 'Режим диапазона: редактирует все категории'}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-colors border',
|
||||
cellEditMode
|
||||
? 'bg-brand-50 dark:bg-brand-900/30 border-brand-300 dark:border-brand-700 text-brand-700 dark:text-brand-300'
|
||||
: 'bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 text-slate-500 hover:text-slate-700 dark:hover:text-slate-300',
|
||||
)}
|
||||
>
|
||||
{cellEditMode ? <MousePointer2 size={13} /> : <Rows3 size={13} />}
|
||||
{cellEditMode ? 'По ячейке' : 'По диапазону'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -798,27 +910,6 @@ export function AvailabilityPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === 'grid' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setCellEditMode(v => !v)
|
||||
setSelectedCat(null)
|
||||
setSelection(null)
|
||||
setShowEditPanel(false)
|
||||
}}
|
||||
title={cellEditMode ? 'Режим ячейки включён — редактирует только выбранную категорию' : 'Режим диапазона — редактирует все категории'}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors border',
|
||||
cellEditMode
|
||||
? 'bg-brand-50 dark:bg-brand-900/30 border-brand-300 dark:border-brand-700 text-brand-700 dark:text-brand-300'
|
||||
: 'bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 text-slate-500 hover:text-slate-700 dark:hover:text-slate-300',
|
||||
)}
|
||||
>
|
||||
{cellEditMode ? <MousePointer2 size={13} /> : <Rows3 size={13} />}
|
||||
{cellEditMode ? 'По ячейке' : 'По диапазону'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{tab === 'periods' && (
|
||||
<button
|
||||
onClick={() => setPeriodModal('new')}
|
||||
|
||||
Reference in New Issue
Block a user