Skip to content

Commit d6e43a9

Browse files
committed
feat: enhance student dashboard with points, badges, and activity chart
1 parent c94caf8 commit d6e43a9

2 files changed

Lines changed: 118 additions & 1 deletion

File tree

web/templates/dashboard/student.html

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,30 @@ <h3 class="text-2xl font-bold">{{ avg_progress }}%</h3>
5151
</div>
5252
</div>
5353
</div>
54+
<!-- Total Points -->
55+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
56+
<div class="flex items-center justify-between">
57+
<div>
58+
<p class="text-sm text-gray-500 dark:text-gray-400">Total Points</p>
59+
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">{{ total_points }}</h3>
60+
</div>
61+
<div class="bg-yellow-100 dark:bg-yellow-900 rounded-full p-3">
62+
<i class="fas fa-star text-yellow-500 dark:text-yellow-300 text-xl"></i>
63+
</div>
64+
</div>
65+
</div>
66+
<!-- Badges Earned -->
67+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
68+
<div class="flex items-center justify-between">
69+
<div>
70+
<p class="text-sm text-gray-500 dark:text-gray-400">Badges Earned</p>
71+
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">{{ total_badges }}</h3>
72+
</div>
73+
<div class="bg-yellow-100 dark:bg-yellow-900 rounded-full p-3">
74+
<i class="fas fa-medal text-yellow-500 dark:text-yellow-300 text-xl"></i>
75+
</div>
76+
</div>
77+
</div>
5478
</div>
5579
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
5680
<!-- Course Progress -->
@@ -209,5 +233,65 @@ <h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">Your Certif
209233
{% endfor %}
210234
</div>
211235
</div>
236+
<!-- Points Chart -->
237+
<div class="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow p-6">
238+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">
239+
<i class="fas fa-chart-bar text-teal-500 mr-2"></i> Points Earned (Last 30 Days)
240+
</h2>
241+
<canvas id="pointsChart" height="80" role="img" aria-label="Bar chart showing points earned per day over the last 30 days"></canvas>
242+
</div>
243+
244+
<!-- Badges Section -->
245+
{% if recent_badges %}
246+
<div class="mt-8 bg-white dark:bg-gray-800 rounded-lg shadow p-6">
247+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">
248+
<i class="fas fa-award text-yellow-500 mr-2"></i> Recent Badges
249+
</h2>
250+
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
251+
{% for ub in recent_badges %}
252+
<div class="flex flex-col items-center text-center p-3 border border-gray-200 dark:border-gray-700 rounded-lg">
253+
{% if ub.badge.icon %}
254+
<i class="{{ ub.badge.icon }} text-3xl text-yellow-500 mb-2"></i>
255+
{% else %}
256+
<i class="fas fa-award text-3xl text-yellow-500 mb-2"></i>
257+
{% endif %}
258+
<p class="text-sm font-medium text-gray-800 dark:text-white">{{ ub.badge.name }}</p>
259+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ ub.awarded_at|date:"M d, Y" }}</p>
260+
</div>
261+
{% endfor %}
262+
</div>
263+
</div>
264+
{% endif %}
212265
</div>
266+
267+
{{ chart_labels|json_script:"points-chart-labels" }}
268+
{{ chart_data|json_script:"points-chart-data" }}
269+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"></script>
270+
<script>
271+
const labels = JSON.parse(document.getElementById('points-chart-labels').textContent);
272+
const pointsData = JSON.parse(document.getElementById('points-chart-data').textContent);
273+
const ctx = document.getElementById('pointsChart').getContext('2d');
274+
new Chart(ctx, {
275+
type: 'bar',
276+
data: {
277+
labels,
278+
datasets: [{
279+
label: 'Points',
280+
data: pointsData,
281+
backgroundColor: 'rgba(20, 184, 166, 0.5)',
282+
borderColor: 'rgba(20, 184, 166, 1)',
283+
borderWidth: 1,
284+
borderRadius: 4,
285+
}]
286+
},
287+
options: {
288+
responsive: true,
289+
plugins: { legend: { display: false } },
290+
scales: {
291+
y: { beginAtZero: true, ticks: { stepSize: 1 } },
292+
x: { ticks: { maxTicksLimit: 10 } }
293+
}
294+
}
295+
});
296+
</script>
213297
{% endblock content %}

web/views.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from django.core.paginator import Paginator
3636
from django.db import IntegrityError, models, router, transaction
3737
from django.db.models import Avg, Count, Q, Sum
38-
from django.db.models.functions import Coalesce
38+
from django.db.models.functions import Coalesce, TruncDate
3939
from django.http import (
4040
FileResponse,
4141
Http404,
@@ -149,6 +149,7 @@
149149
Payment,
150150
PeerConnection,
151151
PeerMessage,
152+
Points,
152153
ProductImage,
153154
Profile,
154155
ProgressTracker,
@@ -2621,13 +2622,45 @@ def student_dashboard(request):
26212622
# Query achievements for the user.
26222623
achievements = Achievement.objects.filter(student=request.user).order_by("-awarded_at")
26232624

2625+
# Points data
2626+
total_points = Points.objects.filter(user=request.user).aggregate(total=Sum("amount"))["total"] or 0
2627+
2628+
# Points over last 30 days for chart
2629+
today = timezone.now().date()
2630+
thirty_days_ago = today - timedelta(days=29)
2631+
start_datetime = timezone.make_aware(timezone.datetime.combine(thirty_days_ago, timezone.datetime.min.time()))
2632+
points_qs = (
2633+
Points.objects.filter(user=request.user, awarded_at__gte=start_datetime)
2634+
.annotate(day=TruncDate("awarded_at"))
2635+
.values("day")
2636+
.annotate(total=Sum("amount"))
2637+
.order_by("day")
2638+
)
2639+
points_by_date = {entry["day"].strftime("%b %d"): entry["total"] for entry in points_qs}
2640+
chart_labels = []
2641+
chart_data = []
2642+
for i in range(30):
2643+
day = thirty_days_ago + timedelta(days=i)
2644+
label = day.strftime("%b %d")
2645+
chart_labels.append(label)
2646+
chart_data.append(points_by_date.get(label, 0))
2647+
2648+
# Badges
2649+
recent_badges = UserBadge.objects.filter(user=request.user).select_related("badge").order_by("-awarded_at")[:6]
2650+
total_badges = UserBadge.objects.filter(user=request.user).count()
2651+
26242652
context = {
26252653
"enrollments": enrollments,
26262654
"upcoming_sessions": upcoming_sessions,
26272655
"progress_data": progress_data,
26282656
"avg_progress": avg_progress,
26292657
"streak": streak,
26302658
"achievements": achievements,
2659+
"total_points": total_points,
2660+
"chart_labels": chart_labels,
2661+
"chart_data": chart_data,
2662+
"recent_badges": recent_badges,
2663+
"total_badges": total_badges,
26312664
}
26322665
return render(request, "dashboard/student.html", context)
26332666

0 commit comments

Comments
 (0)