|
|
|
|
@@ -100,6 +100,37 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Filtre des élèves -->
|
|
|
|
|
<div class="bg-white shadow rounded-lg overflow-hidden mb-4">
|
|
|
|
|
<div class="px-4 py-3 border-b border-gray-200">
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<h3 class="text-sm font-medium text-gray-700">Filtrer les élèves</h3>
|
|
|
|
|
<span id="student-count" class="text-xs text-gray-500">{{ students|length }} élève(s)</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="p-4">
|
|
|
|
|
<div class="relative">
|
|
|
|
|
<input type="text"
|
|
|
|
|
id="student-filter"
|
|
|
|
|
placeholder="Rechercher un élève par nom ou prénom..."
|
|
|
|
|
class="w-full px-3 py-2 pl-10 text-sm border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
|
|
|
<svg class="h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
|
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="button"
|
|
|
|
|
id="clear-filter"
|
|
|
|
|
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 hidden"
|
|
|
|
|
onclick="clearStudentFilter()">
|
|
|
|
|
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
|
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Tableau de saisie -->
|
|
|
|
|
<div class="bg-white shadow rounded-lg overflow-hidden">
|
|
|
|
|
<div class="px-4 py-3 border-b border-gray-200">
|
|
|
|
|
@@ -108,47 +139,94 @@
|
|
|
|
|
|
|
|
|
|
<div class="overflow-x-auto">
|
|
|
|
|
<table class="min-w-full divide-y divide-gray-200 table-fixed">
|
|
|
|
|
<thead class="bg-gray-50">
|
|
|
|
|
<tr>
|
|
|
|
|
<th scope="col" class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gray-50 border-r border-gray-200 w-48">
|
|
|
|
|
<!-- En-têtes des exercices -->
|
|
|
|
|
<thead class="bg-gradient-to-r from-indigo-50 to-purple-50">
|
|
|
|
|
<tr class="border-b-2 border-indigo-200">
|
|
|
|
|
<th scope="col" class="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gradient-to-r from-indigo-50 to-purple-50 border-r border-indigo-200 w-48">
|
|
|
|
|
Élève
|
|
|
|
|
</th>
|
|
|
|
|
{% set current_exercise = '' %}
|
|
|
|
|
{% set exercise_elements = {} %}
|
|
|
|
|
{% for element in grading_elements %}
|
|
|
|
|
<th scope="col" class="grading-header px-2 py-2 text-center text-xs font-medium text-gray-500 uppercase tracking-wider min-w-28">
|
|
|
|
|
<div class="element-label text-xs font-semibold text-gray-900">{{ element.label }}</div>
|
|
|
|
|
<div class="element-type font-normal text-xs mt-1">
|
|
|
|
|
{% 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() %}
|
|
|
|
|
<th scope="colgroup" colspan="{{ elements|length }}" class="px-2 py-1.5 text-center text-sm font-bold text-indigo-900 bg-gradient-to-r from-indigo-100 to-purple-100 border-x border-indigo-300">
|
|
|
|
|
{{ exercise_title }}
|
|
|
|
|
<div class="text-xs font-normal text-indigo-700 mt-0.5">{{ elements|length }} élément{{ 's' if elements|length > 1 else '' }}</div>
|
|
|
|
|
</th>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
|
|
|
|
|
<!-- En-têtes des éléments de notation -->
|
|
|
|
|
<thead class="bg-gray-50">
|
|
|
|
|
<tr>
|
|
|
|
|
<th scope="col" class="px-3 py-1.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider sticky left-0 bg-gray-50 border-r border-gray-200 w-48">
|
|
|
|
|
<!-- Vide pour alignement -->
|
|
|
|
|
</th>
|
|
|
|
|
{% 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 %}
|
|
|
|
|
|
|
|
|
|
<th scope="col" class="grading-header px-2 py-1.5 text-center text-xs font-medium text-gray-500 uppercase tracking-wider min-w-28 {{ exercise_color }} bg-gradient-to-b from-gray-50 to-gray-100">
|
|
|
|
|
<div class="element-label text-xs font-semibold text-gray-900 truncate">{{ element.label }}</div>
|
|
|
|
|
|
|
|
|
|
{% if element.description %}
|
|
|
|
|
<div class="text-xs text-gray-600 mt-1 leading-tight">{{ element.description }}</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
|
|
|
|
<div class="element-type font-normal text-xs mt-1 flex justify-center">
|
|
|
|
|
{% if element.grading_type == 'score' %}
|
|
|
|
|
<span class="badge-score inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
|
|
|
|
|
<span class="badge-score inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800">
|
|
|
|
|
0-3
|
|
|
|
|
</span>
|
|
|
|
|
{% else %}
|
|
|
|
|
<span class="badge-notes inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
|
|
|
|
<span class="badge-notes inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
|
|
|
|
|
/{{ element.max_points }}
|
|
|
|
|
</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% if element.skill %}
|
|
|
|
|
<div class="text-xs text-gray-400 mt-1 truncate">{{ element.skill }}</div>
|
|
|
|
|
<div class="text-xs text-gray-400 mt-0.5 truncate" title="{{ element.skill }}">{{ element.skill }}</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</th>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody class="bg-white divide-y divide-gray-100">
|
|
|
|
|
<tbody class="bg-white divide-y divide-gray-50">
|
|
|
|
|
{% for student in students %}
|
|
|
|
|
<tr class="hover:bg-gray-50 text-sm">
|
|
|
|
|
<td class="px-3 py-2 whitespace-nowrap text-sm font-medium text-gray-900 sticky left-0 bg-white border-r border-gray-200">
|
|
|
|
|
{{ student.first_name }} {{ student.last_name }}
|
|
|
|
|
<tr class="student-row hover:bg-gray-25 text-sm transition-colors duration-150" data-student-name="{{ (student.first_name + ' ' + student.last_name)|lower }}">
|
|
|
|
|
<td class="px-3 py-1.5 whitespace-nowrap text-sm font-medium text-gray-900 sticky left-0 bg-white border-r border-gray-200">
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
<div class="text-xs text-gray-500 w-6 student-index">{{ loop.index }}</div>
|
|
|
|
|
<div>{{ student.first_name }} {{ student.last_name }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
{% 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) %}
|
|
|
|
|
<td class="px-1 py-2 whitespace-nowrap text-center">
|
|
|
|
|
<td class="px-1 py-1.5 whitespace-nowrap text-center {{ exercise_border_color if loop.first or (grading_elements[loop.index0-1].exercise.title != element.exercise.title) else '' }} {{ exercise_bg_color }}">
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<!-- Champs de saisie unifiés -->
|
|
|
|
|
{% if element.grading_type == 'score' %}
|
|
|
|
|
<select name="grade_{{ student.id }}_{{ element.id }}"
|
|
|
|
|
class="grading-input block w-full text-xs border-gray-300 rounded-md focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all duration-200 py-1"
|
|
|
|
|
class="grading-input block w-full text-xs border-gray-300 rounded focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all duration-200 py-0.5 h-7"
|
|
|
|
|
data-type="score"
|
|
|
|
|
data-student-id="{{ student.id }}"
|
|
|
|
|
data-element-id="{{ element.id }}"
|
|
|
|
|
@@ -173,14 +251,14 @@
|
|
|
|
|
<input type="text"
|
|
|
|
|
name="grade_{{ student.id }}_{{ element.id }}"
|
|
|
|
|
value="{% if existing_grade %}{{ existing_grade.value }}{% endif %}"
|
|
|
|
|
class="grading-input block w-full text-xs border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-center transition-all duration-200 py-1"
|
|
|
|
|
class="grading-input block w-full text-xs border-gray-300 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-center transition-all duration-200 py-0.5 h-7"
|
|
|
|
|
data-type="notes"
|
|
|
|
|
data-max-points="{{ element.max_points }}"
|
|
|
|
|
data-student-id="{{ student.id }}"
|
|
|
|
|
data-element-id="{{ element.id }}"
|
|
|
|
|
data-row="{{ loop.index0 }}"
|
|
|
|
|
data-col="{{ loop.index0 }}"
|
|
|
|
|
placeholder="0-{{ element.max_points }} ou {% for v in scale_values.keys() if not v.isdigit() %}{{ v }}{% if not loop.last %} {% endif %}{% endfor %}"
|
|
|
|
|
placeholder="0-{{ element.max_points }}"
|
|
|
|
|
oninput="handleGradeChange(this)"
|
|
|
|
|
onfocus="handleGradeFocus(this)"
|
|
|
|
|
onkeydown="handleGradeKeydown(event, this)">
|
|
|
|
|
@@ -188,12 +266,13 @@
|
|
|
|
|
<input type="text"
|
|
|
|
|
name="comment_{{ student.id }}_{{ element.id }}"
|
|
|
|
|
value="{% if existing_grade and existing_grade.comment %}{{ existing_grade.comment }}{% endif %}"
|
|
|
|
|
class="comment-input block w-full text-xs border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 py-0.5"
|
|
|
|
|
class="comment-input block w-full text-xs border-gray-200 rounded focus:ring-blue-400 focus:border-blue-400 transition-all duration-200 py-0.5 h-6 bg-gray-25"
|
|
|
|
|
data-student-id="{{ student.id }}"
|
|
|
|
|
data-element-id="{{ element.id }}"
|
|
|
|
|
data-row="{{ loop.index0 }}"
|
|
|
|
|
data-col="{{ loop.index0 }}"
|
|
|
|
|
placeholder="Commentaire (optionnel)"
|
|
|
|
|
placeholder="💬"
|
|
|
|
|
title="Commentaire (optionnel)"
|
|
|
|
|
onkeydown="handleCommentKeydown(event, this)">
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
@@ -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) {
|
|
|
|
|
|