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.
115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
"use client"
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
|
|
import { LucideIcon } from "lucide-react";
|
|
import { motion } from "framer-motion";
|
|
|
|
interface MetricsCardProps {
|
|
title: string;
|
|
value: string;
|
|
description: string;
|
|
icon: LucideIcon;
|
|
trend: "up" | "down" | "neutral";
|
|
trendValue: string;
|
|
}
|
|
|
|
export default function MetricsCard({
|
|
title,
|
|
value,
|
|
description,
|
|
icon: Icon,
|
|
trend,
|
|
trendValue
|
|
}: MetricsCardProps) {
|
|
const getTrendIcon = () => {
|
|
switch (trend) {
|
|
case "up":
|
|
return <TrendingUp className="h-4 w-4 text-emerald-500" />;
|
|
case "down":
|
|
return <TrendingDown className="h-4 w-4 text-rose-500" />;
|
|
default:
|
|
return <Minus className="h-4 w-4 text-muted-foreground" />;
|
|
}
|
|
};
|
|
|
|
const getTrendColor = () => {
|
|
switch (trend) {
|
|
case "up":
|
|
return "text-emerald-400 bg-emerald-500/10 border-emerald-500/10";
|
|
case "down":
|
|
return "text-rose-400 bg-rose-500/10 border-rose-500/10";
|
|
default:
|
|
return "text-blue-400 bg-blue-500/10 border-blue-500/10";
|
|
}
|
|
};
|
|
|
|
const getCategoryColor = () => {
|
|
const t = title.toLowerCase();
|
|
if (t.includes("revenue") || t.includes("profit")) return "amber";
|
|
if (t.includes("order")) return "blue";
|
|
if (t.includes("customer")) return "indigo";
|
|
if (t.includes("product") || t.includes("inventory")) return "purple";
|
|
return "primary";
|
|
}
|
|
|
|
const categoryColor = getCategoryColor();
|
|
|
|
const getIconContainerColor = () => {
|
|
switch (categoryColor) {
|
|
case "amber": return "bg-amber-500/15 text-amber-500 border-amber-500/20";
|
|
case "blue": return "bg-blue-500/15 text-blue-500 border-blue-500/20";
|
|
case "indigo": return "bg-indigo-500/15 text-indigo-500 border-indigo-500/20";
|
|
case "purple": return "bg-purple-500/15 text-purple-500 border-purple-500/20";
|
|
default: return "bg-primary/15 text-primary border-primary/20";
|
|
}
|
|
}
|
|
|
|
const getBadgeColor = () => {
|
|
switch (categoryColor) {
|
|
case "amber": return "bg-amber-500/10 text-amber-400/80 border-amber-500/20";
|
|
case "blue": return "bg-blue-500/10 text-blue-400/80 border-blue-500/20";
|
|
case "indigo": return "bg-indigo-500/10 text-indigo-400/80 border-indigo-500/20";
|
|
case "purple": return "bg-purple-500/10 text-purple-400/80 border-purple-500/20";
|
|
default: return "bg-primary/10 text-primary/60 border-primary/20";
|
|
}
|
|
}
|
|
|
|
return (
|
|
<motion.div
|
|
whileHover={{ y: -4 }}
|
|
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
|
>
|
|
<Card className="glass-morphism premium-card relative overflow-hidden group border-white/5">
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4 relative z-10">
|
|
<CardTitle className="text-[10px] font-bold tracking-wider text-white/40 uppercase">
|
|
{title}
|
|
</CardTitle>
|
|
<div className={`p-2.5 rounded-2xl border ${getIconContainerColor()} transition-all duration-300 group-hover:scale-105`}>
|
|
<Icon className="h-4 w-4" />
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<CardContent className="relative z-10">
|
|
<div className="space-y-1.5">
|
|
<div className="text-3xl font-bold text-white">
|
|
{value}
|
|
</div>
|
|
<p className="text-[11px] text-white/40">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 mt-6 pt-5">
|
|
<div className={`flex items-center gap-1.5 px-3 py-1 rounded-full border ${getTrendColor()} transition-all duration-300`}>
|
|
{getTrendIcon()}
|
|
<span className="text-[10px] font-bold uppercase tracking-wide">
|
|
{trend === "up" ? "+" : ""}{trendValue}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
);
|
|
} |