- Nouvelle page corriger-exercice.html : sélection de l'exercice (BioCID, FDS, TP Nuisibles) + zone de dépôt PDF - Le PDF déposé est transmis à la page de correction dédiée qui se charge automatiquement (?auto=1) - Carte 'Corriger un exercice' ajoutée à la section Exercices de l'index - Slide 6 BioCID : suppression du bouton biocid-anses.fr, alignement des boutons Télécharger / Corriger / Recommencer - Le bouton 'Corriger un rendu' redirige vers la nouvelle page
669 lines
23 KiB
HTML
669 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Correction TP Nuisibles — Certibiocide</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@600;700;800&family=DM+Sans:ital,wght@0,400;0,500;1,400&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="../header-style.css"/>
|
||
<style>
|
||
body {
|
||
padding: calc(36px + 70px) 20px 80px;
|
||
}
|
||
.wrap { max-width: 820px; margin: 0 auto; }
|
||
.syne { font-family: 'Syne', sans-serif; }
|
||
.eyebrow {
|
||
font-family: 'Syne', sans-serif;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--amber);
|
||
margin-bottom: 6px;
|
||
}
|
||
h1 {
|
||
font-family: 'Syne', sans-serif;
|
||
font-size: 28px;
|
||
font-weight: 800;
|
||
letter-spacing: -0.5px;
|
||
margin-bottom: 28px;
|
||
}
|
||
|
||
/* DROPZONE */
|
||
#dropzone {
|
||
background: #fff;
|
||
border: 2px dashed rgba(186,117,23,0.35);
|
||
border-radius: 16px;
|
||
padding: 56px 24px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: border-color 0.2s, background 0.2s;
|
||
}
|
||
#dropzone.drag { border-color: var(--amber); background: var(--amber-light); }
|
||
#dropzone .icon { font-size: 40px; margin-bottom: 12px; }
|
||
#dropzone p.title { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 16px; margin-bottom: 6px; }
|
||
#dropzone p.sub { color: var(--text-muted); font-size: 13.5px; margin-bottom: 20px; }
|
||
#filePicker { display: none; }
|
||
.btn-pick {
|
||
font-family: 'Syne', sans-serif;
|
||
display: inline-block;
|
||
background: var(--amber);
|
||
color: #fff;
|
||
font-weight: 700;
|
||
font-size: 13px;
|
||
padding: 10px 26px;
|
||
border-radius: 30px;
|
||
cursor: pointer;
|
||
border: none;
|
||
}
|
||
#status { margin-top: 16px; font-size: 13px; }
|
||
#status.loading { color: var(--amber); }
|
||
#status.error { color: var(--coral); }
|
||
|
||
/* RESULT */
|
||
#result { display: none; }
|
||
.toprow {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 18px;
|
||
}
|
||
#fileName { font-size: 12.5px; color: var(--text-muted); }
|
||
.btn-reset {
|
||
font-family: 'Syne', sans-serif;
|
||
background: transparent;
|
||
border: 1px solid var(--border);
|
||
border-radius: 20px;
|
||
padding: 6px 16px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
color: var(--text-muted);
|
||
}
|
||
.btn-reset:hover { border-color: var(--amber); color: var(--amber); }
|
||
|
||
.id-card { display: none; }
|
||
|
||
/* BANDEAU PARTICIPANTS */
|
||
#participantsBanner {
|
||
background: linear-gradient(135deg, var(--amber) 0%, #D8923A 100%);
|
||
border-radius: 16px;
|
||
padding: 20px 26px;
|
||
margin-bottom: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
color: #fff;
|
||
}
|
||
#participantsBanner .pb-icon {
|
||
width: 48px; height: 48px;
|
||
border-radius: 12px;
|
||
background: rgba(255,255,255,0.2);
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 24px;
|
||
flex-shrink: 0;
|
||
}
|
||
#participantsBanner .pb-group {
|
||
font-family: 'Syne', sans-serif;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
opacity: 0.85;
|
||
margin-bottom: 4px;
|
||
}
|
||
#participantsBanner .pb-names {
|
||
font-family: 'Syne', sans-serif;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
.id-card .label {
|
||
font-family: 'Syne', sans-serif;
|
||
font-size: 10.5px;
|
||
font-weight: 700;
|
||
color: var(--amber-dark);
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
}
|
||
.id-card .value { font-size: 14.5px; margin-top: 2px; }
|
||
|
||
.progress-row { display: flex; align-items: center; gap: 10px; margin-bottom: 30px; font-size: 13px; color: var(--text-muted); }
|
||
.progress-track { flex: 1; height: 7px; background: rgba(0,0,0,0.08); border-radius: 6px; overflow: hidden; }
|
||
.progress-fill { height: 100%; background: var(--teal); transition: width 0.4s ease; }
|
||
.progress-row strong { font-family: 'Syne', sans-serif; color: var(--text); }
|
||
|
||
/* CARROUSEL DE THEMES */
|
||
.carousel-wrap { margin-bottom: 36px; }
|
||
.carousel-nav {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
.nav-btn {
|
||
width: 38px; height: 38px;
|
||
border-radius: 50%;
|
||
border: 1px solid var(--border);
|
||
background: #fff;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
display: flex; align-items: center; justify-content: center;
|
||
transition: background 0.15s, border-color 0.15s;
|
||
}
|
||
.nav-btn:hover { background: var(--amber-light); border-color: var(--amber); }
|
||
.nav-btn:disabled { opacity: 0.3; cursor: default; }
|
||
.nav-btn:disabled:hover { background: #fff; border-color: var(--border); }
|
||
.dots { display: flex; gap: 7px; }
|
||
.dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(0,0,0,0.15); cursor: pointer; transition: background 0.15s, transform 0.15s; }
|
||
.dot.active { background: var(--amber); transform: scale(1.3); }
|
||
|
||
.theme-panel {
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: 18px;
|
||
padding: 26px 28px;
|
||
min-height: 420px;
|
||
}
|
||
.theme-head {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 18px;
|
||
}
|
||
.theme-icon {
|
||
width: 44px; height: 44px;
|
||
border-radius: 12px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 22px;
|
||
flex-shrink: 0;
|
||
}
|
||
.theme-title { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 17px; }
|
||
.theme-count { font-size: 12.5px; color: var(--text-muted); margin-left: auto; }
|
||
|
||
.cards { display: flex; flex-direction: column; gap: 10px; }
|
||
.qcard {
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 14px 18px;
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: flex-start;
|
||
transition: box-shadow 0.2s;
|
||
}
|
||
.qcard:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.06); }
|
||
.qcard .badge {
|
||
width: 22px; height: 22px;
|
||
border-radius: 50%;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 12px;
|
||
flex-shrink: 0;
|
||
margin-top: 1px;
|
||
}
|
||
.qcard .badge.ok { background: var(--teal-light); color: var(--teal); }
|
||
.qcard .badge.miss { background: var(--coral-light); color: var(--coral); }
|
||
.qcard .qtext { flex: 1; }
|
||
.qcard .qlabel { font-weight: 600; font-size: 13.5px; margin-bottom: 4px; }
|
||
.qcard .qanswer { font-size: 13.5px; white-space: pre-wrap; }
|
||
.qcard .qanswer.empty { color: var(--coral); font-style: italic; }
|
||
|
||
.table-wrap {
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 16px 18px;
|
||
font-size: 13px;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
/* TABLEAU ANALYSE DE RISQUES */
|
||
.risk-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||
.risk-table th {
|
||
font-family: 'Syne', sans-serif;
|
||
text-align: left;
|
||
font-size: 11.5px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
color: var(--amber-dark);
|
||
background: var(--amber-light);
|
||
padding: 10px 12px;
|
||
}
|
||
.risk-table th:first-child { border-radius: 8px 0 0 8px; }
|
||
.risk-table th:last-child { border-radius: 0 8px 8px 0; }
|
||
.risk-table td {
|
||
padding: 10px 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
vertical-align: top;
|
||
white-space: pre-wrap;
|
||
}
|
||
.risk-table tr:last-child td { border-bottom: none; }
|
||
.meta-line { font-size: 12.5px; color: var(--text-muted); margin-top: 20px; }
|
||
.meta-line strong { color: var(--text); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<div class="logo">Gauthier Chombart & Nathan Chauwin</div>
|
||
<nav>
|
||
<a href="../index.html">Accueil</a>
|
||
<a href="../index.html#formations">Exercices</a>
|
||
<a href="../index.html#documentation">Documentation</a>
|
||
<a href="../index.html#rapports">Rapports</a>
|
||
<a href="../index.html#about">À propos</a>
|
||
<a href="../index.html#contact">Contact</a>
|
||
</nav>
|
||
</header>
|
||
|
||
<div class="wrap">
|
||
<div id="participantsBanner" style="display:none;"></div>
|
||
<div class="eyebrow">Certibiocide — Correction de TP</div>
|
||
<h1>TP Nuisibles — Lecture du rendu</h1>
|
||
|
||
<div id="dropzone">
|
||
<div class="icon">📄</div>
|
||
<p class="title">Déposez le PDF du stagiaire ici</p>
|
||
<p class="sub">ou cliquez pour sélectionner le fichier exporté depuis le TP</p>
|
||
<label class="btn-pick">Choisir un fichier
|
||
<input type="file" id="filePicker" accept="application/pdf">
|
||
</label>
|
||
<div id="status"></div>
|
||
</div>
|
||
|
||
<div id="result">
|
||
<div class="toprow">
|
||
<span id="fileName"></span>
|
||
<button class="btn-reset" onclick="resetAll()">↺ Charger un autre PDF</button>
|
||
</div>
|
||
|
||
<div class="id-card" id="idCard"></div>
|
||
|
||
<div class="progress-row">
|
||
<div class="progress-track"><div class="progress-fill" id="progressFill"></div></div>
|
||
<span><strong id="progressCount"></strong> questions répondues</span>
|
||
</div>
|
||
|
||
<div class="carousel-wrap">
|
||
<div class="carousel-nav">
|
||
<button class="nav-btn" id="prevBtn" onclick="goTo(currentTheme - 1)">←</button>
|
||
<div class="dots" id="dots"></div>
|
||
<button class="nav-btn" id="nextBtn" onclick="goTo(currentTheme + 1)">→</button>
|
||
</div>
|
||
<div id="themePanel"></div>
|
||
</div>
|
||
|
||
<h2 class="syne" style="font-size:16px;font-weight:800;margin-bottom:12px;color:var(--amber-dark);">Analyse de risques</h2>
|
||
<div class="table-wrap" id="tp3Meta"></div>
|
||
<div class="table-wrap" id="tp3Table" style="display:none;"></div>
|
||
<p class="meta-line" id="suiviLine"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<footer>
|
||
<p>© 2026 Gauthier Chombart & Nathan Chauwin<br>Tous droits réservés — Formations Certibiocide & Techniques — Formateurs indépendants</p>
|
||
<p style="margin-top: 10px; opacity: 0.6;">Tous droits réservés — Gauthier Chombart & Nathan Chauwin — Formateurs indépendants</p>
|
||
</footer>
|
||
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||
<script>
|
||
pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
|
||
|
||
// Thèmes regroupant les sections du TP, avec icône et couleur
|
||
const THEMES = [
|
||
{
|
||
title: "Premier contact & diagnostic",
|
||
icon: "🗣️", color: "purple",
|
||
items: [
|
||
"Questions posées au client lors du premier contact",
|
||
"Conseils donnés en premier lieu",
|
||
"Actions de prévention menées sur le site",
|
||
"Éléments observés / preuve de présence du nuisible",
|
||
]
|
||
},
|
||
{
|
||
title: "Solutions envisagées",
|
||
icon: "🧰", color: "blue",
|
||
items: [
|
||
"Solutions alternatives non-biocides",
|
||
"Solutions biocides mises en place",
|
||
"Type de produit (TP) et mode de traitement",
|
||
"Suivi mis en place et durée",
|
||
"Gestion des déchets",
|
||
]
|
||
},
|
||
{
|
||
title: "Produit & traçabilité",
|
||
icon: "🧪", color: "teal",
|
||
items: [
|
||
"FDS trouvée (éléments principaux)",
|
||
"Nom du produit et fabricant/fournisseur",
|
||
"Numéro AMM",
|
||
]
|
||
},
|
||
{
|
||
title: "Sécurité & santé",
|
||
icon: "🛡️", color: "coral",
|
||
items: [
|
||
"Risques pour la santé",
|
||
"EPI à porter",
|
||
"Risques environnement / dispersion accidentelle",
|
||
"DL50 substance(s) active(s)",
|
||
"Gestes de premiers secours",
|
||
]
|
||
},
|
||
];
|
||
|
||
const ALL_SECTIONS = THEMES.flatMap(t => t.items);
|
||
|
||
const COLOR_MAP = {
|
||
purple: { bg: "var(--purple-light)", fg: "var(--purple)" },
|
||
blue: { bg: "var(--blue-light)", fg: "var(--blue)" },
|
||
teal: { bg: "var(--teal-light)", fg: "var(--teal)" },
|
||
coral: { bg: "var(--coral-light)", fg: "var(--coral)" },
|
||
};
|
||
|
||
function escapeRx(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }
|
||
function escapeHtml(s) {
|
||
if (!s) return "";
|
||
return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||
}
|
||
function isEmpty(v) {
|
||
if (!v) return true;
|
||
const t = v.trim();
|
||
return t === "" || /^\(sans réponse\)$/i.test(t);
|
||
}
|
||
|
||
function captureBetween(text, label, nextLabels) {
|
||
const startRx = new RegExp(escapeRx(label) + "\\s*:?\\s*", "i");
|
||
const m = startRx.exec(text);
|
||
if (!m) return "";
|
||
const from = m.index + m[0].length;
|
||
let to = text.length;
|
||
for (const nl of nextLabels) {
|
||
const endRx = new RegExp(escapeRx(nl), "i");
|
||
const m2 = endRx.exec(text.slice(from));
|
||
if (m2 && from + m2.index < to) to = from + m2.index;
|
||
}
|
||
return text.slice(from, to).trim();
|
||
}
|
||
|
||
function parsePdfText(raw) {
|
||
let clean = raw.replace(/\r/g, "").replace(/[ \t]+\n/g, "\n").replace(/\s+/g, " ").trim();
|
||
// Retire les répétitions d'en-tête de page (ex: "17/06/2026 15:42 Résultats TP — ... about:blank 1/2")
|
||
clean = clean.replace(/\d{1,2}\/\d{1,2}\/\d{4}\s+\d{1,2}:\d{2}\s+Résultats TP[^]*?about:blank\s*\d\/\d/gi, " ");
|
||
|
||
const meta = {
|
||
nom: captureBetween(clean, "Groupe / Stagiaire", ["Date de session"]),
|
||
date_session: captureBetween(clean, "Date de session", ["Formateur"]),
|
||
formateur: captureBetween(clean, "Formateur", ["Cas pratique"]),
|
||
cas: captureBetween(clean, "Cas pratique", ["Réponses TP1"]),
|
||
};
|
||
|
||
// Sépare "Groupe N" du nom des participants (ex: "Groupe 1 Romain Kevin Valere Greg P")
|
||
const groupMatch = /^Groupe\s*(\d+)\s*(.*)$/i.exec(meta.nom.trim());
|
||
if (groupMatch) {
|
||
meta.groupe = "Groupe " + groupMatch[1];
|
||
meta.participants = groupMatch[2].trim();
|
||
} else {
|
||
meta.groupe = "";
|
||
meta.participants = meta.nom.trim();
|
||
}
|
||
|
||
const startIdx = clean.search(/Réponses TP1\s*&?\s*TP2/i);
|
||
const endIdx = clean.search(/TP3\s*[—-]\s*Analyse de risques/i);
|
||
const block = startIdx >= 0 ? clean.slice(startIdx, endIdx >= 0 ? endIdx : clean.length) : clean;
|
||
|
||
const answers = {};
|
||
for (let i = 0; i < ALL_SECTIONS.length; i++) {
|
||
const label = ALL_SECTIONS[i];
|
||
const nextLabel = ALL_SECTIONS[i + 1];
|
||
answers[label] = captureBetween(block, label, nextLabel ? [nextLabel] : []);
|
||
}
|
||
|
||
const tp3Zone = endIdx >= 0 ? clean.slice(endIdx) : "";
|
||
const tp3 = {
|
||
intitule: captureBetween(tp3Zone, "Intitulé", ["Description"]),
|
||
description: captureBetween(tp3Zone, "Description", ["Lieu"]),
|
||
lieu: captureBetween(tp3Zone, "Lieu", ["Début prévu le"]),
|
||
debut: captureBetween(tp3Zone, "Début prévu le", ["Étapes"]),
|
||
};
|
||
|
||
// Tableau de risques : on découpe en s'appuyant sur les intitulés d'étapes connus du TP
|
||
const KNOWN_STEPS = [
|
||
"remplir la poudreuse",
|
||
"sécurisation du site",
|
||
"application traitements",
|
||
"rangement du matériel. purger la perche",
|
||
"retrait du nid",
|
||
"gestion des déchets. élimination par des filiaires spé.",
|
||
];
|
||
let riskRows = [];
|
||
const tableMatch = /Étapes\s*Risques\s*Mesures de prévention([\s\S]*?)(Réalisée par|$)/i.exec(tp3Zone);
|
||
if (tableMatch) {
|
||
let raw = tableMatch[1].trim();
|
||
if (raw) {
|
||
// Repère les positions de chaque étape connue dans le texte aplati
|
||
const positions = [];
|
||
KNOWN_STEPS.forEach(step => {
|
||
const idx = raw.toLowerCase().indexOf(step.toLowerCase());
|
||
if (idx >= 0) positions.push({ step, idx });
|
||
});
|
||
positions.sort((a, b) => a.idx - b.idx);
|
||
|
||
if (positions.length > 0) {
|
||
for (let i = 0; i < positions.length; i++) {
|
||
const from = positions[i].idx + positions[i].step.length;
|
||
const to = i + 1 < positions.length ? positions[i + 1].idx : raw.length;
|
||
let rest = raw.slice(from, to).trim();
|
||
// rest contient "risque... mesure..." -> on coupe sur les mots clés de mesures (gants/masques, barrieres, etc.)
|
||
const measureRx = /(gants\s*\/\s*masques\s*\/\s*lunettes[^.]*|barrieres\.?\s*epi)/i;
|
||
const mm = measureRx.exec(rest);
|
||
let risque = rest, mesure = "";
|
||
if (mm) {
|
||
risque = rest.slice(0, mm.index).trim();
|
||
mesure = rest.slice(mm.index).trim();
|
||
}
|
||
risque = risque.replace(/^[\s\-–.]+/, "").trim();
|
||
riskRows.push({ etape: positions[i].step, risque: risque || "—", mesure: mesure || "—" });
|
||
}
|
||
} else {
|
||
riskRows.push({ etape: raw, risque: "", mesure: "" });
|
||
}
|
||
}
|
||
}
|
||
|
||
const mReal = clean.match(/Réalisée par\s*:\s*([^—\n]*)/i);
|
||
const mVerif = clean.match(/Vérifiée par\s*:\s*([^—\n]*)/i);
|
||
const mMaj = clean.match(/Mise à jour par\s*:\s*([^—\n]*)/i);
|
||
|
||
return {
|
||
meta, answers, tp3, riskRows,
|
||
suivi: {
|
||
realise: mReal ? mReal[1].trim() : "",
|
||
verifie: mVerif ? mVerif[1].trim() : "",
|
||
maj: mMaj ? mMaj[1].trim() : "",
|
||
}
|
||
};
|
||
}
|
||
|
||
let currentThemeData = null;
|
||
let currentTheme = 0;
|
||
|
||
function renderThemePanel() {
|
||
const theme = THEMES[currentTheme];
|
||
const data = currentThemeData;
|
||
const colors = COLOR_MAP[theme.color];
|
||
const themeAnswered = theme.items.filter(s => !isEmpty(data.answers[s])).length;
|
||
|
||
document.getElementById("themePanel").innerHTML = `
|
||
<div class="theme-panel">
|
||
<div class="theme-head">
|
||
<div class="theme-icon" style="background:${colors.bg};color:${colors.fg};">${theme.icon}</div>
|
||
<div class="theme-title">${theme.title}</div>
|
||
<div class="theme-count">${themeAnswered}/${theme.items.length}</div>
|
||
</div>
|
||
<div class="cards">
|
||
${theme.items.map(label => {
|
||
const value = data.answers[label] || "";
|
||
const empty = isEmpty(value);
|
||
return `
|
||
<div class="qcard">
|
||
<div class="badge ${empty ? 'miss' : 'ok'}">${empty ? '!' : '✓'}</div>
|
||
<div class="qtext">
|
||
<div class="qlabel">${escapeHtml(label)}</div>
|
||
<div class="qanswer ${empty ? 'empty' : ''}">${empty ? 'Sans réponse' : escapeHtml(value)}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join("")}
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.getElementById("prevBtn").disabled = currentTheme === 0;
|
||
document.getElementById("nextBtn").disabled = currentTheme === THEMES.length - 1;
|
||
document.querySelectorAll(".dot").forEach((d, i) => d.classList.toggle("active", i === currentTheme));
|
||
}
|
||
|
||
function goTo(idx) {
|
||
if (idx < 0 || idx >= THEMES.length) return;
|
||
currentTheme = idx;
|
||
renderThemePanel();
|
||
}
|
||
|
||
function renderResult(data) {
|
||
currentThemeData = data;
|
||
currentTheme = 0;
|
||
|
||
const banner = document.getElementById("participantsBanner");
|
||
if (data.meta.participants) {
|
||
banner.style.display = "flex";
|
||
banner.innerHTML = `
|
||
<div class="pb-icon">👥</div>
|
||
<div>
|
||
<div class="pb-group">${escapeHtml(data.meta.groupe) || "Participants"}</div>
|
||
<div class="pb-names">${escapeHtml(data.meta.participants)}</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
banner.style.display = "none";
|
||
}
|
||
|
||
document.getElementById("idCard").innerHTML = `
|
||
<div><div class="label">Groupe / Stagiaire</div><div class="value">${escapeHtml(data.meta.nom) || "—"}</div></div>
|
||
<div><div class="label">Date de session</div><div class="value">${escapeHtml(data.meta.date_session) || "—"}</div></div>
|
||
<div><div class="label">Formateur</div><div class="value">${escapeHtml(data.meta.formateur) || "—"}</div></div>
|
||
<div><div class="label">Cas pratique</div><div class="value">${escapeHtml(data.meta.cas) || "—"}</div></div>
|
||
`;
|
||
|
||
const answeredCount = ALL_SECTIONS.filter(s => !isEmpty(data.answers[s])).length;
|
||
document.getElementById("progressFill").style.width = (answeredCount / ALL_SECTIONS.length * 100) + "%";
|
||
document.getElementById("progressCount").textContent = `${answeredCount} / ${ALL_SECTIONS.length}`;
|
||
|
||
document.getElementById("dots").innerHTML = THEMES.map((_, i) =>
|
||
`<div class="dot${i === 0 ? ' active' : ''}" onclick="goTo(${i})"></div>`
|
||
).join("");
|
||
|
||
renderThemePanel();
|
||
|
||
document.getElementById("tp3Meta").innerHTML = `
|
||
<p style="margin-bottom:6px;"><strong>Intitulé :</strong> ${escapeHtml(data.tp3.intitule) || '<em style="color:var(--coral)">—</em>'}</p>
|
||
<p style="margin-bottom:6px;"><strong>Description :</strong> ${escapeHtml(data.tp3.description) || '<em style="color:var(--coral)">—</em>'}</p>
|
||
<p style="margin-bottom:6px;"><strong>Lieu :</strong> ${escapeHtml(data.tp3.lieu) || '<em style="color:var(--coral)">—</em>'}</p>
|
||
<p><strong>Début prévu le :</strong> ${escapeHtml(data.tp3.debut) || '<em style="color:var(--coral)">—</em>'}</p>
|
||
`;
|
||
|
||
const tp3TableEl = document.getElementById("tp3Table");
|
||
if (data.riskRows && data.riskRows.length > 0) {
|
||
tp3TableEl.style.display = "block";
|
||
tp3TableEl.innerHTML = `
|
||
<table class="risk-table">
|
||
<thead><tr><th>Étapes</th><th>Risques</th><th>Mesures de prévention</th></tr></thead>
|
||
<tbody>
|
||
${data.riskRows.map(r => `
|
||
<tr>
|
||
<td>${escapeHtml(r.etape) || "—"}</td>
|
||
<td>${escapeHtml(r.risque) || "—"}</td>
|
||
<td>${escapeHtml(r.mesure) || "—"}</td>
|
||
</tr>
|
||
`).join("")}
|
||
</tbody>
|
||
</table>
|
||
`;
|
||
} else {
|
||
tp3TableEl.style.display = "none";
|
||
}
|
||
|
||
document.getElementById("suiviLine").innerHTML =
|
||
`Réalisée par : <strong>${escapeHtml(data.suivi.realise) || "—"}</strong> — Vérifiée par : <strong>${escapeHtml(data.suivi.verifie) || "—"}</strong> — Mise à jour par : <strong>${escapeHtml(data.suivi.maj) || "—"}</strong>`;
|
||
|
||
document.getElementById("dropzone").style.display = "none";
|
||
document.getElementById("result").style.display = "block";
|
||
}
|
||
|
||
async function handleFile(file) {
|
||
if (!file) return;
|
||
const statusEl = document.getElementById("status");
|
||
statusEl.className = "loading";
|
||
statusEl.textContent = "Lecture du PDF en cours…";
|
||
document.getElementById("fileName").textContent = file.name;
|
||
|
||
try {
|
||
const arrayBuffer = await file.arrayBuffer();
|
||
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||
let fullText = "";
|
||
for (let p = 1; p <= pdf.numPages; p++) {
|
||
const page = await pdf.getPage(p);
|
||
const content = await page.getTextContent();
|
||
fullText += content.items.map(it => it.str).join(" ") + "\n";
|
||
}
|
||
const data = parsePdfText(fullText);
|
||
renderResult(data);
|
||
statusEl.textContent = "";
|
||
} catch (e) {
|
||
console.error(e);
|
||
statusEl.className = "error";
|
||
statusEl.textContent = "Impossible de lire ce PDF. Vérifiez qu'il s'agit bien d'un export du TP.";
|
||
}
|
||
}
|
||
|
||
function resetAll() {
|
||
document.getElementById("result").style.display = "none";
|
||
document.getElementById("dropzone").style.display = "block";
|
||
document.getElementById("participantsBanner").style.display = "none";
|
||
document.getElementById("status").textContent = "";
|
||
document.getElementById("filePicker").value = "";
|
||
document.getElementById("tp3Table").style.display = "none";
|
||
}
|
||
|
||
const dropzone = document.getElementById("dropzone");
|
||
dropzone.addEventListener("dragover", e => { e.preventDefault(); dropzone.classList.add("drag"); });
|
||
dropzone.addEventListener("dragleave", () => dropzone.classList.remove("drag"));
|
||
dropzone.addEventListener("drop", e => {
|
||
e.preventDefault();
|
||
dropzone.classList.remove("drag");
|
||
const file = e.dataTransfer.files?.[0];
|
||
if (file) handleFile(file);
|
||
});
|
||
document.getElementById("filePicker").addEventListener("change", e => handleFile(e.target.files?.[0]));
|
||
|
||
// Chargement automatique depuis la page « Corriger un exercice » (?auto=1)
|
||
(function autoLoadFromHub() {
|
||
const params = new URLSearchParams(location.search);
|
||
if (params.get("auto") !== "1") return;
|
||
const dataUrl = localStorage.getItem("correctionFile");
|
||
const name = localStorage.getItem("correctionFileName") || "rendu.pdf";
|
||
localStorage.removeItem("correctionFile");
|
||
localStorage.removeItem("correctionFileName");
|
||
localStorage.removeItem("correctionExo");
|
||
if (!dataUrl) return;
|
||
try {
|
||
const bin = atob(dataUrl.split(",").pop());
|
||
const bytes = new Uint8Array(bin.length);
|
||
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
||
handleFile(new File([bytes], name, { type: "application/pdf" }));
|
||
} catch (e) { console.error(e); }
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|