Updated events/sessions pages. Added DateDistance component.

This commit is contained in:
Mike Cao 2025-06-30 21:34:56 -07:00
parent 8b64029409
commit 5b300f1ff5
13 changed files with 44 additions and 35 deletions

View file

@ -3,7 +3,6 @@ import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
import { EventsTable } from '@/components/metrics/EventsTable'; import { EventsTable } from '@/components/metrics/EventsTable';
import { useState } from 'react'; import { useState } from 'react';
import { EventsDataTable } from './EventsDataTable'; import { EventsDataTable } from './EventsDataTable';
import { EventsMetricsBar } from './EventsMetricsBar';
import { Panel } from '@/components/common/Panel'; import { Panel } from '@/components/common/Panel';
import { EventsChart } from '@/components/metrics/EventsChart'; import { EventsChart } from '@/components/metrics/EventsChart';
import { GridRow } from '@/components/common/GridRow'; import { GridRow } from '@/components/common/GridRow';
@ -23,7 +22,6 @@ export function EventsPage({ websiteId }) {
return ( return (
<Column gap="3"> <Column gap="3">
<WebsiteControls websiteId={websiteId} /> <WebsiteControls websiteId={websiteId} />
<EventsMetricsBar websiteId={websiteId} />
<GridRow layout="two-one"> <GridRow layout="two-one">
<Panel gridColumn="span 2"> <Panel gridColumn="span 2">
<EventsChart websiteId={websiteId} focusLabel={label} /> <EventsChart websiteId={websiteId} focusLabel={label} />

View file

@ -1,12 +1,12 @@
import { DataTable, DataColumn, Icon, Row } from '@umami/react-zen'; import { DataTable, DataColumn, Icon, Row } from '@umami/react-zen';
import { useMessages, useNavigation, useTimezone } from '@/components/hooks'; import { useMessages, useNavigation } from '@/components/hooks';
import { Empty } from '@/components/common/Empty'; import { Empty } from '@/components/common/Empty';
import { Avatar } from '@/components/common/Avatar'; import { Avatar } from '@/components/common/Avatar';
import Link from 'next/link'; import Link from 'next/link';
import { Bolt, Eye } from '@/components/icons'; import { Bolt, Eye } from '@/components/icons';
import { DateDistance } from '@/components/common/DateDistance';
export function EventsTable({ data = [] }) { export function EventsTable({ data = [] }) {
const { formatTimezoneDate } = useTimezone();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { renderUrl } = useNavigation(); const { renderUrl } = useNavigation();
@ -34,8 +34,8 @@ export function EventsTable({ data = [] }) {
); );
}} }}
</DataColumn> </DataColumn>
<DataColumn id="created" label={formatMessage(labels.created)} width="1fr"> <DataColumn id="created" label={formatMessage(labels.created)} width="200px">
{(row: any) => formatTimezoneDate(row.createdAt, 'PPPpp')} {(row: any) => <DateDistance date={new Date(row.createdAt)} />}
</DataColumn> </DataColumn>
</DataTable> </DataTable>
); );

View file

@ -2,12 +2,8 @@
import { useState } from 'react'; import { useState } from 'react';
import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen'; import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
import { SessionsDataTable } from './SessionsDataTable'; import { SessionsDataTable } from './SessionsDataTable';
import { SessionsMetricsBar } from './SessionsMetricsBar';
import { SessionProperties } from './SessionProperties'; import { SessionProperties } from './SessionProperties';
import { WorldMap } from '@/components/metrics/WorldMap';
import { GridRow } from '@/components/common/GridRow';
import { useMessages } from '@/components/hooks'; import { useMessages } from '@/components/hooks';
import { SessionsWeekly } from './SessionsWeekly';
import { Panel } from '@/components/common/Panel'; import { Panel } from '@/components/common/Panel';
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls'; import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
@ -18,15 +14,6 @@ export function SessionsPage({ websiteId }) {
return ( return (
<Column gap="3"> <Column gap="3">
<WebsiteControls websiteId={websiteId} /> <WebsiteControls websiteId={websiteId} />
<SessionsMetricsBar websiteId={websiteId} />
<GridRow layout="two-one">
<Panel gridColumn="span 2" noPadding>
<WorldMap websiteId={websiteId} />
</Panel>
<Panel>
<SessionsWeekly websiteId={websiteId} />
</Panel>
</GridRow>
<Panel> <Panel>
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}> <Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
<TabList> <TabList>

View file

@ -1,11 +1,11 @@
import Link from 'next/link'; import Link from 'next/link';
import { DataColumn, DataTable } from '@umami/react-zen'; import { DataColumn, DataTable } from '@umami/react-zen';
import { useFormat, useMessages, useTimezone } from '@/components/hooks'; import { useFormat, useMessages } from '@/components/hooks';
import { Avatar } from '@/components/common/Avatar'; import { Avatar } from '@/components/common/Avatar';
import { TypeIcon } from '@/components/common/TypeIcon'; import { TypeIcon } from '@/components/common/TypeIcon';
import { DateDistance } from '@/components/common/DateDistance';
export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean }) { export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean }) {
const { formatTimezoneDate } = useTimezone();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { formatValue } = useFormat(); const { formatValue } = useFormat();
@ -50,7 +50,7 @@ export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean
)} )}
</DataColumn> </DataColumn>
<DataColumn id="lastAt" label={formatMessage(labels.lastSeen)}> <DataColumn id="lastAt" label={formatMessage(labels.lastSeen)}>
{(row: any) => formatTimezoneDate(row.createdAt, 'PPPpp')} {(row: any) => <DateDistance date={new Date(row.createdAt)} />}
</DataColumn> </DataColumn>
</DataTable> </DataTable>
); );

