Initial commit (code), runtime ignoré, secrets exclus

This commit is contained in:
nicoboy
2026-01-02 15:05:25 +01:00
commit 2fdcf50bc1
87 changed files with 11525 additions and 0 deletions

777
enseignant/dashboard.php Normal file
View File

@ -0,0 +1,777 @@
<?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>
</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="../passer_evaluation.php?id_evaluation=<?= $eval['id_evaluation'] ?>&apercu=1" target="_blank" class="btn btn-secondary btn-sm">
<a href="monitoring.php?id_evaluation=<?= $eval['id_evaluation'] ?>"
class="btn-action"
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;"
title="Monitoring temps réel">
📊 Monitoring
</a>
👁️ 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>