Revamp admin dashboard analytics and UI
Refactored the admin dashboard to use tabbed navigation for analytics and management. Enhanced AdminAnalytics with Recharts visualizations, added top vendors by revenue, and improved chart tooltips. Removed unused columns from vendor table. Updated layout and notification context to exclude admin pages from dashboard-specific UI and notifications. Minor debug logging added to SystemStatusCard.
This commit is contained in:
@@ -8,6 +8,9 @@ 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 {
|
||||
@@ -18,7 +21,14 @@ interface AnalyticsData {
|
||||
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;
|
||||
@@ -35,8 +45,9 @@ interface AnalyticsData {
|
||||
dailyRevenue?: { date: string; amount: number }[];
|
||||
};
|
||||
engagement?: {
|
||||
totalMessages?: number;
|
||||
totalChats?: number;
|
||||
activeChats?: number;
|
||||
totalMessages?: number;
|
||||
dailyMessages?: { date: string; count: number }[];
|
||||
};
|
||||
products?: {
|
||||
@@ -59,29 +70,36 @@ export default function AdminAnalytics() {
|
||||
const [dateRange, setDateRange] = useState("7days");
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [showDebug, setShowDebug] = useState(false);
|
||||
|
||||
const fetchAnalyticsData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setErrorMessage(null);
|
||||
|
||||
const token = document.cookie
|
||||
.split("; ")
|
||||
.find((row) => row.startsWith("Authorization="))
|
||||
?.split("=")[1];
|
||||
|
||||
const response = await fetch(`/api/admin/analytics?range=${dateRange}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
const data = await fetchClient<AnalyticsData>(`/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)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch analytics data");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
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);
|
||||
@@ -109,64 +127,89 @@ export default function AdminAnalytics() {
|
||||
);
|
||||
}
|
||||
|
||||
// Chart component for line/area charts
|
||||
const Chart = ({ data, valueKey = "count", color = "#3b82f6", height = 200 }:
|
||||
{ data: any[]; valueKey?: string; color?: string; height?: number }) => {
|
||||
if (!data || data.length === 0) {
|
||||
// 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<string, number>();
|
||||
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 (
|
||||
<div className="w-full flex items-center justify-center text-muted-foreground" style={{ height: `${height}px` }}>
|
||||
No data available
|
||||
<div className="bg-background border border-border p-3 rounded-lg shadow-lg">
|
||||
<p className="text-sm font-medium mb-2">{data.formattedDate || label}</p>
|
||||
{isDualAxis ? (
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-blue-600">
|
||||
Orders: <span className="font-semibold">{data.orders}</span>
|
||||
</p>
|
||||
<p className="text-sm text-green-600">
|
||||
Revenue: <span className="font-semibold">{formatGBP(data.revenue)}</span>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-primary">
|
||||
{isAmount ? formatGBP(data.value || data.amount || 0) : `${data.value || data.count || 0}`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Find min and max for scaling
|
||||
const values = data.map(d => d[valueKey] || 0);
|
||||
const max = Math.max(...values, 1);
|
||||
const min = Math.min(...values, 0);
|
||||
const range = max - min || 1;
|
||||
|
||||
return (
|
||||
<div className="w-full relative" style={{ height: `${height}px` }}>
|
||||
<div className="absolute inset-0 flex items-end">
|
||||
{data.map((item, index) => {
|
||||
const value = item[valueKey] || 0;
|
||||
const normalizedHeight = ((value - min) / range) * 90 + 10; // Scale to 10%-100%
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="group flex-1 relative"
|
||||
>
|
||||
<div
|
||||
className="w-full rounded-t transition-all duration-200"
|
||||
style={{
|
||||
height: `${normalizedHeight}%`,
|
||||
backgroundColor: color,
|
||||
opacity: 0.7
|
||||
}}
|
||||
/>
|
||||
<div className="absolute bottom-0 left-0 right-0 opacity-0 group-hover:opacity-100 bg-background text-xs text-center rounded p-1 transform -translate-y-full pointer-events-none z-10 transition-opacity">
|
||||
{valueKey === "amount" ?
|
||||
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value) :
|
||||
value}
|
||||
<div className="text-[10px] text-muted-foreground">
|
||||
{new Date(item.date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px bg-border"></div>
|
||||
|
||||
{/* X-axis labels */}
|
||||
<div className="absolute bottom-[-24px] left-0 right-0 flex justify-between text-xs text-muted-foreground">
|
||||
<span>{new Date(data[0]?.date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})}</span>
|
||||
<span>{new Date(data[data.length - 1]?.date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
// Trend indicator component for metric cards
|
||||
@@ -189,9 +232,9 @@ export default function AdminAnalytics() {
|
||||
|
||||
// Format currency
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
return new Intl.NumberFormat('en-GB', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
currency: 'GBP',
|
||||
maximumFractionDigits: 0
|
||||
}).format(value);
|
||||
};
|
||||
@@ -237,9 +280,74 @@ export default function AdminAnalytics() {
|
||||
className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowDebug(!showDebug)}
|
||||
>
|
||||
{showDebug ? 'Hide' : 'Show'} Debug
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showDebug && analyticsData && (
|
||||
<Card className="mt-4">
|
||||
<CardHeader>
|
||||
<CardTitle>Debug: Raw Data</CardTitle>
|
||||
<CardDescription>Date Range: {dateRange}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4 text-xs font-mono">
|
||||
<div>
|
||||
<div className="font-semibold mb-2">Orders:</div>
|
||||
<div className="pl-4 space-y-1">
|
||||
<div>Total: {analyticsData?.orders?.total || 'N/A'}</div>
|
||||
<div>Today: {analyticsData?.orders?.totalToday || 'N/A'}</div>
|
||||
<div>Daily Orders Array Length: {analyticsData?.orders?.dailyOrders?.length || 0}</div>
|
||||
<div>First 3 Daily Orders:</div>
|
||||
<pre className="pl-4 bg-muted p-2 rounded overflow-auto max-h-32">
|
||||
{JSON.stringify(analyticsData?.orders?.dailyOrders?.slice(0, 3), null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="font-semibold mb-2">Revenue:</div>
|
||||
<div className="pl-4 space-y-1">
|
||||
<div>Total: {analyticsData?.revenue?.total || 'N/A'}</div>
|
||||
<div>Today: {analyticsData?.revenue?.today || 'N/A'}</div>
|
||||
<div>Daily Revenue Array Length: {analyticsData?.revenue?.dailyRevenue?.length || 0}</div>
|
||||
<div>First 3 Daily Revenue:</div>
|
||||
<pre className="pl-4 bg-muted p-2 rounded overflow-auto max-h-32">
|
||||
{JSON.stringify(analyticsData?.revenue?.dailyRevenue?.slice(0, 3), null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="font-semibold mb-2">Vendors:</div>
|
||||
<div className="pl-4 space-y-1">
|
||||
<div>Total: {analyticsData?.vendors?.total || 'N/A'}</div>
|
||||
<div>Daily Growth Array Length: {analyticsData?.vendors?.dailyGrowth?.length || 0}</div>
|
||||
<div>First 3 Daily Growth:</div>
|
||||
<pre className="pl-4 bg-muted p-2 rounded overflow-auto max-h-32">
|
||||
{JSON.stringify(analyticsData?.vendors?.dailyGrowth?.slice(0, 3), null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details className="mt-4">
|
||||
<summary className="font-semibold cursor-pointer">Full JSON Response</summary>
|
||||
<pre className="mt-2 bg-muted p-4 rounded overflow-auto max-h-96 text-[10px]">
|
||||
{JSON.stringify(analyticsData, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{/* Orders Card */}
|
||||
<Card>
|
||||
@@ -261,13 +369,17 @@ export default function AdminAnalytics() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{analyticsData?.orders?.dailyOrders && (
|
||||
<div className="mt-3 h-10">
|
||||
<Chart
|
||||
data={analyticsData.orders.dailyOrders}
|
||||
height={40}
|
||||
/>
|
||||
{analyticsData?.orders?.dailyOrders && analyticsData.orders.dailyOrders.length > 0 ? (
|
||||
<div className="mt-3 h-12">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<RechartsBarChart data={transformChartData(analyticsData.orders.dailyOrders, 'count')} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
|
||||
<Bar dataKey="value" fill="#3b82f6" radius={[2, 2, 0, 0]} />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
</RechartsBarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 text-xs text-muted-foreground">No chart data available</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -292,15 +404,17 @@ export default function AdminAnalytics() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{analyticsData?.revenue?.dailyRevenue && (
|
||||
<div className="mt-3 h-10">
|
||||
<Chart
|
||||
data={analyticsData.revenue.dailyRevenue}
|
||||
valueKey="amount"
|
||||
color="#10b981"
|
||||
height={40}
|
||||
/>
|
||||
{analyticsData?.revenue?.dailyRevenue && analyticsData.revenue.dailyRevenue.length > 0 ? (
|
||||
<div className="mt-3 h-12">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<RechartsBarChart data={transformChartData(analyticsData.revenue.dailyRevenue, 'amount')} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
|
||||
<Bar dataKey="value" fill="#10b981" radius={[2, 2, 0, 0]} />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
</RechartsBarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 text-xs text-muted-foreground">No chart data available</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -317,6 +431,10 @@ export default function AdminAnalytics() {
|
||||
<div className="text-2xl font-bold">
|
||||
{analyticsData?.vendors?.total?.toLocaleString() || '0'}
|
||||
</div>
|
||||
<div className="flex items-center text-xs text-muted-foreground mt-1">
|
||||
<span>Active: {analyticsData?.vendors?.active || 0}</span>
|
||||
<span className="ml-2">Stores: {analyticsData?.vendors?.activeStores || 0}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-xs text-muted-foreground mt-1">
|
||||
<span>New Today: {analyticsData?.vendors?.newToday || 0}</span>
|
||||
<TrendIndicator
|
||||
@@ -325,14 +443,17 @@ export default function AdminAnalytics() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{analyticsData?.vendors?.dailyGrowth && (
|
||||
<div className="mt-3 h-10">
|
||||
<Chart
|
||||
data={analyticsData.vendors.dailyGrowth}
|
||||
color="#8b5cf6"
|
||||
height={40}
|
||||
/>
|
||||
{analyticsData?.vendors?.dailyGrowth && analyticsData.vendors.dailyGrowth.length > 0 ? (
|
||||
<div className="mt-3 h-12">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<RechartsBarChart data={transformChartData(analyticsData.vendors.dailyGrowth, 'count')} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
|
||||
<Bar dataKey="value" fill="#8b5cf6" radius={[2, 2, 0, 0]} />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
</RechartsBarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 text-xs text-muted-foreground">No chart data available</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -359,9 +480,7 @@ export default function AdminAnalytics() {
|
||||
<Tabs defaultValue="orders" className="mt-6">
|
||||
<TabsList>
|
||||
<TabsTrigger value="orders">Orders</TabsTrigger>
|
||||
<TabsTrigger value="revenue">Revenue</TabsTrigger>
|
||||
<TabsTrigger value="vendors">Vendors</TabsTrigger>
|
||||
<TabsTrigger value="engagement">Engagement</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="orders" className="mt-4">
|
||||
@@ -369,18 +488,49 @@ export default function AdminAnalytics() {
|
||||
<CardHeader>
|
||||
<CardTitle>Order Trends</CardTitle>
|
||||
<CardDescription>
|
||||
Daily order volume over the selected time period
|
||||
Daily order volume and revenue processed over the selected time period
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{analyticsData?.orders?.dailyOrders ? (
|
||||
<Chart
|
||||
data={analyticsData.orders.dailyOrders}
|
||||
height={300}
|
||||
/>
|
||||
{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', 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" />
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
|
||||
No order data available
|
||||
No order data available for the selected time period
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -402,46 +552,6 @@ export default function AdminAnalytics() {
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="revenue" className="mt-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Revenue Trends</CardTitle>
|
||||
<CardDescription>
|
||||
Daily revenue over the selected time period
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{analyticsData?.revenue?.dailyRevenue ? (
|
||||
<Chart
|
||||
data={analyticsData.revenue.dailyRevenue}
|
||||
valueKey="amount"
|
||||
color="#10b981"
|
||||
height={300}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
|
||||
No revenue data available
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 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">{formatCurrency(analyticsData?.revenue?.total || 0)}</div>
|
||||
</div>
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">Today's Revenue</div>
|
||||
<div className="text-2xl font-bold">{formatCurrency(analyticsData?.revenue?.today || 0)}</div>
|
||||
</div>
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">This Week's Revenue</div>
|
||||
<div className="text-2xl font-bold">{formatCurrency(analyticsData?.revenue?.thisWeek || 0)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="vendors" className="mt-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -451,71 +561,73 @@ export default function AdminAnalytics() {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{analyticsData?.vendors?.dailyGrowth ? (
|
||||
<Chart
|
||||
data={analyticsData.vendors.dailyGrowth}
|
||||
color="#8b5cf6"
|
||||
height={300}
|
||||
/>
|
||||
{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
|
||||
No vendor data available for the selected time period
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
|
||||
<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">{analyticsData?.vendors?.total?.toLocaleString() || '0'}</div>
|
||||
</div>
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">New Today</div>
|
||||
<div className="text-2xl font-bold">{analyticsData?.vendors?.newToday?.toLocaleString() || '0'}</div>
|
||||
<div className="text-sm font-medium mb-1">Active Vendors</div>
|
||||
<div className="text-2xl font-bold">{analyticsData?.vendors?.active?.toLocaleString() || '0'}</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">{analyticsData?.vendors?.activeStores?.toLocaleString() || '0'}</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">{analyticsData?.vendors?.newThisWeek?.toLocaleString() || '0'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="engagement" className="mt-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>User Engagement</CardTitle>
|
||||
<CardDescription>
|
||||
Chat and message activity
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{analyticsData?.engagement?.dailyMessages ? (
|
||||
<Chart
|
||||
data={analyticsData.engagement.dailyMessages}
|
||||
color="#ec4899"
|
||||
height={300}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
|
||||
No engagement data available
|
||||
|
||||
{/* 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>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">Total Messages</div>
|
||||
<div className="text-2xl font-bold">{analyticsData?.engagement?.totalMessages?.toLocaleString() || '0'}</div>
|
||||
</div>
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">Active Chats</div>
|
||||
<div className="text-2xl font-bold">{analyticsData?.engagement?.activeChats?.toLocaleString() || '0'}</div>
|
||||
</div>
|
||||
<div className="bg-muted/50 p-4 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">Sessions</div>
|
||||
<div className="text-2xl font-bold">{analyticsData?.sessions?.active?.toLocaleString() || '0'} / {analyticsData?.sessions?.total?.toLocaleString() || '0'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
Reference in New Issue
Block a user