diff --git a/app/dashboard/admin/users/page.tsx b/app/dashboard/admin/users/page.tsx index a313f77..db4e3a5 100644 --- a/app/dashboard/admin/users/page.tsx +++ b/app/dashboard/admin/users/page.tsx @@ -1,12 +1,15 @@ -import React from "react"; +"use client"; + +import React, { useState, useEffect } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -import { Search, Ban, UserCheck, UserX, Package, DollarSign } from "lucide-react"; -import { fetchServer } from "@/lib/api"; +import { Search, Ban, UserCheck, Package, DollarSign, Loader2 } from "lucide-react"; +import { fetchClient } from "@/lib/api-client"; +import { useToast } from "@/hooks/use-toast"; interface TelegramUser { telegramUserId: string; @@ -29,34 +32,41 @@ function formatCurrency(amount: number): string { }).format(amount); } -export default async function AdminUsersPage() { - let users: TelegramUser[] = []; - let error: string | null = null; +export default function AdminUsersPage() { + const { toast } = useToast(); + const [loading, setLoading] = useState(true); + const [users, setUsers] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); - try { - users = await fetchServer("/admin/users"); - } catch (err) { - console.error("Failed to fetch users:", err); - error = "Failed to load users"; - } + useEffect(() => { + fetchUsers(); + }, []); - if (error) { + const fetchUsers = async () => { + try { + setLoading(true); + const data = await fetchClient("/admin/users"); + setUsers(data); + } catch (error: any) { + console.error("Failed to fetch users:", error); + toast({ + title: "Error", + description: error.message || "Failed to load users", + variant: "destructive", + }); + } finally { + setLoading(false); + } + }; + + const filteredUsers = users.filter((user) => { + if (!searchQuery) return true; + const query = searchQuery.toLowerCase(); return ( -
-
-

Telegram Users

-

Manage Telegram user accounts

-
- - -
-

{error}

-
-
-
-
+ user.telegramUserId.toLowerCase().includes(query) || + user.telegramUsername.toLowerCase().includes(query) ); - } + }); const usersWithOrders = users.filter(u => u.totalOrders > 0); const blockedUsers = users.filter(u => u.isBlocked); @@ -77,8 +87,16 @@ export default async function AdminUsersPage() { Total Users -
{users.length}
-

Registered users

+ {loading ? ( +
+ +
+ ) : ( + <> +
{users.length}
+

Registered users

+ + )}
@@ -86,10 +104,18 @@ export default async function AdminUsersPage() { Users with Orders -
{usersWithOrders.length}
-

- {users.length > 0 ? Math.round((usersWithOrders.length / users.length) * 100) : 0}% conversion rate -

+ {loading ? ( +
+ +
+ ) : ( + <> +
{usersWithOrders.length}
+

+ {users.length > 0 ? Math.round((usersWithOrders.length / users.length) * 100) : 0}% conversion rate +

+ + )}
@@ -97,8 +123,16 @@ export default async function AdminUsersPage() { Total Revenue -
{formatCurrency(totalSpent)}
-

{totalOrders} total orders

+ {loading ? ( +
+ +
+ ) : ( + <> +
{formatCurrency(totalSpent)}
+

{totalOrders} total orders

+ + )}
@@ -106,10 +140,18 @@ export default async function AdminUsersPage() { Blocked Users -
{blockedUsers.length}
-

- {users.length > 0 ? Math.round((blockedUsers.length / users.length) * 100) : 0}% blocked rate -

+ {loading ? ( +
+ +
+ ) : ( + <> +
{blockedUsers.length}
+

+ {users.length > 0 ? Math.round((blockedUsers.length / users.length) * 100) : 0}% blocked rate +

+ + )}
@@ -125,106 +167,120 @@ export default async function AdminUsersPage() {
- + setSearchQuery(e.target.value)} + />
- - - - User ID - Username - Orders - Total Spent - Status - First Order - Last Order - Actions - - - - {users.map((user) => ( - - -
{user.telegramUserId}
-
- -
- {user.telegramUsername !== "Unknown" ? `@${user.telegramUsername}` : "Unknown"} -
-
- -
- - {user.totalOrders} - {user.completedOrders > 0 && ( - - {user.completedOrders} completed - - )} -
-
- -
- - {formatCurrency(user.totalSpent)} -
-
- - {user.isBlocked ? ( - - - - - - Blocked - - - {user.blockedReason && ( - -

{user.blockedReason}

-
- )} -
-
- ) : user.totalOrders > 0 ? ( - Active - ) : ( - No Orders - )} -
- - {user.firstOrderDate - ? new Date(user.firstOrderDate).toLocaleDateString() - : 'N/A'} - - - {user.lastOrderDate - ? new Date(user.lastOrderDate).toLocaleDateString() - : 'N/A'} - - -
- {!user.isBlocked ? ( - - ) : ( - - )} -
-
+ {loading ? ( +
+ +
+ ) : filteredUsers.length === 0 ? ( +
+ {searchQuery ? "No users found matching your search" : "No users found"} +
+ ) : ( +
+ + + User ID + Username + Orders + Total Spent + Status + First Order + Last Order + Actions - ))} - -
+ + + {filteredUsers.map((user) => ( + + +
{user.telegramUserId}
+
+ +
+ {user.telegramUsername !== "Unknown" ? `@${user.telegramUsername}` : "Unknown"} +
+
+ +
+ + {user.totalOrders} + {user.completedOrders > 0 && ( + + {user.completedOrders} completed + + )} +
+
+ +
+ + {formatCurrency(user.totalSpent)} +
+
+ + {user.isBlocked ? ( + + + + + + Blocked + + + {user.blockedReason && ( + +

{user.blockedReason}

+
+ )} +
+
+ ) : user.totalOrders > 0 ? ( + Active + ) : ( + No Orders + )} +
+ + {user.firstOrderDate + ? new Date(user.firstOrderDate).toLocaleDateString() + : 'N/A'} + + + {user.lastOrderDate + ? new Date(user.lastOrderDate).toLocaleDateString() + : 'N/A'} + + +
+ {!user.isBlocked ? ( + + ) : ( + + )} +
+
+
+ ))} +
+ + )}
); } - diff --git a/components/admin/AdminAnalytics.tsx b/components/admin/AdminAnalytics.tsx index d1d99a8..d9af35b 100644 --- a/components/admin/AdminAnalytics.tsx +++ b/components/admin/AdminAnalytics.tsx @@ -6,7 +6,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { AlertCircle, BarChart, RefreshCw, Users, ShoppingCart, - TrendingUp, TrendingDown, DollarSign, Package } from "lucide-react"; + TrendingUp, TrendingDown, DollarSign, Package, Trophy } 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'; @@ -78,28 +78,6 @@ export default function AdminAnalytics() { setErrorMessage(null); const data = await fetchClient(`/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) - }); - 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); @@ -239,6 +217,57 @@ export default function AdminAnalytics() { }).format(value); }; + // Calculate best month from daily data (for YTD view) + const calculateBestMonth = () => { + if (dateRange !== 'ytd' || !analyticsData?.revenue?.dailyRevenue || analyticsData.revenue.dailyRevenue.length === 0) { + return null; + } + + // Group daily revenue by month + const monthlyTotals = new Map(); + + analyticsData.revenue.dailyRevenue.forEach(day => { + const date = new Date(day.date); + const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const current = monthlyTotals.get(monthKey) || { revenue: 0, orders: 0 }; + monthlyTotals.set(monthKey, { + revenue: current.revenue + (day.amount || 0), + orders: current.orders + }); + }); + + // Also group orders by month + if (analyticsData.orders?.dailyOrders) { + analyticsData.orders.dailyOrders.forEach(day => { + const date = new Date(day.date); + const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + const current = monthlyTotals.get(monthKey) || { revenue: 0, orders: 0 }; + monthlyTotals.set(monthKey, { + revenue: current.revenue, + orders: current.orders + (day.count || 0) + }); + }); + } + + // Find the month with highest revenue + let bestMonth: { month: string; revenue: number; orders: number } | null = null; + monthlyTotals.forEach((totals, monthKey) => { + if (!bestMonth || totals.revenue > bestMonth.revenue) { + const [year, month] = monthKey.split('-'); + const monthName = new Date(parseInt(year), parseInt(month) - 1, 1).toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }); + bestMonth = { + month: monthName, + revenue: totals.revenue, + orders: totals.orders + }; + } + }); + + return bestMonth; + }; + + const bestMonth = calculateBestMonth(); + return (
{errorMessage && ( @@ -350,6 +379,30 @@ export default function AdminAnalytics() { )} + {/* Best Month Card (only show for YTD) */} + {bestMonth && ( + + +
+
+
+ +
+
+
Best Month (YTD)
+
{bestMonth.month}
+
+
+
+
Revenue
+
{formatCurrency(bestMonth.revenue)}
+
{bestMonth.orders.toLocaleString()} orders
+
+
+
+
+ )} +
{/* Orders Card */} diff --git a/components/admin/SystemStatusCard.tsx b/components/admin/SystemStatusCard.tsx index f0f428e..f035127 100644 --- a/components/admin/SystemStatusCard.tsx +++ b/components/admin/SystemStatusCard.tsx @@ -20,10 +20,13 @@ function formatDuration(seconds: number) { function formatBytes(bytes: number): string { if (bytes === 0) return '0 Bytes'; + if (bytes < 0) return '0 Bytes'; // Handle negative values const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; + const i = Math.max(0, Math.floor(Math.log(bytes) / Math.log(k))); // Ensure i is at least 0 + // Clamp i to valid array index + const clampedI = Math.min(i, sizes.length - 1); + return Math.round(bytes / Math.pow(k, clampedI) * 100) / 100 + ' ' + sizes[clampedI]; } export default function SystemStatusCard() { @@ -36,7 +39,6 @@ export default function SystemStatusCard() { (async () => { try { const res = await fetchClient("/admin/system-status"); - console.log(`Here is your mother fuckin data: ${JSON.stringify(res)}`); if (mounted) setData(res); } catch (e: any) { if (mounted) setError(e?.message || "Failed to load status"); diff --git a/components/analytics/ProfitAnalyticsChart.tsx b/components/analytics/ProfitAnalyticsChart.tsx index cc7fc29..9331d57 100644 --- a/components/analytics/ProfitAnalyticsChart.tsx +++ b/components/analytics/ProfitAnalyticsChart.tsx @@ -45,8 +45,9 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers try { setIsLoading(true); setError(null); - // Use dateRange if provided, otherwise fall back to timeRange - const response = await getProfitOverview(dateRange || timeRange || '30'); + // Use dateRange if provided, otherwise fall back to timeRange, otherwise default to '30' + const periodOrRange = dateRange || (timeRange ? timeRange : '30'); + const response = await getProfitOverview(periodOrRange); setData(response); } catch (error) { console.error('Error fetching profit data:', error); diff --git a/components/layout/nav-item.tsx b/components/layout/nav-item.tsx index b86672a..553be3e 100644 --- a/components/layout/nav-item.tsx +++ b/components/layout/nav-item.tsx @@ -65,5 +65,4 @@ export const NavItem: React.FC = ({ href, icon: Icon, children, on {children} ) -} - +} \ No newline at end of file diff --git a/public/git-info.json b/public/git-info.json index 4d0a41b..17b8e70 100644 --- a/public/git-info.json +++ b/public/git-info.json @@ -1,4 +1,4 @@ { - "commitHash": "1001911", - "buildTime": "2025-11-28T20:06:53.158Z" + "commitHash": "4ef0fd1", + "buildTime": "2025-11-30T15:39:38.047Z" } \ No newline at end of file