Revamp analytics dashboard UI and charts
All checks were successful
Build Frontend / build (push) Successful in 1m11s
All checks were successful
Build Frontend / build (push) Successful in 1m11s
Enhanced the AnalyticsDashboard layout with a premium glassmorphism UI, improved toolbar, and reorganized tabs for better clarity. MetricsCard now features dynamic color coding and trend badges. PredictionsChart received scenario simulation UI upgrades, disabled future ranges based on available history, and improved chart tooltips and visuals. ProfitAnalyticsChart added error handling for product images and minor UI refinements. Updated globals.css with new premium utility classes and improved dark mode color variables.
This commit is contained in:
@@ -4,14 +4,15 @@ import { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import {
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
DollarSign,
|
||||
import {
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
DollarSign,
|
||||
PieChart,
|
||||
Calculator,
|
||||
Info,
|
||||
AlertTriangle
|
||||
AlertTriangle,
|
||||
Package
|
||||
} from "lucide-react";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { formatGBP } from "@/utils/format";
|
||||
@@ -28,6 +29,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
|
||||
const [data, setData] = useState<ProfitOverview | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [imageErrors, setImageErrors] = useState<Record<string, boolean>>({});
|
||||
const { toast } = useToast();
|
||||
|
||||
const maskValue = (value: string): string => {
|
||||
@@ -93,7 +95,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Coverage Card Skeleton */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -110,7 +112,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
{/* Products List Skeleton */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
@@ -188,7 +190,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
|
||||
}
|
||||
|
||||
const profitDirection = data.summary.totalProfit >= 0;
|
||||
|
||||
|
||||
// Fallback for backwards compatibility
|
||||
const revenueFromTracked = data.summary.revenueFromTrackedProducts || data.summary.totalRevenue || 0;
|
||||
const totalRevenue = data.summary.totalRevenue || 0;
|
||||
@@ -237,9 +239,8 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
|
||||
<CardTitle className="text-sm font-medium">Total Profit</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className={`text-2xl font-bold flex items-center gap-2 ${
|
||||
profitDirection ? 'text-green-600' : 'text-red-600'
|
||||
}`}>
|
||||
<div className={`text-2xl font-bold flex items-center gap-2 ${profitDirection ? 'text-green-600' : 'text-red-600'
|
||||
}`}>
|
||||
{profitDirection ? (
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
) : (
|
||||
@@ -286,7 +287,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="w-full bg-secondary rounded-full h-2">
|
||||
<div
|
||||
<div
|
||||
className="bg-primary h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: hideNumbers ? "0%" : `${data.summary.costDataCoverage || 0}%` }}
|
||||
/>
|
||||
@@ -307,7 +308,7 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
|
||||
Most Profitable Products
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{dateRange
|
||||
{dateRange
|
||||
? `Products generating the highest total profit (${new Date(dateRange.from).toLocaleDateString()} - ${new Date(dateRange.to).toLocaleDateString()})`
|
||||
: `Products generating the highest total profit (last ${timeRange || '30'} days)`
|
||||
}
|
||||
@@ -323,24 +324,43 @@ export default function ProfitAnalyticsChart({ timeRange, dateRange, hideNumbers
|
||||
<div className="space-y-4">
|
||||
{data.topProfitableProducts.map((product, index) => {
|
||||
const profitPositive = product.totalProfit >= 0;
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
key={product.productId}
|
||||
className="flex items-center justify-between p-4 border rounded-lg"
|
||||
className="flex items-center justify-between p-4 border rounded-lg transition-colors hover:bg-muted/30"
|
||||
>
|
||||
<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 className="flex items-center gap-4">
|
||||
<div className="relative flex-shrink-0">
|
||||
<div className="w-12 h-12 rounded-full overflow-hidden border-2 border-background shadow-sm bg-muted flex items-center justify-center">
|
||||
{product.image && !imageErrors[product.productId] ? (
|
||||
<img
|
||||
src={`/api/products/${product.productId}/image`}
|
||||
alt={product.productName}
|
||||
className="w-full h-full object-cover"
|
||||
onError={() => {
|
||||
setImageErrors(prev => ({ ...prev, [product.productId]: true }));
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center justify-center w-full h-full bg-primary/10 text-primary font-bold text-lg">
|
||||
{product.productName.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute -top-1 -left-1 w-5 h-5 bg-primary text-[10px] text-primary-foreground flex items-center justify-center rounded-full font-bold border-2 border-background shadow-sm">
|
||||
{index + 1}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{product.productName}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="font-semibold">{product.productName}</p>
|
||||
<p className="text-sm text-muted-foreground flex items-center gap-1">
|
||||
<Package className="h-3 w-3" />
|
||||
{product.totalQuantitySold} units sold
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="text-right">
|
||||
<div className={`font-medium ${profitPositive ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{maskValue(formatGBP(product.totalProfit))}
|
||||
|
||||
Reference in New Issue
Block a user