Bonjour 👋
Prêt à booster tes résultats ?
🎬 Qu'est-ce que Réviscool ?
Regarder la vidéo de présentation
🎥 Comment utiliser Réviscool ?
Regarder le tutoriel
🌟 Défi du jour
⏳ Chargement du défi…
✨ Ce que tu vas aimer
« L'apprentissage sur mesure, comme un costume taillé pour toi. »
Chez Réviscool, nous savons qu'il n'y a pas deux élèves pareils. C'est pourquoi nous avons repensé le cours traditionnel.
  • Diagnostic intelligent : Dès ton arrivée, un test de positionnement rapide identifie tes forces et tes axes de progression. Fini de réviser ce que tu sais déjà !
  • Parcours personnalisés : Selon ton niveau (débutant, intermédiaire ou avancé), l'application te propose un cheminement unique. Si tu bloques sur un concept, Réviscool ajuste automatiquement la difficulté et te propose des rappels ciblés.
  • Interactivité totale : Oublie les longs pavés de texte. Nos cours sont interactifs : réponds à des questions intégrées pendant la lecture, regarde des vidéos explicatives courtes, et débloque des "succès" à chaque chapitre maîtrisé. Tu es acteur de ta réussite.
« Entraîne-toi avec ce qui tombe vraiment aux examens. »
Nous ne croyons pas aux exercices aléatoires. Pour être prêt le jour J, il faut s'entraîner sur le vrai.
  • Base de données certifiée : Nos exercices sont extraits d'annales officielles (9ème AF, Bac, Diplômes universitaires) et conformes aux programmes de l'Éducation nationale.
  • Corrections détaillées en vidéo et texte : Fini les corrigés qui disent juste "Voir cours". Chaque exercice est suivi d'une correction pas à pas. Pour chaque erreur, un lien direct vers la fiche de cours correspondante s'affiche pour combler la lacune immédiatement.
  • Mode examen : Entraîne-toi en conditions réelles avec un chronomètre. Une fois le temps écoulé, Réviscool analyse tes résultats et te génère une "feuille de route" de révision pour tes points faibles.
« Un soutien humain, même à distance. »
La technologie est puissante, mais rien ne remplace un vrai professeur. Réviscool combine le meilleur des deux mondes.
  • Contenu créé par des experts : Tous nos cours et exercices sont conçus et relus par des professeurs certifiés de l'Éducation nationale et des enseignants du supérieur. La qualité pédagogique est notre priorité absolue.
  • Visio et Chat intégrés : Tu as une question à 20h sur un théorème de maths ou une dissertation ? Ouvre un chat instantané avec un professeur certifié. Pour les besoins plus complexes, réserve une session de visio-conférence directement depuis l'application.
  • Suivi personnalisé : Grâce à notre tableau de bord partagé, les enseignants voient tes résultats et peuvent te conseiller des exercices spécifiques avant un rendez-vous, rendant l'accompagnement ultra-efficace.
« Apprends en un coup d'œil. »
Notre cerveau est câblé pour retenir les images et les histoires, pas les lignes de texte monotones. Réviscool transforme la théorie en spectacle pédagogique.
  • Animations ludiques : Nous utilisons des vidéos animées, des infographies interactives et des schémas qui bougent pour expliquer les concepts abstraits. Regarde une cellule se diviser, une bataille de 1815 se dérouler sur une carte animée, ou une formule mathématique se construire étape par étape.
  • Révisions visuelles : Pour chaque chapitre, nous proposons une "Mind Map" (carte mentale) animée et imprimable. Idéal pour les révisions de dernière minute : en 5 minutes, tu as une vision globale et colorée de tout le chapitre.
  • Synthèse audio-visuelle : Tu es plutôt auditif ou visuel ? Chaque présentation animée est accompagnée d'une voix-off claire. Tu peux même télécharger ces vidéos pour les regarder dans les transports sans connexion internet.
💬 Ils nous font confiance
"J'ai eu 7.8/10 à l'examen du cinquième contrôle grâce à Réviscool ! Les cours sont clairs et les quiz m'ont vraiment aidé à mémoriser."
— Fabrice Etienne, NS1 SES
📚 Mes matières
Toutes tes matières
27%

Progression générale

Chapitres complétés sur toutes les matières

Matière
Enseignant · Horaire
0 / 0 leçons
0%
Leçon
Chapitre
👁 Lecture visuelle
🎧 Lecture audio
Révision
Flashcards
Quiz
Sélectionner le type
🃏 Bibliothèque
Flash Cards
✏️ Créer des Flash Cards
0 question ajoutée
📑 Leçon associée
❓ Question 1
Recto / Verso
Connexion
Bon retour parmi nous ! 👋

Pas encore de compte ? S'inscrire

← Retour à l'accueil

Accès administrateur

🔐
Accès Administrateur
Zone sécurisée — accès restreint
👤
✉️
🔑
🛡️

← Retour à la connexion

📱
Vérification 2FA
Code envoyé
🔢
Créer un compte
Rejoins des milliers d'apprenants et enseignants !
📷
Choisir une photo de profil
JPG, PNG — Max 5MB
J'accepte les conditions d'utilisation
Votre profession
Sélectionnez votre domaine d'activité
Choisir son abonnement
Accès illimité à tous les contenus Réviscool
Mensuel
Accès 30 jours, renouvelable
500 HTG
Annuel 🔥 Meilleur prix
12 mois, économisez 28%
4 800 HTG
Méthode de paiement
MonCash
NatCash
🎉
Merci pour votre inscription !
Votre demande a bien été reçue.
Notre équipe va examiner votre dossier et activer votre compte dans les meilleurs délais.
📞 Pour accélérer le processus, contactez notre service support :
WhatsApp — 3801-6068

Vous recevrez une confirmation dès que votre compte sera activé.

Utilisateur
Élève
👤
🎉

Bienvenue sur Réviscool !

Ton espace d'apprentissage est prêt. Bonne révision !

Nouveau membre
⚙️ Paramètres
🌙
Mode sombre
Changer l'apparence
🔔
Notifications
Rappels et alertes
📊 Progression récente
⏳ Chargement…
👤
👤
📷
Mon Profil
🔑 Mot de passe
⚙️ Paramètres
🌙
Mode sombre
Changer l'apparence
🔔
Notifications
Rappels et alertes
🔒 Confidentialité
⚠️ Zone de danger
Cette action est irréversible. Toutes vos données seront supprimées.
📅
← Retour
📅 Mon emploi du temps
Organise ton temps scolaire
🏫 Mes institutions
0 institution(s)
🔍 Choisir une institution
Les institutions disponibles sont chargées depuis la base de données.
Apprenants
0 apprenant(s)
📂 Créer des devoirs
Gérez les devoirs de vos classes
0
Total
0
Publiés
0
Brouillons
✏️
Créer un devoir
Rédiger un nouveau devoir et l'assigner à vos classes
📋
Sélectionner un devoir
Choisir un devoir existant et l'assigner à une classe
✏️ Créer un devoir
Nouveau devoir pour vos apprenants
✅ Devoir créé et enregistré avec succès !
🚀 Devoir soumis aux apprenants !
📌 Informations générales
📝 Contenu du devoir
📅 Dates
🏫 Assigner à des institutions et classes
Aucune institution disponible. Ajoutez des institutions via Mes écoles.
📎 Pièce jointe (optionnel)
📁
Cliquer pour joindre un fichier
PDF, Word, Image — Max 10 MB
⚙️ Publication
📋 Sélectionner un devoir
0 devoir(s) disponible(s)
📚 Créer des cours
Gérez les cours de vos classes
0
Total
0
Publiés
0
Brouillons
✏️
Créer un cours
Rédiger un nouveau cours et l'assigner à vos classes
📋
Sélectionner un cours
Choisir un cours existant et l'assigner à une classe
✏️ Créer un cours
Nouveau cours pour vos apprenants
✅ Cours enregistré comme brouillon !
🚀 Cours publié et soumis aux apprenants !
📌 Informations générales
📝 Contenu du cours
🏫 Assigner à des institutions et classes
Aucune institution disponible. Ajoutez des institutions via Mes institutions.
📎 Pièce jointe (optionnel)
📁
Cliquer pour joindre un fichier
PDF, Word, Image — Max 10 MB
📋 Sélectionner un cours
0 cours disponible(s)
📝 Évaluations
Sélectionnez une institution
Classe
Notes
📅 Emploi du temps
Semaine courante
Chargement...
0
Cours/sem.
0h
Heures/sem.
0
Matières
➕ Nouveau cours
⚠️ Conflit détecté ! Un autre cours occupe ce créneau.
⚙️ Gérer les matières
🗑️ Supprimer tous les cours d'une matière
💬 Mon Forum
Espaces de discussion par institution
🏫 Institution
Choisissez une classe pour discuter
💬 Forum
Classe
En ligne
📝 Mes Quiz
Créer et gérer vos quiz
📊 Mes Sondages
Créer et consulter vos sondages
Créer un sondage
🗂️ Archives
Historique des évaluations
🗂️
Aucune archive disponible pour le moment.
' ].join(''); var blob = new Blob([html], { type: 'text/html;charset=utf-8' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'bulletin_' + appNom.replace(/\s+/g,'_') + '.html'; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(function(){ URL.revokeObjectURL(url); }, 1000); } function exporterEvalPDFClasse(evalId, inst, classe) { var evals = getEvaluationsForClasse(inst, classe); var ev = evals.find(function(e){ return e.id === evalId; }); if (!ev) return; var apprenants = (apprenantsCacheParClasse[classe] || []) /* TODO: GET /api/enseignant/classes/:id/apprenants */; var dateStr = new Date().toLocaleDateString('fr-FR', { day:'2-digit', month:'long', year:'numeric' }); // Collecter les notes de tous les apprenants ayant une note var rows = ''; var total = 0; var count = 0; apprenants.forEach(function(a) { var k = apprenantKey(inst, classe, a.nom); var nd = state.notes[k] && state.notes[k][evalId]; if (!nd || nd.note === '' || nd.note === null) return; var pct = (parseFloat(nd.note) / parseFloat(nd.sur || 20)) * 20; total += pct; count++; var col = pct >= 16 ? '#2E7D32' : pct >= 10 ? 'var(--orange-dark)' : '#B71C1C'; rows += '' + '' + a.nom + '' + '' + pct.toFixed(1) + '/20' + '' + nd.note + '/' + (nd.sur || 20) + '' + ''; }); var moy = count > 0 ? (total / count).toFixed(1) : '—'; var moyCol = count > 0 && parseFloat(moy) >= 10 ? '#2E7D32' : '#B71C1C'; var enseignant = ev.enseignant || state.nom || ''; var html = [ '', '', '

Résultats — ' + ev.titre + '

