import * as React from "react"; import { useState } from "react"; import { blue } from "@mui/material/colors"; import { styled } from "@mui/material/styles"; import { AdapterLuxon } from "@mui/x-date-pickers/AdapterLuxon"; import { DateCalendar } from "@mui/x-date-pickers/DateCalendar"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { PickersDay as MuiPickersDay, PickersDayProps as MuiPickersDayProps, } from "@mui/x-date-pickers/PickersDay"; import { DateTime } from "luxon"; import { useDashboardFilter } from "../../../../contexts/DashboardFilterContext"; export type Selecting = "start" | "end"; export interface PickerDayProps extends MuiPickersDayProps { isSelected: boolean; isHovered: boolean; isStart: boolean; isEnd: boolean; selecting: Selecting; } // TODO Clean up // TODO Use theme colors instead of blue export const PickersDay = styled(MuiPickersDay, { shouldForwardProp: (prop) => prop !== "isSelected" && prop !== "isHovered" && prop !== "isStart" && prop !== "isEnd", })(({ theme, isSelected, isHovered, isStart, isEnd, day, selecting }) => ({ borderRadius: 0, boxSizing: "border-box", border: "2px solid transparent", width: 40, height: 40, "&.MuiPickersDay-today": { borderColor: "transparent", "& .content": { border: `1px solid ${theme.palette.grey[500]}`, borderRadius: "50%", width: 40, height: 40, }, }, "& .content": { boxSizing: "border-box", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1, position: "absolute", borderRadius: "50%", }, "&:hover .content": { border: `1px solid ${theme.palette.grey[800]}`, boxSizing: "border-box", width: 40, height: 40, }, ...(isSelected && { backgroundColor: blue[50], "&:hover, &:focus": { backgroundColor: blue[50], }, "&:hover .content": { backgroundColor: blue[100], width: 40, height: 40, }, }), ...((isStart || isEnd) && { "& .content": { display: "flex", alignItems: "center", justifyContent: "center", color: theme.palette.primary.contrastText, zIndex: 0, position: "absolute", width: 40, height: 40, backgroundColor: (isStart && selecting === "end") || (isEnd && selecting === "start") ? blue[500] : blue[300], borderRadius: "50%", }, }), ...(isStart && { backgroundColor: blue[50], color: theme.palette.primary.contrastText, borderTopLeftRadius: "50%", borderBottomLeftRadius: "50%", position: "relative", "&:hover, &:focus": { backgroundColor: blue[50], }, }), ...(isEnd && { backgroundColor: blue[50], color: theme.palette.primary.contrastText, borderTopRightRadius: "50%", borderBottomRightRadius: "50%", "&:hover, &:focus": { backgroundColor: blue[50], }, }), ...(isHovered && { boxSizing: "border-box", borderTop: `2px dashed ${theme.palette.grey[300]}`, borderBottom: `2px dashed ${theme.palette.grey[300]}`, "& *": { boxSizing: "border-box", }, }), ...(day.weekday === 1 && { borderTopLeftRadius: "50%", borderBottomLeftRadius: "50%", }), ...(day.weekday === 7 && { borderTopRightRadius: "50%", borderBottomRightRadius: "50%", }), })) as React.ComponentType; const isInRange = (day: DateTime, startDate: DateTime, endDate: DateTime) => { if (day.startOf("day") >= startDate.startOf("day") && day.endOf("day") <= endDate.endOf("day")) { return true; } return false; }; const getHovered = ( day: DateTime, hoveredDay: DateTime, startDate: DateTime, endDate: DateTime, selecting: "start" | "end", ) => { if (selecting === "start") { return ( day.startOf("day") < startDate.startOf("day") && day.endOf("day") > hoveredDay?.startOf("day") ); } else { return ( day.startOf("day") > endDate.startOf("day") && day.endOf("day") < hoveredDay?.startOf("day") ); } }; export interface DayProps extends MuiPickersDayProps { selecting: "start" | "end"; startDate: DateTime; endDate: DateTime; selectedDay: DateTime; hoveredDay: DateTime; } export const Day: React.FC = ({ day, selectedDay, hoveredDay, startDate, endDate, selecting, ...props }) => { const isStart = startDate?.hasSame(day, "day") ?? false; const isEnd = endDate?.hasSame(day, "day") ?? false; return ( {day.toFormat("d")} ); }; export const DateRangePicker: React.FC = () => { const { filter, setFilter } = useDashboardFilter(); const [hoveredDay, setHoveredDay] = useState(null); const [value, setValue] = useState(DateTime.now()); const [selecting, setSelecting] = useState("start"); /** * Decide whether to set start or end date, * trying to match Mui X DateRange behavior */ const handleChange = (date: DateTime) => { setValue(date); if (selecting === "start") { if (date >= filter.endDate) { setFilter({ ...filter, startDate: date, endDate: date }); } else { setFilter({ ...filter, startDate: date }); } setSelecting("end"); } else { if (date < filter.startDate) { setFilter({ ...filter, startDate: date, endDate: date }); setSelecting("end"); } else { setFilter({ ...filter, endDate: date }); setSelecting("start"); } } }; return ( ({ selecting, endDate: filter.endDate, startDate: filter.startDate, selectedDay: value, hoveredDay, onPointerEnter: () => setHoveredDay(ownerState.day), onPointerLeave: () => setHoveredDay(null), }), }} slots={{ day: Day as typeof MuiPickersDay }} value={value} onChange={handleChange} /> ); }; export default DateRangePicker;