"use client"; import { useState, useEffect } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useToast } from "@/hooks/use-toast"; import { TrendingUp, TrendingDown, Users, ShoppingCart, DollarSign, Package, ArrowUpRight, ArrowDownRight, Minus, } from "lucide-react"; import { getGrowthAnalyticsWithStore, type GrowthAnalytics, } from "@/lib/services/analytics-service"; import { formatGBP } from "@/utils/format"; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area, BarChart, Bar, } from "recharts"; import { ChartSkeleton } from "./SkeletonLoaders"; interface GrowthAnalyticsChartProps { hideNumbers?: boolean; } export default function GrowthAnalyticsChart({ hideNumbers = false, }: GrowthAnalyticsChartProps) { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [period, setPeriod] = useState("30"); const { toast } = useToast(); useEffect(() => { const fetchData = async () => { try { setIsLoading(true); setError(null); const response = await getGrowthAnalyticsWithStore(period); setData(response); } catch (err) { console.error("Error fetching growth data:", err); setError("Failed to load growth data"); toast({ title: "Error", description: "Failed to load growth analytics data.", variant: "destructive", }); } finally { setIsLoading(false); } }; fetchData(); }, [period, toast]); const maskValue = (value: string): string => { if (!hideNumbers) return value; if (value.includes("£")) return "£***"; if (value.match(/^\d/) || value.match(/^-?\d/)) return "***"; return value; }; const formatGrowthRate = (rate: number): string => { const prefix = rate > 0 ? "+" : ""; return `${prefix}${rate.toFixed(1)}%`; }; const getGrowthIcon = (rate: number) => { if (rate > 0) return ; if (rate < 0) return ; return ; }; const getGrowthColor = (rate: number): string => { if (rate > 0) return "text-green-600"; if (rate < 0) return "text-red-600"; return "text-muted-foreground"; }; const CustomTooltip = ({ active, payload, label }: any) => { if (active && payload && payload.length) { const item = payload[0].payload; return (

{item.date}

Revenue:{" "} {hideNumbers ? "£***" : formatGBP(item.revenue)}

Orders:{" "} {hideNumbers ? "***" : item.orders}

Customers:{" "} {hideNumbers ? "***" : item.uniqueCustomers}

Avg Order:{" "} {hideNumbers ? "£***" : formatGBP(item.avgOrderValue)}

); } return null; }; if (isLoading) { return ( ); } if (error || !data) { return ( Growth Analytics

{error || "No growth data available"}

); } const { summary, customerInsights, timeSeries, topGrowingProducts } = data; return (
{/* Period Selector */}
Growth Analytics Compare performance against the previous period
{/* Growth Rate Cards */}
{/* Revenue Growth */}
Revenue
{getGrowthIcon(summary.growthRates.revenue)} {hideNumbers ? "***" : formatGrowthRate(summary.growthRates.revenue)}
{maskValue(formatGBP(summary.currentPeriod.revenue))}
vs {maskValue(formatGBP(summary.previousPeriod.revenue))} previous
{/* Orders Growth */}
Orders
{getGrowthIcon(summary.growthRates.orders)} {hideNumbers ? "***" : formatGrowthRate(summary.growthRates.orders)}
{maskValue(summary.currentPeriod.orders.toString())}
vs {maskValue(summary.previousPeriod.orders.toString())} previous
{/* AOV Growth */}
Avg Order
{getGrowthIcon(summary.growthRates.avgOrderValue)} {hideNumbers ? "***" : formatGrowthRate(summary.growthRates.avgOrderValue)}
{maskValue(formatGBP(summary.currentPeriod.avgOrderValue))}
vs {maskValue(formatGBP(summary.previousPeriod.avgOrderValue))} previous
{/* Customers Growth */}
Customers
{getGrowthIcon(summary.growthRates.customers)} {hideNumbers ? "***" : formatGrowthRate(summary.growthRates.customers)}
{maskValue(summary.currentPeriod.customers.toString())}
vs {maskValue(summary.previousPeriod.customers.toString())} previous
{/* Revenue Trend Chart */} Revenue & Orders Over Time {data.period.granularity === "daily" ? "Daily" : data.period.granularity === "weekly" ? "Weekly" : "Monthly"}{" "} breakdown from {data.period.start} to {data.period.end} {timeSeries.length === 0 ? (

No data available for this period

) : (
hideNumbers ? "***" : `£${(value / 1000).toFixed(0)}k` } /> (hideNumbers ? "***" : value)} /> } />
)}
{/* Customer Insights */} Customer Insights New vs returning customers and engagement metrics
{maskValue(customerInsights.newCustomers.toString())}
New Customers
{maskValue(customerInsights.returningCustomers.toString())}
Returning
{maskValue(customerInsights.totalCustomers.toString())}
Total
{hideNumbers ? "***%" : `${customerInsights.newCustomerRate}%`}
New Rate
{maskValue(customerInsights.avgOrdersPerCustomer.toString())}
Avg Orders
{maskValue(formatGBP(customerInsights.avgSpentPerCustomer))}
Avg Spent
{/* Top Growing Products */} {topGrowingProducts.length > 0 && ( Top Growing Products Products with the highest revenue growth compared to previous period
{topGrowingProducts.slice(0, 5).map((product, index) => (
{index + 1}
{product.productName}
{maskValue(formatGBP(product.currentPeriodRevenue))} revenue {" · "} {maskValue(product.currentPeriodQuantity.toString())} sold
= 0 ? "default" : "destructive"} className="flex items-center gap-1" > {product.revenueGrowth >= 0 ? ( ) : ( )} {hideNumbers ? "***" : formatGrowthRate(product.revenueGrowth)}
))}
)}
); }