Spaces:
Runtime error
Runtime error
| import React from 'react'; | |
| import { TrendingUp, Award, Target, Calendar, BookOpen, Clock, Star, CheckCircle } from 'lucide-react'; | |
| import { ProgressData } from '../types'; | |
| interface ProgressSectionProps { | |
| progressData: ProgressData; | |
| } | |
| const ProgressSection: React.FC<ProgressSectionProps> = ({ progressData }) => { | |
| const stats = [ | |
| { | |
| label: 'Study Streak', | |
| value: `${progressData.studyStreak} days`, | |
| icon: Calendar, | |
| color: 'text-primary-600', | |
| bgColor: 'bg-primary-50' | |
| }, | |
| { | |
| label: 'Topics Completed', | |
| value: progressData.topicsCompleted.toString(), | |
| icon: CheckCircle, | |
| color: 'text-success-600', | |
| bgColor: 'bg-success-50' | |
| }, | |
| { | |
| label: 'Study Time', | |
| value: `${progressData.totalStudyTime} hours`, | |
| icon: Clock, | |
| color: 'text-secondary-600', | |
| bgColor: 'bg-secondary-50' | |
| }, | |
| { | |
| label: 'Achievements', | |
| value: progressData.achievements.toString(), | |
| icon: Award, | |
| color: 'text-accent-600', | |
| bgColor: 'bg-accent-50' | |
| } | |
| ]; | |
| const getIconComponent = (iconName: string) => { | |
| const icons: { [key: string]: React.ComponentType<any> } = { | |
| CheckCircle, | |
| BookOpen, | |
| Award, | |
| Calendar, | |
| Clock | |
| }; | |
| return icons[iconName] || BookOpen; | |
| }; | |
| const achievements = [ | |
| { | |
| title: 'First Steps', | |
| description: 'Complete your first study topic', | |
| earned: progressData.topicsCompleted >= 1, | |
| icon: 'π―' | |
| }, | |
| { | |
| title: 'Week Warrior', | |
| description: 'Study for 7 consecutive days', | |
| earned: progressData.studyStreak >= 7, | |
| icon: 'π₯' | |
| }, | |
| { | |
| title: 'Knowledge Seeker', | |
| description: 'Complete 10 study topics', | |
| earned: progressData.topicsCompleted >= 10, | |
| icon: 'π' | |
| }, | |
| { | |
| title: 'Time Master', | |
| description: 'Study for 25 hours total', | |
| earned: progressData.totalStudyTime >= 25, | |
| icon: 'β°' | |
| }, | |
| { | |
| title: 'Dedicated Learner', | |
| description: 'Complete 50 study topics', | |
| earned: progressData.topicsCompleted >= 50, | |
| icon: 'π' | |
| }, | |
| { | |
| title: 'Explorer', | |
| description: 'Study for 30 consecutive days', | |
| earned: progressData.studyStreak >= 30, | |
| icon: 'π' | |
| } | |
| ]; | |
| const generateCalendarData = () => { | |
| const today = new Date(); | |
| const startDate = new Date(today); | |
| startDate.setDate(today.getDate() - 34); // Show last 35 days | |
| const calendarData = []; | |
| for (let i = 0; i < 35; i++) { | |
| const date = new Date(startDate); | |
| date.setDate(startDate.getDate() + i); | |
| // Simulate activity based on study streak | |
| const daysSinceToday = Math.floor((today.getTime() - date.getTime()) / (1000 * 60 * 60 * 24)); | |
| let intensity = 0; | |
| if (daysSinceToday <= progressData.studyStreak) { | |
| intensity = Math.random() * 0.8 + 0.2; // Active days | |
| } else { | |
| intensity = Math.random() * 0.3; // Less active days | |
| } | |
| calendarData.push({ | |
| date: date.toISOString().split('T')[0], | |
| intensity, | |
| isToday: daysSinceToday === 0 | |
| }); | |
| } | |
| return calendarData; | |
| }; | |
| const calendarData = generateCalendarData(); | |
| return ( | |
| <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
| <div className="mb-8"> | |
| <h1 className="text-3xl font-bold text-gray-900 mb-2">Your Progress</h1> | |
| <p className="text-gray-600">Track your learning journey and achievements</p> | |
| </div> | |
| {/* Stats Overview */} | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> | |
| {stats.map((stat) => { | |
| const Icon = stat.icon; | |
| return ( | |
| <div key={stat.label} className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <p className="text-sm font-medium text-gray-600">{stat.label}</p> | |
| <p className="text-2xl font-bold text-gray-900 mt-1">{stat.value}</p> | |
| </div> | |
| <div className={`w-12 h-12 ${stat.bgColor} rounded-xl flex items-center justify-center`}> | |
| <Icon className={`w-6 h-6 ${stat.color}`} /> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
| {/* Weekly Goal */} | |
| <div className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h2 className="text-xl font-semibold text-gray-900">Weekly Goal</h2> | |
| <Target className="w-6 h-6 text-primary-600" /> | |
| </div> | |
| <div className="mb-4"> | |
| <div className="flex items-center justify-between text-sm text-gray-600 mb-2"> | |
| <span>Study Time</span> | |
| <span>{progressData.weeklyGoal.current} / {progressData.weeklyGoal.target} hours</span> | |
| </div> | |
| <div className="w-full bg-gray-200 rounded-full h-3"> | |
| <div | |
| className="bg-gradient-to-r from-primary-500 to-secondary-500 h-3 rounded-full transition-all duration-500" | |
| style={{ width: `${Math.min((progressData.weeklyGoal.current / progressData.weeklyGoal.target) * 100, 100)}%` }} | |
| /> | |
| </div> | |
| </div> | |
| <p className="text-sm text-gray-600"> | |
| You're {Math.round(Math.min((progressData.weeklyGoal.current / progressData.weeklyGoal.target) * 100, 100))}% of the way to your weekly goal! | |
| </p> | |
| </div> | |
| {/* Recent Activity */} | |
| <div className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h2 className="text-xl font-semibold text-gray-900">Recent Activity</h2> | |
| <TrendingUp className="w-6 h-6 text-primary-600" /> | |
| </div> | |
| <div className="space-y-4"> | |
| {progressData.recentActivity.length > 0 ? ( | |
| progressData.recentActivity.slice(0, 5).map((activity, index) => { | |
| const Icon = getIconComponent(activity.icon); | |
| return ( | |
| <div key={index} className="flex items-start space-x-3"> | |
| <div className="flex-shrink-0"> | |
| <Icon className={`w-5 h-5 ${activity.color}`} /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <p className="text-sm font-medium text-gray-900">{activity.title}</p> | |
| <p className="text-sm text-gray-600">{activity.course}</p> | |
| <p className="text-xs text-gray-500 mt-1">{activity.time}</p> | |
| </div> | |
| </div> | |
| ); | |
| }) | |
| ) : ( | |
| <div className="text-center py-8"> | |
| <BookOpen className="w-12 h-12 text-gray-300 mx-auto mb-3" /> | |
| <p className="text-gray-500">No recent activity</p> | |
| <p className="text-sm text-gray-400">Start studying to see your progress here!</p> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Achievements */} | |
| <div className="mt-8"> | |
| <div className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm"> | |
| <div className="flex items-center justify-between mb-6"> | |
| <h2 className="text-xl font-semibold text-gray-900">Achievements</h2> | |
| <Award className="w-6 h-6 text-accent-600" /> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> | |
| {achievements.map((achievement, index) => ( | |
| <div | |
| key={index} | |
| className={`p-4 rounded-xl border-2 transition-all duration-200 ${ | |
| achievement.earned | |
| ? 'border-success-200 bg-success-50' | |
| : 'border-gray-200 bg-gray-50 opacity-60' | |
| }`} | |
| > | |
| <div className="flex items-start space-x-3"> | |
| <div className="text-2xl">{achievement.icon}</div> | |
| <div className="flex-1"> | |
| <h3 className={`font-medium ${achievement.earned ? 'text-success-900' : 'text-gray-700'}`}> | |
| {achievement.title} | |
| </h3> | |
| <p className={`text-sm ${achievement.earned ? 'text-success-700' : 'text-gray-600'}`}> | |
| {achievement.description} | |
| </p> | |
| </div> | |
| {achievement.earned && ( | |
| <CheckCircle className="w-5 h-5 text-success-600 flex-shrink-0" /> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| {/* Study Calendar Heatmap */} | |
| <div className="mt-8"> | |
| <div className="bg-white rounded-2xl p-6 border border-gray-200 shadow-sm"> | |
| <h2 className="text-xl font-semibold text-gray-900 mb-6">Study Activity</h2> | |
| <div className="mb-4"> | |
| <div className="grid grid-cols-7 gap-1 text-xs text-gray-500 mb-2"> | |
| <div>Sun</div> | |
| <div>Mon</div> | |
| <div>Tue</div> | |
| <div>Wed</div> | |
| <div>Thu</div> | |
| <div>Fri</div> | |
| <div>Sat</div> | |
| </div> | |
| <div className="grid grid-cols-7 gap-1"> | |
| {calendarData.map((day, i) => ( | |
| <div | |
| key={i} | |
| className={`w-8 h-8 rounded-sm transition-colors ${ | |
| day.isToday | |
| ? 'ring-2 ring-primary-500' | |
| : '' | |
| } ${ | |
| day.intensity > 0.7 | |
| ? 'bg-primary-500' | |
| : day.intensity > 0.4 | |
| ? 'bg-primary-300' | |
| : day.intensity > 0.2 | |
| ? 'bg-primary-100' | |
| : 'bg-gray-100' | |
| }`} | |
| title={`${day.date}: Activity level ${Math.round(day.intensity * 100)}%`} | |
| /> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="flex items-center justify-between text-sm text-gray-600"> | |
| <span>Less active</span> | |
| <div className="flex items-center space-x-1"> | |
| <div className="w-3 h-3 bg-gray-100 rounded-sm" /> | |
| <div className="w-3 h-3 bg-primary-100 rounded-sm" /> | |
| <div className="w-3 h-3 bg-primary-300 rounded-sm" /> | |
| <div className="w-3 h-3 bg-primary-500 rounded-sm" /> | |
| </div> | |
| <span>More active</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default ProgressSection; |