analytics/assets/js/dashboard/nav-menu/query-periods/query-period-menu.tsx

225 lines
6.1 KiB
TypeScript

import React, { useMemo, useRef } from 'react'
import classNames from 'classnames'
import { useQueryContext } from '../../query-context'
import { useSiteContext } from '../../site-context'
import {
BlurMenuButtonOnEscape,
isModifierPressed,
isTyping,
Keybind,
KeybindHint
} from '../../keybinding'
import {
AppNavigationLink,
useAppNavigate
} from '../../navigation/use-app-navigate'
import {
getCompareLinkItem,
last6MonthsLinkItem,
getDatePeriodGroups,
LinkItem,
QueryPeriod,
getCurrentPeriodDisplayName,
getSearchToApplyCustomDates,
isComparisonForbidden
} from '../../query-time-periods'
import { useMatch } from 'react-router-dom'
import { rootRoute } from '../../router'
import { Popover, Transition } from '@headlessui/react'
import { popover } from '../../components/popover'
import {
datemenuButtonClassName,
DateMenuChevron,
PopoverMenuProps,
linkClassName,
CalendarPanel,
hiddenCalendarButtonClassName
} from './shared-menu-items'
import { DateRangeCalendar } from './date-range-calendar'
import { formatISO, nowForSite } from '../../util/date'
import { MenuSeparator } from '../nav-menu-components'
function QueryPeriodMenuKeybinds({
closeDropdown,
groups
}: {
groups: LinkItem[][]
closeDropdown: () => void
}) {
const dashboardRouteMatch = useMatch(rootRoute.path)
const navigate = useAppNavigate()
if (!dashboardRouteMatch) {
return null
}
return (
<>
{groups.concat([[last6MonthsLinkItem]]).flatMap((group) =>
group
.filter(([[_name, keyboardKey]]) => !!keyboardKey)
.map(([[_name, keyboardKey], { search, onEvent }]) => (
<Keybind
key={keyboardKey}
keyboardKey={keyboardKey}
type="keydown"
handler={(e) => {
if (typeof search === 'function') {
navigate({ search })
}
if (typeof onEvent === 'function') {
onEvent(e)
} else {
closeDropdown()
}
}}
shouldIgnoreWhen={[isModifierPressed, isTyping]}
targetRef="document"
/>
))
)}
</>
)
}
export const QueryPeriodMenu = ({
closeDropdown,
calendarButtonRef
}: PopoverMenuProps) => {
const site = useSiteContext()
const { query } = useQueryContext()
const buttonRef = useRef<HTMLButtonElement>(null)
const toggleCalendar = () => {
if (typeof calendarButtonRef.current?.click === 'function') {
calendarButtonRef.current.click()
}
}
return (
<>
<BlurMenuButtonOnEscape targetRef={buttonRef} />
<Popover.Button ref={buttonRef} className={datemenuButtonClassName}>
<span className={popover.toggleButton.classNames.truncatedText}>
{getCurrentPeriodDisplayName({ query, site })}
</span>
<DateMenuChevron />
</Popover.Button>
<QueryPeriodMenuInner
toggleCalendar={toggleCalendar}
closeDropdown={closeDropdown}
/>
</>
)
}
const QueryPeriodMenuInner = ({
closeDropdown,
toggleCalendar
}: {
closeDropdown: () => void
toggleCalendar: () => void
}) => {
const site = useSiteContext()
const { query, expandedSegment } = useQueryContext()
const groups = useMemo(() => {
const compareLink = getCompareLinkItem({ site, query })
return getDatePeriodGroups({
site,
onEvent: closeDropdown,
extraItemsInLastGroup: [
[
['Custom Range', 'C'],
{
search: (s) => s,
isActive: ({ query }) => query.period === QueryPeriod.custom,
onEvent: toggleCalendar
}
]
],
extraGroups: isComparisonForbidden({
period: query.period,
segmentIsExpanded: !!expandedSegment
})
? []
: [[compareLink]]
})
}, [site, query, closeDropdown, toggleCalendar, expandedSegment])
return (
<>
<QueryPeriodMenuKeybinds closeDropdown={closeDropdown} groups={groups} />
<Transition
{...popover.transition.props}
className={classNames(
'mt-2',
popover.transition.classNames.fullwidth,
'md:left-auto md:w-56'
)}
>
<Popover.Panel
className={popover.panel.classNames.roundedSheet}
data-testid="datemenu"
>
{groups.map((group, index) => (
<React.Fragment key={index}>
{group.map(
([[label, keyboardKey], { search, isActive, onEvent }]) => (
<AppNavigationLink
key={label}
data-selected={isActive({ site, query })}
className={linkClassName}
search={search}
onClick={onEvent && ((e) => onEvent(e))}
>
{label}
{!!keyboardKey && <KeybindHint>{keyboardKey}</KeybindHint>}
</AppNavigationLink>
)
)}
{index < groups.length - 1 && <MenuSeparator />}
</React.Fragment>
))}
</Popover.Panel>
</Transition>
</>
)
}
export const MainCalendar = ({
closeDropdown,
calendarButtonRef
}: PopoverMenuProps) => {
const site = useSiteContext()
const { query } = useQueryContext()
const navigate = useAppNavigate()
return (
<>
<BlurMenuButtonOnEscape targetRef={calendarButtonRef} />
<Popover.Button
className={hiddenCalendarButtonClassName}
tabIndex={-1}
ref={calendarButtonRef}
/>
<CalendarPanel className="mt-2">
<DateRangeCalendar
id="calendar"
onCloseWithSelection={(selection) => {
navigate({
search: getSearchToApplyCustomDates(selection)
})
closeDropdown()
}}
minDate={site.statsBegin}
maxDate={formatISO(nowForSite(site))}
defaultDates={
query.from && query.to
? [formatISO(query.from), formatISO(query.to)]
: undefined
}
/>
</CalendarPanel>
</>
)
}