This commit is contained in:
NotII
2025-03-19 01:46:09 +01:00
parent 998986b9fc
commit 6bdcb15552
4 changed files with 1111 additions and 1 deletions

View File

@@ -0,0 +1,937 @@
"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 { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { AlertCircle, BarChart as BarChartIcon, LineChart, RefreshCw, Users, ShoppingCart,
TrendingUp, TrendingDown, DollarSign, MessageSquare, Clock, Landmark } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { ResponsiveContainer, PieChart, Pie, Cell, Tooltip, Legend, XAxis, YAxis, BarChart, Bar } from "recharts";
// API response data structure
interface AnalyticsData {
vendors?: {
total?: number;
newToday?: number;
newThisWeek?: number;
activeToday?: number;
active?: number;
stores?: number;
dailyGrowth?: { date: string; count: number }[];
data?: { date: string; count: number }[];
};
orders?: {
total?: number;
totalToday?: number;
totalThisWeek?: number;
recent?: number;
pending?: number;
completed?: number;
dailyOrders?: { date: string; count: number }[];
data?: { date: string; count: number }[];
};
revenue?: {
total?: number;
today?: number;
thisWeek?: number;
dailyRevenue?: { date: string; amount: number }[];
};
engagement?: {
totalMessages?: number;
activeChats?: number;
avgResponseTime?: number;
dailyMessages?: { date: string; count: number }[];
};
products?: {
total?: number;
recent?: number;
};
stores?: {
total?: number;
active?: number;
};
sessions?: {
total?: number;
active?: number;
};
promotions?: {
total?: number;
active?: number;
used?: number;
totalDiscountAmount?: number;
discountPercentage?: number;
topPromotions?: { _id: string; count: number; totalDiscount: number }[];
discountByType?: {
percentage?: { count: number; totalDiscount: number };
fixed?: { count: number; totalDiscount: number };
};
};
chats?: {
totalChats?: number;
activeChats?: number;
totalMessages?: number;
recentMessages?: number;
messagesBySender?: { buyer: number; vendor: number };
avgResponseTimeMin?: number;
messagesByHour?: number[];
};
telegram?: {
totalUsers?: number;
totalStoreConnections?: number;
avgStoresPerUser?: number;
multiStoreUsers?: number;
};
escrow?: {
total?: number;
active?: number;
released?: number;
disputed?: number;
disputeRate?: number;
totalByCurrency?: { ltc: number; btc: number; xmr: number };
heldByCurrency?: { ltc: number; btc: number; xmr: number };
avgReleaseTimeHours?: number;
};
security?: {
blockedUsers?: {
total?: number;
recentlyBlocked?: number;
}
};
}
export default function AdminAnalytics() {
const [analyticsData, setAnalyticsData] = useState<AnalyticsData | null>(null);
const [loading, setLoading] = useState(true);
const [dateRange, setDateRange] = useState("7days");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [refreshing, setRefreshing] = useState(false);
const fetchAnalyticsData = async () => {
try {
setLoading(true);
setErrorMessage(null);
const token = document.cookie
.split("; ")
.find((row) => row.startsWith("Authorization="))
?.split("=")[1];
console.log("Token from cookie:", token ? token.substring(0, 10) + "..." : "not found");
console.log("API URL:", `${process.env.NEXT_PUBLIC_API_URL}/admin/analytics?range=${dateRange}`);
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/admin/analytics?range=${dateRange}`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
console.log("Response status:", response.status);
console.log("Response ok:", response.ok);
if (!response.ok) {
const errorText = await response.text();
console.error("Error response:", errorText);
throw new Error(`Failed to fetch analytics data: ${response.status} ${errorText}`);
}
const data = await response.json();
console.log("Analytics data received:", data);
setAnalyticsData(data);
} catch (error: any) {
console.error("Error fetching analytics data:", error);
setErrorMessage(`Failed to load analytics data: ${error.message}`);
// For demo purposes, load mock data if API fails
setAnalyticsData({
vendors: {
total: 1248,
newToday: 24,
newThisWeek: 124,
activeToday: 356,
stores: 100,
dailyGrowth: [
{ date: "2023-11-01", count: 15 },
{ date: "2023-11-02", count: 18 },
{ date: "2023-11-03", count: 12 },
{ date: "2023-11-04", count: 22 },
{ date: "2023-11-05", count: 26 },
{ date: "2023-11-06", count: 17 },
{ date: "2023-11-07", count: 14 },
],
},
orders: {
total: 5672,
totalToday: 86,
totalThisWeek: 432,
pending: 47,
completed: 5625,
dailyOrders: [
{ date: "2023-11-01", count: 54 },
{ date: "2023-11-02", count: 62 },
{ date: "2023-11-03", count: 58 },
{ date: "2023-11-04", count: 71 },
{ date: "2023-11-05", count: 68 },
{ date: "2023-11-06", count: 65 },
{ date: "2023-11-07", count: 54 },
],
},
revenue: {
total: 156320,
today: 3280,
thisWeek: 18642,
dailyRevenue: [
{ date: "2023-11-01", amount: 2345 },
{ date: "2023-11-02", amount: 2762 },
{ date: "2023-11-03", amount: 2458 },
{ date: "2023-11-04", amount: 3121 },
{ date: "2023-11-05", amount: 2968 },
{ date: "2023-11-06", amount: 2708 },
{ date: "2023-11-07", amount: 2280 },
],
},
engagement: {
totalMessages: 32450,
activeChats: 123,
avgResponseTime: 5.2,
dailyMessages: [
{ date: "2023-11-01", count: 432 },
{ date: "2023-11-02", count: 512 },
{ date: "2023-11-03", count: 478 },
{ date: "2023-11-04", count: 541 },
{ date: "2023-11-05", count: 498 },
{ date: "2023-11-06", count: 465 },
{ date: "2023-11-07", count: 423 },
],
},
});
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchAnalyticsData();
}, [dateRange]);
const handleRefresh = () => {
setRefreshing(true);
fetchAnalyticsData();
};
if (loading && !analyticsData) {
return (
<div className="flex justify-center my-8">
<div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full"></div>
</div>
);
}
// These would normally be real chart components
// For this example, we'll use simplified representations
const SimpleAreaChart = ({ data, label, color }: { data: any[]; label: string; color: string }) => {
// Handle empty data or undefined
if (!data || data.length === 0) {
return (
<div className="w-full h-32 flex items-center justify-center text-muted-foreground">
No data available
</div>
);
}
return (
<div className="w-full h-32 relative mt-2">
{/* Simplified chart visualization */}
<div className="absolute bottom-0 left-0 right-0 top-0 flex items-end">
{data.map((item, index) => {
// Normalize height between 10% and 90%
const values = data.map(d => d.count || d.amount);
const max = Math.max(...values);
const min = Math.min(...values);
const range = max - min;
const value = item.count || item.amount;
const normalizedHeight = range === 0
? 50
: 10 + (((value - min) / range) * 80);
return (
<div
key={index}
className="flex-1 mx-0.5 rounded-t"
style={{
height: `${normalizedHeight}%`,
backgroundColor: color,
opacity: 0.7
}}
/>
);
})}
</div>
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-border"></div>
<div className="absolute bottom-[-20px] 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>
);
};
// Function to render trend indicator
const TrendIndicator = ({ value, prefix = "", suffix = "" }: { value: number, prefix?: string, suffix?: string }) => {
if (value === 0) return null;
return (
<div className={`flex items-center text-xs font-medium ml-1 ${value > 0 ? 'text-green-500' : 'text-red-500'}`}>
{value > 0 ? <TrendingUp className="h-3 w-3 mr-1" /> : <TrendingDown className="h-3 w-3 mr-1" />}
{prefix}{Math.abs(value).toFixed(1)}{suffix}
</div>
);
};
// Mini sparkline component for metric cards
const MiniSparkline = ({ data, color = "#3b82f6" }: { data: any[], color?: string }) => {
if (!data || data.length === 0) return null;
const values = data.map(item => item.count || item.amount || 0);
const max = Math.max(...values, 1);
return (
<div className="flex items-end h-8 space-x-px mt-2">
{values.map((value, index) => {
const height = (value / max) * 100;
return (
<div
key={index}
className="flex-1 rounded-sm transition-all duration-200"
style={{
height: `${height}%`,
backgroundColor: color,
minHeight: '2px',
opacity: 0.7 + ((index / values.length) * 0.3) // gradually increase opacity
}}
/>
);
})}
</div>
);
};
return (
<div className="w-full">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-bold">Platform Analytics</h2>
<div className="flex items-center space-x-4">
<Select value={dateRange} onValueChange={setDateRange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select timeframe" />
</SelectTrigger>
<SelectContent>
<SelectItem value="7days">Last 7 Days</SelectItem>
<SelectItem value="30days">Last 30 Days</SelectItem>
<SelectItem value="90days">Last 90 Days</SelectItem>
</SelectContent>
</Select>
<Button
size="sm"
onClick={handleRefresh}
disabled={refreshing}
className="flex items-center gap-2"
>
<RefreshCw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
Refresh
</Button>
</div>
</div>
{errorMessage && (
<Alert variant="destructive" className="mb-6">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{errorMessage}</AlertDescription>
</Alert>
)}
<Tabs defaultValue="orders">
<TabsList className="grid grid-cols-6 mb-6">
<TabsTrigger value="orders">Orders</TabsTrigger>
<TabsTrigger value="vendors">Vendors</TabsTrigger>
<TabsTrigger value="revenue">Revenue</TabsTrigger>
<TabsTrigger value="promotions">Promotions</TabsTrigger>
<TabsTrigger value="communication">Communication</TabsTrigger>
<TabsTrigger value="payments">Payments</TabsTrigger>
</TabsList>
<TabsContent value="orders">
<Card>
<CardHeader>
<CardTitle>Order Analytics</CardTitle>
<CardDescription>Order volume and revenue metrics</CardDescription>
</CardHeader>
<CardContent>
{analyticsData && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="p-4 border rounded-md">
<div className="text-sm font-medium text-muted-foreground mb-1">Total Orders</div>
<div className="text-2xl font-semibold">{analyticsData.orders?.total?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md">
<div className="text-sm font-medium text-muted-foreground mb-1">Orders Today</div>
<div className="text-2xl font-semibold">{analyticsData.orders?.totalToday?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md">
<div className="text-sm font-medium text-muted-foreground mb-1">Pending Orders</div>
<div className="text-2xl font-semibold">{analyticsData.orders?.pending?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md">
<div className="text-sm font-medium text-muted-foreground mb-1">Revenue</div>
<div className="text-2xl font-semibold">${analyticsData.revenue?.total?.toLocaleString() || '0'}</div>
</div>
</div>
)}
<div className="mt-6">
<h3 className="text-lg font-medium mb-4">Order & Revenue Trends</h3>
{analyticsData && (
<div className="h-[300px] flex items-center justify-center border rounded-md">
<p className="text-muted-foreground">Order and revenue charts would render here</p>
</div>
)}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="vendors">
<Card>
<CardHeader>
<CardTitle>Vendor Analytics</CardTitle>
<CardDescription>Detailed vendor metrics and store statistics</CardDescription>
</CardHeader>
<CardContent>
{analyticsData && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div className="p-4 border rounded-md">
<div className="text-sm font-medium text-muted-foreground mb-1">Total Vendors</div>
<div className="text-2xl font-semibold">{analyticsData.vendors?.total?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md">
<div className="text-sm font-medium text-muted-foreground mb-1">New Today</div>
<div className="text-2xl font-semibold">{analyticsData.vendors?.newToday?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md">
<div className="text-sm font-medium text-muted-foreground mb-1">New This Week</div>
<div className="text-2xl font-semibold">{analyticsData.vendors?.newThisWeek?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md">
<div className="text-sm font-medium text-muted-foreground mb-1">Active Stores</div>
<div className="text-2xl font-semibold">{analyticsData.vendors?.stores?.toLocaleString() || analyticsData.stores?.total?.toLocaleString() || '0'}</div>
</div>
</div>
)}
<div className="mt-6">
<h3 className="text-lg font-medium mb-4">Vendor Growth Trend</h3>
{analyticsData && (
<div className="h-[300px] flex items-center justify-center border rounded-md">
<p className="text-muted-foreground">Vendor growth chart would render here</p>
</div>
)}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="revenue">
<Card className="shadow-sm">
<CardHeader>
<CardTitle>Revenue Analytics</CardTitle>
<CardDescription>Revenue and financial metrics</CardDescription>
</CardHeader>
<CardContent>
{analyticsData && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div className="p-4 border rounded-md bg-gradient-to-r from-purple-950/30 to-slate-900/30">
<div className="text-sm font-medium text-muted-foreground mb-1">Total Processed</div>
<div className="text-2xl font-semibold">${analyticsData.revenue?.total?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md bg-gradient-to-r from-purple-950/30 to-slate-900/30">
<div className="text-sm font-medium text-muted-foreground mb-1">Today's Revenue</div>
<div className="text-2xl font-semibold">${analyticsData.revenue?.today?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md bg-gradient-to-r from-purple-950/30 to-slate-900/30">
<div className="text-sm font-medium text-muted-foreground mb-1">This Week</div>
<div className="text-2xl font-semibold">${analyticsData.revenue?.thisWeek?.toLocaleString() || '0'}</div>
</div>
<div className="p-4 border rounded-md bg-gradient-to-r from-purple-950/30 to-slate-900/30">
<div className="text-sm font-medium text-muted-foreground mb-1">Average Per Order</div>
<div className="text-2xl font-semibold">
${analyticsData.revenue && analyticsData.orders?.total
? Math.round((analyticsData.revenue.total || 0) / analyticsData.orders.total).toLocaleString()
: '0'}
</div>
</div>
</div>
)}
<div className="mt-6">
<h3 className="text-lg font-medium mb-4">Revenue Trend</h3>
{analyticsData && (
<SimpleAreaChart
data={analyticsData.revenue?.dailyRevenue || []}
label="Revenue"
color="#8b5cf6" // purple
/>
)}
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="promotions">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Active Promotions
</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.promotions?.active || 0}
</div>
<p className="text-xs text-muted-foreground">
out of {analyticsData?.promotions?.total || 0} total
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Total Discount Amount
</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
${analyticsData?.promotions?.totalDiscountAmount?.toLocaleString() || 0}
</div>
<p className="text-xs text-muted-foreground">
{analyticsData?.promotions?.discountPercentage || 0}% of revenue
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Promotion Uses
</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.promotions?.used || 0}
</div>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle className="text-md font-medium">Top Promotions</CardTitle>
<CardDescription>Most used promotion codes</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{analyticsData?.promotions?.topPromotions?.map((promo, i) => (
<div key={i} className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">{promo._id}</p>
<p className="text-xs text-muted-foreground">
{promo.count} uses · ${promo.totalDiscount.toLocaleString()}
</p>
</div>
<div className="font-medium">
${(promo.totalDiscount / promo.count).toFixed(2)}
<span className="text-xs text-muted-foreground ml-1">avg</span>
</div>
</div>
))}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-md font-medium">Discount Types</CardTitle>
<CardDescription>Breakdown by discount type</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center h-[200px]">
{analyticsData?.promotions?.discountByType && (
<div className="w-full flex flex-col gap-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Percentage Discounts</p>
<p className="text-xs text-muted-foreground">
{analyticsData.promotions.discountByType.percentage?.count || 0} uses
</p>
</div>
<div className="font-medium">
${analyticsData.promotions.discountByType.percentage?.totalDiscount?.toLocaleString() || 0}
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Fixed Amount Discounts</p>
<p className="text-xs text-muted-foreground">
{analyticsData.promotions.discountByType.fixed?.count || 0} uses
</p>
</div>
<div className="font-medium">
${analyticsData.promotions.discountByType.fixed?.totalDiscount?.toLocaleString() || 0}
</div>
</div>
</div>
)}
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="communication">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Total Chats
</CardTitle>
<MessageSquare className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.chats?.totalChats || 0}
</div>
<p className="text-xs text-muted-foreground">
{analyticsData?.chats?.activeChats || 0} active in selected period
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Total Messages
</CardTitle>
<MessageSquare className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.chats?.totalMessages?.toLocaleString() || 0}
</div>
<p className="text-xs text-muted-foreground">
{analyticsData?.chats?.recentMessages?.toLocaleString() || 0} in selected period
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Avg Response Time
</CardTitle>
<Clock className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.chats?.avgResponseTimeMin || 0} min
</div>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<Card>
<CardHeader>
<CardTitle className="text-md font-medium">Message Volume by Hour</CardTitle>
<CardDescription>Chat activity distribution</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[200px]">
{analyticsData?.chats?.messagesByHour && (
<ResponsiveContainer width="100%" height="100%">
<BarChart data={analyticsData.chats.messagesByHour.map((count, hour) => ({ hour, count }))}>
<XAxis dataKey="hour" />
<YAxis />
<Tooltip />
<Bar dataKey="count" fill="#6366f1" />
</BarChart>
</ResponsiveContainer>
)}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-md font-medium">Message Breakdown</CardTitle>
<CardDescription>By sender type</CardDescription>
</CardHeader>
<CardContent>
<div className="h-[200px] flex items-center justify-center">
{analyticsData?.chats?.messagesBySender && (
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={[
{ name: 'Buyer', value: analyticsData.chats.messagesBySender.buyer || 0 },
{ name: 'Vendor', value: analyticsData.chats.messagesBySender.vendor || 0 }
]}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
fill="#8884d8"
dataKey="value"
label
>
<Cell fill="#6366f1" />
<Cell fill="#22c55e" />
</Pie>
<Tooltip />
<Legend />
</PieChart>
</ResponsiveContainer>
)}
</div>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle className="text-md font-medium">Telegram Users</CardTitle>
<CardDescription>Bot user statistics</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Total Users</p>
</div>
<div className="font-medium">
{analyticsData?.telegram?.totalUsers || 0}
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Store Connections</p>
</div>
<div className="font-medium">
{analyticsData?.telegram?.totalStoreConnections || 0}
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Avg Stores per User</p>
</div>
<div className="font-medium">
{analyticsData?.telegram?.avgStoresPerUser?.toFixed(2) || 0}
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Multi-Store Users</p>
</div>
<div className="font-medium">
{analyticsData?.telegram?.multiStoreUsers || 0}
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-md font-medium">Security</CardTitle>
<CardDescription>Blocked users and security metrics</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Total Blocked Users</p>
</div>
<div className="font-medium">
{analyticsData?.security?.blockedUsers?.total || 0}
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Recently Blocked</p>
<p className="text-xs text-muted-foreground">
In selected period
</p>
</div>
<div className="font-medium">
{analyticsData?.security?.blockedUsers?.recentlyBlocked || 0}
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="payments">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Active Escrows
</CardTitle>
<Landmark className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.escrow?.active || 0}
</div>
<p className="text-xs text-muted-foreground">
out of {analyticsData?.escrow?.total || 0} total
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Released Escrows
</CardTitle>
<Landmark className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.escrow?.released || 0}
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">
Dispute Rate
</CardTitle>
<Landmark className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{analyticsData?.escrow?.disputeRate?.toFixed(1) || 0}%
</div>
<p className="text-xs text-muted-foreground">
{analyticsData?.escrow?.disputed || 0} disputed escrows
</p>
</CardContent>
</Card>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle className="text-md font-medium">Escrow by Currency</CardTitle>
<CardDescription>Currently held amounts</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{analyticsData?.escrow?.heldByCurrency && (
<>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Bitcoin (BTC)</p>
</div>
<div className="font-medium">
{analyticsData.escrow.heldByCurrency.btc.toFixed(4)} BTC
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Litecoin (LTC)</p>
</div>
<div className="font-medium">
{analyticsData.escrow.heldByCurrency.ltc.toFixed(2)} LTC
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Monero (XMR)</p>
</div>
<div className="font-medium">
{analyticsData.escrow.heldByCurrency.xmr.toFixed(2)} XMR
</div>
</div>
</>
)}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-md font-medium">Escrow Metrics</CardTitle>
<CardDescription>Release time and amounts</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="text-sm font-medium">Avg Release Time</p>
</div>
<div className="font-medium">
{analyticsData?.escrow?.avgReleaseTimeHours ? (
<>
{Math.floor(analyticsData.escrow.avgReleaseTimeHours / 24)} days, {analyticsData.escrow.avgReleaseTimeHours % 24} hours
</>
) : (
"8 days, 0 hours"
)}
</div>
</div>
{analyticsData?.escrow?.totalByCurrency && (
<div className="pt-4">
<p className="text-sm font-medium mb-2">Total Processed (lifetime)</p>
<div className="space-y-2 pl-2">
<div className="text-xs flex justify-between">
<span>BTC:</span>
<span>{analyticsData.escrow.totalByCurrency.btc.toFixed(4)} BTC</span>
</div>
<div className="text-xs flex justify-between">
<span>LTC:</span>
<span>{analyticsData.escrow.totalByCurrency.ltc.toFixed(2)} LTC</span>
</div>
<div className="text-xs flex justify-between">
<span>XMR:</span>
<span>{analyticsData.escrow.totalByCurrency.xmr.toFixed(2)} XMR</span>
</div>
</div>
</div>
)}
</div>
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</div>
);
}