'use client'; import { Area, AreaChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts'; import type { UmamiPageviewsSeries } from '@/lib/services/umami.service'; interface Props { data: UmamiPageviewsSeries | null; } /** * Stacks pageviews on top of sessions in a simple area chart. Umami's * timeseries comes pre-bucketed (the service picks bucket size based on * range span - minute/hour/day/month). * * X-axis labels are kept short to avoid overflow on dense ranges. */ export function PageviewsChart({ data }: Props) { if (!data || data.pageviews.length === 0) { return (
No data in this range
); } // Merge the two series (Umami returns them separately when `compare` is // requested) into one row per bucket so we can drive a single chart. // `sessions` is optional on Umami v3 — only present when the request // included a comparison directive. Guard the read so an undefined // array doesn't crash the chart. const byX = new Map(); for (const p of data.pageviews) { byX.set(p.x, { x: p.x, pageviews: p.y, sessions: 0 }); } for (const s of data.sessions ?? []) { const row = byX.get(s.x); if (row) row.sessions = s.y; else byX.set(s.x, { x: s.x, pageviews: 0, sessions: s.y }); } const merged = Array.from(byX.values()).sort((a, b) => a.x.localeCompare(b.x)); return ( ); } /** Compact tick labels: drop the timestamp entirely — for multi-day ranges * the hour component is meaningless (a "day" bucket aggregates the whole * day) and just causes visual crowding. Keep MM-DD. */ function formatXTick(value: string): string { return value.slice(5, 10); // "MM-DD" } /** Tooltip header: format "2026-03-30 00:00:00" → "Mar 30, 2026" so the * meaningless 00:00:00 timestamp doesn't show. */ function formatTooltipLabel(value: unknown): string { if (typeof value !== 'string') return ''; const datePart = value.slice(0, 10); // "YYYY-MM-DD" const d = new Date(`${datePart}T00:00:00Z`); if (isNaN(d.getTime())) return datePart; return d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', timeZone: 'UTC', }); }