Ajoute la correction sur fichier aux exercices BioCID et FDS

Reprend le fonctionnement du TP Nuisibles (« Corriger » à partir du PDF
généré avec les réponses du groupe) pour les deux exercices.

- BioCID et FDS : carte « Identification du groupe » (groupe, date,
  formateur) sur l'écran d'intro, reprise dans l'en-tête du PDF exporté.
- FDS : ajout d'un export PDF (inexistant) récapitulant réponse du groupe
  et réponse attendue, partie par partie, avec le score global.
- Bouton « Corriger un rendu » sur l'écran final des deux exercices.
- Nouvelle page correction-biocid.html : lecture du PDF, réponses
  regroupées par exercice avec badge renseigné / sans réponse.
- Nouvelle page correction-fds.html : notation automatique du QCM
  (anneau de réussite, score par partie, ✓/✗/? par question).
This commit is contained in:
2026-07-15 23:07:45 +02:00
parent c1bd61c10c
commit a418558393
4 changed files with 1298 additions and 4 deletions

View File

@@ -336,6 +336,40 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
.scroll-area::-webkit-scrollbar { width: 4px; }
.scroll-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
/* IDENTITY CARD */
.identity-card {
background: white;
border: 0.5px solid var(--border);
border-radius: 14px;
padding: 16px 18px;
max-width: 560px;
width: 100%;
margin: 0.5rem 0;
}
.identity-card .identity-title {
font-family: 'Syne', sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--amber-dark);
margin-bottom: 10px;
display: flex; align-items: center; gap: 6px;
}
.identity-fields { display: flex; flex-direction: column; gap: 8px; }
.identity-fields input {
font-family: 'DM Sans', sans-serif;
font-size: 13.5px;
color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 9px 13px;
width: 100%;
box-sizing: border-box;
}
.identity-fields input:focus { outline: none; border-color: var(--amber); background: white; }
/* DECO */
.deco-circle { position: fixed; border-radius: 50%; pointer-events: none; z-index: 0; }
.slide > * { position: relative; z-index: 1; }
@@ -413,6 +447,14 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
<span>⏱️</span>
<div>Durée ≈ 30 min. Pour chaque question, sélectionnez la <strong>zone du document</strong> où trouver l'information, puis vérifiez votre score.</div>
</div>
<div class="identity-card">
<div class="identity-title">👥 Identification du groupe</div>
<div class="identity-fields">
<input type="text" id="idGroupe" placeholder="Nom du groupe / stagiaires (ex : Groupe 3 — Dupont / Martin)" autocomplete="off">
<input type="date" id="idDate">
<input type="text" id="idFormateur" placeholder="Formateur (facultatif)" autocomplete="off">
</div>
</div>
<button class="btn btn-amber" onclick="goTo(2)" style="margin-top:1.5rem;">Commencer la partie 1 →</button>
</div>
@@ -523,7 +565,11 @@ select:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px
<li>Les trois documents sont <strong>complémentaires</strong>, jamais redondants — chacun cible un besoin et un public différents.</li>
</ul>
<div class="actions-row">
<button class="btn btn-amber" onclick="restartAll()">↺ Recommencer</button>
<button class="btn btn-amber" onclick="exporterPDF()">⬇ Télécharger mes réponses (PDF)</button>
<a href="correction-fds.html" class="btn btn-outline">✔ Corriger un rendu</a>
</div>
<div class="actions-row" style="margin-top:0.5rem;">
<button class="btn btn-outline" onclick="restartAll()">↺ Recommencer</button>
<a href="../index.html#formations" class="btn btn-outline">Retour aux exercices</a>
</div>
</div>
@@ -700,6 +746,82 @@ function verifyEtiquette() {
showScore(document.getElementById('etiquette-result'), correct, etiquetteQuestions.length);
}
/* ======================== EXPORT PDF ======================== */
function escapeHtml(str) {
if (!str) return '';
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function idVal(id) {
const el = document.getElementById(id);
return el ? el.value.trim() : '';
}
// Libellé lisible d'une réponse FDS (numéro de rubrique) et de l'attendu
function fdsAnswerLabel(v) {
if (!v) return '(non répondu)';
if (v === 'absent') return 'Absent';
return 'Rubrique ' + v;
}
function fdsExpectedLabel(answer) {
const parts = answer.split(',');
if (parts.length > 1) return 'Rubrique ' + parts.join(' ou ');
return parts[0] === 'absent' ? 'Absent' : 'Rubrique ' + parts[0];
}
function textAnswerLabel(v) {
if (!v) return '(non répondu)';
return v === 'absent' ? 'Absent' : v;
}
function isFdsCorrect(q, v) { return v !== '' && q.answer.split(',').includes(v); }
function isTextCorrect(q, v) { return v !== '' && v === q.answer; }
function exporterPDF() {
const groupe = idVal('idGroupe');
const date = idVal('idDate') || new Date().toISOString().slice(0, 10);
const formateur = idVal('idFormateur');
let scoreTotal = 0, nbTotal = 0;
function partHtml(prefix, title, questions, selPrefix, answerLabelFn, expectedLabelFn, correctFn) {
const rows = questions.map((q, i) => {
const sel = document.getElementById(selPrefix + q.id);
const v = sel ? sel.value : '';
const ok = correctFn(q, v);
nbTotal++; if (ok) scoreTotal++;
const mark = v === '' ? '❓' : (ok ? '✓' : '✗');
return `<div style="margin-bottom:10px;">
<div><strong>[${prefix}-Q${i + 1}]</strong> ${escapeHtml(q.text)}</div>
<div style="margin-top:2px;">Réponse du groupe : <strong>${escapeHtml(answerLabelFn(v))}</strong> — Attendu : ${escapeHtml(expectedLabelFn(q.answer))}${mark}</div>
</div>`;
}).join('');
return `<h2>${title}</h2>${rows}`;
}
const p1 = partHtml('P1', 'Partie 1 — FDS (Fiche de Données de Sécurité)', fdsQuestions, 'fds', fdsAnswerLabel, fdsExpectedLabel, isFdsCorrect);
const p2 = partHtml('P2', 'Partie 2 — FT (Fiche Technique)', ftQuestions, 'ft', textAnswerLabel, a => a, isTextCorrect);
const p3 = partHtml('P3', 'Partie 3 — Étiquette', etiquetteQuestions, 'eti', textAnswerLabel, a => a, isTextCorrect);
const w = window.open('', '_blank');
w.document.write(`
<html><head><meta charset="utf-8"><title>FDS, FT & Étiquettes — Réponses (${escapeHtml(groupe) || 'sans nom'})</title>
<style>
body { font-family: Arial, sans-serif; padding: 30px; color: #1a1a18; }
h1 { font-size: 20px; border-bottom: 2px solid #BA7517; padding-bottom: 8px; }
h2 { font-size: 15px; margin-top: 26px; color: #633806; }
.meta { font-size: 13px; color: #555; margin: 2px 0; }
</style></head>
<body>
<h1>TD — FDS, FT &amp; Étiquettes</h1>
<p class="meta"><strong>Groupe / Stagiaire :</strong> ${escapeHtml(groupe) || '—'}</p>
<p class="meta"><strong>Date de session :</strong> ${escapeHtml(date)}</p>
<p class="meta"><strong>Formateur :</strong> ${escapeHtml(formateur) || '—'}</p>
<p class="meta"><strong>Score global :</strong> ${scoreTotal} / ${nbTotal}</p>
${p1}${p2}${p3}
</body></html>`);
w.document.close();
setTimeout(() => w.print(), 300);
}
/* ======================== NAVIGATION ======================== */
function goTo(n) {
const cur = document.getElementById('slide-' + currentSlide);