97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { motion } from 'framer-motion';
|
||
|
|
import { Package, DollarSign, Leaf, TrendingUp } from 'lucide-react';
|
||
|
|
|
||
|
|
interface OrderStats {
|
||
|
|
totalOrders: number;
|
||
|
|
totalRevenue: number;
|
||
|
|
totalCO2Offset: number;
|
||
|
|
fulfillmentRate: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface OrderStatsCardsProps {
|
||
|
|
stats: OrderStats | null;
|
||
|
|
isLoading?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function OrderStatsCards({ stats, isLoading = false }: OrderStatsCardsProps) {
|
||
|
|
const statCards = stats
|
||
|
|
? [
|
||
|
|
{
|
||
|
|
title: 'Total Orders',
|
||
|
|
value: stats.totalOrders.toLocaleString(),
|
||
|
|
icon: <Package size={24} />,
|
||
|
|
gradient: 'bg-gradient-to-br from-royal-purple to-purple-600',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: 'Total Revenue',
|
||
|
|
value: `$${(stats.totalRevenue / 100).toLocaleString('en-US', {
|
||
|
|
minimumFractionDigits: 2,
|
||
|
|
maximumFractionDigits: 2,
|
||
|
|
})}`,
|
||
|
|
icon: <DollarSign size={24} />,
|
||
|
|
gradient: 'bg-gradient-to-br from-muted-gold to-orange-600',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: 'Total CO₂ Offset',
|
||
|
|
value: `${(typeof stats.totalCO2Offset === 'number' ? stats.totalCO2Offset : parseFloat(stats.totalCO2Offset || '0')).toFixed(2)} tons`,
|
||
|
|
icon: <Leaf size={24} />,
|
||
|
|
gradient: 'bg-gradient-to-br from-sea-green to-green-600',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: 'Fulfillment Rate',
|
||
|
|
value: `${(typeof stats.fulfillmentRate === 'number' ? stats.fulfillmentRate : parseFloat(stats.fulfillmentRate || '0')).toFixed(1)}%`,
|
||
|
|
icon: <TrendingUp size={24} />,
|
||
|
|
gradient: 'bg-gradient-to-br from-maritime-teal to-teal-600',
|
||
|
|
},
|
||
|
|
]
|
||
|
|
: [];
|
||
|
|
|
||
|
|
if (isLoading) {
|
||
|
|
return (
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||
|
|
{[...Array(4)].map((_, index) => (
|
||
|
|
<div
|
||
|
|
key={index}
|
||
|
|
className="bg-white border border-light-gray-border rounded-xl p-6 animate-pulse"
|
||
|
|
>
|
||
|
|
<div className="flex items-center justify-between mb-4">
|
||
|
|
<div className="w-12 h-12 rounded-lg bg-gray-200"></div>
|
||
|
|
</div>
|
||
|
|
<div className="h-4 bg-gray-200 rounded w-24 mb-2"></div>
|
||
|
|
<div className="h-8 bg-gray-200 rounded w-32"></div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||
|
|
{statCards.map((stat, index) => (
|
||
|
|
<motion.div
|
||
|
|
key={stat.title}
|
||
|
|
initial={{ opacity: 0, y: 20 }}
|
||
|
|
animate={{ opacity: 1, y: 0 }}
|
||
|
|
transition={{ delay: index * 0.1 }}
|
||
|
|
className="bg-white border border-light-gray-border rounded-xl p-6 hover:shadow-lg transition-shadow"
|
||
|
|
>
|
||
|
|
<div className="flex items-center justify-between mb-4">
|
||
|
|
<div
|
||
|
|
className={
|
||
|
|
stat.gradient +
|
||
|
|
' w-12 h-12 rounded-lg flex items-center justify-center text-white shadow-md'
|
||
|
|
}
|
||
|
|
>
|
||
|
|
{stat.icon}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<h3 className="text-sm font-medium text-deep-sea-blue/60 mb-1">{stat.title}</h3>
|
||
|
|
<p className="text-2xl font-bold text-deep-sea-blue">{stat.value}</p>
|
||
|
|
</motion.div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|