Add statcard variants

This commit is contained in:
2025-01-01 14:22:26 -05:00
parent 42f5033173
commit 9b5c59c61d
2 changed files with 106 additions and 84 deletions

View File

@@ -351,8 +351,8 @@ const MiniStatCards = ({
if (loading && !stats) { if (loading && !stats) {
return ( return (
<Card className="w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm"> <Card className="w-full bg-white dark:bg-gray-900/60 backdrop-blur-sm">
<CardContent className="p-4 pt-0"> <CardContent className="pb-2">
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-4 gap-2">
{[...Array(4)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<SkeletonCard key={i} /> <SkeletonCard key={i} />
))} ))}
@@ -387,7 +387,7 @@ const MiniStatCards = ({
description={ description={
stats?.periodProgress < 100 ? ( stats?.periodProgress < 100 ? (
<div className="flex items-center gap-1 text-sm"> <div className="flex items-center gap-1 text-sm">
<span>Proj: </span> <span>Projected: </span>
{projectionLoading ? ( {projectionLoading ? (
<Skeleton className="h-4 w-15" /> <Skeleton className="h-4 w-15" />
) : ( ) : (
@@ -398,44 +398,38 @@ const MiniStatCards = ({
</div> </div>
) : null ) : null
} }
progress={ progress={stats?.periodProgress < 100 ? stats.periodProgress : undefined}
stats?.periodProgress < 100 ? stats.periodProgress : undefined trend={projectionLoading && stats?.periodProgress < 100 ? undefined : revenueTrend?.trend}
} trendValue={revenueTrend?.value ? formatPercentage(revenueTrend.value) : null}
trend={
projectionLoading && stats?.periodProgress < 100
? undefined
: revenueTrend?.trend
}
trendValue={
revenueTrend?.value ? formatPercentage(revenueTrend.value) : null
}
colorClass="text-green-600 dark:text-green-400" colorClass="text-green-600 dark:text-green-400"
icon={DollarSign} icon={DollarSign}
iconColor="text-green-500" iconColor="text-green-500"
onDetailsClick={() => setSelectedMetric("revenue")} onDetailsClick={() => setSelectedMetric("revenue")}
isLoading={loading || !stats} isLoading={loading || !stats}
variant="mini"
background="bg-gradient-to-br from-emerald-700/90 to-green-700/90 backdrop-blur-md hover:from-emerald-900 hover:to-green-800"
/> />
<StatCard <StatCard
title="Orders" title="Orders"
value={stats?.orderCount} value={stats?.orderCount}
description={`${stats?.itemCount} items`} description={`${stats?.itemCount} total items`}
trend={orderTrend?.trend} trend={orderTrend?.trend}
trendValue={ trendValue={orderTrend?.value ? formatPercentage(orderTrend.value) : null}
orderTrend?.value ? formatPercentage(orderTrend.value) : null
}
colorClass="text-blue-600 dark:text-blue-400" colorClass="text-blue-600 dark:text-blue-400"
icon={ShoppingCart} icon={ShoppingCart}
iconColor="text-blue-500" iconColor="text-blue-500"
onDetailsClick={() => setSelectedMetric("orders")} onDetailsClick={() => setSelectedMetric("orders")}
isLoading={loading || !stats} isLoading={loading || !stats}
variant="mini"
background="bg-gradient-to-br from-blue-700/90 to-indigo-700/90 backdrop-blur-md hover:from-blue-900 hover:to-indigo-800"
/> />
<StatCard <StatCard
title="AOV" title="AOV"
value={stats?.averageOrderValue?.toFixed(2)} value={stats?.averageOrderValue?.toFixed(2)}
valuePrefix="$" valuePrefix="$"
description={`${stats?.averageItemsPerOrder?.toFixed(1)}/order`} description={`${stats?.averageItemsPerOrder?.toFixed(1)} items per order`}
trend={aovTrend?.trend} trend={aovTrend?.trend}
trendValue={aovTrend?.value ? formatPercentage(aovTrend.value) : null} trendValue={aovTrend?.value ? formatPercentage(aovTrend.value) : null}
colorClass="text-purple-600 dark:text-purple-400" colorClass="text-purple-600 dark:text-purple-400"
@@ -443,6 +437,8 @@ const MiniStatCards = ({
iconColor="text-purple-500" iconColor="text-purple-500"
onDetailsClick={() => setSelectedMetric("average_order")} onDetailsClick={() => setSelectedMetric("average_order")}
isLoading={loading || !stats} isLoading={loading || !stats}
variant="mini"
background="bg-gradient-to-br from-purple-700/90 to-violet-700/90 backdrop-blur-md hover:from-purple-900 hover:to-violet-800"
/> />
<StatCard <StatCard
@@ -454,6 +450,8 @@ const MiniStatCards = ({
iconColor="text-teal-500" iconColor="text-teal-500"
onDetailsClick={() => setSelectedMetric("shipping")} onDetailsClick={() => setSelectedMetric("shipping")}
isLoading={loading || !stats} isLoading={loading || !stats}
variant="mini"
background="bg-gradient-to-br from-teal-700/90 to-cyan-700/90 backdrop-blur-md hover:from-teal-900 hover:to-cyan-800"
/> />
</div> </div>

View File

@@ -979,28 +979,51 @@ const StatCard = ({
onDetailsClick, onDetailsClick,
isLoading = false, isLoading = false,
progress, progress,
}) => ( variant = "default",
background
}) => {
const variants = {
default: "bg-white dark:bg-gray-900/60",
mini: background || "bg-gradient-to-br from-gray-900/90 to-gray-800/90 backdrop-blur-md hover:from-gray-900 hover:to-gray-800"
};
const titleVariants = {
default: "text-sm font-medium text-gray-600 dark:text-gray-300",
mini: "text-sm font-bold text-gray-100"
};
const valueVariants = {
default: "text-2xl font-bold",
mini: "text-2xl font-extrabold text-white"
};
const descriptionVariants = {
default: "text-sm text-muted-foreground",
mini: "text-sm font-semibold text-gray-200"
};
return (
<Card <Card
className={`${className} bg-white dark:bg-gray-900/60 backdrop-blur-sm ${ className={`${className} ${variants[variant]} ${
onClick || onDetailsClick onClick || onDetailsClick
? "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors" ? "cursor-pointer transition-all hover:brightness-110"
: "" : ""
}`} }`}
onClick={onDetailsClick || onClick} onClick={onDetailsClick || onClick}
> >
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<CardTitle className="text-sm font-medium text-gray-600 dark:text-gray-300"> <CardTitle className={titleVariants[variant]}>
{title} {title}
</CardTitle> </CardTitle>
{info && ( {info && (
<Info <Info
className="w-4 h-4 text-muted-foreground cursor-help" className={`w-4 h-4 ${variant === 'mini' ? 'text-gray-300' : 'text-muted-foreground'} cursor-help`}
title={info} title={info}
/> />
)} )}
</div> </div>
{Icon && <Icon className={`h-4 w-4 ${iconColor}`} />} {Icon && <Icon className={`h-4 w-4 ${variant === 'mini' ? 'text-gray-100' : iconColor}`} />}
</CardHeader> </CardHeader>
<CardContent className="p-4 pt-0"> <CardContent className="p-4 pt-0">
{isLoading ? ( {isLoading ? (
@@ -1011,17 +1034,17 @@ const StatCard = ({
) : ( ) : (
<div className="space-y-2"> <div className="space-y-2">
<div> <div>
<div className={`text-2xl font-bold ${colorClass}`}> <div className={`${valueVariants[variant]} ${colorClass}`}>
{valuePrefix} {valuePrefix}
{value} {value}
{valueSuffix} {valueSuffix}
</div> </div>
{description && ( {description && (
<div className="text-sm text-muted-foreground mt-2 items-center gap-2 flex"> <div className={`mt-2 items-center gap-2 flex ${descriptionVariants[variant]}`}>
{description} {description}
{trend && ( {trend && (
<span <span
className={`flex items-center gap-1 ${ className={`flex items-center gap-0 ${
trend === "up" trend === "up"
? "text-emerald-600 dark:text-emerald-400" ? "text-emerald-600 dark:text-emerald-400"
: "text-rose-600 dark:text-rose-400" : "text-rose-600 dark:text-rose-400"
@@ -1045,6 +1068,7 @@ const StatCard = ({
</CardContent> </CardContent>
</Card> </Card>
); );
};
// Add this before the StatCards component // Add this before the StatCards component
const useDataCache = () => { const useDataCache = () => {