', '
Exporté le ' + dateStr + ' · Réviscool
', '
', '
Enseignant
' + enseignant + '
', '
Classe
' + classe + '
', '
Institution
' + inst + '
', '
Matière
' + ev.matiere + '
', '
Type
' + ev.type + '
', '
Élèves notés
' + count + '
', '
', '', rows || '', '
ApprenantNote /20Brut
Aucune note saisie
', '
Moyenne de classe : ' + moy + '/20
', '', '' ].join(''); var blob = new Blob([html], { type: 'text/html;charset=utf-8' }); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'resultats_' + (ev.titre || 'evaluation').replace(/\s+/g,'_').replace(/[^a-zA-Z0-9_\-]/g,'') + '.html'; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(function(){ URL.revokeObjectURL(url); }, 1000); } function archiverEvalClasse(evalId, inst, classe) { var evals = getEvaluationsForClasse(inst, classe); var ev = evals.find(function(e){ return e.id === evalId; }); if (!ev) return; var apprenants = (apprenantsCacheParClasse[classe] || []) /* TODO: GET /api/enseignant/classes/:id/apprenants */; var notesApps = apprenants.filter(function(a) { var k = apprenantKey(inst, classe, a.nom); var nd = state.notes[k] && state.notes[k][evalId]; return nd && nd.note !== '' && nd.note !== null; }); if (notesApps.length === 0) { alert('Aucune note à archiver pour cette évaluation.'); return; } if (!state.archivesEvals) state.archivesEvals = []; notesApps.forEach(function(a) { var k = apprenantKey(inst, classe, a.nom); var nd = state.notes[k][evalId]; var deja = state.archivesEvals.find(function(x){ return x.evalId === evalId && x.inst === inst && x.classe === classe && x.appNom === a.nom; }); if (!deja) { state.archivesEvals.push({ evalId: evalId, inst: inst, classe: classe, appNom: a.nom, titre: ev.titre, matiere: ev.matiere, type: ev.type, sur: nd.sur || 20, note: nd.note, dateArchive: new Date().toISOString() }); } delete state.notes[k][evalId]; }); var clsKey = 'evals_' + inst + '||' + classe; if (state.evalsParClasse && state.evalsParClasse[clsKey]) { state.evalsParClasse[clsKey] = state.evalsParClasse[clsKey].filter(function(e){ return e.id !== evalId; }); } var dcKey = inst + '||' + classe; if (state.devoirsCorrigesClasse && state.devoirsCorrigesClasse[dcKey]) { delete state.devoirsCorrigesClasse[dcKey][evalId]; } openEvalClasse(inst, classe); edtToast('🗂️ Évaluation archivée (' + notesApps.length + ' élève(s))'); } function toggleNiveauSection(header) { const body = header.nextElementSibling; body.style.display = body.style.display === 'none' ? 'block' : ''; } function onNoteInput(input, evalId, sur) { let val = parseFloat(input.value); if (isNaN(val)) val = ''; if (val !== '' && val > sur) { input.value = sur; val = sur; } if (val !== '' && val < 0) { input.value = 0; val = 0; } const key = apprenantKey(evalContextApprenant.inst, evalContextApprenant.classe, evalContextApprenant.appNom); if (!state.notes[key]) state.notes[key] = {}; if (!state.notes[key][evalId]) state.notes[key][evalId] = { note: '', sur }; state.notes[key][evalId].note = val === '' ? '' : val; input.classList.toggle('note-filled', val !== ''); document.getElementById(`note-badge-${evalId}`).innerHTML = val !== '' ? noteBadgeHTML(val, sur) : ''; } function onSurChange(select, evalId) { const sur = parseInt(select.value); const key = apprenantKey(evalContextApprenant.inst, evalContextApprenant.classe, evalContextApprenant.appNom); if (!state.notes[key]) state.notes[key] = {}; if (!state.notes[key][evalId]) state.notes[key][evalId] = { note: '', sur }; state.notes[key][evalId].sur = sur; const inp = document.getElementById(`note-input-${evalId}`); if (inp) { inp.max = sur; document.getElementById(`note-badge-${evalId}`).innerHTML = state.notes[key][evalId].note !== '' ? noteBadgeHTML(state.notes[key][evalId].note, sur) : ''; } } function sauvegarderNotes() { const { inst, classe, appNom } = evalContextApprenant; const key = apprenantKey(inst, classe, appNom); if (!state.notes[key]) state.notes[key] = {}; const now = new Date().toISOString(); getEvaluationsForClasse(inst, classe).forEach(ev => { const inp = document.getElementById(`note-input-${ev.id}`); if (!inp) return; const sur = state.notes[key][ev.id]?.sur || 20; if (!state.notes[key][ev.id]) state.notes[key][ev.id] = { note: '', sur }; const newVal = inp.value === '' ? '' : parseFloat(inp.value); state.notes[key][ev.id].note = newVal; // Enregistrer la date d'attribution si une note est saisie if (newVal !== '') { if (!state.notes[key][ev.id].dateAttribution) { state.notes[key][ev.id].dateAttribution = now; } } }); // Mettre à jour le registre des devoirs corrigés par classe _majDevoirsCorrigesClasse(inst, classe); const toast = document.getElementById('notes-saved-toast'); if (toast) { toast.style.display = 'block'; setTimeout(() => toast.style.display = 'none', 2500); } renderNotesBody(); } // Construit/met à jour le registre des devoirs corrigés pour une classe function _majDevoirsCorrigesClasse(inst, classe) { if (!state.devoirsCorrigesClasse) state.devoirsCorrigesClasse = {}; const clKey = `${inst}||${classe}`; const evals = getEvaluationsForClasse(inst, classe); const apprenants = (apprenantsCacheParClasse[classe] || []) /* TODO: GET /api/enseignant/classes/:id/apprenants */; const map = state.devoirsCorrigesClasse[clKey] || {}; evals.forEach(ev => { // Un devoir est "corrigé" si au moins un apprenant a une note enregistrée let dateMin = null; let hasNote = false; apprenants.forEach(a => { const k = apprenantKey(inst, classe, a.nom); const entry = state.notes[k] && state.notes[k][ev.id]; if (entry && entry.note !== '' && entry.note !== null && entry.note !== undefined) { hasNote = true; if (entry.dateAttribution) { const d = new Date(entry.dateAttribution); if (!dateMin || d < new Date(dateMin)) dateMin = entry.dateAttribution; } } }); if (hasNote) { if (!map[ev.id]) map[ev.id] = {}; map[ev.id].titre = ev.titre; map[ev.id].matiere = ev.matiere; map[ev.id].type = ev.type; if (!map[ev.id].dateAttribution && dateMin) map[ev.id].dateAttribution = dateMin; if (!map[ev.id].dateAttribution) map[ev.id].dateAttribution = new Date().toISOString(); } }); state.devoirsCorrigesClasse[clKey] = map; } // ─── EMPLOI DU TEMPS STATE ───────────────────────────────────────────────────── const EDT_START_HOUR = 7; const EDT_END_HOUR = 18; const EDT_SLOT_HEIGHT = 56; // px per hour const EDT_DAYS = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']; const EDT_DAYS_FULL = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi']; const EDT_COLORS = [ { bg: 'var(--orange-pale)', text: 'var(--orange-dark)', border: 'var(--orange)', dot: 'var(--orange)' }, { bg: '#E8F5E9', text: '#1B5E20', border: '#43A047', dot: '#43A047' }, { bg: '#FFF3E0', text: '#BF360C', border: '#FB8C00', dot: '#FB8C00' }, { bg: '#F3E5F5', text: '#4A148C', border: '#8E24AA', dot: '#8E24AA' }, { bg: '#FCE4EC', text: '#880E4F', border: '#E91E63', dot: '#E91E63' }, { bg: '#E0F2F1', text: '#004D40', border: '#00897B', dot: '#00897B' }, { bg: '#FFF9C4', text: '#F57F17', border: '#FDD835', dot: '#F9A825' }, { bg: '#FFEBEE', text: '#B71C1C', border: '#EF5350', dot: '#EF5350' }, ]; var edtState = { courses: [], weekOffset: 0, filter: 'all', editingId: null, selectedColor: 0, dragState: null, }; var edtIdCounter = 100; // TODO: GET /api/enseignant/:id/emploi-du-temps // Les cours sont chargés dynamiquement depuis l'API à l'ouverture de la page. async function openEmploiDuTemps() { showPage('page-emploi-du-temps'); try { await fetchEdtData(); } catch(e) { RC.Err.handle(e, 'fetchEdtData', { silent: true }); } edtInit(); } function openEmploiDuTempsEcole() { var titre = document.getElementById('edt-header-title'); if (titre) titre.textContent = '📅 Emploi du temps'; openEmploiDuTemps(); } function openEmploiDuTempsApp() { var titre = document.getElementById('edt-header-title'); if (titre) titre.textContent = '📱 Mon emploi du temps sur l\'application'; openEmploiDuTemps(); } async function fetchEdtData() { var enseignantId = (window.currentUser && window.currentUser.id) ? window.currentUser.id : 'moi'; try { // TODO: GET /api/enseignant/:id/emploi-du-temps // data attendu : { courses: [ { id, matiere, classe, jour, debut, fin, salle, color } ] } var res = await fetch('/api/routes/enseignant/' + enseignantId + '/emploi-du-temps', { method: 'GET', headers: { 'Content-Type': 'application/json' } // TODO: 'Authorization': 'Bearer ' + window.authToken }); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); edtState.courses = data.courses || []; if (data.idCounter) edtIdCounter = data.idCounter; } catch(err) { RC.Err.handle(err, 'GET /api/enseignant/emploi-du-temps', { silent: true }); edtState.courses = []; } try { // TODO: GET /api/enseignant/:id/matieres-edt // data attendu : { matieres: [ 'Mathématiques', 'Français', … ] } var res2 = await fetch('/api/routes/enseignant/' + enseignantId + '/matieres-edt', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!res2.ok) throw new Error('HTTP ' + res2.status); var data2 = await res2.json(); edtMatieres = data2.matieres || []; } catch(err) { RC.Err.handle(err, 'GET /api/enseignant/matieres-edt', { silent: true }); edtMatieres = []; } } function edtInit() { buildEdtTimeSelects(); buildColorPicker(); if (typeof rebuildFilterChips === 'function') rebuildFilterChips(); if (typeof rebuildMatiereSelect === 'function') rebuildMatiereSelect(); renderEdtCalendar(); } function buildEdtTimeSelects() { const slots = []; for (let h = EDT_START_HOUR; h <= EDT_END_HOUR; h++) { slots.push(`${String(h).padStart(2,'0')}:00`); if (h < EDT_END_HOUR) slots.push(`${String(h).padStart(2,'0')}:30`); } ['edt-debut','edt-fin'].forEach(id => { const sel = document.getElementById(id); if (!sel) return; sel.innerHTML = slots.map(s => ``).join(''); }); } function buildColorPicker() { const cp = document.getElementById('edt-color-picker'); if (!cp) return; cp.innerHTML = EDT_COLORS.map((c, i) => `
` ).join(''); } function edtSelectColor(i) { edtState.selectedColor = i; buildColorPicker(); } function edtAutoEndTime() { const debut = document.getElementById('edt-debut').value; const [h, m] = debut.split(':').map(Number); let endH = h + 2; if (endH > EDT_END_HOUR) endH = EDT_END_HOUR; const endStr = `${String(endH).padStart(2,'0')}:${String(m).padStart(2,'0')}`; document.getElementById('edt-fin').value = endStr; } // ─── WEEK HELPERS ────────────────────────────────────────────────────────────── function edtGetWeekStart(offset = 0) { const now = new Date(); const day = now.getDay(); // 0=Sun const monday = new Date(now); monday.setDate(now.getDate() - (day === 0 ? 6 : day - 1) + offset * 7); monday.setHours(0,0,0,0); return monday; } function edtNavWeek(dir) { edtState.weekOffset += dir; renderEdtCalendar(); } function edtGoToday() { edtState.weekOffset = 0; renderEdtCalendar(); } function edtSetFilter(f, btn) { edtState.filter = f; document.querySelectorAll('.edt-filter-chip').forEach(c => c.classList.remove('active')); btn.classList.add('active'); renderEdtCalendar(); } function edtTimeToMinutes(t) { const [h, m] = t.split(':').map(Number); return h * 60 + m; } function edtFilteredCourses() { if (edtState.filter === 'all') return edtState.courses; return edtState.courses.filter(c => c.matiere === edtState.filter); } // ─── RENDER ──────────────────────────────────────────────────────────────────── function renderEdtCalendar() { const weekStart = edtGetWeekStart(edtState.weekOffset); const today = new Date(); today.setHours(0,0,0,0); const isThisWeek = edtState.weekOffset === 0; // Label const weekEnd = new Date(weekStart); weekEnd.setDate(weekStart.getDate() + 5); const fmt = d => d.toLocaleDateString('fr-FR', {day:'numeric', month:'short'}); document.getElementById('edt-week-label').textContent = `${fmt(weekStart)} – ${fmt(weekEnd)}`; document.getElementById('edt-header-sub').textContent = isThisWeek ? 'Semaine courante' : `Décalage : ${edtState.weekOffset > 0 ? '+' : ''}${edtState.weekOffset} sem.`; // Stats const courses = edtFilteredCourses(); const totalMin = courses.reduce((s, c) => { return s + edtTimeToMinutes(c.fin) - edtTimeToMinutes(c.debut); }, 0); document.getElementById('edt-stat-total').textContent = courses.length; document.getElementById('edt-stat-heures').textContent = `${Math.floor(totalMin/60)}h${totalMin%60>0?String(totalMin%60).padStart(2,'0'):''}`; document.getElementById('edt-stat-matieres').textContent = [...new Set(courses.map(c=>c.matiere))].length; // Day headers const headersEl = document.getElementById('edt-day-headers'); headersEl.innerHTML = '
'; for (let d = 0; d < 6; d++) { const date = new Date(weekStart); date.setDate(weekStart.getDate() + d); const isToday = isThisWeek && date.getTime() === today.getTime(); headersEl.innerHTML += `
${EDT_DAYS[d]}
${date.getDate()}
`; } // Grid const gridEl = document.getElementById('edt-grid'); gridEl.innerHTML = ''; // Time column const timeCol = document.createElement('div'); timeCol.className = 'edt-time-col'; for (let h = EDT_START_HOUR; h < EDT_END_HOUR; h++) { const slot = document.createElement('div'); slot.className = 'edt-time-slot'; slot.textContent = `${String(h).padStart(2,'0')}:00`; timeCol.appendChild(slot); } gridEl.appendChild(timeCol); // Day columns for (let d = 0; d < 6; d++) { const col = document.createElement('div'); col.className = 'edt-day-col'; col.dataset.day = d; col.style.height = `${(EDT_END_HOUR - EDT_START_HOUR) * EDT_SLOT_HEIGHT}px`; // Hour lines for (let h = 0; h < (EDT_END_HOUR - EDT_START_HOUR); h++) { const line = document.createElement('div'); line.className = 'edt-hour-line' + (h % 1 === 0 ? ' major' : ''); line.style.top = `${h * EDT_SLOT_HEIGHT}px`; col.appendChild(line); // half-hour const half = document.createElement('div'); half.className = 'edt-hour-line'; half.style.top = `${h * EDT_SLOT_HEIGHT + EDT_SLOT_HEIGHT/2}px`; half.style.borderTop = '1px dashed #F0EDE8'; half.style.height = '0'; col.appendChild(half); } // Course blocks courses.filter(c => c.jour === d).forEach(c => { const block = buildCourseBlock(c); col.appendChild(block); }); // Now line if (isThisWeek && d === (new Date().getDay() === 6 ? 5 : new Date().getDay() - 1)) { const now = new Date(); const nowMin = now.getHours() * 60 + now.getMinutes(); const startMin = EDT_START_HOUR * 60; if (nowMin >= startMin && nowMin <= EDT_END_HOUR * 60) { const nowLine = document.createElement('div'); nowLine.className = 'edt-now-line'; nowLine.style.top = `${(nowMin - startMin) / 60 * EDT_SLOT_HEIGHT}px`; nowLine.innerHTML = '
'; col.appendChild(nowLine); } } // Drop zone events col.addEventListener('dragover', edtOnDragOver); col.addEventListener('drop', edtOnDrop); col.addEventListener('dragleave', e => col.classList.remove('drag-over')); gridEl.appendChild(col); } // Mobile view renderEdtMobile(weekStart, courses); } function buildCourseBlock(c) { const color = EDT_COLORS[c.color] || EDT_COLORS[0]; const startMin = edtTimeToMinutes(c.debut) - EDT_START_HOUR * 60; const durationMin = edtTimeToMinutes(c.fin) - edtTimeToMinutes(c.debut); const top = (startMin / 60) * EDT_SLOT_HEIGHT; const height = Math.max((durationMin / 60) * EDT_SLOT_HEIGHT - 4, 20); const block = document.createElement('div'); block.className = 'edt-course-block'; block.dataset.id = c.id; block.style.top = `${top}px`; block.style.height = `${height}px`; block.style.background = color.bg; block.style.color = color.text; block.style.borderLeft = `3px solid ${color.border}`; block.draggable = true; block.innerHTML = `
${admEsc(c.matiere)}
⏰ ${admEsc(c.debut)} – ${admEsc(c.fin)}${c.salle ? ' · 🚪 ' + admEsc(c.salle) : ''}
${c.institution ? '
🏫 ' + admEsc(c.institution) + '
' : ''} ${c.niveau ? '
📶 ' + admEsc(c.niveau) + '
' : ''}
`; block.addEventListener('dragstart', e => edtOnDragStart(e, c)); block.addEventListener('click', e => { if (!e.target.closest('.edt-course-action-btn')) { ouvrirModalCours(c.id); } }); return block; } // ─── MOBILE RENDER ───────────────────────────────────────────────────────────── function renderEdtMobile(weekStart, courses) { const mobileEl = document.getElementById('edt-mobile-view'); const today = new Date(); today.setHours(0,0,0,0); let html = ''; for (let d = 0; d < 6; d++) { const date = new Date(weekStart); date.setDate(weekStart.getDate() + d); const isToday = edtState.weekOffset === 0 && date.getTime() === today.getTime(); const dayCourses = courses.filter(c => c.jour === d).sort((a,b) => a.debut.localeCompare(b.debut)); html += `
${EDT_DAYS_FULL[d]} ${date.getDate()}${isToday ? 'Aujourd\'hui' : ''}
`; if (dayCourses.length === 0) { html += `
Aucun cours
`; } else { dayCourses.forEach(c => { const color = EDT_COLORS[c.color] || EDT_COLORS[0]; html += `
${c.matiere}
⏰ ${c.debut} – ${c.fin}
${c.institution ? '
🏫 ' + c.institution + '
' : ''} ${c.niveau ? '
📶 ' + c.niveau + '
' : ''}
`; }); } html += '
'; } mobileEl.innerHTML = html; } // ─── DRAG & DROP ─────────────────────────────────────────────────────────────── function edtOnDragStart(e, c) { edtState.dragState = { courseId: c.id, startY: e.clientY }; setTimeout(() => { const el = document.querySelector(`[data-id="${c.id}"]`); if (el) el.classList.add('dragging'); }, 0); e.dataTransfer.effectAllowed = 'move'; } function edtOnDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; e.currentTarget.classList.add('drag-over'); } function edtOnDrop(e) { e.preventDefault(); const col = e.currentTarget; col.classList.remove('drag-over'); if (!edtState.dragState) return; const newDay = parseInt(col.dataset.day); const rect = col.getBoundingClientRect(); const relY = e.clientY - rect.top; const hours = relY / EDT_SLOT_HEIGHT; const snapped = Math.round(hours * 2) / 2; // snap to 30min const newStartH = Math.max(EDT_START_HOUR, Math.min(EDT_END_HOUR - 1, EDT_START_HOUR + snapped)); const newStartStr = `${String(Math.floor(newStartH)).padStart(2,'0')}:${newStartH % 1 === 0.5 ? '30' : '00'}`; const course = edtState.courses.find(c => c.id === edtState.dragState.courseId); if (!course) return; const dur = edtTimeToMinutes(course.fin) - edtTimeToMinutes(course.debut); const newStartMin = edtTimeToMinutes(newStartStr); const newEndMin = newStartMin + dur; const newEndH = Math.floor(newEndMin / 60); const newEndM = newEndMin % 60; if (newEndH > EDT_END_HOUR) return; // out of bounds const newFinStr = `${String(newEndH).padStart(2,'0')}:${String(newEndM).padStart(2,'0')}`; // Conflict check (excluding self) const conflict = edtState.courses.some(c => { if (c.id === course.id) return false; if (c.jour !== newDay) return false; const cS = edtTimeToMinutes(c.debut), cE = edtTimeToMinutes(c.fin); return newStartMin < cE && newEndMin > cS; }); if (conflict) { // Flash red const el = document.querySelector(`[data-id="${course.id}"]`); if (el) { el.style.background = '#FFEBEE'; setTimeout(() => renderEdtCalendar(), 400); } edtState.dragState = null; return; } course.jour = newDay; course.debut = newStartStr; course.fin = newFinStr; edtState.dragState = null; renderEdtCalendar(); } // ─── MODAL ───────────────────────────────────────────────────────────────────── function ouvrirModalCours(id = null) { const overlay = document.getElementById('edt-modal-overlay'); const titleEl = document.getElementById('edt-modal-title-text'); const deleteRow = document.getElementById('edt-delete-row'); document.getElementById('edt-conflict-alert').classList.remove('show'); if (id) { const c = edtState.courses.find(x => x.id === id); if (!c) return; edtState.editingId = id; titleEl.textContent = '✏️ Modifier le cours'; deleteRow.style.display = 'block'; document.getElementById('edt-matiere').value = c.matiere; document.getElementById('edt-jour').value = c.jour; document.getElementById('edt-debut').value = c.debut; document.getElementById('edt-fin').value = c.fin; var salleInp = document.getElementById('edt-salle'); if (salleInp) salleInp.value = c.salle || ''; edtState.selectedColor = c.color; buildColorPicker(); /* Restaurer institution + niveau si présents dans le cours */ _edtLoadInstitutions(c.institution || '', c.niveau || ''); } else { edtState.editingId = null; titleEl.textContent = '➕ Nouveau cours'; deleteRow.style.display = 'none'; document.getElementById('edt-matiere').value = ''; document.getElementById('edt-jour').value = 0; document.getElementById('edt-debut').value = '08:00'; document.getElementById('edt-fin').value = '10:00'; var salleInp2 = document.getElementById('edt-salle'); if (salleInp2) salleInp2.value = ''; edtState.selectedColor = 0; buildColorPicker(); /* Charger les institutions depuis l'API */ _edtLoadInstitutions('', ''); } /* Afficher les champs institution/niveau uniquement pour l'enseignant */ var instGroup = document.getElementById('edt-institution-group'); var nivGroup = document.getElementById('edt-niveau-group'); var isEns = (state.type === 'enseignant'); if (instGroup) instGroup.style.display = isEns ? '' : 'none'; if (nivGroup) nivGroup.style.display = isEns ? '' : 'none'; /* Afficher le champ Salle uniquement pour l'enseignant */ var salleGroup = document.getElementById('edt-salle-group'); if (salleGroup) salleGroup.style.display = isEns ? '' : 'none'; overlay.classList.add('open'); } function fermerModalCours() { document.getElementById('edt-modal-overlay').classList.remove('open'); edtState.editingId = null; } function sauvegarderCours() { const matiere = document.getElementById('edt-matiere').value; const jour = parseInt(document.getElementById('edt-jour').value); const debut = document.getElementById('edt-debut').value; const fin = document.getElementById('edt-fin').value; const salle = (document.getElementById('edt-salle') && state.type === 'enseignant') ? document.getElementById('edt-salle').value.trim() : ''; if (!matiere) { alert('Veuillez remplir la matière.'); return; } if (edtTimeToMinutes(fin) <= edtTimeToMinutes(debut)) { alert('L\'heure de fin doit être après l\'heure de début.'); return; } const startMin = edtTimeToMinutes(debut); const endMin = edtTimeToMinutes(fin); // Conflict detection const conflict = edtState.courses.some(c => { if (edtState.editingId && c.id === edtState.editingId) return false; if (c.jour !== jour) return false; const cS = edtTimeToMinutes(c.debut), cE = edtTimeToMinutes(c.fin); return startMin < cE && endMin > cS; }); if (conflict) { document.getElementById('edt-conflict-alert').classList.add('show'); return; } document.getElementById('edt-conflict-alert').classList.remove('show'); const institution = ((document.getElementById('edt-institution') || {}).value || ''); const niveau = ((document.getElementById('edt-niveau') || {}).value || ''); // Classe nom et ID — le niveau sélectionné sert de classe_nom // Si le niveau est un ID (UUID), on résout le nom via les référentiels var classe_nom = niveau; var classe_id = null; if (niveau && window.referentiels && window.referentiels.niveaux) { var nivObj = window.referentiels.niveaux.find(function(n) { return String(n.id) === String(niveau); }); if (nivObj) { classe_nom = nivObj.nom; classe_id = nivObj.id; } } // Si le niveau est déjà un nom (chaîne lisible), classe_nom = niveau, classe_id reste null // On tente aussi de résoudre via les classes du référentiel if (!classe_id && niveau && window.referentiels && window.referentiels.classes) { var classeObj = window.referentiels.classes.find(function(c) { return c.nom === niveau || String(c.id) === String(niveau); }); if (classeObj) { classe_id = classeObj.id; classe_nom = classeObj.nom; } } if (edtState.editingId) { const c = edtState.courses.find(x => x.id === edtState.editingId); Object.assign(c, { matiere, jour, debut, fin, salle, institution, niveau, classe_nom, classe_id, color: edtState.selectedColor }); } else { edtState.courses.push({ id: ++edtIdCounter, matiere, jour, debut, fin, salle, institution, niveau, classe_nom, classe_id, color: edtState.selectedColor }); } fermerModalCours(); renderEdtCalendar(); } /* ════════════════════════════════════════ EDT MODAL — Institutions & Niveau via API ════════════════════════════════════════ */ async function _edtLoadInstitutions(selectedInst, selectedNiv) { var sel = document.getElementById('edt-institution'); var errEl = document.getElementById('edt-institution-error'); if (!sel) return; sel.innerHTML = ''; sel.disabled = true; if (errEl) errEl.style.display = 'none'; var enseignantId = (window.currentUser && window.currentUser.id) ? window.currentUser.id : 'moi'; try { /* GET /api/enseignant/:id/institutions Réponse : { institutions: ['Nom 1', 'Nom 2', …] } */ var res = await fetch(DB._config.apiBaseUrl + '/enseignant/' + enseignantId + '/institutions', { method: 'GET', headers: Object.assign({ 'Content-Type': 'application/json' }, typeof Session !== 'undefined' ? Session.authHeader() : {}) }); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); var insts = data.institutions || []; if (insts.length === 0) throw new Error('empty'); sel.innerHTML = ''; insts.forEach(function(inst) { var opt = document.createElement('option'); opt.value = inst; opt.textContent = inst; if (inst === selectedInst) opt.selected = true; sel.appendChild(opt); }); sel.disabled = false; /* Auto-sélectionner la première institution si aucune n'est pré-choisie */ if (!selectedInst && insts.length > 0) { sel.value = insts[0]; selectedInst = insts[0]; } } catch (e) { /* Fallback sur state.institutions */ var fallback = (typeof state !== 'undefined' && state.institutions) ? state.institutions : []; if (fallback.length > 0) { sel.innerHTML = ''; fallback.forEach(function(inst) { var opt = document.createElement('option'); opt.value = inst; opt.textContent = inst; if (inst === selectedInst) opt.selected = true; sel.appendChild(opt); }); sel.disabled = false; /* Auto-sélectionner la première institution si aucune n'est pré-choisie */ if (!selectedInst && fallback.length > 0) { sel.value = fallback[0]; selectedInst = fallback[0]; } } else { sel.innerHTML = ''; sel.disabled = false; if (errEl) { errEl.textContent = '⚠️ Ajoutez des institutions via « Mes institutions ».'; errEl.style.display = 'block'; } } } /* Si une institution était sélectionnée, charger ses niveaux */ if (selectedInst) { await edtOnInstitutionChange(selectedInst, selectedNiv); } } async function edtOnInstitutionChange(institutionName, preselectNiveau) { var niveauSel = document.getElementById('edt-niveau'); var niveauErr = document.getElementById('edt-niveau-error'); if (!niveauSel) return; if (!institutionName) { niveauSel.innerHTML = ''; niveauSel.disabled = true; return; } niveauSel.innerHTML = ''; niveauSel.disabled = true; if (niveauErr) niveauErr.style.display = 'none'; try { /* GET /api/institutions/:id/niveaux Réponse : { niveaux: ['7ème AF', '8ème AF', 'NS1', …] } */ var instId = encodeURIComponent(institutionName); var res = await fetch(DB._config.apiBaseUrl + '/institutions/' + instId + '/niveaux', { method: 'GET', headers: Object.assign({ 'Content-Type': 'application/json' }, typeof Session !== 'undefined' ? Session.authHeader() : {}) }); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); var niveaux = data.niveaux || []; if (niveaux.length === 0) throw new Error('empty'); niveauSel.innerHTML = ''; niveaux.forEach(function(n) { var opt = document.createElement('option'); opt.value = n; opt.textContent = n; if (n === preselectNiveau) opt.selected = true; niveauSel.appendChild(opt); }); niveauSel.disabled = false; } catch (e) { /* Fallback niveaux standards */ var fallbackNiv = ['7ème AF','8ème AF','9ème AF','NS1','NS2','NS3','NS4']; niveauSel.innerHTML = ''; fallbackNiv.forEach(function(n) { var opt = document.createElement('option'); opt.value = n; opt.textContent = n; if (n === preselectNiveau) opt.selected = true; niveauSel.appendChild(opt); }); niveauSel.disabled = false; if (niveauErr) { niveauErr.textContent = '⚠️ Niveaux locaux (API indisponible).'; niveauErr.style.display = 'block'; } } } function supprimerCours() { if (!edtState.editingId) return; const c = edtState.courses.find(x => x.id === edtState.editingId); const label = c ? c.matiere : 'ce cours'; customConfirm(`Supprimer ${label} ?`, function() { edtState.courses = edtState.courses.filter(c => c.id !== edtState.editingId); fermerModalCours(); renderEdtCalendar(); edtToast('🗑️ Cours supprimé'); }); } function supprimerCoursId(id) { const c = edtState.courses.find(x => x.id === id); const label = c ? c.matiere : 'ce cours'; customConfirm(`Supprimer ${label} ?`, function() { edtState.courses = edtState.courses.filter(c => c.id !== id); renderEdtCalendar(); edtToast('🗑️ Cours supprimé'); }); } // ─── MATIÈRES DYNAMIQUES ─────────────────────────────────────────────────────── const EDT_MATIERE_EMOJIS = { 'Mathématiques': '📐', 'Français': '📖', 'Sciences': '🔬', 'Histoire-Géo': '🌍', 'Anglais': '🇬🇧', 'Espagnol': '🌴', 'Physique-Chimie': '⚗️', 'SVT': '🌿', 'Informatique': '💻', 'Philosophie': '🧠', 'Économie': '📊', 'Éducation Physique': '⚽', 'Autre': '📚' }; // TODO: GET /api/enseignant/:id/matieres-edt — chargé dynamiquement dans fetchEdtData() var edtMatieres = []; function edtGetEmoji(m) { return EDT_MATIERE_EMOJIS[m] || '📚'; } // ── Rebuild filter chips ─────────────────────────────────────────────────────── function rebuildFilterChips() { const bar = document.getElementById('edt-filters'); if (!bar) return; bar.innerHTML = ''; const allChip = document.createElement('div'); allChip.className = 'edt-filter-chip' + (edtState.filter === 'all' ? ' active' : ''); allChip.textContent = 'Tous'; allChip.onclick = function(){ edtSetFilter('all', this); }; bar.appendChild(allChip); edtMatieres.forEach(m => { const chip = document.createElement('div'); chip.className = 'edt-filter-chip' + (edtState.filter === m ? ' active' : ''); chip.textContent = edtGetEmoji(m) + ' ' + (m.length > 8 ? m.substring(0,8) + '…' : m); chip.onclick = (function(mat){ return function(){ edtSetFilter(mat, this); }; })(m); bar.appendChild(chip); }); } // ── Rebuild matière select in modal ────────────────────────────────────────── function rebuildMatiereSelect() { const sel = document.getElementById('edt-matiere'); if (!sel) return; const cur = sel.value; sel.innerHTML = '' + edtMatieres.map(m => ``).join(''); } // edtInit already patched above to call rebuildFilterChips / rebuildMatiereSelect // ─── GESTION MATIÈRES MODAL ──────────────────────────────────────────────────── function ouvrirGestionMatieres() { renderMatieresListeModal(); remplirSelectSuppression(); document.getElementById('edt-gestion-overlay').classList.add('open'); } function fermerGestionMatieres() { document.getElementById('edt-gestion-overlay').classList.remove('open'); } function renderMatieresListeModal() { const list = document.getElementById('edt-matieres-list'); if (edtMatieres.length === 0) { list.innerHTML = '
Aucune matière. Ajoutez-en ci-dessus.
'; return; } list.innerHTML = ''; edtMatieres.forEach((m, i) => { const count = edtState.courses.filter(c => c.matiere === m).length; const row = document.createElement('div'); row.style.cssText = 'display:flex;align-items:center;gap:10px;background:white;border-radius:12px;padding:11px 14px;border:2px solid #EEE;animation:fadeUp 0.2s ' + (i*0.03) + 's both;'; row.innerHTML = '' + edtGetEmoji(m) + '' + '' + admEsc(m) + '' + '' + count + ' cours'; const btn = document.createElement('button'); btn.textContent = '🗑️ Supprimer'; btn.style.cssText = 'background:#FFEBEE;color:#C62828;border:none;border-radius:8px;padding:6px 12px;font-size:12px;font-weight:700;cursor:pointer;transition:all 0.15s;white-space:nowrap;'; btn.onmouseover = function(){ this.style.background='#FFCDD2'; }; btn.onmouseout = function(){ this.style.background='#FFEBEE'; }; btn.onclick = (function(mat){ return function(){ supprimerMatiere(mat); }; })(m); row.appendChild(btn); list.appendChild(row); }); } function remplirSelectSuppression() { // Matières existantes dans les cours const matUsed = [...new Set(edtState.courses.map(c => c.matiere))].sort(); const selM = document.getElementById('edt-supp-matiere-select'); selM.innerHTML = '' + matUsed.map(m => ``).join(''); } function ajouterMatiere() { const input = document.getElementById('edt-new-matiere-input'); const val = input.value.trim(); if (!val) { input.focus(); return; } if (edtMatieres.includes(val)) { input.style.borderColor = '#EF5350'; setTimeout(() => input.style.borderColor = '', 1500); return; } edtMatieres.push(val); input.value = ''; rebuildFilterChips(); rebuildMatiereSelect(); renderMatieresListeModal(); remplirSelectSuppression(); // Flash success input.style.borderColor = '#43A047'; setTimeout(() => input.style.borderColor = '', 1500); } function supprimerMatiere(m) { const count = edtState.courses.filter(c => c.matiere === m).length; const msg = count > 0 ? 'Supprimer ' + m + ' et ses ' + count + ' cours ?' : 'Supprimer ' + m + ' ?'; customConfirm(msg, function() { edtState.courses = edtState.courses.filter(c => c.matiere !== m); edtMatieres = edtMatieres.filter(x => x !== m); if (edtState.filter === m) edtState.filter = 'all'; rebuildFilterChips(); rebuildMatiereSelect(); renderMatieresListeModal(); remplirSelectSuppression(); renderEdtCalendar(); }); } function supprimerTousCoursMatiere() { const m = document.getElementById('edt-supp-matiere-select').value; if (!m) { alert('Veuillez sélectionner une matière.'); return; } const count = edtState.courses.filter(c => c.matiere === m).length; if (count === 0) { alert('Aucun cours pour cette matière.'); return; } customConfirm('Supprimer les ' + count + ' cours de ' + m + ' ?', function() { edtState.courses = edtState.courses.filter(c => c.matiere !== m); if (edtState.filter === m) edtState.filter = 'all'; rebuildFilterChips(); remplirSelectSuppression(); renderEdtCalendar(); document.getElementById('edt-supp-matiere-select').value = ''; edtToast(`✅ ${count} cours de "${m}" supprimés`); }); } // supprimerTousCoursClasse — supprimé (colonne Classe retirée) // ── Toast feedback ──────────────────────────────────────────────────────────── function edtToast(msg) { let t = document.getElementById('edt-toast'); if (!t) { t = document.createElement('div'); t.id = 'edt-toast'; t.style.cssText = "position:fixed;bottom:90px;left:50%;transform:translateX(-50%);background:#2E7D32;color:white;padding:12px 22px;border-radius:50px;font-family:Nunito,sans-serif;font-size:14px;font-weight:700;z-index:999;box-shadow:0 4px 20px rgba(0,0,0,0.25);transition:opacity 0.4s;white-space:nowrap;"; document.body.appendChild(t); } t.textContent = msg; t.style.opacity = '1'; clearTimeout(t._timer); t._timer = setTimeout(() => t.style.opacity = '0', 2500); } function customConfirm(message, onOk, okLabel) { const overlay = document.getElementById('custom-confirm-overlay'); document.getElementById('confirm-message').textContent = message; overlay.style.display = 'flex'; const okBtn = document.getElementById('confirm-ok-btn'); const cancelBtn = document.getElementById('confirm-cancel-btn'); // Libellé + couleur du bouton OK selon le contexte if (okLabel === 'Archiver') { okBtn.textContent = '🗂️ Archiver'; okBtn.style.background = '#4A148C'; okBtn.onmouseover = function(){ this.style.background = '#6A1B9A'; }; okBtn.onmouseout = function(){ this.style.background = '#4A148C'; }; } else if (okLabel === 'Restaurer') { okBtn.textContent = '↩️ Restaurer'; okBtn.style.background = '#2E7D32'; okBtn.onmouseover = function(){ this.style.background = '#1B5E20'; }; okBtn.onmouseout = function(){ this.style.background = '#2E7D32'; }; } else { okBtn.textContent = '🗑️ Supprimer'; okBtn.style.background = '#E53935'; okBtn.onmouseover = function(){ this.style.background = '#C62828'; }; okBtn.onmouseout = function(){ this.style.background = '#E53935'; }; } cancelBtn.textContent = 'Annuler'; const close = () => { overlay.style.display = 'none'; }; okBtn.onclick = () => { close(); onOk(); }; cancelBtn.onclick = close; overlay.onclick = (e) => { if (e.target === overlay) close(); }; } // ─── FORUM ──────────────────────────────────────────────────────────────────── var forumInstCourante = null; /* ══════════════════════════════════════════════════════════════════════════ FORUM — Liaison enseignant ↔ élève Quand un enseignant ajoute des institutions/classes, il apparaît automatiquement dans le forum des élèves appartenant à ces classes. En production : géré côté serveur via GET /api/forum/enseignants?niveau=:niveau ══════════════════════════════════════════════════════════════════════════ */ // Registre partagé (simulé en mémoire — en production : base de données) // Structure : [{ id, nom, matiere, avatar, institution, classes:[], niveau }] if (!window._forumEnseignants) window._forumEnseignants = []; // Appelée à la connexion enseignant pour s'enregistrer dans le forum function enregistrerEnseignantForum() { if (state.type !== 'enseignant') return; var insts = state.institutions || []; if (insts.length === 0) return; // Retirer l'ancienne entrée de cet enseignant window._forumEnseignants = window._forumEnseignants.filter(function(e) { return e.id !== (state.pseudo || state.nom); }); // Construire les entrées (une par institution) insts.forEach(function(inst) { var classes = state.classesParEcole[inst] || []; if (classes.length === 0) return; // TODO: POST /api/forum/enseignants { id, nom, matiere, institution, classes, cycle } window._forumEnseignants.push({ id: state.pseudo || state.nom || ('ens_' + Date.now()), nom: state.nom || state.pseudo || 'Enseignant', matiere: state.matiereEnseignee || '', avatar: '👨‍🏫', institution: inst, classes: classes, cycle: state.niveau || '', }); }); } // Appelée quand un enseignant ajoute une institution ou classe function mettreAJourForumEnseignant() { enregistrerEnseignantForum(); } // Retourne les enseignants visibles par l'élève connecté function getEnseignantsDuForum() { // TODO: GET /api/forum/enseignants?classe=:classeEleve // En production : l'API retourne les enseignants ayant explicitement choisi // la classe de l'élève dans leurs institutions. // La classe de l'élève est stockée dans state.niveau (ex: "NS2", "7ème AF") var classeEleve = state.niveau || ''; if (!classeEleve) return []; return window._forumEnseignants.filter(function(ens) { // L'enseignant apparaît uniquement s'il a explicitement choisi // cette classe dans l'une de ses institutions return ens.classes.some(function(cl) { return cl === classeEleve; }); }); } function openForum() { var titres = { eleve: '💬 Mon Forum', enseignant: '💬 Mon Forum', technicien: '💬 Mon Forum', universitaire: '💬 Mon Forum', }; var sousTitres = { eleve: 'Vos enseignants', enseignant: 'Espaces de discussion par institution', technicien: 'Vos formateurs', universitaire: 'Vos enseignants', }; var titleEl = document.querySelector('#page-forum .forum-main-title'); var subEl = document.querySelector('#page-forum .forum-main-sub'); if (titleEl) titleEl.textContent = titres[state.type] || '💬 Mon Forum'; if (subEl) subEl.textContent = sousTitres[state.type] || 'Espaces de discussion'; renderForumList(); showPage('page-forum'); } function renderForumList() { var body = document.getElementById('forum-inst-list'); if (!body) return; // ── Enseignant : liste des institutions + outils quiz/sondages ── if (state.type === 'enseignant') { _renderForumEnseignant(); return; } // ── Élève, Technicien, Universitaire : enseignants dont les classes correspondent ── // TODO: GET /api/forum/enseignants?niveau=:niveau // En production, l'API retourne les enseignants ayant ce niveau dans leurs classes. // Ici on cherche dans state.enseignantsForum (alimenté quand un enseignant ajoute des classes). var enseignants = getEnseignantsDuForum(); if (!enseignants || enseignants.length === 0) { body.innerHTML = '
' + '
👨‍🏫
' + 'Aucun enseignant disponible pour le moment.
' + 'Vos enseignants apparaîtront ici une fois connectés.' + '
'; return; } body.innerHTML = ''; enseignants.forEach(function(prof) { var key = 'prof::' + prof.id; var msgs = (state.forumMessages[key] || []).length; var card = document.createElement('div'); card.className = 'forum-inst-card'; card.style.cursor = 'pointer'; var icon = document.createElement('div'); icon.className = 'forum-inst-icon'; icon.textContent = prof.avatar || '👨‍🏫'; var info = document.createElement('div'); info.className = 'forum-inst-info'; var nom = document.createElement('div'); nom.className = 'forum-inst-name'; nom.textContent = prof.nom; var meta = document.createElement('div'); meta.className = 'forum-inst-meta'; // Trouver la classe commune entre l'élève et l'enseignant var classeCommune = (prof.classes || []).find(function(cl){ return cl === (state.niveau||''); }) || ''; meta.textContent = '📘 ' + (classeCommune || prof.cycle || '') + (prof.institution ? ' · 🏫 ' + prof.institution : '') + (msgs > 0 ? ' · ' + msgs + ' msg' : ''); info.appendChild(nom); info.appendChild(meta); var btn = document.createElement('button'); btn.className = 'forum-connect-btn connected'; btn.textContent = '💬 Écrire'; btn.onclick = (function(p){ return function(e){ e.stopPropagation(); ouvrirChatProf(p); }; })(prof); card.appendChild(icon); card.appendChild(info); card.appendChild(btn); card.onclick = (function(p){ return function(){ ouvrirChatProf(p); }; })(prof); body.appendChild(card); }); } function ouvrirSalleForum(instName) { state.forumConnected[instName] = true; var classes = state.classesParEcole[instName] || []; document.getElementById('forum-classes-inst-title').textContent = (state.type === 'universitaire' ? '🎓 ' : state.type === 'technicien' ? '🛠️ ' : '🏫 ') + instName; // Adapter le sous-titre var classesSub = document.querySelector('#page-forum-classes .forum-classes-sub'); var subTexts = { eleve:'Choisissez une classe pour discuter', enseignant:'Choisissez une classe pour discuter', technicien:'Choisissez une filière pour échanger', universitaire:'Choisissez une faculté pour discuter' }; if (classesSub) classesSub.textContent = subTexts[state.type] || 'Choisissez une classe pour discuter'; var list = document.getElementById('forum-classes-list'); if (classes.length === 0) { list.innerHTML = '
' + '
👥
' + 'Aucune classe enregistrée pour cette institution.
' + 'Ajoutez des classes via Mes institutions.' + '
'; } else { list.innerHTML = '
Sélectionnez une classe :
'; classes.forEach(function(classe) { var key = instName + '::' + classe; var nb = (state.forumMessages[key] || []).length; var card = document.createElement('div'); card.style.cssText = 'background:white;border-radius:16px;padding:16px 18px;display:flex;align-items:center;gap:14px;box-shadow:0 3px 12px rgba(0,0,0,0.06);margin-bottom:12px;cursor:pointer;transition:all 0.2s;border:2px solid transparent;'; card.onmouseover = function(){ this.style.borderColor='var(--orange)';this.style.transform='translateY(-2px)'; }; card.onmouseout = function(){ this.style.borderColor='transparent';this.style.transform=''; }; card.innerHTML = '
👥
' + '
' + '
' + classe + '
' + '
' + nb + ' message(s)
' + '
' + '
Entrer →
'; card.onclick = (function(inst, cl){ return function(){ ouvrirChatClasse(inst, cl); }; })(instName, classe); list.appendChild(card); }); } showPage('page-forum-classes'); } function ouvrirChatProf(prof) { // L'élève rejoint l'espace forum de l'enseignant pour suivre et interagir // TODO: GET /api/forum/espace?enseignantId=:prof.id&classe=:classeEleve // TODO: GET /api/forum/messages?espaceId=:key // Clé unique : espace forum de cet enseignant pour cette classe var classeEleve = state.niveau || ''; var key = 'espace::' + prof.id + '::' + classeEleve; forumInstCourante = key; // Titre : nom de l'enseignant document.getElementById('forum-salle-title').textContent = '🎓 ' + prof.nom; // Sous-titre : classe + institution document.getElementById('forum-salle-sub').textContent = classeEleve + (prof.institution ? ' · ' + prof.institution : ''); document.getElementById('forum-salle-status').textContent = '● Espace de cours'; // Retour → liste des enseignants document.getElementById('forum-salle-back').onclick = function() { renderForumList(); showPage('page-forum'); }; // Message de bienvenue à la première ouverture if (!state.forumMessages[key] || state.forumMessages[key].length === 0) { var msgBienvenue = 'Bienvenue dans mon espace forum ! 👋 Classe : ' + classeEleve + (prof.institution ? ' · ' + prof.institution : '') + ' — Posez vos questions ou suivez les cours ici.'; state.forumMessages[key] = [ { auteur: prof.nom, texte: msgBienvenue, time: maintenant(), mine: false } ]; } var nomEleve = state.pseudo || state.nom || 'Élève'; var inp = document.getElementById('forum-input'); if (inp) inp.placeholder = 'Écrire à ' + prof.nom + '…'; // Barre outils enseignant (masquée pour les élèves) var toolbar = document.getElementById('forum-ens-toolbar'); if (toolbar) toolbar.style.display = 'none'; renderMessages(); showPage('page-forum-salle'); setTimeout(function(){ scrollMessagesEnBas(); }, 100); } function ouvrirChatClasse(instName, classe) { var key = instName + '::' + classe; forumInstCourante = key; document.getElementById('forum-salle-title').textContent = '💬 ' + classe; document.getElementById('forum-salle-sub').textContent = instName; document.getElementById('forum-salle-status').textContent = '● En ligne'; document.getElementById('forum-salle-back').onclick = function() { ouvrirSalleForum(instName); }; if (!state.forumMessages[key] || state.forumMessages[key].length === 0) { state.forumMessages[key] = [ { auteur: 'Système', texte: 'Bienvenue dans le forum de ' + classe + ' — ' + instName + ' ! 🎉', time: maintenant(), mine: false } ]; } // Afficher le nom de l'enseignant connecté dans l'input placeholder var nomEns = getNomAuteur(); var inp = document.getElementById('forum-input'); if (inp) inp.placeholder = 'Message de ' + nomEns + '...'; // Barre outils enseignant var toolbar = document.getElementById('forum-ens-toolbar'); if (toolbar) toolbar.style.display = (state.type === 'enseignant') ? 'flex' : 'none'; renderMessages(); showPage('page-forum-salle'); setTimeout(function(){ scrollMessagesEnBas(); }, 100); } function renderMessages() { var container = document.getElementById('forum-messages'); var msgs = state.forumMessages[forumInstCourante] || []; container.innerHTML = ''; msgs.forEach(function(m) { var div = document.createElement('div'); div.className = 'forum-msg ' + (m.mine ? 'mine' : 'other'); var author = document.createElement('div'); author.className = 'forum-msg-author'; author.textContent = m.auteur; div.appendChild(author); var type = m.type || 'texte'; if (type === 'audio') { // Bulle audio var audioWrap = document.createElement('div'); audioWrap.style.cssText = 'display:flex;align-items:center;gap:8px;padding:4px 0;'; var playBtn = document.createElement('button'); playBtn.style.cssText = 'width:32px;height:32px;border-radius:50%;background:rgba(255,255,255,0.25);border:none;color:inherit;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;'; playBtn.textContent = '▶'; var audioEl = document.createElement('audio'); audioEl.src = m.audioUrl; audioEl.style.display = 'none'; playBtn.onclick = function() { if (audioEl.paused) { audioEl.play(); playBtn.textContent = '⏸'; } else { audioEl.pause(); playBtn.textContent = '▶'; } }; audioEl.onended = function() { playBtn.textContent = '▶'; }; var bar = document.createElement('div'); bar.style.cssText = 'flex:1;height:3px;background:rgba(255,255,255,0.35);border-radius:3px;min-width:80px;'; var dur = document.createElement('span'); dur.style.cssText = 'font-size:11px;opacity:0.8;white-space:nowrap;'; dur.textContent = '🎤 ' + (m.duree || '0:00'); audioWrap.appendChild(playBtn); audioWrap.appendChild(bar); audioWrap.appendChild(dur); audioWrap.appendChild(audioEl); div.appendChild(audioWrap); } else if (type === 'image') { var img = document.createElement('img'); img.src = m.dataUrl; img.style.cssText = 'max-width:200px;max-height:180px;border-radius:10px;display:block;margin:4px 0;cursor:pointer;'; img.onclick = function() { window.open(m.dataUrl, '_blank'); }; div.appendChild(img); if (m.texte) { var cap = document.createElement('div'); cap.className = 'forum-msg-text'; cap.style.marginTop = '4px'; cap.textContent = m.texte; div.appendChild(cap); } } else if (type === 'document') { var docWrap = document.createElement('div'); docWrap.style.cssText = 'display:flex;align-items:center;gap:8px;background:rgba(255,255,255,0.15);border-radius:10px;padding:8px 10px;cursor:pointer;'; docWrap.onclick = function() { window.open(m.dataUrl, '_blank'); }; var icn = document.createElement('span'); icn.style.fontSize = '22px'; icn.textContent = getDocIcon(m.fileName); var info = document.createElement('div'); var fn = document.createElement('div'); fn.style.cssText = 'font-size:13px;font-weight:700;word-break:break-all;'; fn.textContent = m.fileName; var dl = document.createElement('div'); dl.style.cssText = 'font-size:11px;opacity:0.7;margin-top:2px;'; dl.textContent = 'Appuyer pour ouvrir'; info.appendChild(fn); info.appendChild(dl); docWrap.appendChild(icn); docWrap.appendChild(info); div.appendChild(docWrap); if (m.texte) { var cap2 = document.createElement('div'); cap2.className = 'forum-msg-text'; cap2.style.marginTop = '4px'; cap2.textContent = m.texte; div.appendChild(cap2); } } else if (type === 'html') { var wrap = document.createElement('div'); wrap.innerHTML = m.html || ''; div.appendChild(wrap); } else { var text = document.createElement('div'); text.className = 'forum-msg-text'; text.textContent = m.texte; div.appendChild(text); } var time = document.createElement('div'); time.className = 'forum-msg-time'; time.textContent = m.time; div.appendChild(time); container.appendChild(div); }); } function getNomAuteur() { var nom = (state.nom || '').trim(); var pseudo = (state.pseudo || '').trim(); return nom || pseudo || 'Enseignant'; } function envoyerMessage() { var input = document.getElementById('forum-input'); var texte = input.value.trim(); var hasFichiers = forumFilesEnAttente.length > 0; if (!texte && !hasFichiers) return; if (!forumInstCourante) return; if (!state.forumMessages[forumInstCourante]) state.forumMessages[forumInstCourante] = []; // Envoyer fichiers joints forumFilesEnAttente.forEach(function(f) { state.forumMessages[forumInstCourante].push({ auteur: getNomAuteur(), type: f.type.startsWith('image/') ? 'image' : 'document', fileName: f.name, fileType: f.type, dataUrl: f.dataUrl, texte: texte || '', time: maintenant(), mine: true }); }); // Envoyer message texte si pas de fichier, ou en plus if (texte && forumFilesEnAttente.length === 0) { state.forumMessages[forumInstCourante].push({ auteur: getNomAuteur(), type: 'texte', texte: texte, time: maintenant(), mine: true }); } forumFilesEnAttente = []; renderDocPreview(); input.value = ''; renderMessages(); scrollMessagesEnBas(); } function scrollMessagesEnBas() { var c = document.getElementById('forum-messages'); if (c) c.scrollTop = c.scrollHeight; } // ── FORUM : PIÈCES JOINTES ────────────────────────────────────────────────── var forumFilesEnAttente = []; // { name, type, dataUrl } function forumAttachFile(input) { var file = input.files[0]; if (!file) return; var maxSize = 10 * 1024 * 1024; // 10 MB if (file.size > maxSize) { alert('Fichier trop lourd (max 10 Mo).'); input.value=''; return; } var reader = new FileReader(); reader.onload = function(e) { forumFilesEnAttente.push({ name: file.name, type: file.type, dataUrl: e.target.result }); renderDocPreview(); }; reader.readAsDataURL(file); input.value = ''; } function renderDocPreview() { var zone = document.getElementById('forum-doc-preview'); if (forumFilesEnAttente.length === 0) { zone.style.display = 'none'; zone.innerHTML = ''; return; } zone.style.display = 'flex'; zone.innerHTML = ''; forumFilesEnAttente.forEach(function(f, i) { var isImage = f.type.startsWith('image/'); var chip = document.createElement('div'); chip.style.cssText = 'display:flex;align-items:center;gap:6px;background:var(--orange-pale);border-radius:20px;padding:5px 10px 5px 8px;font-size:12px;font-weight:700;color:var(--orange-dark);max-width:200px;'; var icon = isImage ? '🖼️' : getDocIcon(f.name); chip.innerHTML = '' + icon + '' + '' + f.name + '' + ''; zone.appendChild(chip); }); } function retirerFichier(i) { forumFilesEnAttente.splice(i, 1); renderDocPreview(); } function getDocIcon(name) { var ext = name.split('.').pop().toLowerCase(); var map = { pdf:'📄', doc:'📝', docx:'📝', ppt:'📊', pptx:'📊', xls:'📈', xlsx:'📈', txt:'📃', png:'🖼️', jpg:'🖼️', jpeg:'🖼️', gif:'🖼️' }; return map[ext] || '📎'; } // ── FORUM : AUDIO ─────────────────────────────────────────────────────────── var mediaRecorder = null; var audioChunks = []; var recTimerInterval = null; var recSeconds = 0; var recWaveInterval = null; function toggleEnregistrement() { if (mediaRecorder && mediaRecorder.state === 'recording') { stopperEnregistrement(); } else { demarrerEnregistrement(); } } function demarrerEnregistrement() { if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { alert("L'enregistrement audio n'est pas supporté par ce navigateur."); return; } navigator.mediaDevices.getUserMedia({ audio: true }).then(function(stream) { window._micStream = stream; window._recActive = true; audioChunks = []; var mimeType = ''; if (typeof MediaRecorder !== 'undefined') { if (MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) mimeType = 'audio/webm;codecs=opus'; else if (MediaRecorder.isTypeSupported('audio/webm')) mimeType = 'audio/webm'; else if (MediaRecorder.isTypeSupported('audio/ogg')) mimeType = 'audio/ogg'; } mediaRecorder = mimeType ? new MediaRecorder(stream, { mimeType: mimeType }) : new MediaRecorder(stream); mediaRecorder.ondataavailable = function(e) { if (e.data && e.data.size > 0) audioChunks.push(e.data); }; mediaRecorder.onstop = function() { if (window._recActive) { var blob = new Blob(audioChunks, { type: mediaRecorder.mimeType || 'audio/webm' }); var url = URL.createObjectURL(blob); envoyerAudio(url, recSeconds); } if (window._micStream) { window._micStream.getTracks().forEach(function(t){ t.stop(); }); window._micStream = null; } window._recActive = false; resetRecUI(); }; mediaRecorder.start(100); var micBtn = document.getElementById('forum-mic-btn'); document.getElementById('forum-rec-bar').style.display = 'flex'; micBtn.style.background = '#E53935'; micBtn.style.color = 'white'; micBtn.style.borderColor = '#E53935'; micBtn.innerHTML = '⏹'; recSeconds = 0; document.getElementById('forum-rec-timer').textContent = '0:00'; recTimerInterval = setInterval(function() { recSeconds++; var m = Math.floor(recSeconds / 60), s = recSeconds % 60; document.getElementById('forum-rec-timer').textContent = m + ':' + (s < 10 ? '0' : '') + s; if (recSeconds >= 300) stopperEnregistrement(); }, 1000); recWaveInterval = setInterval(function() { var w = 15 + Math.random() * 75; document.getElementById('forum-rec-wave').style.width = w + '%'; }, 150); }).catch(function(err) { window._recActive = false; alert('Impossible d\'accéder au microphone : ' + err.message); }); } function stopperEnregistrement() { if (mediaRecorder && mediaRecorder.state === 'recording') { mediaRecorder.stop(); } clearInterval(recTimerInterval); clearInterval(recWaveInterval); } function annulerEnregistrement() { window._recActive = false; // désactiver l'envoi if (mediaRecorder && mediaRecorder.state === 'recording') { mediaRecorder.stop(); } else { if (window._micStream) { window._micStream.getTracks().forEach(function(t){ t.stop(); }); window._micStream = null; } resetRecUI(); } clearInterval(recTimerInterval); clearInterval(recWaveInterval); } function seDeconnecterForum() { // Annuler enregistrement en cours si besoin if (mediaRecorder && mediaRecorder.state === 'recording') annulerEnregistrement(); // Marquer déconnecté if (forumInstCourante) { var inst = forumInstCourante.split('::')[0]; state.forumConnected[inst] = false; } forumInstCourante = null; // Retour à la liste des institutions renderForumList(); showPage('page-forum'); } function resetRecUI() { document.getElementById('forum-rec-bar').style.display = 'none'; document.getElementById('forum-rec-wave').style.width = '0%'; var btn = document.getElementById('forum-mic-btn'); btn.style.background = 'var(--orange-pale)'; btn.style.color = 'var(--orange)'; btn.style.borderColor = 'var(--orange)'; btn.innerHTML = '🎤'; mediaRecorder = null; mediaRecorder = null; } function envoyerAudio(url, duree) { if (!forumInstCourante) return; if (!state.forumMessages[forumInstCourante]) state.forumMessages[forumInstCourante] = []; var m = Math.floor(duree/60), s = duree%60; state.forumMessages[forumInstCourante].push({ auteur: getNomAuteur(), type: 'audio', audioUrl: url, duree: m + ':' + (s<10?'0':'') + s, time: maintenant(), mine: true }); renderMessages(); scrollMessagesEnBas(); } // ══════════════════════════════════════════════════════════════ // FORUM ENSEIGNANT — QUIZ & SONDAGES // ══════════════════════════════════════════════════════════════ /* ── Données dures de démonstration ── */ var _forumEnsQuiz = []; // TODO: GET /api/quiz?enseignantId=:id → { quiz: [...] } var _forumEnsNdages = []; // TODO: GET /api/sondages?enseignantId=:id → { sondages: [...] } /* ── État du créateur de quiz ── */ var _quizEditeur = { titre:'', classe:'', melange:false, questions:[], editIndex:-1 }; var _ndageEditeur = { titre:'', classe:'', multiple:false, options:[] }; /* ── Fonctions d'ouverture du forum enseignant amélioré ── */ function _renderForumEnseignant() { var body = document.getElementById('forum-inst-list'); if (!body) return; // ── Si aucune institution : message vide ── var insts = state.institutions || []; if (insts.length === 0) { body.innerHTML = '
' + '
🏫
' + 'Aucune institution disponible.
' + 'Ajoutez des institutions via Mes institutions.' + '
' + _buildForumEnsOutils(); return; } // ── Institutions + outils ── var html = ''; insts.forEach(function(inst) { var msgs = (state.forumMessages[inst] || []).length; var connected = state.forumConnected[inst] || false; var classes = (state.classesParEcole[inst] || []).length; html += '
' + '
🏫
' + '
' + '
' + inst + '
' + '
' + classes + ' classe(s) · ' + msgs + ' message(s)' + (connected ? ' ● Connecté' : '') + '
' + '
' + '' + '
'; }); html += _buildForumEnsOutils(); body.innerHTML = html; } function _buildForumEnsOutils() { return ''; } /* ══════════════════════════════════ GESTION QUIZ ══════════════════════════════════ */ function ouvrirGestionQuiz() { var page = document.getElementById('page-forum-quiz'); _renderListeQuiz(); showPage('page-forum-quiz'); } function _renderListeQuiz() { var body = document.getElementById('forum-quiz-body'); if (!body) return; var vl = document.getElementById('forum-quiz-view-liste'); var ve = document.getElementById('forum-quiz-view-editeur'); var va = document.getElementById('forum-quiz-view-apercu'); if (vl) vl.style.display = ''; if (ve) ve.style.display = 'none'; if (va) va.style.display = 'none'; if (_forumEnsQuiz.length === 0) { body.innerHTML = '
📝
Aucun quiz créé.
'; return; } body.innerHTML = _forumEnsQuiz.map(function(q) { var total = q.questions.reduce(function(s,r){ return s+r.note; }, 0); return '
' + '
' + '
' + '
📝 ' + _esc(q.titre) + '
' + '
👥 ' + _esc(q.classe) + ' · ' + q.questions.length + ' question(s) · ' + total + ' pts · ' + (q.melange ? '🔀 Mélangé' : '📋 Ordre fixe') + '
' + '
' + '
' + '' + '' + '' + '
' + '
' + '' + '
'; }).join(''); } function _supprimerQuiz(id) { if (!confirm('Supprimer ce quiz ?')) return; _forumEnsQuiz = _forumEnsQuiz.filter(function(q){ return q.id !== id; }); _renderListeQuiz(); } function _apercuQuiz(id) { var q = _forumEnsQuiz.find(function(x){ return x.id === id; }); if (!q) return; var vl2 = document.getElementById('forum-quiz-view-liste'); var ve2 = document.getElementById('forum-quiz-view-editeur'); if (vl2) vl2.style.display = 'none'; if (ve2) ve2.style.display = 'none'; var ap = document.getElementById('forum-quiz-view-apercu'); if (!ap) return; ap.style.display = ''; var qs = q.melange ? _melangerTableau(q.questions.slice()) : q.questions; var total = q.questions.reduce(function(s,r){ return s+r.note; }, 0); ap.innerHTML = '
' + '' + '
' + '
📝 ' + q.titre + '
' + '
👥 ' + q.classe + ' · ' + total + ' pts au total' + (q.melange ? ' · 🔀 Ordre aléatoire' : '') + '
' + '
' + '
' + qs.map(function(r, i) { return '
' + '
Q' + (i+1) + '. ' + r.texte + ' ' + r.note + ' pt(s)
' + r.choix.map(function(c) { var isBonne = c === r.bonneReponse; return '
' + (isBonne ? '✅ ' : '○ ') + c + '
'; }).join('') + '
'; }).join(''); } function _editerQuiz(id) { // Extraire la classe courante depuis forumInstCourante (format "Institution::Classe") var classeAuto = ''; if (forumInstCourante && forumInstCourante.indexOf('::') !== -1) { classeAuto = forumInstCourante.split('::')[1] || ''; } var q = id ? _forumEnsQuiz.find(function(x){ return x.id === id; }) : null; _quizEditeur = q ? { id: q.id, titre: q.titre, classe: q.classe, melange: q.melange, questions: JSON.parse(JSON.stringify(q.questions)), editIndex: -1 } : { id: null, titre: '', classe: classeAuto, melange: false, questions: [], editIndex: -1 }; _renderEditeurQuiz(); } function _renderEditeurQuiz() { var vl3 = document.getElementById('forum-quiz-view-liste'); var va3 = document.getElementById('forum-quiz-view-apercu'); if (vl3) vl3.style.display = 'none'; if (va3) va3.style.display = 'none'; var ed = document.getElementById('forum-quiz-view-editeur'); if (!ed) return; ed.style.display = ''; var totalPts = _quizEditeur.questions.reduce(function(s,r){ return s+r.note; }, 0); ed.innerHTML = // En-tête éditeur '
' + '' + '
' + (_quizEditeur.id ? '✏️ Modifier le quiz' : '📝 Nouveau quiz') + '
' + '
' + // Infos générales '
' + '
📋 Informations générales
' + '' + '' + '
' + '
' + '
🔀 Mélanger l\'ordre des questions
' + '
Chaque apprenant verra les questions dans un ordre différent
' + '
' + '
' + '
' + '
' + '
' + '
' + // Liste des questions '
❓ Questions (' + _quizEditeur.questions.length + ' question(s) · ' + totalPts + ' pts)
' + _quizEditeur.questions.map(function(q, i) { return '
' + '
' + '
Q' + (i+1) + '. ' + q.texte + '
' + '
' + (i > 0 ? '' : '') + (i < _quizEditeur.questions.length-1 ? '' : '') + '' + '
' + '
' + '
✅ Bonne réponse : ' + q.bonneReponse + ' · ' + q.note + ' pt(s) · ' + q.choix.length + ' choix
' + '
'; }).join('') + // Formulaire ajout de question '
' + '
➕ Ajouter une question
' + '' + '
' + '' + '' + '' + '' + '
' + '' + '
' + '' + '' + '
' + '' + '
' + // Bouton sauvegarder ''; } function _toggleMelangeQuiz() { _quizEditeur.melange = !_quizEditeur.melange; var btn = document.getElementById('qed-melange-btn'); if (!btn) return; btn.style.background = _quizEditeur.melange ? 'var(--orange)' : '#DDD'; btn.querySelector('div').style.left = _quizEditeur.melange ? '23px' : '3px'; } function _ajouterQuestion() { var texte = (document.getElementById('qed-q-texte').value || '').trim(); var c1 = (document.getElementById('qed-c1').value || '').trim(); var c2 = (document.getElementById('qed-c2').value || '').trim(); var c3 = (document.getElementById('qed-c3').value || '').trim(); var c4 = (document.getElementById('qed-c4').value || '').trim(); var bonne = (document.getElementById('qed-bonne').value || '').trim(); var note = parseInt(document.getElementById('qed-note').value) || 2; if (!texte || !c1 || !c2 || !bonne) { alert('⚠️ Veuillez remplir le texte, au moins 2 choix et la bonne réponse.'); return; } var choix = [c1, c2]; if (c3) choix.push(c3); if (c4) choix.push(c4); if (!choix.includes(bonne)) { alert('⚠️ La bonne réponse doit correspondre exactement à l\'un des choix.'); return; } _quizEditeur.questions.push({ id: Date.now(), texte: texte, bonneReponse: bonne, note: note, choix: choix }); _renderEditeurQuiz(); } function _supprimerQuestion(i) { _quizEditeur.questions.splice(i, 1); _renderEditeurQuiz(); } function _deplacerQuestion(i, dir) { var j = i + dir; if (j < 0 || j >= _quizEditeur.questions.length) return; var tmp = _quizEditeur.questions[i]; _quizEditeur.questions[i] = _quizEditeur.questions[j]; _quizEditeur.questions[j] = tmp; _renderEditeurQuiz(); } function _sauvegarderQuiz() { _quizEditeur.titre = (document.getElementById('qed-titre') ? document.getElementById('qed-titre').value.trim() : _quizEditeur.titre); _quizEditeur.classe = (document.getElementById('qed-classe') ? document.getElementById('qed-classe').value.trim() : _quizEditeur.classe); if (!_quizEditeur.titre || !_quizEditeur.classe) { alert('⚠️ Veuillez renseigner le titre et la classe.'); return; } if (_quizEditeur.questions.length === 0) { alert('⚠️ Ajoutez au moins une question.'); return; } // TODO: POST /api/quiz (création) ou PUT /api/quiz/:id (modification) if (_quizEditeur.id) { var idx = _forumEnsQuiz.findIndex(function(q){ return q.id === _quizEditeur.id; }); if (idx >= 0) _forumEnsQuiz[idx] = JSON.parse(JSON.stringify(_quizEditeur)); } else { var nouveau = JSON.parse(JSON.stringify(_quizEditeur)); nouveau.id = 'q' + Date.now(); nouveau.dateCreation = new Date().toISOString().slice(0,10); _forumEnsQuiz.push(nouveau); } // Envoyer directement dans le chat si une salle est active var idSauvegarde = _quizEditeur.id || _forumEnsQuiz[_forumEnsQuiz.length - 1].id; if (forumInstCourante) { _envoyerQuizDansChat(idSauvegarde); } else { _renderListeQuiz(); } } /* ── Envoyer un quiz dans le fil de chat ── */ function _envoyerQuizDansChat(id) { var q = _forumEnsQuiz.find(function(x){ return x.id === id; }); if (!q) return; if (!forumInstCourante) { alert('Ouvrez d\'abord une salle de classe.'); return; } var qs = q.melange ? _melangerTableau(q.questions.slice()) : q.questions.slice(); var total = q.questions.reduce(function(s,r){ return s+r.note; }, 0); var carteHtml = '
' + '
📝 Quiz · ' + _esc(q.titre) + '
' + '
👥 ' + _esc(q.classe) + ' · ' + qs.length + ' question(s) · ' + total + ' pts' + (q.melange ? ' · 🔀 Ordre aléatoire' : '') + '
' + qs.map(function(r, i){ return '
' + '
Q' + (i+1) + '. ' + _esc(r.texte) + ' ' + r.note + ' pt(s)
' + r.choix.map(function(c){ return '
○ ' + _esc(c) + '
'; }).join('') + '
'; }).join('') + '
' + '' + '
' + '
'; if (!state.forumMessages[forumInstCourante]) state.forumMessages[forumInstCourante] = []; state.forumMessages[forumInstCourante].push({ auteur: getNomAuteur(), type: 'html', html: carteHtml, texte: '📝 Quiz : ' + q.titre, time: maintenant(), mine: true }); showPage('page-forum-salle'); renderMessages(); setTimeout(scrollMessagesEnBas, 80); } /* ── Envoyer un sondage dans le fil de chat ── */ function _envoyerSondageDansChat(id) { var s = _forumEnsNdages.find(function(x){ return x.id === id; }); if (!s) return; if (!forumInstCourante) { alert('Ouvrez d\'abord une salle de classe.'); return; } // Support ancien format (options[]) et nouveau format (questions[]) var questions = s.questions || [{ texte: s.titre, options: s.options, multiple: s.multiple }]; var questionsHtml = questions.map(function(q, qi) { var opts = (q.options || []).filter(function(o){ return o && o.trim(); }); var shape = q.multiple ? 'border-radius:4px;' : 'border-radius:50%;'; return '
' + '
' + _esc(q.texte) + '
' + opts.map(function(opt){ return '
' + '' + '' + _esc(opt) + '' + '
'; }).join('') + '
'; }).join(''); var carteHtml = '
' + '
📊 Sondage
' + '
👥 ' + _esc(s.classe) + ' · ' + questions.length + ' question(s)
' + questionsHtml + '
' + '' + '
' + '
'; if (!state.forumMessages[forumInstCourante]) state.forumMessages[forumInstCourante] = []; state.forumMessages[forumInstCourante].push({ auteur: getNomAuteur(), type: 'html', html: carteHtml, texte: '📊 Sondage : ' + (s.titre || questions[0].texte), time: maintenant(), mine: true }); showPage('page-forum-salle'); renderMessages(); setTimeout(scrollMessagesEnBas, 80); } function _melangerTableau(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return arr; } function _esc(s) { return String(s||'').replace(/&/g,'&').replace(/"/g,'"').replace(//g,'>'); } /* ══════════════════════════════════ GESTION SONDAGES ══════════════════════════════════ */ function ouvrirGestionSondages() { _renderListeSondages(); showPage('page-forum-sondages'); } function _renderListeSondages() { var body = document.getElementById('forum-sondages-body'); if (!body) return; var vl = document.getElementById('forum-sond-view-liste'); var ve = document.getElementById('forum-sond-view-editeur'); if (vl) vl.style.display = ''; if (ve) ve.style.display = 'none'; if (_forumEnsNdages.length === 0) { body.innerHTML = '
📊
Aucun sondage créé.
'; return; } body.innerHTML = _forumEnsNdages.map(function(s) { var totalVotes = s.votes.reduce(function(a,b){ return a+b; }, 0); var optsHtml = s.options.map(function(opt, i) { var pct = totalVotes ? Math.round((s.votes[i]/totalVotes)*100) : 0; return '
' + '
' + '' + _esc(opt) + '' + s.votes[i] + ' vote(s) · ' + pct + '%' + '
' + '
' + '
' + '
' + '
'; }).join(''); return '
' + '
' + '
' + '
📊 ' + _esc(s.titre) + '
' + '
👥 ' + _esc(s.classe) + ' · ' + totalVotes + ' vote(s) · ' + (s.multiple ? 'Choix multiple' : 'Choix unique') + '
' + '
' + '' + '
' + optsHtml + '' + '
'; }).join(''); } function _supprimerSondage(id) { if (!confirm('Supprimer ce sondage ?')) return; _forumEnsNdages = _forumEnsNdages.filter(function(s){ return s.id !== id; }); _renderListeSondages(); } function _ouvrirNouveauSondage() { _ndageEditeur = { titre: '', classe: '', questions: [ { texte: '', options: ['', ''], multiple: false } ] }; // Afficher la page d'abord, puis remplir le contenu showPage('page-forum-sond-create'); _renderEditeurSondage(); } function _renderEditeurSondage() { // Utilise la page dédiée — header propre sans orange var body = document.getElementById('forum-sond-create-body'); if (!body) return; var html = // ── Bloc par question ── _ndageEditeur.questions.map(function(q, qi) { return '
' + // Séparateur + numéro si >1 question (qi > 0 ? '
' + '
' + 'Question ' + (qi+1) + '' + '
' + '' + '
' : '') + // Champ question '
Question
' + '' + // Options '
Options
' + q.options.map(function(opt, oi) { return '
' + '' + (q.options.length > 2 ? '' : '
') + '
'; }).join('') + // + Ajouter option (champ vide cliquable) '
+ Ajouter
' + // Toggle "Autoriser plusieurs réponses" '
' + '
Autoriser plusieurs réponses
' + '
' + '
' + '
' + '
' + '
'; }).join('') + // ── + Ajouter une question ── '' + // ── Créer ── ''; body.innerHTML = html; } function _toggleMultiple_sond(qi) { _ndageEditeur.questions[qi].multiple = !_ndageEditeur.questions[qi].multiple; _renderEditeurSondage(); } function _ajouterOption_sond(qi) { // Lire les valeurs actuelles avant de re-render _syncSondageEditeur(); _ndageEditeur.questions[qi].options.push(''); _renderEditeurSondage(); // Focus sur la dernière option de cette question setTimeout(function(){ var blocks = document.querySelectorAll('#sed-q-block-' + qi + ' input'); if (blocks.length) blocks[blocks.length-1].focus(); }, 30); } function _supprimerOption_sond(qi, oi) { _syncSondageEditeur(); _ndageEditeur.questions[qi].options.splice(oi, 1); _renderEditeurSondage(); } function _ajouterQuestion_sond() { _syncSondageEditeur(); _ndageEditeur.questions.push({ texte: '', options: ['', ''], multiple: false }); _renderEditeurSondage(); setTimeout(function(){ window.scrollTo(0, document.body.scrollHeight); }, 30); } function _supprimerQuestion_sond(qi) { _syncSondageEditeur(); _ndageEditeur.questions.splice(qi, 1); _renderEditeurSondage(); } // Lit les inputs du DOM et synchronise _ndageEditeur avant de modifier la structure function _syncSondageEditeur() { // Sync questions textes et options via les inputs en place _ndageEditeur.questions.forEach(function(q, qi) { var block = document.getElementById('sed-q-block-' + qi); if (!block) return; var inputs = block.querySelectorAll('input'); if (inputs.length === 0) return; // Premier input = texte de la question q.texte = inputs[0].value; // Inputs suivants = options for (var oi = 0; oi < q.options.length; oi++) { if (inputs[oi + 1]) q.options[oi] = inputs[oi + 1].value; } }); } function _toggleMultipleSondage() { /* ancien — gardé pour compatibilité */ } function _ajouterOptionSondage() { /* ancien — gardé pour compatibilité */ } function _sauvegarderSondage() { _syncSondageEditeur(); var classe = _ndageEditeur.classe || ''; if (_ndageEditeur.questions.length === 0) { alert('⚠️ Ajoutez au moins une question.'); return; } for (var qi = 0; qi < _ndageEditeur.questions.length; qi++) { var q = _ndageEditeur.questions[qi]; if (!q.texte.trim()) { alert('⚠️ La question ' + (qi+1) + ' est vide.'); return; } var opts = q.options.filter(function(o){ return o.trim(); }); if (opts.length < 2) { alert('⚠️ La question ' + (qi+1) + ' doit avoir au moins 2 options.'); return; } } // TODO: POST /api/sondages var nouveau = { id: 's' + Date.now(), titre: _ndageEditeur.questions[0].texte, // titre = 1ère question pour affichage liste classe: classe, multiple: _ndageEditeur.questions[0].multiple, questions: JSON.parse(JSON.stringify(_ndageEditeur.questions)), options: _ndageEditeur.questions[0].options.filter(function(o){ return o.trim(); }), votes: _ndageEditeur.questions[0].options.filter(function(o){ return o.trim(); }).map(function(){ return 0; }), dateCreation: new Date().toISOString().slice(0,10) }; _forumEnsNdages.push(nouveau); _renderListeSondages(); showPage('page-forum-sondages'); } // ══════════════════════════════════════════════════════════════ // SYSTÈME MES MATIÈRES — COMPLET // Structure : Matière → Chapitre → Leçon → Lire / Réviser / Quiz // ══════════════════════════════════════════════════════════════ // ── Données ─────────────────────────────────────────────────── // Source: DB.getMatieres() → TODO: GET /api/matieres?niveau=:niveau&serie=:serie // TODO: Données chargées via API — GET /api/eleve/:id/matieres // Structure attendue : { id, nom, icon, couleur, couleurBg, groupe, enseignant, horaire, // chapitres: [ { num, titre, lecons: [ { id, titre, contenu, flashcards, quiz } ] } ], // devoirs: [ { titre, date, prof, desc, statut } ] } var MATIERES = []; var _MATIERES_FALLBACK = []; // ── État global ──────────────────────────────────────────────── var mmFilter = 'tout'; var mmMatCourante = null; var mmChapCourant = null; var mmLeconCourante = null; var mmLeconsTerminees = DB._data.leconsTerminees; // Source: DB._data.leconsTerminees → TODO: GET /api/eleve/progression var mmQuizBiblio = {}; // { matiereId: [{titre,type,score,date}] } var mmQuizActuel = null; var mmQIndex = 0; var mmQReponse = null; var mmFlashIndex = 0; var mmFlashOk = []; var mmFlashKo = []; var mmTimerInterval = null; var mmTimerSec = 0; var mmAudioPlaying = false; var mmAudioProgress = 35; // ── 1. Ouvrir Mes Matières ──────────────────────────────────── function openMesMatieres() { showPage('page-mes-matieres'); fetchMatieres(); } async function fetchMatieres() { var user = window.currentUser; var sub = document.getElementById('mm-subtitle'); var list = document.getElementById('mm-list'); if (list) list.innerHTML = '
⏳ Chargement des matières…
'; if (sub) sub.textContent = 'Chargement…'; /* Icônes et couleurs par matière */ var _MAT_META = { 'maths': { icon:'📐', couleur:'#3F51B5', couleurBg:'#E8EAF6', groupe:'sciences' }, 'francais': { icon:'📖', couleur:'#E53935', couleurBg:'#FFEBEE', groupe:'lettres' }, 'anglais': { icon:'🌍', couleur:'#0097A7', couleurBg:'#E0F7FA', groupe:'langues' }, 'sciences': { icon:'🔬', couleur:'#43A047', couleurBg:'#E8F5E9', groupe:'sciences' }, 'histoire_geo':{ icon:'🗺️', couleur:'#FB8C00', couleurBg:'#FFF3E0', groupe:'sciences-humaines'}, 'phys_chimie': { icon:'⚗️', couleur:'#8E24AA', couleurBg:'#F3E5F5', groupe:'sciences' }, 'svt': { icon:'🌿', couleur:'#00897B', couleurBg:'#E0F2F1', groupe:'sciences' }, 'informatique':{ icon:'💻', couleur:'#1E88E5', couleurBg:'#E3F2FD', groupe:'sciences' }, 'espagnol': { icon:'🇪🇸', couleur:'#C62828', couleurBg:'#FFEBEE', groupe:'langues' }, 'philo': { icon:'🧠', couleur:'#6D4C41', couleurBg:'#EFEBE9', groupe:'lettres' } }; try { /* ── 0. Vérification stricte : niveau de l'apprenant requis ── */ if (!state.niveau) { MATIERES = []; mmRendreListeMatieres(); updateMmRing(); if (sub) sub.textContent = '0 matière disponible'; return; } /* ── 1. Matières — filtrées STRICTEMENT par matieres.niveaux (array) ── */ var matRes = await db.from('matieres').select('id, nom').contains('niveaux', [state.niveau]).order('nom'); if (matRes.error) throw matRes.error; var matList = matRes.data || []; /* ── 2. Chapitres actifs ── */ var matIds = matList.map(function(m){ return m.id; }); var chapRes = { data: [] }; if (matIds.length > 0) { chapRes = await db.from('chapitres') .select('id, matiere_id, titre, ordre') .in('matiere_id', matIds) .eq('actif', true) .order('ordre'); if (chapRes.error) throw chapRes.error; } var chapList = chapRes.data || []; /* ── 3. Leçons actives ── */ var chapIds = chapList.map(function(c){ return c.id; }); var lecRes = { data: [] }; if (chapIds.length > 0) { lecRes = await db.from('lecons') .select('id, chapitre_id, titre, contenu, fichier_path, audio_path, ordre') .in('chapitre_id', chapIds) .eq('actif', true) .order('ordre'); if (lecRes.error) throw lecRes.error; } var lecList = lecRes.data || []; /* ── 4. Contenus publiés ── */ var lecIds = lecList.map(function(l){ return l.id; }); var contRes = { data: [] }; if (lecIds.length > 0) { contRes = await db.from('contenus') .select('id, lecon_id, fichier_url, fichier_type_mime') .in('lecon_id', lecIds) .eq('statut', 'publie'); if (contRes.error) throw contRes.error; } /* ── 5. Flashcards ── */ var flashRes = { data: [] }; if (lecIds.length > 0) { flashRes = await db.from('flashcards') .select('id, lecon_id, question, reponse, ordre') .in('lecon_id', lecIds) .eq('actif', true) .order('ordre'); } /* ── 6. Questions de quiz ── */ var quizRes = { data: [] }; if (lecIds.length > 0) { quizRes = await db.from('quiz_questions') .select('id, lecon_id, type, question, options, correct, ordre') .in('lecon_id', lecIds) .order('ordre'); } /* ── 7. Assemblage MATIERES ── */ MATIERES = matList.map(function(m) { var meta = _MAT_META[m.id] || { icon:'📚', couleur:'#607D8B', couleurBg:'#ECEFF1', groupe:'autres' }; var chapsMat = chapList.filter(function(c){ return c.matiere_id === m.id; }); var chapitres = chapsMat.map(function(ch, ci) { var leconsCh = lecList.filter(function(l){ return l.chapitre_id === ch.id; }); var lecons = leconsCh.map(function(l) { var conts = contRes.data.filter(function(c){ return c.lecon_id === l.id; }); var docCont = conts.find(function(c){ return c.fichier_url && !(c.fichier_type_mime||'').startsWith('audio'); }); var audCont = conts.find(function(c){ return c.fichier_url && (c.fichier_type_mime||'').startsWith('audio'); }); return { id: l.id, titre: l.titre, contenu: l.contenu || '', fichier_path: l.fichier_path || (docCont ? docCont.fichier_url : null), audio_path: l.audio_path || (audCont ? audCont.fichier_url : null), flashcards: flashRes.data.filter(function(f){ return f.lecon_id === l.id; }), quiz: quizRes.data.filter(function(q){ return q.lecon_id === l.id; }) }; }); return { id: ch.id, num: ch.ordre || (ci+1), titre: ch.titre, lecons: lecons }; }); return { id: m.id, nom: m.nom, icon: meta.icon, couleur: meta.couleur, couleurBg: meta.couleurBg, groupe: meta.groupe, enseignant: '', horaire: '', chapitres: chapitres, devoirs: [] }; }).filter(function(m){ return m.chapitres.length > 0; }); /* ── Strict : widget vide si aucun document n'est chargé dans le bucket reviscool-documents (ou reviscool-audio) pour le niveau de l'apprenant. fichier_path et audio_path sont renseignés uniquement après upload réel. ── */ var _hasDocsBucket = MATIERES.some(function(m){ return m.chapitres.some(function(ch){ return ch.lecons.some(function(l){ return !!(l.fichier_path || l.audio_path); }); }); }); if (!_hasDocsBucket) MATIERES = []; } catch(err) { RC.Err.handle(err, 'Supabase fetchMatieres', { silent: true }); MATIERES = []; } mmRendreListeMatieres(); updateMmRing(); var niv = user && user.niveau ? user.niveau + ' — ' : ''; if (sub) sub.textContent = niv + MATIERES.length + ' matière' + (MATIERES.length > 1 ? 's' : ''); } function filtrerMat(el, groupe) { mmFilter = groupe; document.querySelectorAll('.mm-filter-chip').forEach(function(c){c.classList.remove('active');}); el.classList.add('active'); mmRendreListeMatieres(); } function rechercherMatiere() { mmRendreListeMatieres(); } function mmGetTerminees(mat) { var total = 0, done = 0; mat.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ total++; if (mmLeconsTerminees[l.id]) done++; }); }); return {total:total, done:done, pct: total>0 ? Math.round(done/total*100) : 0}; } function updateMmRing() { var allTotal = 0, allDone = 0; MATIERES.forEach(function(m){ var t=mmGetTerminees(m); allTotal+=t.total; allDone+=t.done; }); var pct = allTotal>0 ? Math.round(allDone/allTotal*100) : 0; var circ = 163; var offset = circ - (pct/100*circ); var fill = document.getElementById('mm-ring-fill'); var pctEl = document.getElementById('mm-ring-pct'); var sub = document.getElementById('mm-prog-sub'); if (fill) fill.setAttribute('stroke-dashoffset', offset); if (pctEl) pctEl.textContent = pct+'%'; if (sub) sub.textContent = allDone+' / '+allTotal+' leçons complétées'; } function mmRendreListeMatieres() { var container = document.getElementById('mm-list'); if (!container) return; var search = (document.getElementById('mm-search-input')||{}).value||''; search = search.toLowerCase(); container.innerHTML = ''; var liste = MATIERES.filter(function(m){ var okGroupe = mmFilter==='tout' || m.groupe===mmFilter; var okSearch = !search || m.nom.toLowerCase().includes(search) || m.enseignant.toLowerCase().includes(search); return okGroupe && okSearch; }); if (liste.length===0) { container.innerHTML='
Aucune matière trouvée.
'; return; } liste.forEach(function(m,i){ var prog = mmGetTerminees(m); var nbChap = m.chapitres.length; var nbLecons = prog.total; var card = document.createElement('div'); card.className = 'mat-card'; card.style.animationDelay = (i*0.06)+'s'; card.style.setProperty('--mat-color', m.couleur); var badgeStyle = 'background:'+m.couleurBg+';color:'+m.couleur+';'; var actionColor = 'background:'+m.couleurBg+';color:'+m.couleur+';'; var actionColorAlt = 'background:'+m.couleur+';color:white;'; card.innerHTML = '
'+ '
'+ '
'+ '
'+m.icon+'
'+ '
'+ '
'+m.nom+'
'+ '
👨‍🏫 '+m.enseignant+' · ⏱ '+m.horaire+'
'+ '
'+ '
'+prog.pct+'%
'+ '
'+ '
'+ '
'+ '
📑 '+nbChap+' chap.
'+ '
📝 '+nbLecons+' leçons
'+ '
'+prog.done+' faites
'+ '
'+ '
'+ '
'+ '
'+ ''+ '
'+ '
'; container.appendChild(card); }); } // ── 2. Page Chapitres ───────────────────────────────────────── function ouvrirChapitres(matId) { var mat = MATIERES.find(function(m){return m.id===matId;}); if (!mat) return; mmMatCourante = mat; mmLeconSelection = []; // reset sélection // Header - orange vif var hdr = document.getElementById('chap-header'); hdr.style.background = 'linear-gradient(135deg, #FF6B00, #FF8C33)'; hdr.style.boxShadow = '0 4px 16px rgba(255,107,0,0.35)'; document.getElementById('chap-header-title').textContent = mat.nom; document.getElementById('chap-header-sub').textContent = mat.enseignant+' · '+mat.horaire; hdr.style.setProperty('--mat-color', mat.couleur); // Barre de progression verte var progFill = document.getElementById('chap-prog-fill'); if (progFill) { progFill.style.setProperty('background', 'linear-gradient(to right, #2E7D32, #43A047, #66BB6A)', 'important'); } // Supprimer l'ancienne barre si présente var oldBarre = document.getElementById('selection-barre'); if (oldBarre) oldBarre.remove(); mmRendreChapitres(); creerBarreSelection(); majBarreSelection(); showPage('page-chapitres'); } function mmRendreChapitres() { var mat = mmMatCourante; if (!mat) return; var prog = mmGetTerminees(mat); // Progress bar var progFillEl = document.getElementById('chap-prog-fill'); if (progFillEl) { progFillEl.style.width = prog.pct+'%'; progFillEl.style.setProperty('background', 'linear-gradient(to right, #2E7D32, #43A047, #66BB6A)', 'important'); } document.getElementById('chap-prog-label').textContent = prog.pct+'%'; // Compteur en haut à droite var counter = document.getElementById('chap-lecon-counter'); if (counter) counter.textContent = prog.done+' / '+prog.total+' leçons'; var body = document.getElementById('chap-body'); body.innerHTML = ''; mat.chapitres.forEach(function(ch, ci){ var lTotal = ch.lecons.length; var lDone = ch.lecons.filter(function(l){return mmLeconsTerminees[l.id];}).length; var lPct = lTotal>0 ? Math.round(lDone/lTotal*100) : 0; var allDone = lDone===lTotal && lTotal>0; var isLocked = false; // tous les chapitres sont accessibles var item = document.createElement('div'); item.className = 'chap-item'+(isLocked?' locked':''); item.id = 'chap-item-'+ci; item.style.animationDelay = (ci*0.08)+'s'; item.style.setProperty('--mat-color', mat.couleur); var lockIcon = isLocked ? '🔒' : (allDone ? '🔓' : '🔓'); var numBg = isLocked ? '#9E9E9E' : (allDone ? '#43A047' : mat.couleur); // Leçons HTML var leconsHTML = ch.lecons.map(function(l){ var done = mmLeconsTerminees[l.id]; var sel = mmLeconSelection.indexOf(l.id) !== -1; var btnStyle = sel ? 'opacity:1;cursor:pointer;pointer-events:auto;' : 'opacity:0.15;cursor:not-allowed;pointer-events:none;'; return '
'+ '
'+ (done?'': sel?''+(mmLeconSelection.indexOf(l.id)+1)+'':'')+ '
'+ '
'+ '
'+l.titre+'
'+ '
'+(done?'Terminée':'Non commencée')+'
'+ '
'+ '
'+ ''+ ''+ '
'+ '
'; }).join(''); item.innerHTML = '
'+ '
'+ch.num+'
'+ '
'+ '
'+ch.titre+'
'+ '
'+lDone+'/'+lTotal+' leçons · '+lPct+'%
'+ '
'+ '
'+lockIcon+'
'+ '
'+ '
'+ '
'+leconsHTML+'
'; body.appendChild(item); }); } function toggleChap(ci) { var item = document.getElementById('chap-item-'+ci); if (!item) return; item.classList.toggle('expanded'); } // ── Sélection de leçons (max 3) ─────────────────────────────── var mmLeconSelection = []; // ids des leçons sélectionnées function selectionnerLecon(leconId) { var idx = mmLeconSelection.indexOf(leconId); if (idx !== -1) { mmLeconSelection.splice(idx, 1); } else { if (mmLeconSelection.length >= 3) { var row = document.getElementById('lecon-row-'+leconId); if (row) { row.style.animation='shake 0.4s'; setTimeout(function(){ if(row) row.style.animation=''; },400); } return; } mmLeconSelection.push(leconId); } // Mise à jour DOM ciblée — aucun re-rendu global, aucun tremblement var mat = mmMatCourante; // Fonction helper : activer/désactiver les boutons d'une leçon function majBoutonsLecon(lid, actif) { var r = document.getElementById('lecon-row-'+lid); if (!r) return; var btnLire = r.querySelector('.lecon-btn-lire'); var btnReviser = r.querySelector('.lecon-btn-reviser'); var onclickLire = actif ? 'event.stopPropagation();lireLeconDirect(\''+lid+'\')' : ''; var onclickRev = actif ? 'event.stopPropagation();afficherSousMenuReviser(\''+lid+'\')' : ''; [btnLire, btnReviser].forEach(function(btn){ if (!btn) return; btn.style.opacity = actif ? '1' : '0.15'; btn.style.cursor = actif ? 'pointer' : 'not-allowed'; btn.style.pointerEvents = actif ? 'auto' : 'none'; btn.title = actif ? '' : 'Sélectionne cette leçon d\'abord'; }); if (btnLire) btnLire.setAttribute('onclick', onclickLire); if (btnReviser) btnReviser.setAttribute('onclick', onclickRev); } // 1. Mettre à jour toutes les leçons sélectionnées (numéros peuvent changer) mmLeconSelection.forEach(function(lid, pos) { var r = document.getElementById('lecon-row-'+lid); if (!r) return; r.classList.add('lecon-selected'); var cb = r.querySelector('.lecon-checkbox'); if (cb) { cb.classList.add('sel-check'); cb.style.background = mat.couleur; cb.style.borderColor = mat.couleur; cb.innerHTML = ''+(pos+1)+''; } majBoutonsLecon(lid, true); }); // 2. Réinitialiser la leçon qui vient d'être désélectionnée if (mmLeconSelection.indexOf(leconId) === -1) { var rowCible = document.getElementById('lecon-row-'+leconId); if (rowCible) { rowCible.classList.remove('lecon-selected'); var cb2 = rowCible.querySelector('.lecon-checkbox'); if (cb2) { var done = mmLeconsTerminees[leconId]; cb2.classList.remove('sel-check'); if (done) { cb2.classList.add('checked'); cb2.style.background = mat.couleur; cb2.style.borderColor = mat.couleur; cb2.innerHTML = ''; } else { cb2.classList.remove('checked'); cb2.style.background = ''; cb2.style.borderColor = ''; cb2.innerHTML = ''; } } } majBoutonsLecon(leconId, false); } majBarreSelection(); } function majBarreSelection() { var barre = document.getElementById('selection-barre'); if (!barre) return; var n = mmLeconSelection.length; var label = document.getElementById('sel-barre-label'); var maxLbl = document.getElementById('sel-barre-max'); if (n < 2) { barre.style.transform = 'translateY(100%)'; barre.style.opacity = '0'; barre.style.pointerEvents = 'none'; document.querySelectorAll('.lecon-actions').forEach(function(el){ el.style.display = ''; }); } else { barre.style.transform = 'translateY(0)'; barre.style.opacity = '1'; barre.style.pointerEvents = 'auto'; if (label) label.textContent = n + ' leçon' + (n > 1 ? 's' : '') + ' sélectionnée' + (n > 1 ? 's' : ''); if (maxLbl) { maxLbl.textContent = n >= 3 ? 'Max. atteint' : 'Max. 3 leçons'; maxLbl.style.color = n >= 3 ? '#E53935' : ''; maxLbl.style.fontWeight = n >= 3 ? '800' : '700'; } document.querySelectorAll('.lecon-actions').forEach(function(el){ el.style.display = 'none'; }); } } function toggleLeconCheck(leconId, matId, chapIdx) { // Kept for backward compat — now delegates to selectionnerLecon selectionnerLecon(leconId); } // ── Barre flottante — Lire / Réviser ───────────────────────── function creerBarreSelection() { if (document.getElementById('selection-barre')) return; var mat = mmMatCourante; var barre = document.createElement('div'); barre.id = 'selection-barre'; barre.style.cssText = 'position:fixed;bottom:0;left:0;right:0;z-index:200;'+ 'background:white;border-top:2px solid #EEE;'+ 'padding:16px 20px;display:flex;align-items:center;gap:12px;'+ 'box-shadow:0 -6px 24px rgba(0,0,0,0.12);'+ 'transform:translateY(100%);opacity:0;'+ 'transition:transform 0.35s cubic-bezier(0.34,1.2,0.64,1),opacity 0.3s;'+ 'max-width:640px;margin:0 auto;border-radius:20px 20px 0 0;'; barre.innerHTML = '
'+ '
'+ '
0 leçon sélectionnée
'+ '
Max. 3 leçons
'+ '
'+ '
'+ ''+ ''+ '
'+ '
'; document.getElementById('page-chapitres').appendChild(barre); } function getLeconsBySelection() { var lecons = []; MATIERES.forEach(function(m){ m.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ if (mmLeconSelection.indexOf(l.id) !== -1) lecons.push(l); }); }); }); // Trier dans l'ordre de sélection lecons.sort(function(a,b){ return mmLeconSelection.indexOf(a.id) - mmLeconSelection.indexOf(b.id); }); return lecons; } // ── Accès direct à une leçon depuis le bouton Lire ─────────── function lireLeconDirect(leconId) { var mat = mmMatCourante; if (!mat) return; var lecon = null, chap = null; mat.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ if (l.id === leconId) { lecon = l; chap = ch; } }); }); if (!lecon) return; mmLeconCourante = lecon; mmChapCourant = chap; mmLectureFile = [lecon]; mmLectureIndex = 0; mmLectureMode = 'visuelle'; afficherChoixLecture(); } function quizLeconDirect(leconId) { var mat = mmMatCourante; if (!mat) return; var lecon = null, chap = null; mat.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ if (l.id === leconId) { lecon = l; chap = ch; } }); }); if (!lecon) return; mmLeconCourante = lecon; mmChapCourant = chap; afficherSelectQuiz(); } // ── Bibliothèque Flashcards ─────────────────────────────────── function ouvrirFlashBiblio() { var mat = mmMatCourante; if (!mat) return; // Récupérer uniquement les leçons sélectionnées var leconsSel = getLeconsBySelection(); // Si aucune sélection, prendre toutes les leçons de la matière if (leconsSel.length === 0) { mat.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ leconsSel.push(l); }); }); } // Header var hdr = document.getElementById('flash-biblio-header'); if (hdr) hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('flash-biblio-title').textContent = '🃏 ' + mat.nom; document.getElementById('flash-biblio-sub').textContent = mmLeconSelection.length > 0 ? mmLeconSelection.length + ' leçon' + (mmLeconSelection.length > 1 ? 's' : '') + ' sélectionnée' + (mmLeconSelection.length > 1 ? 's' : '') : 'Toutes les leçons'; document.getElementById('flash-biblio-back-btn').onclick = function(){ showPage('page-chapitres'); }; // Total cartes var totalCards = 0; leconsSel.forEach(function(l){ totalCards += (l.flashcards||[]).length; }); var countEl = document.getElementById('flash-biblio-count'); if (countEl) countEl.textContent = totalCards + ' carte' + (totalCards > 1 ? 's' : ''); var body = document.getElementById('flash-biblio-body'); body.innerHTML = ''; // Bouton "Tout réviser" if (totalCards > 0) { var allBtn = document.createElement('button'); allBtn.className = 'fb-start-all'; allBtn.style.cssText = 'background:linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'BB);color:white;'; allBtn.innerHTML = '▶ Réviser toutes les cartes (' + totalCards + ')'; allBtn.onclick = function() { lancerFlashBiblioTout(leconsSel); }; body.appendChild(allBtn); } // Afficher les leçons sélectionnées (regroupées par chapitre) mat.chapitres.forEach(function(ch) { // Garder seulement les leçons de ce chapitre qui sont dans la sélection var leconsChap = ch.lecons.filter(function(l){ return leconsSel.indexOf(l) !== -1; }); if (leconsChap.length === 0) return; var chapCards = 0; leconsChap.forEach(function(l){ chapCards += (l.flashcards||[]).length; }); var block = document.createElement('div'); block.className = 'fb-chap-block'; var headerHTML = '
'+ '
'+ch.num+'
'+ '
'+ch.titre+'
'+ '
'+chapCards+' carte'+(chapCards>1?'s':'')+'
'+ '
'; var leconsHTML = leconsChap.map(function(l) { var nb = (l.flashcards||[]).length; return '
'+ '
'+ '
'+l.titre+'
'+ '
🃏 '+nb+' carte'+(nb>1?'s':'')+'
'+ '
'+ (nb > 0 ? '' : 'Aucune carte')+ '
'; }).join(''); block.innerHTML = headerHTML + leconsHTML; body.appendChild(block); }); if (totalCards === 0) { body.innerHTML += '
Aucune flashcard disponible pour les leçons sélectionnées.
'; } showPage('page-flash-biblio'); } function lancerFlashBiblioTout(leconsSel) { var mat = mmMatCourante; if (!leconsSel) { leconsSel = getLeconsBySelection(); if (leconsSel.length === 0) { mat.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ leconsSel.push(l); }); }); } } var allCards = []; leconsSel.forEach(function(l){ (l.flashcards||[]).forEach(function(fc){ allCards.push(fc); }); }); if (allCards.length === 0) return; mmFlashCards = allCards; mmFlashIndex = 0; mmFlashOk = []; mmFlashKo = []; var hdr = document.getElementById('flash-header'); hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('flash-title').textContent = '🃏 Toutes les cartes'; document.getElementById('flash-sub').textContent = mat.nom + ' · ' + allCards.length + ' cartes'; document.getElementById('flash-back-btn').onclick = function(){ ouvrirFlashBiblio(); }; renderFlashcard(); showPage('page-flashcards'); } function ouvrirFlashcardsDirect(leconId) { var mat = mmMatCourante; if (!mat) return; var lecon = null, chap = null; mat.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ if (l.id === leconId) { lecon = l; chap = ch; } }); }); if (!lecon) return; mmLeconCourante = lecon; mmChapCourant = chap; var cards = lecon.flashcards || []; if (cards.length === 0) { alert('Aucune flashcard disponible pour cette leçon.'); return; } mmFlashIndex = 0; mmFlashOk = []; mmFlashKo = []; var hdr = document.getElementById('flash-header'); hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('flash-title').textContent = '🃏 Flash Cards'; document.getElementById('flash-sub').textContent = lecon.titre; document.getElementById('flash-back-btn').onclick = function(){ showPage('page-chapitres'); }; renderFlashcard(); showPage('page-flashcards'); } // ── Sous-menu Réviser (Flash Cards / Quiz) ──────────────────── var _reviserMenuActif = null; var _reviserOverlay = null; function fermerSousMenuReviser() { if (_reviserOverlay) { _reviserOverlay.remove(); _reviserOverlay = null; } if (_reviserMenuActif) { _reviserMenuActif.remove(); _reviserMenuActif = null; } } function _creerSousMenu(ligne1Html, ligne2Html, btn, ouvertureBas) { fermerSousMenuReviser(); var menu = document.createElement('div'); menu.className = 'reviser-submenu'; menu.style.cssText = 'position:fixed;z-index:9999;'; menu.innerHTML = ligne1Html + ligne2Html; document.body.appendChild(menu); _reviserMenuActif = menu; var r = btn.getBoundingClientRect(); var mw = 165, mh = 102; var left = r.right - mw; var top = ouvertureBas ? r.bottom + 6 : r.top - mh - 6; if (top + mh > window.innerHeight) top = r.top - mh - 6; if (top < 6) top = r.bottom + 6; if (left < 8) left = 8; menu.style.left = left + 'px'; menu.style.top = top + 'px'; menu.style.minWidth = mw + 'px'; // Fermer uniquement si clic hors du menu ET hors d'une lecon-row function onDocClick(e) { if (menu.contains(e.target)) return; // clic dans le menu : ignorer if (e.target.closest && e.target.closest('.lecon-row')) return; // selection de lecon : ignorer if (e.target.closest && e.target.closest('.lecon-btn-reviser')) return; // re-clic sur btn : ignorer fermerSousMenuReviser(); document.removeEventListener('click', onDocClick, true); } // Capture:true pour intercepter avant stopPropagation des boutons setTimeout(function(){ document.addEventListener('click', onDocClick, true); }, 50); _reviserOverlay = { remove: function(){ document.removeEventListener('click', onDocClick, true); } }; } function afficherSousMenuReviser(leconId) { var btn = document.getElementById('revbtn-' + leconId); if (!btn) return; var mat = mmMatCourante; var html1 = ''; var html2 = ''; _creerSousMenu(html1, html2, btn, true); } function afficherSousMenuReviserBarre(btn) { var mat = mmMatCourante; _creerSousMenu( '', '', btn, false ); } // ── Sous-menu secondaire Flash Cards ───────────────────────── function afficherSousMenuFlash() { fermerSousMenuFlash(); var mat = mmMatCourante; var overlay = document.createElement('div'); overlay.id = 'flash-submenu-overlay'; overlay.style.cssText = 'position:fixed;inset:0;z-index:9998;background:rgba(0,0,0,0.35);display:flex;align-items:flex-end;'; var panel = document.createElement('div'); panel.style.cssText = 'width:100%;background:white;border-radius:22px 22px 0 0;padding:24px 20px 40px;'+ 'box-shadow:0 -8px 32px rgba(0,0,0,0.18);animation:slideUp 0.28s cubic-bezier(0.34,1.1,0.64,1) both;'; panel.innerHTML = '
'+ '
🃏 Flash Cards
'+ '
Choisissez une option
'+ '
'+ ''+ ''+ '
'; overlay.appendChild(panel); document.body.appendChild(overlay); overlay.addEventListener('click', function(e){ if (e.target === overlay) fermerSousMenuFlash(); }); } function fermerSousMenuFlash() { var el = document.getElementById('flash-submenu-overlay'); if (el) el.remove(); } // ── Créer une Flash Card (multi-questions) ──────────────────── var _flashEnCours = []; // liste temporaire {q, r} function ouvrirCreerFlashCard() { var mat = mmMatCourante; var page = document.getElementById('page-creer-flash'); if (!page) return; var hdr = document.getElementById('creer-flash-header'); if (hdr) hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('creer-flash-back').onclick = function(){ showPage('page-chapitres'); }; // Peupler le select des leçons var sel = document.getElementById('creer-flash-lecon'); sel.innerHTML = ''; mat.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ var opt = document.createElement('option'); opt.value = l.id; opt.textContent = ch.titre + ' › ' + l.titre; sel.appendChild(opt); }); }); // Réinitialiser _flashEnCours = []; document.getElementById('creer-flash-question').value = ''; document.getElementById('creer-flash-reponse').value = ''; document.getElementById('creer-flash-feedback').style.display = 'none'; document.getElementById('creer-flash-save-btn').style.display = 'none'; document.getElementById('creer-flash-liste').innerHTML = ''; _majCompteurFlash(); showPage('page-creer-flash'); } function _majCompteurFlash() { var n = _flashEnCours.length; var c = document.getElementById('creer-flash-compteur'); if (c) c.textContent = n + ' question' + (n > 1 ? 's' : '') + ' ajoutée' + (n > 1 ? 's' : ''); var lbl = document.getElementById('creer-flash-num-label'); if (lbl) lbl.textContent = '❓ Question ' + (n + 1); var btn = document.getElementById('creer-flash-save-btn'); if (btn) btn.style.display = n > 0 ? 'block' : 'none'; } function ajouterQuestion() { var q = document.getElementById('creer-flash-question').value.trim(); var r = document.getElementById('creer-flash-reponse').value.trim(); var fb = document.getElementById('creer-flash-feedback'); if (!q || !r) { fb.textContent = '⚠️ Remplis les deux champs avant d\'ajouter.'; fb.style.cssText = 'display:block;padding:12px 16px;border-radius:12px;background:#FFEBEE;color:#C62828;font-weight:700;font-size:13px;'; setTimeout(function(){ fb.style.display = 'none'; }, 2000); return; } _flashEnCours.push({q: q, r: r}); // Afficher la carte ajoutée dans la liste var liste = document.getElementById('creer-flash-liste'); var idx = _flashEnCours.length; var mat = mmMatCourante; var item = document.createElement('div'); item.style.cssText = 'background:white;border-radius:14px;padding:14px 16px;box-shadow:0 2px 8px rgba(0,0,0,0.06);border-left:4px solid '+(mat?mat.couleur:'var(--orange)')+';display:flex;align-items:flex-start;gap:12px;animation:fadeUp 0.2s both;'; item.innerHTML = '
Q'+idx+'
'+ '
'+ '
'+q+'
'+ '
→ '+r+'
'+ '
'+ ''; liste.appendChild(item); // Vider les champs document.getElementById('creer-flash-question').value = ''; document.getElementById('creer-flash-reponse').value = ''; document.getElementById('creer-flash-question').focus(); _majCompteurFlash(); } function supprimerQuestion(idx) { _flashEnCours.splice(idx, 1); // Reconstruire la liste visuelle var liste = document.getElementById('creer-flash-liste'); liste.innerHTML = ''; var mat = mmMatCourante; _flashEnCours.forEach(function(fc, i){ var item = document.createElement('div'); item.style.cssText = 'background:white;border-radius:14px;padding:14px 16px;box-shadow:0 2px 8px rgba(0,0,0,0.06);border-left:4px solid '+(mat?mat.couleur:'var(--orange)')+';display:flex;align-items:flex-start;gap:12px;'; item.innerHTML = '
Q'+(i+1)+'
'+ '
'+ '
'+fc.q+'
'+ '
→ '+fc.r+'
'+ '
'+ ''; liste.appendChild(item); }); _majCompteurFlash(); } function sauvegarderFlashCard() { if (_flashEnCours.length === 0) return; var leconId = document.getElementById('creer-flash-lecon').value; var fb = document.getElementById('creer-flash-feedback'); var lecon = null; MATIERES.forEach(function(m){ m.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ if(l.id===leconId) lecon=l; }); }); }); if (lecon) { if (!lecon.flashcards) lecon.flashcards = []; _flashEnCours.forEach(function(fc){ lecon.flashcards.push(fc); }); } var n = _flashEnCours.length; fb.textContent = '✅ ' + n + ' Flash Card' + (n > 1 ? 's' : '') + ' enregistrée' + (n > 1 ? 's' : '') + ' !'; fb.style.cssText = 'display:block;padding:12px 16px;border-radius:12px;background:#E8F5E9;color:#2E7D32;font-weight:700;font-size:13px;'; _flashEnCours = []; document.getElementById('creer-flash-liste').innerHTML = ''; document.getElementById('creer-flash-question').value = ''; document.getElementById('creer-flash-reponse').value = ''; document.getElementById('creer-flash-save-btn').style.display = 'none'; _majCompteurFlash(); setTimeout(function(){ fb.style.display = 'none'; }, 2500); } function lancerQuizSelection() { var lecons = getLeconsBySelection(); if (lecons.length === 0) return; // Utiliser la première leçon sélectionnée comme contexte quiz mmLeconCourante = lecons[0]; mat = mmMatCourante; // Fusionner les quiz de toutes les leçons sélectionnées var allQuiz = []; lecons.forEach(function(l){ (l.quiz||[]).forEach(function(q){ allQuiz.push(q); }); }); mmLeconCourante = { titre: lecons.length+' leçon(s)', quiz: allQuiz }; afficherSelectQuiz(); } // ── Lire les leçons sélectionnées ──────────────────────────── var mmLectureFile = []; // liste de leçons à lire var mmLectureIndex = 0; var mmLectureMode = 'visuelle'; function lancerLectureSelection() { mmLectureFile = getLeconsBySelection(); if (mmLectureFile.length === 0) return; mmLectureIndex = 0; // Montrer le choix visuelle/audio afficherChoixLecture(); } function afficherChoixLecture() { var mat = mmMatCourante; var n = mmLectureFile.length; // Construire une page de choix modale dans page-lecture var hdr = document.getElementById('lecture-header'); hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('lecture-title').textContent = '📖 Mode de lecture'; document.getElementById('lecture-sub').textContent = n+' leçon'+(n>1?'s':'')+' sélectionnée'+(n>1?'s':''); var backBtn = document.getElementById('lecture-back-btn'); backBtn.onclick = function(){ showPage('page-chapitres'); }; // Masquer la barre de mode (on la remplace par notre choix) document.querySelector('.lecture-mode-bar').style.display = 'none'; var body = document.getElementById('lecture-body'); var titres = mmLectureFile.map(function(l,i){ return '
'+(i+1)+''+l.titre+'
'; }).join(''); body.innerHTML = '
'+ // Résumé leçons '
'+ '
📋 Leçons à lire
'+ '
'+titres+'
'+ '
'+ // Choix du mode '
'+ '
🎯 Choisir le mode
'+ '
'+ '
'+ '
👁
'+ '
'+ '
Lecture visuelle
'+ '
Lire le contenu structuré des leçons
'+ '
'+ '
'+ '
'+ '
'+ '
🎧
'+ '
'+ '
Lecture audio
'+ '
Écouter les leçons en mode audio
'+ '
'+ '
'+ '
'+ '
'+ '
'+ '
'; showPage('page-lecture'); } function demarrerLecture(mode) { mmLectureMode = mode; mmLectureIndex = 0; afficherLeconEnCours(); } function afficherLeconEnCours() { var l = mmLectureFile[mmLectureIndex]; if (!l) return; // Trouver la matière de cette leçon var mat = mmMatCourante; MATIERES.forEach(function(m){ m.chapitres.forEach(function(ch){ ch.lecons.forEach(function(lc){ if(lc.id===l.id) mat=m; }); }); }); mmMatCourante = mat; mmLeconCourante = l; // Restaurer la barre de mode (masquée lors du choix) document.querySelector('.lecture-mode-bar').style.display = 'none'; var hdr = document.getElementById('lecture-header'); hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('lecture-title').textContent = l.titre; var total = mmLectureFile.length; document.getElementById('lecture-sub').textContent = mat.icon+' '+mat.nom+' · '+(mmLectureIndex+1)+'/'+total; var backBtn = document.getElementById('lecture-back-btn'); backBtn.onclick = function(){ if (mmLectureIndex > 0) { mmLectureIndex--; afficherLeconEnCours(); } else { // Retour au choix afficherChoixLecture(); } }; mmAudioProgress = 0; mmAudioPlaying = false; clearInterval(mmTimerInterval); if (mmLectureMode === 'visuelle') { renderLectureVisuelleMulti(); } else { renderLectureAudioMulti(); } showPage('page-lecture'); } function marquerLeconEffectuee() { if (!mmLeconCourante) return; var lid = mmLeconCourante.id; var matId = mmMatCourante ? mmMatCourante.id : ''; // Déjà effectuée — ne rien faire if (DB.isLeconEffectuee(lid)) return; // Enregistrer via DB (TODO: POST /api/eleve/progression/:leconId) DB.marquerLeconEffectuee(lid, matId); // Mettre à jour la progression updateMmRing(); // Mettre à jour le bouton → classe 'fait' (marron CSS) + désactivé var btn = document.getElementById('btn-effectuee'); if (btn) { btn.className = 'lec-btn-effectuee fait'; btn.textContent = '✔ Effectuée'; btn.onclick = null; btn.style.marginTop = btn.style.marginTop; // preserve margin } // Mettre à jour la case à cocher dans la liste des chapitres var row = document.getElementById('lecon-row-' + lid); if (row && mmMatCourante) { var cb = row.querySelector('.lecon-checkbox'); if (cb) { cb.classList.add('checked'); cb.style.background = mmMatCourante.couleur; cb.style.borderColor = mmMatCourante.couleur; cb.innerHTML = ''; } } } function majNavBarLecture() { var bar = document.getElementById('lecture-nav-bar'); var prev = document.getElementById('lec-nav-prev'); var next = document.getElementById('lec-nav-next'); if (!bar) return; var total = (mmLectureFile || []).length; if (total <= 1) { bar.style.display = 'none'; return; } bar.style.display = 'block'; if (prev) { prev.style.display = mmLectureIndex > 0 ? 'block' : 'none'; prev.style.visibility = mmLectureIndex > 0 ? 'visible' : 'hidden'; prev.style.flex = mmLectureIndex > 0 ? '1' : '0'; prev.style.padding = mmLectureIndex > 0 ? '' : '0'; prev.style.border = mmLectureIndex > 0 ? '' : 'none'; prev.style.minWidth = mmLectureIndex > 0 ? '' : '0'; } if (next) { var isLast = mmLectureIndex >= total - 1; next.style.display = 'block'; next.textContent = isLast ? 'Fin ✓' : 'Suivante →'; next.style.opacity = isLast ? '0.4' : '1'; next.style.cursor = isLast ? 'default' : 'pointer'; next.style.pointerEvents = isLast ? 'none' : 'auto'; } // Adjust lecture-body padding to avoid overlap with fixed bar var body = document.getElementById('lecture-body'); if (body) body.style.paddingBottom = '90px'; } function renderLectureVisuelleMulti() { var l = mmLeconCourante; var mat = mmMatCourante; var done = mmLeconsTerminees[l.id]; var total = mmLectureFile.length; var pills = mmLectureFile.map(function(lx, i){ var active = i === mmLectureIndex; return '
'; }).join(''); var body = document.getElementById('lecture-body'); var contenu = (l.contenu||'').replace(/class="definition-box"/g,'class="lecture-def"').replace(/class="example-box"/g,'class="lecture-example"'); /* ── Bouton PDF si disponible ── */ var pdfSection = ''; if (l.fichier_path) { pdfSection = '
'+ '
📄
'+ '
Document disponible
'+ ''+ '
'; } body.innerHTML = '
'+pills+'
'+ pdfSection+ '
'+ '

'+l.titre+'

'+ (contenu ? '
'+contenu+'
' : '
Aucun contenu textuel pour cette leçon.
' )+ ''+ '
'; majNavBarLecture(); } /* Ouvre le PDF de la leçon courante via URL signée Supabase */ async function mmOuvrirPDF() { var l = mmLeconCourante; if (!l || !l.fichier_path) return; try { var url = await RC.Storage.getUrlSignee('reviscool-documents', l.fichier_path); window.open(url, '_blank'); } catch(e) { alert('Impossible d\'ouvrir le document : ' + e.message); } } function renderLectureAudioMulti() { var l = mmLeconCourante; var mat = mmMatCourante; var total = mmLectureFile.length; var pills = mmLectureFile.map(function(lx, i){ var active = i === mmLectureIndex; return '
'; }).join(''); var body = document.getElementById('lecture-body'); var btnEffectuee = ''; if (l.audio_path) { /* ── Vrai lecteur audio HTML5 ── */ body.innerHTML = '
'+pills+'
'+ '
'+ '
'+l.titre+'
'+ '
'+mat.icon+' '+mat.nom+' · Leçon '+(mmLectureIndex+1)+'/'+total+'
'+ '
'+ '
'+ '
Chargement de l\'audio…
'+ '
'+ btnEffectuee+ '
'; majNavBarLecture(); /* Génère l'URL signée de façon asynchrone */ RC.Storage.getUrlSignee('reviscool-audio', l.audio_path) .then(function(url) { var c = document.getElementById('lecture-audio-container'); if (c) c.innerHTML = ''; }) .catch(function(e) { var c = document.getElementById('lecture-audio-container'); if (c) c.innerHTML = '
Impossible de charger l\'audio.
'; }); } else { /* ── Pas d'audio — message informatif ── */ body.innerHTML = '
'+pills+'
'+ '
'+ '
'+l.titre+'
'+ '
'+mat.icon+' '+mat.nom+'
'+ '
'+ '
🔇
'+ '
Aucun fichier audio disponible pour cette leçon.
'+ '
'+ btnEffectuee+ '
'; majNavBarLecture(); } } function marquerEtSuivre() { // Marquer la leçon courante comme terminée if (mmLeconCourante) mmLeconsTerminees[mmLeconCourante.id] = true; clearInterval(mmTimerInterval); mmAudioPlaying = false; mmAudioProgress = 0; if (mmLectureIndex < mmLectureFile.length - 1) { mmLectureIndex++; afficherLeconEnCours(); } else { // Toutes les leçons terminées updateMmRing(); mmLeconSelection = []; var mat = mmMatCourante; var body = document.getElementById('lecture-body'); document.querySelector('.lecture-mode-bar').style.display = 'none'; body.innerHTML = '
'+ '
🎉
'+ '
Bravo !
'+ '
Tu as terminé toutes les leçons sélectionnées.
'+ ''+ ''+ '
'; document.getElementById('lecture-title').textContent = '🎉 Lecture terminée'; document.getElementById('lecture-sub').textContent = mmLectureFile.length+' leçon(s) complétées'; } } // ── Réviser les leçons sélectionnées ───────────────────────── function lancerReviserSelection() { var lecons = getLeconsBySelection(); if (lecons.length === 0 && mmLectureFile.length > 0) lecons = mmLectureFile; if (lecons.length === 0) return; // Regrouper toutes les flashcards var allCards = []; lecons.forEach(function(l){ (l.flashcards||[]).forEach(function(fc){ allCards.push(fc); }); }); if (allCards.length === 0) { alert('Aucune flashcard disponible pour les leçons sélectionnées.'); return; } mmLeconCourante = { titre: lecons.length+' leçon(s)', flashcards: allCards }; mmFlashIndex = 0; mmFlashOk = []; mmFlashKo = []; var mat = mmMatCourante; var hdr = document.getElementById('flash-header'); hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('flash-title').textContent = '🃏 Révision'; document.getElementById('flash-sub').textContent = lecons.length+' leçon'+(lecons.length>1?'s':'')+' — '+allCards.length+' cartes'; document.getElementById('flash-back-btn').onclick = function(){ mmLeconSelection=[]; mmRendreChapitres(); majBarreSelection(); showPage('page-chapitres'); }; renderFlashcard(); showPage('page-flashcards'); } function toggleAudio() { mmAudioPlaying = !mmAudioPlaying; var btn = document.getElementById('audio-play-btn'); var wave = document.getElementById('audio-wave'); var svgPlay = ''; var svgPause = ''; if (btn) btn.innerHTML = mmAudioPlaying ? svgPause : svgPlay; if (wave) wave.style.opacity = mmAudioPlaying ? '1' : '0.4'; if (mmAudioPlaying) { mmTimerInterval = setInterval(function(){ mmAudioProgress = Math.min(100, mmAudioProgress+0.5); var p = document.getElementById('audio-prog'); if (p) p.style.width = mmAudioProgress+'%'; var s = Math.round(mmAudioProgress/100*208); var tc = document.getElementById('audio-t-cur'); if (tc) tc.textContent = Math.floor(s/60)+':'+(s%60<10?'0':'')+s%60; if (mmAudioProgress>=100) { mmAudioPlaying=false; clearInterval(mmTimerInterval); } }, 200); } else { clearInterval(mmTimerInterval); } } function seekAudio(sec) { mmAudioProgress = Math.max(0, Math.min(100, mmAudioProgress + sec/208*100)); renderLectureAudio(); } function scrubAudio(e) { var r = e.currentTarget.getBoundingClientRect(); mmAudioProgress = (e.clientX-r.left)/r.width*100; renderLectureAudio(); } function marquerLeconTerminee() { if (!mmLeconCourante) return; mmLeconsTerminees[mmLeconCourante.id] = true; var btn = document.getElementById('lecture-done-btn'); if (btn) { btn.textContent = '✅ Leçon terminée !'; btn.classList.add('done'); } updateMmRing(); } // ── 4. Flashcards ───────────────────────────────────────────── function ouvrirFlashcards(matId, chapIdx, leconIdx) { var mat = MATIERES.find(function(m){return m.id===matId;}); if (!mat) return; mmMatCourante = mat; mmChapCourant = mat.chapitres[chapIdx]; mmLeconCourante = mmChapCourant.lecons[leconIdx]; mmFlashIndex = 0; mmFlashOk = []; mmFlashKo = []; var hdr = document.getElementById('flash-header'); hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('flash-title').textContent = '🃏 Flashcards'; document.getElementById('flash-sub').textContent = mmLeconCourante.titre; document.getElementById('flash-back-btn').onclick = function(){ showPage('page-chapitres'); }; renderFlashcard(); showPage('page-flashcards'); } function renderFlashcard() { var cards = mmLeconCourante.flashcards || []; var body = document.getElementById('flash-body'); body.innerHTML = ''; if (cards.length===0) { body.innerHTML='
Aucune flashcard disponible pour cette leçon.
'; return; } if (mmFlashIndex >= cards.length) { // Résultats var score = mmFlashOk.length; var total = cards.length; var pct = Math.round(score / total * 100); // Passer le header en orange sur la page de résultats var hdr = document.getElementById('flash-header'); if (hdr) hdr.style.background = 'linear-gradient(135deg,var(--orange),var(--orange-dark))'; var titleEl = document.getElementById('flash-title'); var subEl = document.getElementById('flash-sub'); var backBtn = document.getElementById('flash-back-btn'); if (titleEl) { titleEl.textContent = '🃏 Flash Cards'; titleEl.style.color = 'white'; } if (subEl) { subEl.style.color = 'rgba(255,255,255,0.9)'; } if (backBtn) { backBtn.style.background = 'rgba(255,255,255,0.2)'; backBtn.style.border = '2px solid rgba(255,255,255,0.4)'; backBtn.style.color = 'white'; } var msg = pct >= 80 ? 'Excellent !' : pct >= 50 ? 'Bien joué !' : 'Continue les efforts !'; var emoji = pct >= 80 ? '🏆' : pct >= 50 ? '👍' : '💪'; var col = pct >= 80 ? '#2E7D32' : pct >= 50 ? '#E65100' : '#B71C1C'; var colB = pct >= 80 ? '#43A047' : pct >= 50 ? '#F57C00' : '#E53935'; var stars = ''; var starCount = pct >= 80 ? 3 : pct >= 50 ? 2 : 1; for (var s = 0; s < 3; s++) { stars += ''; } var mat = mmMatCourante; body.innerHTML = '
'+ /* ── Bandeau résultat ── */ '
'+ '
'+emoji+'
'+ '
'+msg+'
'+ '
'+stars+'
'+ '
'+ /* ── Score central ── */ '
'+ '
'+ '
Correctes
'+ '
'+score+'
'+ '
cartes
'+ '
'+ '
'+ '
'+ '
Score
'+ '
'+pct+'%
'+ '
réussite
'+ '
'+ '
'+ '
'+ '
À revoir
'+ '
'+(total-score)+'
'+ '
cartes
'+ '
'+ '
'+ /* ── Barre progression ── */ '
'+ '
'+ 'Progression'+ ''+score+' / '+total+''+ '
'+ '
'+ '
'+ '
'+ '
'+ /* ── Boutons d'action ── */ '
'+ ''+ ''+ ''+ '
'+ '
'; return; } var card = cards[mmFlashIndex]; var total = cards.length; var pct = Math.round(mmFlashIndex/total*100); body.innerHTML = '
Carte '+(mmFlashIndex+1)+' / '+total+'
'+ '
'+ '
'+ '
'+ '
'+ '
RECTO
'+ '
Question
'+ '
'+card.q+'
'+ '
👆 Toucher pour voir la réponse
'+ '
'+ '
'+ '
VERSO
'+ '
Réponse
'+ '
'+card.r+'
'+ '
'+ '
'+ '
'+ '
Toucher la carte pour voir le verso
'+ '
'+ ''+ ''+ '
'; } function flipFlashcard() { var c = document.getElementById('flash-card'); if (c) c.classList.toggle('flipped'); } function flashVote(ok) { if (ok) mmFlashOk.push(mmFlashIndex); else mmFlashKo.push(mmFlashIndex); mmFlashIndex++; renderFlashcard(); } // ── 5. Quiz ─────────────────────────────────────────────────── function ouvrirQuizMatiere(matId) { var mat = MATIERES.find(function(m){return m.id===matId;}); if (!mat) return; mmMatCourante = mat; // Collecter toutes les questions de la matière var allQ = []; mat.chapitres.forEach(function(ch){ ch.lecons.forEach(function(l){ (l.quiz||[]).forEach(function(q){ allQ.push(q); }); }); }); mmLeconCourante = {titre: mat.nom+' — Quiz général', quiz: allQ}; mmChapCourant = {lecons:[]}; afficherSelectQuiz(); } function ouvrirQuizLecon(matId, chapIdx, leconIdx) { var mat = MATIERES.find(function(m){return m.id===matId;}); if (!mat) return; mmMatCourante = mat; mmChapCourant = mat.chapitres[chapIdx]; mmLeconCourante = mmChapCourant.lecons[leconIdx]; afficherSelectQuiz(); } function afficherSelectQuiz() { var mat = mmMatCourante; var hdr = document.getElementById('quiz-header'); hdr.style.background = 'linear-gradient(135deg,'+mat.couleur+','+mat.couleur+'CC)'; document.getElementById('quiz-header-title').textContent = '🎯 Quiz'; document.getElementById('quiz-header-sub').textContent = mmLeconCourante.titre; document.getElementById('quiz-timer').style.display = 'none'; document.getElementById('quiz-back-btn').onclick = function(){ showPage('page-chapitres'); }; var questions = mmLeconCourante.quiz || []; var biblio = mmQuizBiblio[mat.id] || []; var types = [ {id:'QCM', icon:'🔘', nom:'QCM', desc:'Une seule bonne réponse parmi 4', bg:'var(--orange-pale)', col:'var(--orange-dark)'}, {id:'VF', icon:'✔️', nom:'Vrai / Faux', desc:'Évaluer si l\'affirmation est correcte', bg:'#E8F5E9', col:'#2E7D32'}, {id:'MULTI', icon:'☑️', nom:'Réponses multiples', desc:'Plusieurs bonnes réponses possibles', bg:'#FFF3E0', col:'#E65100'}, {id:'COMP', icon:'✍️', nom:'Complétion', desc:'Compléter la phrase avec le bon mot', bg:'#F3E5F5', col:'#6A1B9A'}, {id:'OUVERTE', icon:'📝', nom:'Question ouverte', desc:'Rédiger une réponse (littérature & philo)', bg:'#FCE4EC', col:'#880E4F'}, ]; var typesHTML = types.map(function(t, i){ var nb = questions.filter(function(q){return q.type===t.id;}).length; return '
'+ '
'+t.icon+'
'+ '
'+ '
'+t.nom+'
'+ '
'+t.desc+'
'+ '
'+ '
'+nb+' q.
'+ '
'; }).join(''); var biblioHTML = ''; if (biblio.length>0) { var items = biblio.map(function(q, i){ return '
'+ '
'+(i+1)+'
'+ '
'+ '
'+q.titre+'
'+ '
'+q.type+' · '+q.score+' · '+q.date+'
'+ '
'+ '
'+ ''+ ''+ '
'+ '
'; }).join(''); biblioHTML = '
'+ '
📚 Bibliothèque — Mes quiz précédents
'+ items+ '
'; } document.getElementById('quiz-content').innerHTML = '
'+ '
🎯 Choisir le type de quiz
'+ typesHTML+ biblioHTML+ '
'; showPage('page-quiz'); } function lancerQuiz(typeId) { var questions = (mmLeconCourante.quiz||[]).filter(function(q){ return q.type===typeId; }); if (questions.length===0) { // Générer des questions mock si pas de questions de ce type questions = genererQMock(typeId); } mmQuizActuel = { type:typeId, questions:questions, reponses:[], index:0, debut: Date.now() }; mmQIndex = 0; mmQReponse = null; demarrerTimerQuiz(); afficherQuestionQuiz(); } function genererQMock(typeId) { var mat = mmMatCourante; var l = mmLeconCourante; if (typeId==='VF') return [{type:'VF',q:'Cette leçon porte sur : '+l.titre,correct:true}]; if (typeId==='COMP') return [{type:'COMP',q:'La matière étudiée est ___.',correct:mat.nom}]; if (typeId==='OUVERTE') return [{type:'OUVERTE',q:'Expliquez en quelques phrases ce que vous avez retenu de : '+l.titre}]; if (typeId==='MULTI') return [{type:'MULTI',q:'Lesquels sont des types de quiz ?',opts:['QCM','Flashcard','Vrai/Faux','Vidéo'],corrects:[0,2]}]; return [{type:'QCM',q:'Cette leçon fait partie de la matière…',opts:[mat.nom,'Autre','Inconnu','Aucune'],correct:0}]; } function demarrerTimerQuiz() { clearInterval(mmTimerInterval); mmTimerSec = 0; document.getElementById('quiz-timer').style.display = 'block'; mmTimerInterval = setInterval(function(){ mmTimerSec++; var m = Math.floor(mmTimerSec/60), s = mmTimerSec%60; var el = document.getElementById('quiz-timer'); if (el) el.textContent = '⏱ '+m+':'+(s<10?'0':'')+s; }, 1000); } function afficherQuestionQuiz() { var qz = mmQuizActuel; if (!qz || mmQIndex >= qz.questions.length) { afficherResultatQuiz(); return; } var q = qz.questions[mmQIndex]; var total = qz.questions.length; var labels = {QCM:'QCM', VF:'Vrai / Faux', MULTI:'Réponses multiples', COMP:'Complétion', OUVERTE:'Question ouverte'}; var optLetters = ['A','B','C','D','E']; var optsHTML = ''; if (q.type==='QCM') { optsHTML = q.opts.map(function(o,i){ return '
'+ '
'+optLetters[i]+'
'+o+'
'; }).join(''); } else if (q.type==='VF') { optsHTML = '
V
Vrai
'+ '
F
Faux
'; } else if (q.type==='MULTI') { optsHTML = (q.opts||[]).map(function(o,i){ return '
'+ '
'+optLetters[i]+'
'+o+'
'; }).join(''); } else if (q.type==='COMP') { optsHTML = ''; } else if (q.type==='OUVERTE') { optsHTML = ''; } document.getElementById('quiz-content').innerHTML = '
'+ '
'+ 'Question '+(mmQIndex+1)+' / '+total+''+ ''+labels[q.type]+''+ '
'+ '
'+ '
'+ '
'+labels[q.type]+'
'+ '
'+q.q+'
'+ '
'+ '
'+optsHTML+'
'+ '
'+ (mmQIndex>0?'':'')+ ''+ '
'+ '
'; mmQReponse = null; } var mmMultiSelected = []; function selectionnerOpt(idx) { mmQReponse = idx; document.querySelectorAll('.quiz-option').forEach(function(el,i){ el.classList.toggle('selected', i===idx); }); } function selectionnerOptMulti(idx) { if (!mmMultiSelected) mmMultiSelected = []; var pos = mmMultiSelected.indexOf(idx); if (pos===-1) mmMultiSelected.push(idx); else mmMultiSelected.splice(pos,1); document.querySelectorAll('.quiz-option').forEach(function(el,i){ el.classList.toggle('selected', mmMultiSelected.indexOf(i)!==-1); }); } function validerQuestion() { var qz = mmQuizActuel; var q = qz.questions[mmQIndex]; var correct = false; if (q.type==='COMP') { var inp = document.getElementById('comp-input'); mmQReponse = inp ? inp.value.trim() : ''; correct = mmQReponse.toLowerCase() === (q.correct+'').toLowerCase(); } else if (q.type==='OUVERTE') { var ta = document.getElementById('ouverte-input'); mmQReponse = ta ? ta.value.trim() : ''; correct = mmQReponse.length > 10; // accepté si >10 chars } else if (q.type==='VF') { correct = (mmQReponse===0) === q.correct; } else if (q.type==='MULTI') { var corrects = q.corrects || []; correct = JSON.stringify((mmMultiSelected||[]).sort()) === JSON.stringify(corrects.sort()); } else { correct = mmQReponse === q.correct; } // Feedback visuel sur les options if (['QCM','VF','MULTI'].indexOf(q.type)!==-1 && !(q.type==='OUVERTE')) { document.querySelectorAll('.quiz-option').forEach(function(el,i){ if (q.type==='QCM'||q.type==='VF') { if (i===q.correct) el.classList.add('correct'); else if (i===mmQReponse && !correct) el.classList.add('wrong'); } }); } qz.reponses.push({correct:correct, reponse:mmQReponse}); mmMultiSelected = []; // Attendre 0.8s puis passer à la suivante setTimeout(function(){ mmQIndex++; afficherQuestionQuiz(); }, 900); } function precedentQuestion() { if (mmQIndex>0) { mmQIndex--; mmQuizActuel.reponses.pop(); afficherQuestionQuiz(); } } function afficherResultatQuiz() { clearInterval(mmTimerInterval); document.getElementById('quiz-timer').style.display = 'none'; var qz = mmQuizActuel; var score = qz.reponses.filter(function(r){return r.correct;}).length; var total = qz.questions.length; var pct = total>0 ? Math.round(score/total*100) : 0; var msg = pct>=80 ? '🎉 Excellent !' : (pct>=60 ? '👍 Bien joué !' : (pct>=40 ? '📚 Continue !' : '💪 Persévère !')); var col = pct>=80 ? '#43A047' : (pct>=60 ? '#F57C00' : (pct>=40 ? 'var(--orange)' : '#C62828')); var m = Math.floor(mmTimerSec/60), s = mmTimerSec%60; var temps = m+'\''+( s<10?'0':'')+s+'"'; // Sauvegarder dans bibliothèque if (!mmQuizBiblio[mmMatCourante.id]) mmQuizBiblio[mmMatCourante.id] = []; var today = new Date(); mmQuizBiblio[mmMatCourante.id].push({ titre: qz.type+' — '+mmLeconCourante.titre.substring(0,30), type: qz.type, score: score+'/'+total, date: today.getDate()+'/'+(today.getMonth()+1) }); document.getElementById('quiz-content').innerHTML = '
'+ '
'+ '
'+score+'/'+total+'
'+ '
'+pct+'%
'+ '
'+ '
'+msg+'
'+ '
Temps : ⏱ '+temps+' · '+qz.type+'
'+ '
'+ ''+ ''+ ''+ '
'+ '
'; } function relancerQuiz(i) { var q = (mmQuizBiblio[mmMatCourante.id]||[])[i]; if (q) lancerQuiz(q.type); } function supprimerQuiz(i) { if (!mmQuizBiblio[mmMatCourante.id]) return; mmQuizBiblio[mmMatCourante.id].splice(i,1); afficherSelectQuiz(); } // openMesMatieres définie précédemment — doublon supprimé // Alias pour les anciens appels var matieresCatalogue = MATIERES; var chapitresTermines = mmLeconsTerminees; function filterMatieres() {} function ouvrirMatiereDetail() { openMesMatieres(); } function switchMatiereTab() {} function renderMatiereDetailContent() {} function ouvrirChapitre() {} function fermerChapitreModal() {} function marquerChapitreTermine() {} var matiereCourante = null; var matiereTabActif = 'cours'; var chapitreModalActuel = null; var matiereFilterActif = 'tout'; // ── FORUM : ENVOI AVEC PIÈCE JOINTE ───────────────────────────────────────── function openCalendrier() { showPage('page-calendrier'); // Refermer le sous-menu à chaque ouverture var sm = document.getElementById('cal-edt-sousmenu'); var arrow = document.getElementById('cal-edt-arrow'); if (sm) sm.style.display = 'none'; if (arrow) { arrow.textContent = '›'; arrow.style.transform = 'rotate(0deg)'; } } async function ouvrirCalendrierScolaire() { var eleveId = (window.currentUser && window.currentUser.id) ? window.currentUser.id : 'moi'; var niveau = (state && state.niveau) ? state.niveau : ''; try { // TODO: GET /api/eleve/:id/calendrier-scolaire?niveau=:niveau // data attendu : { url: 'https://...' } OU { contenu: '...' } var res = await fetch('/api/routes/eleve/' + eleveId + '/calendrier-scolaire?niveau=' + encodeURIComponent(niveau), { method: 'GET', headers: { 'Content-Type': 'application/json' } // TODO: 'Authorization': 'Bearer ' + window.authToken }); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); if (data.url) { window.open(data.url, '_blank', 'noopener'); } else if (data.contenu) { afficherModalInfo('🗓️ Calendrier scolaire', data.contenu); } else { afficherModalInfo('🗓️ Calendrier scolaire', 'Le calendrier scolaire sera disponible très prochainement.'); } } catch (err) { RC.Err.handle(err, 'GET /api/eleve/calendrier-scolaire', { silent: false }); afficherModalInfo('🗓️ Calendrier scolaire', 'Le calendrier scolaire sera disponible très prochainement.'); } } async function ouvrirProgrammeScolaire() { var eleveId = (window.currentUser && window.currentUser.id) ? window.currentUser.id : 'moi'; var niveau = (state && state.niveau) ? state.niveau : ''; try { // TODO: GET /api/eleve/:id/programme-scolaire?niveau=:niveau // data attendu : { url: 'https://...' } OU { contenu: '...' } var res = await fetch('/api/routes/eleve/' + eleveId + '/programme-scolaire?niveau=' + encodeURIComponent(niveau), { method: 'GET', headers: { 'Content-Type': 'application/json' } // TODO: 'Authorization': 'Bearer ' + window.authToken }); if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); if (data.url) { window.open(data.url, '_blank', 'noopener'); } else if (data.contenu) { afficherModalInfo('📋 Mon programme scolaire', data.contenu); } else { afficherModalInfo('📋 Mon programme scolaire', 'Le programme scolaire sera disponible très prochainement.'); } } catch (err) { RC.Err.handle(err, 'GET /api/eleve/programme-scolaire', { silent: false }); afficherModalInfo('📋 Mon programme scolaire', 'Le programme scolaire sera disponible très prochainement.'); } } function toggleCalEdtSousmenu() { var sm = document.getElementById('cal-edt-sousmenu'); var arrow = document.getElementById('cal-edt-arrow'); if (!sm) return; var open = sm.style.display === 'flex'; sm.style.display = open ? 'none' : 'flex'; sm.style.flexDirection = 'column'; if (arrow) { arrow.textContent = open ? '›' : '⌄'; arrow.style.transform = open ? 'rotate(0deg)' : 'rotate(0deg)'; } } function toggleFeature(id) { var acc = document.getElementById(id); var btn = acc ? acc.previousElementSibling : null; var arrow = document.getElementById('fa-' + id); if (!acc) return; var isOpen = acc.classList.contains('open'); // Fermer tous les autres document.querySelectorAll('.feature-accordion.open').forEach(function(el) { el.classList.remove('open'); var prevBtn = el.previousElementSibling; if (prevBtn) prevBtn.classList.remove('open'); var arrowEl = prevBtn ? prevBtn.querySelector('.feature-arrow') : null; }); document.querySelectorAll('.feature-item.open').forEach(function(el){ el.classList.remove('open'); }); if (!isOpen) { acc.classList.add('open'); if (btn) btn.classList.add('open'); } } function maintenant() { var d = new Date(); return d.getHours().toString().padStart(2,'0') + ':' + d.getMinutes().toString().padStart(2,'0'); }
🏆 Quiz — Seconde Guerre mondiale
1 / 20
⏱ 3:00
🏆 Mon classement
Vois où tu te situes parmi tes camarades
Réviscool Tableau de bord
AR
Tableau de bord
Vue d'ensemble de la plateforme Réviscool —
Élèves inscrits
0
sur la plateforme
Abonnements actifs
0
en cours
En attente
0
Activation requise
Comptes bloqués
0
Accès restreint
Cours publiés
0
Disponibles
Exercices
0
Disponibles
Enseignants
0
Actifs sur la plateforme
Revenus
Ce mois
Répartition par niveau
Contenus les plus consultés
Répartition des plans
Boîte à suggestions 0
Schéma d'inscription & accès
Activité en direct
Toutes les actions réalisées sur la plateforme
Gestion des élèves
Tous les comptes élèves
Élève Niveau Statut Abonnement Inscrit le Durée (jours) Durée (mois) Temps restant Actions
Gestion des enseignants
Tous les comptes enseignants
Enseignant Matières Statut Abonnement Inscrit le Durée (jours) Durée (mois) Temps restant Actions
Gestion des techniciens
Tous les comptes techniciens
Technicien Filière Statut Abonnement Inscrit le Durée (jours) Durée (mois) Temps restant Actions
Gestion des universitaires
Tous les comptes universitaires
Universitaire Filière Statut Abonnement Inscrit le Durée (jours) Durée (mois) Temps restant Actions
Administrateurs
Comptes avec accès admin
AdministrateurEmailRôleCréé parDernière connexionActions
Comptes en attente
Activations requises
UtilisateurRôleNiveauDemande leActions
Bibliothèque de cours
Gérez tous les contenus pédagogiques
Tout
Maths
Histoire
Sciences
Langues
Exercices & Révisions
Base d'exercices de la plateforme
TitreMatièreNiveauTypeDifficultéVuesActions
Matières
Organisation pédagogique
Centre d'upload
Importer des documents, vidéos et ressources pédagogiques
Localisation pédagogique
Import en masse
Importer plusieurs PDF via CSV + dossier
Fichier CSV

Colonnes requises : lecon_id, titre, statut, fichier

Dossier des PDF

Sélectionne le dossier contenant tes fichiers PDF.

Abonnements
Gestion des plans et accès
UtilisateurPlanStatutDébutExpirationMontantActions
Plans tarifaires
Plans affichés sur la page de paiement — modifications appliquées en temps réel
Aperçu — Page de paiement
Ceci est exactement ce que voient les apprenants sur la page de paiement.
Modifier les plans
Rapports & Analytiques
Statistiques détaillées de la plateforme
Inscriptions par mois
Cours les plus consultés
Revenus mensuels (HTG)
Progression par niveau
Paramètres
Configuration générale de la plateforme
Informations de la plateforme
type="text">
Fonctionnalités
Inscription libre
Autoriser les auto-inscriptions
Mode maintenance
Restreindre l'accès aux élèves
Forum élèves
Activer l'espace d'entraide
Badges de progression
Gamification de l'apprentissage
Notifications
Centre de messages et alertes
Alertes système
Préférences de notification
Nouvelle inscription
Email + dashboard
Abonnement expiré
48h avant expiration
Rapport hebdomadaire
Tous les lundis à 9h
Sécurité
Accès, logs et protection de la plateforme
Identifiants de connexion administrateur
Confirmation requise — Entrez votre mot de passe actuel pour valider les changements.
Authentification à deux facteurs par SMS (2FA)
⏳ Chargement du statut…
Politiques d'accès
Double authentification
Obligatoire pour les admins
Expiration de session
Déconnexion après 2h d'inactivité
Blocage après tentatives
3 échecs = compte verrouillé
IP whitelistée
Accès admin depuis IP connues uniquement
Journaux de connexion
Institutions
Gérer les établissements scolaires de la plateforme
Nom Ville Type Statut Actions
⏳ Chargement…
0 institution(s)
📝 Mes devoirs
Gérer mes travaux
⚠️ Impossible de charger les devoirs.
📬
Partagés par mes profs
Récupérer les devoirs assignés par mes enseignants
Mes devoirs reçus
📭 Appuie sur le bouton ci-dessus pour charger tes devoirs.
✏️ Mes exercices
S'entraîner & progresser
⚠️ Impossible de charger les exercices.
📚
Bibliothèque
Accéder aux exercices de la bibliothèque
Mes exercices supplémentaires
📭 Appuie sur le bouton ci-dessus pour charger tes exercices.
📖 Mes cours
Cours partagés par mes enseignants
⚠️ Impossible de charger les leçons.
📬
Partagées par mes profs
Récupérer les leçons assignées par mes enseignants
Mes leçons reçues
📭 Appuie sur le bouton ci-dessus pour charger tes leçons.
📊 Statistiques
Suivi des performances de vos élèves
⚠️ Impossible de charger les statistiques.
⏳ Chargement des statistiques…
⭐ Laisser un témoignage
Veuillez remplir tous les champs obligatoires.
✅ Témoignage envoyé ! Il apparaîtra sur la page d'accueil.
💡 Boîte à suggestions
✅ Suggestion envoyée ! Merci pour votre contribution.
ℹ️

📱 Installer Réviscool

Scannez ce QR code avec votre téléphone pour installer l'application sur votre écran d'accueil.

📚
Installer Réviscool Appuyez sur ⎋ Partager puis « Sur l'écran d'accueil » pour installer l'application.