EDF
Challenge Cup
RPP EDF
Recueil de Prescriptions
Votre logo
ici
👨‍🏫

Connexion Formateur

Recueil de Prescriptions au Personnel

Propulsé par MIBSoft Enterprise · Sécurité EDF

'); w.document.close(); setTimeout(function(){w.print();},500); } // ═══ MODALE CONNEXION DOUBLE ═══ // Marquer déconnexion si fermeture navigateur window.addEventListener('beforeunload', function(){ if(currentTrainer?.id){ // Utiliser sendBeacon pour être sûr que la requête part même en cas de fermeture const url=`${SUPABASE_URL}/rest/v1/trainers?id=eq.${currentTrainer.id}`; const data=JSON.stringify({is_connected:false}); navigator.sendBeacon && navigator.sendBeacon(url+'&apikey='+SUPABASE_ANON_KEY, new Blob([data],{type:'application/json'})); } }); // ═══════════════════════════════════════════════ // QUESTIONS FORMATEUR — Vue + Demandes superviseur // ═══════════════════════════════════════════════ let _currentViewQ = null; // question en cours de vue function loadTrainerList(){} // Plus utilisé function togglePwd(inputId,btn){ const i=document.getElementById(inputId); if(!i)return; i.type=i.type==='password'?'text':'password'; btn.textContent=i.type==='password'?'👁️':'🙈'; } // Fonction togglePwd locale pour trainer.html (supabase.js pas accessible) function togglePwd(inputId, btn){ var i = document.getElementById(inputId); if(!i) return; i.type = i.type === 'password' ? 'text' : 'password'; if(btn) btn.textContent = i.type === 'password' ? '👁️' : '🙈'; } // Charger la liste des formateurs au démarrage loadTrainerList(); // Restaurer session formateur si existante (async function(){ // Si l'URL contient ?session=... → flux stagiaire (QR scanné), ne pas auto-restaurer le formateur const _qp=new URLSearchParams(window.location.search); if(_qp.get('session'))return; const saved=SESSION.load('rpp_trainer'); // TTL 8h : si la session sauvegardée est plus vieille, on l'efface et on force un nouveau login const TTL_MS=8*3600*1000; if(saved?.savedAt && (Date.now()-saved.savedAt) > TTL_MS){ SESSION.clear('rpp_trainer'); return; } if(saved?.trainerId){ const{data:trainer}=await sb.from('trainers').select('id,supervisor_id,firstname,lastname,email,created_at,is_connected,last_connected_at,organization_id').eq('id',saved.trainerId).single(); if(trainer){ let supervisor=null; if(saved.supervisorId){ const{data:sup}=await sb.from('supervisors').select('*').eq('id',saved.supervisorId).single(); supervisor=sup||null; } // Restauration depuis session sauvegardée — marquer connecté await sb.from('trainers').update({is_connected:true,last_connected_at:new Date().toISOString()}).eq('id',trainer.id).then(()=>{}); connectTrainer(trainer,supervisor); } } })(); async function initSupervisor(){ // Mode projecteur (verify-password) — pas de superviseur identifié. // Récupérer la première organisation comme fallback (MVP mono-org). // TODO: étendre verify-password pour retourner organization_id du superviseur. const{data:orgs}=await sb.from('organizations').select('id').limit(1); const orgId=orgs?.[0]?.id; if(!orgId){addLog('error','SESSION','Aucune organisation trouvée — impossible de créer une session');return;} _currentSessionOrg=orgId; sessionCode_='rpp_'+UTILS.uuid();QPB=parseInt(document.getElementById('admin-qpb')?.value)||QPB; const sd=await DB.rpcInsertSession({session_code:sessionCode_,organization_id:orgId,game_mode:GAME_MODE,status:'waiting',game_status:'waiting',config:{activeModules:[...activeModules],qtypeFilters:[...ALL_Q_TYPES],chapters:[...ALL_CHAP_IDS],qpb:QPB,libreCount:libreQCount}}); sessionId=sd.id;_qrZoomGenerated=false;document.getElementById('qrcode-zoom').innerHTML='';addLog('success','SESSION','Créée: '+sessionCode_);document.getElementById('session-id-disp').textContent=sessionCode_;const qrEl=document.getElementById('qrcode-container');qrEl.innerHTML='';new QRCode(qrEl,{text:window.location.href.split('?')[0]+'?session='+sessionId,width:180,height:180,colorDark:'#003DA5',colorLight:'#ffffff',correctLevel:QRCode.CorrectLevel.H});subscribeToTeams();loadAndDisplayTeams(); } let _buzzerOrder=[]; // ordre d'arrivée des réponses let _buzzerPrevAnswered=new Set(); // équipes déjà comptées function resetBuzzer(){ _buzzerOrder=[]; _buzzerPrevAnswered=new Set(); const el=document.getElementById('buzzer-list'); if(el)el.innerHTML=''; } function updateBuzzerList(teams){ const el=document.getElementById('buzzer-list'); if(!el)return; // Détecter les nouvelles réponses (teams||[]).forEach(function(t){ if(t.has_answered&&!_buzzerPrevAnswered.has(t.id)){ _buzzerPrevAnswered.add(t.id); _buzzerOrder.push({id:t.id,name:t.name,rank:_buzzerOrder.length+1,correct:t.is_correct}); playBuzzerSound(); } }); // Rendre la liste if(!_buzzerOrder.length){el.innerHTML='';return;} const medals=['🥇','🥈','🥉']; el.innerHTML=_buzzerOrder.map(function(b){ const medal=medals[b.rank-1]||b.rank+'e'; return '
' +''+medal+'' +''+b.name+'' +'
'; }).join(''); } function subscribeToTeams(){if(channelTeams)REALTIME.unsubscribe(channelTeams);channelTeams=REALTIME.subscribe('teams-'+sessionId,'teams',`session_id=eq.${sessionId}`,()=>{loadAndDisplayTeams();checkAllAnswered();});} async function loadAndDisplayTeams(){const t=await DB.query('teams',q=>q.eq('session_id',sessionId).order('joined_at'));updateTeamsList(t||[]);updateBuzzerList(t||[]);} function renderAvatar(t,size){ size=size||40; if(t.avatar&&t.avatar.startsWith('data:')){ return ''; } return ''+(t.avatar||getTeamEmoji(t.id))+''; } function updateTeamsList(teams){const list=document.getElementById('teams-list'),cnt=document.getElementById('teams-count');const active=(teams||[]).filter(t=>t.name!=='[Supprimé]');if(!active.length){list.innerHTML='
En attente des équipes...
';cnt.textContent='0';return;}cnt.textContent=active.length;list.innerHTML=active.map(t=>`
${renderAvatar(t,40)}
${t.name}
${t.score||0} pts
${!t.online?'
hors ligne
':''}
`).join('');} window.removeTeam=function(tId){showConfirmDialog('Supprimer cette équipe ?',async()=>{try{try{await DB.rpcDeleteTeam(tId);}catch(_){await DB.rpcUpdateTeam(tId,{online:false,name:'[Supprimé]'});}}catch(err){showAlert('❌ Erreur: '+err.message);}loadAndDisplayTeams();});}; document.getElementById('start-game-btn').addEventListener('click',async()=>{ if(_gameFinished){ // ── MODE RELANCER ── showConfirmDialog( '🔄 Relancer une nouvelle partie avec les mêmes équipes ?

Les scores seront remis à zéro. Vous pouvez modifier les paramètres avant de confirmer.', async()=>{ await relancerJeu(); } ); }else{ // ── MODE LANCER (première fois) ── QPB=parseInt(document.getElementById('admin-qpb')?.value)||QPB; await DB.rpcUpdateSession(sessionId,{status:'playing',game_mode:GAME_MODE}); _correctionInProgress=false;_resultShown=false; if(GAME_MODE==='boxes')showBoxesScreen();else startLibreMode(); } }); async function relancerJeu(){ try{ addLog('info','RELANCE','Relancement du jeu...'); // Relire les paramètres potentiellement modifiés QPB=parseInt(document.getElementById('admin-qpb')?.value)||QPB; libreQCount=parseInt(document.getElementById('admin-libre-nb')?.value)||libreQCount; // Remettre les scores et stats de toutes les équipes à zéro const teams=await DB.query('teams',q=>q.eq('session_id',sessionId)); if(teams){ for(const t of teams){ try{await DB.rpcUpdateTeam(t.id,{ score:0,last_points:0,last_speed_bonus:0, total_correct:0,total_answered:0, has_answered:false,answer:null,is_correct:false,answer_time:0 });}catch(e){console.error('[relance reset team]',e);} } } // Supprimer les team_answers de la session pour repartir propre try{ const delResult=await DB.rpcDeleteTeamAnswersForSession(sessionId); addLog('info','RELANCE','team_answers vidés pour nouveau jeu'); }catch(e){console.warn('[relance del team_answers]',e);} // Initialiser le timestamp du nouveau jeu _gameStartTime=new Date().toISOString(); // Réinitialiser les boîtes ouvertes et le statut de session openedBoxes=[]; _correctionInProgress=false; _resultShown=false; _gameFinished=false; _allTraineeGames=[]; await DB.rpcUpdateSession(sessionId,{ status:'playing', game_status:'waiting', game_mode:GAME_MODE, opened_boxes:[], current_q_idx:0, current_box:null, randomized_games:[], answers_count:0 }); // Restaurer le bouton Lancer const startBtn=document.getElementById('start-game-btn'); if(startBtn){ startBtn.textContent='🚀 Lancer le Jeu'; startBtn.style.background=''; } addLog('success','RELANCE','Jeu relancé — équipes conservées, scores remis à zéro'); // Retourner au dashboard et relancer showScreen('supervisor-dashboard'); loadAndDisplayTeams(); // Attendre 1s puis lancer directement setTimeout(async()=>{ await DB.rpcUpdateSession(sessionId,{status:'playing',game_mode:GAME_MODE}); if(GAME_MODE==='boxes')showBoxesScreen();else startLibreMode(); },800); }catch(e){ console.error('[relancerJeu]',e); showAlert('Erreur lors du relancement : '+(e.message||e)); _gameFinished=false; } } async function showBoxesScreen(){showScreenSafe('boxes-screen');try{const s=await DB.getById('sessions',sessionId);openedBoxes=s?.opened_boxes||[];}catch(e){openedBoxes=[];}renderBoxes();} function renderBoxes(){ console.log('[renderBoxes] MODULES_RPP:', MODULES_RPP.length, 'activeModules:', activeModules.size); const boxes=MODULES_RPP.filter(m=>activeModules.has(m.id)); console.log('[renderBoxes] boxes filtrés:', boxes.length);document.getElementById('boxes-container').innerHTML=boxes.map(mod=>{const done=openedBoxes.includes(mod.id);const modIdx=MODULES_RPP.indexOf(mod);const filteredGames=getQuestionsForBox(modIdx);const nb=filteredGames.length;return`
${mod.icon}

${mod.title}

${mod.subtitle}

${done?'✅ Terminé':nb+' questions'}
`;}).join('');} window.openBox=async function(moduleId){ try{ // Relire QPB depuis l'input admin (au cas où modifié) const qpbInput=document.getElementById('admin-qpb'); if(qpbInput)QPB=parseInt(qpbInput.value)||QPB; currentBox=MODULES_RPP.findIndex(m=>m.id===moduleId); currentQIdx=0; _correctionInProgress=false;_resultShown=false; const rawGames=getQuestionsForBox(currentBox); addLog('info','MODULE',`${moduleId}: ${rawGames.length} questions (QPB=${QPB})`); const games=rawGames.map(prepareGame); if(!games.length){showAlert('⚠️ Aucune question disponible pour ce module.');return;} await DB.rpcUpdateSession(sessionId,{current_box:moduleId,current_q_idx:0,game_status:'question',randomized_games:games,answers_count:0,question_timestamp:new Date().toISOString()}); resetBuzzer(); try{await DB.rpcResetTeamsForSession(sessionId);}catch(_e){} showProjectorQuestion(games[0],0,games.length);startTimer(); }catch(e){ console.error('[openBox]',e); showAlert('Erreur lors de l\'ouverture du module : '+(e.message||e)); _correctionInProgress=false; } }; async function startLibreMode(){currentBox=-1;currentQIdx=0;const games=getQuestionsForMode().map(prepareGame);if(!games.length){showAlert('⚠️ Aucune question pour ces filtres.');return;}_correctionInProgress=false;_resultShown=false;await DB.rpcUpdateSession(sessionId,{current_box:'libre',current_q_idx:0,game_status:'question',randomized_games:games,answers_count:0,question_timestamp:new Date().toISOString()});await DB.rpcResetTeamsForSession(sessionId);showProjectorQuestion(games[0],0,games.length);startTimer();} async function checkAllAnswered(){ if(!sessionId)return; try{ const teams=await DB.query('teams',q=>q.eq('session_id',sessionId).eq('online',true)); if(!teams||teams.length===0)return; const answered=teams.filter(t=>t.has_answered).length; const total=teams.length; const ansEl=document.getElementById('ans-count'); const totEl=document.getElementById('total-teams-cnt'); if(ansEl)ansEl.textContent=answered; if(totEl)totEl.textContent=total; const hint=document.getElementById('all-answered-hint'); if(hint){if(answered>=total&&total>0)hint.classList.remove('hidden');else hint.classList.add('hidden');} // Diffuse le compteur aux stagiaires via la table sessions (subscribe deja en place) try{await DB.rpcUpdateSession(sessionId,{answers_count:answered});}catch(_e){} if(answered>=total&&total>0){ // Stoppe le timer mais ne revele PAS automatiquement : le formateur clique pour reveler if(timerInterval){clearInterval(timerInterval);timerInterval=null;} } }catch(e){console.error('[checkAllAnswered]',e);} } function showProjectorQuestion(game,idx,total){showScreenSafe('proj-question');_resultShown=false;document.getElementById('proj-icon').textContent=getTypeIcon(game.type);document.getElementById('proj-type-lbl').textContent=getTypeLabel(game.type);if(typeof idx==='number'&&typeof total==='number'){var _cn=document.getElementById('proj-q-counter');if(_cn)_cn.textContent='Question '+(idx+1)+' / '+total;}document.getElementById('proj-tags').innerHTML=renderTags(game.tags||[]); // Pour fill-blank : si la question contient %1% %2%, remplacer par consigne courte // (sinon on a la phrase complete dans la consigne ET en bas avec blanks = duplication "en code") const _projQText = (game.type==='fill-blank' && /%\d+%/.test(game.question||'')) ? '📝 Complétez la phrase ci-dessous' : (game.question||''); document.getElementById('proj-q-text').textContent=_projQText; const sb2=document.getElementById('proj-scenario-box'),st=document.getElementById('proj-scenario-txt');if(game.scenario){st.textContent=game.scenario;sb2.classList.remove('hidden');}else sb2.classList.add('hidden');const md=document.getElementById('proj-media'),img=document.getElementById('proj-img'),vw=document.getElementById('proj-video-wrap'),ve=document.getElementById('proj-video');if(game.imageUrl&&!game.imageUrl.includes('/rpp2025/')){img.src=game.imageUrl;img.classList.remove('hidden');vw.classList.add('hidden');md.classList.remove('hidden');}else if(game.videoUrl){ve.src=game.videoUrl;vw.classList.remove('hidden');img.classList.add('hidden');md.classList.remove('hidden');}else md.classList.add('hidden');document.getElementById('proj-game-cont').innerHTML=renderProjector(game);document.getElementById('ans-count').textContent='0'; // Reinit etat des controles formateur a chaque nouvelle question const _hint=document.getElementById('all-answered-hint');if(_hint)_hint.classList.add('hidden'); const _rab=document.getElementById('reveal-answer-btn');if(_rab){_rab.disabled=false;_rab.textContent='👀 Afficher la réponse';} const _reb=document.getElementById('reveal-expl-btn');if(_reb){_reb.disabled=false;_reb.classList.remove('hidden');} const _nb=document.getElementById('next-q-btn');if(_nb){_nb.classList.add('hidden');_nb.disabled=false;_nb.textContent='Question Suivante →';} const _expBlock=document.getElementById('proj-expl-block');if(_expBlock)_expBlock.classList.add('hidden'); } function renderProjector(game){const bG='style="background:var(--edf-blue)"';const oG='style="background:var(--edf-orange)"';const mkCard=(t,s,i)=>`
${s}. ${t}
`;if(['quiz','true-false','find-intruder','scenario'].includes(game.type))return`
${(game.optionsWithIndex||[]).map((o,i)=>mkCard(o.text,String.fromCharCode(65+i),i)).join('')}
`;if(game.type==='multiple-select')return`
☑️ Plusieurs bonnes réponses
${(game.optionsWithIndex||[]).map((o,i)=>mkCard(o.text,String.fromCharCode(65+i),i)).join('')}
`;if(['sequence','ranking'].includes(game.type))return`
${(game.itemsWithIndex||[]).map((item,i)=>`
${i+1}. ${item.text}
`).join('')}
`;if(game.type==='matching')return`
${(game.pairsLeft||[]).map(p=>`
${p.text}
`).join('')}
${(game.pairsRight||[]).map(p=>`
${p.text}
`).join('')}
`;if(game.type==='fill-blank'){const d=game.sentence.replace(/%\d+%/g,'______');return`
${d}
${(game.wordBankShuffled||[]).map(w=>`${w}`).join('')}
`;}if(game.type==='categories')return`
${(game.categories||[]).map((c,i)=>`
${c.label}
`).join('')}
${(game.items||[]).map(item=>`${item.text||item}`).join('')}
`;if(game.type==='decision')return`
🌳
Arbre de décision — ${(game.steps||[]).length} étapes
`;return'';} async function showCorrection(){ const btn=document.getElementById('next-q-btn'); const revealExpBtn=document.getElementById('reveal-expl-btn'); try{ const session=await DB.getById('sessions',sessionId); if(!session)return; const game=(session.randomized_games||[])[session.current_q_idx]; if(!game)return; // calcAndUpdateScores ne doit pas bloquer l affichage try{await calcAndUpdateScores(game);}catch(e){console.error('[showCorrection>calcScores]',e);} try{await DB.rpcUpdateSession(sessionId,{game_status:'answer_revealed'});}catch(e){console.error('[showCorrection>update]',e);} showScreenSafe('proj-correction'); const explEl=document.getElementById('proj-expl'); const corrEl=document.getElementById('proj-corr-content'); const explBlock=document.getElementById('proj-expl-block'); if(explEl)explEl.textContent=game.explanation||''; // Phase answer_revealed : cache l explication, montre seulement le bouton reveal-expl if(explBlock)explBlock.classList.add('hidden'); if(revealExpBtn){revealExpBtn.classList.remove('hidden');revealExpBtn.disabled=false;} if(btn)btn.classList.add('hidden'); // Bouton RPP source const rppBtn=document.getElementById('proj-rpp-btn'); if(rppBtn){ if(game.pdfSource && game.pdfPage){ rppBtn.style.display=''; rppBtn.classList.remove('hidden'); rppBtn.onclick=function(){window.openPdfRef(game.pdfSource, game.pdfPage);}; rppBtn.textContent='📄 Voir la page '+game.pdfPage; }else if(game.imageUrl){ rppBtn.style.display=''; rppBtn.classList.remove('hidden'); rppBtn.onclick=function(){window.openRppModal(game.imageUrl, game.pdfPage||'');}; rppBtn.textContent='📄 Voir la page'; }else{ rppBtn.classList.add('hidden'); } } if(corrEl)try{corrEl.innerHTML=renderCorrection(game);}catch(e){corrEl.textContent='Voir la correction avec le formateur.';} }catch(e){ console.error('[showCorrection]',e); } } async function revealExplanation(){ const revealExpBtn=document.getElementById('reveal-expl-btn'); const nextBtn=document.getElementById('next-q-btn'); const explBlock=document.getElementById('proj-expl-block'); try{ if(explBlock)explBlock.classList.remove('hidden'); if(revealExpBtn)revealExpBtn.classList.add('hidden'); if(nextBtn){nextBtn.classList.remove('hidden');nextBtn.disabled=false;nextBtn.textContent='Question Suivante →';} await DB.rpcUpdateSession(sessionId,{game_status:'explanation_revealed'}); }catch(e){console.error('[revealExplanation]',e);} } function renderCorrection(game){const scen=game.scenario?`
📋 Situation :

${game.scenario}

`:'';if(['quiz','true-false','find-intruder','scenario'].includes(game.type))return scen+`
${(game.optionsWithIndex||[]).map((o,i)=>`
${o.originalIndex===game.correctAnswer?'✅ ':''} ${String.fromCharCode(65+i)}. ${o.text}
`).join('')}
`;if(game.type==='multiple-select')return`
${(game.optionsWithIndex||[]).map((o,i)=>{const ok=(game.correctAnswers||[]).includes(o.originalIndex);return`
${ok?'☑️':'☐'}${String.fromCharCode(65+i)}. ${o.text}
`;}).join('')}
`;if(['sequence','ranking'].includes(game.type)){const gt=idx=>(game.itemsWithIndex||[]).find(i=>i.originalIndex===idx)?.text||'';return`
${(game.correctOrder||[]).map((idx,i)=>`
${i+1}${gt(idx)}
`).join('')}
`;}if(game.type==='matching')return`
${(game.pairs||[]).map(p=>`
✅ ${p.left} → ${p.right}
`).join('')}
`;if(game.type==='fill-blank'){let f=game.sentence;(game.correctBlanks||[]).forEach((b,i)=>{f=f.replace(`%${i+1}%`,`${b}`);});return`
${f}
`;}if(game.type==='categories'){const cm={};(game.categories||[]).forEach(cat=>{cm[cat.id]=[];});(game.items||[]).forEach(item=>{if(cm[item.category])cm[item.category].push(item.text||item);});return`
${(game.categories||[]).map(cat=>`
${cat.label}
${(cm[cat.id]||[]).map(t=>`
✅ ${t}
`).join('')}
`).join('')}
`;}if(game.type==='decision')return`
${(game.steps||[]).map((step,i)=>{const cI=(game.correctPath||[])[i];const cO=step.options[cI];return`
${i+1}
${step.question}
✅ ${cO?cO.text:'?'}
`;}).join('')}
`;return'';} let _calcInProgress=false; const _calcDoneForQuestion=new Set(); async function calcAndUpdateScores(game){ const qId=game?.id||game?.question||'unknown'; if(_calcDoneForQuestion.has(qId)){addLog('warn','SCORES','calcAndUpdateScores déjà fait pour cette question: '+qId);return;} if(_calcInProgress){addLog('warn','SCORES','calcAndUpdateScores déjà en cours, ignoré');return;} _calcDoneForQuestion.add(qId); _calcInProgress=true; try{ const teams=await DB.query('teams',q=>q.eq('session_id',sessionId)); if(!teams||!teams.length)return; for(const team of teams){ try{ const reallyAnswered=team.has_answered; // timer expiré = répondu (incorrectement) const pts=reallyAnswered&&team.is_correct?calcPts(true,team.answer_time||0):{base:0,bonus:0,total:0}; addLog('info','SCORES',team.name+' rep:'+reallyAnswered+' ok:'+team.is_correct+' pts:'+pts.total); await DB.rpcUpdateTeam(team.id,{ score:(team.score||0)+pts.total,last_points:pts.total,last_speed_bonus:pts.bonus, total_correct:(team.total_correct||0)+(reallyAnswered&&team.is_correct?1:0), total_answered:(team.total_answered||0)+(reallyAnswered?1:0) }); // team_answers : optionnel, ne bloque pas si ça échoue try{await DB.rpcInsertTeamAnswer({session_id:sessionId,organization_id:_currentSessionOrg||currentTrainer?.organization_id||null,team_id:team.id,question_id:game.id||game._id||null,question_version_id:game.current_version_id||null,answer:team.answer,is_correct:team.is_correct||false,answer_time:team.answer_time||0,base_points:pts.base,speed_bonus:pts.bonus,total_points:pts.total});}catch(_e){console.error('[team_answers insert]',_e);} }catch(e){console.error('[calcScores team]',team.id,e);} } }catch(e){console.error('[calcAndUpdateScores]',e);} finally{_calcInProgress=false;} } document.getElementById('reveal-answer-btn')?.addEventListener('click',async()=>{ const b=document.getElementById('reveal-answer-btn');if(!b||b.disabled)return;b.disabled=true;b.textContent='⏳ ...'; try{if(timerInterval){clearInterval(timerInterval);timerInterval=null;}await showCorrection();} catch(e){console.error('[reveal-answer-btn]',e);} finally{b.disabled=false;b.textContent='👀 Afficher la réponse';} }); document.getElementById('reveal-expl-btn')?.addEventListener('click',async()=>{ const b=document.getElementById('reveal-expl-btn');if(!b||b.disabled)return;b.disabled=true; try{await revealExplanation();}catch(e){console.error('[reveal-expl-btn]',e);} finally{b.disabled=false;} }); document.getElementById('next-q-btn').addEventListener('click',async()=>{const btn=document.getElementById('next-q-btn');if(btn.disabled)return;btn.disabled=true;btn.textContent='⏳ Chargement...';_calcDoneForQuestion.clear();try{const session=await DB.getById('sessions',sessionId);if(!session){btn.disabled=false;btn.textContent='Question Suivante →';return;}const nextIdx=(session.current_q_idx||0)+1;const games=session.randomized_games||[];if(nextIdx>=games.length){if(session.game_mode==='boxes'&¤tBox>=0){const modId=MODULES_RPP[currentBox]?.id;const nO=[...(session.opened_boxes||[])];if(modId&&!nO.includes(modId))nO.push(modId);await DB.rpcUpdateSession(sessionId,{opened_boxes:nO});openedBoxes=nO;const boxes=MODULES_RPP.filter(m=>activeModules.has(m.id));if(nO.length{_correctionInProgress=false;_resultShown=false;await DB.rpcUpdateSession(sessionId,{game_status:'boxes'});showBoxesScreen();}); async function displayRanking(cId){const teams=await DB.query('teams',q=>q.eq('session_id',sessionId).order('score',{ascending:false}));if(!teams)return;const el=document.getElementById(cId);if(!el)return;el.innerHTML=teams.map((t,i)=>`
${i+1}
${t.name}
${t.score||0} pts
`).join('');} async function showPodiumSupervisor(){ showScreenSafe('podium-screen'); _gameFinished=true; try{await sb.rpc('fn_finalize_session',{p_session_id:sessionId});}catch(e){} await DB.rpcUpdateSession(sessionId,{game_status:'finished',status:'finished'}); createConfetti(); playApplause(6); // Transformer le bouton "Lancer" en "Relancer" const startBtn=document.getElementById('start-game-btn'); if(startBtn){ startBtn.textContent='🔄 Relancer le Jeu'; startBtn.style.background='linear-gradient(135deg,#7c3aed,#6d28d9)'; }const teams=await DB.query('teams',q=>q.eq('session_id',sessionId).order('score',{ascending:false}));if(!teams)return;const pc=document.getElementById('podium-cont');pc.innerHTML='';const mk=(t,h,s)=>`
${s}
${t.name}
${t.score} pts
`;if(teams[1])pc.innerHTML+=mk(teams[1],170,'🥈').replace('var(--edf-blue)','linear-gradient(135deg,#C0C0C0,#808080)');if(teams[0])pc.innerHTML+=mk(teams[0],240,'🥇');if(teams[2])pc.innerHTML+=mk(teams[2],130,'🥉').replace('var(--edf-blue)','linear-gradient(135deg,#CD7F32,#8B4513)');await displayRanking('full-rank-list');} async function showPodiumTrainee(){ const teams=await DB.query('teams',q=>q.eq('session_id',sessionId).order('score',{ascending:false})); if(!teams)return; showScreenSafe('trainee-podium');createConfetti();playApplause(6); const my=teams.find(t=>t.id===teamDbId)||{score:0,total_correct:0,total_answered:0}; const myR=teams.findIndex(t=>t.id===teamDbId)+1; // ── STATS : compteurs directs de teams ── // Remis à 0 à chaque relancement, incrémentés une fois par réponse // Même question rejouée = compte deux fois → comportement voulu const totalAnswered=my.total_answered||0; const totalCorrect=my.total_correct||0; const pct=totalAnswered>0?Math.round((totalCorrect/totalAnswered)*100):0;document.getElementById('tp-teamname').textContent=teamName;document.getElementById('tp-score').textContent=(my.score||0)+' pts';document.getElementById('tp-rank').textContent=myR<=3?['🥇 1ère place !','🥈 2ème place !','🥉 3ème place !'][myR-1]:`${myR}ème place`;document.getElementById('tp-correct').textContent=totalCorrect;document.getElementById('tp-total').textContent=totalAnswered;document.getElementById('tp-pct').textContent=pct+'%';document.getElementById('tp-pct').style.color=pct>=60?'#16a34a':'#dc2626';setTimeout(()=>{const b=document.getElementById('tp-bar');if(b)b.style.width=pct+'%';},300);document.getElementById('tp-rank-final').innerHTML=teams.map((t,i)=>`
${i===0?'🥇':i===1?'🥈':i===2?'🥉':(i+1)+'.'}${t.name}${t.id===teamDbId?' 👈':''}
${t.score||0} pts
`).join(''); // Charger les erreurs du stagiaire try{await loadTraineeErrors();}catch(e){console.error('[podium errors]',e);} } // ══════════════════════════════════════ // RÉVISION DES ERREURS — STAGIAIRE // ══════════════════════════════════════ async function loadTraineeErrors(){ const section=document.getElementById('tp-review-section'); const list=document.getElementById('tp-review-list'); const countEl=document.getElementById('tp-review-count'); if(!section||!list)return; // Stratégie 1 : utiliser _allTraineeGames (accumulé côté client, le plus fiable) // Stratégie 2 : fallback sur team_answers + randomized_games let wrongGames=[]; if(_allTraineeGames.length>0){ // Méthode client-side : _allTraineeGames contient UNIQUEMENT les jeux du jeu EN COURS // On cherche dans team_answers uniquement les erreurs récentes (après _gameStartTime) let wrongIds=new Set(); try{ let q2=DB.query('team_answers',qb=> qb.eq('session_id',sessionId).eq('team_id',teamDbId).eq('is_correct',false) ); const ans=await q2; if(ans&&ans.length){ ans.forEach(a=>{ // Filtrer par timestamp si disponible if(_gameStartTime&&a.created_at&&a.created_at<_gameStartTime)return; if(a.question_id)wrongIds.add(a.question_id); }); } }catch(e){console.warn('[loadTraineeErrors]',e);} if(wrongIds.size>0){ wrongGames=_allTraineeGames.filter(g=>wrongIds.has(g.id)); } // Fallback: si pas de question_id dans team_answers → utiliser le score de l'équipe // Les questions vues mais mal répondues sont dans _allTraineeGames // et on sait que l'équipe a eu des mauvaises réponses via total_correct vs total_answered if(!wrongGames.length&&wrongIds.size===0){ section.classList.add('hidden');return; } }else{ // Fallback : récupérer depuis la DB try{ const ans=await DB.query('team_answers',q=> q.eq('session_id',sessionId).eq('team_id',teamDbId).eq('is_correct',false) ); const sess=await DB.getById('sessions',sessionId); const allGames=sess?.randomized_games||[]; if(ans&&ans.length&&allGames.length){ const seen=new Set(); wrongGames=ans.filter(a=>{ if(!a.question_id||seen.has(a.question_id))return false; seen.add(a.question_id); return true; }).map(a=>allGames.find(g=>g.id===a.question_id)||null).filter(Boolean); } }catch(e){console.error('[loadTraineeErrors fallback]',e);} } if(!wrongGames.length){section.classList.add('hidden');return;} // Afficher la section section.classList.remove('hidden'); if(countEl)countEl.textContent=wrongGames.length+' question'+(wrongGames.length>1?'s':''); list.innerHTML=wrongGames.map((game,idx)=>renderReviewQuestion(game,idx)).join(''); } function renderReviewQuestion(game,idx){ const typeLabel=Q_TYPE_LABELS[game.type]||game.type; let correctionHtml=''; // Style commun : font-size 0.95rem (lisible mobile), word-break, padding genereux const cellOk = 'background:#d1fae5;color:#065f46;border:1.5px solid #6ee7b7;padding:10px 12px;border-radius:10px;font-size:0.92rem;font-weight:600;word-break:break-word;line-height:1.4'; const cellKo = 'background:#f3f4f6;color:#6b7280;padding:10px 12px;border-radius:10px;font-size:0.92rem;font-weight:500;word-break:break-word;line-height:1.4'; if(['quiz','true-false','find-intruder','scenario'].includes(game.type)){ const opts=game.optionsWithIndex||game.options?.map((o,i)=>({text:o,originalIndex:i}))||[]; correctionHtml=opts.map(o=>{ const isCorrect=o.originalIndex===parseInt(game.correctAnswer); return `
${isCorrect?'✅':' '}${o.text||o}
`; }).join(''); }else if(game.type==='multiple-select'){ const opts=game.optionsWithIndex||game.options?.map((o,i)=>({text:o,originalIndex:i}))||[]; correctionHtml=opts.map(o=>{ const isCorrect=(game.correctAnswers||[]).map(Number).includes(o.originalIndex); return `
${isCorrect?'☑️':'☐'}${o.text||o}
`; }).join(''); }else if(['sequence','ranking'].includes(game.type)){ const items=game.itemsWithIndex||[]; const getT=i=>items.find(x=>x.originalIndex===i)?.text||'?'; correctionHtml=(game.correctOrder||[]).map((i,pos)=> `
${pos+1} ${getT(i)}
` ).join(''); }else if(game.type==='matching'){ correctionHtml=(game.pairs||[]).map(p=> `
✅ ${p.left}
→ ${p.right}
` ).join(''); }else if(game.type==='fill-blank'){ let s=game.sentence||''; (game.correctBlanks||[]).forEach((b,i)=>{s=s.replace('%'+(i+1)+'%',''+b+'');}); correctionHtml=`
${s}
`; }else{ correctionHtml=`
Voir la correction avec le formateur.
`; } const scenHtml=game.scenario?`
📋 ${game.scenario}
`:'' return `
${typeLabel} Q${idx+1}

${game.question}

${scenHtml}

✅ Bonne réponse :

${correctionHtml}
${game.explanation?`
💡 ${game.explanation}
`:''} ${(game.pdfSource&&game.pdfPage)?``:(game.imageUrl?``:'')}
`; } window.toggleReview=function(){ const list=document.getElementById('tp-review-list'); const chevron=document.getElementById('tp-review-chevron'); if(!list)return; const hidden=list.classList.contains('hidden'); list.classList.toggle('hidden',!hidden); if(chevron)chevron.textContent=hidden?'▲':'▼'; }; window.addEventListener('DOMContentLoaded',async()=>{ // Afficher le login formateur par défaut showScreen('trainer-login-screen'); loadSettings();const p=new URLSearchParams(window.location.search);const sP=p.get('session'),tP=p.get('team');if(sP&&tP){sessionId=sP;try{const team=await DB.getById('teams',tP);if(team){teamDbId=tP;teamName=team.name;await DB.rpcUpdateTeam(teamDbId,{online:true});SESSION.save('rpp_challenge',{sessionId,teamDbId,teamName});const s=await DB.getById('sessions',sessionId);if(!s){showAlert('Session introuvable.');showScreenSafe('trainee-login');return;}showScreenSafe('trainee-waiting');document.getElementById('tw-teamname').textContent=teamName;listenGameUpdatesTrainee();await handleSessionUpdate(s);}else{SESSION.clear('rpp_challenge');showScreenSafe('trainee-login');}}catch(err){showScreenSafe('trainee-login');}}else if(sP){sessionId=sP;const saved=SESSION.load('rpp_challenge');if(saved&&saved.sessionId===sP){const url=new URL(window.location.href);url.searchParams.set('team',saved.teamDbId);window.history.replaceState({},'',url.toString());location.reload();}else showScreenSafe('trainee-login');}}); document.getElementById('join-btn').addEventListener('click',async()=>{teamName=document.getElementById('team-name-inp').value.trim();const e=document.getElementById('join-err');if(!teamName){e.textContent='Entrez un nom d\'équipe !';e.classList.remove('hidden');return;}if(!sessionId){e.textContent='Session introuvable. Scannez le QR code.';e.classList.remove('hidden');return;}e.classList.add('hidden');try{const s=await DB.getById('sessions',sessionId);if(!s){e.textContent='Session introuvable.';e.classList.remove('hidden');return;}const _av=document.getElementById('selected-avatar')?.value||'👷';const team=await DB.rpcJoinTeam(sessionId,teamName,_av);teamDbId=team.id;SESSION.save('rpp_challenge',{sessionId,teamDbId,teamName});const url=new URL(window.location.href);url.searchParams.set('session',sessionId);url.searchParams.set('team',teamDbId);window.history.replaceState({},'',url.toString());showScreenSafe('trainee-waiting');document.getElementById('tw-teamname').textContent=teamName;listenGameUpdatesTrainee();if(s.status==='playing'){const ss=await DB.getById('sessions',sessionId);if(ss)await handleSessionUpdate(ss);}}catch(err){e.textContent='Erreur: '+(err.message||'Réessayez');e.classList.remove('hidden');}}); function listenGameUpdatesTrainee(){if(channelSession)REALTIME.unsubscribe(channelSession);channelSession=REALTIME.subscribeUpdate('session-'+sessionId,'sessions',`id=eq.${sessionId}`,async(ns)=>{await handleSessionUpdate(ns);});} async function handleSessionUpdate(session){const status=session.game_status||session.status; // Mise a jour du compteur "X / Y equipes ont repondu" sur l ecran d attente try{ const twAns=document.getElementById('tw-ans-count'); const twTot=document.getElementById('tw-total-count'); if(twAns||twTot){ const ans=session.answers_count; if(typeof ans==='number'){ if(twAns)twAns.textContent=ans; // total : on relit teams si on n a pas encore le total (cache trainee-side) if(twTot&&(!twTot.textContent||twTot.textContent==='0')){ const ts=await DB.query('teams',q=>q.eq('session_id',sessionId).eq('online',true)); twTot.textContent=(ts||[]).length; } } } }catch(_e){} if(status==='question'){if(_resultShown){setTimeout(async()=>{_resultShown=false;const s=await DB.getById('sessions',sessionId);if(s&&s.game_status==='question'){const g=(s.randomized_games||[])[s.current_q_idx];if(g)showTraineeGame(g);}},300);return;}_resultShown=false;const games=session.randomized_games||[];const game=games[session.current_q_idx];if(game)showTraineeGame(game);} else if(status==='answer_revealed'){if(!_resultShown){_resultShown=true;setTimeout(()=>showTraineeResult(false),200);}} else if(status==='explanation_revealed'||status==='correction'){if(!_resultShown){_resultShown=true;setTimeout(()=>showTraineeResult(true),200);}else{showTraineeResult(true);}} else if(status==='pause'||status==='boxes'){_resultShown=false;showScreenSafe('ranking-screen');await displayRanking('ranking-list');}else if(status==='finished'){_resultShown=false;await showPodiumTrainee();}else if(status==='waiting'){ _resultShown=false; _allTraineeGames=[]; _gameStartTime=new Date().toISOString(); // Nouveau jeu → nouveau timestamp const scoreEl=document.getElementById('tg-score-disp'); if(scoreEl)scoreEl.textContent='0 pts'; showScreenSafe('trainee-waiting'); document.getElementById('tw-teamname').textContent=teamName||''; addLog('info','RELANCE','Nouvelle partie — en attente'); }} function showTraineeGame(game){ lastTraineeGame=game; // Accumuler tous les jeux vus (pour révision erreurs en fin de session) if(game&&game.id&&!_allTraineeGames.find(g=>g.id===game.id)){ _allTraineeGames.push(game); } showScreenSafe('trainee-game');document.getElementById('tg-team-disp').textContent=teamName;if(channelScore)REALTIME.unsubscribe(channelScore);channelScore=REALTIME.subscribeUpdate('my-score-'+teamDbId,'teams',`id=eq.${teamDbId}`,(t)=>{document.getElementById('tg-score-disp').textContent=(t.score||0)+' pts';});document.getElementById('trainee-game-cont').innerHTML=renderTraineeGame(game);selectedAns=null;selectedSeq=[];selectedMulti=[];selectedRank=[];fillBlanks=[];catAnswers={};decisionPath=[];selCatItem=null;selectedBlank=null;matchLeft=null;matchRight=null;matchingPairs=[];questionStartTime=Date.now();const btn=document.getElementById('validate-btn');btn.disabled=false;btn.textContent='✓ Valider ma réponse';btn.className='btn btn-blue btn-2xl btn-full';btn.onclick=()=>validateAnswer(game);} function renderTraineeGame(game){const badge=`
${getTypeLabel(game.type)}
`;const tags=game.tags?.length?`
${renderTags(game.tags)}
`:'';const scen=game.scenario?`
📋 Situation :

${game.scenario}

`:'';const media=renderMediaHTML(game); // Pour fill-blank : si la question contient deja les marqueurs %1% %2%, on remplace par une // consigne courte (sinon on affiche 2x la phrase : une avec %1% lisible "en code", une avec blanks). const qText = (game.type==='fill-blank' && /%\d+%/.test(game.question||'')) ? '📝 Completez la phrase ci-dessous :' : (game.question||''); const qH=`

${qText}

`;if(['quiz','true-false','find-intruder','scenario'].includes(game.type))return`
${badge}${tags}
${scen}${media}${qH}
${(game.optionsWithIndex||[]).map((item,i)=>`
${String.fromCharCode(65+i)}${item.text}
`).join('')}
`;if(game.type==='multiple-select')return`
${badge}${tags}
${qH}
☑️ Cochez TOUTES les bonnes réponses (${(game.correctAnswers||[]).length} attendues)
${(game.optionsWithIndex||[]).map((item,i)=>`
✓
${String.fromCharCode(65+i)}.${item.text}
`).join('')}
`;if(game.type==='sequence')return`
${badge}${tags}
${qH}
${(game.itemsWithIndex||[]).map(item=>`
${item.text}
`).join('')}
Votre ordre :
Cliquez dans l'ordre...
`;if(game.type==='ranking')return`
${badge}${tags}
${qH}
🏅 1er clic = priorité 1
${(game.itemsWithIndex||[]).map(item=>`
${item.text}
`).join('')}
Votre classement :
Cliquez...
`;if(game.type==='matching'){const total=(game.pairsLeft||[]).length;return`
${badge}${tags}
${qH}
🔗 Cliquez gauche puis droite — recliquez pour défaire
${(game.pairsLeft||[]).map(item=>`
${item.text}
`).join('')}
${(game.pairsRight||[]).map(item=>`
${item.text}
`).join('')}
Paires formées :0/${total}

Aucune paire formée

`;}if(game.type==='fill-blank'){let sH=game.sentence;const bC=(game.sentence.match(/%\d+%/g)||[]).length;fillBlanks=new Array(bC).fill(null);selectedBlank=null;for(let i=1;i<=bC;i++)sH=sH.replace(`%${i}%`,`___`);return`
${badge}${tags}
${qH}
${sH}
Cliquez sur un trou pour le selectionner, puis choisissez un mot
${(game.wordBankShuffled||game.wordBank||[]).map((w,i)=>``).join('')}
`;}if(game.type==='categories'){(game.categories||[]).forEach(cat=>{catAnswers[cat.id]=[];});const items=game.itemsShuffled||game.items||[];return`
${badge}${tags}
${qH}
${(game.categories||[]).map((cat,ci)=>`
${cat.label}
Ici
`).join('')}
👆 Sélectionnez un élément :
${items.map((item,i)=>``).join('')}
`;}if(game.type==='decision'){decisionPath=[];return`
${badge}${tags}
📋 Situation :

${game.situation||game.scenario||game.question}

${renderDecisionStep(game,0)}
`;}return'';} function renderDecisionStep(game,si){const step=(game.steps||[])[si];if(!step)return'';return`
${step.question}
${step.options.map((opt,i)=>``).join('')}
`;} window.selectAns=function(idx){document.querySelectorAll('.ans-opt').forEach(o=>o.classList.remove('sel'));document.querySelector(`.ans-opt[data-oi="${idx}"]`)?.classList.add('sel');selectedAns=idx;}; window.selectMulti=function(idx){const el=document.querySelector(`.multi-opt[data-oi="${idx}"]`);if(!el)return;const pos=selectedMulti.indexOf(idx);if(pos!==-1){selectedMulti.splice(pos,1);el.classList.remove('sel');}else{selectedMulti.push(idx);el.classList.add('sel');}}; window.selectSeqItem=function(idx){const pos=selectedSeq.indexOf(idx);if(pos!==-1)selectedSeq.splice(pos,1);else selectedSeq.push(idx);document.querySelectorAll('.seq-item').forEach(el=>{const i=parseInt(el.getAttribute('data-oi'));const p=selectedSeq.indexOf(i);el.classList.remove('sel');el.querySelector('.ord-num')?.remove();if(p!==-1){el.classList.add('sel');const d=document.createElement('div');d.className='ord-num';d.textContent=p+1;el.appendChild(d);}});const game=lastTraineeGame;if(!game?.itemsWithIndex)return;const labels=selectedSeq.map(i=>game.itemsWithIndex.find(x=>x.originalIndex===i)?.text||'');const el=document.getElementById('seq-disp');if(el)el.textContent=labels.join(' → ')||"Cliquez dans l'ordre...";}; window.selectRankItem=function(idx){const pos=selectedRank.indexOf(idx);if(pos!==-1)selectedRank.splice(pos,1);else selectedRank.push(idx);document.querySelectorAll('.rank-drag').forEach(el=>{const i=parseInt(el.getAttribute('data-oi'));const p=selectedRank.indexOf(i);el.classList.remove('ranked');el.querySelector('.rnk-badge')?.remove();if(p!==-1){el.classList.add('ranked');const d=document.createElement('div');d.className='rnk-badge';d.textContent=p+1;el.appendChild(d);}});const game=lastTraineeGame;if(!game?.itemsWithIndex)return;const labels=selectedRank.map(i=>game.itemsWithIndex.find(x=>x.originalIndex===i)?.text||'');const el=document.getElementById('rank-disp');if(el)el.textContent=labels.map((l,i)=>`${i+1}. ${l}`).join(' | ')||'Cliquez...';}; window.selectMatch=function(side,idx){const item=document.querySelector(`.match-item[data-side="${side}"][data-index="${idx}"]`);if(!item)return;if(item.classList.contains('paired')){const pi=matchingPairs.findIndex(p=>p[side]===idx);if(pi!==-1){const pair=matchingPairs[pi];matchingPairs.splice(pi,1);[document.querySelector(`.match-item[data-side="left"][data-index="${pair.left}"]`),document.querySelector(`.match-item[data-side="right"][data-index="${pair.right}"]`)].forEach(el=>{if(el){el.classList.remove('paired','sel');el.querySelector('.pair-num')?.remove();}});renumberPairs();updatePairsSummary();}return;}if(side==='left'){if(matchLeft!==null)document.querySelector(`.match-item[data-side="left"][data-index="${matchLeft}"]`)?.classList.remove('sel');if(matchLeft===idx){matchLeft=null;return;}matchLeft=idx;item.classList.add('sel');}else{if(matchRight!==null)document.querySelector(`.match-item[data-side="right"][data-index="${matchRight}"]`)?.classList.remove('sel');if(matchRight===idx){matchRight=null;return;}matchRight=idx;item.classList.add('sel');}if(matchLeft!==null&&matchRight!==null){matchingPairs.push({left:matchLeft,right:matchRight});const num=matchingPairs.length;[document.querySelector(`.match-item[data-side="left"][data-index="${matchLeft}"]`),document.querySelector(`.match-item[data-side="right"][data-index="${matchRight}"]`)].forEach(el=>{if(!el)return;el.classList.remove('sel');el.classList.add('paired');const b=document.createElement('span');b.className='pair-num';b.textContent=num;el.appendChild(b);});matchLeft=null;matchRight=null;updatePairsSummary();}}; function renumberPairs(){document.querySelectorAll('.pair-num').forEach(el=>el.remove());matchingPairs.forEach((p,i)=>{[document.querySelector(`.match-item[data-side="left"][data-index="${p.left}"]`),document.querySelector(`.match-item[data-side="right"][data-index="${p.right}"]`)].forEach(el=>{if(!el)return;const b=document.createElement('span');b.className='pair-num';b.textContent=i+1;el.appendChild(b);});});} function updatePairsSummary(){const game=lastTraineeGame;const total=(game?.pairsLeft||[]).length;const cE=document.getElementById('pairs-cnt');if(cE)cE.textContent=`${matchingPairs.length}/${total}`;const sE=document.getElementById('pairs-summary');if(!sE||!game)return;if(matchingPairs.length===0){sE.innerHTML='

Aucune paire formée

';return;}sE.innerHTML=matchingPairs.map((p,i)=>{const lt=(game.pairsLeft||[]).find(pl=>pl.originalIndex===p.left)?.text||'?';const rt=(game.pairsRight||[]).find(pr=>pr.originalIndex===p.right)?.text||'?';return`
${i+1}${lt}→${rt}
`;}).join('');} window.unpairByIndex=function(i){if(i<0||i>=matchingPairs.length)return;const pair=matchingPairs[i];matchingPairs.splice(i,1);[document.querySelector(`.match-item[data-side="left"][data-index="${pair.left}"]`),document.querySelector(`.match-item[data-side="right"][data-index="${pair.right}"]`)].forEach(el=>{if(el){el.classList.remove('paired','sel');el.querySelector('.pair-num')?.remove();}});renumberPairs();updatePairsSummary();}; // ════════════════════════════════════════ // SYSTÈME PDFs DE RÉFÉRENCE // ════════════════════════════════════════ window.PDF_REFS=[];// Chargé depuis Supabase au démarrage // Charger les PDFs de référence depuis Supabase // ════════════════════════════════════════ // UPLOAD PDF vers Supabase Storage // ════════════════════════════════════════ window._selectedPdfFile=null; window.onPdfFileSelected=function(input){ if(!input.files||!input.files[0])return; var f=input.files[0]; window._selectedPdfFile=f; var el=document.getElementById('pdf-upload-filename'); if(el)el.textContent=f.name+' ('+Math.round(f.size/1024)+' Ko)'; var btn=document.getElementById('pdf-upload-btn'); if(btn)btn.disabled=false; }; // ════════════════════════════════════════ // CLAUDE AI — Trouver la page du PDF automatiquement // ════════════════════════════════════════ window.findPdfPageAuto=async function(){ var pdfId=document.getElementById('e-pdfsource')?.value; var expl=(document.getElementById('e-expl')?.value||'').trim(); var question=(document.getElementById('e-question')?.value||'').trim(); var status=document.getElementById('find-page-status'); if(!pdfId){ if(status)status.textContent='Selectionnez d abord un document PDF.'; return; } if(!expl&&!question){ if(status)status.textContent='Remplissez la question ou l explication d abord.'; return; } var pdf=window.PDF_REFS.find(function(p){return p.id===pdfId;}); if(!pdf){if(status)status.textContent='Document introuvable.';return;} if(status)status.textContent='🔍 Claude analyse le PDF... (peut prendre 10-30s)'; var pageInput=document.getElementById('e-pdfpage'); try{ // Appel à l'Edge Function Supabase qui fait appel à l'API Claude var SUPABASE_URL=sb.supabaseUrl||document.querySelector('meta[name="sb-url"]')?.content||window._SB_URL||''; var resp=await fetch(SUPABASE_URL+'/functions/v1/quick-function',{ method:'POST', headers:{'Content-Type':'application/json','Authorization':'Bearer '+window._SB_ANON_KEY}, body:JSON.stringify({ pdf_url:pdf.url, query:(question+' '+expl).trim(), pdf_name:pdf.name }) }); if(!resp.ok)throw new Error('HTTP '+resp.status); var data=await resp.json(); if(data.page){ if(pageInput)pageInput.value=data.page; if(status)status.textContent='Page trouvee: '+data.page+' — '+( data.excerpt||''); status.style.color='#16a34a'; }else if(data.error){ throw new Error(data.error); }else{ if(status)status.textContent='Page non trouvee. Entrez-la manuellement.'; status.style.color='#dc2626'; } }catch(e){ console.error('[findPdfPage]',e); if(status){status.textContent='Erreur: '+e.message;status.style.color='#dc2626';} } }; window.uploadPdfToStorage=async function(){ var name=(document.getElementById('pdf-ref-name')?.value||'').trim(); var file=window._selectedPdfFile; if(!name){showAlert('Donnez un nom au document.');return;} if(!file){showAlert('Selectionnez un fichier PDF.');return;} var btn=document.getElementById('pdf-upload-btn'); var prog=document.getElementById('pdf-upload-progress'); var bar=document.getElementById('pdf-progress-bar'); var txt=document.getElementById('pdf-progress-text'); if(btn)btn.disabled=true; if(prog)prog.classList.remove('hidden'); if(bar)bar.style.width='15%'; if(txt)txt.textContent='Upload en cours...'; try{ var filename='rpp/'+Date.now()+'-'+file.name.replace(/[^a-zA-Z0-9._-]/g,'_'); var uploadResult=await sb.storage.from('pdf-references').upload(filename,file,{contentType:'application/pdf',upsert:false}); if(uploadResult.error)throw new Error('Storage: '+uploadResult.error.message); if(bar)bar.style.width='60%'; if(txt)txt.textContent='Obtention URL publique...'; var urlResult=sb.storage.from('pdf-references').getPublicUrl(filename); var publicUrl=urlResult?.data?.publicUrl; if(!publicUrl)throw new Error('URL publique introuvable'); if(bar)bar.style.width='80%'; if(txt)txt.textContent='Enregistrement...'; await DB.insert('pdf_references',{name:name,url:publicUrl,storage_path:filename}); if(bar)bar.style.width='100%'; if(txt)txt.textContent='Document ajoute !'; window._selectedPdfFile=null; document.getElementById('pdf-ref-name').value=''; document.getElementById('pdf-file-input').value=''; var fn=document.getElementById('pdf-upload-filename'); if(fn)fn.textContent='Aucun fichier selectionne'; if(btn)btn.disabled=true; await loadPdfRefs();renderPdfRefsList(); setTimeout(function(){if(prog)prog.classList.add('hidden');if(bar)bar.style.width='0%';},2500); addLog('success','DOCS','PDF uploade: '+name); }catch(e){ console.error('[uploadPdf]',e); showAlert('Erreur upload: '+e.message); if(prog)prog.classList.add('hidden'); if(btn)btn.disabled=false; } }; // Supprimer un PDF (Storage + DB) window.deletePdfRef=async function(id){ showConfirmDialog('Supprimer ce document de reference ?',async function(){ try{ var pdf=window.PDF_REFS.find(function(p){return p.id===id;}); if(pdf&&pdf.storage_path){ await sb.storage.from('pdf-references').remove([pdf.storage_path]); } await DB.delete('pdf_references',id); await loadPdfRefs();renderPdfRefsList(); addLog('info','DOCS','PDF supprime: '+(pdf?pdf.name:id)); }catch(e){showAlert('Erreur suppression: '+e.message);} }); }; async function loadPdfRefs(){ try{ const docs=await DB.query('pdf_references',q=>q.order('name')); if(docs){ window.PDF_REFS=docs; addLog('info','PDFS',docs.length+' document(s) de référence chargés'); // Pre-warm : telecharger les PDFs en arriere-plan via PDF.js pour que le // 1er clic "Voir la page" soit instantane (formateur uniquement, pas trainee) if(window.pdfjsLib && docs.length){ docs.forEach(function(p){ if(!p.url || !p.url.toLowerCase().includes('.pdf')) return; if(window._pdfDocCache && window._pdfDocCache[p.url]) return; window._pdfDocCache = window._pdfDocCache || {}; // Promise stockee directement -> les appels concurrents partagent le meme telechargement window._pdfDocCache[p.url] = window.pdfjsLib.getDocument(p.url).promise.then(function(doc){ // Une fois charge, on remplace la promise par le doc resolu window._pdfDocCache[p.url] = doc; addLog('success','PDFS','PDF "'+p.name+'" pre-charge ('+doc.numPages+' pages)'); return doc; }).catch(function(err){ console.warn('[loadPdfRefs prewarm]', err); delete window._pdfDocCache[p.url]; }); }); } } }catch(e){ // Table peut-être inexistante - pas critique console.warn('[loadPdfRefs]',e); window.PDF_REFS=[]; } } // Cache PDF.js documents charges (evite de re-telecharger 6 Mo a chaque clic) window._pdfDocCache = window._pdfDocCache || {}; // Ouvrir une page de PDF de reference dans une modale (rendu PDF.js -> image) // Si pdfjsLib pas dispo (ex: ancien navigateur), fallback ouvrir le PDF dans nouvel onglet. window.openPdfRef=async function(pdfId,page){ // Si PDF_REFS pas encore charge (cas trainee qui rejoint via QR), tenter de le charger if(!window.PDF_REFS || !window.PDF_REFS.length){ try{ if(typeof loadPdfRefs==='function') await loadPdfRefs(); }catch(_e){} } var pdf=(window.PDF_REFS||[]).find(function(p){return p.id===pdfId;}); if(!pdf){ // Tentative directe sur Supabase (fallback) try{ const { data } = await sb.from('pdf_references').select('*').eq('id', pdfId).maybeSingle(); if(data) pdf = data; }catch(_e){} } if(!pdf){showAlert('Document de référence introuvable.');return;} var url=pdf.url; if(!url){showAlert('URL du document non configurée.');return;} // Si PDF.js non disponible OU URL non-PDF (Google Drive etc.), fallback nouvel onglet if(!window.pdfjsLib || !url.toLowerCase().includes('.pdf')){ window.open(url + (url.includes('.pdf')?('#page='+page):''),'_blank','noopener'); return; } // Afficher modale loader _showPdfPageModal({loading:true, page:page, name:pdf.name||'Document RPP', url:url}); try{ // Charger document (cache, peut contenir une Promise si pre-warm en cours) if(!window._pdfDocCache[url]){ window._pdfDocCache[url] = window.pdfjsLib.getDocument(url).promise; } let doc = await window._pdfDocCache[url]; window._pdfDocCache[url] = doc; // remplace la promise par la valeur resolue const pageNum = Math.max(1, Math.min(page||1, doc.numPages)); const pdfPage = await doc.getPage(pageNum); // Render en haute resolution const viewport = pdfPage.getViewport({ scale: 2.0 }); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = viewport.width; canvas.height = viewport.height; await pdfPage.render({ canvasContext: ctx, viewport: viewport }).promise; // Convert canvas en data URL et afficher const dataUrl = canvas.toDataURL('image/png'); _showPdfPageModal({loading:false, page:pageNum, name:pdf.name||'Document RPP', url:url, imgSrc:dataUrl}); }catch(e){ console.error('[openPdfRef] PDF.js error', e); _showPdfPageModal({loading:false, error:e.message||'Erreur de chargement', page:page, name:pdf.name||'', url:url}); } }; // Modale d affichage d une page PDF (canvas rendu en image) function _showPdfPageModal(opts){ let m = document.getElementById('pdf-page-modal'); if(!m){ m = document.createElement('div'); m.id = 'pdf-page-modal'; m.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;padding:16px'; m.onclick = function(e){ if(e.target===m) m.style.display='none'; }; document.body.appendChild(m); } m.style.display = 'flex'; let bodyHtml = ''; if(opts.loading){ bodyHtml = '
⏳
Chargement de la page '+(opts.page||'?')+'…
'; } else if(opts.error){ bodyHtml = '
⚠️
Impossible d\'afficher la page
'+(opts.error||'')+'
📄 Ouvrir le PDF complet
'; } else { bodyHtml = 'Page '+opts.page+''; } m.innerHTML = '
'+ ''+ bodyHtml+ '
'+(opts.name||'')+(opts.page?' — Page '+opts.page:'')+'
'+ (opts.imgSrc ? 'Ouvrir le PDF complet' : '')+ '
'; }; // Rendre un PDF de référence (affichage dans l'onglet Documents admin) function renderPdfRefsList(){ var container=document.getElementById('pdf-refs-list'); if(!container)return; if(!window.PDF_REFS.length){ container.innerHTML='

Aucun document. Ajoutez-en un ci-dessus.

'; return; } container.innerHTML=''; window.PDF_REFS.forEach(function(p){ var div=document.createElement('div'); div.style.cssText='display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:12px;background:var(--edf-bg);border:1px solid var(--edf-border);margin-bottom:8px'; var info=document.createElement('div'); var nm=document.createElement('p');nm.style.cssText='font-weight:700;font-size:.85rem;color:var(--edf-blue)';nm.textContent=p.name; var ul=document.createElement('p');ul.style.cssText='font-size:.72rem;color:#9ca3af';ul.textContent=p.url.substring(0,55)+(p.url.length>55?'...':''); info.appendChild(nm);info.appendChild(ul); var btns=document.createElement('div');btns.style.cssText='display:flex;gap:8px;flex-shrink:0'; var bv=document.createElement('button');bv.textContent='Voir';bv.className='btn btn-sm btn-outline'; bv.onclick=function(){window.open(p.url,'_blank');}; var bd=document.createElement('button');bd.textContent='Suppr.';bd.style.cssText='padding:4px 8px;border-radius:8px;background:#fee2e2;color:#dc2626;border:none;cursor:pointer;font-size:.75rem;font-weight:700'; bd.onclick=function(){window.deletePdfRef(p.id);}; btns.appendChild(bv);btns.appendChild(bd); div.appendChild(info);div.appendChild(btns); container.appendChild(div); }); }; window.deletePdfRef=async function(id){ showConfirmDialog('Supprimer ce document de référence ?',async function(){ try{ await DB.delete('pdf_references',id); await loadPdfRefs(); renderPdfRefsList(); }catch(e){showAlert('Erreur : '+e.message);} }); }; window.applyWebImageUrl=function(){ var url=(document.getElementById('e-img-web-url')?.value||'').trim(); if(!url)return; var field=document.getElementById('e-imageurl'); if(field){field.value=url;field.dispatchEvent(new Event('input'));} var panel=document.getElementById('e-img-web-input'); if(panel)panel.style.display='none'; document.getElementById('e-img-web-url').value=''; }; window.applyWebVideoUrl=function(){ var url=(document.getElementById('e-vid-web-url')?.value||'').trim(); if(!url)return; var field=document.getElementById('e-videourl'); if(field){field.value=url;field.dispatchEvent(new Event('input'));} var panel=document.getElementById('e-vid-web-input'); if(panel)panel.style.display='none'; document.getElementById('e-vid-web-url').value=''; }; window.uploadImageToField=function(input){ if(!input.files||!input.files[0])return; var file=input.files[0]; if(file.size>2*1024*1024){ showAlert('Image trop grande (max 2 Mo). Compressez-la ou utilisez une URL.'); return; } var reader=new FileReader(); reader.onload=function(e){ var dataUrl=e.target.result; var field=document.getElementById('e-imageurl'); if(field)field.value=dataUrl; var preview=document.getElementById('e-img-preview'); if(preview){ preview.style.display=''; var img=document.createElement('img'); img.src=dataUrl; img.style.cssText='max-height:120px;max-width:100%;border-radius:8px;border:1px solid #e5e7eb;object-fit:contain'; var btn=document.createElement('button'); btn.textContent='×'; btn.onclick=function(){clearImageField();}; btn.style.cssText='position:absolute;top:-6px;right:-6px;background:#dc2626;color:white;border:none;border-radius:50%;width:20px;height:20px;font-size:12px;cursor:pointer'; var wrap=document.createElement('div'); wrap.style.cssText='position:relative;display:inline-block'; wrap.appendChild(img);wrap.appendChild(btn); preview.innerHTML='';preview.appendChild(wrap); } }; reader.readAsDataURL(file); }; window.uploadVideoToField=function(input){ if(!input.files||!input.files[0])return; var file=input.files[0]; if(file.size>20*1024*1024){ showAlert('Vidéo trop grande pour être intégrée directement (max 20 Mo). Utilisez une URL YouTube ou hébergez la vidéo en ligne.'); return; } var reader=new FileReader(); reader.onload=function(e){ var dataUrl=e.target.result; var field=document.getElementById('e-videourl'); if(field)field.value=dataUrl; var preview=document.getElementById('e-video-preview'); if(preview){ preview.style.display=''; var vid=document.createElement('video'); vid.src=dataUrl;vid.controls=true; vid.style.cssText='max-height:120px;max-width:100%;border-radius:8px'; preview.innerHTML='';preview.appendChild(vid); } }; reader.readAsDataURL(file); }; window.clearImageField=function(){ var field=document.getElementById('e-imageurl'); if(field)field.value=''; var preview=document.getElementById('e-img-preview'); if(preview){preview.style.display='none';preview.innerHTML='';} }; // ════════════════════════════════════════ // MODALE IMAGE RPP // ════════════════════════════════════════ window.openRppModal=function(imgUrl,page){ if(!imgUrl)return; // Plus de modale interne (un cadre image vide quand l URL est cassee est trompeur). // On ouvre directement dans un nouvel onglet : si le fichier existe il s affiche, // si non le navigateur montre un 404 explicite. try{ window.open(imgUrl,'_blank','noopener'); }catch(_e){} }; window.closeRppModal=function(){ var modal=document.getElementById('rpp-img-modal'); if(modal){modal.style.display='none';modal.classList.add('hidden');} var img=document.getElementById('rpp-img-modal-img'); if(img)img.src=''; }; // Fermer avec Escape document.addEventListener('keydown',function(e){ if(e.key==='Escape')window.closeRppModal(); }); window.previewMedia=function(imgInputId,imgPreviewId,videoPreviewId){ var imgUrl=document.getElementById(imgInputId)?.value||''; var vidUrl=document.getElementById('e-videourl')?.value||''; var imgDiv=document.getElementById(imgPreviewId); var vidDiv=document.getElementById(videoPreviewId); var errImg='

Image non chargee - verifiez l URL

'; var errVid='

Video non chargee - verifiez l URL

'; if(imgDiv){ if(imgUrl){ imgDiv.style.display=''; var img=document.createElement('img'); img.src=imgUrl;img.alt='Apercu'; img.style.cssText='max-height:120px;max-width:100%;border-radius:8px;border:1px solid #e5e7eb;object-fit:contain'; img.onerror=function(){imgDiv.innerHTML=errImg;}; imgDiv.innerHTML='';imgDiv.appendChild(img); }else{imgDiv.style.display='none';imgDiv.innerHTML='';} } if(vidDiv){ if(vidUrl){ vidDiv.style.display=''; var vid=document.createElement('video'); vid.src=vidUrl;vid.controls=true; vid.style.cssText='max-height:120px;max-width:100%;border-radius:8px'; vid.onerror=function(){vidDiv.innerHTML=errVid;}; vidDiv.innerHTML='';vidDiv.appendChild(vid); }else{vidDiv.style.display='none';vidDiv.innerHTML='';} } }; window.selectBlank=function(blankNum){var slot=document.getElementById('blank-'+blankNum);if(!slot)return;if(fillBlanks[blankNum-1]!==null){document.querySelectorAll('.word-btn').forEach(function(b){if(parseInt(b.dataset.blankIndex)===blankNum){b.classList.remove('used');delete b.dataset.blankIndex;}});fillBlanks[blankNum-1]=null;slot.textContent='___';slot.classList.remove('filled');}document.querySelectorAll('.blank-slot').forEach(function(s){s.classList.remove('blank-selected');});selectedBlank=blankNum;slot.classList.add('blank-selected');var h=document.getElementById('blank-hint');if(h)h.textContent='Trou '+blankNum+' selectionne - choisissez un mot';}; window.selectWord=function(word,wordIdx){if(selectedBlank===null){var h=document.getElementById('blank-hint');if(h){h.textContent='Selectionnez dabord un trou !';h.style.color='#dc2626';setTimeout(function(){h.style.color='var(--edf-orange)';},1500);}return;}var bn=selectedBlank;var slot=document.getElementById('blank-'+bn);var btn=document.getElementById('word-'+wordIdx);if(!slot)return;if(btn&&btn.dataset.blankIndex){var ob=parseInt(btn.dataset.blankIndex);if(ob!==bn){fillBlanks[ob-1]=null;var os=document.getElementById('blank-'+ob);if(os){os.textContent='___';os.classList.remove('filled');}}}document.querySelectorAll('.word-btn').forEach(function(b){if(parseInt(b.dataset.blankIndex)===bn){b.classList.remove('used');delete b.dataset.blankIndex;}});fillBlanks[bn-1]=word;slot.textContent=word;slot.classList.remove('blank-selected');slot.classList.add('filled');if(btn){btn.classList.add('used');btn.dataset.blankIndex=bn;}selectedBlank=null;var h=document.getElementById('blank-hint');if(h){var rem=fillBlanks.filter(function(b){return b===null;}).length;h.textContent=rem===0?'Tous remplis - cliquez un trou pour modifier':rem+' trou(s) restant(s)';h.style.color='var(--edf-orange)';}}; window.clearBlank=function(blankNum){window.selectBlank(blankNum);}; window.selectCatItemByEl=function(el){const idx=parseInt(el.dataset.idx);const text=decodeURIComponent(el.dataset.text);selectCatItemFn(idx,text);}; window.selectCatItemFn=function(idx,text){document.querySelectorAll('.cat-item').forEach(el=>el.classList.remove('ring-sel'));document.getElementById('cat-item-'+idx)?.classList.add('ring-sel');selCatItem={idx,text};}; window.placeCat=function(catId){if(!selCatItem)return;const zone=document.getElementById('cat-zone-'+catId);const empty=document.getElementById('cat-empty-'+catId);if(empty)empty.style.display='none';const tag=document.createElement('div');tag.className='px-2 py-1 text-white rounded-lg text-xs font-bold flex items-center justify-between gap-1 mb-1';tag.style.background='var(--edf-blue)';tag.innerHTML=`${selCatItem.text}`;zone.appendChild(tag);document.getElementById('cat-item-'+selCatItem.idx).style.display='none';if(!catAnswers[catId])catAnswers[catId]=[];catAnswers[catId].push(selCatItem.idx);selCatItem=null;document.querySelectorAll('.cat-item').forEach(el=>el.classList.remove('ring-sel'));}; window.removeCatItem=function(btn,catId,itemIdx){btn.parentElement.remove();catAnswers[catId]=(catAnswers[catId]||[]).filter(i=>i!==itemIdx);document.getElementById('cat-item-'+itemIdx).style.display='';const zone=document.getElementById('cat-zone-'+catId);if(zone&&!zone.querySelector('div[style*="background"]')){{const e=document.getElementById('cat-empty-'+catId);if(e)e.style.display='';}};}; window.makeDecisionByEl=function(el){const si=parseInt(el.dataset.si);const i=parseInt(el.dataset.i);const text=decodeURIComponent(el.dataset.text);makeDecision(si,i,text);}; window.makeDecision=function(stepIdx,optIdx,optText){const game=lastTraineeGame;const step=(game.steps||[])[stepIdx];const opt=step.options[optIdx];decisionPath.push({step:stepIdx,choice:optIdx,text:optText});const pd=document.getElementById('dec-path');if(pd)pd.classList.remove('hidden');const pdd=document.getElementById('dec-path-disp');if(pdd)pdd.innerHTML=decisionPath.map((p,i)=>`
${i+1}${p.text}
`).join('');if(opt.nextStep!==undefined&&opt.nextStep!==null&&opt.nextStep!==99&&opt.nextStep<(game.steps||[]).length){document.getElementById('dec-cont').innerHTML=renderDecisionStep(game,opt.nextStep);}else{document.getElementById('dec-cont').innerHTML=`
✅ Arbre terminé — validez votre réponse
`;}}; async function validateAnswer(game){const btn=document.getElementById('validate-btn');if(btn.disabled)return;btn.disabled=true;btn.textContent='⏳ Envoi...';btn.style.background='#9ca3af';const elapsed=Math.round((Date.now()-questionStartTime)/1000);let answer=null,isCorrect=false;if(['quiz','true-false','find-intruder','scenario'].includes(game.type)){answer=selectedAns;isCorrect=(selectedAns===parseInt(game.correctAnswer));}else if(game.type==='multiple-select'){const ns=(a,b)=>a-b;answer=[...selectedMulti].sort(ns);const cn=(game.correctAnswers||[]).map(Number).sort(ns);isCorrect=JSON.stringify(answer)===JSON.stringify(cn);}else if(['sequence','ranking'].includes(game.type)){answer=game.type==='ranking'?selectedRank:selectedSeq;isCorrect=JSON.stringify(answer)===JSON.stringify(game.correctOrder);}else if(game.type==='matching'){answer=matchingPairs;const total=(game.pairsLeft||[]).length;isCorrect=matchingPairs.length===total&&matchingPairs.every(p=>p.left===p.right);}else if(game.type==='fill-blank'){answer=[...fillBlanks];isCorrect=JSON.stringify(answer)===JSON.stringify(game.correctBlanks);}else if(game.type==='categories'){const items=game.itemsShuffled||game.items||[];answer=catAnswers;let allOk=true;items.forEach((item,i)=>{if(!(catAnswers[item.category]||[]).includes(i))allOk=false;});isCorrect=allOk;}else if(game.type==='decision'){answer=decisionPath.map(p=>p.choice);isCorrect=JSON.stringify(answer)===JSON.stringify(game.correctPath);}try{await DB.rpcUpdateTeam(teamDbId,{has_answered:true,answer,is_correct:isCorrect,answer_time:elapsed});showScreenSafe('trainee-waiting-others');}catch(err){btn.disabled=false;btn.textContent='✓ Valider ma réponse';btn.style.background='';showAlert('Erreur. Réessayez.');}} async function showTraineeResult(withExplanation){ if(typeof withExplanation==='undefined')withExplanation=true; showScreenSafe('trainee-result'); const team=await DB.getById('teams',teamDbId);if(!team)return; const pts=team.last_points||0,bonus=team.last_speed_bonus||0; const icon=document.getElementById('tr-icon'),title=document.getElementById('tr-title'),earn=document.getElementById('tr-pts'),bText=document.getElementById('tr-bonus'),sBox=document.getElementById('tr-score-box'); if(!team.has_answered||team.answer===null||team.answer===undefined){icon.textContent='⏰';title.textContent='Temps écoulé !';title.style.color='#6b7280';sBox.style.background='#9ca3af';earn.textContent='0';bText.textContent='';} else if(pts>0){icon.textContent='✓';title.textContent='Bonne réponse !';title.style.color='#16a34a';sBox.style.background='var(--edf-blue)';earn.textContent='+'+pts;bText.textContent=bonus>0?`⚡ Bonus vitesse : +${bonus} pts`:'';} else{icon.textContent='✗';title.textContent='Mauvaise réponse';title.style.color='#dc2626';sBox.style.background='#dc2626';earn.textContent='0';bText.textContent='';} document.getElementById('tr-total').textContent=(team.score||0)+' pts'; // Explication et bouton RPP — controles par phase const game=lastTraineeGame; const explBox=document.getElementById('tr-expl-box'); const explTxt=document.getElementById('tr-expl-text'); const waitExp=document.getElementById('tr-wait-expl'); const trRppBtn=document.getElementById('tr-rpp-btn'); if(withExplanation){ if(waitExp)waitExp.classList.add('hidden'); if(explBox&&game?.explanation){ explBox.classList.remove('hidden'); if(explTxt)explTxt.textContent=game.explanation; }else if(explBox){ explBox.classList.add('hidden'); } if(trRppBtn){ if(game?.pdfSource && game?.pdfPage){ trRppBtn.style.display='';trRppBtn.classList.remove('hidden'); trRppBtn.onclick=function(){window.openPdfRef(game.pdfSource, game.pdfPage);}; trRppBtn.textContent='📄 Voir la page '+game.pdfPage; }else if(game?.imageUrl){ trRppBtn.style.display='';trRppBtn.classList.remove('hidden'); trRppBtn.onclick=function(){window.openRppModal(game.imageUrl, game.pdfPage||'');}; trRppBtn.textContent='📄 Voir la page'; }else{trRppBtn.classList.add('hidden');} } }else{ // Phase answer_revealed : reponse visible (icone + score), explication cachee if(explBox)explBox.classList.add('hidden'); if(trRppBtn)trRppBtn.classList.add('hidden'); if(waitExp)waitExp.classList.remove('hidden'); } } document.getElementById('admin-btn').addEventListener('click',()=>{showScreenSafe('admin-panel');loadSettings();initModeUI();renderAdminQ();}); document.getElementById('close-admin-btn').addEventListener('click',()=>showScreenSafe('supervisor-dashboard')); document.getElementById('back-to-game-btn').addEventListener('click',()=>showScreenSafe('supervisor-dashboard')); document.getElementById('reset-btn').addEventListener('click',()=>showConfirmDialog('Réinitialiser ?',async()=>{REALTIME.unsubscribeAll();if(sessionId)try{await DB.rpcUpdateSession(sessionId,{status:'finished',game_status:'finished',finished_at:new Date().toISOString()});}catch(_e){}SESSION.clear('rpp_challenge');location.reload();})); document.querySelectorAll('.tab-btn').forEach(btn=>{btn.addEventListener('click',()=>{const tab=btn.id.replace('tab-','');document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.querySelectorAll('[id^="content-"]').forEach(c=>c.classList.add('hidden'));btn.classList.add('active');document.getElementById('content-'+tab)?.classList.remove('hidden');if(tab==='mode'||tab==='questions'){initModeUI();if(tab==='questions'){loadPdfRefs().then(renderAdminQ);}}if(tab==='logs')refreshLogsDisplay();});}); function renderAdminQ(){const mF=document.getElementById('q-filter-module')?.value||'all';const tF=document.getElementById('q-filter-type')?.value||'all';const srch=(document.getElementById('q-search')?.value||'').toLowerCase();const dF=document.getElementById('q-filter-diff')?.value||'all';const qF=document.getElementById('q-filter-qtype')?.value||'all';let all=[];MODULES_RPP.forEach(mod=>{mod.games.forEach(g=>{all.push({...g,_mT:mod.title,_mI:mod.icon,_mX:MODULES_RPP.indexOf(mod)});});});if(mF!=='all')all=all.filter(g=>g._moduleId===mF);if(tF!=='all')all=all.filter(g=>(g.tags||[]).includes(tF));if(srch)all=all.filter(g=>g.question.toLowerCase().includes(srch));if(dF!=='all')all=all.filter(g=>String(g.difficulty)===dF);if(qF!=='all')all=all.filter(g=>g.type===qF);const stats=document.getElementById('q-stats');const totalAll=MODULES_RPP.reduce((s,m)=>s+m.games.length,0); const totalValidated=MODULES_RPP.reduce((s,m)=>s+m.games.filter(g=>g.isValidated).length,0); if(stats)stats.innerHTML=`${all.length} question(s) affichée(s) · ✅ ${totalValidated} validées · ⬜ ${totalAll-totalValidated} à valider · Total: ${totalAll}`;const tc={'quiz':'bg-blue-100 text-blue-800','true-false':'bg-green-100 text-green-800','sequence':'bg-yellow-100 text-yellow-800','matching':'bg-purple-100 text-purple-800','find-intruder':'bg-pink-100 text-pink-800','multiple-select':'bg-indigo-100 text-indigo-800','scenario':'bg-orange-100 text-orange-800','ranking':'bg-teal-100 text-teal-800','fill-blank':'bg-rose-100 text-rose-800','categories':'bg-amber-100 text-amber-800','decision':'bg-sky-100 text-sky-800'};document.getElementById('admin-q-container').innerHTML=all.map(g=>{const mI=g._mX;const qI=MODULES_RPP[mI]?.games.findIndex(q=>q.id===g.id);const mi=g.imageUrl?'🖼️':g.videoUrl?'🎬':''; const vBadge=g.isValidated ?'✅ Validée' :'En attente'; return`
${g._mI}${getTypeLabel(g.type)}${vBadge}${renderTags(g.tags||[])}Ch.${g.chapitre||'?'}${mi?`${mi}`:''}${g.question}
`;}).join('');} window.duplicateQ=async function(bIdx,gIdx){const orig=MODULES_RPP[bIdx].games[gIdx];const dup={...JSON.parse(JSON.stringify(orig)),id:'Q_'+UTILS.uuid()};MODULES_RPP[bIdx].games.splice(gIdx+1,0,dup);await saveQuestionToDB(dup);renderAdminQ();showAlert('✅ Question dupliquée !');}; window.deleteQ=function(bIdx,gIdx){showConfirmDialog('Supprimer ?',async()=>{const game=MODULES_RPP[bIdx].games[gIdx];await DB.delete('questions',game.id);MODULES_RPP[bIdx].games.splice(gIdx,1);renderAdminQ();showAlert('✅ Supprimée !');});}; window.openEditModal=function(bIdx,gIdx){editingBoxIdx=bIdx;editingQIdx=gIdx;let game=bIdx>=0&&gIdx>=0?MODULES_RPP[bIdx].games[gIdx]:{type:'quiz',question:'Nouvelle question ?',chapitre:'01',tags:[],metier:['terrain'],difficulty:1,options:['Option A','Option B','Option C','Option D'],correctAnswer:0,explanation:'',imageUrl:null,videoUrl:null,_moduleId:MODULES_RPP[0]?.id||''};const mO=MODULES_RPP.map(m=>``).join('');let html=`
`;html+=`
`;html+=`
${['PRESCRIT','INTERDIT','RÈGLE VITALE'].map(t=>``).join('')}
`;html+=`
${['terrain','electricite','mecanique','nucleaire','hydro'].map(m=>``).join('')}
`;if(game.scenario!==undefined||game.situation!==undefined)html+=`
`;html+=`
`;if(game.options){html+=`
`;game.options.forEach((opt,i)=>{const isC=Array.isArray(game.correctAnswers)?game.correctAnswers.includes(i):game.correctAnswer===i;html+=`
`;});html+='
';}if(game.items&&!game.categories)html+=`
${game.items.map((item,i)=>`
${i+1}
`).join('')}
`;if(game.pairs)html+=`
${game.pairs.map((pair,i)=>`
→
`).join('')}
`;if(game.sentence!==undefined)html+=`
`;html+=`
${game.imageUrl?`
Apercu
`:''}
`; html+=`
${game.videoUrl?``:''}
`;html+=`

Le stagiaire pourra ouvrir ce document a la page indiquee depuis l explication

`; // Question ephemere (uniquement si on est en session active ET nouvelle question) const _isNewQ = (bIdx<0 || gIdx<0); const _canEphemeral = _isNewQ && !!sessionId; if(_canEphemeral){ html+=`

Sera visible seulement dans la session courante puis supprimée à la fin. Idéal pour une mise en situation ponctuelle.

`; } html+=`

Cochez pour marquer cette question comme prête

`; html+=`
`; document.getElementById('modal-edit-content').innerHTML=html;document.getElementById('q-edit-modal').classList.remove('hidden');}; window.closeEditModal=function(){document.getElementById('q-edit-modal').classList.add('hidden');editingBoxIdx=-1;editingQIdx=-1;}; window.saveQEdit=async function(){let bIdx=editingBoxIdx,gIdx=editingQIdx;const nMId=document.getElementById('e-module').value;const nMX=MODULES_RPP.findIndex(m=>m.id===nMId);const isNew=bIdx<0||gIdx<0;const tags=[];['PRESCRIT','INTERDIT','RÈGLE VITALE'].forEach(t=>{if(document.getElementById('e-tag-'+t.replace(/ /g,'-'))?.checked)tags.push(t);});const metier=[];['terrain','electricite','mecanique','nucleaire','hydro'].forEach(m=>{if(document.getElementById('e-met-'+m)?.checked)metier.push(m);});let game=isNew?{id:'Q_'+UTILS.uuid(),type:document.getElementById('e-type')?.value||'quiz'}:{...MODULES_RPP[bIdx].games[gIdx]};game.question=document.getElementById('e-question')?.value||game.question;game.chapitre=document.getElementById('e-chap')?.value||game.chapitre;game.difficulty=parseInt(document.getElementById('e-diff')?.value)||game.difficulty;game.tags=tags;game.metier=metier; game.isValidated=document.getElementById('e-validated')?.checked||false; game.pdfSource=document.getElementById('e-pdfsource')?.value||null; game.pdfPage=parseInt(document.getElementById('e-pdfpage')?.value)||null; // Ephemeral (nouvelle question + session active) if(isNew && sessionId && document.getElementById('e-ephemeral')?.checked){ game.isEphemeral=true; game.ephemeralSessionId=sessionId; } game.explanation=document.getElementById('e-expl')?.value||'';game.imageUrl=document.getElementById('e-imageurl')?.value||null;game.videoUrl=document.getElementById('e-videourl')?.value||null;game._moduleId=nMId;const sE=document.getElementById('e-scenario');if(sE){game.scenario=sE.value;game.situation=sE.value;}if(game.options){game.options.forEach((_,i)=>{const el=document.getElementById('opt-'+i);if(el)game.options[i]=el.value;});if(Array.isArray(game.correctAnswers)){game.correctAnswers=[];document.querySelectorAll('input[name="correct"]:checked').forEach(cb=>game.correctAnswers.push(parseInt(cb.value)));}else{const ck=document.querySelector('input[name="correct"]:checked');if(ck)game.correctAnswer=parseInt(ck.value);}}if(game.items&&!game.categories)game.items.forEach((_,i)=>{const el=document.getElementById('item-'+i);if(el&&typeof game.items[i]==='string')game.items[i]=el.value;});if(game.pairs)game.pairs.forEach((_,i)=>{const l=document.getElementById('pair-l-'+i),r=document.getElementById('pair-r-'+i);if(l)game.pairs[i].left=l.value;if(r)game.pairs[i].right=r.value;});if(game.sentence!==undefined){const sE2=document.getElementById('e-sentence'),bE=document.getElementById('e-blanks'),wE=document.getElementById('e-wordbank');if(sE2)game.sentence=sE2.value;if(bE)game.correctBlanks=bE.value.split('|').map(s=>s.trim());if(wE)game.wordBank=wE.value.split('|').map(s=>s.trim());}await saveQuestionToDB(game);if(!isNew){MODULES_RPP[bIdx].games[gIdx]=game;if(nMX>=0&&nMX!==bIdx){MODULES_RPP[nMX].games.push(game);MODULES_RPP[bIdx].games.splice(gIdx,1);}}else{if(nMX>=0)MODULES_RPP[nMX].games.push(game);}closeEditModal();renderAdminQ();showAlert(isNew?'✅ Question créée !':'✅ Question modifiée !');}; // Charge les questions ephemeres d une session (cas autoRestore d une session active) async function loadEphemeralQuestionsForSession(sid){ if(!sid) return; try{ const[questions,opts,itms,pairs,cats,catitms,steps]=await Promise.all([ DB.query('questions',q=>q.eq('app_id','challenge_cup_edf').eq('is_ephemeral',true).eq('ephemeral_session_id',sid)), DB.get('question_options'),DB.get('question_items'),DB.get('question_pairs'), DB.get('question_categories'),DB.get('question_category_items'),DB.get('question_decision_steps') ]); if(!questions || !questions.length) return; questions.forEach(q=>{ const mod = MODULES_RPP.find(m=>m.id===q.module_id); if(!mod) return; const game = transformQuestion(q,opts||[],itms||[],pairs||[],cats||[],catitms||[],steps||[]); if(!mod.games.find(g=>g.id===game.id)) mod.games.push(game); }); addLog('info','DB',`${questions.length} question(s) éphémère(s) restaurée(s)`); }catch(e){ console.warn('[loadEphemeralQuestionsForSession]',e); } } async function saveQuestionToDB(game){ const payload={ question:{ id:game.id, module_id:game._moduleId, type:game.type, title:game.title||'', chapitre:game.chapitre||'', tags:game.tags||[], metier:game.metier||[], difficulty:game.difficulty||1, question:game.question, scenario:game.scenario||null, situation:game.situation||null, explanation:game.explanation||'', image_key:game.imageUrl||null, image_url:game.imageUrl||null, video_url:game.videoUrl||null, correct_answer:game.correctAnswer!==undefined?game.correctAnswer:null, correct_answers:game.correctAnswers||null, correct_order:game.correctOrder||null, correct_blanks:game.correctBlanks||null, correct_path:game.correctPath||null, word_bank:game.wordBank||null, sentence:game.sentence||null, is_active:true, is_validated:game.isValidated||false, pdf_page:game.pdfPage||null, app_id:'challenge_cup_edf', is_ephemeral: !!game.isEphemeral, ephemeral_session_id: game.ephemeralSessionId||null } }; if(game.options) payload.options = game.options.map((o,i)=>({text:o,index:i})); if(game.items && !game.categories) payload.items = game.items.map((item,i)=>({text:typeof item==='string'?item:item.text,index:i})); if(game.pairs) payload.pairs = game.pairs.map((p,i)=>({left:p.left,right:p.right,index:i})); const{error}=await sb.rpc('save_question',{p_payload:payload}); if(error)console.error('Erreur save:',error); } function loadSettings(){const saved=localStorage.getItem('rppAdminSettings');if(!saved)return;const s=JSON.parse(saved);QUESTION_TIME=s.qt||30;POINTS_PER_ANS=s.pts||150;SPEED_BONUS=s.bonus||100;QPB=s.qpb||8;['s-time','s-pts','s-bonus'].forEach((id,i)=>{const el=document.getElementById(id);if(el)el.value=[QUESTION_TIME,POINTS_PER_ANS,SPEED_BONUS][i];});} // ── WATCHDOG : déblocage automatique si bloqué ── setInterval(()=>{ // Si _correctionInProgress bloqué > 15s sans afficher la correction → reset const corrScreen=document.getElementById('proj-correction'); const questScreen=document.getElementById('proj-question'); if(_correctionInProgress){ const isOnCorr=corrScreen&&!corrScreen.classList.contains('hidden'); const isOnQuest=questScreen&&!questScreen.classList.contains('hidden'); if(!isOnCorr&&!isOnQuest){ // On n'est ni sur question ni sur correction → bloqué _correctionInProgress=false; const btn=document.getElementById('next-q-btn'); if(btn&&btn.disabled){btn.disabled=false;btn.textContent='Question Suivante →';} addLog('warn','WATCHDOG','_correctionInProgress reseté automatiquement'); } } // Si btn bloqué sur "⏳ Chargement..." → reset const btn=document.getElementById('next-q-btn'); if(btn&&btn.disabled&&btn.textContent==='⏳ Chargement...'){ btn.disabled=false;btn.textContent='Question Suivante →'; addLog('warn','WATCHDOG','next-q-btn débloqué automatiquement'); } },10000); document.getElementById('save-settings-btn').addEventListener('click',()=>{QUESTION_TIME=parseInt(document.getElementById('s-time').value);POINTS_PER_ANS=parseInt(document.getElementById('s-pts').value);SPEED_BONUS=parseInt(document.getElementById('s-bonus').value);localStorage.setItem('rppAdminSettings',JSON.stringify({qt:QUESTION_TIME,pts:POINTS_PER_ANS,bonus:SPEED_BONUS,qpb:QPB}));showAlert('✅ Paramètres sauvegardés !');}); document.addEventListener('DOMContentLoaded',()=>setTimeout(initModeUI,100)); MIBsoft
Gérer mon consentement