"use client"; import { useState, useEffect, useCallback } from "react"; import { useAuth } from "@/contexts/AuthContext"; import { visitorsApi, OverviewStats, ChartData, BreakdownData, TopPagesData, TopReferrersData, RealtimeStats, CountryStats, } from "@/lib/api"; import { Users, Globe, Monitor, Smartphone, Tablet, TrendingUp, TrendingDown, RefreshCw, BarChart3, Activity, } from "lucide-react"; export default function VisitorStatsPage() { const { user, token } = useAuth(); const [days, setDays] = useState(30); const [loading, setLoading] = useState(true); const [overview, setOverview] = useState(null); const [visitsChart, setVisitsChart] = useState(null); const [uniqueChart, setUniqueChart] = useState(null); const [deviceBreakdown, setDeviceBreakdown] = useState(null); const [browserBreakdown, setBrowserBreakdown] = useState(null); const [osBreakdown, setOsBreakdown] = useState(null); const [topPages, setTopPages] = useState(null); const [topReferrers, setTopReferrers] = useState(null); const [realtime, setRealtime] = useState(null); const [countryStats, setCountryStats] = useState([]); const fetchData = useCallback(async () => { if (!token) return; setLoading(true); try { const [ overviewRes, visitsRes, uniqueRes, deviceRes, browserRes, osRes, pagesRes, referrersRes, realtimeRes, countryRes, ] = await Promise.all([ visitorsApi.adminGetOverview(token, days), visitorsApi.adminGetVisitsChart(token, days), visitorsApi.adminGetUniqueChart(token, days), visitorsApi.adminGetDeviceBreakdown(token, days), visitorsApi.adminGetBrowserBreakdown(token, days), visitorsApi.adminGetOsBreakdown(token, days), visitorsApi.adminGetTopPages(token, days), visitorsApi.adminGetTopReferrers(token, days), visitorsApi.adminGetRealtime(token), visitorsApi.adminGetCountryStats(token), ]); setOverview(overviewRes); setVisitsChart(visitsRes); setUniqueChart(uniqueRes); setDeviceBreakdown(deviceRes); setBrowserBreakdown(browserRes); setOsBreakdown(osRes); setTopPages(pagesRes); setTopReferrers(referrersRes); setRealtime(realtimeRes); setCountryStats(countryRes); } catch (error) { console.error("Failed to fetch stats:", error); } finally { setLoading(false); } }, [token, days]); useEffect(() => { fetchData(); }, [fetchData]); // Auto-refresh realtime stats every 30 seconds useEffect(() => { if (!token) return; const interval = setInterval(async () => { try { const realtimeRes = await visitorsApi.adminGetRealtime(token); setRealtime(realtimeRes); } catch (error) { console.error("Failed to refresh realtime:", error); } }, 30000); return () => clearInterval(interval); }, [token]); if (!user?.is_admin) { return (
Access denied. Admin only.
); } const getCountryFlag = (countryCode: string) => { const flags: Record = { KR: "🇰🇷", US: "🇺🇸", JP: "🇯🇵", CN: "🇨🇳", DE: "🇩🇪", GB: "🇬🇧", FR: "🇫🇷", CA: "🇨🇦", AU: "🇦🇺", IN: "🇮🇳", RU: "🇷🇺", BR: "🇧🇷", MX: "🇲🇽", ES: "🇪🇸", IT: "🇮🇹", LO: "🏠", "??": "🌍", }; return flags[countryCode] || "🌍"; }; const getDeviceIcon = (deviceType: string) => { switch (deviceType.toLowerCase()) { case "mobile": return ; case "tablet": return ; default: return ; } }; // Simple bar chart component const BarChart = ({ data, labels, color = "bg-blue-500" }: { data: number[], labels: string[], color?: string }) => { const max = Math.max(...data, 1); return (
{data.map((value, index) => (
0 ? "4px" : "0" }} title={`${labels[index]}: ${value}`} /> {data.length <= 14 && ( {labels[index]} )}
))}
); }; // Progress bar component const ProgressBar = ({ percentage, color = "bg-blue-500" }: { percentage: number, color?: string }) => (
); return (
{/* Header */}

Visitor Statistics

Detailed analytics for your website

{/* Period selector */}
{[7, 14, 30, 90].map((d) => ( ))}
{/* Realtime indicator */}
{realtime?.active_visitors || 0} active
{/* Refresh button */}
{/* Overview Cards */}

Total Visits

{overview?.total_visits?.toLocaleString() || 0}

Last {days} days

Unique Visitors

{overview?.unique_visitors?.toLocaleString() || 0}

{overview?.pages_per_visit || 0} pages/visit

Today

{overview?.today_visitors || 0}

= 0 ? "bg-green-100" : "bg-red-100" }`}> {(overview?.growth_rate || 0) >= 0 ? ( ) : ( )}

= 0 ? "text-green-600" : "text-red-600" }`}> {(overview?.growth_rate || 0) >= 0 ? "+" : ""}{overview?.growth_rate || 0}% vs yesterday

Mobile

{overview?.mobile_percentage || 0}%

of all visitors

{/* Charts Row */}
{/* Daily Visits Chart */}

Daily Visits

{visitsChart && ( )}
{/* Unique Visitors Chart */}

Unique Visitors

{uniqueChart && ( )}
{/* Country Stats */}

Visitors by Country

{countryStats.map((stat, index) => { const total = countryStats.reduce((sum, s) => sum + s.visitor_count, 0); const percentage = total > 0 ? (stat.visitor_count / total) * 100 : 0; return (
{getCountryFlag(stat.country_code)} {stat.country}
{stat.visitor_count.toLocaleString()} ({percentage.toFixed(1)}%)
); })} {countryStats.length === 0 && (

No country data available

)}
{/* Breakdowns Row */}
{/* Device Breakdown */}

Device Type

{deviceBreakdown?.items.map((item, index) => (
{getDeviceIcon(item.name)} {item.name} {item.count} ({item.percentage}%)
))} {(!deviceBreakdown?.items || deviceBreakdown.items.length === 0) && (

No data

)}
{/* Browser Breakdown */}

Browser

{browserBreakdown?.items.map((item, index) => (
{item.name} {item.count} ({item.percentage}%)
))} {(!browserBreakdown?.items || browserBreakdown.items.length === 0) && (

No data

)}
{/* OS Breakdown */}

Operating System

{osBreakdown?.items.map((item, index) => (
{item.name} {item.count} ({item.percentage}%)
))} {(!osBreakdown?.items || osBreakdown.items.length === 0) && (

No data

)}
{/* Top Pages & Referrers */}
{/* Top Pages */}

Top Pages

{topPages?.pages.map((page, index) => (
{index + 1}. {page.path} {page.count}
))} {(!topPages?.pages || topPages.pages.length === 0) && (

No page data available

)}
{/* Top Referrers */}

Top Referrers

{topReferrers?.referrers.map((ref, index) => (
{index + 1}. {ref.domain} {ref.count}
))} {(!topReferrers?.referrers || topReferrers.referrers.length === 0) && (

No referrer data available

)}
); }