"use client"; import React, { useState, useEffect } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { AlertCircle, BarChart, RefreshCw, Users, ShoppingCart, TrendingUp, TrendingDown, DollarSign, Package, Trophy, } from "lucide-react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { fetchClient } from "@/lib/api-client"; import { BarChart as RechartsBarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, ComposedChart, } from "recharts"; import { formatGBP } from "@/utils/format"; import { PieChart, Pie, Cell, Legend } from "recharts"; interface GrowthData { launchDate: string; generatedAt: string; daily: Array<{ date: string; orders: number; revenue: number; customers: number; avgOrderValue: number; }>; monthly: Array<{ month: string; orders: number; revenue: number; customers: number; avgOrderValue: number; newVendors: number; newCustomers: number; }>; customers: { total: number; segments: { new: number; returning: number; loyal: number; vip: number; }; segmentDetails: { [key: string]: { count: number; totalRevenue: number; avgOrderCount: number; avgSpent: number; }; }; segmentPercentages: { new: number; returning: number; loyal: number; vip: number; }; }; cumulative: { orders: number; revenue: number; customers: number; vendors: number; products: number; avgOrderValue: number; }; } interface AnalyticsData { meta?: { year: number; currentYear: number; availableYears: number[]; range: string; isCurrentYear: boolean; }; vendors?: { total?: number; newToday?: number; newThisWeek?: number; activeToday?: number; active?: number; stores?: number; activeStores?: number; dailyGrowth?: { date: string; count: number }[]; topVendors?: Array<{ vendorId: string; vendorName: string; totalRevenue: number; orderCount: number; }>; }; orders?: { total?: number; totalToday?: number; totalThisWeek?: number; pending?: number; completed?: number; dailyOrders?: { date: string; count: number }[]; }; revenue?: { total?: number; today?: number; thisWeek?: number; dailyRevenue?: { date: string; amount: number; orders?: number; avgOrderValue?: number; }[]; }; engagement?: { totalChats?: number; activeChats?: number; totalMessages?: number; dailyMessages?: { date: string; count: number }[]; }; products?: { total?: number; recent?: number; }; stores?: { total?: number; active?: number; }; sessions?: { total?: number; active?: number; }; } export default function AdminAnalytics() { const [analyticsData, setAnalyticsData] = useState( null, ); const [loading, setLoading] = useState(true); const [dateRange, setDateRange] = useState("7days"); const [selectedYear, setSelectedYear] = useState( new Date().getFullYear(), ); const [availableYears, setAvailableYears] = useState([ new Date().getFullYear(), ]); const [errorMessage, setErrorMessage] = useState(null); const [refreshing, setRefreshing] = useState(false); const [showDebug, setShowDebug] = useState(false); const [growthData, setGrowthData] = useState(null); const [growthLoading, setGrowthLoading] = useState(false); const currentYear = new Date().getFullYear(); // Segment colors for pie chart const SEGMENT_COLORS = { new: "#3b82f6", // blue returning: "#10b981", // green loyal: "#f59e0b", // amber vip: "#8b5cf6", // purple }; const isViewingCurrentYear = selectedYear === currentYear; const fetchAnalyticsData = async () => { try { setLoading(true); setErrorMessage(null); // Build query params - include year if not current year const params = new URLSearchParams(); params.set("range", dateRange); if (selectedYear !== currentYear) { params.set("year", selectedYear.toString()); } const data = await fetchClient( `/admin/analytics?${params.toString()}`, ); setAnalyticsData(data); // Update available years from response if provided if (data.meta?.availableYears && data.meta.availableYears.length > 0) { setAvailableYears(data.meta.availableYears); } } catch (error) { console.error("Error fetching analytics data:", error); setErrorMessage("Failed to load analytics data. Please try again."); } finally { setLoading(false); setRefreshing(false); } }; useEffect(() => { fetchAnalyticsData(); }, [dateRange, selectedYear]); // Fetch growth data (cached, since launch) const fetchGrowthData = async (forceRefresh = false) => { try { setGrowthLoading(true); const url = forceRefresh ? "/admin/growth?refresh=true" : "/admin/growth"; const data = await fetchClient(url); setGrowthData(data); } catch (error) { console.error("Error fetching growth data:", error); } finally { setGrowthLoading(false); } }; // Fetch growth data on mount useEffect(() => { fetchGrowthData(); }, []); const handleRefresh = () => { setRefreshing(true); fetchAnalyticsData(); }; const handleGrowthRefresh = () => { fetchGrowthData(true); }; if (loading && !analyticsData) { return (
); } // Helper to transform data for recharts const transformChartData = ( data: Array<{ date: string;[key: string]: any }>, valueKey: string = "count", ) => { if (!data || data.length === 0) return []; return data.map((item) => { const dateStr = item.date; // Parse YYYY-MM-DD format const parts = dateStr.split("-"); const date = parts.length === 3 ? new Date( parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), ) : new Date(dateStr); // Format with day of week: "Mon, Nov 21" const dayOfWeek = date.toLocaleDateString("en-GB", { weekday: "short" }); const monthDay = date.toLocaleDateString("en-GB", { month: "short", day: "numeric", }); return { date: dateStr, formattedDate: `${dayOfWeek}, ${monthDay}`, value: Number(item[valueKey]) || 0, [valueKey]: Number(item[valueKey]) || 0, }; }); }; // Helper to combine orders and revenue data for dual-axis chart const combineOrdersAndRevenue = ( orders: Array<{ date: string; count: number }>, revenue: Array<{ date: string; amount: number; orders?: number; avgOrderValue?: number; }>, ) => { if (!orders || orders.length === 0) return []; // Create a map of revenue and AOV by date for quick lookup const revenueMap = new Map< string, { amount: number; avgOrderValue: number } >(); if (revenue && revenue.length > 0) { revenue.forEach((r) => { revenueMap.set(r.date, { amount: r.amount || 0, avgOrderValue: r.avgOrderValue || 0, }); }); } return orders.map((order) => { const dateStr = order.date; const parts = dateStr.split("-"); const date = parts.length === 3 ? new Date( parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), ) : new Date(dateStr); // Format with day of week: "Mon, Nov 21" const dayOfWeek = date.toLocaleDateString("en-GB", { weekday: "short" }); const monthDay = date.toLocaleDateString("en-GB", { month: "short", day: "numeric", }); const revenueData = revenueMap.get(dateStr) || { amount: 0, avgOrderValue: 0, }; return { date: dateStr, formattedDate: `${dayOfWeek}, ${monthDay}`, orders: order.count || 0, revenue: revenueData.amount, avgOrderValue: revenueData.avgOrderValue, }; }); }; // Custom tooltip for charts const CustomTooltip = ({ active, payload, label }: any) => { if (active && payload && payload.length) { const data = payload[0].payload; const dataKey = payload[0].dataKey; const isDualAxis = data.orders !== undefined && data.revenue !== undefined; // Determine if this is a currency amount or a count // transformChartData creates both 'value' and the original key (count/amount) // So we check the original key to determine the type const isAmount = dataKey === "amount" || dataKey === "revenue" || (dataKey === "value" && data.amount !== undefined && data.count === undefined); return (

