feat: add completion indicator

This commit is contained in:
2025-08-04 15:36:24 +02:00
parent a1e3e4a688
commit 21aa7015f9
4 changed files with 216 additions and 11 deletions

View File

@@ -48,6 +48,65 @@ class Assessment(db.Model):
def __repr__(self):
return f'<Assessment {self.title}>'
@property
def grading_progress(self):
"""Calcule le pourcentage de progression des notes saisies pour cette évaluation.
Retourne un dictionnaire avec les statistiques de progression."""
# Obtenir tous les éléments de notation pour cette évaluation
total_elements = 0
completed_elements = 0
total_students = len(self.class_group.students)
if total_students == 0:
return {
'percentage': 0,
'completed': 0,
'total': 0,
'status': 'no_students'
}
# Parcourir tous les exercices et leurs éléments de notation
for exercise in self.exercises:
for grading_element in exercise.grading_elements:
total_elements += total_students
# Compter les notes saisies (valeur non nulle et non vide)
completed_for_element = db.session.query(Grade).filter(
Grade.grading_element_id == grading_element.id,
Grade.value.isnot(None),
Grade.value != '',
Grade.value != '.'
).count()
completed_elements += completed_for_element
if total_elements == 0:
return {
'percentage': 0,
'completed': 0,
'total': 0,
'status': 'no_elements'
}
percentage = round((completed_elements / total_elements) * 100)
# Déterminer le statut
if percentage == 0:
status = 'not_started'
elif percentage == 100:
status = 'completed'
else:
status = 'in_progress'
return {
'percentage': percentage,
'completed': completed_elements,
'total': total_elements,
'status': status,
'students_count': total_students
}
class Exercise(db.Model):
id = db.Column(db.Integer, primary_key=True)

View File

@@ -52,6 +52,62 @@
<dt class="text-sm font-medium text-gray-500">Nombre d'exercices</dt>
<dd class="mt-1 text-sm text-gray-900">{{ assessment.exercises|length }}</dd>
</div>
<!-- Indicateur de progression des corrections -->
<div class="md:col-span-2">
<dt class="text-sm font-medium text-gray-500 mb-2">État des corrections</dt>
<dd class="mt-1">
{% set progress = assessment.grading_progress %}
<div class="flex items-center justify-between">
<!-- Indicateur fusionné -->
{% if progress.status == 'completed' %}
<div class="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium bg-green-100 text-green-800 border border-green-200">
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<span class="font-semibold">Corrections terminées (100%)</span>
</div>
{% elif progress.status == 'in_progress' %}
<a href="{{ url_for('grading.assessment_grading', assessment_id=assessment.id) }}"
class="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium bg-orange-100 text-orange-800 border border-orange-200 hover:bg-orange-200 transition-colors cursor-pointer">
<div class="relative w-4 h-4 mr-2">
<svg class="w-4 h-4 transform -rotate-90" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="2" fill="none" class="text-orange-300"/>
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="2" fill="none"
class="text-orange-600" stroke-dasharray="37.7"
stroke-dashoffset="{{ 37.7 - (37.7 * progress.percentage / 100) }}"/>
</svg>
</div>
<span class="font-semibold">Corrections en cours ({{ progress.percentage }}%) - Cliquer pour continuer</span>
</a>
{% elif progress.status == 'not_started' %}
<a href="{{ url_for('grading.assessment_grading', assessment_id=assessment.id) }}"
class="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium bg-red-100 text-red-800 border border-red-200 hover:bg-red-200 transition-colors cursor-pointer">
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
<span class="font-semibold">Corrections non commencées - Cliquer pour démarrer</span>
</a>
{% else %}
<div class="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium bg-gray-100 text-gray-800 border border-gray-200">
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
<span class="font-semibold">État indéterminé</span>
</div>
{% endif %}
<!-- Info détaillée -->
<div class="text-sm text-gray-500">
<span class="font-medium">{{ progress.completed }}/{{ progress.total }}</span> notes saisies
{% if progress.students_count %}
<span class="ml-2">({{ progress.students_count }} élèves)</span>
{% endif %}
</div>
</div>
</dd>
</div>
{% if assessment.description %}
<div class="md:col-span-2">
<dt class="text-sm font-medium text-gray-500">Description</dt>

View File

