'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 ? (
|
QR Code
|
Type
|
Total Scans
|
Unique Scans
|
Conversions
Conversion Rate
Percentage of unique scans vs total scans. Formula: (Unique Scans / Total Scans) × 100%
|
Trend
|
{analyticsData!.qrPerformance.map((qr) => (
|
{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!
)}
);
}