import React, { Fragment } from "react";
import { withRouter } from "react-router-dom";
import Flatpickr from "react-flatpickr";
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { Transition } from '@headlessui/react'
import {
shiftDays,
shiftMonths,
formatDay,
formatDayShort,
formatMonthYYYY,
formatYear,
formatISO,
isToday,
lastMonth,
nowForSite,
isSameMonth,
isThisMonth,
isThisYear,
parseUTCDate,
isBefore,
isAfter,
} from "./util/date";
import { navigateToQuery, QueryLink, QueryButton } from "./query";
import { shouldIgnoreKeypress } from "./keybinding.js"
function renderArrow(query, site, period, prevDate, nextDate) {
const insertionDate = parseUTCDate(site.statsBegin);
const disabledLeft = isBefore(
parseUTCDate(prevDate),
insertionDate,
period
);
const disabledRight = isAfter(
parseUTCDate(nextDate),
nowForSite(site),
period
);
const leftClasses = `flex items-center px-1 sm:px-2 border-r border-gray-300 rounded-l
dark:border-gray-500 dark:text-gray-100 ${
disabledLeft ? "bg-gray-300 dark:bg-gray-950" : "hover:bg-gray-100 dark:hover:bg-gray-900"
}`;
const rightClasses = `flex items-center px-1 sm:px-2 rounded-r dark:text-gray-100 ${
disabledRight ? "bg-gray-300 dark:bg-gray-950" : "hover:bg-gray-100 dark:hover:bg-gray-900"
}`;
return (
);
}
function DatePickerArrows({site, query}) {
if (query.period === "year") {
const prevDate = formatISO(shiftMonths(query.date, -12));
const nextDate = formatISO(shiftMonths(query.date, 12));
return renderArrow(query, site, "year", prevDate, nextDate);
} else if (query.period === "month") {
const prevDate = formatISO(shiftMonths(query.date, -1));
const nextDate = formatISO(shiftMonths(query.date, 1));
return renderArrow(query, site, "month", prevDate, nextDate);
} else if (query.period === "day") {
const prevDate = formatISO(shiftDays(query.date, -1));
const nextDate = formatISO(shiftDays(query.date, 1));
return renderArrow(query, site, "day", prevDate, nextDate);
}
return null
}
class DatePicker extends React.Component {
constructor(props) {
super(props);
this.handleKeydown = this.handleKeydown.bind(this);
this.handleClick = this.handleClick.bind(this);
this.setCustomDate = this.setCustomDate.bind(this);
this.openCalendar = this.openCalendar.bind(this);
this.close = this.close.bind(this);
this.toggle = this.toggle.bind(this);
this.state = { mode: "menu", open: false };
}
componentDidMount() {
document.addEventListener("keydown", this.handleKeydown);
document.addEventListener("mousedown", this.handleClick, false);
}
componentWillUnmount() {
document.removeEventListener("keydown", this.handleKeydown);
document.removeEventListener("mousedown", this.handleClick, false);
}
handleKeydown(e) {
const { query, history } = this.props;
if (shouldIgnoreKeypress(e)) return true
const newSearch = {
period: false,
from: false,
to: false,
date: false
};
const insertionDate = parseUTCDate(this.props.site.statsBegin);
if (e.key === "ArrowLeft") {
const prevDate = formatISO(shiftDays(query.date, -1));
const prevMonth = formatISO(shiftMonths(query.date, -1));
const prevYear = formatISO(shiftMonths(query.date, -12));
if (query.period === "day" && !isBefore(parseUTCDate(prevDate), insertionDate, query.period)) {
newSearch.period = "day";
newSearch.date = prevDate;
} else if (query.period === "month" && !isBefore(parseUTCDate(prevMonth), insertionDate, query.period)) {
newSearch.period = "month";
newSearch.date = prevMonth;
} else if (query.period === "year" && !isBefore(parseUTCDate(prevYear), insertionDate, query.period)) {
newSearch.period = "year";
newSearch.date = prevYear;
}
} else if (e.key === "ArrowRight") {
const now = nowForSite(this.props.site)
const nextDate = formatISO(shiftDays(query.date, 1));
const nextMonth = formatISO(shiftMonths(query.date, 1));
const nextYear = formatISO(shiftMonths(query.date, 12));
if (query.period === "day" && !isAfter(parseUTCDate(nextDate), now, query.period)) {
newSearch.period = "day";
newSearch.date = nextDate;
} else if (query.period === "month" && !isAfter(parseUTCDate(nextMonth), now, query.period)) {
newSearch.period = "month";
newSearch.date = nextMonth;
} else if (query.period === "year" && !isAfter(parseUTCDate(nextYear), now, query.period)) {
newSearch.period = "year";
newSearch.date = nextYear;
}
}
this.setState({open: false});
const keys = ['d', 'e', 'r', 'w', 'm', 'y', 't', 's', 'l', 'a'];
const redirects = [{date: false, period: 'day'}, {date: formatISO(shiftDays(nowForSite(this.props.site), -1)), period: 'day'}, {period: 'realtime'}, {date: false, period: '7d'}, {date: false, period: 'month'}, {date: false, period: 'year'}, {date: false, period: '30d'}, {date: false, period: '6mo'}, {date: false, period: '12mo'}, {date: false, period: 'all'}];
if (keys.includes(e.key.toLowerCase())) {
navigateToQuery(history, query, {...newSearch, ...(redirects[keys.indexOf(e.key.toLowerCase())])});
} else if (e.key.toLowerCase() === 'c') {
this.setState({mode: 'calendar', open: true}, this.openCalendar);
} else if (newSearch.date) {
navigateToQuery(history, query, newSearch);
}
}
handleClick(e) {
if (this.dropDownNode && this.dropDownNode.contains(e.target)) return;
this.setState({ open: false });
}
setCustomDate(dates) {
if (dates.length === 2) {
const [from, to] = dates
if (formatISO(from) === formatISO(to)) {
navigateToQuery(
this.props.history,
this.props.query,
{
period: 'day',
date: formatISO(from),
from: false,
to: false
}
)
} else {
navigateToQuery(
this.props.history,
this.props.query,
{
period: 'custom',
date: false,
from: formatISO(from),
to: formatISO(to),
}
)
}
this.close()
}
}
timeFrameText() {
const { query, site } = this.props;
if (query.period === "day") {
if (isToday(site, query.date)) {
return "Today";
}
return formatDay(query.date);
} if (query.period === '7d') {
return 'Last 7 days'
} if (query.period === '30d') {
return 'Last 30 days'
} if (query.period === 'month') {
if (isThisMonth(site, query.date)) {
return 'Month to Date'
}
return formatMonthYYYY(query.date)
} if (query.period === '6mo') {
return 'Last 6 months'
} if (query.period === '12mo') {
return 'Last 12 months'
} if (query.period === 'year') {
if (isThisYear(site, query.date)) {
return 'Year to Date'
}
return formatYear(query.date)
} if (query.period === 'all') {
return 'All time'
} if (query.period === 'custom') {
return `${formatDayShort(query.from)} - ${formatDayShort(query.to)}`
}
return 'Realtime'
}
toggle() {
const newMode = this.state.mode === 'calendar' && !this.state.open ? 'menu' : this.state.mode
this.setState(prevState => ({ mode: newMode, open: !prevState.open }));
}
close() {
this.setState({ open: false });
}
openCalendar() {
this.calendar && this.calendar.flatpickr.open();
}
renderLink(period, text, opts = {}) {
const { query, site } = this.props;
let boldClass;
if (query.period === "day" && period === "day") {
boldClass = isToday(site, query.date) ? "font-bold" : "";
} else if (query.period === "month" && period === "month") {
const linkDate = opts.date || nowForSite(site);
boldClass = isSameMonth(linkDate, query.date) ? "font-bold" : "";
} else {
boldClass = query.period === period ? "font-bold" : "";
}
opts.date = opts.date ? formatISO(opts.date) : false;
const keybinds = {
'Today': 'D',
'Realtime': 'R',
'Last 7 days': 'W',
'Month to Date': 'M',
'Year to Date': 'Y',
'Last 12 months': 'L',
'Last 30 days': 'T',
'All time': 'A',
};
return (
{text}
{keybinds[text]}
);
}
renderDropDownContent() {
const { site } = this.props
if (this.state.mode === "menu") {
return (
);
} if (this.state.mode === "calendar") {
const insertionDate = new Date(this.props.site.statsBegin);
const dayBeforeCreation = insertionDate - 86400000;
return (
this.calendar = calendar}
className="invisible"
onChange={this.setCustomDate}
/>
)
}
}
renderPicker() {
return (
(this.dropDownNode = node)}
>
{this.props.leadingText}
{this.timeFrameText()}
{this.renderDropDownContent()}
);
}
render() {
return (
{this.renderPicker()}
)
}
}
export default withRouter(DatePicker);