Refactor admin analytics stat cards to reusable component

Extracted repeated stat card logic in AdminAnalytics to a new AdminStatCard component and moved the trend indicator to its own file. Updated AdminAnalytics to use AdminStatCard for orders, revenue, vendors, and products, improving code maintainability and consistency. Also updated chart and loading skeleton handling for better UX.
This commit is contained in:
g
2026-01-13 06:01:39 +00:00
parent a07ca55a1e
commit 66964a3218
4 changed files with 568 additions and 486 deletions

View File

@@ -47,6 +47,14 @@ import {
} from "recharts";
import { formatGBP, formatNumber } from "@/lib/utils/format";
import { PieChart, Pie, Cell, Legend } from "recharts";
import { AdminStatCard } from "./AdminStatCard";
import { TrendIndicator } from "./TrendIndicator";
import {
ChartSkeleton,
CustomerInsightsSkeleton,
MetricsCardSkeleton,
TableSkeleton
} from "../analytics/SkeletonLoaders";
interface GrowthData {
launchDate: string;
@@ -417,33 +425,7 @@ export default function AdminAnalytics() {
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 (
<div
className={`flex items-center text-xs font-medium ${percentChange >= 0 ? "text-green-500" : "text-red-500"}`}
>
{percentChange >= 0 ? (
<TrendingUp className="h-3 w-3 mr-1" />
) : (
<TrendingDown className="h-3 w-3 mr-1" />
)}
{Math.abs(percentChange).toFixed(1)}%
</div>
);
};
// Format currency
const formatCurrency = (value: number) => {
@@ -707,234 +689,98 @@ export default function AdminAnalytics() {
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Orders Card */}
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-300">
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<CardTitle className="text-sm font-medium text-muted-foreground">
Total Orders
</CardTitle>
<div className="p-2 bg-blue-500/10 rounded-md">
<ShoppingCart className="h-4 w-4 text-blue-500" />
</div>
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.orders?.total)}
</div>
<div className="flex items-center text-xs text-muted-foreground mt-1">
<span className="bg-muted/50 px-1.5 py-0.5 rounded">Today: {analyticsData?.orders?.totalToday || 0}</span>
<div className="ml-auto">
<TrendIndicator
current={analyticsData?.orders?.totalToday || 0}
previous={(analyticsData?.orders?.total || 0) / 30}
/>
</div>
</div>
{loading || refreshing ? (
<div className="mt-4 h-14 flex items-center justify-center">
<div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div>
</div>
) : analyticsData?.orders?.dailyOrders &&
analyticsData.orders.dailyOrders.length > 0 ? (
<div className="mt-4 h-14 -mx-2">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={transformChartData(
analyticsData.orders.dailyOrders,
"count",
)}
margin={{ top: 5, right: 0, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorOrdersAdmin" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
</linearGradient>
</defs>
<Area
type="monotone"
dataKey="value"
stroke="#3b82f6"
fillOpacity={1}
fill="url(#colorOrdersAdmin)"
strokeWidth={2}
activeDot={{ r: 4, strokeWidth: 0, fill: "#3b82f6" }}
/>
<Tooltip content={<CustomTooltip />} cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} />
</AreaChart>
</ResponsiveContainer>
</div>
) : (
<div className="mt-4 h-14 flex items-center justify-center text-xs text-muted-foreground bg-muted/20 rounded">
No chart data
</div>
)}
</CardContent>
</Card>
<AdminStatCard
title="Total Orders"
icon={ShoppingCart}
iconColorClass="text-blue-500"
iconBgClass="bg-blue-500/10"
value={formatNumber(analyticsData?.orders?.total)}
subtext={
<span className="bg-muted/50 px-1.5 py-0.5 rounded">
Today: {analyticsData?.orders?.totalToday || 0}
</span>
}
trend={{
current: analyticsData?.orders?.totalToday || 0,
previous: (analyticsData?.orders?.total || 0) / 30, // Approx simple moving average
}}
loading={loading || refreshing}
chartData={transformChartData(
analyticsData?.orders?.dailyOrders || [],
"count"
)}
chartColor="#3b82f6"
chartGradientId="colorOrdersStat"
/>
{/* Revenue Card */}
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-300">
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<CardTitle className="text-sm font-medium text-muted-foreground">
Total Revenue
</CardTitle>
<div className="p-2 bg-green-500/10 rounded-md">
<DollarSign className="h-4 w-4 text-green-500" />
</div>
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{formatCurrency(analyticsData?.revenue?.total || 0)}
</div>
<div className="flex items-center text-xs text-muted-foreground mt-1">
<span className="bg-muted/50 px-1.5 py-0.5 rounded">Today: {formatCurrency(analyticsData?.revenue?.today || 0)}</span>
<div className="ml-auto">
<TrendIndicator
current={analyticsData?.revenue?.today || 0}
previous={(analyticsData?.revenue?.total || 0) / 30}
/>
</div>
</div>
{loading || refreshing ? (
<div className="mt-4 h-14 flex items-center justify-center">
<div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div>
</div>
) : analyticsData?.revenue?.dailyRevenue &&
analyticsData.revenue.dailyRevenue.length > 0 ? (
<div className="mt-4 h-14 -mx-2">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={transformChartData(
analyticsData.revenue.dailyRevenue,
"amount",
)}
margin={{ top: 5, right: 0, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorRevenueAdmin" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#10b981" stopOpacity={0.3} />
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
</linearGradient>
</defs>
<Area
type="monotone"
dataKey="value"
stroke="#10b981"
fillOpacity={1}
fill="url(#colorRevenueAdmin)"
strokeWidth={2}
activeDot={{ r: 4, strokeWidth: 0, fill: "#10b981" }}
/>
<Tooltip content={<CustomTooltip />} cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} />
</AreaChart>
</ResponsiveContainer>
</div>
) : (
<div className="mt-4 h-14 flex items-center justify-center text-xs text-muted-foreground bg-muted/20 rounded">
No chart data
</div>
)}
</CardContent>
</Card>
<AdminStatCard
title="Total Revenue"
icon={DollarSign}
iconColorClass="text-green-500"
iconBgClass="bg-green-500/10"
value={formatCurrency(analyticsData?.revenue?.total || 0)}
subtext={
<span className="bg-muted/50 px-1.5 py-0.5 rounded">
Today: {formatCurrency(analyticsData?.revenue?.today || 0)}
</span>
}
trend={{
current: analyticsData?.revenue?.today || 0,
previous: (analyticsData?.revenue?.total || 0) / 30,
}}
loading={loading || refreshing}
chartData={transformChartData(
analyticsData?.revenue?.dailyRevenue || [],
"amount"
)}
chartColor="#10b981"
chartGradientId="colorRevenueStat"
tooltipPrefix="£"
/>
{/* Vendors Card */}
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-300">
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<CardTitle className="text-sm font-medium text-muted-foreground">Vendors</CardTitle>
<div className="p-2 bg-purple-500/10 rounded-md">
<Users className="h-4 w-4 text-purple-500" />
</div>
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.vendors?.total?.toLocaleString() || "0"}
</div>
<div className="flex items-center text-xs text-muted-foreground mt-1 gap-2">
<span className="bg-muted/50 px-1.5 py-0.5 rounded">Active: {analyticsData?.vendors?.active || 0}</span>
<span className="bg-muted/50 px-1.5 py-0.5 rounded">Stores: {analyticsData?.vendors?.activeStores || 0}</span>
</div>
<div className="flex items-center text-xs text-muted-foreground mt-2">
<span>New: {analyticsData?.vendors?.newToday || 0}</span>
<div className="ml-auto">
<TrendIndicator
current={analyticsData?.vendors?.newToday || 0}
previous={(analyticsData?.vendors?.newThisWeek || 0) / 7}
/>
</div>
</div>
{loading || refreshing ? (
<div className="mt-2 h-12 flex items-center justify-center">
<div className="animate-spin h-4 w-4 border-2 border-primary border-t-transparent rounded-full"></div>
</div>
) : analyticsData?.vendors?.dailyGrowth &&
analyticsData.vendors.dailyGrowth.length > 0 ? (
<div className="mt-2 h-12 -mx-2">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={transformChartData(
analyticsData.vendors.dailyGrowth,
"count",
)}
margin={{ top: 5, right: 0, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="colorVendorsAdmin" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#8b5cf6" stopOpacity={0.3} />
<stop offset="95%" stopColor="#8b5cf6" stopOpacity={0} />
</linearGradient>
</defs>
<Area
type="monotone"
dataKey="value"
stroke="#8b5cf6"
fillOpacity={1}
fill="url(#colorVendorsAdmin)"
strokeWidth={2}
activeDot={{ r: 4, strokeWidth: 0, fill: "#8b5cf6" }}
/>
<Tooltip content={<CustomTooltip />} cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} />
</AreaChart>
</ResponsiveContainer>
</div>
) : (
<div className="mt-2 h-12 flex items-center justify-center text-xs text-muted-foreground bg-muted/20 rounded">
No chart data
</div>
)}
</CardContent>
</Card>
<AdminStatCard
title="Vendors"
icon={Users}
iconColorClass="text-purple-500"
iconBgClass="bg-purple-500/10"
value={analyticsData?.vendors?.total?.toLocaleString() || "0"}
subtext={<span>New: {analyticsData?.vendors?.newToday || 0}</span>}
trend={{
current: analyticsData?.vendors?.newToday || 0,
previous: (analyticsData?.vendors?.newThisWeek || 0) / 7,
}}
loading={loading || refreshing}
chartData={transformChartData(
analyticsData?.vendors?.dailyGrowth || [],
"count"
)}
chartColor="#8b5cf6"
chartGradientId="colorVendorsStat"
>
<div className="flex items-center text-xs text-muted-foreground gap-2">
<span className="bg-muted/50 px-1.5 py-0.5 rounded">
Active: {analyticsData?.vendors?.active || 0}
</span>
<span className="bg-muted/50 px-1.5 py-0.5 rounded">
Stores: {analyticsData?.vendors?.activeStores || 0}
</span>
</div>
</AdminStatCard>
{/* Products Card */}
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-300">
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<CardTitle className="text-sm font-medium text-muted-foreground">Products</CardTitle>
<div className="p-2 bg-amber-500/10 rounded-md">
<Package className="h-4 w-4 text-amber-500" />
</div>
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.products?.total)}
</div>
<div className="flex items-center text-xs text-muted-foreground mt-1">
<span className="bg-muted/50 px-1.5 py-0.5 rounded">New This Week: {analyticsData?.products?.recent || 0}</span>
</div>
{/* Visual spacer since no chart here */}
<div className="mt-4 h-14 w-full bg-gradient-to-r from-amber-500/5 to-transparent rounded-md flex items-center justify-center">
<span className="text-xs text-muted-foreground/50 italic">Inventory Overview</span>
</div>
</CardContent>
</Card>
<AdminStatCard
title="Products"
icon={Package}
iconColorClass="text-amber-500"
iconBgClass="bg-amber-500/10"
value={formatNumber(analyticsData?.products?.total)}
loading={loading || refreshing}
chartColor="#f59e0b"
chartGradientId="colorProductsStat"
hideChart={true}
/>
</div>
<Tabs defaultValue="orders" className="mt-8">
@@ -960,251 +806,290 @@ export default function AdminAnalytics() {
</TabsList>
<TabsContent value="orders" className="mt-4 animate-in fade-in slide-in-from-bottom-2 duration-500">
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden">
<CardHeader>
<CardTitle className="bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-transparent w-fit">Order Trends</CardTitle>
<CardDescription>
Daily order volume and revenue processed over the selected time period
</CardDescription>
</CardHeader>
<CardContent>
{loading || refreshing ? (
<div className="flex items-center justify-center h-80">
<div className="flex flex-col items-center gap-2">
<div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full"></div>
<p className="text-sm text-muted-foreground">
Loading chart data...
</p>
</div>
</div>
) : analyticsData?.orders?.dailyOrders &&
analyticsData.orders.dailyOrders.length > 0 ? (
<div className="h-80">
<ResponsiveContainer width="100%" height="100%">
<ComposedChart
data={combineOrdersAndRevenue(
analyticsData.orders.dailyOrders,
analyticsData.revenue?.dailyRevenue || [],
)}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="formattedDate"
tick={{ fontSize: 12 }}
angle={-45}
textAnchor="end"
height={60}
/>
<YAxis
yAxisId="left"
tick={{ fontSize: 12 }}
label={{
value: "Orders",
angle: -90,
position: "insideLeft",
}}
/>
<YAxis
yAxisId="right"
orientation="right"
tick={{ fontSize: 12 }}
tickFormatter={(value) =>
`£${(value / 1000).toFixed(0)}k`
}
label={{
value: "Revenue / AOV",
angle: 90,
position: "insideRight",
}}
/>
<Tooltip content={<CustomTooltip />} />
<Bar
yAxisId="left"
dataKey="orders"
fill="#3b82f6"
radius={[2, 2, 0, 0]}
name="Orders"
/>
<Line
yAxisId="right"
type="monotone"
dataKey="revenue"
stroke="#10b981"
strokeWidth={2}
dot={{ fill: "#10b981", r: 4 }}
name="Revenue"
/>
<Line
yAxisId="right"
type="monotone"
dataKey="avgOrderValue"
stroke="#a855f7"
strokeWidth={2}
strokeDasharray="5 5"
dot={{ fill: "#a855f7", r: 3 }}
name="Avg Order Value"
/>
</ComposedChart>
</ResponsiveContainer>
</div>
) : (
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
No order data available for the selected time period
</div>
)}
{/* Calculate totals for the selected period */}
{analyticsData?.orders?.dailyOrders &&
analyticsData?.revenue?.dailyRevenue && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">
Total Revenue
</div>
<div className="text-2xl font-bold text-green-600">
{formatCurrency(
analyticsData.revenue.dailyRevenue.reduce(
(sum, day) => sum + (day.amount || 0),
0,
),
{loading || refreshing ? (
<ChartSkeleton
title="Order Trends"
description="Daily order volume and revenue processed over the selected time period"
icon={BarChart}
showStats={true}
/>
) : (
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden">
<CardHeader>
<CardTitle className="bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-transparent w-fit">
Order Trends
</CardTitle>
<CardDescription>
Daily order volume and revenue processed over the selected time
period
</CardDescription>
</CardHeader>
<CardContent>
{analyticsData?.orders?.dailyOrders &&
analyticsData.orders.dailyOrders.length > 0 ? (
<div className="h-80">
<ResponsiveContainer width="100%" height="100%">
<ComposedChart
data={combineOrdersAndRevenue(
analyticsData.orders.dailyOrders,
analyticsData.revenue?.dailyRevenue || [],
)}
</div>
</div>
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">
Total Orders
</div>
<div className="text-2xl font-bold text-blue-600">
{analyticsData.orders.dailyOrders
.reduce((sum, day) => sum + (day.count || 0), 0)
.toLocaleString()}
</div>
</div>
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<defs>
<linearGradient id="colorOrdersTrends" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3} />
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
</linearGradient>
<linearGradient id="colorRevenueTrends" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#10b981" stopOpacity={0.3} />
<stop offset="95%" stopColor="#10b981" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="hsl(var(--border))" opacity={0.4} />
<XAxis
dataKey="formattedDate"
tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }}
angle={-45}
textAnchor="end"
height={60}
axisLine={false}
tickLine={false}
/>
<YAxis
yAxisId="left"
tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }}
axisLine={false}
tickLine={false}
label={{
value: "Orders",
angle: -90,
position: "insideLeft",
style: { fill: 'hsl(var(--muted-foreground))' }
}}
/>
<YAxis
yAxisId="right"
orientation="right"
tick={{ fontSize: 12, fill: 'hsl(var(--muted-foreground))' }}
axisLine={false}
tickLine={false}
tickFormatter={(value) =>
`£${(value / 1000).toFixed(0)}k`
}
label={{
value: "Revenue",
angle: 90,
position: "insideRight",
style: { fill: 'hsl(var(--muted-foreground))' }
}}
/>
<Tooltip content={<CustomTooltip />} cursor={{ stroke: 'rgba(255,255,255,0.1)', strokeWidth: 1 }} />
<Area
yAxisId="left"
type="monotone"
dataKey="orders"
stroke="#3b82f6"
fill="url(#colorOrdersTrends)"
strokeWidth={2}
name="Orders"
activeDot={{ r: 4, strokeWidth: 0, fill: "#3b82f6" }}
/>
<Area
yAxisId="right"
type="monotone"
dataKey="revenue"
stroke="#10b981"
fill="url(#colorRevenueTrends)"
strokeWidth={2}
name="Revenue"
activeDot={{ r: 4, strokeWidth: 0, fill: "#10b981" }}
/>
</ComposedChart>
</ResponsiveContainer>
</div>
) : (
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
No order data available for the selected time period
</div>
)}
</CardContent>
</Card>
{/* Calculate totals for the selected period */}
{analyticsData?.orders?.dailyOrders &&
analyticsData?.revenue?.dailyRevenue && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">
Total Revenue
</div>
<div className="text-2xl font-bold text-green-600">
{formatCurrency(
analyticsData.revenue.dailyRevenue.reduce(
(sum, day) => sum + (day.amount || 0),
0,
),
)}
</div>
</div>
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">
Total Orders
</div>
<div className="text-2xl font-bold text-blue-600">
{analyticsData.orders.dailyOrders
.reduce((sum, day) => sum + (day.count || 0), 0)
.toLocaleString()}
</div>
</div>
</div>
)}
</CardContent>
</Card>
)}
</TabsContent>
<TabsContent value="vendors" className="mt-4 animate-in fade-in slide-in-from-bottom-2 duration-500">
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden">
<CardHeader>
<CardTitle className="bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent w-fit">Vendor Growth</CardTitle>
<CardDescription>
New vendor registrations over time
</CardDescription>
</CardHeader>
<CardContent>
{loading || refreshing ? (
<div className="flex items-center justify-center h-80">
<div className="flex flex-col items-center gap-2">
<div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full"></div>
<p className="text-sm text-muted-foreground">
Loading chart data...
</p>
{loading || refreshing ? (
<ChartSkeleton
title="Vendor Growth"
description="New vendor registrations over time"
icon={Users}
showStats={true}
/>
) : (
<Card className="border-border/40 bg-background/50 backdrop-blur-sm shadow-sm overflow-hidden">
<CardHeader>
<CardTitle className="bg-gradient-to-r from-purple-600 to-pink-500 bg-clip-text text-transparent w-fit">
Vendor Growth
</CardTitle>
<CardDescription>New vendor registrations over time</CardDescription>
</CardHeader>
<CardContent>
{analyticsData?.vendors?.dailyGrowth &&
analyticsData.vendors.dailyGrowth.length > 0 ? (
<div className="h-80">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={transformChartData(
analyticsData.vendors.dailyGrowth,
"count",
)}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<defs>
<linearGradient
id="colorVendorsAdminChart"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="5%"
stopColor="#8b5cf6"
stopOpacity={0.3}
/>
<stop
offset="95%"
stopColor="#8b5cf6"
stopOpacity={0}
/>
</linearGradient>
</defs>
<Area
type="monotone"
dataKey="value"
stroke="#8b5cf6"
fillOpacity={1}
fill="url(#colorVendorsAdminChart)"
strokeWidth={2}
activeDot={{ r: 4, strokeWidth: 0, fill: "#8b5cf6" }}
/>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="formattedDate"
tick={{ fontSize: 12 }}
angle={-45}
textAnchor="end"
height={60}
/>
<YAxis tick={{ fontSize: 12 }} />
<Tooltip content={<CustomTooltip />} />
</AreaChart>
</ResponsiveContainer>
</div>
</div>
) : analyticsData?.vendors?.dailyGrowth &&
analyticsData.vendors.dailyGrowth.length > 0 ? (
<div className="h-80">
<ResponsiveContainer width="100%" height="100%">
<RechartsBarChart
data={transformChartData(
analyticsData.vendors.dailyGrowth,
"count",
)}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="formattedDate"
tick={{ fontSize: 12 }}
angle={-45}
textAnchor="end"
height={60}
/>
<YAxis tick={{ fontSize: 12 }} />
<Tooltip content={<CustomTooltip />} />
<Bar
dataKey="value"
fill="#8b5cf6"
radius={[2, 2, 0, 0]}
/>
</RechartsBarChart>
</ResponsiveContainer>
</div>
) : (
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
No vendor data available for the selected time period
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-6">
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">Total Vendors</div>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.vendors?.total)}
</div>
</div>
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">Active Vendors</div>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.vendors?.active)}
</div>
</div>
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">Active Stores</div>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.vendors?.activeStores)}
</div>
</div>
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">New This Week</div>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.vendors?.newThisWeek)}
</div>
</div>
</div>
{/* Top Vendors by Revenue */}
{analyticsData?.vendors?.topVendors &&
analyticsData.vendors.topVendors.length > 0 && (
<div className="mt-6">
<h3 className="text-lg font-semibold mb-4">
Top Vendors by Revenue
</h3>
<div className="space-y-2">
{analyticsData.vendors.topVendors.map((vendor, index) => (
<div
key={vendor.vendorId}
className="flex items-center justify-between p-3 bg-muted/30 rounded-lg"
>
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-primary/10 text-primary font-semibold text-sm">
{index + 1}
</div>
<div>
<div className="font-medium">
{vendor.vendorName}
</div>
<div className="text-xs text-muted-foreground">
{vendor.orderCount} orders
</div>
</div>
</div>
<div className="text-right">
<div className="font-semibold text-green-600">
{formatCurrency(vendor.totalRevenue)}
</div>
</div>
</div>
))}
</div>
) : (
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
No vendor data available for the selected time period
</div>
)}
</CardContent>
</Card>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-6">
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">Total Vendors</div>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.vendors?.total)}
</div>
</div>
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">Active Vendors</div>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.vendors?.active)}
</div>
</div>
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">Active Stores</div>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.vendors?.activeStores)}
</div>
</div>
<div className="bg-muted/50 p-4 rounded-lg">
<div className="text-sm font-medium mb-1">New This Week</div>
<div className="text-2xl font-bold">
{formatNumber(analyticsData?.vendors?.newThisWeek)}
</div>
</div>
</div>
{/* Top Vendors by Revenue */}
{analyticsData?.vendors?.topVendors &&
analyticsData.vendors.topVendors.length > 0 && (
<div className="mt-6">
<h3 className="text-lg font-semibold mb-4">
Top Vendors by Revenue
</h3>
<div className="space-y-2">
{analyticsData.vendors.topVendors.map((vendor, index) => (
<div
key={vendor.vendorId}
className="flex items-center justify-between p-3 bg-muted/30 rounded-lg"
>
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-primary/10 text-primary font-semibold text-sm">
{index + 1}
</div>
<div>
<div className="font-medium">
{vendor.vendorName}
</div>
<div className="text-xs text-muted-foreground">
{vendor.orderCount} orders
</div>
</div>
</div>
<div className="text-right">
<div className="font-semibold text-green-600">
{formatCurrency(vendor.totalRevenue)}
</div>
</div>
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
)}
</TabsContent>
<TabsContent value="growth" className="mt-4 space-y-6">