'use client'; import React, { useState, useEffect, useCallback } from 'react'; import dynamic from 'next/dynamic'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { Table } from '@/components/ui/Table'; import { useTranslation } from '@/hooks/useTranslation'; import { StatCard, Sparkline } from '@/components/analytics'; import { Line, Doughnut } from 'react-chartjs-2'; import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, BarElement, ArcElement, Title, Tooltip, Legend, Filler, } from 'chart.js'; import { BarChart3, Users, Smartphone, Globe, Calendar, Download, TrendingUp, QrCode, HelpCircle, } from 'lucide-react'; // Dynamically import GeoMap to avoid SSR issues with d3 const GeoMap = dynamic(() => import('@/components/analytics/GeoMap'), { ssr: false, loading: () => (
Loading map...
), }); ChartJS.register( CategoryScale, LinearScale, PointElement, LineElement, BarElement, ArcElement, Title, Tooltip, Legend, Filler ); interface QRPerformance { id: string; title: string; type: string; totalScans: number; uniqueScans: number; conversion: number; trend: 'up' | 'down' | 'flat'; trendPercentage: number; sparkline: number[]; lastScanned: string | null; isNew?: boolean; } interface CountryStat { country: string; count: number; percentage: number; trend: 'up' | 'down' | 'flat'; trendPercentage: number; isNew?: boolean; } interface AnalyticsData { summary: { totalScans: number; uniqueScans: number; avgScansPerQR: number; mobilePercentage: number; topCountry: string; topCountryPercentage: number; scansTrend?: { trend: 'up' | 'down' | 'flat'; percentage: number; isNew?: boolean }; avgScansTrend?: { trend: 'up' | 'down' | 'flat'; percentage: number; isNew?: boolean }; comparisonPeriod?: string; }; deviceStats: Record; countryStats: CountryStat[]; dailyScans: Record; qrPerformance: QRPerformance[]; } export default function AnalyticsPage() { const { t } = useTranslation(); const [timeRange, setTimeRange] = useState('7'); const [loading, setLoading] = useState(true); const [analyticsData, setAnalyticsData] = useState(null); const fetchAnalytics = useCallback(async () => { setLoading(true); try { const response = await fetch(`/api/analytics/summary?range=${timeRange}`); if (response.ok) { const data = await response.json(); setAnalyticsData(data); } else { setAnalyticsData(null); } } catch (error) { console.error('Error fetching analytics:', error); setAnalyticsData(null); } finally { setLoading(false); } }, [timeRange]); useEffect(() => { fetchAnalytics(); }, [fetchAnalytics]); const exportReport = () => { if (!analyticsData) return; const csvData = [ ['QR Master Analytics Report'], ['Generated:', new Date().toLocaleString()], ['Time Range:', `Last ${timeRange} days`], [''], ['Summary'], ['Total Scans', analyticsData.summary.totalScans], ['Unique Scans', analyticsData.summary.uniqueScans], ['Mobile Usage %', analyticsData.summary.mobilePercentage], ['Top Country', analyticsData.summary.topCountry], [''], ['Top QR Codes'], ['Title', 'Type', 'Total Scans', 'Unique Scans', 'Conversion %', 'Last Scanned'], ...analyticsData.qrPerformance.map((qr) => [ qr.title, qr.type, qr.totalScans, qr.uniqueScans, qr.conversion, qr.lastScanned ? new Date(qr.lastScanned).toLocaleString() : 'Never', ]), ]; const csv = csvData.map((row) => row.join(',')).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `qr-analytics-${new Date().toISOString().split('T')[0]}.csv`; a.click(); URL.revokeObjectURL(url); }; // Prepare chart data const daysToShow = parseInt(timeRange); const dateRange = Array.from({ length: daysToShow }, (_, i) => { const date = new Date(); date.setDate(date.getDate() - (daysToShow - 1 - i)); return date.toISOString().split('T')[0]; }); const scanChartData = { labels: dateRange.map((date) => { const d = new Date(date); return d.toLocaleDateString('en', { month: 'short', day: 'numeric' }); }), datasets: [ { label: 'Scans', data: dateRange.map((date) => analyticsData?.dailyScans[date] || 0), borderColor: 'rgb(59, 130, 246)', backgroundColor: (context: any) => { const chart = context.chart; const { ctx, chartArea } = chart; if (!chartArea) return 'rgba(59, 130, 246, 0.1)'; const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom); gradient.addColorStop(0, 'rgba(59, 130, 246, 0.3)'); gradient.addColorStop(1, 'rgba(59, 130, 246, 0.01)'); return gradient; }, tension: 0.4, fill: true, pointRadius: 4, pointBackgroundColor: 'rgb(59, 130, 246)', pointBorderColor: '#fff', pointBorderWidth: 2, pointHoverRadius: 6, }, ], }; const deviceChartData = { labels: ['Desktop', 'Mobile', 'Tablet'], datasets: [ { data: [ analyticsData?.deviceStats.desktop || 0, analyticsData?.deviceStats.mobile || 0, analyticsData?.deviceStats.tablet || 0, ], backgroundColor: [ 'rgba(59, 130, 246, 0.85)', 'rgba(34, 197, 94, 0.85)', 'rgba(249, 115, 22, 0.85)', ], borderWidth: 0, hoverOffset: 4, }, ], }; // Find top performing QR code const topQR = analyticsData?.qrPerformance[0]; if (loading) { return (
{[1, 2, 3, 4].map((i) => (
))}
); } return (
{/* Header */}

QR Code Analytics

Track and analyze your QR code performance

{/* Date Range Selector */}
{[ { value: '7', label: '7 Days' }, { value: '30', label: '30 Days' }, { value: '90', label: '90 Days' }, ].map((range) => ( ))}
{/* KPI Cards */}
} /> } /> } /> } />
{/* Main Chart Row */}
{/* Scans Over Time - Takes 2 columns */} Scan Trends Over Time
{timeRange} Days
items[0]?.label || '', label: (item) => `${item.formattedValue} scans`, }, }, }, scales: { x: { grid: { display: false }, ticks: { color: '#9CA3AF' }, }, y: { beginAtZero: true, grid: { color: 'rgba(156, 163, 175, 0.1)' }, ticks: { color: '#9CA3AF', precision: 0 }, }, }, }} />
{/* Device Types Donut */} Device Types
{(analyticsData?.summary.totalScans || 0) > 0 ? ( ) : (

No scan data available

)}
{/* Geographic & Country Stats Row */}
{/* Geographic Insights with Map */} Geographic Insights
{/* Top Countries Table */} Top Countries {(analyticsData?.countryStats?.length || 0) > 0 ? (
{analyticsData!.countryStats.slice(0, 5).map((country, index) => (
{index + 1} {country.country}
{country.count.toLocaleString()} {country.percentage}% {country.trend === 'up' ? '↑' : country.trend === 'down' ? '↓' : '→'} {country.trendPercentage}%{country.isNew ? ' (new)' : ''}
))}
) : (

No country data available yet

)}
{/* Top Performing QR Codes with Sparklines */} Top Performing QR Codes {(analyticsData?.qrPerformance?.length || 0) > 0 ? (
{analyticsData!.qrPerformance.map((qr) => ( ))}
QR Code Type Total Scans Unique Scans
Conversions
Conversion Rate
Percentage of unique scans vs total scans. Formula: (Unique Scans / Total Scans) × 100%
Trend
{qr.title} {qr.type} {qr.totalScans.toLocaleString()} {qr.uniqueScans.toLocaleString()} {qr.conversion}%
{qr.trend === 'up' ? '↑' : qr.trend === 'down' ? '↓' : '→'} {qr.trendPercentage}%{qr.isNew ? ' (new)' : ''}
) : (

No QR codes created yet. Create your first QR code to see analytics!

)}
); }