diff --git a/CHANGELOG.md b/CHANGELOG.md index db68f7e..db85433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- TimeRangePicker - prop to hide `Default` +- TimeRangePicker - `maxRangeMins` prop to set set a max range in selected date/times + +## [1.22.0] - 2024-03-12 + ### Changed - FilterBar - minor refactor diff --git a/src/components/time-range-picker/README.md b/src/components/time-range-picker/README.md index 3e09082..78359c9 100644 --- a/src/components/time-range-picker/README.md +++ b/src/components/time-range-picker/README.md @@ -23,6 +23,8 @@ function MyComponent() { ### Props - date (date): The default time period +- maxRangeMins (int): The max time range allowed, in minutes +- hideDefault (boolean): If true, hides the `Default` selection - onChange (function): A function that is called when the user selects a time range. The function is passed an abject in the following format: ```javascript diff --git a/src/components/time-range-picker/index.js b/src/components/time-range-picker/index.js index 3ca8a5a..657980f 100644 --- a/src/components/time-range-picker/index.js +++ b/src/components/time-range-picker/index.js @@ -2,13 +2,14 @@ import React, { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { + Button, Icon, + List, + ListItem, Popover, PopoverTrigger, PopoverBody, - List, - ListItem, - Button, + Tooltip, } from 'nr1'; import DateTimePicker from '../date-time-picker'; @@ -20,6 +21,7 @@ const TEXTS = { CANCEL: 'Cancel', CUSTOM: 'Custom', DEFAULT: 'Default', + SELECT: 'Select', }; const TIME_RANGES = [ @@ -37,31 +39,73 @@ const TIME_RANGES = [ { break: true }, ]; -const normalizedDateTime = (dt = new Date()) => - new Date( - dt.getFullYear(), - dt.getMonth(), - dt.getDate(), - dt.getHours(), - dt.getMinutes() - ); +const normalizedDateTime = (dt = new Date()) => new Date(dt.setSeconds(0, 0)); + +const formattedText = (num, txt) => { + if (!num || !txt) return ''; + return `${num} ${txt}${num > 1 ? 's' : ''}`; +}; + +const displayMins = (minutes) => { + if (!minutes) return ''; + const minsInHour = 60, + minsInDay = 60 * 24; + if (minutes < minsInHour) return formattedText(minutes, 'minute'); + const mins = minutes % minsInHour; + if (minutes < minsInDay) { + const hours = Math.floor(minutes / minsInHour); + return `${formattedText(hours, 'hour')} ${formattedText(mins, 'minute')}`; + } + const days = Math.floor(minutes / minsInDay); + const hours = Math.floor((minutes - days * minsInDay) / minsInHour); + return `${formattedText(days, 'day')} ${formattedText( + hours, + 'hour' + )} ${formattedText(mins, 'minute')}`; +}; -const TimeRangePicker = ({ timeRange, onChange }) => { +const TimeRangePicker = ({ + timeRange, + maxRangeMins, + hideDefault, + onChange, +}) => { const [opened, setOpened] = useState(false); const [isCustomOpen, setIsCustomOpen] = useState(false); + const [customSaveDisabled, setCustomSaveDisabled] = useState(true); const [selected, setSelected] = useState(''); const [beginTime, setBeginTime] = useState(); const [endTime, setEndTime] = useState(); + const [filteredTimeRanges, setFilteredTimeRanges] = useState(TIME_RANGES); + + useEffect( + () => + setFilteredTimeRanges(() => { + if (!hideDefault && !maxRangeMins) return TIME_RANGES; + return TIME_RANGES.reduce((acc, tr, idx) => { + if (hideDefault && idx < 2) return acc; + if ( + (tr.break && !acc[acc.length - 1]?.break) || + !maxRangeMins || + tr.offset / 60000 <= maxRangeMins + ) + return [...acc, tr]; + return acc; + }, []); + }), + [maxRangeMins, hideDefault] + ); useEffect(() => { + const fallbackTimeRangeLabel = hideDefault ? TEXTS.SELECT : TEXTS.DEFAULT; if (!timeRange) { - setSelected(TEXTS.DEFAULT); + setSelected(fallbackTimeRangeLabel); setBeginTime(null); setEndTime(null); } else if (timeRange.duration) { setSelected( - TIME_RANGES.find((tr) => tr.offset === timeRange.duration)?.label || - TEXTS.DEFAULT + filteredTimeRanges.find((tr) => tr.offset === timeRange.duration) + ?.label || fallbackTimeRangeLabel ); setBeginTime(null); setEndTime(null); @@ -80,7 +124,17 @@ const TimeRangePicker = ({ timeRange, onChange }) => { setEndTime(e); } } - }, [timeRange]); + }, [timeRange, filteredTimeRanges, hideDefault]); + + useEffect(() => { + if (!beginTime || !endTime) { + setCustomSaveDisabled(true); + } else if (maxRangeMins && (endTime - beginTime) / 60000 > maxRangeMins) { + setCustomSaveDisabled(true); + } else { + setCustomSaveDisabled(false); + } + }, [beginTime, endTime, maxRangeMins]); const setDurationHandler = (duration) => { if (onChange) @@ -161,7 +215,7 @@ const TimeRangePicker = ({ timeRange, onChange }) => {
- {TIME_RANGES.map((tr, i) => ( + {filteredTimeRanges.map((tr, i) => ( {tr.break ? (
@@ -211,13 +265,22 @@ const TimeRangePicker = ({ timeRange, onChange }) => { validTill={normalizedDateTime()} />
- + +