diff --git a/src/components/Header/TimeRange.tsx b/src/components/Header/TimeRange.tsx index c94dac9f..73fd9d2b 100644 --- a/src/components/Header/TimeRange.tsx +++ b/src/components/Header/TimeRange.tsx @@ -1,27 +1,41 @@ import useMountedState from '@/hooks/useMountedState'; -import { Box, Button, Divider, Menu, NumberInput, Stack, Text, Tooltip, UnstyledButton, px } from '@mantine/core'; -import { DateTimePicker } from '@mantine/dates'; -import { IconChevronLeft, IconChevronRight, IconClock } from '@tabler/icons-react'; +import { Box, Button, Divider, Menu, NumberInput, Stack, Text, Tooltip, px } from '@mantine/core'; +import { DatePicker, TimeInput } from '@mantine/dates'; +import { IconCalendarEvent, IconCheck, IconChevronLeft, IconChevronRight } from '@tabler/icons-react'; import dayjs from 'dayjs'; import type { FC } from 'react'; -import { Fragment, useCallback, useMemo } from 'react'; -import { FIXED_DURATIONS, FIXED_DURATIONS_LABEL } from '@/constants/timeConstants'; +import { Fragment, useCallback, useMemo, useRef, useState } from 'react'; +import { FIXED_DURATIONS } from '@/constants/timeConstants'; import classes from './styles/LogQuery.module.css'; import { useOuterClick } from '@/hooks/useOuterClick'; import { logsStoreReducers, useLogsStore } from '@/pages/Stream/providers/LogsProvider'; +import _ from 'lodash'; const { setTimeRange, setshiftInterval } = logsStoreReducers; type FixedDurations = (typeof FIXED_DURATIONS)[number]; -const { - timeRangeBTn, - timeRangeContainer, - fixedRangeContainer, - fixedRangeBtn, - fixedRangeBtnSelected, - customRangeContainer, - shiftIntervalContainer, -} = classes; +const { timeRangeContainer, fixedRangeBtn, fixedRangeBtnSelected, customRangeContainer, shiftIntervalContainer } = + classes; + +const RelativeTimeIntervals = (props: { + interval: number; + onDurationSelect: (fixedDuration: FixedDurations) => void; +}) => { + const { interval, onDurationSelect } = props; + return ( + + {_.map(FIXED_DURATIONS, (duration) => { + return ( + onDurationSelect(duration)} key={duration.name}> + + {duration.label} + + + ); + })} + + ); +}; const TimeRange: FC = () => { const [timeRange, setLogsStore] = useLogsStore((store) => store.timeRange); @@ -36,6 +50,7 @@ const TimeRange: FC = () => { const shouldIgnoreClick = classNames.some((className) => { return ( className.startsWith('mantine-DateTimePicker') || + className.startsWith('mantine-DatePicker') || className.startsWith('mantine-TimeInput') || className === 'mantine-Popover-dropdown' ); @@ -45,6 +60,8 @@ const TimeRange: FC = () => { const innerRef = useOuterClick(handleOuterClick); const [opened, setOpened] = useMountedState(false); + const [showTick, setShowTick] = useState(false); + const shiftIntervalRef = useRef(null); const toggleMenu = useCallback(() => { setOpened((prev) => !prev); @@ -58,9 +75,27 @@ const TimeRange: FC = () => { setOpened(false); }; + const resetToRelative = useCallback(() => { + const now = dayjs().startOf('minute'); + const startTime = now.subtract(FIXED_DURATIONS[0].milliseconds, 'milliseconds'); + const endTime = now; + setLogsStore((store) => setTimeRange(store, { startTime, endTime, type: 'fixed' })); + setOpened(false); + }, []); + + const debouncedShowTick = useCallback( + _.debounce(() => { + setShowTick(true); + shiftIntervalRef.current?.blur(); // Remove focus after showing tick + }, 1000), // 1000ms = 1 second delay + [], + ); + const onSetShiftInterval = useCallback((val: number | string) => { if (typeof val === 'number') { setLogsStore((store) => setshiftInterval(store, val)); + setShowTick(false); // Hide the tick when editing starts again + debouncedShowTick(); // Show the tick after the user stops typing } }, []); @@ -92,15 +127,18 @@ const TimeRange: FC = () => { - - - + + {type === 'fixed' ? ( + + ) : ( + + {label} + + )} + + + + shiftTimeRange('right')}> @@ -111,33 +149,24 @@ const TimeRange: FC = () => {
- - {FIXED_DURATIONS.map((duration) => { - return ( - onDurationSelect(duration)}> - {duration.name} - - ); - })} - Shift Interval (In Mins) - + + + + - +
@@ -148,17 +177,52 @@ const TimeRange: FC = () => { type CustomTimeRangeProps = { setOpened: (opened: boolean) => void; + resetToRelative: () => void; }; -const CustomTimeRange: FC = ({ setOpened }) => { - const [{ startTime, endTime }, setLogsStore] = useLogsStore((store) => store.timeRange); - const [localSelectedRange, setLocalSelectedRange] = useMountedState({ - startTime, - endTime, +function normalizeDate(date: Date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); +} + +function isDateInRange(startDate: Date, endDate: Date, currentDate: Date) { + const normalizedStart = normalizeDate(startDate); + const normalizedEnd = normalizeDate(endDate); + const normalizedTest = normalizeDate(currentDate); + + return ( + (normalizedTest >= normalizedStart && normalizedTest <= normalizedEnd) || + normalizedTest.getTime() === normalizedStart.getTime() || + normalizedTest.getTime() === normalizedEnd.getTime() + ); +} + +const CustomTimeRange: FC = ({ setOpened, resetToRelative }) => { + const [{ startTime: startTimeFromStore, endTime: endTimeFromStore, type }, setLogsStore] = useLogsStore( + (store) => store.timeRange, + ); + + const [localSelectedRange, setLocalSelectedRange] = useState({ + startTime: _.clone(startTimeFromStore), + endTime: _.clone(endTimeFromStore), }); const onRangeSelect = (key: keyof typeof localSelectedRange, date: Date) => { setLocalSelectedRange((state) => { + const year = date.getFullYear(); + const month = date.getMonth(); + const day = date.getDate(); + const newDate = state[key]; + newDate.setFullYear(year, month, day); + state[key] = newDate; + return { ...state }; + }); + }; + + const onTimeSelect = (key: keyof typeof localSelectedRange, time: string) => { + setLocalSelectedRange((state) => { + const [hours, minutes] = time.split(':').map(Number); + const date = state[key]; + date.setHours(hours, minutes, 0, 0); state[key] = date; return { ...state }; }); @@ -179,52 +243,88 @@ const CustomTimeRange: FC = ({ setOpened }) => { const isApplicable = useMemo(() => { return ( - dayjs(localSelectedRange.startTime).isSame(startTime, 'seconds') && - dayjs(localSelectedRange.endTime).isSame(endTime, 'seconds') + dayjs(localSelectedRange.startTime).isSame(startTimeFromStore, 'seconds') && + dayjs(localSelectedRange.endTime).isSame(endTimeFromStore, 'seconds') ); }, [localSelectedRange]); const isStartTimeMoreThenEndTime = useMemo(() => { return dayjs(localSelectedRange.startTime).isAfter(localSelectedRange.endTime, 'seconds'); }, [localSelectedRange]); + const startingTime = (() => { + const hours = localSelectedRange.startTime.getHours().toString().padStart(2, '0'); + const minutes = localSelectedRange.startTime.getMinutes().toString().padStart(2, '0'); + return `${hours}:${minutes}`; + })(); + const endingTime = (() => { + const hours = localSelectedRange.endTime.getHours().toString().padStart(2, '0'); + const minutes = localSelectedRange.endTime.getMinutes().toString().padStart(2, '0'); + return `${hours}:${minutes}`; + })(); + + const highlightDate = useCallback( + (date: Date, key: keyof typeof localSelectedRange) => { + const day = date.getDate(); + const selectedDate = localSelectedRange[key]; + const isNotSelectedDate = selectedDate.toLocaleDateString() !== date.toLocaleDateString(); + + const shouldHighlight = + !isStartTimeMoreThenEndTime && + isNotSelectedDate && + isDateInRange(localSelectedRange.startTime, localSelectedRange.endTime, date); + return ( +
+
{day}
+
+ ); + }, + [localSelectedRange, isStartTimeMoreThenEndTime], + ); return ( - Custom Range - { - if (date) { - onRangeSelect('startTime', date); - } - }} - valueFormat="DD-MM-YY HH:mm" - label="From" - placeholder="Pick date and time" - /> - { - if (date) { - onRangeSelect('endTime', date); - } - }} - valueFormat="DD-MM-YY HH:mm" - label="To" - placeholder="Pick date and time" - /> - - - + Absolute Range + + + { + if (date) { + onRangeSelect('startTime', date); + } + }} + renderDay={(date) => highlightDate(date, 'startTime')} + /> + onTimeSelect('startTime', e.currentTarget.value)} /> + + + { + if (date) { + onRangeSelect('endTime', date); + } + }} + renderDay={(date) => highlightDate(date, 'endTime')} + /> + onTimeSelect('endTime', e.currentTarget.value)} /> + + + + + + + + + + ); }; diff --git a/src/components/Header/styles/LogQuery.module.css b/src/components/Header/styles/LogQuery.module.css index 446cdbd8..8daf263d 100644 --- a/src/components/Header/styles/LogQuery.module.css +++ b/src/components/Header/styles/LogQuery.module.css @@ -4,61 +4,63 @@ align-items: center; padding: 0 1rem; } - - .innerContainer { + +.innerContainer { display: flex; height: 100%; align-items: center; width: 100%; - } - - .refreshNowBtn { +} + +.refreshNowBtn { background: #fff; padding: 0; width: 36px; color: #211F1F; border: 1px #e9ecef solid; - border-radius: rem(8px); + border-radius: rem(8px); + &:hover { color: black; } - } +} - .refreshNowBtn:hover { - background-color: #E0E0E0; - } - - .streamButton { +.refreshNowBtn:hover { + background-color: #E0E0E0; +} + +.streamButton { background: #fff; color: #211F1F; border: 1px #e9ecef solid; - border-radius: rem(8px); - } - - .intervalbtn { + border-radius: rem(8px); +} + +.intervalbtn { background-color: white; color: var(--mantine-color-gray-7); border: 1px #e9ecef solid; - border-radius: rem(8px); + border-radius: rem(8px); font-size: 0.65rem; + &:hover { color: black; } - } - - .intervalbtn:hover { - background-color: #E0E0E0; - } - - .homeIcon { +} + +.intervalbtn:hover { + background-color: #E0E0E0; +} + +.homeIcon { size: 24px; - } - - .timeRangeContainer { +} + +.timeRangeContainer { display: flex; - } - - .fixedRangeContainer { +} + +.fixedRangeContainer { display: flex; flex-direction: column; background: #f8f9fa; @@ -67,55 +69,59 @@ margin-top: 0.5rem; margin-bottom: 0.5rem; overflow: hidden; - } - - .fixedRangeBtn { +} + +.fixedRangeBtn { padding: 0.65rem; font-size: 0.675rem; background: #f8f9fa; text-transform: capitalize; + cursor: pointer; + border-radius: 0.65rem; + &:hover { - color: black !important; + /* color: black !important; */ + &:disabled { color: white !important; } } - } - - .fixedRangeBtn:hover { +} + +.fixedRangeBtn:hover { background: #ced4da; - } +} - .fixedRangeBtn:disabled { +.fixedRangeBtn:disabled { cursor: not-allowed; background: #424242; font-weight: 600; color: #fff; - } - - .customRangeContainer { +} + +.customRangeContainer { padding: 0.625rem 1rem; min-width: 20rem; flex: 1; display: flex; flex-direction: column; justify-content: stretch; - } - - .customTimeRangeFooter { +} + +.customTimeRangeFooter { display: flex; margin-top: auto; justify-content: flex-end; align-items: center; - } - - .searchContainer { +} + +.searchContainer { display: flex; padding-right: 0.75rem; - } - - .searchTypeBtn { +} + +.searchTypeBtn { border: 1px #f1f3f5 solid; border-top-right-radius: 0; border-bottom-right-radius: 0; @@ -124,43 +130,43 @@ border-right: none; font-weight: 700; padding-right: 0.75rem; - } - - .searchTypeActive { +} + +.searchTypeActive { background: #545beb; font-weight: 500; color: #fff; - } - - .searchTypeActive:hover { +} + +.searchTypeActive:hover { background: #fc466b; - } - - .searchInput { +} + +.searchInput { width: 100%; flex: 1; - } - - .searchInput input { +} + +.searchInput input { background: #fff; border: 1px #e9ecef solid; - border-radius: rem(8px); + border-radius: rem(8px); font-weight: 500; - } - - .toggleBtn { +} + +.toggleBtn { background-color: white; padding: 6px 12px; margin-right: 0.625rem; color: #211F1F; border: 1px #e9ecef solid; - } - - .toggleBtn:hover { +} + +.toggleBtn:hover { background: #f1f3f5; - } - - .timeRangeBTn { +} + +.timeRangeBTn { background-color: white; color: var(--mantine-color-gray-7); padding: 0 0.5rem; @@ -168,42 +174,45 @@ border-right: 1px solid var(--mantine-color-gray-2); border-left: 1px solid var(--mantine-color-gray-2); height: 100%; - } - - .timeRangeBTn:hover { - background-color: #E0E0E0; +} + +.timeRangeBTn:hover { + background-color: #E0E0E0; color: black; - } - - .dropdownBtn { +} + +.dropdownBtn { margin-right: 0.625rem; border-radius: rem(8px); - } +} - .dropdownOption { +.dropdownOption { color: #211F1F; font-size: var(--mantine-font-size-md); - } +} - .liveTailFilterContainer { +.liveTailFilterContainer { display: flex; gap: 12px; margin-right: 20px; - } - - .fixedRangeBtnSelected { +} + +.fixedRangeBtnSelected { background: #545beb !important; font-weight: 500; color: white; - } - - .fixedRangeBtnSelected:hover { +} + +.fixedRangeBtnSelected:hover { background: #f1f3f5; - } - - .customTimeRangeApplyBtn { + cursor: not-allowed; + color: white; +} + +.customTimeRangeApplyBtn { background: #545BEB; color: white; + &:hover { background: #FC466B; } @@ -224,22 +233,9 @@ } .streamInput { - /* border: none; */ - /* padding-left: 0; */ - /* padding-right: 30px; */ - /* height: 50px; */ - /* font-size: 24px; */ - /* font-weight: 600; */ - /* margin-top: -10px; */ - /* background-color: transparent; */ cursor: pointer; border: 1px var(--mantine-color-gray-2) solid; border-radius: rem(8px); - /* height: 50px; */ - /* height: 100%; */ - /* font-size: 1rem; */ - /* width: 200px; */ - } .chevronDown { @@ -247,15 +243,15 @@ } .iconBtn { - cursor: pointer; - align-items: center; + cursor: pointer; + align-items: center; /* padding: 0.25rem; */ border-radius: 0.25rem; /* padding: 0.25rem 0; */ } .iconBtnIcon { - color: var(--mantine-color-brandPrimary-6); + color: var(--mantine-color-brandPrimary-6); /* color: white; background-color: var(--mantine-color-brandPrimary-4); */ border-radius: 0.25rem; @@ -263,7 +259,7 @@ } .iconBtnLabel { - font-size: 0.8rem; + font-size: 0.8rem; white-space: nowrap; } @@ -304,16 +300,20 @@ border: 1px solid var(--mantine-color-gray-3); overflow: hidden; border-radius: rem(8px); + align-items: center; + height: 1.6rem } .timeRangeCtrlIcon { height: 100%; - align-items: center; - justify-content: center; + align-items: center; + justify-content: center; + &:hover { background-color: #E0E0E0; color: black; } + padding: 0 0.25rem; color: var(--mantine-color-gray-7); cursor: pointer; @@ -330,4 +330,24 @@ font-size: 0.7rem; font-weight: 500; text-align: center; +} + +.datePickerContainer { + border: 1px solid var(--mantine-color-gray-2); + border-radius: 0.2rem; + padding: 1rem; +} + +.calendarDate { + width: 1.2rem; + height: 1.2rem; + align-items: center; + justify-content: center; + display: flex; +} + +.highlightDate { + background-color: var(--mantine-color-gray-5); + color: white; + border-radius: 0.15rem; } \ No newline at end of file diff --git a/src/constants/timeConstants.ts b/src/constants/timeConstants.ts index 49d81cb8..56124051 100644 --- a/src/constants/timeConstants.ts +++ b/src/constants/timeConstants.ts @@ -12,27 +12,27 @@ export const FIXED_DURATIONS: ReadonlyArray = [ { name: 'last 10 minutes', milliseconds: dayjs.duration({ minutes: 10 }).asMilliseconds(), - label: '10M', + label: '10m', }, { name: 'last 1 hour', milliseconds: dayjs.duration({ hours: 1 }).asMilliseconds(), - label: '1H', + label: '1h', }, { name: 'last 5 hours', milliseconds: dayjs.duration({ hours: 5 }).asMilliseconds(), - label: '5H', + label: '5h', }, { name: 'last 24 hours', milliseconds: dayjs.duration({ days: 1 }).asMilliseconds(), - label: '1D', + label: '1d', }, { name: 'last 3 days', milliseconds: dayjs.duration({ days: 3 }).asMilliseconds(), - label: '3D', + label: '3d', }, ] as const; diff --git a/src/pages/Dashboards/Dashboard.tsx b/src/pages/Dashboards/Dashboard.tsx index f33b8882..0d5c56b5 100644 --- a/src/pages/Dashboards/Dashboard.tsx +++ b/src/pages/Dashboards/Dashboard.tsx @@ -196,7 +196,7 @@ const ImportDashboardModal = () => { }, []); const onImport = useCallback(() => { - if (activeDashboard === null || file === null) return; + if (file === null) return; if (file) { const reader = new FileReader(); @@ -209,7 +209,9 @@ const ImportDashboardModal = () => { if (_.isEmpty(newDashboard)) return; return makePostCall(newDashboard) - } catch (error) {} + } catch (error) { + console.log("error", error) + } }; reader.readAsText(file); } else { diff --git a/src/pages/Dashboards/Toolbar.tsx b/src/pages/Dashboards/Toolbar.tsx index 43336476..e0661747 100644 --- a/src/pages/Dashboards/Toolbar.tsx +++ b/src/pages/Dashboards/Toolbar.tsx @@ -155,7 +155,9 @@ const DeleteDashboardModal = () => {
- +