diff --git a/services.py b/services.py index 08c954f..982ffda 100644 --- a/services.py +++ b/services.py @@ -126,8 +126,8 @@ class AssessmentService: if not elem_data.get('label'): raise ValidationError("Libellé d'élément de notation requis") - grading_type = elem_data.get('grading_type', 'points') - if grading_type not in ['points', 'score']: + grading_type = elem_data.get('grading_type', 'notes') + if grading_type not in ['notes', 'score']: raise ValidationError("Type de notation invalide") try: diff --git a/templates/assessment_form_unified.html b/templates/assessment_form_unified.html index ea57164..1fdaf4d 100644 --- a/templates/assessment_form_unified.html +++ b/templates/assessment_form_unified.html @@ -258,7 +258,7 @@ diff --git a/templates/assessment_grading.html b/templates/assessment_grading.html index 847727f..020f44f 100644 --- a/templates/assessment_grading.html +++ b/templates/assessment_grading.html @@ -100,6 +100,37 @@ + +
+
+
+

Filtrer les élèves

+ {{ students|length }} élève(s) +
+
+
+
+ +
+ + + +
+ +
+
+
+
@@ -108,47 +139,94 @@
- - - + + + {% set current_exercise = '' %} + {% set exercise_elements = {} %} {% for element in grading_elements %} - + {% endfor %} + + + + + + + + {% set current_exercise = '' %} + {% for element in grading_elements %} + {% if element.exercise.title != current_exercise %} + {% set current_exercise = element.exercise.title %} + {% set exercise_color = 'border-l-4 border-' + ['blue', 'green', 'purple', 'orange', 'pink'][loop.index0 % 5] + '-400' %} + {% endif %} + + {% endfor %} - + {% for student in students %} - - + + {% set current_exercise = '' %} {% for element in grading_elements %} + {% if element.exercise.title != current_exercise %} + {% set current_exercise = element.exercise.title %} + {% set exercise_bg_color = ['bg-blue-25', 'bg-green-25', 'bg-purple-25', 'bg-orange-25', 'bg-pink-25'][loop.index0 % 5] %} + {% set exercise_border_color = ['border-l-2 border-blue-300', 'border-l-2 border-green-300', 'border-l-2 border-purple-300', 'border-l-2 border-orange-300', 'border-l-2 border-pink-300'][loop.index0 % 5] %} + {% endif %} + {% set grade_key = student.id ~ '_' ~ element.id %} {% set existing_grade = existing_grades.get(grade_key) %} - @@ -278,6 +357,7 @@ let totalCols = {{ grading_elements|length }}; let unsavedChanges = new Set(); let undoStack = []; let isAutoSaving = false; +let filterIsActive = false; // Nouvelle variable pour tracker l'état du filtre // Liste dynamique des valeurs spéciales (exclut les scores 0-3) const SPECIAL_VALUES_KEYS = Object.keys(GRADING_CONFIG.scale_values).filter(key => !['0', '1', '2', '3'].includes(key)); @@ -286,9 +366,14 @@ const SPECIAL_VALUES_KEYS = Object.keys(GRADING_CONFIG.scale_values).filter(key document.addEventListener('DOMContentLoaded', function() { setupKeyboardNavigation(); updateProgressIndicator(); - focusFirstInput(); setupAutosave(); applyInitialColors(); + setupStudentFilter(); + + // NE PAS auto-focus - laisser l'utilisateur choisir où cliquer + // setTimeout(() => { + // focusFirstInput(); + // }, 100); }); // Applique les couleurs aux champs déjà remplis au chargement @@ -309,6 +394,12 @@ function applyInitialColors() { function setupKeyboardNavigation() { // Raccourcis globaux document.addEventListener('keydown', function(e) { + // PROTECTION ABSOLUE : Ne JAMAIS intercepter si on est dans le champ de filtre + if (e.target.id === 'student-filter' || filterIsActive) { + console.log('Navigation bloquée - filtre actif'); + return; + } + // F1 : Aide if (e.key === 'F1') { e.preventDefault(); @@ -345,6 +436,12 @@ function handleGradeFocus(input) { // Gestion des touches pour les champs de notes function handleGradeKeydown(event, input) { + // PROTECTION : Ne pas traiter si le filtre est actif + if (filterIsActive) { + console.log('Événement bloqué - filtre actif'); + return; + } + const e = event; // Échap : Vider le champ @@ -402,6 +499,11 @@ function handleCommentKeydown(event, input) { // Navigation vers le champ suivant function navigateToNext(direction) { + // Ne pas naviguer si le filtre est actif + if (filterIsActive) { + return; + } + let newRow = currentRow; let newCol = currentCol; @@ -626,6 +728,11 @@ function updateSaveStatus() { // Focuser le premier champ function focusFirstInput() { + // Ne pas auto-focus si le filtre est actif ou si l'utilisateur interagit avec le filtre + if (filterIsActive || (document.activeElement && document.activeElement.id === 'student-filter')) { + return; + } + const firstInput = document.querySelector('.grading-input'); if (firstInput) { firstInput.focus(); @@ -764,6 +871,166 @@ function showToast(message, type = 'success') { }, 3000); } +// Configuration du filtre des élèves +function setupStudentFilter() { + const filterInput = document.getElementById('student-filter'); + const clearButton = document.getElementById('clear-filter'); + const studentCount = document.getElementById('student-count'); + + if (!filterInput) return; + + console.log('Configuration du filtre des élèves'); + + // Isoler seulement les événements de navigation, pas les événements fonctionnels + ['keydown', 'keyup', 'keypress'].forEach(eventType => { + filterInput.addEventListener(eventType, function(e) { + // Ne bloquer que les événements de navigation, pas les caractères normaux + if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key)) { + e.stopImmediatePropagation(); + } + }, true); + }); + + // Gestion du focus + filterInput.addEventListener('focus', function(e) { + filterIsActive = true; + this.select(); + console.log('🔍 Filtre FOCUS - actif:', filterIsActive); + }); + + // Gestion du blur + filterInput.addEventListener('blur', function(e) { + setTimeout(() => { + if (document.activeElement !== filterInput) { + filterIsActive = false; + console.log('🔍 Filtre BLUR - inactif:', filterIsActive); + } + }, 50); + }); + + // Gestion de la saisie - SANS bloquer l'événement + filterInput.addEventListener('input', function(e) { + filterIsActive = true; + const searchTerm = this.value.toLowerCase().trim(); + console.log('🔍 Filtre INPUT:', searchTerm, 'Longueur:', searchTerm.length); + + // Appeler la fonction de filtrage + filterStudents(searchTerm); + + // Gérer le bouton de nettoyage + if (searchTerm) { + clearButton.classList.remove('hidden'); + } else { + clearButton.classList.add('hidden'); + } + }); + + // Gestion des touches spéciales + filterInput.addEventListener('keydown', function(e) { + console.log('🔍 Filtre KEYDOWN:', e.key); + + if (e.key === 'Escape') { + e.preventDefault(); + e.stopPropagation(); + clearStudentFilter(); + } else if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + filterIsActive = false; + const firstVisibleInput = document.querySelector('.student-row:not([style*="display: none"]) .grading-input'); + if (firstVisibleInput) { + firstVisibleInput.focus(); + } + } + // Pour les flèches et Tab, empêcher la navigation + else if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key)) { + e.preventDefault(); + e.stopPropagation(); + } + }); +} + +// Filtrer les élèves selon le terme de recherche +function filterStudents(searchTerm) { + console.log('🔍 filterStudents appelée avec:', searchTerm); + + const studentRows = document.querySelectorAll('.student-row'); + const studentCount = document.getElementById('student-count'); + let visibleCount = 0; + let visibleIndex = 1; + + console.log('🔍 Nombre de lignes d\'élèves trouvées:', studentRows.length); + + studentRows.forEach((row, index) => { + const studentName = row.dataset.studentName; + const isVisible = !searchTerm || studentName.includes(searchTerm); + + console.log(`🔍 Élève ${index + 1}: "${studentName}" - Visible: ${isVisible}`); + + if (isVisible) { + row.style.display = ''; + visibleCount++; + // Mettre à jour l'index visible + const indexEl = row.querySelector('.student-index'); + if (indexEl) { + indexEl.textContent = visibleIndex; + } + visibleIndex++; + } else { + row.style.display = 'none'; + } + }); + + console.log('🔍 Résultats du filtrage:', visibleCount, 'élèves visibles sur', studentRows.length); + + // Mettre à jour le compteur + if (studentCount) { + const totalStudents = studentRows.length; + if (searchTerm) { + studentCount.textContent = `${visibleCount} / ${totalStudents} élève(s)`; + studentCount.className = visibleCount === 0 ? 'text-xs text-red-500' : 'text-xs text-blue-600'; + } else { + studentCount.textContent = `${totalStudents} élève(s)`; + studentCount.className = 'text-xs text-gray-500'; + } + } + + // Recalculer les variables globales pour la navigation + totalRows = visibleCount; + + // Réinitialiser la position si l'élève actuel n'est plus visible + const currentRowEl = document.querySelector(`.student-row:nth-child(${currentRow + 1})`); + if (currentRowEl && currentRowEl.style.display === 'none') { + // Trouver le premier élève visible + const firstVisibleRow = document.querySelector('.student-row:not([style*="display: none"])'); + if (firstVisibleRow) { + const firstVisibleInput = firstVisibleRow.querySelector('.grading-input'); + if (firstVisibleInput && !filterIsActive) { + firstVisibleInput.focus(); + } + } + } + + updateProgressIndicator(); +} + +// Nettoyer le filtre +function clearStudentFilter() { + const filterInput = document.getElementById('student-filter'); + const clearButton = document.getElementById('clear-filter'); + + if (filterInput) { + filterInput.value = ''; + filterStudents(''); + clearButton.classList.add('hidden'); + // Garder le focus et l'état actif + filterIsActive = true; + setTimeout(() => { + filterInput.focus(); + }, 10); + } +} + // Gestion de la fermeture de la page avec modifications non sauvegardées window.addEventListener('beforeunload', function(e) { if (unsavedChanges.size > 0) { diff --git a/templates/config/general.html b/templates/config/general.html index f7c6090..36bce53 100644 --- a/templates/config/general.html +++ b/templates/config/general.html @@ -86,10 +86,10 @@ name="default_grading_system" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" > - -
+ +
Élève -
{{ element.label }}
-
+ {% if element.exercise.title not in exercise_elements %} + {% set _ = exercise_elements.update({element.exercise.title: []}) %} + {% endif %} + {% set _ = exercise_elements[element.exercise.title].append(element) %} + {% endfor %} + + {% for exercise_title, elements in exercise_elements.items() %} +
+ {{ exercise_title }} +
{{ elements|length }} élément{{ 's' if elements|length > 1 else '' }}
+
+ + +
{{ element.label }}
+ + {% if element.description %} +
{{ element.description }}
+ {% endif %} + +
{% if element.grading_type == 'score' %} - + 0-3 {% else %} - + /{{ element.max_points }} {% endif %}
+ {% if element.skill %} -
{{ element.skill }}
+
{{ element.skill }}
{% endif %}
- {{ student.first_name }} {{ student.last_name }} +
+
+
{{ loop.index }}
+
{{ student.first_name }} {{ student.last_name }}
+
+
{% if element.grading_type == 'score' %}