diff --git a/components/metrics/MetricCard.js b/components/metrics/MetricCard.js index 6209509c..d0394b97 100644 --- a/components/metrics/MetricCard.js +++ b/components/metrics/MetricCard.js @@ -3,13 +3,38 @@ import { useSpring, animated } from 'react-spring'; import { formatNumber } from '../../lib/format'; import styles from './MetricCard.module.css'; -const MetricCard = ({ value = 0, label, format = formatNumber }) => { +const MetricCard = ({ + value = 0, + change = 0, + label, + reverseColors = false, + format = formatNumber, +}) => { const props = useSpring({ x: Number(value) || 0, from: { x: 0 } }); + const changeProps = useSpring({ x: Number(change) || 0, from: { x: 0 } }); return (
{props.x.interpolate(x => format(x))} -
{label}
+
+ {label} + {~~change === 0 && {format(0)}} + {~~change !== 0 && ( + = 0 + ? !reverseColors + ? styles.positive + : styles.negative + : !reverseColors + ? styles.negative + : styles.positive + }`} + > + {changeProps.x.interpolate(x => `${change >= 0 ? '+' : ''}${format(x)}`)} + + )} +
); }; diff --git a/components/metrics/MetricCard.module.css b/components/metrics/MetricCard.module.css index 50b92ac8..76a69609 100644 --- a/components/metrics/MetricCard.module.css +++ b/components/metrics/MetricCard.module.css @@ -16,4 +16,24 @@ .label { font-size: var(--font-size-normal); white-space: nowrap; + display: flex; + align-items: center; + gap: 5px; +} + +.change { + font-size: 12px; + padding: 0 5px; + border-radius: 5px; + margin-left: 4px; + border: 1px solid var(--gray200); + color: var(--gray500); +} + +.change.positive { + color: var(--green500); +} + +.change.negative { + color: var(--red500); } diff --git a/components/metrics/MetricsBar.js b/components/metrics/MetricsBar.js index 945cd5e2..435870a1 100644 --- a/components/metrics/MetricsBar.js +++ b/components/metrics/MetricsBar.js @@ -34,14 +34,22 @@ export default function MetricsBar({ websiteId, className }) { [url, modified], ); - const formatFunc = format ? formatLongNumber : formatNumber; + const formatFunc = format + ? n => (n >= 0 ? formatLongNumber(n) : `-${formatLongNumber(Math.abs(n))}`) + : formatNumber; function handleSetFormat() { setFormat(state => !state); } const { pageviews, uniques, bounces, totaltime } = data || {}; - const num = Math.min(uniques, bounces); + const num = Math.min(data && uniques.value, data && bounces.value); + const diffs = data && { + pageviews: pageviews.value - pageviews.change, + uniques: uniques.value - uniques.change, + bounces: bounces.value - bounces.change, + totaltime: totaltime.value - totaltime.change, + }; return (
@@ -51,18 +59,27 @@ export default function MetricsBar({ websiteId, className }) { <> } - value={pageviews} + value={pageviews.value} + change={pageviews.change} format={formatFunc} /> } - value={uniques} + value={uniques.value} + change={uniques.change} format={formatFunc} /> } - value={uniques ? (num / uniques) * 100 : 0} + value={uniques.value ? (num / uniques.value) * 100 : 0} + change={ + uniques.value && uniques.change + ? (num / uniques.value) * 100 - + (Math.min(diffs.uniques, diffs.bounces) / diffs.uniques) * 100 || 0 + : 0 + } format={n => Number(n).toFixed(0) + '%'} + reverseColors /> } - value={totaltime && pageviews ? totaltime / (pageviews - bounces) : 0} - format={n => formatShortTime(n, ['m', 's'], ' ')} + value={ + totaltime.value && pageviews.value + ? totaltime.value / (pageviews.value - bounces.value) + : 0 + } + change={ + totaltime.value && pageviews.value + ? (diffs.totaltime / (diffs.pageviews - diffs.bounces) - + totaltime.value / (pageviews.value - bounces.value)) * + -1 || 0 + : 0 + } + format={n => `${n < 0 ? '-' : ''}${formatShortTime(Math.abs(~~n), ['m', 's'], ' ')}`} /> )} diff --git a/pages/api/website/[id]/stats.js b/pages/api/website/[id]/stats.js index 8b80a363..80dd22ec 100644 --- a/pages/api/website/[id]/stats.js +++ b/pages/api/website/[id]/stats.js @@ -14,10 +14,18 @@ export default async (req, res) => { const startDate = new Date(+start_at); const endDate = new Date(+end_at); + const distance = end_at - start_at; + const prevStartDate = new Date(+start_at - distance); + const prevEndDate = new Date(+end_at - distance); + const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url }); + const prevPeriod = await getWebsiteStats(websiteId, prevStartDate, prevEndDate, { url }); const stats = Object.keys(metrics[0]).reduce((obj, key) => { - obj[key] = Number(metrics[0][key]) || 0; + obj[key] = { + value: Number(metrics[0][key]) || 0, + change: Number(metrics[0][key] - prevPeriod[0][key]) || 0, + }; return obj; }, {});