mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Refactored useDateRange to always use query string. Fixed all time filter.
This commit is contained in:
parent
4d06b0ca5b
commit
92ee44756c
28 changed files with 106 additions and 112 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { Column, Row } from '@umami/react-zen';
|
||||
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||
import { WebsiteFilterButton } from '@/components/input/WebsiteFilterButton';
|
||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
||||
import { FilterBar } from '@/components/input/FilterBar';
|
||||
import { WebsiteMonthSelect } from '@/components/input/WebsiteMonthSelect';
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ export function LinkMetricsBar({
|
|||
showChange?: boolean;
|
||||
compareMode?: boolean;
|
||||
}) {
|
||||
const { dateRange } = useDateRange(linkId);
|
||||
const { isAllTime } = useDateRange();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(linkId);
|
||||
const isAllTime = dateRange.value === 'all';
|
||||
|
||||
const { pageviews, visitors, visits, comparison } = data || {};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Column, Row } from '@umami/react-zen';
|
||||
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||
import { WebsiteFilterButton } from '@/components/input/WebsiteFilterButton';
|
||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
||||
import { FilterBar } from '@/components/input/FilterBar';
|
||||
import { WebsiteMonthSelect } from '@/components/input/WebsiteMonthSelect';
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ export function PixelMetricsBar({
|
|||
showChange?: boolean;
|
||||
compareMode?: boolean;
|
||||
}) {
|
||||
const { dateRange } = useDateRange(pixelId);
|
||||
const { isAllTime } = useDateRange();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(pixelId);
|
||||
const isAllTime = dateRange.value === 'all';
|
||||
|
||||
const { pageviews, visitors, visits, comparison } = data || {};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
|
|||
const { formatMessage, labels } = useMessages();
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
|
||||
return (
|
||||
<Column gap="6">
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { SectionHeader } from '@/components/common/SectionHeader';
|
|||
export function BreakdownPage({ websiteId }: { websiteId: string }) {
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
const [fields, setFields] = useState(['path']);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function FunnelsPage({ websiteId }: { websiteId: string }) {
|
|||
const { data, isLoading, error } = useReportsQuery({ websiteId, type: 'funnel' });
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function GoalsPage({ websiteId }: { websiteId: string }) {
|
|||
const { data, isLoading, error } = useReportsQuery({ websiteId, type: 'goal' });
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export function JourneysPage({ websiteId }: { websiteId: string }) {
|
|||
const { formatMessage, labels } = useMessages();
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
const [steps, setSteps] = useState(DEFAULT_STEP);
|
||||
const [startStep, setStartStep] = useState('');
|
||||
const [endStep, setEndStep] = useState('');
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { useDateRange } from '@/components/hooks';
|
|||
export function RevenuePage({ websiteId }: { websiteId: string }) {
|
||||
const {
|
||||
dateRange: { startDate, endDate, unit },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteContro
|
|||
export function UTMPage({ websiteId }: { websiteId: string }) {
|
||||
const {
|
||||
dateRange: { startDate, endDate },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
|
||||
return (
|
||||
<Column gap>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function WebsiteChart({
|
|||
websiteId: string;
|
||||
compareMode?: boolean;
|
||||
}) {
|
||||
const { dateRange, dateCompare } = useDateRange(websiteId);
|
||||
const { dateRange, dateCompare } = useDateRange();
|
||||
const { startDate, endDate, unit, value } = dateRange;
|
||||
const { data, isLoading, isFetching, error } = useWebsitePageviewsQuery({
|
||||
websiteId,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Column, Row } from '@umami/react-zen';
|
||||
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||
import { WebsiteFilterButton } from '@/components/input/WebsiteFilterButton';
|
||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
||||
import { FilterBar } from '@/components/input/FilterBar';
|
||||
import { WebsiteMonthSelect } from '@/components/input/WebsiteMonthSelect';
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ export function WebsiteMetricsBar({
|
|||
showChange?: boolean;
|
||||
compareMode?: boolean;
|
||||
}) {
|
||||
const { dateRange } = useDateRange(websiteId);
|
||||
const { isAllTime } = useDateRange();
|
||||
const { formatMessage, labels, getErrorMessage } = useMessages();
|
||||
const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(websiteId);
|
||||
const isAllTime = dateRange.value === 'all';
|
||||
|
||||
const { pageviews, visitors, visits, bounces, totaltime, comparison } = data || {};
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useState } from 'react';
|
|||
|
||||
export function CompareTables({ websiteId }: { websiteId: string }) {
|
||||
const [data, setData] = useState([]);
|
||||
const { dateRange, dateCompare } = useDateRange(websiteId);
|
||||
const { dateRange, dateCompare } = useDateRange();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const {
|
||||
router,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ export * from './useModified';
|
|||
export * from './useNavigation';
|
||||
export * from './usePagedQuery';
|
||||
export * from './usePageParameters';
|
||||
export * from './useQueryStringDate';
|
||||
export * from './useRegionNames';
|
||||
export * from './useSlug';
|
||||
export * from './useSticky';
|
||||
|
|
|
|||
|
|
@ -1,12 +1,23 @@
|
|||
import { useApi } from '../useApi';
|
||||
import { ReactQueryOptions } from '@/lib/types';
|
||||
|
||||
type DateRange = {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
};
|
||||
|
||||
export function useDateRangeQuery(websiteId: string, options?: ReactQueryOptions) {
|
||||
const { get, useQuery } = useApi();
|
||||
return useQuery<any>({
|
||||
|
||||
const { data } = useQuery<DateRange>({
|
||||
queryKey: ['date-range', websiteId],
|
||||
queryFn: () => get(`/websites/${websiteId}/daterange`),
|
||||
enabled: !!websiteId,
|
||||
...options,
|
||||
});
|
||||
|
||||
return {
|
||||
startDate: data?.startDate ? new Date(data.startDate) : null,
|
||||
endDate: data?.endDate ? new Date(data.endDate) : null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useDateRange } from './useDateRange';
|
||||
import { useTimezone } from './useTimezone';
|
||||
|
||||
export function useDateParameters(websiteId: string) {
|
||||
export function useDateParameters() {
|
||||
const {
|
||||
dateRange: { startDate, endDate, unit },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
const { timezone, toUtc } = useTimezone();
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,35 +1,32 @@
|
|||
import { getMinimumUnit, parseDateRange } from '@/lib/date';
|
||||
import { useNavigation } from '@/components/hooks/useNavigation';
|
||||
import { useMemo } from 'react';
|
||||
import { getCompareDate, getOffsetDateRange, parseDateRange } from '@/lib/date';
|
||||
import { useLocale } from '@/components/hooks/useLocale';
|
||||
import { useApi } from '@/components/hooks//useApi';
|
||||
import { useQueryStringDate } from '@/components/hooks/useQueryStringDate';
|
||||
import { useGlobalState } from '@/components/hooks/useGlobalState';
|
||||
import { DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||
|
||||
export function useDateRange(websiteId: string) {
|
||||
const { get } = useApi();
|
||||
export function useDateRange(options: { ignoreOffset?: boolean } = {}) {
|
||||
const {
|
||||
query: { date = DEFAULT_DATE_RANGE_VALUE, offset = 0, compare = 'prev', all },
|
||||
} = useNavigation();
|
||||
const { locale } = useLocale();
|
||||
const { dateRange: defaultDateRange, dateCompare } = useQueryStringDate();
|
||||
|
||||
const [dateRange, setDateRange] = useGlobalState(`date-range:${websiteId}`, defaultDateRange);
|
||||
const dateRange = useMemo(() => {
|
||||
const dateRangeObject = parseDateRange(date, locale);
|
||||
|
||||
const setDateRangeValue = async (value: string) => {
|
||||
if (value === 'all') {
|
||||
const result = await get(`/websites/${websiteId}/daterange`);
|
||||
const { mindate, maxdate } = result;
|
||||
return !options.ignoreOffset && offset
|
||||
? getOffsetDateRange(dateRangeObject, +offset)
|
||||
: dateRangeObject;
|
||||
}, [date, offset, options]);
|
||||
|
||||
const startDate = new Date(mindate);
|
||||
const endDate = new Date(maxdate);
|
||||
const unit = getMinimumUnit(startDate, endDate);
|
||||
const dateCompare = getCompareDate(compare, dateRange.startDate, dateRange.endDate);
|
||||
|
||||
setDateRange({
|
||||
startDate,
|
||||
endDate,
|
||||
unit,
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
setDateRange(parseDateRange(value, locale));
|
||||
}
|
||||
return {
|
||||
date,
|
||||
offset,
|
||||
compare,
|
||||
isAllTime: !!all,
|
||||
isCustomRange: date.startsWith('range:'),
|
||||
dateRange,
|
||||
dateCompare,
|
||||
};
|
||||
|
||||
return { dateRange, dateCompare, setDateRange, setDateRangeValue };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
import { useNavigation } from '@/components/hooks/useNavigation';
|
||||
import { useMemo } from 'react';
|
||||
import { getCompareDate, getOffsetDateRange, parseDateRange } from '@/lib/date';
|
||||
import { useLocale } from '@/components/hooks/useLocale';
|
||||
import { DEFAULT_DATE_RANGE_VALUE } from '@/lib/constants';
|
||||
|
||||
export function useQueryStringDate(options: { ignoreOffset?: boolean } = {}) {
|
||||
const {
|
||||
query: { date = DEFAULT_DATE_RANGE_VALUE, offset = 0, compare = 'prev' },
|
||||
} = useNavigation();
|
||||
const { locale } = useLocale();
|
||||
|
||||
const dateRange = useMemo(() => {
|
||||
const dateRangeObject = parseDateRange(date, locale);
|
||||
|
||||
return !options.ignoreOffset && offset
|
||||
? getOffsetDateRange(dateRangeObject, +offset)
|
||||
: dateRangeObject;
|
||||
}, [date, offset, options]);
|
||||
|
||||
const dateCompare = getCompareDate(compare, dateRange.startDate, dateRange.endDate);
|
||||
|
||||
return { date, offset, dateRange, dateCompare };
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ export function RefreshButton({
|
|||
isLoading?: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { dateRange } = useDateRange(websiteId);
|
||||
const { dateRange } = useDateRange();
|
||||
|
||||
function handleClick() {
|
||||
if (!isLoading && dateRange) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { List, ListItem } from '@umami/react-zen';
|
||||
import { IconLabel, List, ListItem } from '@umami/react-zen';
|
||||
import { useWebsiteSegmentsQuery } from '@/components/hooks';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { ChartPie, UserPlus } from '@/components/icons';
|
||||
|
||||
export interface SegmentFiltersProps {
|
||||
websiteId: string;
|
||||
|
|
@ -29,7 +30,9 @@ export function SegmentFilters({
|
|||
{data?.data?.map(item => {
|
||||
return (
|
||||
<ListItem key={item.id} id={item.id}>
|
||||
{item.name}
|
||||
<IconLabel icon={type === 'segment' ? <ChartPie /> : <UserPlus />}>
|
||||
{item.name}
|
||||
</IconLabel>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import { Button, Icon, Row, Text, Select, ListItem } from '@umami/react-zen';
|
||||
import { isAfter } from 'date-fns';
|
||||
import { ChevronRight } from '@/components/icons';
|
||||
import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { useDateRange, useDateRangeQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { getDateRangeValue } from '@/lib/date';
|
||||
import { DateFilter } from './DateFilter';
|
||||
import { getOffsetDateRange } from '@/lib/date';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export interface WebsiteDateFilterProps {
|
||||
websiteId: string;
|
||||
|
|
@ -20,30 +20,33 @@ export function WebsiteDateFilter({
|
|||
showButtons = true,
|
||||
allowCompare,
|
||||
}: WebsiteDateFilterProps) {
|
||||
const { dateRange, setDateRange, setDateRangeValue } = useDateRange(websiteId);
|
||||
const { value, endDate } = dateRange;
|
||||
const { dateRange, isAllTime, isCustomRange } = useDateRange();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const {
|
||||
router,
|
||||
updateParams,
|
||||
query: { compare = 'prev', offset = 0 },
|
||||
} = useNavigation();
|
||||
const isAllTime = value === 'all';
|
||||
const disableForward = isAllTime || isAfter(dateRange.endDate, new Date());
|
||||
|
||||
const isCustomRange = value.startsWith('range');
|
||||
|
||||
const disableForward = value === 'all' || isAfter(endDate, new Date());
|
||||
const websiteDateRange = useDateRangeQuery(websiteId);
|
||||
|
||||
const handleChange = (date: string) => {
|
||||
setDateRangeValue(date);
|
||||
router.push(updateParams({ date, offset: undefined }));
|
||||
if (date === 'all') {
|
||||
router.push(
|
||||
updateParams({
|
||||
date: getDateRangeValue(websiteDateRange.startDate, websiteDateRange.endDate),
|
||||
offset: undefined,
|
||||
all: 1,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
router.push(updateParams({ date, offset: undefined }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleIncrement = useCallback(
|
||||
(increment: number) => {
|
||||
const offsetDate = getOffsetDateRange(dateRange, +offset + increment);
|
||||
|
||||
setDateRange(offsetDate);
|
||||
router.push(updateParams({ offset: +offset + increment }));
|
||||
},
|
||||
[offset],
|
||||
|
|
@ -53,6 +56,12 @@ export function WebsiteDateFilter({
|
|||
router.push(updateParams({ compare }));
|
||||
};
|
||||
|
||||
const dateValue = useMemo(() => {
|
||||
return offset !== 0
|
||||
? getDateRangeValue(dateRange.startDate, dateRange.endDate)
|
||||
: dateRange.value;
|
||||
}, [dateRange]);
|
||||
|
||||
return (
|
||||
<Row gap>
|
||||
{showButtons && !isAllTime && !isCustomRange && (
|
||||
|
|
@ -71,7 +80,7 @@ export function WebsiteDateFilter({
|
|||
)}
|
||||
<Row minWidth="200px">
|
||||
<DateFilter
|
||||
value={value}
|
||||
value={dateValue}
|
||||
onChange={handleChange}
|
||||
showAllTime={showAllTime}
|
||||
renderDate={+offset !== 0}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import { useDateRange } from '@/components/hooks';
|
||||
import { dateToRangeValue } from '@/lib/date';
|
||||
import { useDateRange, useNavigation } from '@/components/hooks';
|
||||
import { getMonthDateRangeValue } from '@/lib/date';
|
||||
import { MonthSelect } from './MonthSelect';
|
||||
|
||||
export function WebsiteMonthSelect({ websiteId }: { websiteId: string }) {
|
||||
export function WebsiteMonthSelect() {
|
||||
const { router, updateParams } = useNavigation();
|
||||
const {
|
||||
dateRange: { startDate },
|
||||
saveDateRange,
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
|
||||
const handleMonthSelect = (date: Date) => {
|
||||
const range = dateToRangeValue(date);
|
||||
saveDateRange(range);
|
||||
const range = getMonthDateRangeValue(date);
|
||||
|
||||
router.push(updateParams({ date: range, offset: undefined }));
|
||||
};
|
||||
|
||||
return <MonthSelect date={startDate} onChange={handleMonthSelect} />;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export interface EventsChartProps extends BarChartProps {
|
|||
export function EventsChart({ websiteId, focusLabel }: EventsChartProps) {
|
||||
const {
|
||||
dateRange: { startDate, endDate, unit },
|
||||
} = useDateRange(websiteId);
|
||||
} = useDateRange();
|
||||
const { locale } = useLocale();
|
||||
const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId);
|
||||
const [label, setLabel] = useState<string>(focusLabel);
|
||||
|
|
|
|||
|
|
@ -131,14 +131,6 @@ export function parseDateRange(value: string, locale = 'en-US'): DateRange {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (value === 'all') {
|
||||
return {
|
||||
startDate: new Date(0),
|
||||
endDate: new Date(1),
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
if (value.startsWith('range')) {
|
||||
const [, startTime, endTime] = value.split(':');
|
||||
|
||||
|
|
@ -225,24 +217,28 @@ export function getOffsetDateRange(dateRange: DateRange, offset: number) {
|
|||
case 'day':
|
||||
return {
|
||||
...dateRange,
|
||||
offset,
|
||||
startDate: addDays(startDate, change),
|
||||
endDate: addDays(endDate, change),
|
||||
};
|
||||
case 'week':
|
||||
return {
|
||||
...dateRange,
|
||||
offset,
|
||||
startDate: addWeeks(startDate, change),
|
||||
endDate: addWeeks(endDate, change),
|
||||
};
|
||||
case 'month':
|
||||
return {
|
||||
...dateRange,
|
||||
offset,
|
||||
startDate: addMonths(startDate, change),
|
||||
endDate: addMonths(endDate, change),
|
||||
};
|
||||
case 'year':
|
||||
return {
|
||||
...dateRange,
|
||||
offset,
|
||||
startDate: addYears(startDate, change),
|
||||
endDate: addYears(endDate, change),
|
||||
};
|
||||
|
|
@ -250,6 +246,7 @@ export function getOffsetDateRange(dateRange: DateRange, offset: number) {
|
|||
return {
|
||||
startDate: add(startDate, change),
|
||||
endDate: add(endDate, change),
|
||||
offset,
|
||||
value,
|
||||
unit,
|
||||
num,
|
||||
|
|
@ -354,6 +351,10 @@ export function generateTimeSeries(
|
|||
});
|
||||
}
|
||||
|
||||
export function dateToRangeValue(date: Date) {
|
||||
return `range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`;
|
||||
export function getDateRangeValue(startDate: Date, endDate: Date) {
|
||||
return `range:${startDate.getTime()}:${endDate.getTime()}`;
|
||||
}
|
||||
|
||||
export function getMonthDateRangeValue(date: Date) {
|
||||
return getDateRangeValue(startOfMonth(date), endOfMonth(date));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ async function relationalQuery(websiteId: string) {
|
|||
const result = await rawQuery(
|
||||
`
|
||||
select
|
||||
min(created_at) as mindate,
|
||||
max(created_at) as maxdate
|
||||
min(created_at) as startDate,
|
||||
max(created_at) as endDate
|
||||
from website_event
|
||||
where website_id = {{websiteId::uuid}}
|
||||
and created_at >= {{startDate}}
|
||||
|
|
@ -42,8 +42,8 @@ async function clickhouseQuery(websiteId: string) {
|
|||
const result = await rawQuery(
|
||||
`
|
||||
select
|
||||
min(created_at) as mindate,
|
||||
max(created_at) as maxdate
|
||||
min(created_at) as startDate,
|
||||
max(created_at) as endDate
|
||||
from website_event_stats_hourly
|
||||
where website_id = {websiteId:UUID}
|
||||
and created_at >= {startDate:DateTime64}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue