875 lines
28 KiB
PHP
875 lines
28 KiB
PHP
<?php
|
|
/**
|
|
* INTERFACE RÉSULTATS ÉVALUATION
|
|
* Affichage complet : stats + graphiques + tableau notes
|
|
*
|
|
* Graphiques:
|
|
* - Histogramme distribution notes par classe
|
|
* - Courbe progression par élève (Top 5 + moyenne)
|
|
*/
|
|
|
|
require_once '../config/database.php';
|
|
require_once '../config/session.php';
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
SessionManager::startSession();
|
|
}
|
|
|
|
// Vérifier authentification enseignant
|
|
if (!isset($_SESSION['user_id']) || $_SESSION['type_libelle'] !== 'enseignant') {
|
|
header('Location: ../login.php?error=access_denied');
|
|
exit;
|
|
}
|
|
|
|
$id_evaluation = isset($_GET['id_evaluation']) ? (int)$_GET['id_evaluation'] : 0;
|
|
|
|
if ($id_evaluation <= 0) {
|
|
die('ID évaluation invalide');
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Résultats Évaluation</title>
|
|
|
|
<!-- Chart.js -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
background: #f8fafc;
|
|
color: #1e293b;
|
|
padding: 20px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* === HEADER === */
|
|
.header {
|
|
background: white;
|
|
padding: 20px 30px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 15px;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 24px;
|
|
color: #1e293b;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #3b82f6;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #2563eb;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #e2e8f0;
|
|
color: #475569;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: #cbd5e1;
|
|
}
|
|
|
|
/* === LOADING === */
|
|
.loading {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
color: #64748b;
|
|
}
|
|
|
|
.loading-spinner {
|
|
display: inline-block;
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 4px solid #e2e8f0;
|
|
border-top-color: #3b82f6;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* === STATISTIQUES === */
|
|
.stats-section {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
color: #1e293b;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 14px;
|
|
color: #64748b;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.stat-card.success .stat-value {
|
|
color: #22c55e;
|
|
}
|
|
|
|
.stat-card.warning .stat-value {
|
|
color: #f59e0b;
|
|
}
|
|
|
|
.stat-card.danger .stat-value {
|
|
color: #ef4444;
|
|
}
|
|
|
|
/* === GRAPHIQUES === */
|
|
.charts-section {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.chart-container {
|
|
background: white;
|
|
padding: 25px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.chart-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.chart-canvas {
|
|
max-height: 400px;
|
|
}
|
|
|
|
/* === TABLEAU === */
|
|
.table-section {
|
|
background: white;
|
|
padding: 25px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.table-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
flex-wrap: wrap;
|
|
gap: 15px;
|
|
}
|
|
|
|
.table-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
}
|
|
|
|
.table-filters {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.filter-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
}
|
|
|
|
.filter-label {
|
|
font-size: 12px;
|
|
color: #64748b;
|
|
font-weight: 500;
|
|
}
|
|
|
|
select, input[type="text"] {
|
|
padding: 8px 12px;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
color: #1e293b;
|
|
background: white;
|
|
}
|
|
|
|
select:focus, input[type="text"]:focus {
|
|
outline: none;
|
|
border-color: #3b82f6;
|
|
}
|
|
|
|
.table-responsive {
|
|
overflow-x: auto;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 14px;
|
|
}
|
|
|
|
th {
|
|
background: #f8fafc;
|
|
padding: 12px;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
color: #475569;
|
|
border-bottom: 2px solid #e2e8f0;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
position: relative;
|
|
}
|
|
|
|
th:hover {
|
|
background: #f1f5f9;
|
|
}
|
|
|
|
th.sortable::after {
|
|
content: ' ↕';
|
|
color: #cbd5e1;
|
|
font-size: 12px;
|
|
}
|
|
|
|
th.sort-asc::after {
|
|
content: ' ↑';
|
|
color: #3b82f6;
|
|
}
|
|
|
|
th.sort-desc::after {
|
|
content: ' ↓';
|
|
color: #3b82f6;
|
|
}
|
|
|
|
td {
|
|
padding: 12px;
|
|
border-bottom: 1px solid #f1f5f9;
|
|
}
|
|
|
|
tr:hover {
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.rang {
|
|
font-weight: 700;
|
|
color: #3b82f6;
|
|
}
|
|
|
|
.note {
|
|
font-weight: 600;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.note.excellent {
|
|
color: #22c55e;
|
|
}
|
|
|
|
.note.good {
|
|
color: #10b981;
|
|
}
|
|
|
|
.note.average {
|
|
color: #f59e0b;
|
|
}
|
|
|
|
.note.bad {
|
|
color: #ef4444;
|
|
}
|
|
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 4px 10px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.badge.termine {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.badge.en-cours {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.table-footer {
|
|
margin-top: 15px;
|
|
padding-top: 15px;
|
|
border-top: 1px solid #e2e8f0;
|
|
text-align: center;
|
|
color: #64748b;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* === RESPONSIVE === */
|
|
@media (max-width: 768px) {
|
|
.header {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.header-actions {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.table-filters {
|
|
flex-direction: column;
|
|
}
|
|
|
|
table {
|
|
font-size: 12px;
|
|
}
|
|
|
|
th, td {
|
|
padding: 8px;
|
|
}
|
|
}
|
|
|
|
/* === EMPTY STATE === */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
color: #64748b;
|
|
}
|
|
|
|
.empty-state-icon {
|
|
font-size: 64px;
|
|
margin-bottom: 15px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.empty-state-text {
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<!-- Header -->
|
|
<div class="header">
|
|
<h1>
|
|
<span>📊</span>
|
|
<span id="eval-title">Résultats Évaluation</span>
|
|
</h1>
|
|
<div class="header-actions">
|
|
<a href="dashboard.php" class="btn btn-secondary">
|
|
← Retour Dashboard
|
|
</a>
|
|
<a href="export.php?id_evaluation=<?= $id_evaluation ?>&format=csv" class="btn btn-primary" id="btn-export">
|
|
📥 Export CSV
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading -->
|
|
<div id="loading" class="loading">
|
|
<div class="loading-spinner"></div>
|
|
<p style="margin-top: 15px;">Chargement des résultats...</p>
|
|
</div>
|
|
|
|
<!-- Content (hidden initially) -->
|
|
<div id="content" style="display: none;">
|
|
<!-- Statistiques -->
|
|
<div class="stats-section">
|
|
<div class="stats-grid" id="stats-grid">
|
|
<!-- Remplies dynamiquement -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Graphiques -->
|
|
<div class="charts-section">
|
|
<!-- Histogramme par classe -->
|
|
<div class="chart-container">
|
|
<div class="chart-title">
|
|
<span>📊</span>
|
|
<span>Distribution des Notes par Classe</span>
|
|
</div>
|
|
<canvas id="chart-histogramme" class="chart-canvas"></canvas>
|
|
</div>
|
|
|
|
<!-- Courbe progression -->
|
|
<div class="chart-container">
|
|
<div class="chart-title">
|
|
<span>📈</span>
|
|
<span>Progression par Élève (Top 5 + Moyenne)</span>
|
|
</div>
|
|
<canvas id="chart-progression" class="chart-canvas"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tableau -->
|
|
<div class="table-section">
|
|
<div class="table-header">
|
|
<div class="table-title">📋 Tableau Détaillé</div>
|
|
<div class="table-filters">
|
|
<div class="filter-group">
|
|
<label class="filter-label">Classe</label>
|
|
<select id="filter-classe">
|
|
<option value="">Toutes les classes</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label class="filter-label">Statut</label>
|
|
<select id="filter-statut">
|
|
<option value="">Tous</option>
|
|
<option value="terminee">Terminé</option>
|
|
<option value="en_cours">En cours</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label class="filter-label">Recherche</label>
|
|
<input type="text" id="search-eleve" placeholder="Nom ou prénom...">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table id="table-resultats">
|
|
<thead>
|
|
<tr>
|
|
<th class="sortable" data-sort="rang">Rang</th>
|
|
<th class="sortable" data-sort="nom">Nom</th>
|
|
<th class="sortable" data-sort="prenom">Prénom</th>
|
|
<th class="sortable" data-sort="classe">Classe</th>
|
|
<th class="sortable" data-sort="note">Note</th>
|
|
<th class="sortable" data-sort="pourcentage">%</th>
|
|
<th class="sortable" data-sort="temps">Temps</th>
|
|
<th class="sortable" data-sort="statut">Statut</th>
|
|
<th class="sortable" data-sort="date_fin">Date</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="table-body">
|
|
<!-- Rempli dynamiquement -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="table-footer" id="table-footer">
|
|
<!-- Total affiché -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty state (si aucun résultat) -->
|
|
<div id="empty-state" style="display: none;" class="empty-state">
|
|
<div class="empty-state-icon">📭</div>
|
|
<div class="empty-state-text">Aucun résultat disponible</div>
|
|
<p style="margin-top: 10px; font-size: 14px;">Les élèves n'ont pas encore commencé cette évaluation.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// ========================================================================
|
|
// VARIABLES GLOBALES
|
|
// ========================================================================
|
|
|
|
let dataGlobal = null;
|
|
let tentativesFiltrees = [];
|
|
let sortColumn = 'rang';
|
|
let sortDirection = 'asc';
|
|
let chartHistogramme = null;
|
|
let chartProgression = null;
|
|
|
|
const idEvaluation = <?= $id_evaluation ?>;
|
|
|
|
// ========================================================================
|
|
// CHARGEMENT DONNÉES
|
|
// ========================================================================
|
|
|
|
async function loadData() {
|
|
try {
|
|
const response = await fetch(`resultats_ajax.php?id_evaluation=${idEvaluation}`);
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
alert('Erreur: ' + data.error);
|
|
return;
|
|
}
|
|
|
|
dataGlobal = data;
|
|
|
|
// Masquer loading
|
|
document.getElementById('loading').style.display = 'none';
|
|
|
|
// Si aucune tentative
|
|
if (data.tentatives.length === 0) {
|
|
document.getElementById('empty-state').style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
// Afficher contenu
|
|
document.getElementById('content').style.display = 'block';
|
|
|
|
// Mettre à jour titre
|
|
document.getElementById('eval-title').textContent =
|
|
'Résultats : ' + data.evaluation.titre;
|
|
|
|
// Remplir interface
|
|
renderStats(data.stats, data.stats_classes, data.stats_questions);
|
|
renderCharts(data.graphiques);
|
|
populateFilters(data.tentatives);
|
|
tentativesFiltrees = [...data.tentatives];
|
|
renderTable();
|
|
|
|
} catch (error) {
|
|
console.error('Erreur chargement:', error);
|
|
alert('Erreur lors du chargement des données');
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// RENDU STATISTIQUES
|
|
// ========================================================================
|
|
|
|
function renderStats(stats, statsClasses, statsQuestions) {
|
|
const grid = document.getElementById('stats-grid');
|
|
|
|
const cards = [
|
|
{
|
|
label: 'Élèves',
|
|
value: stats.total_eleves,
|
|
class: ''
|
|
},
|
|
{
|
|
label: 'Terminés',
|
|
value: stats.termines,
|
|
class: 'success'
|
|
},
|
|
{
|
|
label: 'En cours',
|
|
value: stats.en_cours,
|
|
class: 'warning'
|
|
},
|
|
{
|
|
label: 'Moyenne',
|
|
value: stats.moyenne.toFixed(2) + '/20',
|
|
class: stats.moyenne >= 12 ? 'success' : (stats.moyenne >= 10 ? 'warning' : 'danger')
|
|
},
|
|
{
|
|
label: 'Médiane',
|
|
value: stats.mediane.toFixed(2),
|
|
class: ''
|
|
},
|
|
{
|
|
label: 'Min / Max',
|
|
value: stats.min.toFixed(1) + ' / ' + stats.max.toFixed(1),
|
|
class: ''
|
|
},
|
|
{
|
|
label: 'Écart-type',
|
|
value: stats.ecart_type.toFixed(2),
|
|
class: ''
|
|
},
|
|
{
|
|
label: 'Taux réussite',
|
|
value: stats.taux_reussite.toFixed(1) + '%',
|
|
class: stats.taux_reussite >= 70 ? 'success' : (stats.taux_reussite >= 50 ? 'warning' : 'danger')
|
|
},
|
|
{
|
|
label: 'Temps moyen',
|
|
value: stats.temps_moyen,
|
|
class: ''
|
|
},
|
|
{
|
|
label: 'Meilleure classe',
|
|
value: statsClasses.length > 0 ? statsClasses[0].nom : '-',
|
|
class: 'success'
|
|
}
|
|
];
|
|
|
|
grid.innerHTML = cards.map(card => `
|
|
<div class="stat-card ${card.class}">
|
|
<div class="stat-value">${card.value}</div>
|
|
<div class="stat-label">${card.label}</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// ========================================================================
|
|
// RENDU GRAPHIQUES
|
|
// ========================================================================
|
|
|
|
function renderCharts(graphiques) {
|
|
// Détruire charts existants
|
|
if (chartHistogramme) chartHistogramme.destroy();
|
|
if (chartProgression) chartProgression.destroy();
|
|
|
|
// Histogramme par classe
|
|
const ctxHisto = document.getElementById('chart-histogramme').getContext('2d');
|
|
chartHistogramme = new Chart(ctxHisto, {
|
|
type: 'bar',
|
|
data: graphiques.histogramme_classes,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top'
|
|
},
|
|
title: {
|
|
display: false
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
stepSize: 1
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: 'Nombre d\'élèves'
|
|
}
|
|
},
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Tranches de notes'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Courbe progression
|
|
const ctxProg = document.getElementById('chart-progression').getContext('2d');
|
|
chartProgression = new Chart(ctxProg, {
|
|
type: 'line',
|
|
data: graphiques.progression_eleves,
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top'
|
|
},
|
|
title: {
|
|
display: false
|
|
},
|
|
tooltip: {
|
|
mode: 'index',
|
|
intersect: false
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
title: {
|
|
display: true,
|
|
text: 'Points cumulés'
|
|
}
|
|
},
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: 'Questions'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// ========================================================================
|
|
// FILTRES ET TRI
|
|
// ========================================================================
|
|
|
|
function populateFilters(tentatives) {
|
|
// Remplir filtre classes
|
|
const classes = [...new Set(tentatives.map(t => t.classe))].sort();
|
|
const selectClasse = document.getElementById('filter-classe');
|
|
|
|
classes.forEach(classe => {
|
|
const option = document.createElement('option');
|
|
option.value = classe;
|
|
option.textContent = classe;
|
|
selectClasse.appendChild(option);
|
|
});
|
|
|
|
// Événements filtres
|
|
selectClasse.addEventListener('change', applyFilters);
|
|
document.getElementById('filter-statut').addEventListener('change', applyFilters);
|
|
document.getElementById('search-eleve').addEventListener('input', applyFilters);
|
|
|
|
// Événements tri
|
|
document.querySelectorAll('th.sortable').forEach(th => {
|
|
th.addEventListener('click', () => {
|
|
const column = th.dataset.sort;
|
|
if (sortColumn === column) {
|
|
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
sortColumn = column;
|
|
sortDirection = 'asc';
|
|
}
|
|
renderTable();
|
|
});
|
|
});
|
|
}
|
|
|
|
function applyFilters() {
|
|
const filtreClasse = document.getElementById('filter-classe').value;
|
|
const filtreStatut = document.getElementById('filter-statut').value;
|
|
const search = document.getElementById('search-eleve').value.toLowerCase();
|
|
|
|
tentativesFiltrees = dataGlobal.tentatives.filter(t => {
|
|
// Filtre classe
|
|
if (filtreClasse && t.classe !== filtreClasse) return false;
|
|
|
|
// Filtre statut
|
|
if (filtreStatut && t.statut !== filtreStatut) return false;
|
|
|
|
// Recherche
|
|
if (search) {
|
|
const nomComplet = (t.nom + ' ' + t.prenom).toLowerCase();
|
|
if (!nomComplet.includes(search)) return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
renderTable();
|
|
}
|
|
|
|
function renderTable() {
|
|
// Tri
|
|
tentativesFiltrees.sort((a, b) => {
|
|
let valA = a[sortColumn];
|
|
let valB = b[sortColumn];
|
|
|
|
// Conversion numérique si nécessaire
|
|
if (sortColumn === 'note' || sortColumn === 'pourcentage' || sortColumn === 'rang') {
|
|
valA = parseFloat(valA) || 0;
|
|
valB = parseFloat(valB) || 0;
|
|
}
|
|
|
|
if (sortDirection === 'asc') {
|
|
return valA > valB ? 1 : -1;
|
|
} else {
|
|
return valA < valB ? 1 : -1;
|
|
}
|
|
});
|
|
|
|
// Mise à jour classes th
|
|
document.querySelectorAll('th.sortable').forEach(th => {
|
|
th.classList.remove('sort-asc', 'sort-desc');
|
|
if (th.dataset.sort === sortColumn) {
|
|
th.classList.add('sort-' + sortDirection);
|
|
}
|
|
});
|
|
|
|
// Rendu lignes
|
|
const tbody = document.getElementById('table-body');
|
|
|
|
tbody.innerHTML = tentativesFiltrees.map(t => {
|
|
// Classe note selon valeur
|
|
let noteClass = '';
|
|
if (t.note >= 18) noteClass = 'excellent';
|
|
else if (t.note >= 15) noteClass = 'good';
|
|
else if (t.note >= 10) noteClass = 'average';
|
|
else noteClass = 'bad';
|
|
|
|
// Badge statut
|
|
const badgeClass = t.statut === 'terminee' ? 'termine' : 'en-cours';
|
|
const badgeText = t.statut === 'terminee' ? 'Terminé' : 'En cours';
|
|
|
|
return `
|
|
<tr>
|
|
<td class="rang">${t.rang !== '-' ? t.rang : '-'}</td>
|
|
<td>${t.nom}</td>
|
|
<td>${t.prenom}</td>
|
|
<td>${t.classe}</td>
|
|
<td class="note ${noteClass}">${t.note}/${t.note_sur}</td>
|
|
<td>${t.pourcentage}%</td>
|
|
<td>${t.temps}</td>
|
|
<td><span class="badge ${badgeClass}">${badgeText}</span></td>
|
|
<td>${t.date_fin}</td>
|
|
</tr>
|
|
`;
|
|
}).join('');
|
|
|
|
// Footer
|
|
document.getElementById('table-footer').textContent =
|
|
`Total : ${tentativesFiltrees.length} élève(s) affiché(s)`;
|
|
}
|
|
|
|
// ========================================================================
|
|
// INITIALISATION
|
|
// ========================================================================
|
|
|
|
window.addEventListener('DOMContentLoaded', loadData);
|
|
</script>
|
|
</body>
|
|
</html>
|