Add statcard variants
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user