@@ -142,6 +142,53 @@
Coeff. {{ assessment.coefficient }}
</div>
</div>
<!-- Indicateur de progression des notes -->
{% set progress = assessment.grading_progress %}
<div class="flex items-center justify-between pt-2 border-t border-gray-100">
<!-- Indicateur fusionné cliquable -->
{% if progress.status == 'completed' %}
<div class="inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium bg-green-100 text-green-800 border border-green-200">
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<span class="font-semibold">Correction 100% </span>
</div>
{% elif progress.status == 'in_progress' %}
<a href="{{ url_for('grading.assessment_grading', assessment_id=assessment.id) }}"
class="inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium bg-orange-100 text-orange-800 border border-orange-200 hover:bg-orange-200 transition-colors cursor-pointer">
<div class="relative w-4 h-4 mr-2">
<svg class="w-4 h-4 transform -rotate-90" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="2" fill="none" class="text-orange-300"/>
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="2" fill="none"
class="text-orange-600" stroke-dasharray="37.7"
stroke-dashoffset="{{ 37.7 - (37.7 * progress.percentage / 100) }}"/>
</svg>
</div>
<span class="font-semibold">Correction {{ progress.percentage }}% </span>
</a>
{% elif progress.status == 'not_started' %}
<a href="{{ url_for('grading.assessment_grading', assessment_id=assessment.id) }}"
class="inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium bg-red-100 text-red-800 border border-red-200 hover:bg-red-200 transition-colors cursor-pointer">
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
<span class="font-semibold">Correction 0%</span>
</a>
{% else %}
<div class="inline-flex items-center px-3 py-2 rounded-lg text-sm font-medium bg-gray-100 text-gray-800 border border-gray-200">
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
<span class="font-semibold">Non définie</span>
</div>
{% endif %}
<!-- Info détaillée -->
<div class="text-xs text-gray-500 text-right">
{{ progress.completed }}/{{ progress.total }}
</div>
</div>
</div>
{% if assessment.description %}
@@ -248,4 +295,4 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
{% endblock %}
{% endblock %}

View File

@@ -181,13 +181,13 @@
{% if recent_assessments %}
<div class="space-y-3">
{% for assessment in recent_assessments %}
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="flex items-center justify-between p-4 bg-gray-50 hover:bg-blue-50 rounded-lg transition-colors group block">
<div class="flex items-center space-x-4">
<div class="flex items-center justify-between p-4 bg-gray-50 hover:bg-blue-50 rounded-lg transition-colors group">
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="flex items-center space-x-4 flex-1 min-w-0">
<div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white font-bold text-sm">
{{ assessment.title[0].upper() }}
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900 group-hover:text-blue-700 transition-colors">{{ assessment.title }}</h3>
<div class="min-w-0 flex-1">
<h3 class="text-sm font-semibold text-gray-900 group-hover:text-blue-700 transition-colors truncate">{{ assessment.title }}</h3>
<div class="flex items-center text-xs text-gray-500 space-x-2">
<span class="flex items-center">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
@@ -204,18 +204,61 @@
</span>
</div>
</div>
</div>
<div class="flex items-center space-x-3">
</a>
<div class="flex items-center space-x-3 flex-shrink-0">
<!-- Indicateur de progression compact -->
{% set progress = assessment.grading_progress %}
{% if progress.status == 'completed' %}
<div class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 border border-green-200" title="100% Terminé">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<span class="font-medium">Correction 100%</span>
</div>
{% elif progress.status == 'in_progress' %}
<a href="{{ url_for('grading.assessment_grading', assessment_id=assessment.id) }}"
class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-orange-100 text-orange-800 border border-orange-200 hover:bg-orange-200 transition-colors cursor-pointer"
title="{{ progress.percentage }}% En cours - Cliquer pour corriger"
onclick="event.stopPropagation();">
<div class="relative w-3 h-3 mr-1">
<svg class="w-3 h-3 transform -rotate-90" viewBox="0 0 12 12">
<circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.5" fill="none" class="text-orange-300"/>
<circle cx="6" cy="6" r="4" stroke="currentColor" stroke-width="1.5" fill="none"
class="text-orange-600" stroke-dasharray="25.1"
stroke-dashoffset="{{ 25.1 - (25.1 * progress.percentage / 100) }}"/>
</svg>
</div>
<span class="font-medium">Correction {{ progress.percentage }}%</span>
</a>
{% elif progress.status == 'not_started' %}
<a href="{{ url_for('grading.assessment_grading', assessment_id=assessment.id) }}"
class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800 border border-red-200 hover:bg-red-200 transition-colors cursor-pointer"
title="Non commencée - Cliquer pour corriger"
onclick="event.stopPropagation();">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
<span class="font-medium">Correction 0%</span>
</a>
{% else %}
<div class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800 border border-gray-200" title="Non définie">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
<span class="font-medium">-</span>
</div>
{% endif %}
<span class="bg-white text-gray-600 text-xs px-2 py-1 rounded-full border group-hover:border-blue-200 transition-colors">
Coeff. {{ assessment.coefficient }}
</span>
<div class="text-blue-600 group-hover:text-blue-800 opacity-70 group-hover:opacity-100 transition-all">
<a href="{{ url_for('assessments.detail', id=assessment.id) }}" class="text-blue-600 group-hover:text-blue-800 opacity-70 group-hover:opacity-100 transition-all">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
</svg>
</div>
</a>
</div>
</a>
</div>
{% endfor %}
</div>
<div class="mt-4 text-center">
@@ -298,4 +341,4 @@
</div>
</div>
</div>
{% endblock %}
{% endblock %}