Spaces:
Configuration error
Configuration error
| // src/components/StatsGrid.tsx | |
| import React, { useContext } from 'react'; | |
| import { TrendingUp, FileText, Zap, Target } from 'lucide-react'; | |
| import { ThemeContext } from '../contexts/ThemeContext'; | |
| import { useUserStats } from '../hooks/useUserStats'; | |
| const StatsGrid: React.FC = () => { | |
| const { theme } = useContext(ThemeContext); | |
| const { stats } = useUserStats(); // Live stats from Firestore | |
| const isDark = theme === 'dark'; | |
| // --- Safely parse all stats --- | |
| const textsHumanized = Number(stats?.textsHumanized ?? 0); | |
| const wordsProcessed = Number(stats?.wordsProcessed ?? 0); | |
| const totalSessions = Number(stats?.totalSessions ?? 0); | |
| const gptAverage = Number(stats?.gptAverage ?? 0); | |
| // --- Format derived stats --- | |
| const gptAverageDisplay = `${gptAverage.toFixed(1)}%`; | |
| const statItems = [ | |
| { | |
| label: 'Texts Humanized', | |
| value: textsHumanized.toLocaleString(), | |
| icon: FileText, | |
| color: 'blue', | |
| change: '+12%', | |
| }, | |
| { | |
| label: 'Words Processed', | |
| value: wordsProcessed.toLocaleString(), | |
| icon: Zap, | |
| color: 'green', | |
| change: '+8%', | |
| }, | |
| { | |
| label: 'GPT Average', | |
| value: gptAverageDisplay, | |
| icon: Target, | |
| color: 'purple', | |
| change: '+3%', | |
| }, | |
| { | |
| label: 'Total Sessions', | |
| value: totalSessions.toLocaleString(), | |
| icon: TrendingUp, | |
| color: 'orange', | |
| change: '+15%', | |
| }, | |
| ]; | |
| const getColorClasses = (color: string) => { | |
| const colors = { | |
| blue: `bg-${isDark ? 'blue-800' : 'blue-50'} text-${ | |
| isDark ? 'blue-300' : 'blue-600' | |
| } border-${isDark ? 'blue-700' : 'blue-200'}`, | |
| green: `bg-${isDark ? 'green-800' : 'green-50'} text-${ | |
| isDark ? 'green-300' : 'green-600' | |
| } border-${isDark ? 'green-700' : 'green-200'}`, | |
| purple: `bg-${isDark ? 'purple-800' : 'purple-50'} text-${ | |
| isDark ? 'purple-300' : 'purple-600' | |
| } border-${isDark ? 'purple-700' : 'purple-200'}`, | |
| orange: `bg-${isDark ? 'orange-800' : 'orange-50'} text-${ | |
| isDark ? 'orange-300' : 'orange-600' | |
| } border-${isDark ? 'orange-700' : 'orange-200'}`, | |
| }; | |
| return colors[color as keyof typeof colors] || colors.blue; | |
| }; | |
| const cardBgClass = isDark | |
| ? 'bg-gray-900 border-gray-700 text-white' | |
| : 'bg-white border-gray-200 text-gray-900'; | |
| return ( | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> | |
| {statItems.map((item, index) => { | |
| const Icon = item.icon; | |
| return ( | |
| <div | |
| key={index} | |
| className={`rounded-xl border p-6 hover:shadow-md transition-shadow ${cardBgClass}`} | |
| > | |
| <div className="flex items-center justify-between mb-4"> | |
| <div className={`p-3 rounded-lg ${getColorClasses(item.color)}`}> | |
| <Icon className="w-6 h-6" /> | |
| </div> | |
| <span | |
| className={`text-sm font-medium px-2 py-1 rounded ${ | |
| isDark | |
| ? 'bg-gray-800 text-green-400' | |
| : 'bg-green-50 text-green-600' | |
| }`} | |
| > | |
| {item.change} | |
| </span> | |
| </div> | |
| <div> | |
| <p | |
| className={`text-2xl font-bold mb-1 ${ | |
| isDark ? 'text-white' : 'text-gray-900' | |
| }`} | |
| > | |
| {item.value || '0'} | |
| </p> | |
| <p | |
| className={`text-sm ${ | |
| isDark ? 'text-gray-300' : 'text-gray-600' | |
| }`} | |
| > | |
| {item.label} | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| }; | |
| export default StatsGrid; | |