Move timezone selection to query parameters
This commit is contained in:
1 parent
9b41053d47
commit
4d7930060c
19 files changed
+127
-145
No files matched your search
@@ -3,7 +3,6 @@ import { useLoginQuery, useMessages } from '@/components/hooks';
|
|||||||
import { DateRangeSetting } from './DateRangeSetting';
|
import { DateRangeSetting } from './DateRangeSetting';
|
||||||
import { LanguageSetting } from './LanguageSetting';
|
import { LanguageSetting } from './LanguageSetting';
|
||||||
import { ThemeSetting } from './ThemeSetting';
|
import { ThemeSetting } from './ThemeSetting';
|
||||||
import { TimezoneSetting } from './TimezoneSetting';
|
|
||||||
import { VersionSetting } from './VersionSetting';
|
import { VersionSetting } from './VersionSetting';
|
||||||
|
|
||||||
export function PreferenceSettings() {
|
export function PreferenceSettings() {
|
||||||
@@ -20,10 +19,6 @@ export function PreferenceSettings() {
|
|||||||
<Label>{t(labels.defaultDateRange)}</Label>
|
<Label>{t(labels.defaultDateRange)}</Label>
|
||||||
<DateRangeSetting />
|
<DateRangeSetting />
|
||||||
</Column>
|
</Column>
|
||||||
<Column>
|
|
||||||
<Label>{t(labels.timezone)}</Label>
|
|
||||||
<TimezoneSetting />
|
|
||||||
</Column>
|
|
||||||
<Column>
|
<Column>
|
||||||
<Label>{t(labels.language)}</Label>
|
<Label>{t(labels.language)}</Label>
|
||||||
<LanguageSetting />
|
<LanguageSetting />
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import { Button, ListItem, Row, Select } from '@umami/react-zen';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useMessages, useTimezone } from '@/components/hooks';
|
|
||||||
import { getTimezone } from '@/lib/date';
|
|
||||||
|
|
||||||
const timezones = Intl.supportedValuesOf('timeZone');
|
|
||||||
|
|
||||||
export function TimezoneSetting() {
|
|
||||||
const [search, setSearch] = useState('');
|
|
||||||
const { t, labels } = useMessages();
|
|
||||||
const { timezone, saveTimezone } = useTimezone();
|
|
||||||
const items = search
|
|
||||||
? timezones.filter(n => n.toLowerCase().includes(search.toLowerCase()))
|
|
||||||
: timezones;
|
|
||||||
|
|
||||||
const handleReset = () => saveTimezone(getTimezone());
|
|
||||||
|
|
||||||
const handleOpen = isOpen => {
|
|
||||||
if (isOpen) {
|
|
||||||
setSearch('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row gap>
|
|
||||||
<Select
|
|
||||||
value={timezone}
|
|
||||||
onChange={(value: any) => saveTimezone(value)}
|
|
||||||
allowSearch={true}
|
|
||||||
onSearch={setSearch}
|
|
||||||
onOpenChange={handleOpen}
|
|
||||||
maxHeight={480}
|
|
||||||
style={{ minWidth: '250px' }}
|
|
||||||
>
|
|
||||||
{items.map((item: any) => (
|
|
||||||
<ListItem key={item} id={item}>
|
|
||||||
{item}
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
{!items.length && <ListItem></ListItem>}
|
|
||||||
</Select>
|
|
||||||
<Button onPress={handleReset}>{t(labels.reset)}</Button>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||||
import { useDateRange, useTimezone } from '@/components/hooks';
|
import { useDateRange } from '@/components/hooks';
|
||||||
import { useRevenueChartQuery } from '@/components/hooks/queries/useRevenueChartQuery';
|
import { useRevenueChartQuery } from '@/components/hooks/queries/useRevenueChartQuery';
|
||||||
import { RevenueChart } from './RevenueChart';
|
import { RevenueChart } from './RevenueChart';
|
||||||
import { useBoardRevenueCurrency } from './useBoardRevenueCurrency';
|
import { useBoardRevenueCurrency } from './useBoardRevenueCurrency';
|
||||||
@@ -12,10 +12,9 @@ export function BoardRevenueChart({
|
|||||||
currency?: string;
|
currency?: string;
|
||||||
}) {
|
}) {
|
||||||
const currency = useBoardRevenueCurrency(selectedCurrency);
|
const currency = useBoardRevenueCurrency(selectedCurrency);
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange({ timezone });
|
} = useDateRange();
|
||||||
const { data, isLoading, isFetching, error } = useRevenueChartQuery(websiteId, currency);
|
const { data, isLoading, isFetching, error } = useRevenueChartQuery(websiteId, currency);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { Column } from '@umami/react-zen';
|
import { Column } from '@umami/react-zen';
|
||||||
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||||
import { useDateRange, useTimezone } from '@/components/hooks';
|
import { useDateRange } from '@/components/hooks';
|
||||||
import { Revenue } from './Revenue';
|
import { Revenue } from './Revenue';
|
||||||
|
|
||||||
export function RevenuePage({ websiteId }: { websiteId: string }) {
|
export function RevenuePage({ websiteId }: { websiteId: string }) {
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange({ timezone });
|
} = useDateRange();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap>
|
<Column gap>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||||
import { useDateRange, useTimezone } from '@/components/hooks';
|
import { useDateRange } from '@/components/hooks';
|
||||||
import { useWebsitePageviewsQuery } from '@/components/hooks/queries/useWebsitePageviewsQuery';
|
import { useWebsitePageviewsQuery } from '@/components/hooks/queries/useWebsitePageviewsQuery';
|
||||||
import { PageviewsChart } from '@/components/metrics/PageviewsChart';
|
import { PageviewsChart } from '@/components/metrics/PageviewsChart';
|
||||||
|
|
||||||
@@ -11,8 +11,7 @@ export function WebsiteChart({
|
|||||||
websiteId: string;
|
websiteId: string;
|
||||||
compareMode?: boolean;
|
compareMode?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { timezone } = useTimezone();
|
const { dateRange, dateCompare } = useDateRange();
|
||||||
const { dateRange, dateCompare } = useDateRange({ timezone: timezone });
|
|
||||||
const { startDate, endDate, unit, value } = dateRange;
|
const { startDate, endDate, unit, value } = dateRange;
|
||||||
const { data, isLoading, isFetching, error } = useWebsitePageviewsQuery({
|
const { data, isLoading, isFetching, error } = useWebsitePageviewsQuery({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Icon, Row, Text } from '@umami/react-zen';
|
import { Row } from '@umami/react-zen';
|
||||||
import { Favicon } from '@/components/common/Favicon';
|
import { Favicon } from '@/components/common/Favicon';
|
||||||
import { IconLabel } from '@/components/common/IconLabel';
|
import { IconLabel } from '@/components/common/IconLabel';
|
||||||
import { LinkButton } from '@/components/common/LinkButton';
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
@@ -15,7 +15,7 @@ export function WebsiteHeader({
|
|||||||
allowLink?: boolean;
|
allowLink?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const website = useWebsite();
|
const website = useWebsite();
|
||||||
const { renderUrl, pathname } = useNavigation();
|
const { renderUrl, pathname, query } = useNavigation();
|
||||||
const isSettings = pathname.endsWith('/settings');
|
const isSettings = pathname.endsWith('/settings');
|
||||||
|
|
||||||
const { t, labels } = useMessages();
|
const { t, labels } = useMessages();
|
||||||
@@ -28,7 +28,14 @@ export function WebsiteHeader({
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title={website.name}
|
title={website.name}
|
||||||
icon={<Favicon domain={website.domain} />}
|
icon={<Favicon domain={website.domain} />}
|
||||||
titleHref={allowLink ? renderUrl(`/websites/${website.id}`, false) : undefined}
|
titleHref={
|
||||||
|
allowLink
|
||||||
|
? renderUrl(
|
||||||
|
`/websites/${website.id}`,
|
||||||
|
query.timezone ? { timezone: query.timezone } : false,
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Row alignItems="center" gap="6" wrap="wrap">
|
<Row alignItems="center" gap="6" wrap="wrap">
|
||||||
<ActiveUsers websiteId={website.id} allowLink={allowLink} />
|
<ActiveUsers websiteId={website.id} allowLink={allowLink} />
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ export function useDateParameters() {
|
|||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange();
|
} = useDateRange();
|
||||||
const { timezone, localToUtc, canonicalizeTimezone } = useTimezone();
|
const { timezone, toUtc, canonicalizeTimezone } = useTimezone();
|
||||||
|
|
||||||
const utcStart = localToUtc(startDate);
|
const utcStart = toUtc(startDate);
|
||||||
const utcEnd = localToUtc(endDate);
|
const utcEnd = toUtc(endDate);
|
||||||
const startAtValue = +utcStart;
|
const startAtValue = +utcStart;
|
||||||
const endAtValue = +utcEnd;
|
const endAtValue = +utcEnd;
|
||||||
const isStartValid = Number.isFinite(startAtValue);
|
const isStartValid = Number.isFinite(startAtValue);
|
||||||
|
|||||||
@@ -4,24 +4,26 @@ import { useNavigation } from '@/components/hooks/useNavigation';
|
|||||||
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
import { DATE_RANGE_CONFIG, DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||||
import { getCompareDate, getOffsetDateRange, parseDateRange } from '@/lib/date';
|
import { getCompareDate, getOffsetDateRange, parseDateRange } from '@/lib/date';
|
||||||
import { getItem } from '@/lib/storage';
|
import { getItem } from '@/lib/storage';
|
||||||
|
import { useTimezone } from './useTimezone';
|
||||||
|
|
||||||
export function useDateRange(options: { ignoreOffset?: boolean; timezone?: string } = {}) {
|
export function useDateRange(options: { ignoreOffset?: boolean; timezone?: string } = {}) {
|
||||||
const {
|
const {
|
||||||
query: { date = '', unit = '', offset = 0, compare = 'prev' },
|
query: { date = '', unit = '', offset = 0, compare = 'prev' },
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
|
const { timezone: defaultTimezone } = useTimezone();
|
||||||
|
const timezone = options.timezone ?? defaultTimezone;
|
||||||
|
const ignoreOffset = options.ignoreOffset;
|
||||||
const dateRange = useMemo(() => {
|
const dateRange = useMemo(() => {
|
||||||
const dateRangeObject = parseDateRange(
|
const dateRangeObject = parseDateRange(
|
||||||
date || getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
|
date || getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
|
||||||
unit,
|
unit,
|
||||||
locale,
|
locale,
|
||||||
options.timezone,
|
timezone,
|
||||||
);
|
);
|
||||||
|
|
||||||
return !options.ignoreOffset && offset
|
return !ignoreOffset && offset ? getOffsetDateRange(dateRangeObject, +offset) : dateRangeObject;
|
||||||
? getOffsetDateRange(dateRangeObject, +offset)
|
}, [date, unit, offset, locale, timezone, ignoreOffset]);
|
||||||
: dateRangeObject;
|
|
||||||
}, [date, unit, offset, options]);
|
|
||||||
|
|
||||||
const dateCompare = getCompareDate(compare, dateRange.startDate, dateRange.endDate);
|
const dateCompare = getCompareDate(compare, dateRange.startDate, dateRange.endDate);
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
import { formatInTimeZone, fromZonedTime, toZonedTime } from 'date-fns-tz';
|
import { formatInTimeZone, fromZonedTime, toZonedTime } from 'date-fns-tz';
|
||||||
import { TIMEZONE_CONFIG, TIMEZONE_LEGACY } from '@/lib/constants';
|
import { useSyncExternalStore } from 'react';
|
||||||
import { getTimezone } from '@/lib/date';
|
import { TIMEZONE_LEGACY } from '@/lib/constants';
|
||||||
import { setItem } from '@/lib/storage';
|
import { getTimezone, isValidTimezone, normalizeTimezone } from '@/lib/date';
|
||||||
import { setTimezone, useApp } from '@/store/app';
|
|
||||||
import { useLocale } from './useLocale';
|
import { useLocale } from './useLocale';
|
||||||
|
import { useNavigation } from './useNavigation';
|
||||||
|
|
||||||
const selector = (state: { timezone: string }) => state.timezone;
|
function canonicalizeTimezone(timezone: string): string {
|
||||||
|
return TIMEZONE_LEGACY[timezone] ?? normalizeTimezone(timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribe() {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getServerTimezone() {
|
||||||
|
return 'UTC';
|
||||||
|
}
|
||||||
|
|
||||||
export function useTimezone() {
|
export function useTimezone() {
|
||||||
const timezone = useApp(selector);
|
const {
|
||||||
const localTimeZone = getTimezone();
|
query: { timezone: queryTimezone },
|
||||||
|
} = useNavigation();
|
||||||
|
const localTimeZone = canonicalizeTimezone(
|
||||||
|
useSyncExternalStore(subscribe, getTimezone, getServerTimezone),
|
||||||
|
);
|
||||||
|
const requestedTimezone =
|
||||||
|
typeof queryTimezone === 'string' ? canonicalizeTimezone(queryTimezone) : undefined;
|
||||||
|
const timezone =
|
||||||
|
requestedTimezone && isValidTimezone(requestedTimezone) ? requestedTimezone : localTimeZone;
|
||||||
const { dateLocale } = useLocale();
|
const { dateLocale } = useLocale();
|
||||||
|
|
||||||
const saveTimezone = (value: string) => {
|
|
||||||
setItem(TIMEZONE_CONFIG, value);
|
|
||||||
setTimezone(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatTimezoneDate = (date: string, pattern: string) => {
|
const formatTimezoneDate = (date: string, pattern: string) => {
|
||||||
return formatInTimeZone(
|
return formatInTimeZone(
|
||||||
/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{3})?Z$/.test(date)
|
/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{3})?Z$/.test(date)
|
||||||
@@ -76,10 +89,6 @@ export function useTimezone() {
|
|||||||
return toZonedTime(date, localTimeZone);
|
return toZonedTime(date, localTimeZone);
|
||||||
};
|
};
|
||||||
|
|
||||||
const canonicalizeTimezone = (timezone: string): string => {
|
|
||||||
return TIMEZONE_LEGACY[timezone] ?? timezone;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timezone,
|
timezone,
|
||||||
localTimeZone,
|
localTimeZone,
|
||||||
@@ -87,7 +96,6 @@ export function useTimezone() {
|
|||||||
fromUtc,
|
fromUtc,
|
||||||
localToUtc,
|
localToUtc,
|
||||||
localFromUtc,
|
localFromUtc,
|
||||||
saveTimezone,
|
|
||||||
formatTimezoneDate,
|
formatTimezoneDate,
|
||||||
formatSeriesTimezone,
|
formatSeriesTimezone,
|
||||||
canonicalizeTimezone,
|
canonicalizeTimezone,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Dialog, ListItem, ListSeparator, Modal, Select, type SelectProps } from '@umami/react-zen';
|
import { Dialog, ListItem, ListSeparator, Modal, Select, type SelectProps } from '@umami/react-zen';
|
||||||
import { Fragment, type Key, useState } from 'react';
|
import { Fragment, type Key, useState } from 'react';
|
||||||
import { DateDisplay } from '@/components/common/DateDisplay';
|
import { DateDisplay } from '@/components/common/DateDisplay';
|
||||||
import { useMessages, useMobile } from '@/components/hooks';
|
import { useMessages, useMobile, useTimezone } from '@/components/hooks';
|
||||||
import { DatePickerForm } from '@/components/metrics/DatePickerForm';
|
import { DatePickerForm } from '@/components/metrics/DatePickerForm';
|
||||||
import { getMaxSelectableDate, parseDateRange } from '@/lib/date';
|
import { getMaxSelectableDate, parseDateRange } from '@/lib/date';
|
||||||
|
|
||||||
@@ -23,7 +23,8 @@ export function DateFilter({
|
|||||||
}: DateFilterProps) {
|
}: DateFilterProps) {
|
||||||
const { t, labels } = useMessages();
|
const { t, labels } = useMessages();
|
||||||
const [showPicker, setShowPicker] = useState(false);
|
const [showPicker, setShowPicker] = useState(false);
|
||||||
const { startDate, endDate } = parseDateRange(value) || {};
|
const { timezone, fromUtc } = useTimezone();
|
||||||
|
const { startDate, endDate } = parseDateRange(value, undefined, undefined, timezone) || {};
|
||||||
const { isMobile } = useMobile();
|
const { isMobile } = useMobile();
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
@@ -128,7 +129,7 @@ export function DateFilter({
|
|||||||
startDate={startDate}
|
startDate={startDate}
|
||||||
endDate={endDate}
|
endDate={endDate}
|
||||||
minDate={new Date(2000, 0, 1)}
|
minDate={new Date(2000, 0, 1)}
|
||||||
maxDate={getMaxSelectableDate()}
|
maxDate={getMaxSelectableDate(fromUtc(new Date()))}
|
||||||
onChange={handlePickerChange}
|
onChange={handlePickerChange}
|
||||||
onClose={() => setShowPicker(false)}
|
onClose={() => setShowPicker(false)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Button, Column, DialogTrigger, Icon, Label, Popover } from '@umami/react-zen';
|
import { Button, Column, DialogTrigger, Icon, Label, Popover } from '@umami/react-zen';
|
||||||
import { DateRangeSetting } from '@/app/(main)/settings/preferences/DateRangeSetting';
|
import { DateRangeSetting } from '@/app/(main)/settings/preferences/DateRangeSetting';
|
||||||
import { TimezoneSetting } from '@/app/(main)/settings/preferences/TimezoneSetting';
|
|
||||||
import { Panel } from '@/components/common/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { Settings } from '@/components/icons';
|
import { Settings } from '@/components/icons';
|
||||||
@@ -17,10 +16,6 @@ export function PreferencesButton() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Popover placement="bottom end">
|
<Popover placement="bottom end">
|
||||||
<Panel gap="3">
|
<Panel gap="3">
|
||||||
<Column>
|
|
||||||
<Label>{t(labels.timezone)}</Label>
|
|
||||||
<TimezoneSetting />
|
|
||||||
</Column>
|
|
||||||
<Column>
|
<Column>
|
||||||
<Label>{t(labels.defaultDateRange)}</Label>
|
<Label>{t(labels.defaultDateRange)}</Label>
|
||||||
<DateRangeSetting />
|
<DateRangeSetting />
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { ListItem, ListSeparator, Select } from '@umami/react-zen';
|
||||||
|
import { type Key, useState } from 'react';
|
||||||
|
import { useMessages, useNavigation, useTimezone } from '@/components/hooks';
|
||||||
|
import { isValidTimezone } from '@/lib/date';
|
||||||
|
|
||||||
|
const CURRENT_TIMEZONE = 'current';
|
||||||
|
const timezones = [
|
||||||
|
'UTC',
|
||||||
|
...(Intl.supportedValuesOf?.('timeZone') ?? []).filter(value => value !== 'UTC'),
|
||||||
|
];
|
||||||
|
|
||||||
|
export function TimezoneFilter() {
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
const { t, labels } = useMessages();
|
||||||
|
const { router, query, updateParams } = useNavigation();
|
||||||
|
const { timezone, localTimeZone, canonicalizeTimezone } = useTimezone();
|
||||||
|
const hasOverride =
|
||||||
|
typeof query.timezone === 'string' && isValidTimezone(canonicalizeTimezone(query.timezone));
|
||||||
|
const selectedTimezone = hasOverride ? timezone : CURRENT_TIMEZONE;
|
||||||
|
const normalizedSearch = search.trim().toLowerCase();
|
||||||
|
const items = normalizedSearch
|
||||||
|
? timezones.filter(value => value.toLowerCase().includes(normalizedSearch))
|
||||||
|
: timezones;
|
||||||
|
|
||||||
|
const handleChange = (value: Key) => {
|
||||||
|
router.push(
|
||||||
|
updateParams({
|
||||||
|
timezone: value === CURRENT_TIMEZONE ? undefined : value.toString(),
|
||||||
|
page: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpen = (isOpen: boolean) => {
|
||||||
|
if (isOpen) {
|
||||||
|
setSearch('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={selectedTimezone}
|
||||||
|
onChange={handleChange}
|
||||||
|
allowSearch
|
||||||
|
onSearch={setSearch}
|
||||||
|
onOpenChange={handleOpen}
|
||||||
|
maxHeight={480}
|
||||||
|
style={{ minWidth: 220 }}
|
||||||
|
>
|
||||||
|
<ListItem id={CURRENT_TIMEZONE}>
|
||||||
|
{t(labels.current)} ({localTimeZone})
|
||||||
|
</ListItem>
|
||||||
|
<ListSeparator />
|
||||||
|
{items.map(item => (
|
||||||
|
<ListItem key={item} id={item}>
|
||||||
|
{item}
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
{!items.length && <ListItem />}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { useFilters, useMessages, useNavigation } from '@/components/hooks';
|
|||||||
import { ListFilter } from '@/components/icons';
|
import { ListFilter } from '@/components/icons';
|
||||||
import { DialogButton } from '@/components/input/DialogButton';
|
import { DialogButton } from '@/components/input/DialogButton';
|
||||||
import { FilterEditForm } from '@/components/input/FilterEditForm';
|
import { FilterEditForm } from '@/components/input/FilterEditForm';
|
||||||
|
import { TimezoneFilter } from '@/components/input/TimezoneFilter';
|
||||||
import {
|
import {
|
||||||
filtersArrayToObject,
|
filtersArrayToObject,
|
||||||
serializeSessionPropertyFilters,
|
serializeSessionPropertyFilters,
|
||||||
@@ -78,17 +79,14 @@ export function WebsiteFilterButton({
|
|||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Row position="absolute" top="30px" right="30px" alignItems="center" gap>
|
||||||
{(isOverview || allowBounceFilter) && (
|
{(isOverview || allowBounceFilter) && (
|
||||||
<Row position="absolute" top="30px" right="30px">
|
<Checkbox value={excludeBounce ? 'true' : ''} onChange={setExcludeBounce}>
|
||||||
<Checkbox
|
|
||||||
value={excludeBounce ? 'true' : ''}
|
|
||||||
onChange={setExcludeBounce}
|
|
||||||
style={{ marginTop: '3px' }}
|
|
||||||
>
|
|
||||||
{t(labels.excludeBounce)}
|
{t(labels.excludeBounce)}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Row>
|
|
||||||
)}
|
)}
|
||||||
|
<TimezoneFilter />
|
||||||
|
</Row>
|
||||||
<FilterEditForm websiteId={websiteId} onChange={handleChange} onClose={close} />
|
<FilterEditForm websiteId={websiteId} onChange={handleChange} onClose={close} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,12 +2,7 @@ import { colord } from 'colord';
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { BarChart, type BarChartProps } from '@/components/charts/BarChart';
|
import { BarChart, type BarChartProps } from '@/components/charts/BarChart';
|
||||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||||
import {
|
import { useDateRange, useLocale, useWebsiteEventsSeriesQuery } from '@/components/hooks';
|
||||||
useDateRange,
|
|
||||||
useLocale,
|
|
||||||
useTimezone,
|
|
||||||
useWebsiteEventsSeriesQuery,
|
|
||||||
} from '@/components/hooks';
|
|
||||||
import { renderDateLabels } from '@/lib/charts';
|
import { renderDateLabels } from '@/lib/charts';
|
||||||
import { hex6 } from '@/lib/colors';
|
import { hex6 } from '@/lib/colors';
|
||||||
import { CHART_COLORS } from '@/lib/constants';
|
import { CHART_COLORS } from '@/lib/constants';
|
||||||
@@ -20,10 +15,9 @@ export interface EventsChartProps extends BarChartProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function EventsChart({ websiteId, focusLabel, limit }: EventsChartProps) {
|
export function EventsChart({ websiteId, focusLabel, limit }: EventsChartProps) {
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange({ timezone: timezone });
|
} = useDateRange();
|
||||||
const { locale, dateLocale } = useLocale();
|
const { locale, dateLocale } = useLocale();
|
||||||
const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId, { limit });
|
const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId, { limit });
|
||||||
const [label, setLabel] = useState<string>(focusLabel);
|
const [label, setLabel] = useState<string>(focusLabel);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
useMessages,
|
useMessages,
|
||||||
usePropertyArraySeriesQuery,
|
usePropertyArraySeriesQuery,
|
||||||
usePropertySeriesQuery,
|
usePropertySeriesQuery,
|
||||||
useTimezone,
|
|
||||||
} from '@/components/hooks';
|
} from '@/components/hooks';
|
||||||
import type { PropertyDataSource } from '@/components/hooks/queries/usePropertyFieldsQuery';
|
import type { PropertyDataSource } from '@/components/hooks/queries/usePropertyFieldsQuery';
|
||||||
import { ListTable } from '@/components/metrics/ListTable';
|
import { ListTable } from '@/components/metrics/ListTable';
|
||||||
@@ -36,10 +35,9 @@ export function PropertyChart({
|
|||||||
seriesType?: 'property' | 'array';
|
seriesType?: 'property' | 'array';
|
||||||
}) {
|
}) {
|
||||||
const { t, labels } = useMessages();
|
const { t, labels } = useMessages();
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange({ timezone });
|
} = useDateRange();
|
||||||
const { locale, dateLocale } = useLocale();
|
const { locale, dateLocale } = useLocale();
|
||||||
const propertySeriesQuery = usePropertySeriesQuery(
|
const propertySeriesQuery = usePropertySeriesQuery(
|
||||||
source,
|
source,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
useMessages,
|
useMessages,
|
||||||
usePropertyNumericSeriesQuery,
|
usePropertyNumericSeriesQuery,
|
||||||
usePropertyNumericStatsQuery,
|
usePropertyNumericStatsQuery,
|
||||||
useTimezone,
|
|
||||||
} from '@/components/hooks';
|
} from '@/components/hooks';
|
||||||
import type { PropertyDataSource } from '@/components/hooks/queries/usePropertyFieldsQuery';
|
import type { PropertyDataSource } from '@/components/hooks/queries/usePropertyFieldsQuery';
|
||||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||||
@@ -35,10 +34,9 @@ export function PropertyNumericChart({
|
|||||||
}) {
|
}) {
|
||||||
const { t, labels } = useMessages();
|
const { t, labels } = useMessages();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const {
|
const {
|
||||||
dateRange: { startDate, endDate, unit },
|
dateRange: { startDate, endDate, unit },
|
||||||
} = useDateRange({ timezone });
|
} = useDateRange();
|
||||||
const { locale, dateLocale } = useLocale();
|
const { locale, dateLocale } = useLocale();
|
||||||
const { colors } = useMemo(() => getThemeColors(theme), [theme]);
|
const { colors } = useMemo(() => getThemeColors(theme), [theme]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export const CURRENT_VERSION = process.env.currentVersion;
|
export const CURRENT_VERSION = process.env.currentVersion;
|
||||||
export const AUTH_TOKEN = 'umami.auth';
|
export const AUTH_TOKEN = 'umami.auth';
|
||||||
export const LOCALE_CONFIG = 'umami.locale';
|
export const LOCALE_CONFIG = 'umami.locale';
|
||||||
export const TIMEZONE_CONFIG = 'umami.timezone';
|
|
||||||
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
||||||
export const THEME_CONFIG = 'umami.theme';
|
export const THEME_CONFIG = 'umami.theme';
|
||||||
export const CURRENCY_CONFIG = 'umami.currency';
|
export const CURRENCY_CONFIG = 'umami.currency';
|
||||||
|
|||||||
+1
-21
@@ -1,13 +1,5 @@
|
|||||||
import { beforeEach, describe, expect, test } from 'vitest';
|
import { beforeEach, describe, expect, test } from 'vitest';
|
||||||
import {
|
import { setConfig, setDateRangeValue, setLocale, setShareData, setUser, useApp } from './app';
|
||||||
setConfig,
|
|
||||||
setDateRangeValue,
|
|
||||||
setLocale,
|
|
||||||
setShareData,
|
|
||||||
setTimezone,
|
|
||||||
setUser,
|
|
||||||
useApp,
|
|
||||||
} from './app';
|
|
||||||
|
|
||||||
const initialState = { ...useApp.getState() };
|
const initialState = { ...useApp.getState() };
|
||||||
|
|
||||||
@@ -15,18 +7,6 @@ beforeEach(() => {
|
|||||||
useApp.setState({ ...initialState }, true);
|
useApp.setState({ ...initialState }, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setTimezone', () => {
|
|
||||||
test('updates the timezone', () => {
|
|
||||||
setTimezone('America/New_York');
|
|
||||||
expect(useApp.getState().timezone).toBe('America/New_York');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('leaves other fields untouched', () => {
|
|
||||||
setTimezone('UTC');
|
|
||||||
expect(useApp.getState().locale).toBe(initialState.locale);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setLocale', () => {
|
describe('setLocale', () => {
|
||||||
test('updates the locale', () => {
|
test('updates the locale', () => {
|
||||||
setLocale('fr-FR');
|
setLocale('fr-FR');
|
||||||
|
|||||||
@@ -6,15 +6,12 @@ import {
|
|||||||
DEFAULT_THEME,
|
DEFAULT_THEME,
|
||||||
LOCALE_CONFIG,
|
LOCALE_CONFIG,
|
||||||
THEME_CONFIG,
|
THEME_CONFIG,
|
||||||
TIMEZONE_CONFIG,
|
|
||||||
} from '@/lib/constants';
|
} from '@/lib/constants';
|
||||||
import { getTimezone } from '@/lib/date';
|
|
||||||
import { getItem } from '@/lib/storage';
|
import { getItem } from '@/lib/storage';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
locale: getItem(LOCALE_CONFIG) || process.env.defaultLocale || DEFAULT_LOCALE,
|
locale: getItem(LOCALE_CONFIG) || process.env.defaultLocale || DEFAULT_LOCALE,
|
||||||
theme: getItem(THEME_CONFIG) || DEFAULT_THEME,
|
theme: getItem(THEME_CONFIG) || DEFAULT_THEME,
|
||||||
timezone: getItem(TIMEZONE_CONFIG) || getTimezone(),
|
|
||||||
dateRangeValue: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
|
dateRangeValue: getItem(DATE_RANGE_CONFIG) || DEFAULT_DATE_RANGE_VALUE,
|
||||||
share: null,
|
share: null,
|
||||||
shareToken: null,
|
shareToken: null,
|
||||||
@@ -24,10 +21,6 @@ const initialState = {
|
|||||||
|
|
||||||
const store = create(() => ({ ...initialState }));
|
const store = create(() => ({ ...initialState }));
|
||||||
|
|
||||||
export function setTimezone(timezone: string) {
|
|
||||||
store.setState({ timezone });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setLocale(locale: string) {
|
export function setLocale(locale: string) {
|
||||||
store.setState({ locale });
|
store.setState({ locale });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user