From 57e196cc6aafa6aff3e2078ec1c1c4c33804870e Mon Sep 17 00:00:00 2001 From: NotII <46204250+NotII@users.noreply.github.com> Date: Wed, 19 Mar 2025 14:06:58 +0100 Subject: [PATCH] testing --- app/dashboard/admin/page.tsx | 287 ++++--- app/dashboard/orders/[id]/page.tsx | 24 +- components/admin/AdminAnalytics.tsx | 1090 +++++++++------------------ 3 files changed, 499 insertions(+), 902 deletions(-) diff --git a/app/dashboard/admin/page.tsx b/app/dashboard/admin/page.tsx index 579c561..070b972 100644 --- a/app/dashboard/admin/page.tsx +++ b/app/dashboard/admin/page.tsx @@ -1,172 +1,161 @@ "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 { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { - AlertCircle, Users, MessageSquare, BarChart as BarChartIcon, Trash2, Bot, Database, - DollarSign, CreditCard, Landmark, Shield, Tag -} from "lucide-react"; -import AdminAnalytics from "@/components/admin/AdminAnalytics"; +import React, { useEffect, useState } from "react"; +// Example chart library: +// import { LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid } from "recharts"; + +interface DailyDataPoint { + date: string; + count: number; +} + +interface AnalyticsData { + success?: boolean; + orders?: { + total: number; + today: number; + week: number; + dailyOrders: DailyDataPoint[]; + }; + revenue?: { + total: number; + today: number; + week: number; + dailyRevenue: DailyDataPoint[]; + }; + vendors?: { + total: number; + newToday: number; + newThisWeek: number; + dailyVendors: DailyDataPoint[]; + }; + engagement?: { + totalChats: number; + activeChatsToday: number; + dailyChats: DailyDataPoint[]; + }; + products?: { + total: number; + }; + message?: string; +} export default function AdminDashboardPage() { - const [activeTab, setActiveTab] = useState("overview"); - const [isAdmin, setIsAdmin] = useState(false); + const [analytics, setAnalytics] = useState(null); const [loading, setLoading] = useState(true); - useEffect(() => { - // Check if the current user is an admin - const checkAdminStatus = async () => { - try { - // Get auth token from cookies - const token = document.cookie - .split("; ") - .find((row) => row.startsWith("Authorization=")) - ?.split("=")[1]; - - if (!token) { - setIsAdmin(false); - setLoading(false); - return; + const fetchAnalytics = async () => { + try { + setLoading(true); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/admin/analytics`, { + headers: { + 'Authorization': `Bearer ${process.env.NEXT_PUBLIC_JWT_SECRET}` } + }); + const data = await response.json(); + setAnalytics(data); + } catch (error) { + console.error("Error fetching analytics:", error); + } finally { + setLoading(false); + } + }; - const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/me`, { - method: "GET", - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - const data = await response.json(); - // Check if vendor has admin privileges - setIsAdmin(data.vendor?.isAdmin === true); - } catch (error) { - console.error("Error checking admin status:", error); - setIsAdmin(false); - } finally { - setLoading(false); - } - }; - - checkAdminStatus(); + useEffect(() => { + fetchAnalytics(); }, []); if (loading) { + return
Loading Admin Dashboard...
; + } + + if (!analytics?.success) { return ( -
-
+
+

Error Fetching Analytics

+

{analytics?.message || "Unknown error occurred."}

); } - if (!isAdmin) { - return ( -
- - - Access Denied - - You do not have permission to access the admin dashboard. - - -
- ); - } + // Deconstruct the analytics data + const { + orders, + revenue, + vendors, + engagement, + products, + } = analytics; return ( -
-

Admin Dashboard

- - - - Overview - Vendors - Bot Management - Sessions - Analytics - - - -
- setActiveTab("vendors")}> - - Vendor Management - - - - - View and manage vendors across the platform. - - - - - setActiveTab("bot")}> - - Bot Management - - - - - Configure and manage the Telegram bot settings. - - - - - setActiveTab("sessions")}> - - Session Management - - - - - Manage active sessions and clear inactive ones. - - - - - setActiveTab("analytics")}> - - Analytics - - - - - View platform statistics and performance metrics. - - - +
+

Admin Dashboard

