Skip to content

Commit 07a9bf8

Browse files
committed
feat: add student progress report with attendance, quiz scores, badges, and teacher notes
1 parent c94caf8 commit 07a9bf8

5 files changed

Lines changed: 344 additions & 0 deletions

File tree

web/templates/courses/progress_overview.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ <h2 class="text-xl font-semibold mb-4">Student Progress</h2>
165165
class="text-teal-600 hover:text-teal-900 dark:text-teal-400 dark:hover:text-teal-300">
166166
View Details
167167
</a>
168+
<a href="{% url 'student_progress_report' course.slug data.enrollment.student.id %}"
169+
class="ml-3 text-blue-600 hover:text-blue-800 dark:text-blue-400">
170+
Full Report
171+
</a>
168172
</td>
169173
</tr>
170174
{% endfor %}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}Progress Report - {{ student.get_full_name|default:student.username }} - {{ course.title }}{% endblock title %}
4+
5+
{% block content %}
6+
<div class="container mx-auto px-4 py-8">
7+
<!-- Breadcrumbs -->
8+
<nav class="text-sm mb-6">
9+
<a href="{% url 'course_detail' course.slug %}" class="text-teal-600 hover:text-teal-800 dark:text-teal-400">{{ course.title }}</a>
10+
<span class="mx-2 text-gray-400">/</span>
11+
{% if is_teacher %}
12+
<a href="{% url 'course_progress_overview' course.slug %}" class="text-teal-600 hover:text-teal-800 dark:text-teal-400">Progress Overview</a>
13+
<span class="mx-2 text-gray-400">/</span>
14+
{% endif %}
15+
<span class="text-gray-600 dark:text-gray-400">{{ student.get_full_name|default:student.username }}'s Report</span>
16+
</nav>
17+
18+
<!-- Header -->
19+
<div class="flex items-center justify-between mb-8">
20+
<div class="flex items-center space-x-4">
21+
{% if student.profile.avatar %}
22+
<img src="{{ student.profile.avatar.url }}" alt="{{ student.get_full_name|default:student.username }}" class="w-16 h-16 rounded-full object-cover">
23+
{% else %}
24+
<div class="w-16 h-16 rounded-full bg-teal-100 dark:bg-teal-900 flex items-center justify-center">
25+
<i class="fas fa-user text-teal-500 dark:text-teal-300 text-2xl"></i>
26+
</div>
27+
{% endif %}
28+
<div>
29+
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ student.get_full_name|default:student.username }}</h1>
30+
<p class="text-gray-500 dark:text-gray-400">{{ course.title }}</p>
31+
<span class="inline-block mt-1 px-2 py-1 text-xs rounded-full
32+
{% if enrollment.status == 'approved' %}bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300
33+
{% elif enrollment.status == 'completed' %}bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300
34+
{% else %}bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300{% endif %}">
35+
{{ enrollment.get_status_display }}
36+
</span>
37+
</div>
38+
</div>
39+
<div class="text-sm text-gray-500 dark:text-gray-400">
40+
Enrolled {{ enrollment.enrollment_date|date:"M d, Y" }}
41+
</div>
42+
</div>
43+
44+
<!-- Stats Cards -->
45+
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
46+
<!-- Completion -->
47+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
48+
<div class="flex items-center justify-between">
49+
<div>
50+
<p class="text-sm text-gray-500 dark:text-gray-400">Completion</p>
51+
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">{{ progress.completion_percentage }}%</h3>
52+
</div>
53+
<div class="bg-teal-100 dark:bg-teal-900 rounded-full p-3">
54+
<i class="fas fa-graduation-cap text-teal-500 dark:text-teal-300 text-xl"></i>
55+
</div>
56+
</div>
57+
<div class="mt-3 bg-gray-200 dark:bg-gray-700 rounded-full h-2">
58+
<div class="bg-teal-500 h-2 rounded-full" style="--progress: {{ progress.completion_percentage }}%; width: var(--progress)"></div>
59+
</div>
60+
</div>
61+
<!-- Attendance -->
62+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
63+
<div class="flex items-center justify-between">
64+
<div>
65+
<p class="text-sm text-gray-500 dark:text-gray-400">Attendance</p>
66+
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">{{ attendance_rate }}%</h3>
67+
<p class="text-xs text-gray-400 mt-1">{{ attended }}/{{ past_sessions }} sessions</p>
68+
</div>
69+
<div class="bg-orange-100 dark:bg-orange-900 rounded-full p-3">
70+
<i class="fas fa-calendar-check text-orange-500 dark:text-orange-300 text-xl"></i>
71+
</div>
72+
</div>
73+
</div>
74+
<!-- Points -->
75+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
76+
<div class="flex items-center justify-between">
77+
<div>
78+
<p class="text-sm text-gray-500 dark:text-gray-400">Total Points</p>
79+
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">{{ total_points }}</h3>
80+
</div>
81+
<div class="bg-yellow-100 dark:bg-yellow-900 rounded-full p-3">
82+
<i class="fas fa-star text-yellow-500 dark:text-yellow-300 text-xl"></i>
83+
</div>
84+
</div>
85+
</div>
86+
<!-- Streak -->
87+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
88+
<div class="flex items-center justify-between">
89+
<div>
90+
<p class="text-sm text-gray-500 dark:text-gray-400">Learning Streak</p>
91+
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">{{ streak.current_streak|default:0 }} days</h3>
92+
<p class="text-xs text-gray-400 mt-1">Best: {{ streak.longest_streak|default:0 }} days</p>
93+
</div>
94+
<div class="bg-red-100 dark:bg-red-900 rounded-full p-3">
95+
<i class="fas fa-fire text-red-500 dark:text-red-300 text-xl"></i>
96+
</div>
97+
</div>
98+
</div>
99+
</div>
100+
101+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
102+
<!-- Left column -->
103+
<div class="lg:col-span-2 space-y-8">
104+
105+
<!-- Quiz Performance -->
106+
{% if quiz_attempts %}
107+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
108+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">
109+
<i class="fas fa-question-circle text-teal-500 dark:text-teal-300 mr-2"></i> Quiz Performance
110+
</h2>
111+
<div class="flex items-center mb-4">
112+
<span class="text-gray-500 dark:text-gray-400 text-sm mr-2">Average Score:</span>
113+
<span class="text-lg font-bold text-gray-900 dark:text-white">{{ avg_quiz_score }}%</span>
114+
</div>
115+
<div class="space-y-3">
116+
{% for attempt in quiz_attempts %}
117+
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
118+
<div>
119+
<p class="font-medium text-gray-900 dark:text-white text-sm">{{ attempt.quiz.title }}</p>
120+
<p class="text-xs text-gray-500 dark:text-gray-400">{{ attempt.start_time|date:"M d, Y" }}</p>
121+
</div>
122+
<span class="font-bold {% if attempt.score >= 70 %}text-green-600 dark:text-green-400{% else %}text-red-500 dark:text-red-400{% endif %}">
123+
{{ attempt.score }}%
124+
</span>
125+
</div>
126+
{% endfor %}
127+
</div>
128+
</div>
129+
{% endif %}
130+
131+
<!-- Recent Points -->
132+
{% if recent_points %}
133+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
134+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">
135+
<i class="fas fa-star text-yellow-500 dark:text-yellow-300 mr-2"></i> Recent Points
136+
</h2>
137+
<div class="space-y-3">
138+
{% for point in recent_points %}
139+
<div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
140+
<div>
141+
<p class="text-sm text-gray-900 dark:text-white">{{ point.reason }}</p>
142+
<p class="text-xs text-gray-500 dark:text-gray-400">{{ point.awarded_at|date:"M d, Y" }}</p>
143+
</div>
144+
<span class="font-bold text-yellow-600 dark:text-yellow-400">+{{ point.amount }}</span>
145+
</div>
146+
{% endfor %}
147+
</div>
148+
</div>
149+
{% endif %}
150+
151+
<!-- Teacher Notes -->
152+
{% if is_teacher %}
153+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
154+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">
155+
<i class="fas fa-sticky-note text-teal-500 dark:text-teal-300 mr-2"></i> Teacher Notes
156+
</h2>
157+
<form method="post" class="mb-6">
158+
{% csrf_token %}
159+
<label for="note" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Note</label>
160+
<textarea id="note" name="note" rows="3" placeholder="Add a note about this student..."
161+
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-300"
162+
></textarea>
163+
<button type="submit"
164+
class="mt-2 bg-teal-300 hover:bg-teal-400 dark:bg-teal-600 dark:hover:bg-teal-500 text-white font-semibold px-4 py-2 rounded-lg text-sm transition duration-200 focus:outline-none focus:ring-2 focus:ring-teal-300">
165+
Add Note
166+
</button>
167+
</form>
168+
{% if notes %}
169+
<div class="space-y-3">
170+
{% for note in notes %}
171+
<div class="p-3 bg-teal-50 dark:bg-teal-900 rounded-lg border-l-4 border-teal-400">
172+
<p class="text-sm text-gray-800 dark:text-gray-200">{{ note.content }}</p>
173+
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
174+
{{ note.created_by.get_full_name|default:note.created_by.username }} &mdash; {{ note.created_at|date:"M d, Y" }}
175+
</p>
176+
</div>
177+
{% endfor %}
178+
</div>
179+
{% else %}
180+
<p class="text-gray-500 dark:text-gray-400 text-sm">No notes yet.</p>
181+
{% endif %}
182+
</div>
183+
{% endif %}
184+
</div>
185+
186+
<!-- Right column -->
187+
<div class="space-y-8">
188+
<!-- Badges -->
189+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
190+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">
191+
<i class="fas fa-award text-yellow-500 dark:text-yellow-300 mr-2"></i> Badges ({{ badges.count }})
192+
</h2>
193+
{% if badges %}
194+
<div class="grid grid-cols-3 gap-3">
195+
{% for ub in badges %}
196+
<div class="flex flex-col items-center text-center p-2 border border-gray-200 dark:border-gray-700 rounded-lg">
197+
{% if ub.badge.image %}
198+
<img src="{{ ub.badge.image.url }}" alt="{{ ub.badge.name }}" class="w-10 h-10 rounded-full object-cover mb-1">
199+
{% else %}
200+
<i class="fas fa-award text-2xl text-yellow-500 dark:text-yellow-300 mb-1"></i>
201+
{% endif %}
202+
<p class="text-xs font-medium text-gray-800 dark:text-white">{{ ub.badge.name }}</p>
203+
</div>
204+
{% endfor %}
205+
</div>
206+
{% else %}
207+
<p class="text-gray-500 dark:text-gray-400 text-sm">No badges earned yet.</p>
208+
{% endif %}
209+
</div>
210+
211+
<!-- Session Attendance Detail -->
212+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
213+
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4">
214+
<i class="fas fa-calendar text-orange-500 dark:text-orange-300 mr-2"></i> Sessions
215+
</h2>
216+
<div class="space-y-2 text-sm">
217+
<div class="flex justify-between">
218+
<span class="text-gray-500 dark:text-gray-400">Total Sessions</span>
219+
<span class="font-medium text-gray-900 dark:text-white">{{ total_sessions }}</span>
220+
</div>
221+
<div class="flex justify-between">
222+
<span class="text-gray-500 dark:text-gray-400">Sessions Held</span>
223+
<span class="font-medium text-gray-900 dark:text-white">{{ past_sessions }}</span>
224+
</div>
225+
<div class="flex justify-between">
226+
<span class="text-gray-500 dark:text-gray-400">Attended</span>
227+
<span class="font-medium text-green-600 dark:text-green-400">{{ attended }}</span>
228+
</div>
229+
<div class="flex justify-between">
230+
<span class="text-gray-500 dark:text-gray-400">Missed</span>
231+
<span class="font-medium text-red-500 dark:text-red-400">{{ missed_sessions }}</span>
232+
</div>
233+
</div>
234+
</div>
235+
</div>
236+
</div>
237+
</div>
238+
{% endblock content %}

web/templates/dashboard/student.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ <h3 class="font-medium">{{ data.enrollment.course.title }}</h3>
7676
</span>
7777
<a href="{% url 'student_progress' data.enrollment.id %}"
7878
class="text-teal-500 hover:text-teal-600 dark:text-teal-400">View Details</a>
79+
<a href="{% url 'student_progress_report' data.enrollment.course.slug request.user.id %}"
80+
class="ml-3 text-blue-600 hover:text-blue-800 dark:text-blue-400">My Report</a>
7981
</div>
8082
</div>
8183
{% endfor %}

web/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@
185185
views.course_progress_overview,
186186
name="course_progress_overview",
187187
),
188+
path(
189+
"courses/<slug:slug>/progress/<int:student_id>/",
190+
views.student_progress_report,
191+
name="student_progress_report",
192+
),
188193
path(
189194
"courses/<slug:slug>/materials/upload/",
190195
views.upload_material,

0 commit comments

Comments
 (0)