832 lines
29 KiB
PHP
832 lines
29 KiB
PHP
<?php
|
||
/**
|
||
* Dashboard élève - VERSION AMÉLIORÉE
|
||
* Avec historique des 4 dernières évaluations et graphique de progression
|
||
*/
|
||
|
||
require_once __DIR__ . '/../config/config.php';
|
||
require_once __DIR__ . '/../config/database.php';
|
||
|
||
// Vérifier l'authentification
|
||
if (!isLoggedIn()) {
|
||
header('Location: ../login.php');
|
||
exit;
|
||
}
|
||
|
||
$user = currentUser();
|
||
|
||
// Vérifier que c'est bien un élève
|
||
if (!isEleve()) {
|
||
header('Location: ../login.php');
|
||
exit;
|
||
}
|
||
|
||
$db = Database::getInstance();
|
||
|
||
// Déterminer le type d'élève
|
||
$isEleveLibre = ($user['id_type'] == 3);
|
||
$isEleveClasse = ($user['id_type'] == 2);
|
||
|
||
// Récupérer les évaluations disponibles (non encore passées)
|
||
if ($isEleveLibre) {
|
||
$queryEvals = "SELECT DISTINCT e.*,
|
||
COALESCE(t.statut, 'non_commence') as statut_tentative,
|
||
t.note as note_obtenue,
|
||
t.date_debut as date_tentative
|
||
FROM evaluations e
|
||
INNER JOIN acces_evaluations ae ON e.id_evaluation = ae.id_evaluation
|
||
INNER JOIN classes c ON ae.id_classe = c.id_classe
|
||
LEFT JOIN tentatives_eleves t ON e.id_evaluation = t.id_evaluation
|
||
AND t.id_eleve = ?
|
||
WHERE c.type_classe = 'soutien'
|
||
AND ae.actif = 1
|
||
AND NOW() BETWEEN ae.date_debut AND ae.date_fin
|
||
AND e.actif = 1
|
||
ORDER BY ae.date_fin ASC, e.titre ASC";
|
||
|
||
$evaluations = $db->fetchAll($queryEvals, [$user['id_utilisateur']]);
|
||
|
||
} else {
|
||
if ($user['id_classe']) {
|
||
$queryEvals = "SELECT DISTINCT e.*,
|
||
ae.date_debut as debut_acces,
|
||
ae.date_fin as fin_acces,
|
||
COALESCE(t.statut, 'non_commence') as statut_tentative,
|
||
t.note as note_obtenue,
|
||
t.date_debut as date_tentative
|
||
FROM evaluations e
|
||
INNER JOIN acces_evaluations ae ON e.id_evaluation = ae.id_evaluation
|
||
LEFT JOIN tentatives_eleves t ON e.id_evaluation = t.id_evaluation
|
||
AND t.id_eleve = ?
|
||
WHERE ae.id_classe = ?
|
||
AND ae.actif = 1
|
||
AND NOW() BETWEEN ae.date_debut AND ae.date_fin
|
||
AND e.actif = 1
|
||
ORDER BY ae.date_fin ASC, e.titre ASC";
|
||
|
||
$evaluations = $db->fetchAll($queryEvals, [$user['id_utilisateur'], $user['id_classe']]);
|
||
} else {
|
||
$evaluations = [];
|
||
}
|
||
}
|
||
|
||
// Récupérer les 4 dernières évaluations terminées
|
||
$query4Dernieres = "SELECT
|
||
e.id_evaluation,
|
||
e.titre,
|
||
e.description,
|
||
te.note,
|
||
te.pourcentage,
|
||
te.date_fin,
|
||
te.temps_passe_secondes
|
||
FROM tentatives_eleves te
|
||
INNER JOIN evaluations e ON te.id_evaluation = e.id_evaluation
|
||
WHERE te.id_eleve = ?
|
||
AND te.statut = 'terminee'
|
||
ORDER BY te.date_fin DESC
|
||
LIMIT 4";
|
||
|
||
$dernieresEvals = $db->fetchAll($query4Dernieres, [$user['id_utilisateur']]);
|
||
|
||
// Ajouter le nombre de questions pour chaque évaluation
|
||
foreach ($dernieresEvals as &$eval) {
|
||
$queryNbQuestions = "SELECT COUNT(*) as nb FROM questions WHERE id_evaluation = ?";
|
||
$result = $db->fetchOne($queryNbQuestions, [$eval['id_evaluation']]);
|
||
$eval['nb_questions'] = $result['nb'] ?? 0;
|
||
}
|
||
|
||
// Calculer la moyenne des 4 dernières
|
||
$moyenne4Dernieres = 0;
|
||
if (!empty($dernieresEvals)) {
|
||
$notes = array_filter(array_column($dernieresEvals, 'note'), function($n) {
|
||
return $n !== null && is_numeric($n);
|
||
});
|
||
if (!empty($notes)) {
|
||
$moyenne4Dernieres = array_sum($notes) / count($notes);
|
||
}
|
||
}
|
||
|
||
// Récupérer TOUTES les évaluations terminées pour le graphique
|
||
$queryToutesEvals = "SELECT
|
||
e.id_evaluation,
|
||
e.titre,
|
||
e.description,
|
||
te.note,
|
||
te.pourcentage,
|
||
te.date_fin
|
||
FROM tentatives_eleves te
|
||
INNER JOIN evaluations e ON te.id_evaluation = e.id_evaluation
|
||
WHERE te.id_eleve = ?
|
||
AND te.statut = 'terminee'
|
||
ORDER BY te.date_fin ASC";
|
||
|
||
$toutesEvals = $db->fetchAll($queryToutesEvals, [$user['id_utilisateur']]);
|
||
|
||
// Récupérer les statistiques générales
|
||
$queryStats = "SELECT
|
||
COUNT(DISTINCT t.id_evaluation) as nb_evaluations_passees,
|
||
COUNT(CASE WHEN t.statut = 'terminee' THEN 1 END) as nb_evaluations_terminees,
|
||
AVG(CASE WHEN t.statut = 'terminee' THEN t.note ELSE NULL END) as moyenne_generale
|
||
FROM tentatives_eleves t
|
||
INNER JOIN evaluations e ON t.id_evaluation = e.id_evaluation
|
||
WHERE t.id_eleve = ?";
|
||
|
||
$stats = $db->fetchOne($queryStats, [$user['id_utilisateur']]) ?? [
|
||
'nb_evaluations_passees' => 0,
|
||
'nb_evaluations_terminees' => 0,
|
||
'moyenne_generale' => null
|
||
];
|
||
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Dashboard Élève - <?= htmlspecialchars($user['prenom'] . ' ' . $user['nom']) ?></title>
|
||
<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: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
background: #f5f7fa;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 20px 40px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.header-content {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.user-info h1 {
|
||
font-size: 24px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.user-info p {
|
||
opacity: 0.9;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.badge {
|
||
background: rgba(255,255,255,0.2);
|
||
padding: 5px 12px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.logout-btn {
|
||
background: rgba(255,255,255,0.2);
|
||
border: 2px solid white;
|
||
color: white;
|
||
padding: 10px 20px;
|
||
border-radius: 8px;
|
||
text-decoration: none;
|
||
font-weight: 600;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.logout-btn:hover {
|
||
background: white;
|
||
color: #667eea;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
margin: 30px auto;
|
||
padding: 0 20px;
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: white;
|
||
padding: 25px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
border-left: 4px solid #667eea;
|
||
}
|
||
|
||
.stat-card h3 {
|
||
color: #666;
|
||
font-size: 14px;
|
||
margin-bottom: 10px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
.stat-card .value {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
color: #333;
|
||
}
|
||
|
||
.stat-card .sub-value {
|
||
font-size: 14px;
|
||
color: #999;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
/* Historique 4 dernières évaluations */
|
||
.historique-section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 25px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 20px;
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid #f0f0f0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.dernieres-evals-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.eval-card-small {
|
||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
transition: all 0.3s;
|
||
border: 2px solid transparent;
|
||
}
|
||
|
||
.eval-card-small:hover {
|
||
border-color: #667eea;
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 6px 15px rgba(102, 126, 234, 0.2);
|
||
}
|
||
|
||
.eval-card-small h4 {
|
||
color: #333;
|
||
font-size: 15px;
|
||
margin-bottom: 10px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.eval-card-small .chapitre {
|
||
font-size: 11px;
|
||
color: #666;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.note-display-large {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-end;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.note-fraction {
|
||
font-size: 14px;
|
||
color: #666;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.note-sur-20 {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
color: #667eea;
|
||
}
|
||
|
||
.eval-card-small .date {
|
||
font-size: 11px;
|
||
color: #999;
|
||
text-align: right;
|
||
}
|
||
|
||
.moyenne-4 {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
text-align: center;
|
||
}
|
||
|
||
.moyenne-4 h3 {
|
||
font-size: 14px;
|
||
margin-bottom: 10px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.moyenne-4 .value {
|
||
font-size: 42px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* Graphique progression */
|
||
.graph-section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 25px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.chart-container {
|
||
position: relative;
|
||
height: 300px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
/* Évaluations disponibles */
|
||
.section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 30px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.eval-grid {
|
||
display: grid;
|
||
gap: 15px;
|
||
}
|
||
|
||
.eval-card {
|
||
border: 2px solid #e0e0e0;
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
transition: all 0.3s;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.eval-card:hover {
|
||
border-color: #667eea;
|
||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.eval-info h3 {
|
||
color: #333;
|
||
margin-bottom: 8px;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.eval-info p {
|
||
color: #666;
|
||
font-size: 14px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.eval-actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border-radius: 8px;
|
||
text-decoration: none;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
transition: all 0.3s;
|
||
border: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #f0f0f0;
|
||
color: #666;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #e0e0e0;
|
||
}
|
||
|
||
.status-badge {
|
||
padding: 5px 12px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.status-non-commence {
|
||
background: #e3f2fd;
|
||
color: #1976d2;
|
||
}
|
||
|
||
.status-en-cours {
|
||
background: #fff3e0;
|
||
color: #f57c00;
|
||
}
|
||
|
||
.status-termine {
|
||
background: #e8f5e9;
|
||
color: #388e3c;
|
||
}
|
||
|
||
.note-display {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #667eea;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: #999;
|
||
}
|
||
|
||
.alert {
|
||
padding: 15px 20px;
|
||
border-radius: 8px;
|
||
margin-bottom: 20px;
|
||
border-left: 4px solid;
|
||
}
|
||
|
||
.alert-info {
|
||
background: #e3f2fd;
|
||
border-color: #2196f3;
|
||
color: #1565c0;
|
||
}
|
||
|
||
.no-data-message {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #999;
|
||
font-style: italic;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<div class="header-content">
|
||
<div class="user-info">
|
||
<h1>👋 Bonjour, <?= htmlspecialchars($user['prenom']) ?> !</h1>
|
||
<p>
|
||
<?php if ($isEleveLibre): ?>
|
||
<span class="badge">🎯 Groupe de Soutien</span>
|
||
<?php else: ?>
|
||
Classe : <?= htmlspecialchars($user['nom_classe'] ?? 'Non assigné') ?>
|
||
(<?= htmlspecialchars($user['niveau'] ?? '') ?>)
|
||
<?php endif; ?>
|
||
</p>
|
||
</div>
|
||
<a href="../logout.php" class="logout-btn">Déconnexion</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<!-- STATISTIQUES GLOBALES -->
|
||
<div class="stats-grid">
|
||
<div class="stat-card">
|
||
<h3>📚 Évaluations passées</h3>
|
||
<div class="value"><?= $stats['nb_evaluations_passees'] ?></div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<h3>✅ Évaluations terminées</h3>
|
||
<div class="value"><?= $stats['nb_evaluations_terminees'] ?></div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<h3>📊 Moyenne générale</h3>
|
||
<div class="value">
|
||
<?= $stats['moyenne_generale'] !== null
|
||
? number_format($stats['moyenne_generale'], 2) . '/20'
|
||
: '-' ?>
|
||
</div>
|
||
<?php if (!empty($toutesEvals) && count($toutesEvals) > 1): ?>
|
||
<div class="sub-value">
|
||
Sur <?= count($toutesEvals) ?> évaluation<?= count($toutesEvals) > 1 ? 's' : '' ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- INFO ÉLÈVE LIBRE -->
|
||
<?php if ($isEleveLibre): ?>
|
||
<div class="alert alert-info">
|
||
<strong>ℹ️ Mode Soutien :</strong>
|
||
Vous êtes inscrit dans le groupe de soutien. Les évaluations que vous passez ne comptent
|
||
pas dans votre bulletin scolaire, elles sont là pour vous entraîner et progresser.
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<!-- HISTORIQUE 4 DERNIÈRES ÉVALUATIONS -->
|
||
<?php if (!empty($dernieresEvals)): ?>
|
||
<div class="historique-section">
|
||
<h2 class="section-title">
|
||
<span>📋 Mes 4 dernières évaluations</span>
|
||
</h2>
|
||
|
||
<div class="dernieres-evals-grid">
|
||
<?php foreach ($dernieresEvals as $eval): ?>
|
||
<div class="eval-card-small">
|
||
<h4 title="<?= htmlspecialchars($eval['titre']) ?>">
|
||
<?= htmlspecialchars($eval['titre']) ?>
|
||
</h4>
|
||
<div class="chapitre">
|
||
<?= !empty($eval['description']) ? htmlspecialchars(substr($eval['description'], 0, 50)) . (strlen($eval['description']) > 50 ? '...' : '') : 'Évaluation terminée' ?>
|
||
</div>
|
||
|
||
<div class="note-display-large">
|
||
<div>
|
||
<?php if ($eval['nb_questions'] > 0): ?>
|
||
<div class="note-fraction">
|
||
<?= round($eval['pourcentage'] * $eval['nb_questions'] / 100, 1) ?>/<?= $eval['nb_questions'] ?> questions
|
||
</div>
|
||
<?php endif; ?>
|
||
<div class="note-sur-20">
|
||
<?= number_format($eval['note'], 2) ?>/20
|
||
</div>
|
||
</div>
|
||
<div style="text-align: right;">
|
||
<div class="note-fraction">
|
||
<?= round($eval['pourcentage']) ?>%
|
||
</div>
|
||
<div style="font-size: 11px; color: #999;">
|
||
⏱️ <?= gmdate('i:s', $eval['temps_passe_secondes'] ?? 0) ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="date">
|
||
<?= date('d/m/Y à H:i', strtotime($eval['date_fin'])) ?>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
|
||
<!-- Moyenne des 4 dernières -->
|
||
<?php if ($moyenne4Dernieres > 0): ?>
|
||
<div class="moyenne-4">
|
||
<h3>Moyenne sur ces <?= count($dernieresEvals) ?> évaluation<?= count($dernieresEvals) > 1 ? 's' : '' ?></h3>
|
||
<div class="value"><?= number_format($moyenne4Dernieres, 2) ?>/20</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<!-- GRAPHIQUE PROGRESSION -->
|
||
<?php if (!empty($toutesEvals) && count($toutesEvals) > 1): ?>
|
||
<div class="graph-section">
|
||
<h2 class="section-title">📈 Ma progression</h2>
|
||
<div class="chart-container">
|
||
<canvas id="progressionChart"></canvas>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<!-- ÉVALUATIONS DISPONIBLES -->
|
||
<div class="section">
|
||
<h2 class="section-title">📝 Évaluations disponibles</h2>
|
||
|
||
<?php if (empty($evaluations)): ?>
|
||
<div class="empty-state">
|
||
<p style="font-size: 18px; margin-bottom: 10px;">
|
||
Aucune évaluation disponible pour le moment
|
||
</p>
|
||
<p>Les nouvelles évaluations apparaîtront ici lorsque votre enseignant les publiera.</p>
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="eval-grid">
|
||
<?php foreach ($evaluations as $eval): ?>
|
||
<div class="eval-card">
|
||
<div class="eval-info">
|
||
<h3><?= htmlspecialchars($eval['titre']) ?></h3>
|
||
<?php if (!empty($eval['description'])): ?>
|
||
<p><?= htmlspecialchars($eval['description']) ?></p>
|
||
<?php endif; ?>
|
||
<p>
|
||
<strong>Durée :</strong> <?= $eval['duree_minutes'] ?? 'Libre' ?> min
|
||
</p>
|
||
<?php if (isset($eval['fin_acces'])): ?>
|
||
<p style="color: #f57c00;">
|
||
⏰ Disponible jusqu'au <?= date('d/m/Y H:i', strtotime($eval['fin_acces'])) ?>
|
||
</p>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="eval-actions">
|
||
<?php
|
||
$statut = $eval['statut_tentative'] ?? 'non_commence';
|
||
?>
|
||
|
||
<?php if ($statut === 'terminee'): ?>
|
||
<span class="note-display">
|
||
<?= number_format($eval['note_obtenue'], 2) ?>/20
|
||
</span>
|
||
<span class="status-badge status-termine">✅ Terminé</span>
|
||
<a href="../voir_resultat.php?id_evaluation=<?= $eval['id_evaluation'] ?>"
|
||
class="btn btn-secondary">
|
||
Voir le résultat
|
||
</a>
|
||
|
||
<?php elseif ($statut === 'en_cours'): ?>
|
||
<span class="status-badge status-en-cours">⏳ En cours</span>
|
||
<a href="../passer_evaluation.php?id_evaluation=<?= $eval['id_evaluation'] ?>"
|
||
class="btn btn-primary">
|
||
Reprendre
|
||
</a>
|
||
|
||
<?php else: ?>
|
||
<span class="status-badge status-non-commence">🆕 Nouveau</span>
|
||
<a href="../passer_evaluation.php?id_evaluation=<?= $eval['id_evaluation'] ?>"
|
||
class="btn btn-primary">
|
||
Commencer
|
||
</a>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if (!empty($toutesEvals) && count($toutesEvals) > 1): ?>
|
||
<script>
|
||
// Données pour le graphique
|
||
const toutesEvaluations = <?= json_encode($toutesEvals) ?>;
|
||
|
||
// Préparer les données
|
||
const labels = toutesEvaluations.map((e, index) => {
|
||
const date = new Date(e.date_fin);
|
||
return `Eval ${index + 1}\n${date.getDate()}/${date.getMonth() + 1}`;
|
||
});
|
||
|
||
const notes = toutesEvaluations.map(e => parseFloat(e.note));
|
||
|
||
// Calculer la moyenne mobile (sur 3 évaluations)
|
||
const moyennesMobiles = notes.map((note, index, arr) => {
|
||
if (index < 2) return null;
|
||
const sum = arr[index - 2] + arr[index - 1] + arr[index];
|
||
return sum / 3;
|
||
});
|
||
|
||
// Créer le graphique
|
||
const ctx = document.getElementById('progressionChart').getContext('2d');
|
||
new Chart(ctx, {
|
||
type: 'line',
|
||
data: {
|
||
labels: labels,
|
||
datasets: [
|
||
{
|
||
label: 'Note (/20)',
|
||
data: notes,
|
||
borderColor: '#667eea',
|
||
backgroundColor: 'rgba(102, 126, 234, 0.1)',
|
||
borderWidth: 3,
|
||
pointRadius: 6,
|
||
pointBackgroundColor: '#667eea',
|
||
pointBorderColor: '#fff',
|
||
pointBorderWidth: 2,
|
||
pointHoverRadius: 8,
|
||
tension: 0.3,
|
||
fill: true
|
||
},
|
||
{
|
||
label: 'Moyenne mobile (3 évals)',
|
||
data: moyennesMobiles,
|
||
borderColor: '#28a745',
|
||
backgroundColor: 'rgba(40, 167, 69, 0.05)',
|
||
borderWidth: 2,
|
||
borderDash: [5, 5],
|
||
pointRadius: 4,
|
||
pointBackgroundColor: '#28a745',
|
||
pointBorderColor: '#fff',
|
||
pointBorderWidth: 2,
|
||
tension: 0.3,
|
||
fill: false
|
||
}
|
||
]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
display: true,
|
||
position: 'top',
|
||
labels: {
|
||
usePointStyle: true,
|
||
padding: 15,
|
||
font: {
|
||
size: 12,
|
||
weight: '600'
|
||
}
|
||
}
|
||
},
|
||
tooltip: {
|
||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||
padding: 12,
|
||
titleFont: {
|
||
size: 13,
|
||
weight: 'bold'
|
||
},
|
||
bodyFont: {
|
||
size: 12
|
||
},
|
||
callbacks: {
|
||
title: function(context) {
|
||
const index = context[0].dataIndex;
|
||
return toutesEvaluations[index].titre;
|
||
},
|
||
label: function(context) {
|
||
const index = context.dataIndex;
|
||
const eval = toutesEvaluations[index];
|
||
if (context.datasetIndex === 0) {
|
||
return `Note: ${context.parsed.y.toFixed(2)}/20 (${eval.pourcentage}%)`;
|
||
} else {
|
||
return context.parsed.y ? `Moyenne mobile: ${context.parsed.y.toFixed(2)}/20` : '';
|
||
}
|
||
},
|
||
afterLabel: function(context) {
|
||
if (context.datasetIndex === 0) {
|
||
const index = context.dataIndex;
|
||
const eval = toutesEvaluations[index];
|
||
const date = new Date(eval.date_fin);
|
||
return `Date: ${date.toLocaleDateString('fr-FR')}`;
|
||
}
|
||
return '';
|
||
}
|
||
}
|
||
}
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: true,
|
||
max: 20,
|
||
ticks: {
|
||
stepSize: 2,
|
||
callback: function(value) {
|
||
return value + '/20';
|
||
},
|
||
font: {
|
||
size: 11
|
||
}
|
||
},
|
||
grid: {
|
||
color: 'rgba(0, 0, 0, 0.05)'
|
||
}
|
||
},
|
||
x: {
|
||
ticks: {
|
||
font: {
|
||
size: 10
|
||
}
|
||
},
|
||
grid: {
|
||
display: false
|
||
}
|
||
}
|
||
},
|
||
interaction: {
|
||
intersect: false,
|
||
mode: 'index'
|
||
}
|
||
}
|
||
});
|
||
</script>
|
||
<?php endif; ?>
|
||
</body>
|
||
</html>
|