Improve admin analytics and user management UI
Refactored admin users page to use client-side fetching, loading states, and search functionality. Enhanced AdminAnalytics with a best month (YTD) card and removed debug logging. Improved SystemStatusCard formatting and removed console logs. Fixed profit chart period selection logic. Minor formatting fix in nav-item component.
This commit is contained in:
@@ -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<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)
|
||||
});
|
||||
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<string, { revenue: number; orders: number }>();
|
||||
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
{errorMessage && (
|
||||
@@ -350,6 +379,30 @@ export default function AdminAnalytics() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Best Month Card (only show for YTD) */}
|
||||
{bestMonth && (
|
||||
<Card className="border-green-500/50 bg-green-500/5">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-full bg-green-500/20">
|
||||
<Trophy className="h-5 w-5 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-muted-foreground">Best Month (YTD)</div>
|
||||
<div className="text-xl font-bold text-green-600">{bestMonth.month}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-sm font-medium text-muted-foreground">Revenue</div>
|
||||
<div className="text-xl font-bold">{formatCurrency(bestMonth.revenue)}</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">{bestMonth.orders.toLocaleString()} orders</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -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<Status>("/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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -65,5 +65,4 @@ export const NavItem: React.FC<NavItemProps> = ({ href, icon: Icon, children, on
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user