conquiers-ta-vie-proto/js/modals.js
isUnknown a549c08240 Activités réelles, bibliothèque dép., note technique, fix btn-p
- 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>
2026-05-23 18:34:13 +02:00

228 lines
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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

// ===================== 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 ≈ 1015 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();
}