Compare commits
3 Commits
0ed5edfa0b
...
898401ed2c
Author | SHA1 | Date | |
---|---|---|---|
898401ed2c | |||
a28777f1e1 | |||
df5a4b31b5 |
69
models.py
69
models.py
@@ -358,6 +358,75 @@ class Assessment(db.Model):
|
|||||||
total += element.max_points
|
total += element.max_points
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
def get_domains_distribution(self):
|
||||||
|
"""
|
||||||
|
Retourne la distribution des points par domaine pour cette évaluation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict]: Liste des domaines avec leurs points et couleurs
|
||||||
|
[{'name': str, 'points': float, 'color': str}, ...]
|
||||||
|
"""
|
||||||
|
domains_points = {}
|
||||||
|
|
||||||
|
for exercise in self.exercises:
|
||||||
|
for element in exercise.grading_elements:
|
||||||
|
if element.domain:
|
||||||
|
domain_name = element.domain.name
|
||||||
|
domain_color = element.domain.color
|
||||||
|
if domain_name not in domains_points:
|
||||||
|
domains_points[domain_name] = {'points': 0, 'color': domain_color}
|
||||||
|
domains_points[domain_name]['points'] += element.max_points
|
||||||
|
|
||||||
|
# Convertir en liste triée par nombre de points (descendant)
|
||||||
|
domains_list = []
|
||||||
|
for domain_name, data in domains_points.items():
|
||||||
|
domains_list.append({
|
||||||
|
'name': domain_name,
|
||||||
|
'points': data['points'],
|
||||||
|
'color': data['color']
|
||||||
|
})
|
||||||
|
|
||||||
|
# Trier par nombre de points décroissant
|
||||||
|
domains_list.sort(key=lambda x: x['points'], reverse=True)
|
||||||
|
return domains_list
|
||||||
|
|
||||||
|
def get_skills_distribution(self):
|
||||||
|
"""
|
||||||
|
Retourne la distribution des points par compétence pour cette évaluation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict]: Liste des compétences avec leurs points et couleurs
|
||||||
|
[{'name': str, 'points': float, 'color': str}, ...]
|
||||||
|
"""
|
||||||
|
skills_points = {}
|
||||||
|
|
||||||
|
for exercise in self.exercises:
|
||||||
|
for element in exercise.grading_elements:
|
||||||
|
if element.skill:
|
||||||
|
skill_name = element.skill
|
||||||
|
if skill_name not in skills_points:
|
||||||
|
skills_points[skill_name] = 0
|
||||||
|
skills_points[skill_name] += element.max_points
|
||||||
|
|
||||||
|
# Récupérer les couleurs des compétences depuis la base de données
|
||||||
|
competences_colors = {}
|
||||||
|
competences = Competence.query.all()
|
||||||
|
for comp in competences:
|
||||||
|
competences_colors[comp.name] = comp.color
|
||||||
|
|
||||||
|
# Convertir en liste triée par nombre de points (descendant)
|
||||||
|
skills_list = []
|
||||||
|
for skill_name, points in skills_points.items():
|
||||||
|
skills_list.append({
|
||||||
|
'name': skill_name,
|
||||||
|
'points': points,
|
||||||
|
'color': competences_colors.get(skill_name, '#6B7280') # Couleur par défaut si non trouvée
|
||||||
|
})
|
||||||
|
|
||||||
|
# Trier par nombre de points décroissant
|
||||||
|
skills_list.sort(key=lambda x: x['points'], reverse=True)
|
||||||
|
return skills_list
|
||||||
|
|
||||||
class Exercise(db.Model):
|
class Exercise(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
assessment_id = db.Column(db.Integer, db.ForeignKey('assessment.id'), nullable=False)
|
assessment_id = db.Column(db.Integer, db.ForeignKey('assessment.id'), nullable=False)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from models import Assessment, ClassGroup, Exercise
|
from models import Assessment, ClassGroup, Exercise, GradingElement
|
||||||
from .base_repository import BaseRepository
|
from .base_repository import BaseRepository
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class AssessmentRepository(BaseRepository[Assessment]):
|
|||||||
"""Trouve une évaluation avec tous ses détails."""
|
"""Trouve une évaluation avec tous ses détails."""
|
||||||
return Assessment.query.options(
|
return Assessment.query.options(
|
||||||
joinedload(Assessment.class_group),
|
joinedload(Assessment.class_group),
|
||||||
joinedload(Assessment.exercises).joinedload(Exercise.grading_elements)
|
joinedload(Assessment.exercises).joinedload(Exercise.grading_elements).joinedload(GradingElement.domain)
|
||||||
).filter_by(id=id).first()
|
).filter_by(id=id).first()
|
||||||
|
|
||||||
def get_or_404(self, id: int) -> Assessment:
|
def get_or_404(self, id: int) -> Assessment:
|
||||||
|
@@ -153,10 +153,10 @@ def edit(id):
|
|||||||
competences = config_manager.get_competences_list()
|
competences = config_manager.get_competences_list()
|
||||||
domains = config_manager.get_domains_list()
|
domains = config_manager.get_domains_list()
|
||||||
|
|
||||||
return render_template('assessment_form_unified.html',
|
return render_template('assessment_form.html',
|
||||||
form=form,
|
form=form,
|
||||||
title='Modifier l\'évaluation complète',
|
title='Modifier l\'évaluation',
|
||||||
assessment=assessment,
|
assessment=assessment,
|
||||||
exercises_json=exercises_data,
|
exercises_json=exercises_data,
|
||||||
is_edit=True,
|
is_edit=True,
|
||||||
competences=competences,
|
competences=competences,
|
||||||
@@ -178,9 +178,9 @@ def new():
|
|||||||
competences = config_manager.get_competences_list()
|
competences = config_manager.get_competences_list()
|
||||||
domains = config_manager.get_domains_list()
|
domains = config_manager.get_domains_list()
|
||||||
|
|
||||||
return render_template('assessment_form_unified.html',
|
return render_template('assessment_form.html',
|
||||||
form=form,
|
form=form,
|
||||||
title='Nouvelle évaluation complète',
|
title='Nouvelle évaluation',
|
||||||
competences=competences,
|
competences=competences,
|
||||||
domains=domains)
|
domains=domains)
|
||||||
|
|
||||||
|
@@ -216,7 +216,46 @@
|
|||||||
<span class="text-sm font-medium text-gray-500">Exercices</span>
|
<span class="text-sm font-medium text-gray-500">Exercices</span>
|
||||||
<span class="text-sm font-bold text-gray-900">{{ assessment.exercises|length }}</span>
|
<span class="text-sm font-bold text-gray-900">{{ assessment.exercises|length }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-sm font-medium text-gray-500">Total points</span>
|
||||||
|
<span class="text-sm font-bold text-purple-600">{{ "%.1f"|format(assessment.get_total_max_points()) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Domaines -->
|
||||||
|
{% set domains = assessment.get_domains_distribution() %}
|
||||||
|
{% if domains %}
|
||||||
|
<div class="border-t border-gray-200 pt-4">
|
||||||
|
<span class="text-sm font-medium text-gray-500 mb-2 block">Domaines</span>
|
||||||
|
<div class="space-y-1">
|
||||||
|
{% for domain in domains %}
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-3 h-3 rounded-full mr-2" style="background-color: {{ domain.color }};"></div>
|
||||||
|
<span class="text-xs text-gray-700">{{ domain.name }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-bold text-gray-900">{{ "%.1f"|format(domain.points) }} pts</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Compétences -->
|
||||||
|
{% set skills = assessment.get_skills_distribution() %}
|
||||||
|
{% if skills %}
|
||||||
|
<div class="border-t border-gray-200 pt-4">
|
||||||
|
<span class="text-sm font-medium text-gray-500 mb-2 block">Compétences</span>
|
||||||
|
<div class="space-y-1">
|
||||||
|
{% for skill in skills %}
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs text-gray-700">{{ skill.name }}</span>
|
||||||
|
<span class="text-xs font-bold text-gray-900">{{ "%.1f"|format(skill.points) }} pts</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if assessment.description %}
|
{% if assessment.description %}
|
||||||
<div class="border-t border-gray-200 pt-4">
|
<div class="border-t border-gray-200 pt-4">
|
||||||
<span class="text-sm font-medium text-gray-500">Description</span>
|
<span class="text-sm font-medium text-gray-500">Description</span>
|
||||||
@@ -334,7 +373,57 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Diagrammes de répartition -->
|
||||||
|
{% set domains = assessment.get_domains_distribution() %}
|
||||||
|
{% set skills = assessment.get_skills_distribution() %}
|
||||||
|
{% if domains or skills %}
|
||||||
|
<div class="bg-white shadow rounded-xl">
|
||||||
|
<div class="px-6 py-5 border-b border-gray-200">
|
||||||
|
<h2 class="text-xl font-bold text-gray-900">Répartition des points</h2>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-6">
|
||||||
|
<div class="grid grid-cols-1 {% if domains and skills %}md:grid-cols-2{% endif %} gap-8">
|
||||||
|
<!-- Diagramme des domaines -->
|
||||||
|
{% if domains %}
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Par domaines</h3>
|
||||||
|
<div class="relative">
|
||||||
|
<canvas id="domainsChart" width="300" height="300" class="mx-auto"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 space-y-2">
|
||||||
|
{% for domain in domains %}
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<div class="w-3 h-3 rounded-full mr-2" style="background-color: {{ domain.color }};"></div>
|
||||||
|
<span class="text-sm text-gray-700">{{ domain.name }} ({{ "%.1f"|format(domain.points) }} pts)</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Diagramme des compétences -->
|
||||||
|
{% if skills %}
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">Par compétences</h3>
|
||||||
|
<div class="relative">
|
||||||
|
<canvas id="skillsChart" width="300" height="300" class="mx-auto"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 space-y-2 max-h-48 overflow-y-auto">
|
||||||
|
{% for skill in skills %}
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<div class="w-3 h-3 rounded-full mr-2" style="background-color: {{ skill.color }};"></div>
|
||||||
|
<span class="text-sm text-gray-700">{{ skill.name }} ({{ "%.1f"|format(skill.points) }} pts)</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Actions secondaires -->
|
<!-- Actions secondaires -->
|
||||||
<div class="bg-white shadow rounded-xl">
|
<div class="bg-white shadow rounded-xl">
|
||||||
<div class="px-6 py-5 border-b border-gray-200">
|
<div class="px-6 py-5 border-b border-gray-200">
|
||||||
@@ -369,4 +458,84 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Chart.js pour les diagrammes -->
|
||||||
|
{% if domains or skills %}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Configuration commune pour les graphiques
|
||||||
|
const chartOptions = {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false // Légende externe
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
const label = context.label || '';
|
||||||
|
const value = context.parsed;
|
||||||
|
const percentage = ((value / context.dataset.data.reduce((a, b) => a + b, 0)) * 100).toFixed(1);
|
||||||
|
return `${label}: ${value} pts (${percentage}%)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Diagramme des domaines
|
||||||
|
{% if domains %}
|
||||||
|
const domainsData = {
|
||||||
|
labels: [{% for domain in domains %}'{{ domain.name }}'{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||||
|
datasets: [{
|
||||||
|
data: [{% for domain in domains %}{{ domain.points }}{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||||
|
backgroundColor: [{% for domain in domains %}'{{ domain.color }}'{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#ffffff'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
const domainsCtx = document.getElementById('domainsChart');
|
||||||
|
if (domainsCtx) {
|
||||||
|
new Chart(domainsCtx, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: domainsData,
|
||||||
|
options: {
|
||||||
|
...chartOptions,
|
||||||
|
cutout: '60%'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
// Diagramme des compétences
|
||||||
|
{% if skills %}
|
||||||
|
const skillsData = {
|
||||||
|
labels: [{% for skill in skills %}'{{ skill.name }}'{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||||
|
datasets: [{
|
||||||
|
data: [{% for skill in skills %}{{ skill.points }}{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||||
|
backgroundColor: [{% for skill in skills %}'{{ skill.color }}'{% if not loop.last %}, {% endif %}{% endfor %}],
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#ffffff'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
const skillsCtx = document.getElementById('skillsChart');
|
||||||
|
if (skillsCtx) {
|
||||||
|
new Chart(skillsCtx, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: skillsData,
|
||||||
|
options: {
|
||||||
|
...chartOptions,
|
||||||
|
cutout: '60%'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
1247
templates/assessment_form.html
Normal file
1247
templates/assessment_form.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -220,9 +220,17 @@
|
|||||||
{% for student in students %}
|
{% for student in students %}
|
||||||
<tr class="student-row hover:bg-gray-25 text-sm transition-colors duration-150" data-student-name="{{ (student.first_name + ' ' + student.last_name)|lower }}">
|
<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">
|
<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="flex items-center justify-between">
|
||||||
<div class="text-xs text-gray-500 w-6 student-index">{{ loop.index }}</div>
|
<div class="flex items-center">
|
||||||
<div>{{ student.first_name }} {{ student.last_name }}</div>
|
<div class="text-xs text-gray-500 w-6 student-index">{{ loop.index }}</div>
|
||||||
|
<div>{{ student.first_name }} {{ student.last_name }}</div>
|
||||||
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
onclick="openCompleteModal({{ student.id }})"
|
||||||
|
class="ml-2 text-xs bg-blue-100 hover:bg-blue-200 text-blue-700 px-2 py-1 rounded transition-colors"
|
||||||
|
title="Compléter les notes">
|
||||||
|
⚡
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{% set current_exercise = '' %}
|
{% set current_exercise = '' %}
|
||||||
@@ -437,6 +445,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de complétion des notes -->
|
||||||
|
<div id="complete-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-[10000]">
|
||||||
|
<div class="flex items-center justify-center min-h-screen p-4">
|
||||||
|
<div class="bg-white rounded-lg p-6 w-96 max-w-full">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900">Compléter les notes</h3>
|
||||||
|
<button onclick="closeCompleteModal()" class="text-gray-400 hover:text-gray-600">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="complete-value" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Valeur à attribuer :
|
||||||
|
</label>
|
||||||
|
<select id="complete-value" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<!-- Options générées dynamiquement par JavaScript -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-6">
|
||||||
|
<label class="flex items-center">
|
||||||
|
<input type="checkbox" id="overwrite-existing" class="mr-3 h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
||||||
|
<span class="text-sm text-gray-700">Écraser les valeurs existantes</span>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-gray-500 mt-2 ml-7">
|
||||||
|
Si décoché, seuls les champs vides seront modifiés
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end space-x-3">
|
||||||
|
<button onclick="closeCompleteModal()"
|
||||||
|
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
<button onclick="executeComplete()"
|
||||||
|
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Appliquer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ====== CONFIGURATION GLOBALE ======
|
// ====== CONFIGURATION GLOBALE ======
|
||||||
const GRADING_CONFIG = {
|
const GRADING_CONFIG = {
|
||||||
@@ -1377,5 +1431,97 @@ window.addEventListener('beforeunload', function(e) {
|
|||||||
e.returnValue = 'Vous avez des modifications non sauvegardées. Êtes-vous sûr de vouloir quitter ?';
|
e.returnValue = 'Vous avez des modifications non sauvegardées. Êtes-vous sûr de vouloir quitter ?';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ====== GESTIONNAIRE DE COMPLÉTION DES NOTES ======
|
||||||
|
let currentStudentId = null;
|
||||||
|
|
||||||
|
function populateSpecialValues() {
|
||||||
|
const select = document.getElementById('complete-value');
|
||||||
|
select.innerHTML = '';
|
||||||
|
|
||||||
|
// Récupérer les valeurs spéciales depuis la config existante
|
||||||
|
for (const [value, config] of Object.entries(GRADING_CONFIG.scale_values)) {
|
||||||
|
if (GRADING_CONFIG.special_keys.includes(value)) {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = value;
|
||||||
|
option.textContent = `${value} - ${config.label}`;
|
||||||
|
option.style.color = config.color;
|
||||||
|
select.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCompleteModal(studentId) {
|
||||||
|
currentStudentId = studentId;
|
||||||
|
populateSpecialValues();
|
||||||
|
document.getElementById('complete-modal').classList.remove('hidden');
|
||||||
|
|
||||||
|
// Focus sur le select pour une meilleure UX
|
||||||
|
setTimeout(() => {
|
||||||
|
document.getElementById('complete-value').focus();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCompleteModal() {
|
||||||
|
document.getElementById('complete-modal').classList.add('hidden');
|
||||||
|
currentStudentId = null;
|
||||||
|
|
||||||
|
// Réinitialiser les valeurs
|
||||||
|
document.getElementById('overwrite-existing').checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeComplete() {
|
||||||
|
if (!currentStudentId) {
|
||||||
|
showToast('Erreur: aucun élève sélectionné', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = document.getElementById('complete-value').value;
|
||||||
|
const overwrite = document.getElementById('overwrite-existing').checked;
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
showToast('Veuillez sélectionner une valeur', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const studentInputs = document.querySelectorAll(`[data-student-id="${currentStudentId}"].grading-input`);
|
||||||
|
let modifiedCount = 0;
|
||||||
|
|
||||||
|
studentInputs.forEach(input => {
|
||||||
|
const isEmpty = !input.value || input.value.trim() === '';
|
||||||
|
|
||||||
|
if (isEmpty || overwrite) {
|
||||||
|
if (input.tagName === 'SELECT') {
|
||||||
|
// Pour les selects (scores)
|
||||||
|
input.value = value;
|
||||||
|
} else {
|
||||||
|
// Pour les inputs (notes)
|
||||||
|
input.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Déclencher la validation, coloration et sauvegarde automatique
|
||||||
|
handleGradeChange(input);
|
||||||
|
modifiedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
closeCompleteModal();
|
||||||
|
|
||||||
|
// Message de confirmation avec le label de la valeur
|
||||||
|
const config = GRADING_CONFIG.scale_values[value];
|
||||||
|
const label = config ? config.label : value;
|
||||||
|
const message = overwrite
|
||||||
|
? `${modifiedCount} champs modifiés avec "${value}" (${label})`
|
||||||
|
: `${modifiedCount} champs vides complétés avec "${value}" (${label})`;
|
||||||
|
|
||||||
|
showToast(message, 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestion de la fermeture de la modal avec Escape
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && !document.getElementById('complete-modal').classList.contains('hidden')) {
|
||||||
|
closeCompleteModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Reference in New Issue
Block a user