File size: 5,538 Bytes
25732fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | import React from 'react'
import { motion } from 'framer-motion'
import { Calendar, CheckCircle, Clock, TrendingUp } from 'lucide-react'
const ProgressTimeline = ({ topics }) => {
// Filter and sort topics by last practice date
const timelineData = topics
.filter(topic => topic.practice_count > 0)
.sort((a, b) => new Date(b.last_practiced) - new Date(a.last_practiced))
.slice(0, 10) // Show last 10 activities
const formatDate = (dateString) => {
const date = new Date(dateString)
const now = new Date()
const diffTime = Math.abs(now - date)
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
if (diffDays === 0) return 'Today'
if (diffDays === 1) return 'Yesterday'
if (diffDays < 7) return `${diffDays} days ago`
if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`
return `${Math.floor(diffDays / 30)} months ago`
}
const getStatusColor = (level) => {
if (level >= 0.8) return 'from-green-500 to-emerald-500'
if (level >= 0.6) return 'from-blue-500 to-cyan-500'
if (level >= 0.4) return 'from-yellow-500 to-orange-500'
return 'from-red-500 to-pink-500'
}
const getStatusIcon = (level) => {
if (level >= 0.8) return <CheckCircle className="w-5 h-5 text-white" />
if (level >= 0.6) return <TrendingUp className="w-5 h-5 text-white" />
return <Clock className="w-5 h-5 text-white" />
}
if (timelineData.length === 0) {
return (
<div className="card">
<h2 className="text-2xl font-bold text-slate-800 mb-6 flex items-center gap-2">
<Calendar className="w-7 h-7 text-primary-600" />
Recent Activity
</h2>
<div className="text-center py-12 text-slate-500">
<Calendar className="w-16 h-16 mx-auto mb-4 opacity-50" />
<p>No activity yet - start learning to see your timeline</p>
</div>
</div>
)
}
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="card"
>
<h2 className="text-2xl font-bold text-slate-800 mb-6 flex items-center gap-2">
<Calendar className="w-7 h-7 text-primary-600" />
Recent Learning Activity
</h2>
<div className="relative">
{/* Timeline line */}
<div className="absolute left-6 top-0 bottom-0 w-0.5 bg-gradient-to-b from-primary-500 to-accent-500"></div>
{/* Timeline items */}
<div className="space-y-6">
{timelineData.map((topic, index) => (
<motion.div
key={topic.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.1 }}
className="relative pl-16"
>
{/* Timeline dot */}
<div className={`absolute left-0 w-12 h-12 rounded-xl bg-gradient-to-br ${getStatusColor(topic.knowledge_level)} flex items-center justify-center shadow-lg`}>
{getStatusIcon(topic.knowledge_level)}
</div>
{/* Content */}
<div className="card hover-lift cursor-pointer">
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<h3 className="text-lg font-bold text-slate-800 mb-1">
{topic.name}
</h3>
<p className="text-sm text-slate-600">
{topic.category} • {topic.difficulty}
</p>
</div>
<span className="text-xs font-semibold text-slate-500 whitespace-nowrap ml-4">
{formatDate(topic.last_practiced)}
</span>
</div>
{/* Progress bar */}
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-slate-600 font-medium">Knowledge Level</span>
<span className="font-bold text-slate-800">
{Math.round(topic.knowledge_level * 100)}%
</span>
</div>
<div className="w-full bg-slate-200 rounded-full h-2 overflow-hidden">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${topic.knowledge_level * 100}%` }}
transition={{ delay: 0.3 + (index * 0.1), duration: 0.8 }}
className={`h-full bg-gradient-to-r ${getStatusColor(topic.knowledge_level)} rounded-full`}
/>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-2 gap-4 mt-4 pt-4 border-t border-slate-100">
<div>
<div className="text-xs text-slate-500 mb-1">Practice Sessions</div>
<div className="text-lg font-bold text-slate-800">{topic.practice_count}</div>
</div>
<div>
<div className="text-xs text-slate-500 mb-1">Confidence</div>
<div className="text-lg font-bold text-slate-800">
{Math.round(topic.confidence * 100)}%
</div>
</div>
</div>
</div>
</motion.div>
))}
</div>
</div>
</motion.div>
)
}
export default ProgressTimeline |