"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 } 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"; // API response data structure 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 }[]; }; 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}`); console.log('=== ADMIN ANALYTICS DATA ==='); console.log('Date Range:', dateRange); console.log('Full Response:', JSON.stringify(data, null, 2)); console.log('Orders:', { total: data?.orders?.total, dailyOrders: data?.orders?.dailyOrders, dailyOrdersLength: data?.orders?.dailyOrders?.length, sample: data?.orders?.dailyOrders?.slice(0, 3) }); console.log('Revenue:', { total: data?.revenue?.total, dailyRevenue: data?.revenue?.dailyRevenue, dailyRevenueLength: data?.revenue?.dailyRevenue?.length, sample: data?.revenue?.dailyRevenue?.slice(0, 3) }); console.log('Vendors:', { total: data?.vendors?.total, dailyGrowth: data?.vendors?.dailyGrowth, dailyGrowthLength: data?.vendors?.dailyGrowth?.length, sample: data?.vendors?.dailyGrowth?.slice(0, 3) }); console.log('==========================='); 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); return { date: dateStr, formattedDate: date.toLocaleDateString('en-GB', { month: 'short', day: 'numeric' }), 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 }>) => { if (!orders || orders.length === 0) return []; // Create a map of revenue by date for quick lookup const revenueMap = new Map(); if (revenue && revenue.length > 0) { revenue.forEach(r => { revenueMap.set(r.date, r.amount || 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); return { date: dateStr, formattedDate: date.toLocaleDateString('en-GB', { month: 'short', day: 'numeric' }), orders: order.count || 0, revenue: revenueMap.get(dateStr) || 0 }; }); }; // 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)}

) : (

{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); }; 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)}
                
)}
{/* 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', 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)}
))}
)}
); }