Files
webval/enseignant/dashboard.php

779 lines
27 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Tableau de bord enseignant
* Fichier : dashboard_enseignant.php
*/
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../config/session.php';
// Vérifier que l'utilisateur est connecté et est un enseignant
// if (!SessionManager::isEnseignant()) {
// header('Location: ../login.php?error=session');
// exit();
// }
$user = SessionManager::getUser();
try {
$db = Database::getInstance()->getConnection();
// Récupérer les statistiques globales
$stmt = $db->query("
SELECT
(SELECT COUNT(*) FROM utilisateurs WHERE id_type IN (2,3)) as nb_eleves,
(SELECT COUNT(*) FROM classes WHERE actif = 1) as nb_classes,
(SELECT COUNT(*) FROM utilisateurs WHERE id_type = 1) as nb_enseignants,
(SELECT COUNT(*) FROM evaluations WHERE actif = 1) as nb_evaluations_actives,
(SELECT COUNT(*) FROM evaluations) as nb_evaluations_total
");
$stats = $stmt->fetch();
// Récupérer les évaluations récentes
$stmt = $db->query("
SELECT
e.id_evaluation,
e.titre,
e.type,
e.actif,
e.duree_minutes,
e.note_totale,
COUNT(DISTINCT q.id_question) as nb_questions,
COUNT(DISTINCT t.id_eleve) as nb_participants
FROM evaluations e
LEFT JOIN questions q ON e.id_evaluation = q.id_evaluation
LEFT JOIN tentatives_eleves t ON e.id_evaluation = t.id_evaluation
GROUP BY e.id_evaluation
ORDER BY e.id_evaluation DESC
LIMIT 5
");
$evaluations_recentes = $stmt->fetchAll();
// Récupérer la liste des classes avec le nombre d'élèves
$stmt = $db->query("
SELECT
c.id_classe,
c.nom_classe,
c.niveau,
c.type_classe,
COUNT(u.id_utilisateur) as nb_eleves
FROM classes c
LEFT JOIN utilisateurs u ON c.id_classe = u.id_classe AND u.actif = 1
WHERE c.actif = 1
GROUP BY c.id_classe
ORDER BY c.niveau, c.nom_classe
");
$classes = $stmt->fetchAll();
// Récupérer les dernières connexions
$stmt = $db->query("
SELECT
u.login,
u.nom,
u.prenom,
c.nom_classe,
u.date_derniere_connexion
FROM utilisateurs u
LEFT JOIN classes c ON u.id_classe = c.id_classe
WHERE u.id_type IN (2,3) AND u.date_derniere_connexion IS NOT NULL
ORDER BY u.date_derniere_connexion DESC
LIMIT 10
");
$dernieres_connexions = $stmt->fetchAll();
} catch (PDOException $e) {
error_log("Erreur dashboard enseignant : " . $e->getMessage());
$stats = ['nb_eleves' => 0, 'nb_classes' => 0, 'nb_enseignants' => 0, 'nb_evaluations_actives' => 0, 'nb_evaluations_total' => 0];
$classes = [];
$evaluations_recentes = [];
$dernieres_connexions = [];
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#1976d2">
<title>Dashboard Enseignant - <?= htmlspecialchars($user['prenom']) ?></title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #f5f5f5;
padding-bottom: 20px;
}
/* Header */
.header {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
color: white;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.header h1 {
font-size: 22px;
font-weight: 600;
}
.btn-logout {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 8px 15px;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.user-info {
background: rgba(255, 255, 255, 0.15);
padding: 15px;
border-radius: 10px;
backdrop-filter: blur(10px);
}
.user-info p {
margin: 5px 0;
font-size: 14px;
}
.user-info strong {
font-weight: 600;
}
/* Container */
.container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
/* Cartes de statistiques */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-card {
background: white;
padding: 25px;
border-radius: 15px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
text-align: center;
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-5px);
}
.stat-card .icon {
font-size: 36px;
margin-bottom: 10px;
}
.stat-card .value {
font-size: 32px;
font-weight: 700;
color: #1976d2;
margin-bottom: 5px;
}
.stat-card .label {
font-size: 14px;
color: #666;
}
/* Section */
.section {
background: white;
border-radius: 15px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}
.section h2 {
font-size: 20px;
margin-bottom: 20px;
color: #333;
display: flex;
align-items: center;
gap: 10px;
}
/* Table */
.table-responsive {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background: #f8f9fa;
font-weight: 600;
color: #333;
font-size: 14px;
}
td {
font-size: 14px;
color: #666;
}
tr:hover {
background: #f8f9fa;
}
.badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.badge-primary {
background: #e3f2fd;
color: #1976d2;
}
.badge-success {
background: #e8f5e9;
color: #2e7d32;
}
.badge-warning {
background: #fff3e0;
color: #f57c00;
}
.badge-secondary {
background: #f5f5f5;
color: #666;
}
/* Boutons d'action */
.action-buttons {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.btn {
padding: 15px 20px;
border-radius: 12px;
text-decoration: none;
font-size: 14px;
font-weight: 600;
transition: all 0.2s;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
text-align: center;
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-success {
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
color: white;
}
.btn-info {
background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
color: white;
}
.btn-secondary {
background: #f5f5f5;
color: #333;
border: 2px solid #e0e0e0;
}
.btn-sm {
padding: 6px 12px;
font-size: 12px;
}
/* Temps relatif */
.time-relative {
color: #999;
font-size: 12px;
}
/* Empty state */
.empty-state {
text-align: center;
padding: 40px;
color: #999;
}
.empty-state .icon {
font-size: 48px;
margin-bottom: 15px;
opacity: 0.5;
}
/* Evaluation card */
.eval-card {
border: 2px solid #e0e0e0;
border-radius: 10px;
padding: 15px;
margin-bottom: 15px;
transition: all 0.2s;
}
.eval-card:hover {
border-color: #667eea;
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.2);
}
.eval-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 10px;
}
.eval-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 5px;
}
.eval-meta {
display: flex;
gap: 15px;
flex-wrap: wrap;
font-size: 13px;
color: #666;
}
.eval-actions {
display: flex;
gap: 8px;
margin-top: 10px;
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.action-buttons {
grid-template-columns: 1fr;
}
table {
font-size: 12px;
}
th, td {
padding: 8px;
}
}
</style>
</head>
<body>
<!-- Header -->
<div class="header">
<div class="header-top">
<h1>👨‍🏫 Dashboard Enseignant</h1>
<a href="../logout.php" class="btn-logout">Déconnexion</a>
</div>
<div class="user-info">
<p><strong><?= htmlspecialchars($user['prenom'] . ' ' . $user['nom']) ?></strong></p>
<p>🎓 Enseignant de Mathématiques</p>
<p>Login : <?= htmlspecialchars($user['login']) ?></p>
</div>
</div>
<!-- Container principal -->
<div class="container">
<!-- Statistiques globales -->
<div class="stats-grid">
<div class="stat-card">
<div class="icon">👥</div>
<div class="value"><?= $stats['nb_eleves'] ?></div>
<div class="label">Élèves</div>
</div>
<div class="stat-card">
<div class="icon">🏫</div>
<div class="value"><?= $stats['nb_classes'] ?></div>
<div class="label">Classes</div>
</div>
<div class="stat-card">
<div class="icon">📝</div>
<div class="value"><?= $stats['nb_evaluations_actives'] ?></div>
<div class="label">Évaluations actives</div>
</div>
<div class="stat-card">
<div class="icon">📚</div>
<div class="value"><?= $stats['nb_evaluations_total'] ?></div>
<div class="label">Total évaluations</div>
</div>
</div>
<!-- Actions rapides -->
<div class="section">
<h2>⚡ Gestion des évaluations</h2>
<div class="action-buttons">
<a href="../importer_evaluation.php" class="btn btn-primary">
📥 Importer une évaluation
</a>
<a href="#liste-evaluations" class="btn btn-info">
📋 Voir toutes les évaluations
</a>
<a href="statistiques/index.php" class="btn btn-secondary">
📊 Statistiques détaillées
</a>
<a href="chatbot_api_settings.php" class="btn btn-secondary">
🤖 Configuration IA Chatbot
</a>
</div>
</div>
<!-- Évaluations récentes -->
<div class="section" id="liste-evaluations">
<h2>📝 Évaluations récentes</h2>
<?php if (count($evaluations_recentes) > 0): ?>
<?php foreach ($evaluations_recentes as $eval): ?>
<div class="eval-card">
<div class="eval-header">
<div>
<div class="eval-title"><?= htmlspecialchars($eval['titre']) ?></div>
<div class="eval-meta">
<span>📋 <?= $eval['nb_questions'] ?> question(s)</span>
<span>⏱️ <?= $eval['duree_minutes'] ?> min</span>
<span>📊 <?= $eval['note_totale'] ?> pts</span>
<span>👥 <?= $eval['nb_participants'] ?> participant(s)</span>
</div>
</div>
<span class="badge <?= $eval['actif'] ? 'badge-success' : 'badge-secondary' ?>">
<?= $eval['actif'] ? '✓ Active' : '✕ Inactive' ?>
</span>
</div>
<div class="eval-actions">
<a href="../gerer_evaluation.php?id=<?= $eval['id_evaluation'] ?>" class="btn btn-primary btn-sm">
⚙️ Gérer
</a>
<a href="../resultat_evaluation.php?id=<?= $eval['id_evaluation'] ?>" class="btn btn-info btn-sm">
📊 Résultats
</a>
<a href="monitoring.php?id_evaluation=<?= $eval['id_evaluation'] ?>"
class="btn btn-primary btn-sm"
title="Monitoring temps réel">
📊 Monitoring
</a>
<a href="../passer_evaluation.php?id_evaluation=<?= $eval['id_evaluation'] ?>&apercu=1" target="_blank" class="btn btn-secondary btn-sm">
👁️ Aperçu
</a>
<a href="export.php?id_evaluation=<?= $eval['id_evaluation'] ?>&format=csv"
class="btn btn-success btn-sm"
title="Télécharger CSV (Excel)">
📥 CSV
</a>
<a href="export.php?id_evaluation=<?= $eval['id_evaluation'] ?>&format=json"
class="btn btn-warning btn-sm"
title="Télécharger JSON (Archive)">
📥 JSON
</a>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="empty-state">
<div class="icon">📝</div>
<p>Aucune évaluation créée</p>
<p style="margin-top: 10px;">
<a href="../importer_evaluation.php" class="btn btn-primary">
📥 Importer votre première évaluation
</a>
</p>
</div>
<?php endif; ?>
</div>
<!-- Actions secondaires -->
<div class="section">
<h2>⚡ Autres actions</h2>
<div class="action-buttons">
<button class="btn btn-secondary" disabled title="Fonctionnalité en développement"> Ajouter un élève</button>
<button class="btn btn-secondary" disabled title="Fonctionnalité en développement">📝 Créer un exercice</button>
<button class="btn btn-secondary" disabled title="Fonctionnalité en développement">⚙️ Paramètres</button>
</div>
</div>
<!-- Liste des classes -->
<div class="section">
<h2>🏫 Mes classes</h2>
<div class="table-responsive">
<?php if (count($classes) > 0): ?>
<table>
<thead>
<tr>
<th>Classe</th>
<th>Niveau</th>
<th>Type</th>
<th>Élèves</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($classes as $classe): ?>
<tr>
<td><strong><?= htmlspecialchars($classe['nom_classe']) ?></strong></td>
<td><?= htmlspecialchars($classe['niveau']) ?></td>
<td>
<span class="badge <?= $classe['type_classe'] == 'reguliere' ? 'badge-primary' : 'badge-secondary' ?>">
<?= ucfirst($classe['type_classe']) ?>
</span>
</td>
<td><?= $classe['nb_eleves'] ?> élève(s)</td>
<td>
<button class="btn btn-primary btn-sm" onclick="voirEleves(<?php echo $classe['id_classe']; ?>, '<?php echo htmlspecialchars($classe['nom_classe'], ENT_QUOTES); ?>')">👥 Voir</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="empty-state">
<div class="icon">📚</div>
<p>Aucune classe active</p>
</div>
<?php endif; ?>
</div>
</div>
<!-- Dernières connexions -->
<div class="section">
<h2>🕒 Dernières connexions</h2>
<div class="table-responsive">
<?php if (count($dernieres_connexions) > 0): ?>
<table>
<thead>
<tr>
<th>Élève</th>
<th>Classe</th>
<th>Dernière connexion</th>
</tr>
</thead>
<tbody>
<?php foreach ($dernieres_connexions as $connexion): ?>
<tr>
<td>
<strong><?= htmlspecialchars($connexion['prenom'] . ' ' . $connexion['nom']) ?></strong>
<div class="time-relative"><?= htmlspecialchars($connexion['login']) ?></div>
</td>
<td><?= htmlspecialchars($connexion['nom_classe'] ?? '-') ?></td>
<td>
<?php
$date = new DateTime($connexion['date_derniere_connexion']);
echo $date->format('d/m/Y à H:i');
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="empty-state">
<div class="icon">👤</div>
<p>Aucune connexion récente</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
// Renouvellement automatique de la session
setInterval(() => {
fetch('api/renew_session.php', { method: 'POST' })
.catch(err => console.error('Erreur renouvellement:', err));
}, 600000); // Toutes les 10 minutes
</script>
<!-- Modal pour voir les élèves -->
<div id="modalEleves" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000;">
<div style="background: white; margin: 50px auto; max-width: 800px; border-radius: 10px; padding: 30px; max-height: 80vh; overflow-y: auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2 id="modalTitre" style="margin: 0;">Liste des élèves</h2>
<button onclick="fermerModal()" style="background: #e74c3c; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer;">✕ Fermer</button>
</div>
<div id="modalContenu">
<div style="text-align: center; padding: 40px;">
<p>Chargement des élèves...</p>
</div>
</div>
</div>
</div>
<script>
function voirEleves(idClasse, nomClasse) {
document.getElementById('modalEleves').style.display = 'block';
document.getElementById('modalTitre').textContent = 'Élèves de ' + nomClasse;
fetch('get_eleves_classe.php?id_classe=' + idClasse)
.then(response => {
console.log('Réponse get_eleves_classe:', response);
if (!response.ok) {
console.error('Erreur HTTP:', response.status);
}
return response.text();
})
.then(text => {
console.log('Contenu brut:', text.substring(0, 200));
return JSON.parse(text);
})
.then(data => {
if (data.success) {
afficherEleves(data.eleves);
} else {
document.getElementById('modalContenu').innerHTML = '<div class="alert alert-danger">Erreur : ' + data.message + '</div>';
}
})
.catch(error => {
document.getElementById('modalContenu').innerHTML = '<div class="alert alert-danger">Erreur : ' + error.message + '</div>';
});
}
function afficherEleves(eleves) {
if (eleves.length === 0) {
document.getElementById('modalContenu').innerHTML = '<div class="alert alert-info">Aucun élève dans cette classe</div>';
return;
}
// Message mot de passe en haut
let html = '<div class="alert alert-info mb-3">';
html += '<strong>🔑 Mot de passe par défaut :</strong> <code class="bg-white text-dark px-2 py-1" style="font-size: 1.1em;">eleve2025</code>';
html += '<p class="mb-0 mt-2"><small>Ce mot de passe est utilisé pour tous les élèves lors de l\'import.</small></p>';
html += '</div>';
// Tableau avec colonne mot de passe
html += '<table class="table table-striped"><thead><tr><th>Login</th><th>Nom</th><th>Prénom</th><th>Mot de passe</th><th>Statut</th><th>Actions</th></tr></thead><tbody>';
eleves.forEach(eleve => {
let statut = eleve.actif == 1 ? '<span class="badge badge-success">Actif</span>' : '<span class="badge badge-secondary">Inactif</span>';
html += '<tr>';
html += '<td><code>' + eleve.login + '</code></td>';
html += '<td>' + eleve.nom + '</td>';
html += '<td>' + eleve.prenom + '</td>';
html += '<td><code class="text-primary">eleve2025</code></td>';
html += '<td>' + statut + '</td>';
html += '<td><button class="btn btn-sm btn-warning" onclick="resetMotDePasse(' + eleve.id_utilisateur + ', \'' + eleve.login + '\', \'' + eleve.prenom + ' ' + eleve.nom + '\')">🔄 Reset</button></td>';
html += '</tr>';
});
html += '</tbody></table><p class="text-muted"><strong>Total :</strong> ' + eleves.length + ' élève(s)</p>';
document.getElementById('modalContenu').innerHTML = html;
}
function fermerModal() {
document.getElementById('modalEleves').style.display = 'none';
}
function resetMotDePasse(idEleve, login, nomComplet) {
if (!confirm('Réinitialiser le mot de passe de ' + nomComplet + ' (login: ' + login + ') ?\n\nLe mot de passe sera remis à la valeur par défaut.')) {
return;
}
// Afficher loader
const btn = event.target;
const textOriginal = btn.innerHTML;
btn.innerHTML = '⏳';
btn.disabled = true;
// Appel AJAX
fetch('../api/reset_password_eleve.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials: 'include',
body: 'id_eleve=' + idEleve
})
.then(response => {
console.log('Réponse reset_password:', response);
if (!response.ok) {
console.error('Erreur HTTP:', response.status);
}
return response.text();
})
.then(text => {
console.log('Contenu brut reset:', text.substring(0, 200));
return JSON.parse(text);
})
.then(data => {
btn.innerHTML = textOriginal;
btn.disabled = false;
if (data.success) {
alert('✅ Mot de passe réinitialisé avec succès !\n\nLogin: ' + data.data.login + '\nNouveau mot de passe: ' + data.data.nouveau_mdp);
// Rafraîchir la liste si besoin
} else {
alert('❌ Erreur: ' + data.message);
}
})
.catch(error => {
btn.innerHTML = textOriginal;
btn.disabled = false;
alert('❌ Erreur de communication avec le serveur');
console.error('Erreur:', error);
});
}
document.addEventListener('click', function(event) {
const modal = document.getElementById('modalEleves');
if (event.target === modal) fermerModal();
});
</script>
</body>
</html>