diff --git a/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss b/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss new file mode 100644 index 0000000000..9efbf8f17c --- /dev/null +++ b/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss @@ -0,0 +1,88 @@ +.time-options-container { + .time-options-item { + margin: 2px 0; + padding: 8px; + border-radius: 2px; + + &.active { + background-color: rgba($color: #000000, $alpha: 0.2); + + &:hover { + cursor: pointer; + background-color: rgba($color: #000000, $alpha: 0.3); + } + } + + &:hover { + cursor: pointer; + background-color: rgba($color: #000000, $alpha: 0.3); + } + } +} + +.time-selection-dropdown-content { + min-width: 172px; + width: 100%; +} + +.timeSelection-input { + display: flex; + gap: 8px; + align-items: center; + padding: 4px 8px; + padding-left: 0px !important; + + input::placeholder { + color: white; + } + + input:focus::placeholder { + color: rgba($color: #ffffff, $alpha: 0.4); + } +} + +.valid-format-error { + margin-top: 4px; + color: var(--bg-cherry-400, #ea6d71); +} + +.lightMode { + .time-options-container { + .time-options-item { + &.active { + background-color: rgba($color: #ffffff, $alpha: 0.2); + + &:hover { + cursor: pointer; + background-color: rgba($color: #ffffff, $alpha: 0.3); + } + } + + &:hover { + cursor: pointer; + background-color: rgba($color: #ffffff, $alpha: 0.3); + } + } + } + + .timeSelection-input { + display: flex; + gap: 8px; + align-items: center; + padding: 4px 8px; + padding-left: 0px !important; + + input::placeholder { + color: var(---bg-ink-300); + } + + input:focus::placeholder { + color: rgba($color: #000000, $alpha: 0.4); + } + } + + .valid-format-error { + margin-top: 4px; + color: var(--bg-cherry-400, #ea6d71); + } +} diff --git a/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx b/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx new file mode 100644 index 0000000000..c76baaf2ac --- /dev/null +++ b/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx @@ -0,0 +1,208 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +import './CustomTimePicker.styles.scss'; + +import { Input, Popover, Tooltip } from 'antd'; +import cx from 'classnames'; +import { Options } from 'container/TopNav/DateTimeSelection/config'; +import dayjs from 'dayjs'; +import debounce from 'lodash-es/debounce'; +import { CheckCircle, ChevronDown, Clock } from 'lucide-react'; +import { ChangeEvent, useEffect, useState } from 'react'; +import { popupContainer } from 'utils/selectPopupContainer'; + +interface CustomTimePickerProps { + onSelect: (value: string) => void; + items: any[]; + selectedValue: string; + selectedTime: string; + onValidCustomDateChange: ([t1, t2]: any[]) => void; +} + +function CustomTimePicker({ + onSelect, + items, + selectedValue, + selectedTime, + onValidCustomDateChange, +}: CustomTimePickerProps): JSX.Element { + const [open, setOpen] = useState(false); + const [ + selectedTimePlaceholderValue, + setSelectedTimePlaceholderValue, + ] = useState('Select / Enter Time Range'); + + const [inputValue, setInputValue] = useState(''); + const [inputStatus, setInputStatus] = useState<'' | 'error' | 'success'>(''); + const [isInputFocused, setIsInputFocused] = useState(false); + + const getSelectedTimeRangeLabel = ( + selectedTime: string, + selectedTimeValue: string, + ): string => { + if (selectedTime === 'custom') { + return selectedTimeValue; + } + + for (let index = 0; index < Options.length; index++) { + if (Options[index].value === selectedTime) { + return Options[index].label; + } + } + + return ''; + }; + + useEffect(() => { + const value = getSelectedTimeRangeLabel(selectedTime, selectedValue); + + setSelectedTimePlaceholderValue(value); + }, [selectedTime, selectedValue]); + + const hide = (): void => { + setOpen(false); + }; + + const handleOpenChange = (newOpen: boolean): void => { + setOpen(newOpen); + }; + + const debouncedHandleInputChange = debounce((inputValue): void => { + const isValidFormat = /^(\d+)([mhdw])$/.test(inputValue); + if (isValidFormat) { + setInputStatus('success'); + + const match = inputValue.match(/^(\d+)([mhdw])$/); + + const value = parseInt(match[1], 10); + const unit = match[2]; + + const currentTime = dayjs(); + let minTime = null; + + switch (unit) { + case 'm': + minTime = currentTime.subtract(value, 'minute'); + break; + + case 'h': + minTime = currentTime.subtract(value, 'hour'); + break; + case 'd': + minTime = currentTime.subtract(value, 'day'); + break; + case 'w': + minTime = currentTime.subtract(value, 'week'); + break; + default: + break; + } + + onValidCustomDateChange([minTime, currentTime]); + } else { + setInputStatus('error'); + } + }, 300); + + const handleInputChange = (event: ChangeEvent): void => { + const inputValue = event.target.value; + + if (inputValue.length > 0) { + setOpen(false); + } else { + setOpen(true); + } + + setInputValue(inputValue); + + // Call the debounced function with the input value + debouncedHandleInputChange(inputValue); + }; + + const content = ( +
+
+ {items.map(({ value, label }) => ( +
{ + onSelect(value); + setSelectedTimePlaceholderValue(label); + setInputStatus(''); + setInputValue(''); + hide(); + }} + key={value} + className={cx( + 'time-options-item', + selectedValue === value ? 'active' : '', + )} + > + {label} +
+ ))} +
+
+ ); + + const handleFocus = (): void => { + setIsInputFocused(true); + }; + + const handleBlur = (): void => { + setIsInputFocused(false); + }; + + return ( + + + ) : ( + + + + ) + } + suffix={ + { + setOpen(!open); + }} + /> + } + /> + + ); +} + +export default CustomTimePicker; diff --git a/frontend/src/container/TopNav/DateTimeSelection/index.tsx b/frontend/src/container/TopNav/DateTimeSelection/index.tsx index 4cb538cb57..0f0e47d7df 100644 --- a/frontend/src/container/TopNav/DateTimeSelection/index.tsx +++ b/frontend/src/container/TopNav/DateTimeSelection/index.tsx @@ -1,7 +1,8 @@ import { SyncOutlined } from '@ant-design/icons'; -import { Button, Select as DefaultSelect } from 'antd'; +import { Button } from 'antd'; import getLocalStorageKey from 'api/browser/localstorage/get'; import setLocalStorageKey from 'api/browser/localstorage/set'; +import CustomTimePicker from 'components/CustomTimePicker/CustomTimePicker'; import { LOCALSTORAGE } from 'constants/localStorage'; import { QueryParams } from 'constants/query'; import ROUTES from 'constants/routes'; @@ -21,7 +22,6 @@ import { GlobalTimeLoading, UpdateTimeInterval } from 'store/actions'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; import { GlobalReducer } from 'types/reducer/globalTime'; -import { popupContainer } from 'utils/selectPopupContainer'; import AutoRefresh from '../AutoRefresh'; import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal'; @@ -29,8 +29,6 @@ import { getDefaultOption, getOptions, Time } from './config'; import RefreshText from './Refresh'; import { Form, FormContainer, FormItem } from './styles'; -const { Option } = DefaultSelect; - function DateTimeSelection({ location, updateTimeInterval, @@ -211,6 +209,7 @@ function DateTimeSelection({ }; const onCustomDateHandler = (dateTimeRange: DateTimeRangeType): void => { + console.log('dateTimeRange', dateTimeRange); if (dateTimeRange !== null) { const [startTimeMoment, endTimeMoment] = dateTimeRange; if (startTimeMoment && endTimeMoment) { @@ -289,26 +288,22 @@ function DateTimeSelection({ initialValues={{ interval: selectedTime }} > - onSelectHandler(value as Time)} - value={getInputLabel( + { + onSelectHandler(value as Time); + }} + selectedTime={selectedTime} + onValidCustomDateChange={(dateTime): void => + onCustomDateHandler(dateTime as DateTimeRangeType) + } + selectedValue={getInputLabel( dayjs(minTime / 1000000), dayjs(maxTime / 1000000), selectedTime, )} data-testid="dropDown" - style={{ - minWidth: 120, - }} - listHeight={400} - > - {options.map(({ value, label }) => ( - - ))} - + items={options} + />