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