Fixed attribution report. New metric cards. Converted ListTable.

This commit is contained in:
Mike Cao 2025-06-11 00:05:34 -07:00
parent b2aa37a3df
commit 4995a0e1e4
19 changed files with 167 additions and 288 deletions

View file

@ -17,9 +17,7 @@ export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
return (
<Column gap>
<WebsiteControls websiteId={websiteId} allowCompare={true} />
<Panel>
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} />
</Panel>
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} />
<Panel>
<WebsiteChart websiteId={websiteId} compareMode={compare} />
</Panel>

View file

@ -32,7 +32,7 @@ export function WebsiteTableView({ websiteId }: { websiteId: string }) {
</Panel>
</GridRow>
<GridRow layout="two-one">
<Panel padding="0" gridColumn="span 2">
<Panel gridColumn="span 2" noPadding>
<WorldMap websiteId={websiteId} />
</Panel>
<Panel>

View file

@ -23,9 +23,7 @@ export function EventsPage({ websiteId }) {
return (
<Column gap="3">
<WebsiteControls websiteId={websiteId} />
<Panel>
<EventsMetricsBar websiteId={websiteId} />
</Panel>
<EventsMetricsBar websiteId={websiteId} />
<GridRow layout="two-one">
<Panel gridColumn="span 2">
<EventsChart websiteId={websiteId} focusLabel={label} />

View file

@ -13,7 +13,7 @@ import { RealtimeUrls } from './RealtimeUrls';
import { RealtimeCountries } from './RealtimeCountries';
import { percentFilter } from '@/lib/filters';
export function WebsiteRealtimePage({ websiteId }: { websiteId: string }) {
export function RealtimePage({ websiteId }: { websiteId: string }) {
const { data, isLoading, error } = useRealtimeQuery(websiteId);
if (isLoading || error) {
@ -28,9 +28,7 @@ export function WebsiteRealtimePage({ websiteId }: { websiteId: string }) {
return (
<Grid gap="3">
<Panel>
<RealtimeHeader data={data} />
</Panel>
<RealtimeHeader data={data} />
<Panel>
<RealtimeChart data={data} unit="minute" />
</Panel>
@ -46,7 +44,7 @@ export function WebsiteRealtimePage({ websiteId }: { websiteId: string }) {
<Panel>
<RealtimeCountries data={countries} />
</Panel>
<Panel padding="0" gridColumn="span 2">
<Panel gridColumn="span 2" noPadding>
<WorldMap data={countries} />
</Panel>
</GridRow>

View file

@ -1,10 +1,10 @@
import { WebsiteRealtimePage } from './WebsiteRealtimePage';
import { RealtimePage } from './RealtimePage';
import { Metadata } from 'next';
export default async function ({ params }: { params: Promise<{ websiteId: string }> }) {
const { websiteId } = await params;
return <WebsiteRealtimePage websiteId={websiteId} />;
return <RealtimePage websiteId={websiteId} />;
}
export const metadata: Metadata = {

View file

@ -2,13 +2,12 @@ import { Grid, Column } from '@umami/react-zen';
import { useMessages, useResultQuery } from '@/components/hooks';
import { Panel } from '@/components/common/Panel';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { formatLongNumber } from '@/lib/format';
import { CHART_COLORS } from '@/lib/constants';
import { PieChart } from '@/components/charts/PieChart';
import { ListTable } from '@/components/metrics/ListTable';
import { MetricCard } from '@/components/metrics/MetricCard';
import { MetricsBar } from '@/components/metrics/MetricsBar';
import { SectionHeader } from '@/components/common/SectionHeader';
import { formatLongNumber } from '@/lib/format';
import { percentFilter } from '@/lib/filters';
export interface AttributionProps {
websiteId: string;
@ -44,16 +43,8 @@ export function Attribution({
const isEmpty = !Object.keys(data || {}).length;
const { formatMessage, labels } = useMessages();
const ATTRIBUTION_PARAMS = [
{ value: 'referrer', label: formatMessage(labels.referrers) },
{ value: 'paidAds', label: formatMessage(labels.paidAds) },
];
if (!data) {
return null;
}
const { pageviews, visitors, visits } = data.total;
const { pageviews, visitors, visits } = data?.total || {};
const metrics = data
? [
@ -75,22 +66,18 @@ export function Attribution({
]
: [];
function UTMTable(UTMTableProps: { data: any; title: string; utm: string }) {
const { data, title, utm } = UTMTableProps;
const total = data[utm].reduce((sum, { value }) => {
return +sum + +value;
}, 0);
function UTMTable({ data = [], title }: { data: any; title: string }) {
return (
<ListTable
title={title}
metric={formatMessage(currency ? labels.revenue : labels.visitors)}
currency={currency}
data={data[utm].map(({ name, value }) => ({
x: name,
y: Number(value),
z: (value / total) * 100,
}))}
data={percentFilter(
data.map(({ name, value }) => ({
x: name,
y: Number(value),
})),
)}
/>
);
}
@ -98,58 +85,58 @@ export function Attribution({
return (
<LoadingPanel isEmpty={isEmpty} isLoading={isLoading} error={error}>
<Column gap>
<Panel>
<MetricsBar isFetched={data}>
{metrics?.map(({ label, value, formatValue }) => {
return (
<MetricCard key={label} value={value} label={label} formatValue={formatValue} />
);
})}
</MetricsBar>
</Panel>
{ATTRIBUTION_PARAMS.map(({ value, label }) => {
const items = data[value];
const total = items.reduce((sum, { value }) => {
return +sum + +value;
}, 0);
const chartData = {
labels: items.map(({ name }) => name),
datasets: [
{
data: items.map(({ value }) => value),
backgroundColor: CHART_COLORS,
borderWidth: 0,
},
],
};
return (
<Panel key={value} title={label}>
<Grid columns="1fr 1fr" gap>
<ListTable
metric={formatMessage(currency ? labels.revenue : labels.visitors)}
currency={currency}
data={items.map(({ name, value }) => ({
x: name,
y: Number(value),
z: (value / total) * 100,
}))}
/>
<PieChart type="doughnut" data={chartData} isLoading={isLoading} />
</Grid>
</Panel>
);
})}
<Panel title="UTM">
<Grid columns="1fr 1fr" gap>
<UTMTable data={data} title={formatMessage(labels.sources)} utm={'utm_source'} />
<UTMTable data={data} title={formatMessage(labels.medium)} utm={'utm_medium'} />
<UTMTable data={data} title={formatMessage(labels.campaigns)} utm={'utm_campaign'} />
<UTMTable data={data} title={formatMessage(labels.content)} utm={'utm_content'} />
<UTMTable data={data} title={formatMessage(labels.terms)} utm={'utm_term'} />
</Grid>
</Panel>
<MetricsBar isFetched={data}>
{metrics?.map(({ label, value, formatValue }) => {
return <MetricCard key={label} value={value} label={label} formatValue={formatValue} />;
})}
</MetricsBar>
<SectionHeader title={formatMessage(labels.sources)} />
<Grid columns="1fr 1fr" gap>
<Panel>
<ListTable
title={formatMessage(labels.referrer)}
metric={formatMessage(currency ? labels.revenue : labels.visitors)}
currency={currency}
data={percentFilter(
data?.['referrer']?.map(({ name, value }) => ({
x: name,
y: Number(value),
})),
)}
/>
</Panel>
<Panel>
<ListTable
title={formatMessage(labels.paidAds)}
metric={formatMessage(currency ? labels.revenue : labels.visitors)}
currency={currency}
data={percentFilter(
data?.['paidAds']?.map(({ name, value }) => ({
x: name,
y: Number(value),
})),
)}
/>
</Panel>
</Grid>
<SectionHeader title="UTM" />
<Grid columns="1fr 1fr" gap>
<Panel>
<UTMTable data={data?.['utm_source']} title={formatMessage(labels.sources)} />
</Panel>
<Panel>
<UTMTable data={data?.['utm_medium']} title={formatMessage(labels.medium)} />
</Panel>
<Panel>
<UTMTable data={data?.['utm_cmapaign']} title={formatMessage(labels.campaigns)} />
</Panel>
<Panel>
<UTMTable data={data?.['utm_content']} title={formatMessage(labels.content)} />
</Panel>
<Panel>
<UTMTable data={data?.['utm_term']} title={formatMessage(labels.terms)} />
</Panel>
</Grid>
</Column>
</LoadingPanel>
);

View file

@ -15,7 +15,7 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
} = useDateRange(websiteId);
return (
<Column gap>
<Column gap="6">
<WebsiteControls websiteId={websiteId} />
<Grid columns="1fr 1fr 1fr" gap>
<Column>
@ -46,6 +46,7 @@ export function AttributionPage({ websiteId }: { websiteId: string }) {
value={step}
defaultValue={step}
onSearch={setStep}
delay={1000}
/>
</Column>
</Grid>

View file

@ -22,7 +22,7 @@ export function BreakdownPage({ websiteId }: { websiteId: string }) {
const {
dateRange: { startDate, endDate },
} = useDateRange(websiteId);
const [fields, setFields] = useState([]);
const [fields, setFields] = useState(['url']);
return (
<Column gap>

View file

@ -154,15 +154,13 @@ export function Revenue({ websiteId, startDate, endDate }: RevenueProps) {
<LoadingPanel isEmpty={isEmpty} isLoading={isLoading} error={error}>
<Column gap>
<Panel>
<MetricsBar isFetched={!!data}>
{metricData?.map(({ label, value, formatValue }) => {
return (
<MetricCard key={label} value={value} label={label} formatValue={formatValue} />
);
})}
</MetricsBar>
</Panel>
<MetricsBar isFetched={!!data}>
{metricData?.map(({ label, value, formatValue }) => {
return (
<MetricCard key={label} value={value} label={label} formatValue={formatValue} />
);
})}
</MetricsBar>
{data && (
<>
<Panel>

View file

@ -18,11 +18,9 @@ export function SessionsPage({ websiteId }) {
return (
<Column gap="3">
<WebsiteControls websiteId={websiteId} />
<Panel>
<SessionsMetricsBar websiteId={websiteId} />
</Panel>
<SessionsMetricsBar websiteId={websiteId} />
<GridRow layout="two-one">
<Panel padding="0" gridColumn="span 2">
<Panel gridColumn="span 2" noPadding>
<WorldMap websiteId={websiteId} />
</Panel>
<Panel>