"use client"; import { Fragment, useEffect, useState } from "react"; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, } from "recharts"; import { Download, ScanLine, Users, Globe, ShieldCheck } from "lucide-react"; import ScanTrendChart from "@/components/ScanTrendChart"; import { api, apiBase } from "@/lib/api"; import GeographicOverview from "@/components/GeographicOverview"; const PRESETS = [ { label: "7d", days: 7 }, { label: "30d", days: 30 }, { label: "90d", days: 90 }, ]; const DONUT = ["#2563eb", "#10b981", "#f59e0b", "#94a3b8"]; const DOW = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; export default function Analytics() { const [days, setDays] = useState(30); const [s, setS] = useState(null); const [trend, setTrend] = useState([]); const [topP, setTopP] = useState([]); const [cities, setCities] = useState([]); const [devices, setDevices] = useState([]); const [heat, setHeat] = useState([]); const [recent, setRecent] = useState([]); const [geo, setGeo] = useState([]); const totalDevices = devices.reduce((sum, d) => sum + d.scans, 0); const [brands, setBrands] = useState([]); const [brandId, setBrandId] = useState(""); const [recentTotal, setRecentTotal] = useState(0); const DONUT = [ "#22c55e", // Android - Green "#2563eb", // iOS - Blue "#7c3aed", // Desktop - Purple "#f59e0b", // Other - Orange ]; const [fromDate, setFromDate] = useState(""); const [toDate, setToDate] = useState(""); const params = new URLSearchParams(); if (fromDate && toDate) { params.append("from_date", fromDate); params.append("to_date", toDate); } else { params.append("days", String(days)); } if (brandId) { params.append("brand_id", brandId); } const query = params.toString(); const [recentPage, setRecentPage] = useState(1); const [recentTotalPages, setRecentTotalPages] = useState(1); const recentPageSize = 20; const clearFilters = () => { setBrandId(""); setFromDate(""); setToDate(""); setDays(30); setRecentPage(1); // default preset }; const [showAllProducts, setShowAllProducts] = useState(false); useEffect(() => { api("/analytics/brands").then(setBrands); }, []); useEffect(() => { api(`/analytics/summary?${query}`).then(setS).catch(() => {}); api(`/analytics/trend?${query}`).then(setTrend).catch(() => {}); api(`/analytics/top-products?${query}`).then(setTopP).catch(() => {}); api(`/analytics/top-cities?${query}`).then(setCities).catch(() => {}); api(`/analytics/devices?${query}`).then(setDevices).catch(() => {}); api(`/analytics/geography?${query}`).then(setGeo).catch(() => {}); api(`/analytics/time-distribution?${query}`).then(setHeat).catch(() => {}); api( `/analytics/recent-scans?${query}&page=${recentPage}&page_size=${recentPageSize}` ) .then((res) => { setRecent(res.items); setRecentTotalPages(res.pages); setRecentTotal(res.total); }) .catch(() => {}); }, [days, fromDate, toDate,brandId, recentPage]); const maxP = Math.max(1, ...topP.map((p) => p.scans)); const maxHeat = Math.max(1, ...heat.map((h) => h.count)); const startPage = Math.max(1, recentPage - 2); const endPage = Math.min(recentTotalPages, startPage + 4); const pages = []; for (let i = startPage; i <= endPage; i++) { pages.push(i); } const downloadCsv = async () => { const token = localStorage.getItem("vp_token"); // or whatever key you use const res = await fetch(`${apiBase}/analytics/export?${query}`, { headers: { Authorization: `Bearer ${token}`, }, }); if (!res.ok) { alert("Failed to download CSV"); return; } const blob = await res.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "scans.csv"; document.body.appendChild(a); a.click(); a.remove(); window.URL.revokeObjectURL(url); }; return (

Analytics & Insights

{PRESETS.map((p) => ( ))}
setFromDate(e.target.value)} className="border rounded-lg px-3 py-2 text-sm" /> to setToDate(e.target.value)} className="border rounded-lg px-3 py-2 text-sm" />
{/* KPIs */}
} tint="bg-blue-50 text-blue-600" /> } tint="bg-violet-50 text-violet-600" /> } tint="bg-orange-50 text-orange-500" /> } tint="bg-emerald-50 text-emerald-600" /> } tint="bg-blue-50 text-blue-600" /> } tint="bg-amber-50 text-amber-500" />

Scan Trend

Device Types

{/* Donut Chart */}
{devices.map((_, i) => ( ))} [ `${value} scans`, "Scans", ]} />
{/* Legend */}
{devices.map((d, i) => { const percent = totalDevices === 0 ? 0 : ((d.scans / totalDevices) * 100).toFixed(1); return (
{d.device}
{percent}% ({d.scans.toLocaleString()})
); })}

Scan Time Distribution

{Array.from({ length: 24 }).map((_, h) => (
{h % 3 === 0 ? h : ""}
))} {DOW.map((day, d) => (
{day}
{Array.from({ length: 24 }).map((_, h) => { const cell = heat.find((x) => x.dow === d && x.hour === h); const v = cell?.count ?? 0; const op = v === 0 ? 0.06 : 0.2 + (v / maxHeat) * 0.8; return
; })} ))}

Top Products

{topP.length > 5 && ( )}
{(showAllProducts ? topP : topP.slice(0, 5)).map((p) => (
{p.name}
{p.scans}
))} {!showAllProducts && topP.length > 5 && ( )} {topP.length === 0 && (

No data.

)}

Top Cities

    {cities.map((c, i) => (
  1. {i + 1}. {c.city} {c.scans}
  2. ))} {cities.length === 0 &&
  3. No data.
  4. }

Geographic Overview

{/* Heatmap */} {/* Recent scans */}

Recent Scans

{recent.map((r, i) => ( ))} {recent.length === 0 && }
Time Code City Country Device IP Address
{new Date(r.at).toLocaleString()} {r.code} {r.city ?? "—"} {r.country ?? "—"} {r.device ?? "—"} {r.ip ?? "—"}
No scans yet.
Showing{" "} {recent.length === 0 ? 0 : (recentPage - 1) * recentPageSize + 1}{" "} to{" "} {Math.min(recentPage * recentPageSize, recentTotal)}{" "} of {recentTotal}
{pages.map((p) => ( ))}
); } function Kpi({ label, value, icon, tint }: any) { return (
{icon}
{value}
{label}
); }