"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"; interface AnalyticsData { 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 [errorMessage, setErrorMessage] = useState(null); const [refreshing, setRefreshing] = useState(false); const [showDebug, setShowDebug] = useState(false); const fetchAnalyticsData = async () => { try { setLoading(true); setErrorMessage(null); const data = await fetchClient(`/admin/analytics?range=${dateRange}`); setAnalyticsData(data); } 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]); const handleRefresh = () => { setRefreshing(true); fetchAnalyticsData(); }; 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(); 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 view) const calculateBestMonth = () => { if (dateRange !== 'ytd' || !analyticsData?.revenue?.dailyRevenue || analyticsData.revenue.dailyRevenue.length === 0) { return null; } // Group daily revenue by month const monthlyTotals = new Map(); 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

Overview of your marketplace performance

{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 (only show for YTD) */} {bestMonth && (
Best Month (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 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)}
))}
)}
); }