puffin-app/components/admin/OrderStatsCards.tsx
Matt 4b408986e5
All checks were successful
Build and Push Docker Images / docker (push) Successful in 2m25s
Add complete admin portal implementation with orders management
- Fully implemented OrdersTable with sorting, pagination, and filtering
- Added OrderFilters component for search, status, and date range filtering
- Created OrderStatsCards for dashboard metrics display
- Built OrderDetailsModal for viewing complete order information
- Implemented ExportButton for CSV export functionality
- Updated dashboard and orders pages to use new components
- Enhanced OrderRecord type definitions in src/types.ts
- All components working with NocoDB API integration
2025-11-03 22:24:17 +01:00

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>
);
}