'use client' import { useState } from 'react' import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' import { formatNumber, formatDuration } from '@/lib/utils/format' import { ArrowTopRightIcon, ArrowBottomRightIcon, DownloadIcon } from '@radix-ui/react-icons' const COLORS = { brand: '#FD5E0F', success: '#10B981', // Emerald-500 danger: '#EF4444', // Red-500 border: '#E5E5E5', // Neutral-200 text: '#171717', // Neutral-900 textMuted: '#737373', // Neutral-500 axis: '#A3A3A3', // Neutral-400 } interface DailyStat { date: string pageviews: number visitors: number } interface Stats { pageviews: number visitors: number bounce_rate: number avg_duration: number } interface ChartProps { data: DailyStat[] prevData?: DailyStat[] stats: Stats prevStats?: Stats interval: 'minute' | 'hour' | 'day' | 'month' } type MetricType = 'pageviews' | 'visitors' | 'bounce_rate' | 'avg_duration' export default function Chart({ data, prevData, stats, prevStats, interval }: ChartProps) { const [metric, setMetric] = useState('visitors') // * Align current and previous data const chartData = data.map((item, i) => { // * Try to find matching previous item (assuming same length/order) // * For more robustness, we could match by relative index const prevItem = prevData && prevData[i] // * Format date based on interval let formattedDate: string if (interval === 'minute') { formattedDate = new Date(item.date).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }) } else if (interval === 'hour') { formattedDate = new Date(item.date).toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' }) } else { formattedDate = new Date(item.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) } return { date: formattedDate, originalDate: item.date, pageviews: item.pageviews, visitors: item.visitors, prevPageviews: prevItem?.pageviews, prevVisitors: prevItem?.visitors, } }) // * Calculate trends const calculateTrend = (current: number, previous?: number) => { if (!previous) return null if (previous === 0) return current > 0 ? 100 : 0 return Math.round(((current - previous) / previous) * 100) } const handleExport = () => { const csvContent = "data:text/csv;charset=utf-8," + "Date,Pageviews,Visitors\n" + data.map(row => `${new Date(row.date).toISOString()},${row.pageviews},${row.visitors}`).join("\n") const encodedUri = encodeURI(csvContent) const link = document.createElement("a") link.setAttribute("href", encodedUri) link.setAttribute("download", `analytics_export_${new Date().toISOString().split('T')[0]}.csv`) document.body.appendChild(link) link.click() document.body.removeChild(link) } const metrics = [ { id: 'visitors', label: 'Unique Visitors', value: formatNumber(stats.visitors), trend: calculateTrend(stats.visitors, prevStats?.visitors), color: COLORS.brand, invertTrend: false, }, { id: 'pageviews', label: 'Total Pageviews', value: formatNumber(stats.pageviews), trend: calculateTrend(stats.pageviews, prevStats?.pageviews), color: COLORS.brand, invertTrend: false, }, { id: 'bounce_rate', label: 'Bounce Rate', value: `${Math.round(stats.bounce_rate)}%`, trend: calculateTrend(stats.bounce_rate, prevStats?.bounce_rate), color: COLORS.danger, invertTrend: true, // Lower bounce rate is better }, { id: 'avg_duration', label: 'Visit Duration', value: formatDuration(stats.avg_duration), trend: calculateTrend(stats.avg_duration, prevStats?.avg_duration), color: COLORS.success, invertTrend: false, }, ] as const const activeMetric = metrics.find(m => m.id === metric) || metrics[0] return (
{/* Stats Header (Interactive Tabs) */}
{metrics.map((item) => ( ))}
{/* Chart Area */}
value >= 1000 ? `${value/1000}k` : value} /> {/* Previous Period Line (Dashed) */} {prevData && ( )} {/* Current Period Area */}
) }