Skip to Content
UIComponentsDateTimeRangePicker

DateTimeRangePicker

Description

The date time range picker.

Parameters

{ start, end, hideTime, useSeconds, preselectOptions, onChange, ...rest }
DateTimeRangePickerProps

=>

react_jsx_runtime.JSX.Element

Returns date time range picker component.

Example

Inspect

Source code

/packages/react-ui/DateTimeRangePicker/DateTimeRangePicker.tsx
import { Button, Grid, Popover, TextField, useMediaQuery, type Theme } from '@mui/material'; import { ComponentProps, MouseEvent, useEffect, useMemo, useState } from 'react'; import { StaticDateRangePicker, LocalizationProvider } from '@mui/x-date-pickers-pro'; import { AdapterDateFns } from '@mui/x-date-pickers-pro/AdapterDateFnsV3'; import { hr } from 'date-fns/locale/hr'; import { parse, format, isSameDay, startOfDay, endOfDay, startOfYesterday, endOfYesterday, sub, startOfMonth, endOfMonth, intervalToDuration, Duration } from 'date-fns'; import { TimeInput } from '../TimeInput'; import { DateTimeRangePickerInput } from './DateTimeRangePickerInput'; import { Stack } from '@mui/system'; // TODO: Test cases // - Test if start date format shows only date when time is 00:00 // - Test if end date format shows only date when time is 23:59 // - Test if start date and start time is combined correctly (without time-zone offsets) // - Test if end date and end time is combined correctly (without time-zone offsets) function durationGetTime(duration: Duration) { return (typeof duration.years !== 'undefined' ? duration.years * 365 * 24 * 60 * 60 * 1000 : 0) + (typeof duration.months !== 'undefined' ? duration.months * 30 * 24 * 60 * 60 * 1000 : 0) + (typeof duration.weeks !== 'undefined' ? duration.weeks * 7 * 24 * 60 * 60 * 1000 : 0) + (typeof duration.days !== 'undefined' ? duration.days * 24 * 60 * 60 * 1000 : 0) + (typeof duration.hours !== 'undefined' ? duration.hours * 60 * 60 * 1000 : 0) + (typeof duration.minutes !== 'undefined' ? duration.minutes * 60 * 1000 : 0) + (typeof duration.seconds !== 'undefined' ? duration.seconds * 1000 : 0); } /** * The date time range picker preselect option. * @public */ export type DateTimeRangePickerPreselectOption = { name: string, startDate: Date, endDate: Date }; /** * The date time range picker props. * @public */ export type DateTimeRangePickerProps = Omit<ComponentProps<typeof TextField>, "onClick" | "value" | "title" | "onChange"> & { start: Date; end: Date; hideTime?: boolean; useSeconds?: boolean; dense?: boolean; preselectOptions?: DateTimeRangePickerPreselectOption[]; onChange: (startDate: Date, endDate: Date) => void; }; /** * The date time range picker. * * @param props - The props; * @returns Returns date time range picker component. * @public */ export function DateTimeRangePicker({ start, end, hideTime, useSeconds, preselectOptions, onChange, ...rest }: DateTimeRangePickerProps) { const [anchorEl, setAnchorEl] = useState<HTMLDivElement | null>(null); const [startTime, setStartTime] = useState(format(start, 'HH:mm')); const [endTime, setEndTime] = useState(format(end, 'HH:mm')); // Use js dates because we use date-fns adapter const defaultStartDate = startOfDay(start); const defaultEndDate = startOfDay(end); const [dateValue, setDateValue] = useState<[Date | null, Date | null]>([defaultStartDate, defaultEndDate]); const isDesktop = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm')); // Reset cached value when props change (only when not in popover) useEffect(() => { if (!anchorEl) { setDateValue([defaultStartDate, defaultEndDate]); } }, [start, end]); /** * Handles the picker input click. * This will open picker popover. * * @param event - The click event */ const handleOpen = (event: MouseEvent<HTMLDivElement>) => { setAnchorEl(event.currentTarget); }; /** * Handles the picker close. * This will close the picker and reset selection. */ const handleClose = () => { setAnchorEl(null); setDateValue([defaultStartDate, defaultEndDate]); setStartTime(format(start, 'HH:mm')); setEndTime(format(end, 'HH:mm')); }; /** * Combines the date and time into Date objects. * * @returns First element is start date and time, seconds is end date and time. */ const combineDateAndTime = () => { if (dateValue[0] == null || dateValue[1] == null) return []; let startDurationTime = durationGetTime(intervalToDuration({ start: startOfDay(new Date()), end: parse(startTime, "HH:mm", new Date()) })); const endDurationTime = durationGetTime(intervalToDuration({ start: startOfDay(new Date()), end: parse(endTime, "HH:mm", new Date()) })); // Check if start is after end - set equal if (dateValue[0] != null && dateValue[1] != null && isSameDay(dateValue[0], dateValue[1]) && startDurationTime > endDurationTime) { startDurationTime = endDurationTime; } const startDateTime = dateValue[0] ? startOfDay(dateValue[0]) : undefined; if (startDateTime != null) { startDateTime.setMilliseconds(startDateTime.getMilliseconds() + startDurationTime); } const endDateTime = dateValue[1] ? startOfDay(dateValue[1]) : undefined; if (endDateTime != null) { endDateTime.setMilliseconds(endDateTime.getMilliseconds() + endDurationTime); } return [startDateTime, endDateTime]; }; /** * Handles the picker accept option. */ const handleAccept = () => { const combined = combineDateAndTime(); if (combined[0] != null && combined[1] != null) { onChange(combined[0], combined[1]); setAnchorEl(null); } }; /** * Handle the preselect option click. * This will set start and end date to preselected values. * * @param startDate The start date and time. * @param endDate The end date and time. */ const handlePreselect = (startDate: Date, endDate: Date) => { setDateValue([startDate, endDate]); }; const open = Boolean(anchorEl); const id = open ? 'simple-popover' : undefined; const preselectedOptionsOrDefault: { name: string, startDate: Date, endDate: Date }[] = useMemo(() => { if (preselectOptions) return preselectOptions; return [ { name: 'Danas', startDate: startOfDay(new Date()), endDate: endOfDay(new Date()) }, { name: 'Jučer', startDate: startOfYesterday(), endDate: endOfYesterday() }, { name: 'Proteklih 7 dana', startDate: sub(startOfDay(new Date()), { days: 7 }), endDate: endOfDay(new Date()) }, { name: 'Proteklih 30 dana', startDate: sub(startOfDay(new Date()), { days: 30 }), endDate: endOfDay(new Date()) }, { name: 'Ovaj mjesec', startDate: startOfMonth(new Date()), endDate: endOfDay(new Date()) }, { name: 'Prošli mjesec', startDate: sub(startOfMonth(new Date()), { months: 1 }), endDate: endOfMonth(sub(startOfMonth(new Date()), { months: 1 })) } ]; }, [preselectOptions]); return ( <> <DateTimeRangePickerInput onClick={handleOpen} start={start} end={end} hideTime={hideTime} useSeconds={useSeconds} {...rest} /> <Popover id={id} open={open} anchorEl={anchorEl} onClose={handleClose} anchorOrigin={{ vertical: 'bottom', horizontal: 'left', }} transformOrigin={{ vertical: 'top', horizontal: 'left', }} > <Grid container sx={{ maxWidth: { md: '840px' }, maxHeight: { md: '496px' }, overflow: { md: 'hidden' }, flexDirection: { md: 'row', lg: 'column' } }} justifyContent="center" > <Grid size={{ xs: 12, md: 3 }}> <Stack spacing={1} sx={{ p: 2 }}> <Stack direction="row" spacing={1}> <Button variant="contained" fullWidth onClick={handleAccept}>Potvrdi</Button> <Button variant="text" fullWidth onClick={handleClose}>Odustani</Button> </Stack> {preselectedOptionsOrDefault.map((opt) => ( <Button key={opt.name} variant="outlined" fullWidth title={`${opt.startDate} ${opt.endDate}`} onClick={() => handlePreselect(opt.startDate, opt.endDate)} > {opt.name} </Button> ))} </Stack> </Grid> <Grid size={{ xs: 12, md: 9 }} sx={{ maxWidth: '662px', overflow: 'hidden' }}> <Stack justifyContent="center"> <Stack direction="row" spacing={1} sx={{ p: 2, pb: 1 }}> <TextField type="date" value={dateValue[0] ? format(dateValue[0], 'yyyy-MM-dd') : ''} onChange={(e) => setDateValue([new Date(e.target.value), dateValue[1]])} fullWidth size="small" label="Od" /> <TextField type="date" value={dateValue[1] ? format(dateValue[1], 'yyyy-MM-dd') : ''} onChange={(e) => setDateValue([dateValue[0], new Date(e.target.value)])} fullWidth size="small" label="Do" /> </Stack> {!hideTime && ( <Stack direction="row" spacing={1} sx={{ pb: 2, px: 2 }}> <TimeInput value={startTime} onTimeChange={setStartTime} useSeconds={useSeconds} size="small" fullWidth /> <TimeInput value={endTime} onTimeChange={setEndTime} useSeconds={useSeconds} size="small" fullWidth /> </Stack> )} <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={hr}> <StaticDateRangePicker value={dateValue} onChange={setDateValue} disableFuture displayStaticWrapperAs="desktop" calendars={isDesktop ? 2 : 1} // renderInput={(startProps, endProps) => ( // <Stack direction="row" spacing={2}> // <TextField {...startProps} variant="standard" /> // <TextField {...endProps} variant="standard" /> // </Stack> // )} /> </LocalizationProvider> </Stack> </Grid> </Grid> </Popover> </> ); };
Last updated on