add comparison to events page metric bar
Some checks are pending
Node.js CI / build (push) Waiting to run

This commit is contained in:
Francis Cao 2026-02-17 22:14:10 -08:00
parent 530c21dac0
commit 99a328359b
3 changed files with 63 additions and 33 deletions

View file

@ -1,12 +1,11 @@
'use client'; 'use client';
import { Column, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen'; import { Column, Tab, TabList, TabPanel, Tabs } from '@umami/react-zen';
import locale from 'date-fns/locale/af'; import { type Key, useState } from 'react';
import { type Key, useMemo, useState } from 'react';
import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal'; import { SessionModal } from '@/app/(main)/websites/[websiteId]/sessions/SessionModal';
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
import { LoadingPanel } from '@/components/common/LoadingPanel'; import { LoadingPanel } from '@/components/common/LoadingPanel';
import { Panel } from '@/components/common/Panel'; import { Panel } from '@/components/common/Panel';
import { useMessages } from '@/components/hooks'; import { useDateRange, useMessages } from '@/components/hooks';
import { useEventStatsQuery } from '@/components/hooks/queries/useEventStatsQuery'; import { useEventStatsQuery } from '@/components/hooks/queries/useEventStatsQuery';
import { EventsChart } from '@/components/metrics/EventsChart'; import { EventsChart } from '@/components/metrics/EventsChart';
import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricCard } from '@/components/metrics/MetricCard';
@ -21,6 +20,7 @@ const KEY_NAME = 'umami.events.tab';
export function EventsPage({ websiteId }) { export function EventsPage({ websiteId }) {
const [tab, setTab] = useState(getItem(KEY_NAME) || 'chart'); const [tab, setTab] = useState(getItem(KEY_NAME) || 'chart');
const { isAllTime } = useDateRange();
const { t, labels, getErrorMessage } = useMessages(); const { t, labels, getErrorMessage } = useMessages();
const { data, isLoading, isFetching, error } = useEventStatsQuery({ const { data, isLoading, isFetching, error } = useEventStatsQuery({
websiteId, websiteId,
@ -31,34 +31,36 @@ export function EventsPage({ websiteId }) {
setTab(value); setTab(value);
}; };
const metrics = useMemo(() => { const { events, visitors, visits, uniqueEvents, comparison } = data || {};
if (!data) return [];
const { events, visitors, visits, uniqueEvents } = data || {}; const metrics = data
? [
return [ {
{ value: visitors,
value: visitors, label: t(labels.visitors),
label: t(labels.visitors), change: visitors - comparison.visitors,
formatValue: formatLongNumber, formatValue: formatLongNumber,
}, },
{ {
value: visits, value: visits,
label: t(labels.visits), label: t(labels.visits),
formatValue: formatLongNumber, change: visits - comparison.visits,
}, formatValue: formatLongNumber,
{ },
value: events, {
label: t(labels.events), value: events,
formatValue: formatLongNumber, label: t(labels.events),
}, change: events - comparison.events,
{ formatValue: formatLongNumber,
value: uniqueEvents, },
label: t(labels.uniqueEvents), {
formatValue: formatLongNumber, value: uniqueEvents,
}, label: t(labels.uniqueEvents),
] as any; change: uniqueEvents - comparison.uniqueEvents,
}, [data, locale]); formatValue: formatLongNumber,
},
]
: null;
return ( return (
<Column gap="3"> <Column gap="3">
@ -71,8 +73,17 @@ export function EventsPage({ websiteId }) {
minHeight="136px" minHeight="136px"
> >
<MetricsBar> <MetricsBar>
{metrics?.map(({ label, value, formatValue }) => { {metrics?.map(({ label, value, change, formatValue }) => {
return <MetricCard key={label} value={value} label={label} formatValue={formatValue} />; return (
<MetricCard
key={label}
value={value}
label={label}
change={change}
formatValue={formatValue}
showChange={!isAllTime}
/>
);
})} })}
</MetricsBar> </MetricsBar>
</LoadingPanel> </LoadingPanel>

View file

@ -1,3 +1,4 @@
import { getCompareDate } from '@/lib/date';
import { getQueryFilters, parseRequest } from '@/lib/request'; import { getQueryFilters, parseRequest } from '@/lib/request';
import { json, unauthorized } from '@/lib/response'; import { json, unauthorized } from '@/lib/response';
import { filterParams, withDateRange } from '@/lib/schema'; import { filterParams, withDateRange } from '@/lib/schema';
@ -28,5 +29,17 @@ export async function GET(
const data = await getWebsiteEventStats(websiteId, filters); const data = await getWebsiteEventStats(websiteId, filters);
return json({ data }); const { startDate, endDate } = getCompareDate(
filters.compare ?? 'prev',
filters.startDate,
filters.endDate,
);
const comparison = await getWebsiteEventStats(websiteId, {
...filters,
startDate,
endDate,
});
return json({ data: { ...data, comparison } });
} }

View file

@ -8,6 +8,12 @@ export interface EventStatsData {
visitors: number; visitors: number;
visits: number; visits: number;
uniqueEvents: number; uniqueEvents: number;
comparison: {
events: number;
visitors: number;
visits: number;
uniqueEvents: number;
};
} }
type EventStatsApiResponse = { type EventStatsApiResponse = {