- setActiveTab("analytics")}> - - Promotions - - - - - Track promotion usage and performance metrics. - - - + {/* Quick stats row */} +
+
+

Total Orders

+

{orders?.total || 0}

+

+ Today: {orders?.today || 0} | This Week: {orders?.week || 0} +

+
- setActiveTab("analytics")}> - - Payments & Escrow - - - - - Monitor escrow status and financial operations. - - - -
- - - - - +
+

Revenue

+

£{revenue?.total?.toFixed(2) || 0}

+

+ Today: £{revenue?.today?.toFixed(2) || 0} | This Week: £{revenue?.week?.toFixed(2) || 0} +

+
+ +
+

Vendors

+

{vendors?.total || 0}

+

+ New Today: {vendors?.newToday || 0} | This Week: {vendors?.newThisWeek || 0} +

+
+ +
+

Chats

+

{engagement?.totalChats || 0}

+

+ Active Today: {engagement?.activeChatsToday || 0} +

+
+ +
+

Products

+

{products?.total || 0}

+
+
+ + {/* Example chart section */} +
+

Orders This Week

+ + {/* + Uncomment and customize if using a chart library like Recharts: + + + + + + + + + */} + +
+          {JSON.stringify(orders?.dailyOrders, null, 2)}
+        
+
); } \ No newline at end of file diff --git a/app/dashboard/orders/[id]/page.tsx b/app/dashboard/orders/[id]/page.tsx index c6d090a..978840a 100644 --- a/app/dashboard/orders/[id]/page.tsx +++ b/app/dashboard/orders/[id]/page.tsx @@ -132,11 +132,17 @@ export default function OrderDetailsPage() { const results = await Promise.all(responses.map((res) => res)); results.forEach((product, index) => { - productNamesMap[productIds[index]] = product.name || "Unknown Product"; + productNamesMap[productIds[index]] = product.name || "Unknown Product (Deleted)"; }); } catch (err) { console.error("Failed to fetch product names:", err); + // Initialize product names that failed to load + productIds.forEach(id => { + if (!productNamesMap[id]) { + productNamesMap[id] = "Unknown Product (Deleted)"; + } + }); } return productNamesMap; }; @@ -323,6 +329,20 @@ export default function OrderDetailsPage() { const productIds = data.products.map((product) => product.productId); const productNamesMap = await fetchProductNames(productIds, authToken); setProductNames(productNamesMap); + + // Add a short timeout to ensure any products still showing as loading + // are marked as deleted/unknown + setTimeout(() => { + setProductNames(prev => { + const newMap = {...prev}; + productIds.forEach(id => { + if (!newMap[id] || newMap[id] === "Loading...") { + newMap[id] = "Unknown Product (Deleted)"; + } + }); + return newMap; + }); + }, 3000); // 3 second timeout if (data.status === "paid") { setIsPaid(true); @@ -603,7 +623,7 @@ export default function OrderDetailsPage() { {order?.products.map((product) => ( - {productNames[product.productId] || "Loading..."} + {productNames[product.productId] || "Unknown Product (Deleted)"} {product.quantity} £{product.pricePerUnit.toFixed(2)} diff --git a/components/admin/AdminAnalytics.tsx b/components/admin/AdminAnalytics.tsx index f2c1357..1f43f14 100644 --- a/components/admin/AdminAnalytics.tsx +++ b/components/admin/AdminAnalytics.tsx @@ -3,12 +3,11 @@ 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 { 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 { ResponsiveContainer, PieChart, Pie, Cell, Tooltip, Legend, XAxis, YAxis, BarChart, Bar } from "recharts"; // API response data structure interface AnalyticsData { @@ -20,17 +19,14 @@ interface AnalyticsData { 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; @@ -41,7 +37,6 @@ interface AnalyticsData { engagement?: { totalMessages?: number; activeChats?: number; - avgResponseTime?: number; dailyMessages?: { date: string; count: number }[]; }; products?: { @@ -56,49 +51,6 @@ interface AnalyticsData { 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() { @@ -118,9 +70,6 @@ export default function AdminAnalytics() { .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: { @@ -128,85 +77,15 @@ export default function AdminAnalytics() { }, }); - 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}`); + throw new Error("Failed to fetch analytics data"); } const data = await response.json(); - console.log("Analytics data received:", data); setAnalyticsData(data); - } catch (error: any) { + } catch (error) { 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 }, - ], - }, - }); + setErrorMessage("Failed to load analytics data. Please try again."); } finally { setLoading(false); setRefreshing(false); @@ -230,706 +109,415 @@ export default function AdminAnalytics() { ); } - // 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 + // 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) { return ( -
+
No data available
); } + // 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 ( -
- {/* Simplified chart visualization */} -
+
+
{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); + const value = item[valueKey] || 0; + const normalizedHeight = ((value - min) / range) * 90 + 10; // Scale to 10%-100% return (
+ key={index} + className="group flex-1 relative" + > +
+
+ {valueKey === "amount" ? + new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value) : + value} +
+ {new Date(item.date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})} +
+
+
); })}
-
-
- {new Date(data[0].date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})} - {new Date(data[data.length - 1].date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})} +
+ + {/* X-axis labels */} +
+ {new Date(data[0]?.date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})} + {new Date(data[data.length - 1]?.date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})}
); }; - // Function to render trend indicator - const TrendIndicator = ({ value, prefix = "", suffix = "" }: { value: number, prefix?: string, suffix?: string }) => { - if (value === 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'}`}> - {value > 0 ? : } - {prefix}{Math.abs(value).toFixed(1)}{suffix} +
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {percentChange >= 0 ? + : + } + {Math.abs(percentChange).toFixed(1)}%
); }; - // 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 ( -
- {values.map((value, index) => { - const height = (value / max) * 100; - return ( -
- ); - })} -
- ); + // Format currency + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + maximumFractionDigits: 0 + }).format(value); }; return ( -
-
-

