diff --git a/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/ChartTooltip.tsx b/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/ChartTooltip.tsx index 0997719467..f8a38280d1 100644 --- a/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/ChartTooltip.tsx +++ b/app/vmui/packages/vmui/src/components/Chart/ChartTooltip/ChartTooltip.tsx @@ -49,7 +49,7 @@ const ChartTooltip: FC = ({ const value = useMemo(() => get(u, ["data", seriesIdx, dataIdx], 0), [u, seriesIdx, dataIdx]); const valueFormat = useMemo(() => formatPrettyNumber(value), [value]); const dataTime = useMemo(() => u.data[0][dataIdx], [u, dataIdx]); - const date = useMemo(() => dayjs(new Date(dataTime * 1000)).format(DATE_FULL_TIMEZONE_FORMAT), [dataTime]); + const date = useMemo(() => dayjs(dataTime * 1000).tz().format(DATE_FULL_TIMEZONE_FORMAT), [dataTime]); const color = useMemo(() => getColorLine(series[seriesIdx]?.label || ""), [series, seriesIdx]); diff --git a/app/vmui/packages/vmui/src/components/Chart/LineChart/LineChart.tsx b/app/vmui/packages/vmui/src/components/Chart/LineChart/LineChart.tsx index 1e7e4ff7c0..b7d8e475f8 100644 --- a/app/vmui/packages/vmui/src/components/Chart/LineChart/LineChart.tsx +++ b/app/vmui/packages/vmui/src/components/Chart/LineChart/LineChart.tsx @@ -11,7 +11,7 @@ import { defaultOptions } from "../../../utils/uplot/helpers"; import { dragChart } from "../../../utils/uplot/events"; import { getAxes, getMinMaxBuffer } from "../../../utils/uplot/axes"; import { MetricResult } from "../../../api/types"; -import { limitsDurations } from "../../../utils/time"; +import { dateFromSeconds, formatDateForNativeInput, limitsDurations } from "../../../utils/time"; import throttle from "lodash.throttle"; import useResize from "../../../hooks/useResize"; import { TimeParams } from "../../../types"; @@ -20,6 +20,7 @@ import "uplot/dist/uPlot.min.css"; import "./style.scss"; import classNames from "classnames"; import ChartTooltip, { ChartTooltipProps } from "../ChartTooltip/ChartTooltip"; +import dayjs from "dayjs"; export interface LineChartProps { metrics: MetricResult[]; @@ -57,7 +58,10 @@ const LineChart: FC = ({ const tooltipId = useMemo(() => `${tooltipIdx.seriesIdx}_${tooltipIdx.dataIdx}`, [tooltipIdx]); const setScale = ({ min, max }: { min: number, max: number }): void => { - setPeriod({ from: new Date(min * 1000), to: new Date(max * 1000) }); + setPeriod({ + from: dayjs(min * 1000).toDate(), + to: dayjs(max * 1000).toDate() + }); }; const throttledSetScale = useCallback(throttle(setScale, 500), []); const setPlotScale = ({ u, min, max }: { u: uPlot, min: number, max: number }) => { @@ -163,6 +167,7 @@ const LineChart: FC = ({ const options: uPlotOptions = { ...defaultOptions, + tzDate: ts => dayjs(formatDateForNativeInput(dateFromSeconds(ts))).local().toDate(), series, axes: getAxes( [{}, { scale: "1" }], unit), scales: { ...getScales() }, diff --git a/app/vmui/packages/vmui/src/components/Configurators/CardinalityDatePicker/CardinalityDatePicker.tsx b/app/vmui/packages/vmui/src/components/Configurators/CardinalityDatePicker/CardinalityDatePicker.tsx index ed6b5bb489..04fa6c3c83 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/CardinalityDatePicker/CardinalityDatePicker.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/CardinalityDatePicker/CardinalityDatePicker.tsx @@ -15,7 +15,7 @@ const CardinalityDatePicker: FC = () => { const { date } = useCardinalityState(); const cardinalityDispatch = useCardinalityDispatch(); - const dateFormatted = useMemo(() => dayjs(date).format(DATE_FORMAT), [date]); + const dateFormatted = useMemo(() => dayjs.tz(date).format(DATE_FORMAT), [date]); const handleChangeDate = (val: string) => { cardinalityDispatch({ type: "SET_DATE", payload: val }); diff --git a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx index 09e351f678..589b353f34 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/GlobalSettings.tsx @@ -11,6 +11,8 @@ import { SeriesLimits } from "../../../types"; import { useCustomPanelDispatch, useCustomPanelState } from "../../../state/customPanel/CustomPanelStateContext"; import { getAppModeEnable } from "../../../utils/app-mode"; import classNames from "classnames"; +import Timezones from "./Timezones/Timezones"; +import { useTimeDispatch, useTimeState } from "../../../state/time/TimeStateContext"; const title = "Settings"; @@ -18,13 +20,16 @@ const GlobalSettings: FC = () => { const appModeEnable = getAppModeEnable(); const { serverUrl: stateServerUrl } = useAppState(); + const { timezone: stateTimezone } = useTimeState(); const { seriesLimits } = useCustomPanelState(); const dispatch = useAppDispatch(); + const timeDispatch = useTimeDispatch(); const customPanelDispatch = useCustomPanelDispatch(); const [serverUrl, setServerUrl] = useState(stateServerUrl); const [limits, setLimits] = useState(seriesLimits); + const [timezone, setTimezone] = useState(stateTimezone); const [open, setOpen] = useState(false); const handleOpen = () => setOpen(true); @@ -32,6 +37,7 @@ const GlobalSettings: FC = () => { const handlerApply = () => { dispatch({ type: "SET_SERVER", payload: serverUrl }); + timeDispatch({ type: "SET_TIMEZONE", payload: timezone }); customPanelDispatch({ type: "SET_SERIES_LIMITS", payload: limits }); handleClose(); }; @@ -70,6 +76,12 @@ const GlobalSettings: FC = () => { onEnter={handlerApply} /> +
+ +
+ ); +}; + +export default Timezones; diff --git a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/Timezones/style.scss b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/Timezones/style.scss new file mode 100644 index 0000000000..185d7e845a --- /dev/null +++ b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/Timezones/style.scss @@ -0,0 +1,96 @@ +@use "src/styles/variables" as *; + +.vm-timezones { + + &-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: $padding-small; + cursor: pointer; + + &_selected { + border: $border-divider; + padding: $padding-small $padding-global; + border-radius: $border-radius-small; + } + + &__title { + text-transform: capitalize; + } + + &__utc { + display: inline-flex; + align-items: center; + justify-content: center; + background-color: rgba($color-black, 0.06); + padding: calc($padding-small/2); + border-radius: $border-radius-small; + } + + &__icon { + display: inline-flex; + align-items: center; + justify-content: flex-end; + margin: 0 0 0 auto; + transition: transform 200ms ease-in; + + svg { + width: 14px; + } + + &_open { + transform: rotate(180deg); + } + } + } + + &-list { + min-width: 600px; + max-height: 300px; + background-color: $color-background-block; + border-radius: $border-radius-medium; + overflow: auto; + + &-header { + position: sticky; + top: 0; + background-color: $color-background-block; + z-index: 2; + border-bottom: $border-divider; + + &__search { + padding: $padding-small; + } + } + + &-group { + padding: $padding-small 0; + border-bottom: $border-divider; + + &:last-child { + border-bottom: none; + } + + &__title { + font-weight: bold; + color: $color-text-secondary; + padding: $padding-small $padding-global; + } + + &-options { + display: grid; + align-items: flex-start; + + &__item { + padding: $padding-small $padding-global; + transition: background-color 200ms ease; + + &:hover { + background-color: rgba($color-black, 0.1); + } + } + } + } + } +} diff --git a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/style.scss b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/style.scss index 5f40cf32d2..ec0d5c391d 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/style.scss +++ b/app/vmui/packages/vmui/src/components/Configurators/GlobalSettings/style.scss @@ -10,6 +10,15 @@ } + &__title { + display: flex; + align-items: center; + justify-content: flex-start; + font-size: $font-size; + font-weight: bold; + margin-bottom: $padding-global; + } + &__footer { display: inline-grid; grid-template-columns: repeat(2, 1fr); diff --git a/app/vmui/packages/vmui/src/components/Configurators/TimeRangeSettings/TimeDurationSelector/style.scss b/app/vmui/packages/vmui/src/components/Configurators/TimeRangeSettings/TimeDurationSelector/style.scss index 79eb3be594..e00038492d 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/TimeRangeSettings/TimeDurationSelector/style.scss +++ b/app/vmui/packages/vmui/src/components/Configurators/TimeRangeSettings/TimeDurationSelector/style.scss @@ -1,7 +1,7 @@ @use "src/styles/variables" as *; .vm-time-duration { - max-height: 168px; + max-height: 200px; overflow: auto; font-size: $font-size; } diff --git a/app/vmui/packages/vmui/src/components/Configurators/TimeRangeSettings/TimeSelector/TimeSelector.tsx b/app/vmui/packages/vmui/src/components/Configurators/TimeRangeSettings/TimeSelector/TimeSelector.tsx index 75d5a82cfc..d362277fc2 100644 --- a/app/vmui/packages/vmui/src/components/Configurators/TimeRangeSettings/TimeSelector/TimeSelector.tsx +++ b/app/vmui/packages/vmui/src/components/Configurators/TimeRangeSettings/TimeSelector/TimeSelector.tsx @@ -1,5 +1,5 @@ import React, { FC, useEffect, useState, useMemo, useRef } from "preact/compat"; -import { dateFromSeconds, formatDateForNativeInput } from "../../../../utils/time"; +import { dateFromSeconds, formatDateForNativeInput, getRelativeTime, getUTCByTimezone } from "../../../../utils/time"; import TimeDurationSelector from "../TimeDurationSelector/TimeDurationSelector"; import dayjs from "dayjs"; import { getAppModeEnable } from "../../../../utils/app-mode"; @@ -22,20 +22,25 @@ export const TimeSelector: FC = () => { const [until, setUntil] = useState(); const [from, setFrom] = useState(); - const formFormat = useMemo(() => dayjs(from).format(DATE_TIME_FORMAT), [from]); - const untilFormat = useMemo(() => dayjs(until).format(DATE_TIME_FORMAT), [until]); + const formFormat = useMemo(() => dayjs.tz(from).format(DATE_TIME_FORMAT), [from]); + const untilFormat = useMemo(() => dayjs.tz(until).format(DATE_TIME_FORMAT), [until]); - const { period: { end, start }, relativeTime } = useTimeState(); + const { period: { end, start }, relativeTime, timezone, duration } = useTimeState(); const dispatch = useTimeDispatch(); const appModeEnable = getAppModeEnable(); + const activeTimezone = useMemo(() => ({ + region: timezone, + utc: getUTCByTimezone(timezone) + }), [timezone]); + useEffect(() => { setUntil(formatDateForNativeInput(dateFromSeconds(end))); - }, [end]); + }, [timezone, end]); useEffect(() => { setFrom(formatDateForNativeInput(dateFromSeconds(start))); - }, [start]); + }, [timezone, start]); const setDuration = ({ duration, until, id }: {duration: string, until: Date, id: string}) => { dispatch({ type: "SET_RELATIVE_TIME", payload: { duration, until, id } }); @@ -43,13 +48,13 @@ export const TimeSelector: FC = () => { }; const formatRange = useMemo(() => { - const startFormat = dayjs(dateFromSeconds(start)).format(DATE_TIME_FORMAT); - const endFormat = dayjs(dateFromSeconds(end)).format(DATE_TIME_FORMAT); + const startFormat = dayjs.tz(dateFromSeconds(start)).format(DATE_TIME_FORMAT); + const endFormat = dayjs.tz(dateFromSeconds(end)).format(DATE_TIME_FORMAT); return { start: startFormat, end: endFormat }; - }, [start, end]); + }, [start, end, timezone]); const dateTitle = useMemo(() => { const isRelativeTime = relativeTime && relativeTime !== "none"; @@ -65,7 +70,10 @@ export const TimeSelector: FC = () => { const setTimeAndClosePicker = () => { if (from && until) { - dispatch({ type: "SET_PERIOD", payload: { from: new Date(from), to: new Date(until) } }); + dispatch({ type: "SET_PERIOD", payload: { + from: dayjs(from).toDate(), + to: dayjs(until).toDate() + } }); } setOpenOptions(false); }; @@ -91,6 +99,15 @@ export const TimeSelector: FC = () => { setOpenOptions(false); }; + useEffect(() => { + const value = getRelativeTime({ + relativeTimeId: relativeTime, + defaultDuration: duration, + defaultEndInput: dateFromSeconds(end), + }); + setDuration({ id: value.relativeTimeId, duration: value.duration, until: value.endInput }); + }, [timezone]); + useClickOutside(wrapperRef, (e) => { const target = e.target as HTMLElement; const isFromButton = fromRef?.current && fromRef.current.contains(target); @@ -159,6 +176,10 @@ export const TimeSelector: FC = () => { /> +
+
{activeTimezone.region}
+
{activeTimezone.utc}
+