Files
Puechberty Arthur a72f502342 first commit
2026-03-30 20:42:29 +02:00

350 lines
16 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="tabs">
<button class="tab-button" id="tab-btn-all">Toutes</button>
<button class="tab-button" id="tab-btn-random">Aléatoire</button>
<button class="tab-button" id="tab-btn-manage">Gérer</button>
</div>
<!-- Onglet : Toutes (liste complète) -->
<div id="tab-all">
<div class="controls">
<button class="small-btn btn" id="reset-all">Réinitialiser</button>
<label style="margin-left:1rem;display:flex;align-items:center;gap:.4rem;">
<input type="checkbox" id="all-randomize"> Aléatoire
</label>
<span id="all-score" style="margin-left:1rem;color:#333"></span>
<div style="margin-left:auto;color:#666" id="all-count"></div>
</div>
<form id="qcm-form">
{% for q in questions %}
<div class="question" data-qid="{{ q.id }}">
<div><strong>Question {{ loop.index }}:</strong> {{ q.text | safe }}</div>
<div class="answers">
{% for a in q.answers %}
<label class="answer" data-idx="{{ a.idx }}">
<input type="radio" name="q_{{ q.id }}" value="{{ a.idx }}" data-correct="{% if a.correct %}1{% else %}0{% endif %}"> {{ a.text }}
</label>
{% endfor %}
</div>
<div class="feedback" style="margin-top:.5rem"></div>
</div>
{% endfor %}
</form>
</div>
<!-- Onglet : Aléatoire -->
<div id="tab-random" style="display:none">
<div class="controls" style="margin-bottom:.5rem">
<button class="small-btn btn" id="random-next">Nouvelle question</button>
<button class="small-btn btn" id="random-reset">Reset score</button>
<span id="random-score" style="margin-left:1rem"></span>
</div>
<div id="random-area"></div>
</div>
<!-- Onglet : Gérer -->
<div id="tab-manage" style="display:none">
<div style="margin-bottom:.5rem">Choisissez les questions autorisées</div>
<div id="manage-list">
{% for q in questions %}
<div class="manage-item">
<label>
<input type="checkbox" class="manage-allow" data-qid="{{ q.id }}"> Question {{ loop.index }}
</label>
<div style="margin-left:1.2rem">{{ q.text | safe }}</div>
<div style="margin-left:1.2rem;color:#666">
Dernière actualisation: {{
(
q.last_scraped[:10].split('-')[2] ~ '/' ~
q.last_scraped[:10].split('-')[1] ~ '/' ~
q.last_scraped[:10].split('-')[0] ~
' - ' ~
"%02d"|format((q.last_scraped[11:13] | int + 1) % 24) ~
'h' ~
q.last_scraped[14:16]
) or 'inconnu'
}}
</div>
<div style="margin-left:1.2rem;margin-top:.4rem">
<strong>Réponses :</strong>
<ul style="margin-top:.25rem">
{% for a in q.answers %}
<li style="margin:.15rem 0;">
<span {% if a.correct %} style="color:green;font-weight:bold" {% endif %}>{{ a.text }}</span>
{% if a.correct %} <strong>(bonne)</strong> {% endif %}
</li>
{% endfor %}
</ul>
</div>
</div>
{% endfor %}
</div>
</div>
<script id="all-questions" type="application/json">{{ questions_json | safe }}</script>
<script>
// embed data for client-side random selection (JSON fourni côté serveur)
const ALL_QUESTIONS = JSON.parse(document.getElementById('all-questions').textContent);
document.addEventListener('DOMContentLoaded', function(){
// tab switching
function showTab(id){
document.getElementById('tab-all').style.display = id==='all' ? '' : 'none';
document.getElementById('tab-random').style.display = id==='random' ? '' : 'none';
document.getElementById('tab-manage').style.display = id==='manage' ? '' : 'none';
// re-typeset MathJax for the shown tab (if needed)
try{
if(window.MathJax && window.MathJax.typesetPromise){
const el = id==='all' ? document.getElementById('tab-all') : (id==='random' ? document.getElementById('tab-random') : document.getElementById('tab-manage'));
if(el) window.MathJax.typesetPromise([el]);
}
}catch(e){ /* ignore */ }
// toggle active class on tab buttons
document.querySelectorAll('.tab-button').forEach(function(b){ b.classList.remove('active'); });
if(id === 'all') document.getElementById('tab-btn-all').classList.add('active');
if(id === 'random') document.getElementById('tab-btn-random').classList.add('active');
if(id === 'manage') document.getElementById('tab-btn-manage').classList.add('active');
}
document.getElementById('tab-btn-all').addEventListener('click', ()=> { showTab('all'); localStorage.setItem('q_last_tab','all'); });
document.getElementById('tab-btn-random').addEventListener('click', ()=> { showTab('random'); localStorage.setItem('q_last_tab','random'); const q = pickRandomQuestion(); renderQuestionInto(document.getElementById('random-area'), q); });
document.getElementById('tab-btn-manage').addEventListener('click', ()=> { showTab('manage'); localStorage.setItem('q_last_tab','manage'); });
// restore allowed ids from localStorage or default to all
function getAllowed(){
let raw = localStorage.getItem('q_allowed_ids');
if(!raw) return ALL_QUESTIONS.map(q=>q.id);
try{ return JSON.parse(raw); }catch(e){ return ALL_QUESTIONS.map(q=>q.id); }
}
function setAllowed(arr){ localStorage.setItem('q_allowed_ids', JSON.stringify(arr)); }
// init manage checkboxes
const allowed = new Set(getAllowed());
document.querySelectorAll('.manage-allow').forEach(function(cb){
let id = Number(cb.getAttribute('data-qid'));
cb.checked = allowed.has(id);
cb.addEventListener('change', function(){
let cur = new Set(getAllowed());
if(cb.checked) cur.add(id); else cur.delete(id);
setAllowed(Array.from(cur));
// apply allowed set to the 'Toutes' tab and reapply ordering
applyAllowedToAllTab();
applyAllOrderSetting();
// if random area displays a question that is no longer allowed, clear it
const randArea = document.getElementById('random-area');
if(randArea){
const qEl = randArea.querySelector('.question');
if(qEl && !cur.has(Number(qEl.getAttribute('data-qid')))) randArea.innerHTML = '<div>Aucune question disponible.</div>';
}
});
});
// apply allowed set to the 'Toutes' tab (hide questions not allowed)
function applyAllowedToAllTab(){
const allowedIds = new Set(getAllowed());
const container = document.querySelector('#tab-all form');
if(!container) return;
const qs = Array.from(container.querySelectorAll('.question'));
qs.forEach(function(q){
const qid = Number(q.getAttribute('data-qid'));
if(allowedIds.has(qid)) q.style.display = '';
else q.style.display = 'none';
});
const visible = qs.filter(q => q.style.display !== 'none');
document.getElementById('all-count').textContent = `Questions: ${visible.length}`;
// if random area displays a question that is no longer allowed, clear it
const randArea = document.getElementById('random-area');
if(randArea){
const qEl = randArea.querySelector('.question');
if(qEl && !allowedIds.has(Number(qEl.getAttribute('data-qid')))) randArea.innerHTML = '<div>Aucune question disponible.</div>';
}
}
// random question logic
function pickRandomQuestion(){
const allowedIds = new Set(getAllowed());
const pool = ALL_QUESTIONS.filter(q => allowedIds.has(q.id));
if(pool.length === 0) return null;
return pool[Math.floor(Math.random()*pool.length)];
}
function renderQuestionInto(container, q){
if(!q){ container.innerHTML = '<div>Aucune question disponible.</div>'; return; }
let html = `<div class="question" data-qid="${q.id}">`;
html += `<div><strong>Question:</strong> ${q.text}</div>`;
html += '<div class="answers">';
// shuffle answers for this question
const answersShuffled = q.answers.slice().sort(()=>Math.random()-0.5);
answersShuffled.forEach(function(a){
html += `<label class="answer"><input type="radio" name="rand_${q.id}" value="${a.idx}" data-correct="${a.correct?1:0}"> ${a.text}</label>`;
});
html += '</div><div class="feedback" style="margin-top:.5rem"></div></div>';
container.innerHTML = html;
// render LaTeX in the injected content
try{
if(window.MathJax && window.MathJax.typesetPromise){
window.MathJax.typesetPromise([container]);
} else if(window.MathJax && window.MathJax.typeset){
window.MathJax.typeset([container]);
}
}catch(e){ /* ignore MathJax errors */ }
// attach immediate feedback handler
container.querySelectorAll('input[type=radio]').forEach(function(radio){
radio.addEventListener('change', function(e){
const input = e.target;
const qDiv = input.closest('.question');
const feedback = qDiv.querySelector('.feedback');
const isCorrect = input.getAttribute('data-correct') === '1';
qDiv.querySelectorAll('input').forEach(i=>i.disabled=true);
if(isCorrect) feedback.innerHTML = '<span style="color:green;font-weight:bold">Bonne réponse</span>';
else {
feedback.innerHTML = '<span style="color:red;font-weight:bold">Mauvaise réponse</span>';
qDiv.querySelectorAll('input').forEach(function(i){ if(i.getAttribute('data-correct')==='1') i.closest('label').style.outline='2px solid green'; });
}
input.closest('label').style.background = isCorrect? '#ddffdd' : '#ffdddd';
// update score display
updateRandomScore(isCorrect);
});
});
}
function updateRandomScore(lastCorrect){
let s = localStorage.getItem('q_random_score');
let st = s ? JSON.parse(s) : {correct:0,total:0};
if(typeof lastCorrect === 'boolean'){ st.total += 1; if(lastCorrect) st.correct +=1; }
localStorage.setItem('q_random_score', JSON.stringify(st));
document.getElementById('random-score').textContent = `Score: ${st.correct} / ${st.total}`;
}
document.getElementById('random-next').addEventListener('click', function(){
const q = pickRandomQuestion();
renderQuestionInto(document.getElementById('random-area'), q);
});
document.getElementById('random-reset').addEventListener('click', function(){
localStorage.removeItem('q_random_score'); updateRandomScore();
});
// init score for random tab
updateRandomScore();
// --- All-tab score handling ---
// --- All-tab score handling ---
function updateAllScore(lastCorrect){
let s = localStorage.getItem('q_all_score');
let st = s ? JSON.parse(s) : {correct:0,total:0};
if(typeof lastCorrect === 'boolean'){ st.total += 1; if(lastCorrect) st.correct +=1; }
localStorage.setItem('q_all_score', JSON.stringify(st));
const el = document.getElementById('all-score');
if(el) el.textContent = `Score: ${st.correct} / ${st.total}`;
}
// ensure all answers are displayed in random order for each question
function shuffleAnswersForQuestion(qElem){
if(!qElem) return;
const answers = qElem.querySelector('.answers');
if(!answers) return;
const labs = Array.from(answers.querySelectorAll('label'));
const shuffled = labs.sort(()=>Math.random()-0.5);
shuffled.forEach(l => answers.appendChild(l));
}
// maintain initial order of question elements but always randomize answers inside each question
const allContainer = document.querySelector('#tab-all form');
const initialQs = allContainer ? Array.from(allContainer.querySelectorAll('.question')) : [];
// shuffle answers initially for every question
initialQs.forEach(q => shuffleAnswersForQuestion(q));
function shuffleAllQuestions(){
if(!allContainer) return;
const qs = Array.from(allContainer.querySelectorAll('.question'));
const visible = qs.filter(q => q.style.display !== 'none');
// remove visible nodes and re-append them shuffled so hidden ones stay in place
visible.forEach(q => allContainer.removeChild(q));
const shuffled = visible.sort(()=>Math.random()-0.5);
shuffled.forEach(q=> allContainer.appendChild(q));
// always randomize answers too
shuffled.forEach(q => shuffleAnswersForQuestion(q));
document.getElementById('all-count').textContent = `Questions: ${shuffled.length}`;
}
function restoreInitialOrder(){
if(!allContainer) return;
// remove currently visible questions first
const qs = Array.from(allContainer.querySelectorAll('.question'));
const visible = qs.filter(q => q.style.display !== 'none');
visible.forEach(q => allContainer.removeChild(q));
// append initial questions but only those allowed (visible)
const allowedIds = new Set(getAllowed());
const toAppend = initialQs.filter(q => allowedIds.has(Number(q.getAttribute('data-qid'))));
toAppend.forEach(q => allContainer.appendChild(q));
// still randomize answers for each appended question
toAppend.forEach(q => shuffleAnswersForQuestion(q));
document.getElementById('all-count').textContent = `Questions: ${toAppend.length}`;
}
function applyAllOrderSetting(){
const enabled = localStorage.getItem('q_all_random') === '1';
if(enabled) shuffleAllQuestions(); else restoreInitialOrder();
}
// checkbox for toggling random display of question order (answers remain random)
const allRandCb = document.getElementById('all-randomize');
if(allRandCb){
allRandCb.checked = localStorage.getItem('q_all_random') === '1';
allRandCb.addEventListener('change', function(){
localStorage.setItem('q_all_random', allRandCb.checked ? '1' : '0');
applyAllOrderSetting();
});
}
// reset stored all-tab score on page load so refresh clears the score
localStorage.removeItem('q_all_score');
// init reset on all tab
document.getElementById('reset-all').addEventListener('click', function(){
document.querySelectorAll('#tab-all .question').forEach(function(q){ q.querySelectorAll('input').forEach(function(i){ i.disabled=false; i.checked=false; }); q.querySelector('.feedback').innerHTML=''; q.querySelectorAll('label').forEach(function(l){ l.style.background=''; l.style.outline=''; });
// re-randomize answers for this question
shuffleAnswersForQuestion(q);
});
// clear stored score for all tab
localStorage.removeItem('q_all_score'); updateAllScore();
// apply order setting (shuffle or restore)
applyAllOrderSetting();
});
// also attach immediate feedback for the all-tab radios and update the all score
document.querySelectorAll('#tab-all input[type=radio]').forEach(function(radio){
radio.addEventListener('change', function(e){
var input = e.target;
var qDiv = input.closest('.question');
var feedback = qDiv.querySelector('.feedback');
var isCorrect = input.getAttribute('data-correct') === '1';
qDiv.querySelectorAll('input').forEach(function(i){ i.disabled = true; });
if(isCorrect){ feedback.innerHTML = '<span style="color:green;font-weight:bold">Bonne réponse</span>'; }
else { feedback.innerHTML = '<span style="color:red;font-weight:bold">Mauvaise réponse</span>'; qDiv.querySelectorAll('input').forEach(function(i){ if(i.getAttribute('data-correct') === '1'){ var lab = i.closest('label'); if(lab) lab.style.outline = '2px solid green'; } }); }
var chosenLabel = input.closest('label'); if(chosenLabel){ chosenLabel.style.background = isCorrect ? '#ddffdd' : '#ffdddd'; }
updateAllScore(isCorrect);
});
});
// initial apply according to allowed/setting and init all score display
applyAllowedToAllTab();
applyAllOrderSetting();
updateAllScore();
// restore last tab from localStorage (or show all)
const lastTab = localStorage.getItem('q_last_tab') || 'all';
showTab(lastTab);
// if last tab was random, show a question immediately
if(lastTab === 'random'){
const q0 = pickRandomQuestion();
renderQuestionInto(document.getElementById('random-area'), q0);
}
});
</script>
<!-- script de feedback et reset global intégré dans le premier bloc; script dupliqué supprimé -->
{% endblock %}