wiki-project / src /components /ProgressSection.tsx
Nagi15's picture
Add codebase
fcb5a67
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;