- tabActiviteClasse : 3ème section "Activités dans la vie réelle" (8 toggles + code déverrouillage offline par item activé) - Codes de déverrouillage sur les modules quiz activés (colonne Code / Stats) - generateUnlockCode() dans helpers.js — algo déterministe pour usage offline - Bibliothèque du département dans Mes modules (5 modules pré-faits, import indépendant) - showImportActivityModal() implémenté (import par ID) - docs/NOTE_TECHNIQUE.md : note technique architecture/RGPD/anonymat pour le client - CSS : btn-p/btn-s/btn-d redéclarés après .btn pour corriger la cascade gms.css Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
228 lines
16 KiB
JavaScript
228 lines
16 KiB
JavaScript
// ===================== MODALS =====================
|
||
|
||
function showSupportModal(){
|
||
const classOptions=DB.classes.map(c=>`<option value="${c.id}">${c.name}</option>`).join('');
|
||
showModal(`<div class="modal"><div class="mhd"><h2>⚙️ Support</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">
|
||
<p class="sm muted mb16">Réinitialisez l'association d'un élève à sa classe. L'élève devra resaisir son code dans l'application pour se rattacher à nouveau. Sa progression n'est pas effacée.</p>
|
||
<div class="fg"><label>Classe</label><select id="supportClassId">${classOptions}</select></div>
|
||
<div class="fg"><label>Code élève</label><input type="text" id="supportCodeModal" placeholder="Ex. : A3K7" style="font-family:monospace;font-weight:700;letter-spacing:.1em;text-transform:uppercase;"></div>
|
||
<div id="supportModalFeedback" class="xs muted mt8"></div>
|
||
</div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Fermer</button><button class="btn btn-p" onclick="resetStudentModal()">Réinitialiser</button></div></div>`);
|
||
}
|
||
function resetStudentModal(){
|
||
const classId=document.getElementById('supportClassId').value;
|
||
const code=document.getElementById('supportCodeModal').value.trim().toUpperCase();
|
||
const fb=document.getElementById('supportModalFeedback');
|
||
const c=cls(classId);
|
||
if(!c||!code){showToast('Renseignez la classe et le code.');return;}
|
||
const stu=c.students.find(s=>s.code===code);
|
||
if(!stu){if(fb)fb.textContent=`Aucun élève avec le code "${code}" dans ${c.name}.`;return;}
|
||
if(fb)fb.textContent='';
|
||
showToast(`Élève ${code} réinitialisé ✓`,'ok');
|
||
document.getElementById('supportCodeModal').value='';
|
||
}
|
||
|
||
function confirmDeleteActivity(actId){
|
||
const a=act(actId);if(!a)return;
|
||
showModal(`<div class="modal"><div class="mhd"><h2>Supprimer le module</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">
|
||
<div class="alert alert-warn">⚠️ Supprimer <strong>${a.name}</strong> retirera ce module de toutes les classes auxquelles il est assigné. Cette action est irréversible.</div>
|
||
</div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-d" onclick="deleteActivity('${actId}')">Supprimer</button></div></div>`);
|
||
}
|
||
function deleteActivity(actId){
|
||
const a=act(actId);if(!a)return;
|
||
DB.classes.forEach(c=>{c.activities=c.activities.filter(id=>id!==actId);});
|
||
DB.activities=DB.activities.filter(a=>a.id!==actId);
|
||
closeModal();showToast('Module supprimé','ok');
|
||
S.navigate('mes-activites',{year:S.params.year},false);
|
||
}
|
||
|
||
function toggleClassMenu(classId){
|
||
const m=document.getElementById('cmenu_'+classId);
|
||
if(!m)return;
|
||
const isOpen=m.style.display!=='none';
|
||
document.querySelectorAll('[id^="cmenu_"]').forEach(el=>el.style.display='none');
|
||
m.style.display=isOpen?'none':'block';
|
||
if(!isOpen)setTimeout(()=>document.addEventListener('click',()=>m.style.display='none',{once:true}),0);
|
||
}
|
||
function closeClassMenu(classId){
|
||
const m=document.getElementById('cmenu_'+classId);if(m)m.style.display='none';
|
||
}
|
||
|
||
function showModal(html){document.getElementById('modalContainer').innerHTML=`<div class="ov" onclick="if(event.target===this)closeModal()">${html}</div>`;}
|
||
function closeModal(){document.getElementById('modalContainer').innerHTML='';}
|
||
|
||
function showTipsModal(){showModal(`<div class="modal modal-lg"><div class="mhd"><h2>💡 Conseils de rédaction</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">
|
||
<div class="tip"><div class="tip-title">🎯 Discriminer, pas piéger</div><div class="tip-body">Chaque question doit différencier ce qui est maîtrisé ou non. Évitez les pièges artificiels sans valeur pédagogique.</div></div>
|
||
<div class="tip"><div class="tip-title">🪤 Faux-vrai</div><div class="tip-body">Incluez des affirmations qui semblent vraies mais sont fausses. Elles révèlent les fausses certitudes.</div></div>
|
||
<div class="tip"><div class="tip-title">🧩 Réponses plausibles</div><div class="tip-body">Les mauvaises réponses doivent être suffisamment plausibles pour tester la connaissance.</div></div>
|
||
<div class="tip"><div class="tip-title">💬 Feedback guidant</div><div class="tip-body">Expliquez <strong>pourquoi</strong> c'est faux. « Pas tout à fait ! Voici ce qui s'est vraiment passé… »</div></div>
|
||
<div class="tip tip-blue"><div class="tip-title">⏱ Durée cible</div><div class="tip-body">10 questions ≈ 10–15 minutes. Au-delà, l'attention chute.</div></div>
|
||
</div>
|
||
<div class="mfoot"><button class="btn btn-p" onclick="closeModal()">Fermer</button></div></div>`);}
|
||
|
||
|
||
function showRenameClassModal(classId){
|
||
const c=cls(classId);if(!c)return;
|
||
showModal(`<div class="modal"><div class="mhd"><h2>Renommer la classe</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">
|
||
<div class="fg"><label>Nom de la classe</label><input type="text" id="renameClassInput" value="${c.name}"></div>
|
||
<div class="alert alert-warn" style="margin-top:12px;">⚠️ Ce changement sera visible par tous les enseignants qui ont accès à cette classe.</div>
|
||
</div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-p" onclick="renameClass('${classId}')">Renommer</button></div></div>`);
|
||
setTimeout(()=>document.getElementById('renameClassInput')?.focus(),50);
|
||
}
|
||
function renameClass(classId){
|
||
const c=cls(classId);const input=document.getElementById('renameClassInput');
|
||
if(!c||!input)return;
|
||
const name=input.value.trim();if(!name){showToast('Saisissez un nom.');return;}
|
||
c.name=name;closeModal();showToast('"'+name+'" ✓','ok');
|
||
S.navigate('une-classe',{classId,tab:S.params.tab||'eleves'},false);
|
||
}
|
||
|
||
function showNewClassModal(){showModal(`<div class="modal"><div class="mhd"><h2>Nouvelle classe</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">
|
||
<div class="fg"><label>Nom de la classe</label><input type="text" id="newClassName" placeholder="Ex. : 5ème A"></div>
|
||
<div class="fg"><label>Année scolaire</label><select id="newClassYear"><option value="2024-2025">2024 – 2025</option><option value="2025-2026" selected>2025 – 2026</option><option value="2026-2027">2026 – 2027</option></select></div>
|
||
<div class="alert alert-info">ℹ️ Un code de partage unique sera généré automatiquement.</div>
|
||
</div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-p" onclick="createClass()">Créer</button></div></div>`);}
|
||
|
||
function createClass(){
|
||
const name=document.getElementById('newClassName').value.trim();const year=document.getElementById('newClassYear').value.trim();
|
||
if(!name){showToast('Donnez un nom.');return;}
|
||
const id='c'+Date.now();const code='GC-'+name.replace(/\s/g,'').toUpperCase().slice(0,4)+'-'+Math.random().toString(36).slice(2,6).toUpperCase();
|
||
DB.classes.push({id,name,code,year,students:[],activities:[]});DB.freeAccess[id]=new Set();
|
||
closeModal();showToast('"'+name+'" créé·e !','ok');S.navigate('une-classe',{classId:id,tab:'eleves'});
|
||
}
|
||
|
||
function showImportClassModal(){showModal(`<div class="modal"><div class="mhd"><h2>Importer une classe</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">
|
||
<div class="alert alert-info">ℹ️ Saisissez le code partagé par un autre enseignant.</div>
|
||
<div class="fg"><label>Code de classe</label><input type="text" placeholder="Ex. : GC-5A-2025"></div>
|
||
</div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-p" onclick="showToast('Import simulé','ok');closeModal()">Importer</button></div></div>`);}
|
||
|
||
function showAddStudentModal(classId){showModal(`<div class="modal"><div class="mhd"><h2>Ajouter un élève</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">
|
||
<div class="alert alert-info">ℹ️ L'élève trouve son code dans les paramètres de l'app.</div>
|
||
<div class="fg"><label>Code élève</label><input type="text" id="newStuCode" placeholder="Ex. : X4K2" style="font-size:16px;font-family:monospace;font-weight:700;letter-spacing:.1em;"></div>
|
||
</div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-p" onclick="addStudent('${classId}')">Ajouter</button></div></div>`);}
|
||
|
||
function addStudent(classId){
|
||
const code=document.getElementById('newStuCode').value.trim().toUpperCase();
|
||
if(!code){showToast('Saisissez un code élève.');return;}
|
||
const c=cls(classId);if(!c)return;
|
||
const id='s'+Date.now();c.students.push({id,code});DB.results[id]={};DB.progression[id]={c1:0,c2:0,c3:0,c4:0};
|
||
closeModal();showToast('Élève '+code+' ajouté !','ok');S.navigate('une-classe',{classId,tab:'eleves'},false);
|
||
}
|
||
|
||
function showAssignActivityModal(classId){
|
||
const c=cls(classId);const ua=DB.activities.filter(a=>a.status==='published'&&!c.activities.includes(a.id));
|
||
showModal(`<div class="modal"><div class="mhd"><h2>Assigner une activité</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">${ua.length===0?'<p class="muted sm">Toutes les modules publiés sont déjà assignés.</p>':ua.map(a=>`
|
||
<div style="border:1px solid var(--border);border-radius:8px;padding:13px;margin-bottom:10px;">
|
||
<div class="fbet"><div><div class="semi">${a.name}</div><div class="xs muted">${a.questions.length} questions</div></div>
|
||
<button class="btn btn-p btn-sm" onclick="assignActivity('${classId}','${a.id}')">Assigner</button></div>
|
||
</div>`).join('')}
|
||
</div><div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Fermer</button></div></div>`);
|
||
}
|
||
|
||
function assignActivity(classId,actId){
|
||
const c=cls(classId);const a=act(actId);if(!c||!a)return;
|
||
if(!c.activities.includes(actId))c.activities.push(actId);
|
||
if(!a.assignedClasses.includes(classId))a.assignedClasses.push(classId);
|
||
closeModal();showToast('"'+a.name+'" assigné à '+c.name+' !','ok');
|
||
S.navigate('une-classe',{classId,tab:'activite'},false);
|
||
}
|
||
function assignFromAct(actId,classId){
|
||
const c=cls(classId);const a=act(actId);if(!c||!a)return;
|
||
if(!c.activities.includes(actId))c.activities.push(actId);
|
||
if(!a.assignedClasses.includes(classId))a.assignedClasses.push(classId);
|
||
showToast(c.name+' assignée !','ok');S.navigate('une-activite',{activityId:actId,tab:'classes'},false);
|
||
}
|
||
function showImportActivityModal(){
|
||
showModal(`<div class="modal">
|
||
<div class="mhd"><h2>Importer un module par ID</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody">
|
||
<div class="alert alert-info">ℹ️ L'import crée une copie indépendante et modifiable dans vos modules.</div>
|
||
<div class="fg"><label>ID du module</label><input type="text" id="importActId" placeholder="Ex. : lib2" autofocus></div>
|
||
<div id="importActFeedback" class="xs muted mt8"></div>
|
||
</div>
|
||
<div class="mfoot">
|
||
<button class="btn btn-s" onclick="closeModal()">Annuler</button>
|
||
<button class="btn btn-p" onclick="doImportActivityById()">Importer</button>
|
||
</div>
|
||
</div>`);
|
||
}
|
||
function doImportActivityById(){
|
||
const id=(document.getElementById('importActId').value||'').trim();
|
||
const fb=document.getElementById('importActFeedback');
|
||
if(!id){if(fb)fb.textContent='Saisissez un ID.';return;}
|
||
const lib=DB.libraryActivities.find(a=>a.id===id);
|
||
if(!lib){if(fb)fb.textContent='Aucun module trouvé avec cet ID.';return;}
|
||
closeModal();
|
||
importLibraryActivity(id);
|
||
}
|
||
|
||
function confirmDesassign(classId,actId){
|
||
const c=cls(classId);const a=act(actId);if(!c||!a)return;
|
||
showModal(`<div class="modal"><div class="mhd"><h2>Désassigner le module ?</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody"><p style="font-size:13px;line-height:1.6;">Désassigner <strong>${a.name}</strong> de <strong>${c.name}</strong> ?<br>Les résultats déjà enregistrés sont conservés.</p></div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-d" onclick="doDesassign('${classId}','${actId}')">Désassigner</button></div></div>`);
|
||
}
|
||
function doDesassign(classId,actId){
|
||
const c=cls(classId);const a=act(actId);if(!c||!a)return;
|
||
c.activities=c.activities.filter(id=>id!==actId);a.assignedClasses=a.assignedClasses.filter(id=>id!==classId);
|
||
closeModal();showToast('Désassignée.');S.navigate('une-classe',{classId,tab:'activite'},false);
|
||
}
|
||
function confirmDesassignFromAct(actId,classId){
|
||
const c=cls(classId);const a=act(actId);if(!c||!a)return;
|
||
showModal(`<div class="modal"><div class="mhd"><h2>Désassigner la classe ?</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody"><p style="font-size:13px;line-height:1.6;">Désassigner <strong>${c.name}</strong> de <strong>${a.name}</strong> ?<br>Les résultats sont conservés.</p></div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-d" onclick="doDesassignFromAct('${actId}','${classId}')">Désassigner</button></div></div>`);
|
||
}
|
||
function doDesassignFromAct(actId,classId){
|
||
const c=cls(classId);const a=act(actId);if(!c||!a)return;
|
||
c.activities=c.activities.filter(id=>id!==actId);a.assignedClasses=a.assignedClasses.filter(id=>id!==classId);
|
||
closeModal();showToast(c.name+' désassigné.');S.navigate('une-activite',{activityId:actId,tab:'classes'},false);
|
||
}
|
||
function confirmDeleteClass(classId){
|
||
const c=cls(classId);if(!c)return;
|
||
showModal(`<div class="modal"><div class="mhd"><h2>Supprimer la classe ?</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody"><p style="font-size:13px;line-height:1.6;">Supprimer <strong>${c.name}</strong> et ses <strong>${c.students.length} élèves</strong> ?<br>Action irréversible.</p></div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-d" onclick="doDeleteClass('${classId}')">Supprimer</button></div></div>`);
|
||
}
|
||
function doDeleteClass(classId){
|
||
DB.classes=DB.classes.filter(c=>c.id!==classId);DB.activities.forEach(a=>{a.assignedClasses=a.assignedClasses.filter(id=>id!==classId);});
|
||
closeModal();showToast('Classe supprimée.');S.navigate('mes-classes');
|
||
}
|
||
function confirmDeleteStudent(classId,stuId){
|
||
const c=cls(classId);const s=c?.students.find(s=>s.id===stuId);if(!c||!s)return;
|
||
showModal(`<div class="modal"><div class="mhd"><h2>Supprimer l'élève ?</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody"><p style="font-size:13px;line-height:1.6;">Supprimer le code <strong>${s.code}</strong> de <strong>${c.name}</strong> ?</p></div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-d" onclick="doDeleteStudent('${classId}','${stuId}')">Supprimer</button></div></div>`);
|
||
}
|
||
function deleteStudent(classId,stuId){confirmDeleteStudent(classId,stuId);}
|
||
|
||
function doDeleteStudent(classId,stuId){
|
||
const c=cls(classId);if(!c)return;
|
||
c.students=c.students.filter(s=>s.id!==stuId);closeModal();showToast('Élève retiré.');S.navigate('une-classe',{classId,tab:'eleves'},false);
|
||
}
|
||
function confirmResetResults(classId,actId){
|
||
const c=cls(classId);const a=act(actId);if(!c||!a)return;
|
||
showModal(`<div class="modal"><div class="mhd"><h2>Réinitialiser les résultats ?</h2><button class="btn-ico" onclick="closeModal()">✕</button></div>
|
||
<div class="mbody"><p style="font-size:13px;line-height:1.6;">Réinitialiser les résultats de <strong>${c.name}</strong> pour <strong>${a.name}</strong> ?<br>Les élèves pourront recommencer. Action irréversible.</p></div>
|
||
<div class="mfoot"><button class="btn btn-s" onclick="closeModal()">Annuler</button><button class="btn btn-d" onclick="doResetResults('${classId}','${actId}')">Réinitialiser</button></div></div>`);
|
||
}
|
||
function doResetResults(classId,actId){
|
||
const c=cls(classId);if(!c)return;
|
||
c.students.forEach(s=>{if(DB.results[s.id])delete DB.results[s.id][actId];});
|
||
closeModal();showToast('Résultats réinitialisés pour '+c.name+'.');render();
|
||
}
|
||
|