Platform Analytics

-
- - -
-
- +
{errorMessage && ( - + Error {errorMessage} )} - - - + +
+
+

Dashboard Analytics

+

Overview of your marketplace performance

+
+ +
+ + + +
+
+ +
+ {/* Orders Card */} + + +
+ Total Orders + +
+
+ +
+ {analyticsData?.orders?.total?.toLocaleString() || '0'} +
+
+ Today: {analyticsData?.orders?.totalToday || 0} + +
+ + {analyticsData?.orders?.dailyOrders && ( +
+ +
+ )} +
+
+ + {/* Revenue Card */} + + +
+ Total Revenue + +
+
+ +
+ {formatCurrency(analyticsData?.revenue?.total || 0)} +
+
+ Today: {formatCurrency(analyticsData?.revenue?.today || 0)} + +
+ + {analyticsData?.revenue?.dailyRevenue && ( +
+ +
+ )} +
+
+ + {/* Vendors Card */} + + +
+ Vendors + +
+
+ +
+ {analyticsData?.vendors?.total?.toLocaleString() || '0'} +
+
+ New Today: {analyticsData?.vendors?.newToday || 0} + +
+ + {analyticsData?.vendors?.dailyGrowth && ( +
+ +
+ )} +
+
+ + {/* Products Card */} + + +
+ Products + +
+
+ +
+ {analyticsData?.products?.total?.toLocaleString() || '0'} +
+
+ New This Week: {analyticsData?.products?.recent || 0} +
+
+
+
+ + + Orders - Vendors Revenue - Promotions - Communication - Payments + Vendors + Engagement - - + + - Order Analytics - Order volume and revenue metrics + Order Trends + + Daily order volume over the selected time period + - {analyticsData && ( -
-
-
Total Orders
-
{analyticsData.orders?.total?.toLocaleString() || '0'}
-
- -
-
Orders Today
-
{analyticsData.orders?.totalToday?.toLocaleString() || '0'}
-
- -
-
Pending Orders
-
{analyticsData.orders?.pending?.toLocaleString() || '0'}
-
- -
-
Revenue
-
${analyticsData.revenue?.total?.toLocaleString() || '0'}
-
+ {analyticsData?.orders?.dailyOrders ? ( + + ) : ( +
+ No order data available
)} -
-

Order & Revenue Trends

- {analyticsData && ( -
-

Order and revenue charts would render here

-
- )} +
+
+
Total Orders
+
{analyticsData?.orders?.total?.toLocaleString() || '0'}
+
+
+
Pending Orders
+
{analyticsData?.orders?.pending?.toLocaleString() || '0'}
+
+
+
Completed Orders
+
{analyticsData?.orders?.completed?.toLocaleString() || '0'}
+
- + - Vendor Analytics - Detailed vendor metrics and store statistics + Revenue Trends + + Daily revenue over the selected time period + - {analyticsData && ( -
-
-
Total Vendors
-
{analyticsData.vendors?.total?.toLocaleString() || '0'}
-
- -
-
New Today
-
{analyticsData.vendors?.newToday?.toLocaleString() || '0'}
-
- -
-
New This Week
-
{analyticsData.vendors?.newThisWeek?.toLocaleString() || '0'}
-
- -
-
Active Stores
-
{analyticsData.vendors?.stores?.toLocaleString() || analyticsData.stores?.total?.toLocaleString() || '0'}
-
+ {analyticsData?.revenue?.dailyRevenue ? ( + + ) : ( +
+ No revenue data available
)} -
-

Vendor Growth Trend

- {analyticsData && ( -
-

Vendor growth chart would render here

-
- )} +
+
+
Total Revenue
+
{formatCurrency(analyticsData?.revenue?.total || 0)}
+
+
+
Today's Revenue
+
{formatCurrency(analyticsData?.revenue?.today || 0)}
+
+
+
This Week's Revenue
+
{formatCurrency(analyticsData?.revenue?.thisWeek || 0)}
+
- - + + - Revenue Analytics - Revenue and financial metrics + Vendor Growth + + New vendor registrations over time + - {analyticsData && ( -
-
-
Total Processed
-
${analyticsData.revenue?.total?.toLocaleString() || '0'}
-
- -
-
Today's Revenue
-
${analyticsData.revenue?.today?.toLocaleString() || '0'}
-
- -
-
This Week
-
${analyticsData.revenue?.thisWeek?.toLocaleString() || '0'}
-
- -
-
Average Per Order
-
- ${analyticsData.revenue && analyticsData.orders?.total - ? Math.round((analyticsData.revenue.total || 0) / analyticsData.orders.total).toLocaleString() - : '0'} -
-
+ {analyticsData?.vendors?.dailyGrowth ? ( + + ) : ( +
+ No vendor data available
)} -
-

Revenue Trend

- {analyticsData && ( - - )} +
+
+
Total Vendors
+
{analyticsData?.vendors?.total?.toLocaleString() || '0'}
+
+
+
New Today
+
{analyticsData?.vendors?.newToday?.toLocaleString() || '0'}
+
+
+
New This Week
+
{analyticsData?.vendors?.newThisWeek?.toLocaleString() || '0'}
+
- - -
- - - - Active Promotions - - - - -
- {analyticsData?.promotions?.active || 0} + + + + + User Engagement + + Chat and message activity + + + + {analyticsData?.engagement?.dailyMessages ? ( + + ) : ( +
+ No engagement data available
-

- out of {analyticsData?.promotions?.total || 0} total -

-
-
- - - - Total Discount Amount - - - - -
- ${analyticsData?.promotions?.totalDiscountAmount?.toLocaleString() || 0} + )} + +
+
+
Total Messages
+
{analyticsData?.engagement?.totalMessages?.toLocaleString() || '0'}
-

- {analyticsData?.promotions?.discountPercentage || 0}% of revenue -

- - - - - - Promotion Uses - - - - -
- {analyticsData?.promotions?.used || 0} +
+
Active Chats
+
{analyticsData?.engagement?.activeChats?.toLocaleString() || '0'}
- - -
- -
- - - Top Promotions - Most used promotion codes - - -
- {analyticsData?.promotions?.topPromotions?.map((promo, i) => ( -
-
-

{promo._id}

-

- {promo.count} uses · ${promo.totalDiscount.toLocaleString()} -

-
-
- ${(promo.totalDiscount / promo.count).toFixed(2)} - avg -
-
- ))} +
+
Sessions
+
{analyticsData?.sessions?.active?.toLocaleString() || '0'} / {analyticsData?.sessions?.total?.toLocaleString() || '0'}
- - - - - - Discount Types - Breakdown by discount type - - -
- {analyticsData?.promotions?.discountByType && ( -
-
-
-

Percentage Discounts

-

- {analyticsData.promotions.discountByType.percentage?.count || 0} uses -

-
-
- ${analyticsData.promotions.discountByType.percentage?.totalDiscount?.toLocaleString() || 0} -
-
-
-
-

Fixed Amount Discounts

-

- {analyticsData.promotions.discountByType.fixed?.count || 0} uses -

-
-
- ${analyticsData.promotions.discountByType.fixed?.totalDiscount?.toLocaleString() || 0} -
-
-
- )} -
-
-
-
- - - -
- - - - Total Chats - - - - -
- {analyticsData?.chats?.totalChats || 0} -
-

- {analyticsData?.chats?.activeChats || 0} active in selected period -

-
-
- - - - Total Messages - - - - -
- {analyticsData?.chats?.totalMessages?.toLocaleString() || 0} -
-

- {analyticsData?.chats?.recentMessages?.toLocaleString() || 0} in selected period -

-
-
- - - - Avg Response Time - - - - -
- {analyticsData?.chats?.avgResponseTimeMin || 0} min -
-
-
-
- -
- - - Message Volume by Hour - Chat activity distribution - - -
- {analyticsData?.chats?.messagesByHour && ( - - ({ hour, count }))}> - - - - - - - )} -
-
-
- - - - Message Breakdown - By sender type - - -
- {analyticsData?.chats?.messagesBySender && ( - - - - - - - - - - - )} -
-
-
-
- -
- - - Telegram Users - Bot user statistics - - -
-
-
-

Total Users

-
-
- {analyticsData?.telegram?.totalUsers || 0} -
-
-
-
-

Store Connections

-
-
- {analyticsData?.telegram?.totalStoreConnections || 0} -
-
-
-
-

Avg Stores per User

-
-
- {analyticsData?.telegram?.avgStoresPerUser?.toFixed(2) || 0} -
-
-
-
-

Multi-Store Users

-
-
- {analyticsData?.telegram?.multiStoreUsers || 0} -
-
-
-
-
- - - - Security - Blocked users and security metrics - - -
-
-
-

Total Blocked Users

-
-
- {analyticsData?.security?.blockedUsers?.total || 0} -
-
-
-
-

Recently Blocked

-

- In selected period -

-
-
- {analyticsData?.security?.blockedUsers?.recentlyBlocked || 0} -
-
-
-
-
-
-
- - -
- - - - Active Escrows - - - - -
- {analyticsData?.escrow?.active || 0} -
-

- out of {analyticsData?.escrow?.total || 0} total -

-
-
- - - - Released Escrows - - - - -
- {analyticsData?.escrow?.released || 0} -
-
-
- - - - Dispute Rate - - - - -
- {analyticsData?.escrow?.disputeRate?.toFixed(1) || 0}% -
-

- {analyticsData?.escrow?.disputed || 0} disputed escrows -

-
-
-
- -
- - - Escrow by Currency - Currently held amounts - - -
- {analyticsData?.escrow?.heldByCurrency && ( - <> -
-
-

Bitcoin (BTC)

-
-
- {analyticsData.escrow.heldByCurrency.btc.toFixed(4)} BTC -
-
-
-
-

Litecoin (LTC)

-
-
- {analyticsData.escrow.heldByCurrency.ltc.toFixed(2)} LTC -
-
-
-
-

Monero (XMR)

-
-
- {analyticsData.escrow.heldByCurrency.xmr.toFixed(2)} XMR -
-
- - )} -
-
-
- - - - Escrow Metrics - Release time and amounts - - -
-
-
-

Avg Release Time

-
-
- {analyticsData?.escrow?.avgReleaseTimeHours ? ( - <> - {Math.floor(analyticsData.escrow.avgReleaseTimeHours / 24)} days, {analyticsData.escrow.avgReleaseTimeHours % 24} hours - - ) : ( - "8 days, 0 hours" - )} -
-
- - {analyticsData?.escrow?.totalByCurrency && ( -
-

Total Processed (lifetime)

-
-
- BTC: - {analyticsData.escrow.totalByCurrency.btc.toFixed(4)} BTC -
-
- LTC: - {analyticsData.escrow.totalByCurrency.ltc.toFixed(2)} LTC -
-
- XMR: - {analyticsData.escrow.totalByCurrency.xmr.toFixed(2)} XMR -
-
-
- )} -
-
-
-
+
+
+