Added comparison tables.

This commit is contained in:
Mike Cao 2024-05-26 17:26:15 -07:00
parent 626fe14fc2
commit b7a7d4de4d
18 changed files with 220 additions and 168 deletions

View file

@ -4,7 +4,7 @@ import { useFilterParams } from '../useFilterParams';
export function useWebsiteMetrics(
websiteId: string,
query: { type: string; limit: number; search: string },
queryParams: { type: string; limit: number; search: string; startAt?: number; endAt?: number },
options?: Omit<UseQueryOptions & { onDataLoad?: (data: any) => void }, 'queryKey' | 'queryFn'>,
) {
const { get, useQuery } = useApi();
@ -16,17 +16,17 @@ export function useWebsiteMetrics(
{
websiteId,
...params,
...query,
...queryParams,
},
],
queryFn: async () => {
const filters = { ...params };
filters[query.type] = undefined;
filters[queryParams.type] = undefined;
const data = await get(`/websites/${websiteId}/metrics`, {
...filters,
...query,
...queryParams,
});
options?.onDataLoad?.(data);

View file

@ -253,8 +253,10 @@ export const labels = defineMessages({
defaultMessage: 'Understand how users nagivate through your website.',
},
compare: { id: 'label.compare', defaultMessage: 'Compare' },
current: { id: 'label.current', defaultMessage: 'Current' },
previous: { id: 'label.previous', defaultMessage: 'Previous' },
previousPeriod: { id: 'label.previous-period', defaultMessage: 'Previous period' },
yearOverYear: { id: 'label.year-over-year', defaultMessage: 'Year over year' },
previousYear: { id: 'label.previous-year', defaultMessage: 'Previous year' },
});
export const messages = defineMessages({

View file

@ -0,0 +1,31 @@
.label {
display: flex;
align-items: center;
gap: 5px;
font-size: 13px;
font-weight: 700;
padding: 0.1em 0.5em;
border-radius: 5px;
color: var(--base500);
align-self: flex-start;
}
.positive {
color: var(--green700);
background: var(--green100);
}
.negative {
color: var(--red700);
background: var(--red100);
}
.neutral {
color: var(--base700);
background: var(--base100);
}
.new {
color: var(--blue900);
background: var(--blue100);
}

View file

@ -0,0 +1,44 @@
import classNames from 'classnames';
import { Icon, Icons } from 'react-basics';
import { ReactNode } from 'react';
import styles from './ChangeLabel.module.css';
export function ChangeLabel({
value,
size,
reverseColors,
className,
children,
}: {
value: number;
size?: 'xs' | 'sm' | 'md' | 'lg';
reverseColors?: boolean;
showPercentage?: boolean;
className?: string;
children?: ReactNode;
}) {
const positive = value * (reverseColors ? -1 : 1) >= 0;
const negative = value * (reverseColors ? -1 : 1) < 0;
const isNew = isNaN(value);
return (
<div
className={classNames(styles.label, className, {
[styles.positive]: positive,
[styles.negative]: negative,
[styles.neutral]: value === 0,
[styles.new]: isNew,
})}
title={value.toString()}
>
{!isNew && (
<Icon rotate={value === 0 ? 0 : positive || reverseColors ? -45 : 45} size={size}>
<Icons.ArrowRight />
</Icon>
)}
{children || value}
</div>
);
}
export default ChangeLabel;

View file

@ -72,9 +72,11 @@
}
.value {
width: 50px;
display: flex;
align-items: center;
gap: 10px;
text-align: end;
margin-inline-end: 10px;
margin-inline-end: 5px;
font-weight: 600;
}

View file

@ -15,6 +15,7 @@ export interface ListTableProps {
metric?: string;
className?: string;
renderLabel?: (row: any, index: number) => ReactNode;
renderChange?: (row: any, index: number) => ReactNode;
animate?: boolean;
virtualize?: boolean;
showPercentage?: boolean;
@ -27,6 +28,7 @@ export function ListTable({
metric,
className,
renderLabel,
renderChange,
animate = true,
virtualize = false,
showPercentage = true,
@ -45,6 +47,7 @@ export function ListTable({
percent={percent}
animate={animate && !virtualize}
showPercentage={showPercentage}
change={renderChange ? renderChange(row, index) : null}
/>
);
};
@ -78,7 +81,7 @@ export function ListTable({
);
}
const AnimatedRow = ({ label, value = 0, percent, animate, showPercentage = true }) => {
const AnimatedRow = ({ label, value = 0, percent, change, animate, showPercentage = true }) => {
const props = useSpring({
width: percent,
y: value,
@ -90,6 +93,7 @@ const AnimatedRow = ({ label, value = 0, percent, animate, showPercentage = true
<div className={styles.row}>
<div className={styles.label}>{label}</div>
<div className={styles.value}>
{change}
<animated.div className={styles.value} title={props?.y as any}>
{props.y?.to(formatLongNumber)}
</animated.div>

View file

@ -35,29 +35,3 @@
white-space: nowrap;
color: var(--base800);
}
.change {
display: flex;
align-items: center;
gap: 5px;
font-size: 13px;
font-weight: 700;
padding: 0.1em 0.5em;
border-radius: 5px;
color: var(--base500);
align-self: flex-start;
}
.change.positive {
color: var(--green700);
background: var(--green100);
}
.change.negative {
color: var(--red700);
background: var(--red100);
}
.hide {
visibility: hidden;
}

View file

@ -1,7 +1,7 @@
import classNames from 'classnames';
import { Icon, Icons } from 'react-basics';
import { useSpring, animated } from '@react-spring/web';
import { formatNumber } from 'lib/format';
import ChangeLabel from 'components/metrics/ChangeLabel';
import styles from './MetricCard.module.css';
export interface MetricCardProps {
@ -31,30 +31,19 @@ export const MetricCard = ({
const props = useSpring({ x: Number(value) || 0, from: { x: 0 } });
const changeProps = useSpring({ x: Number(change) || 0, from: { x: 0 } });
const prevProps = useSpring({ x: Number(value - change) || 0, from: { x: 0 } });
const positive = change * (reverseColors ? -1 : 1) >= 0;
const negative = change * (reverseColors ? -1 : 1) < 0;
return (
<div className={classNames(styles.card, className, showPrevious && styles.compare)}>
{showLabel && <div className={styles.label}>{label}</div>}
<animated.div className={styles.value} title={props?.x as any}>
<animated.div className={styles.value} title={value.toString()}>
{props?.x?.to(x => format(x))}
</animated.div>
{showChange && (
<div
className={classNames(styles.change, {
[styles.positive]: positive,
[styles.negative]: negative,
[styles.hide]: ~~change === 0,
})}
>
<Icon rotate={positive || reverseColors ? -45 : 45} size={showPrevious ? 'md' : 'xs'}>
<Icons.ArrowRight />
</Icon>
<animated.span title={changeProps?.x as any}>
<ChangeLabel className={styles.change} value={change} reverseColors={reverseColors}>
<animated.span title={change.toString()}>
{changeProps?.x?.to(x => format(Math.abs(x)))}
</animated.span>
</div>
</ChangeLabel>
)}
{showPrevious && (
<animated.div className={classNames(styles.value, styles.prev)} title={prevProps?.x as any}>

View file

@ -27,6 +27,7 @@ export interface MetricsTableProps extends ListTableProps {
onSearch?: (search: string) => void;
allowSearch?: boolean;
showMore?: boolean;
params?: { [key: string]: any };
children?: ReactNode;
}
@ -40,6 +41,7 @@ export function MetricsTable({
delay = null,
allowSearch = false,
showMore = true,
params,
children,
...props
}: MetricsTableProps) {
@ -51,7 +53,7 @@ export function MetricsTable({
const { data, isLoading, isFetched, error } = useWebsiteMetrics(
websiteId,
{ type, limit, search },
{ type, limit, search, ...params },
{
retryDelay: delay || DEFAULT_ANIMATION_DURATION,
onDataLoad,

View file

@ -55,7 +55,7 @@ export function PagesTable({ allowFilter, ...props }: PagesTableProps) {
type={view}
metric={formatMessage(labels.views)}
dataFilter={emptyFilter}
renderLabel={props.renderLabel || renderLink}
renderLabel={renderLink}
>
{allowFilter && <FilterButtons items={buttons} selectedKey={view} onSelect={handleSelect} />}
</MetricsTable>

View file

@ -46,7 +46,7 @@ export function PageviewsChart({ data, unit, isLoading, ...props }: PageviewsCha
? [
{
type: 'line',
label: `${formatMessage(labels.visits)} (VS)`,
label: `${formatMessage(labels.visits)} (${formatMessage(labels.previous)})`,
data: data.compare.pageviews,
borderWidth: 2,
backgroundColor: '#8601B0',
@ -55,7 +55,7 @@ export function PageviewsChart({ data, unit, isLoading, ...props }: PageviewsCha
},
{
type: 'line',
label: `${formatMessage(labels.visitors)} (VS)`,
label: `${formatMessage(labels.visitors)} (${formatMessage(labels.previous)})`,
data: data.compare.sessions,
borderWidth: 2,
backgroundColor: '#f15bb5',