{data.formattedDate || label}

{isDualAxis ? (

Orders: {data.orders}

Revenue:{" "} {formatGBP(data.revenue)}

{data.avgOrderValue !== undefined && data.avgOrderValue > 0 && (

Avg Order Value:{" "} {formatGBP(data.avgOrderValue)}

)}
) : (

{isAmount ? formatGBP(data.value || data.amount || 0) : `${data.value || data.count || 0}`}

)}
); } return null; }; // Trend indicator component for metric cards const TrendIndicator = ({ current, previous, }: { current: number; previous: number; }) => { if (!current || !previous) return null; const percentChange = ((current - previous) / previous) * 100; if (Math.abs(percentChange) < 0.1) return null; return (
= 0 ? "text-green-500" : "text-red-500"}`} > {percentChange >= 0 ? ( ) : ( )} {Math.abs(percentChange).toFixed(1)}%
); }; // Format currency const formatCurrency = (value: number) => { return new Intl.NumberFormat("en-GB", { style: "currency", currency: "GBP", maximumFractionDigits: 0, }).format(value); }; // Calculate best month from daily data (for YTD, full year, or previous years) const calculateBestMonth = () => { // Show best month for YTD, full year view, or when viewing previous years const showBestMonth = dateRange === "ytd" || dateRange === "year" || !isViewingCurrentYear; if ( !showBestMonth || !analyticsData?.revenue?.dailyRevenue || analyticsData.revenue.dailyRevenue.length === 0 ) { return null; } // Group daily revenue by month const monthlyTotals = new Map< string, { revenue: number; orders: number } >(); analyticsData.revenue.dailyRevenue.forEach((day) => { const date = new Date(day.date); const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`; const current = monthlyTotals.get(monthKey) || { revenue: 0, orders: 0 }; monthlyTotals.set(monthKey, { revenue: current.revenue + (day.amount || 0), orders: current.orders, }); }); // Also group orders by month if (analyticsData.orders?.dailyOrders) { analyticsData.orders.dailyOrders.forEach((day) => { const date = new Date(day.date); const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`; const current = monthlyTotals.get(monthKey) || { revenue: 0, orders: 0, }; monthlyTotals.set(monthKey, { revenue: current.revenue, orders: current.orders + (day.count || 0), }); }); } // Find the month with highest revenue let bestMonth: { month: string; revenue: number; orders: number } | null = null; monthlyTotals.forEach((totals, monthKey) => { if (!bestMonth || totals.revenue > bestMonth.revenue) { const [year, month] = monthKey.split("-"); const monthName = new Date( parseInt(year), parseInt(month) - 1, 1, ).toLocaleDateString("en-GB", { month: "long", year: "numeric" }); bestMonth = { month: monthName, revenue: totals.revenue, orders: totals.orders, }; } }); return bestMonth; }; const bestMonth = calculateBestMonth(); return (
{errorMessage && ( Error {errorMessage} )}