View file

@ -28,7 +28,7 @@ export async function GET(
return unauthorized(); return unauthorized();
} }
const data = await getWebsiteEvents(websiteId, { startDate, endDate }, query); const data = await getWebsiteEvents(websiteId, { ...query, startDate, endDate }, query);
return json(data); return json(data);
} }

View file

@ -21,7 +21,7 @@ const dateFormats = {
}; };
export interface BarChartProps extends ChartProps { export interface BarChartProps extends ChartProps {
unit: string; unit?: string;
stacked?: boolean; stacked?: boolean;
currency?: string; currency?: string;
renderXLabel?: (label: string, index: number, values: any[]) => string; renderXLabel?: (label: string, index: number, values: any[]) => string;

View file

@ -0,0 +1,17 @@
import { Tooltip, TooltipTrigger, Text, Focusable } from '@umami/react-zen';
import { formatDistanceToNow } from 'date-fns';
import { useLocale, useTimezone } from '@/components/hooks';
export function DateDistance({ date }: { date: Date }) {
const { formatTimezoneDate } = useTimezone();
const { dateLocale } = useLocale();
return (
<TooltipTrigger delay={0}>
<Focusable>
<Text>{formatDistanceToNow(date, { addSuffix: true, locale: dateLocale })}</Text>
</Focusable>
<Tooltip>{formatTimezoneDate(date.toISOString(), 'PPPpp')}</Tooltip>
</TooltipTrigger>
);
}

View file

@ -2,7 +2,7 @@ import { useApi } from '../useApi';
import { useFilterParams } from '../useFilterParams'; import { useFilterParams } from '../useFilterParams';
import { ReactQueryOptions } from '@/lib/types'; import { ReactQueryOptions } from '@/lib/types';
export function useResultQuery<T>( export function useResultQuery<T = any>(
type: string, type: string,
params?: { [key: string]: any }, params?: { [key: string]: any },
options?: ReactQueryOptions<T>, options?: ReactQueryOptions<T>,

View file

@ -1,12 +1,11 @@
import { useApi } from '../useApi'; import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import { useModified } from '../useModified'; import { useModified } from '../useModified';
export function useTeamsQuery(userId: string) { export function useTeamsQuery(userId: string) {
const { get } = useApi(); const { get, useQuery } = useApi();
const { modified } = useModified(`teams`); const { modified } = useModified(`teams`);
return usePagedQuery({ return useQuery({
queryKey: ['teams', { userId, modified }], queryKey: ['teams', { userId, modified }],
queryFn: (params: any) => { queryFn: (params: any) => {
return get(`/users/${userId}/teams`, params); return get(`/users/${userId}/teams`, params);

View file

@ -5,12 +5,11 @@ import { ReactQueryOptions } from '@/lib/types';
export function useWebsiteEventsQuery(websiteId: string, options?: ReactQueryOptions<any>) { export function useWebsiteEventsQuery(websiteId: string, options?: ReactQueryOptions<any>) {
const { get } = useApi(); const { get } = useApi();
const params = useFilterParams(websiteId); const filterParams = useFilterParams(websiteId);
return usePagedQuery({ return usePagedQuery({
queryKey: ['websites:events', { websiteId, ...params }], queryKey: ['websites:events', { websiteId, ...filterParams }],
queryFn: pageParams => queryFn: () => get(`/websites/${websiteId}/events`, { ...filterParams, pageSize: 20 }),
get(`/websites/${websiteId}/events`, { ...params, ...pageParams, pageSize: 20 }),
enabled: !!websiteId, enabled: !!websiteId,
...options, ...options,
}); });

View file

@ -22,14 +22,19 @@ export function useFilterParams(websiteId: string) {
event, event,
tag, tag,
hostname, hostname,
page,
pageSize,
search,
}, },
} = useNavigation(); } = useNavigation();
return { return {
// Date range
startAt: +toUtc(startDate), startAt: +toUtc(startDate),
endAt: +toUtc(endDate), endAt: +toUtc(endDate),
unit, unit,
timezone, timezone,
// Filters
path, path,
referrer, referrer,
title, title,
@ -44,5 +49,9 @@ export function useFilterParams(websiteId: string) {
event, event,
tag, tag,
hostname, hostname,
// Paging
page,
pageSize,
search,
}; };
} }

View file

@ -12,7 +12,7 @@ export function usePagedQuery<T = any>({
const { query: queryParams } = useNavigation(); const { query: queryParams } = useNavigation();
const [params, setParams] = useState<PageParams>({ const [params, setParams] = useState<PageParams>({
search: '', search: '',
page: +queryParams?.page || 1, page: queryParams?.page || '1',
}); });
const { useQuery } = useApi(); const { useQuery } = useApi();
@ -25,7 +25,7 @@ export function usePagedQuery<T = any>({
return { return {
result: data as PageResult<T>, result: data as PageResult<T>,
query, query,
filterParams: params, params,
setParams, setParams,
}; };
} }

View file

@ -29,7 +29,7 @@ export interface PageResult<T> {
sortDescending?: boolean; sortDescending?: boolean;
} }
export interface PagedQueryResult<T> { export interface PagedQueryResult<T = any> {
result: PageResult<T>; result: PageResult<T>;
query: any; query: any;
params: PageParams; params: PageParams;