Dashboard Analytics {!isViewingCurrentYear && ( ({selectedYear}) )}

{isViewingCurrentYear ? "Overview of your marketplace performance" : `Historical data for ${selectedYear}`}

{/* Year selector */} {/* Date range selector - only show options for current year */}
{showDebug && analyticsData && ( Debug: Raw Data Date Range: {dateRange}
Orders:
Total: {analyticsData?.orders?.total || "N/A"}
Today: {analyticsData?.orders?.totalToday || "N/A"}
Daily Orders Array Length:{" "} {analyticsData?.orders?.dailyOrders?.length || 0}
First 3 Daily Orders:
                    {JSON.stringify(
                      analyticsData?.orders?.dailyOrders?.slice(0, 3),
                      null,
                      2,
                    )}
                  
Revenue:
Total: {analyticsData?.revenue?.total || "N/A"}
Today: {analyticsData?.revenue?.today || "N/A"}
Daily Revenue Array Length:{" "} {analyticsData?.revenue?.dailyRevenue?.length || 0}
First 3 Daily Revenue:
                    {JSON.stringify(
                      analyticsData?.revenue?.dailyRevenue?.slice(0, 3),
                      null,
                      2,
                    )}
                  
Vendors:
Total: {analyticsData?.vendors?.total || "N/A"}
Daily Growth Array Length:{" "} {analyticsData?.vendors?.dailyGrowth?.length || 0}
First 3 Daily Growth:
                    {JSON.stringify(
                      analyticsData?.vendors?.dailyGrowth?.slice(0, 3),
                      null,
                      2,
                    )}
                  
Full JSON Response
                  {JSON.stringify(analyticsData, null, 2)}
                
)} {/* Best Month Card (show for YTD, full year, or previous years) */} {bestMonth && (
Best Month{" "} {!isViewingCurrentYear ? `of ${selectedYear}` : dateRange === "year" ? "(Full Year)" : "(YTD)"}
{bestMonth.month}
Revenue
{formatCurrency(bestMonth.revenue)}
{bestMonth.orders.toLocaleString()} orders
)}
{/* Orders Card */}
Total Orders
{analyticsData?.orders?.total?.toLocaleString() || "0"}
Today: {analyticsData?.orders?.totalToday || 0}
{loading || refreshing ? (
) : analyticsData?.orders?.dailyOrders && analyticsData.orders.dailyOrders.length > 0 ? (
} />
) : (
No chart data available
)}
{/* Revenue Card */}
Total Revenue
{formatCurrency(analyticsData?.revenue?.total || 0)}
Today: {formatCurrency(analyticsData?.revenue?.today || 0)}
{loading || refreshing ? (
) : analyticsData?.revenue?.dailyRevenue && analyticsData.revenue.dailyRevenue.length > 0 ? (
} />
) : (
No chart data available
)}
{/* Vendors Card */}
Vendors
{analyticsData?.vendors?.total?.toLocaleString() || "0"}
Active: {analyticsData?.vendors?.active || 0} Stores: {analyticsData?.vendors?.activeStores || 0}
New Today: {analyticsData?.vendors?.newToday || 0}
{loading || refreshing ? (
) : analyticsData?.vendors?.dailyGrowth && analyticsData.vendors.dailyGrowth.length > 0 ? (
} />
) : (
No chart data available
)}
{/* Products Card */}
Products
{analyticsData?.products?.total?.toLocaleString() || "0"}
New This Week: {analyticsData?.products?.recent || 0}
Orders Vendors Growth Since Launch Order Trends Daily order volume and revenue processed over the selected time period {loading || refreshing ? (

Loading chart data...

) : analyticsData?.orders?.dailyOrders && analyticsData.orders.dailyOrders.length > 0 ? (
`£${(value / 1000).toFixed(0)}k` } label={{ value: "Revenue / AOV", angle: 90, position: "insideRight", }} /> } />
) : (
No order data available for the selected time period
)} {/* Calculate totals for the selected period */} {analyticsData?.orders?.dailyOrders && analyticsData?.revenue?.dailyRevenue && (
Total Revenue
{formatCurrency( analyticsData.revenue.dailyRevenue.reduce( (sum, day) => sum + (day.amount || 0), 0, ), )}
Total Orders
{analyticsData.orders.dailyOrders .reduce((sum, day) => sum + (day.count || 0), 0) .toLocaleString()}
)}
Vendor Growth New vendor registrations over time {loading || refreshing ? (

Loading chart data...

) : analyticsData?.vendors?.dailyGrowth && analyticsData.vendors.dailyGrowth.length > 0 ? (
} />
) : (
No vendor data available for the selected time period
)}
Total Vendors
{analyticsData?.vendors?.total?.toLocaleString() || "0"}
Active Vendors
{analyticsData?.vendors?.active?.toLocaleString() || "0"}
Active Stores
{analyticsData?.vendors?.activeStores?.toLocaleString() || "0"}
New This Week
{analyticsData?.vendors?.newThisWeek?.toLocaleString() || "0"}
{/* Top Vendors by Revenue */} {analyticsData?.vendors?.topVendors && analyticsData.vendors.topVendors.length > 0 && (

Top Vendors by Revenue

{analyticsData.vendors.topVendors.map((vendor, index) => (
{index + 1}
{vendor.vendorName}
{vendor.orderCount} orders
{formatCurrency(vendor.totalRevenue)}
))}
)}
{/* Growth Header */}

Platform Growth Since Launch

{growthData?.launchDate ? `Tracking since ${new Date(growthData.launchDate).toLocaleDateString("en-GB", { month: "long", year: "numeric" })}` : "February 2025"} {growthData?.generatedAt && ( • Last updated:{" "} {new Date(growthData.generatedAt).toLocaleTimeString( "en-GB", { hour: "2-digit", minute: "2-digit" }, )} )}

{/* Cumulative Stats Cards */} {growthData?.cumulative && (
Total Orders
{growthData.cumulative.orders.toLocaleString()}
Total Revenue
{formatCurrency(growthData.cumulative.revenue)}
Customers
{growthData.cumulative.customers.toLocaleString()}
Vendors
{growthData.cumulative.vendors.toLocaleString()}
Products
{growthData.cumulative.products.toLocaleString()}
Avg Order Value
{formatCurrency(growthData.cumulative.avgOrderValue)}
)} {/* Monthly Revenue & Orders Chart */} Monthly Revenue & Orders Platform performance by month since launch {growthLoading ? (
) : growthData?.monthly && growthData.monthly.length > 0 ? (
({ ...m, formattedMonth: new Date( m.month + "-01", ).toLocaleDateString("en-GB", { month: "short", year: "2-digit", }), }))} margin={{ top: 5, right: 30, left: 20, bottom: 5 }} > `£${(value / 1000).toFixed(0)}k` } /> { if (active && payload?.length) { const data = payload[0].payload; return (

{data.month}

Orders: {data.orders.toLocaleString()}

Revenue: {formatCurrency(data.revenue)}

Customers: {data.customers.toLocaleString()}

New Vendors: {data.newVendors}

); } return null; }} />
) : (
No growth data available
)}
{/* Customer Segments Pie Chart */} Customer Segments Breakdown by purchase behavior {growthLoading ? (
) : growthData?.customers ? (
`${name}: ${(percent * 100).toFixed(0)}%` } labelLine={false} > {[ { color: SEGMENT_COLORS.new }, { color: SEGMENT_COLORS.returning }, { color: SEGMENT_COLORS.loyal }, { color: SEGMENT_COLORS.vip }, ].map((entry, index) => ( ))} { if (active && payload?.length) { const data = payload[0].payload; const details = growthData.customers.segmentDetails[ data.name.split(" ")[0].toLowerCase() ]; return (

{data.name}

Count: {data.value.toLocaleString()}

{details && ( <>

Revenue:{" "} {formatCurrency(details.totalRevenue)}

Avg Orders: {details.avgOrderCount}

)}
); } return null; }} />
) : (
No customer data available
)} {/* Segment Stats */} {growthData?.customers && (
{growthData.customers.segments.new}
New
{growthData.customers.segments.returning}
Returning
{growthData.customers.segments.loyal}
Loyal
{growthData.customers.segments.vip}
VIP
)}
{/* Monthly Growth Table */} {growthData?.monthly && growthData.monthly.length > 0 && ( Monthly Breakdown Detailed metrics by month
{growthData.monthly.map((month) => ( ))}
Month Orders Revenue Customers Avg Order New Vendors New Customers
{new Date(month.month + "-01").toLocaleDateString( "en-GB", { month: "long", year: "numeric" }, )} {month.orders.toLocaleString()} {formatCurrency(month.revenue)} {month.customers.toLocaleString()} {formatCurrency(month.avgOrderValue)} {month.newVendors} {month.newCustomers}
)}
); }