Ajout d'une possibilité de correction pour le TP Nuisibles
This commit is contained in:
@@ -628,6 +628,9 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<!-- CONFETTI CANVAS -->
|
||||||
|
<canvas id="confettiCanvas" style="position:fixed;inset:0;pointer-events:none;z-index:150;display:none;"></canvas>
|
||||||
|
|
||||||
<!-- HEADER WITH NAVIGATION -->
|
<!-- HEADER WITH NAVIGATION -->
|
||||||
<header>
|
<header>
|
||||||
<div class="logo">NextGN Formation</div>
|
<div class="logo">NextGN Formation</div>
|
||||||
@@ -688,7 +691,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="idError" style="color:var(--coral);font-size:13px;margin-bottom:0.5rem;display:none;">Merci de renseigner au moins le nom du groupe.</div>
|
<div id="idError" style="color:var(--coral);font-size:13px;margin-bottom:0.5rem;display:none;">Merci de renseigner au moins le nom du groupe.</div>
|
||||||
<button class="btn btn-teal" onclick="confirmIdentite()">Continuer →</button>
|
<div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap;">
|
||||||
|
<button class="btn btn-teal" onclick="confirmIdentite()">Continuer →</button>
|
||||||
|
<a href="correction-tp-nuisibles.html" class="btn btn-outline btn-teal">Corriger</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -697,7 +703,7 @@
|
|||||||
<div class="deco-circle" style="width:400px;height:400px;background:var(--amber-light);top:-100px;right:-80px;opacity:0.6;"></div>
|
<div class="deco-circle" style="width:400px;height:400px;background:var(--amber-light);top:-100px;right:-80px;opacity:0.6;"></div>
|
||||||
<div class="deco-circle" style="width:200px;height:200px;background:var(--amber-light);bottom:60px;left:-60px;opacity:0.5;"></div>
|
<div class="deco-circle" style="width:200px;height:200px;background:var(--amber-light);bottom:60px;left:-60px;opacity:0.5;"></div>
|
||||||
<div style="text-align:center; z-index:1;">
|
<div style="text-align:center; z-index:1;">
|
||||||
<div class="hero-label">Formation 2025</div>
|
<div class="hero-label">Formation</div>
|
||||||
<div class="hero-title">CERTIBIOCIDE</div>
|
<div class="hero-title">CERTIBIOCIDE</div>
|
||||||
<div style="font-family:'Syne',sans-serif;font-size:clamp(14px,2.5vw,22px);font-weight:600;color:var(--amber-mid);letter-spacing:0.04em;margin-bottom:1.5rem;">TRAVAUX PRATIQUES</div>
|
<div style="font-family:'Syne',sans-serif;font-size:clamp(14px,2.5vw,22px);font-weight:600;color:var(--amber-mid);letter-spacing:0.04em;margin-bottom:1.5rem;">TRAVAUX PRATIQUES</div>
|
||||||
<p class="hero-sub">Votre mission, si vous l'acceptez : maîtriser l'identification, le traitement et la sécurité face aux nuisibles.</p>
|
<p class="hero-sub">Votre mission, si vous l'acceptez : maîtriser l'identification, le traitement et la sécurité face aux nuisibles.</p>
|
||||||
@@ -1119,7 +1125,7 @@
|
|||||||
<div style="font-size:48px;margin-bottom:1rem;">🎉</div>
|
<div style="font-size:48px;margin-bottom:1rem;">🎉</div>
|
||||||
<div class="section-tag" style="background:var(--amber-light);color:var(--amber-dark);">Mission accomplie</div>
|
<div class="section-tag" style="background:var(--amber-light);color:var(--amber-dark);">Mission accomplie</div>
|
||||||
<div class="slide-title" style="margin-top:0.5rem;">Félicitations !</div>
|
<div class="slide-title" style="margin-top:0.5rem;">Félicitations !</div>
|
||||||
<p style="font-size:14px;color:var(--text-muted);max-width:480px;text-align:center;line-height:1.7;margin:1rem auto;">Vous avez complété les 3 travaux pratiques de la formation Certibiocide 2025. Le formateur va maintenant corriger à l'oral.</p>
|
<p style="font-size:14px;color:var(--text-muted);max-width:480px;text-align:center;line-height:1.7;margin:1rem auto;">Vous avez complété les 3 travaux pratiques de la formation Certibiocide. Le formateur va maintenant corriger à l'oral.</p>
|
||||||
<div class="final-grid">
|
<div class="final-grid">
|
||||||
<div class="final-card">
|
<div class="final-card">
|
||||||
<div class="final-card-icon">🗂️</div>
|
<div class="final-card-icon">🗂️</div>
|
||||||
@@ -1140,6 +1146,7 @@
|
|||||||
<div class="submit-zone">
|
<div class="submit-zone">
|
||||||
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;">
|
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;">
|
||||||
<button class="btn btn-teal" onclick="exporterPDF()">⬇ Télécharger en PDF</button>
|
<button class="btn btn-teal" onclick="exporterPDF()">⬇ Télécharger en PDF</button>
|
||||||
|
<a href="correction-tp-nuisibles.html" class="btn btn-outline btn-teal">Corriger</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:0.75rem;">
|
<div style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:0.75rem;">
|
||||||
@@ -1243,6 +1250,7 @@ function goTo(n) {
|
|||||||
currentSlide = n;
|
currentSlide = n;
|
||||||
next.style.display = 'flex';
|
next.style.display = 'flex';
|
||||||
setTimeout(() => { next.classList.add('active', 'fade-in'); }, 10);
|
setTimeout(() => { next.classList.add('active', 'fade-in'); }, 10);
|
||||||
|
if (n === 13) setTimeout(launchConfetti, 200);
|
||||||
updateUI();
|
updateUI();
|
||||||
updateCaseBanners();
|
updateCaseBanners();
|
||||||
}
|
}
|
||||||
@@ -1497,7 +1505,7 @@ function exporterPDF() {
|
|||||||
.meta { font-size: 13px; color: #555; margin-bottom: 4px; }
|
.meta { font-size: 13px; color: #555; margin-bottom: 4px; }
|
||||||
</style></head>
|
</style></head>
|
||||||
<body>
|
<body>
|
||||||
<h1>TP Nuisibles — Certibiocide 2025</h1>
|
<h1>TP Nuisibles — Certibiocide</h1>
|
||||||
<p class="meta"><strong>Groupe / Stagiaire :</strong> ${escapeHtml(data.nom)}</p>
|
<p class="meta"><strong>Groupe / Stagiaire :</strong> ${escapeHtml(data.nom)}</p>
|
||||||
<p class="meta"><strong>Date de session :</strong> ${escapeHtml(data.date_session)}</p>
|
<p class="meta"><strong>Date de session :</strong> ${escapeHtml(data.date_session)}</p>
|
||||||
<p class="meta"><strong>Formateur :</strong> ${escapeHtml(data.formateur)}</p>
|
<p class="meta"><strong>Formateur :</strong> ${escapeHtml(data.formateur)}</p>
|
||||||
@@ -1520,6 +1528,79 @@ function exporterPDF() {
|
|||||||
setTimeout(() => w.print(), 300);
|
setTimeout(() => w.print(), 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================== CONFETTI ======================== */
|
||||||
|
function launchConfetti() {
|
||||||
|
const canvas = document.getElementById('confettiCanvas');
|
||||||
|
if (!canvas) return;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
canvas.style.display = 'block';
|
||||||
|
|
||||||
|
const colors = ['#BA7517', '#D85A30', '#F5DFB8', '#633806', '#F0A855', '#E8C068', '#FF8C5A'];
|
||||||
|
const particles = Array.from({ length: 180 }, () => ({
|
||||||
|
x: Math.random() * canvas.width,
|
||||||
|
y: -(Math.random() * canvas.height * 0.5),
|
||||||
|
w: Math.random() * 10 + 5,
|
||||||
|
h: Math.random() * 5 + 3,
|
||||||
|
color: colors[Math.floor(Math.random() * colors.length)],
|
||||||
|
rotation: Math.random() * Math.PI * 2,
|
||||||
|
rotSpeed: (Math.random() - 0.5) * 0.18,
|
||||||
|
vx: (Math.random() - 0.5) * 2.5,
|
||||||
|
vy: Math.random() * 3 + 2,
|
||||||
|
opacity: 1,
|
||||||
|
shape: Math.random() < 0.4 ? 'circle' : 'rect'
|
||||||
|
}));
|
||||||
|
|
||||||
|
let rafId;
|
||||||
|
let start = null;
|
||||||
|
const totalDuration = 3800;
|
||||||
|
|
||||||
|
function draw(ts) {
|
||||||
|
if (!start) start = ts;
|
||||||
|
const elapsed = ts - start;
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
let active = false;
|
||||||
|
|
||||||
|
particles.forEach(p => {
|
||||||
|
p.x += p.vx;
|
||||||
|
p.y += p.vy;
|
||||||
|
p.vy += 0.04;
|
||||||
|
p.rotation += p.rotSpeed;
|
||||||
|
p.opacity = elapsed < totalDuration * 0.55
|
||||||
|
? 1
|
||||||
|
: Math.max(0, 1 - (elapsed - totalDuration * 0.55) / (totalDuration * 0.45));
|
||||||
|
|
||||||
|
if (p.y < canvas.height + 20) active = true;
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(p.x, p.y);
|
||||||
|
ctx.rotate(p.rotation);
|
||||||
|
ctx.globalAlpha = p.opacity;
|
||||||
|
ctx.fillStyle = p.color;
|
||||||
|
if (p.shape === 'circle') {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, p.w / 2, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
} else {
|
||||||
|
ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (active && elapsed < totalDuration * 1.3) {
|
||||||
|
rafId = requestAnimationFrame(draw);
|
||||||
|
} else {
|
||||||
|
canvas.style.display = 'none';
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rafId) cancelAnimationFrame(rafId);
|
||||||
|
rafId = requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
|||||||
@@ -1,299 +0,0 @@
|
|||||||
<%@ Language="VBScript" %>
|
|
||||||
<%
|
|
||||||
Response.Buffer = True
|
|
||||||
Response.CharSet = "UTF-8"
|
|
||||||
|
|
||||||
' ------- À ADAPTER : même chaîne de connexion que save_results.asp -------
|
|
||||||
Dim connString
|
|
||||||
connString = "Provider=SQLOLEDB;Data Source=sql.bsite.net\MSSQL2016;" & _
|
|
||||||
"Initial Catalog=nextgnformation_reponses_exercices;" & _
|
|
||||||
"User ID=nextgnformation_reponses_exercices;" & _
|
|
||||||
"Password=S5$NyFXzEjJCg6jD;"
|
|
||||||
|
|
||||||
' ------- À ADAPTER : mot de passe d'accès à cette page -------
|
|
||||||
Dim ADMIN_PASSWORD
|
|
||||||
ADMIN_PASSWORD = "Sm#gnttCaYE5f3ED"
|
|
||||||
' -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
Dim authenticated
|
|
||||||
authenticated = False
|
|
||||||
|
|
||||||
If Request.Cookies("tp_admin_auth") = "ok" Then
|
|
||||||
authenticated = True
|
|
||||||
End If
|
|
||||||
|
|
||||||
If Request.Form("password") <> "" Then
|
|
||||||
If Request.Form("password") = ADMIN_PASSWORD Then
|
|
||||||
authenticated = True
|
|
||||||
Response.Cookies("tp_admin_auth") = "ok"
|
|
||||||
Response.Cookies("tp_admin_auth").Expires = DateAdd("h", 8, Now)
|
|
||||||
Else
|
|
||||||
authenticated = False
|
|
||||||
End If
|
|
||||||
End If
|
|
||||||
|
|
||||||
Function HtmlEscape(s)
|
|
||||||
If IsNull(s) Then
|
|
||||||
HtmlEscape = ""
|
|
||||||
Exit Function
|
|
||||||
End If
|
|
||||||
s = CStr(s)
|
|
||||||
s = Replace(s, "&", "&")
|
|
||||||
s = Replace(s, "<", "<")
|
|
||||||
s = Replace(s, ">", ">")
|
|
||||||
HtmlEscape = s
|
|
||||||
End Function
|
|
||||||
%>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Résultats des TP — NextGN Formation</title>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--amber: #BA7517; --amber-light: #FAEEDA; --amber-dark: #633806;
|
|
||||||
--coral: #993C1D; --coral-mid: #D85A30; --coral-light: #FAECE7;
|
|
||||||
--bg: #F7F6F2; --white: #FFFFFF; --text: #1a1a18; --text-muted: #6b6a65;
|
|
||||||
--border: rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
body {
|
|
||||||
margin: 0; font-family: 'DM Sans', sans-serif; background: var(--bg); color: var(--text);
|
|
||||||
padding-top: 70px;
|
|
||||||
}
|
|
||||||
.nav-header {
|
|
||||||
position: fixed; top: 0; left: 0; right: 0; height: 70px;
|
|
||||||
background: rgba(250,238,218,0.98); border-bottom: 2px solid var(--amber);
|
|
||||||
display: flex; align-items: center; justify-content: space-between; padding: 0 40px; z-index: 1000;
|
|
||||||
}
|
|
||||||
.header-logo {
|
|
||||||
font-family: 'Syne', sans-serif; font-size: 16px; font-weight: 700;
|
|
||||||
background: linear-gradient(135deg, var(--amber), var(--coral-mid));
|
|
||||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
|
||||||
}
|
|
||||||
.wrap { max-width: 1200px; margin: 0 auto; padding: 2rem; }
|
|
||||||
h1 { font-family: 'Syne', sans-serif; font-size: 24px; font-weight: 800; }
|
|
||||||
.login-box {
|
|
||||||
max-width: 360px; margin: 80px auto; background: white; border: 1px solid var(--border);
|
|
||||||
border-radius: 16px; padding: 2rem; text-align: center;
|
|
||||||
}
|
|
||||||
.login-box input[type="password"] {
|
|
||||||
width: 100%; padding: 12px 16px; border: 1.5px solid var(--border); border-radius: 10px;
|
|
||||||
font-size: 15px; margin: 1rem 0;
|
|
||||||
}
|
|
||||||
.login-box button, .btn {
|
|
||||||
font-family: 'Syne', sans-serif; font-size: 13px; font-weight: 700; letter-spacing: 0.05em;
|
|
||||||
text-transform: uppercase; background: var(--amber); color: white; border: none;
|
|
||||||
border-radius: 30px; padding: 10px 24px; cursor: pointer;
|
|
||||||
}
|
|
||||||
.error-msg { color: var(--coral); font-size: 13px; margin-bottom: 0.5rem; }
|
|
||||||
table.results { width: 100%; border-collapse: collapse; background: white; border-radius: 12px; overflow: hidden; border: 1px solid var(--border); font-size: 13px; }
|
|
||||||
table.results th {
|
|
||||||
background: var(--amber-light); color: var(--amber-dark); font-family: 'Syne', sans-serif;
|
|
||||||
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
|
|
||||||
padding: 10px 14px; text-align: left; border-bottom: 2px solid var(--amber);
|
|
||||||
}
|
|
||||||
table.results td { padding: 10px 14px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
||||||
table.results tr:hover { background: #FAFAFA; }
|
|
||||||
.detail-toggle { cursor: pointer; color: var(--amber-dark); font-weight: 700; text-decoration: underline; background: none; border: none; font-family: 'DM Sans', sans-serif; font-size: 13px; }
|
|
||||||
.detail-row { display: none; }
|
|
||||||
.detail-row.open { display: table-row; }
|
|
||||||
.detail-content { background: #FAFAFA; padding: 16px 24px; }
|
|
||||||
.detail-content h3 { font-family: 'Syne', sans-serif; font-size: 13px; margin: 14px 0 6px; color: var(--amber-dark); }
|
|
||||||
.detail-content p { margin: 4px 0; font-size: 13px; line-height: 1.5; }
|
|
||||||
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; }
|
|
||||||
.count-badge { background: var(--amber-light); color: var(--amber-dark); padding: 6px 14px; border-radius: 20px; font-size: 12px; font-weight: 700; font-family: 'Syne', sans-serif; }
|
|
||||||
.risk-mini { width: 100%; border-collapse: collapse; margin-top: 4px; }
|
|
||||||
.risk-mini td { border: 1px solid var(--border); padding: 4px 8px; font-size: 12px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<nav class="nav-header">
|
|
||||||
<span class="header-logo">NextGN Formation — Admin</span>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="wrap">
|
|
||||||
<%
|
|
||||||
If Not authenticated Then
|
|
||||||
%>
|
|
||||||
<div class="login-box">
|
|
||||||
<h1 style="font-size:18px;">Accès résultats TP</h1>
|
|
||||||
<% If Request.Form("password") <> "" Then %>
|
|
||||||
<div class="error-msg">Mot de passe incorrect.</div>
|
|
||||||
<% End If %>
|
|
||||||
<form method="POST">
|
|
||||||
<input type="password" name="password" placeholder="Mot de passe" autofocus>
|
|
||||||
<br>
|
|
||||||
<button type="submit">Se connecter</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<%
|
|
||||||
Else
|
|
||||||
Dim conn, rs
|
|
||||||
Set conn = Server.CreateObject("ADODB.Connection")
|
|
||||||
On Error Resume Next
|
|
||||||
conn.Open connString
|
|
||||||
If Err.Number <> 0 Then
|
|
||||||
%>
|
|
||||||
<p style="color:var(--coral);">Erreur de connexion à la base : <%= HtmlEscape(Err.Description) %></p>
|
|
||||||
<%
|
|
||||||
Else
|
|
||||||
Err.Clear
|
|
||||||
Set rs = conn.Execute("SELECT * FROM tp_resultats ORDER BY date_enregistrement DESC")
|
|
||||||
If Err.Number <> 0 Then
|
|
||||||
%>
|
|
||||||
<p style="color:var(--coral);">La table tp_resultats n'existe pas encore — aucun envoi reçu pour le moment.</p>
|
|
||||||
<%
|
|
||||||
Else
|
|
||||||
%>
|
|
||||||
<div class="topbar">
|
|
||||||
<h1>Résultats des stagiaires</h1>
|
|
||||||
<span class="count-badge"><%= IIf_Count(rs) %></span>
|
|
||||||
</div>
|
|
||||||
<table class="results">
|
|
||||||
<tr>
|
|
||||||
<th>Groupe / Stagiaire</th>
|
|
||||||
<th>Date session</th>
|
|
||||||
<th>Cas pratique</th>
|
|
||||||
<th>Reçu le</th>
|
|
||||||
<th>Détail</th>
|
|
||||||
</tr>
|
|
||||||
<%
|
|
||||||
Dim rowIndex
|
|
||||||
rowIndex = 0
|
|
||||||
Do While Not rs.EOF
|
|
||||||
rowIndex = rowIndex + 1
|
|
||||||
%>
|
|
||||||
<tr>
|
|
||||||
<td><%= HtmlEscape(rs("nom")) %></td>
|
|
||||||
<td><%= HtmlEscape(rs("date_session")) %></td>
|
|
||||||
<td><%= HtmlEscape(rs("cas_choisi")) %></td>
|
|
||||||
<td><%= HtmlEscape(rs("date_enregistrement")) %></td>
|
|
||||||
<td><button class="detail-toggle" onclick="toggleDetail(<%= rowIndex %>)">Voir le détail</button></td>
|
|
||||||
</tr>
|
|
||||||
<tr class="detail-row" id="detail-<%= rowIndex %>">
|
|
||||||
<td colspan="5">
|
|
||||||
<div class="detail-content">
|
|
||||||
<h3>Réponses TP1 & TP2</h3>
|
|
||||||
<%= RenderReponses(rs("reponses_json")) %>
|
|
||||||
<h3>TP3 — Analyse de risques</h3>
|
|
||||||
<p><strong>Intitulé :</strong> <%= HtmlEscape(rs("tp3_intitule")) %></p>
|
|
||||||
<p><strong>Description :</strong> <%= HtmlEscape(rs("tp3_description")) %></p>
|
|
||||||
<p><strong>Lieu :</strong> <%= HtmlEscape(rs("tp3_lieu")) %></p>
|
|
||||||
<p><strong>Début prévu :</strong> <%= HtmlEscape(rs("tp3_debut")) %></p>
|
|
||||||
<%= RenderTableau(rs("tp3_tableau_json")) %>
|
|
||||||
<p><strong>Réalisée par :</strong> <%= HtmlEscape(rs("tp3_realise_par")) %> —
|
|
||||||
<strong>Vérifiée par :</strong> <%= HtmlEscape(rs("tp3_verifie_par")) %> —
|
|
||||||
<strong>MAJ par :</strong> <%= HtmlEscape(rs("tp3_maj_par")) %></p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<%
|
|
||||||
rs.MoveNext
|
|
||||||
Loop
|
|
||||||
%>
|
|
||||||
</table>
|
|
||||||
<%
|
|
||||||
End If
|
|
||||||
conn.Close
|
|
||||||
End If
|
|
||||||
End If
|
|
||||||
%>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function toggleDetail(i) {
|
|
||||||
var row = document.getElementById('detail-' + i);
|
|
||||||
row.classList.toggle('open');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
<%
|
|
||||||
Function IIf_Count(rs)
|
|
||||||
Dim n
|
|
||||||
n = 0
|
|
||||||
Do While Not rs.EOF
|
|
||||||
n = n + 1
|
|
||||||
rs.MoveNext
|
|
||||||
Loop
|
|
||||||
rs.MoveFirst
|
|
||||||
IIf_Count = n & " envoi(s)"
|
|
||||||
End Function
|
|
||||||
|
|
||||||
' --- Parsing JSON minimal via ScriptControl pour l'affichage admin ---
|
|
||||||
Function RenderReponses(jsonStr)
|
|
||||||
Dim out
|
|
||||||
out = ""
|
|
||||||
If Trim(jsonStr) = "" Then
|
|
||||||
RenderReponses = "<p><em>Aucune réponse.</em></p>"
|
|
||||||
Exit Function
|
|
||||||
End If
|
|
||||||
Dim sc
|
|
||||||
Set sc = Server.CreateObject("MSScriptControl.ScriptControl")
|
|
||||||
sc.Language = "JScript"
|
|
||||||
sc.AddCode "function parseArr(s){ return eval('(' + s + ')'); }"
|
|
||||||
On Error Resume Next
|
|
||||||
Dim arr
|
|
||||||
Set arr = sc.Run("parseArr", jsonStr)
|
|
||||||
If Err.Number <> 0 Then
|
|
||||||
RenderReponses = "<p><em>Erreur de lecture des réponses.</em></p>"
|
|
||||||
Exit Function
|
|
||||||
End If
|
|
||||||
On Error Goto 0
|
|
||||||
Dim i
|
|
||||||
For i = 0 To arr.length - 1
|
|
||||||
Dim q, r
|
|
||||||
q = arr.Item(i).question
|
|
||||||
r = arr.Item(i).reponse
|
|
||||||
If Trim(r) = "" Then r = "(sans réponse)"
|
|
||||||
out = out & "<p><strong>" & ServerHtmlEscape(q) & " :</strong> " & ServerHtmlEscape(r) & "</p>"
|
|
||||||
Next
|
|
||||||
RenderReponses = out
|
|
||||||
End Function
|
|
||||||
|
|
||||||
Function RenderTableau(jsonStr)
|
|
||||||
Dim out
|
|
||||||
out = ""
|
|
||||||
If Trim(jsonStr) = "" Then
|
|
||||||
RenderTableau = ""
|
|
||||||
Exit Function
|
|
||||||
End If
|
|
||||||
Dim sc
|
|
||||||
Set sc = Server.CreateObject("MSScriptControl.ScriptControl")
|
|
||||||
sc.Language = "JScript"
|
|
||||||
sc.AddCode "function parseArr(s){ return eval('(' + s + ')'); }"
|
|
||||||
On Error Resume Next
|
|
||||||
Dim arr
|
|
||||||
Set arr = sc.Run("parseArr", jsonStr)
|
|
||||||
If Err.Number <> 0 Then
|
|
||||||
RenderTableau = "<p><em>Erreur de lecture du tableau.</em></p>"
|
|
||||||
Exit Function
|
|
||||||
End If
|
|
||||||
On Error Goto 0
|
|
||||||
out = "<table class='risk-mini'><tr><th>Étape</th><th>Risque</th><th>Mesure</th></tr>"
|
|
||||||
Dim i
|
|
||||||
For i = 0 To arr.length - 1
|
|
||||||
out = out & "<tr><td>" & ServerHtmlEscape(arr.Item(i).etape) & "</td><td>" & _
|
|
||||||
ServerHtmlEscape(arr.Item(i).risque) & "</td><td>" & _
|
|
||||||
ServerHtmlEscape(arr.Item(i).mesure) & "</td></tr>"
|
|
||||||
Next
|
|
||||||
out = out & "</table>"
|
|
||||||
RenderTableau = out
|
|
||||||
End Function
|
|
||||||
|
|
||||||
Function ServerHtmlEscape(s)
|
|
||||||
If IsNull(s) Then
|
|
||||||
ServerHtmlEscape = ""
|
|
||||||
Exit Function
|
|
||||||
End If
|
|
||||||
s = CStr(s)
|
|
||||||
s = Replace(s, "&", "&")
|
|
||||||
s = Replace(s, "<", "<")
|
|
||||||
s = Replace(s, ">", ">")
|
|
||||||
ServerHtmlEscape = s
|
|
||||||
End Function
|
|
||||||
%>
|
|
||||||
650
exercices/correction-tp-nuisibles.html
Normal file
650
exercices/correction-tp-nuisibles.html
Normal file
@@ -0,0 +1,650 @@
|
|||||||
|
<!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">NextGN Formation</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 NextGN Formation - Formations Certibiocide & Techniques</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]));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
<%@ Language="VBScript" %>
|
|
||||||
<%
|
|
||||||
' ============================================================
|
|
||||||
' save_results.asp
|
|
||||||
' Reçoit les réponses de l'exercice TP Nuisibles (JSON en POST)
|
|
||||||
' et les enregistre dans la base SQL Server.
|
|
||||||
' ============================================================
|
|
||||||
Response.Buffer = True
|
|
||||||
Response.ContentType = "application/json"
|
|
||||||
Response.CharSet = "UTF-8"
|
|
||||||
|
|
||||||
' ------- À ADAPTER : chaîne de connexion vers votre base -------
|
|
||||||
' Récupérée depuis le panneau "Manage Databases" de FreeASPHosting :
|
|
||||||
' Server Name : sql.bsite.net\MSSQL2016
|
|
||||||
' SQL Username : nextgnformation_reponses_exercices
|
|
||||||
' Database name : (généralement identique au username, à vérifier dans le panneau)
|
|
||||||
Dim connString
|
|
||||||
connString = "Provider=SQLOLEDB;Data Source=sql.bsite.net\MSSQL2016;" & _
|
|
||||||
"Initial Catalog=nextgnformation_reponses_exercices;" & _
|
|
||||||
"User ID=nextgnformation_reponses_exercices;" & _
|
|
||||||
"Password=S5$NyFXzEjJCg6jD;"
|
|
||||||
' -----------------------------------------------------------------
|
|
||||||
|
|
||||||
Function JSONEscape(s)
|
|
||||||
s = Replace(s, "\", "\\")
|
|
||||||
s = Replace(s, Chr(34), "\""")
|
|
||||||
s = Replace(s, vbCrLf, "\n")
|
|
||||||
s = Replace(s, vbLf, "\n")
|
|
||||||
s = Replace(s, vbCr, "\n")
|
|
||||||
s = Replace(s, vbTab, "\t")
|
|
||||||
JSONEscape = s
|
|
||||||
End Function
|
|
||||||
|
|
||||||
Sub SendJSON(success, message)
|
|
||||||
Dim out
|
|
||||||
out = "{""success"":" & LCase(CStr(success)) & ",""error"":""" & JSONEscape(message) & """}"
|
|
||||||
Response.Write out
|
|
||||||
Response.End
|
|
||||||
End Sub
|
|
||||||
|
|
||||||
' --- Lecture du corps de la requête (JSON brut envoyé en POST) ---
|
|
||||||
Dim rawJSON
|
|
||||||
Dim totalBytes
|
|
||||||
totalBytes = Request.TotalBytes
|
|
||||||
If totalBytes = 0 Then
|
|
||||||
SendJSON False, "Aucune donnée reçue."
|
|
||||||
End If
|
|
||||||
|
|
||||||
Dim binData
|
|
||||||
binData = Request.BinaryRead(totalBytes)
|
|
||||||
|
|
||||||
' Conversion des octets UTF-8 en chaîne VBScript
|
|
||||||
Dim objStream
|
|
||||||
Set objStream = Server.CreateObject("ADODB.Stream")
|
|
||||||
objStream.Type = 1 ' adTypeBinary
|
|
||||||
objStream.Open
|
|
||||||
objStream.Write binData
|
|
||||||
objStream.Position = 0
|
|
||||||
objStream.Type = 2 ' adTypeText
|
|
||||||
objStream.CharSet = "utf-8"
|
|
||||||
rawJSON = objStream.ReadText
|
|
||||||
objStream.Close
|
|
||||||
Set objStream = Nothing
|
|
||||||
|
|
||||||
If Trim(rawJSON) = "" Then
|
|
||||||
SendJSON False, "Corps de requête vide."
|
|
||||||
End If
|
|
||||||
|
|
||||||
' --- Parsing JSON minimal sans librairie externe ---
|
|
||||||
' On utilise le moteur JScript via ScriptControl pour parser le JSON de façon fiable.
|
|
||||||
Dim sc
|
|
||||||
Set sc = Server.CreateObject("MSScriptControl.ScriptControl")
|
|
||||||
sc.Language = "JScript"
|
|
||||||
sc.AddCode "function parseIt(s){ return eval('(' + s + ')'); } " & _
|
|
||||||
"function getStr(o,k){ return (o && o[k] != null) ? String(o[k]) : ''; } " & _
|
|
||||||
"function getReponsesJSON(o){ var arr = o.reponses || []; var out = []; " & _
|
|
||||||
"for (var i=0;i<arr.length;i++){ out.push({id:String(arr[i].id||''), question:String(arr[i].question||''), reponse:String(arr[i].reponse||'')}); } return out; } " & _
|
|
||||||
"function getTableauJSON(o){ var arr = o.tp3_tableau || []; var out = []; " & _
|
|
||||||
"for (var i=0;i<arr.length;i++){ out.push({etape:String(arr[i].etape||''), risque:String(arr[i].risque||''), mesure:String(arr[i].mesure||'')}); } return out; }"
|
|
||||||
|
|
||||||
Dim dataObj
|
|
||||||
On Error Resume Next
|
|
||||||
Set dataObj = sc.Run("parseIt", rawJSON)
|
|
||||||
If Err.Number <> 0 Then
|
|
||||||
SendJSON False, "JSON invalide : " & Err.Description
|
|
||||||
End If
|
|
||||||
On Error Goto 0
|
|
||||||
|
|
||||||
Dim nom, dateSession, casChoisi, casCle, dateEnvoi
|
|
||||||
Dim metaIntitule, metaDescription, metaLieu, metaDebut, metaRealise, metaVerifie, metaMaj
|
|
||||||
|
|
||||||
nom = sc.Run("getStr", dataObj, "nom")
|
|
||||||
dateSession = sc.Run("getStr", dataObj, "date_session")
|
|
||||||
casChoisi = sc.Run("getStr", dataObj, "cas_choisi")
|
|
||||||
casCle = sc.Run("getStr", dataObj, "cas_cle")
|
|
||||||
dateEnvoi = sc.Run("getStr", dataObj, "date_envoi")
|
|
||||||
|
|
||||||
Dim tp3Obj
|
|
||||||
tp3Obj = dataObj.tp3_meta
|
|
||||||
metaIntitule = sc.Run("getStr", tp3Obj, "intitule")
|
|
||||||
metaDescription = sc.Run("getStr", tp3Obj, "description")
|
|
||||||
metaLieu = sc.Run("getStr", tp3Obj, "lieu")
|
|
||||||
metaDebut = sc.Run("getStr", tp3Obj, "debut")
|
|
||||||
metaRealise = sc.Run("getStr", tp3Obj, "realise_par")
|
|
||||||
metaVerifie = sc.Run("getStr", tp3Obj, "verifie_par")
|
|
||||||
metaMaj = sc.Run("getStr", tp3Obj, "maj_par")
|
|
||||||
|
|
||||||
If Trim(nom) = "" Then
|
|
||||||
SendJSON False, "Le nom du groupe est obligatoire."
|
|
||||||
End If
|
|
||||||
|
|
||||||
' --- Sérialisation des réponses et du tableau en JSON texte (stockés en colonnes NVARCHAR(MAX)) ---
|
|
||||||
Dim reponsesArr, tableauArr
|
|
||||||
Set reponsesArr = sc.Run("getReponsesJSON", dataObj)
|
|
||||||
Set tableauArr = sc.Run("getTableauJSON", dataObj)
|
|
||||||
|
|
||||||
Dim reponsesJSONStr, tableauJSONStr
|
|
||||||
reponsesJSONStr = "["
|
|
||||||
Dim i
|
|
||||||
For i = 0 To reponsesArr.length - 1
|
|
||||||
If i > 0 Then reponsesJSONStr = reponsesJSONStr & ","
|
|
||||||
reponsesJSONStr = reponsesJSONStr & "{""id"":""" & JSONEscape(reponsesArr.Item(i).id) & _
|
|
||||||
""",""question"":""" & JSONEscape(reponsesArr.Item(i).question) & _
|
|
||||||
""",""reponse"":""" & JSONEscape(reponsesArr.Item(i).reponse) & """}"
|
|
||||||
Next
|
|
||||||
reponsesJSONStr = reponsesJSONStr & "]"
|
|
||||||
|
|
||||||
tableauJSONStr = "["
|
|
||||||
For i = 0 To tableauArr.length - 1
|
|
||||||
If i > 0 Then tableauJSONStr = tableauJSONStr & ","
|
|
||||||
tableauJSONStr = tableauJSONStr & "{""etape"":""" & JSONEscape(tableauArr.Item(i).etape) & _
|
|
||||||
""",""risque"":""" & JSONEscape(tableauArr.Item(i).risque) & _
|
|
||||||
""",""mesure"":""" & JSONEscape(tableauArr.Item(i).mesure) & """}"
|
|
||||||
Next
|
|
||||||
tableauJSONStr = tableauJSONStr & "]"
|
|
||||||
|
|
||||||
' --- Connexion et insertion en base ---
|
|
||||||
Dim conn
|
|
||||||
Set conn = Server.CreateObject("ADODB.Connection")
|
|
||||||
|
|
||||||
On Error Resume Next
|
|
||||||
conn.Open connString
|
|
||||||
If Err.Number <> 0 Then
|
|
||||||
SendJSON False, "Connexion BDD impossible : " & Err.Description
|
|
||||||
End If
|
|
||||||
On Error Goto 0
|
|
||||||
|
|
||||||
' Création de la table si elle n'existe pas encore
|
|
||||||
Dim createTableSQL
|
|
||||||
createTableSQL = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='tp_resultats' AND xtype='U') " & _
|
|
||||||
"CREATE TABLE tp_resultats (" & _
|
|
||||||
"id INT IDENTITY(1,1) PRIMARY KEY, " & _
|
|
||||||
"nom NVARCHAR(255), " & _
|
|
||||||
"date_session NVARCHAR(50), " & _
|
|
||||||
"cas_choisi NVARCHAR(255), " & _
|
|
||||||
"cas_cle NVARCHAR(100), " & _
|
|
||||||
"reponses_json NVARCHAR(MAX), " & _
|
|
||||||
"tp3_intitule NVARCHAR(500), " & _
|
|
||||||
"tp3_description NVARCHAR(1000), " & _
|
|
||||||
"tp3_lieu NVARCHAR(500), " & _
|
|
||||||
"tp3_debut NVARCHAR(50), " & _
|
|
||||||
"tp3_realise_par NVARCHAR(255), " & _
|
|
||||||
"tp3_verifie_par NVARCHAR(255), " & _
|
|
||||||
"tp3_maj_par NVARCHAR(255), " & _
|
|
||||||
"tp3_tableau_json NVARCHAR(MAX), " & _
|
|
||||||
"date_envoi NVARCHAR(50), " & _
|
|
||||||
"date_enregistrement DATETIME DEFAULT GETDATE())"
|
|
||||||
|
|
||||||
On Error Resume Next
|
|
||||||
conn.Execute createTableSQL
|
|
||||||
If Err.Number <> 0 Then
|
|
||||||
SendJSON False, "Création de table impossible : " & Err.Description
|
|
||||||
End If
|
|
||||||
On Error Goto 0
|
|
||||||
|
|
||||||
' Insertion via requête paramétrée (Command) pour éviter les injections SQL
|
|
||||||
Dim cmd
|
|
||||||
Set cmd = Server.CreateObject("ADODB.Command")
|
|
||||||
cmd.ActiveConnection = conn
|
|
||||||
cmd.CommandText = "INSERT INTO tp_resultats " & _
|
|
||||||
"(nom, date_session, cas_choisi, cas_cle, reponses_json, tp3_intitule, tp3_description, tp3_lieu, tp3_debut, tp3_realise_par, tp3_verifie_par, tp3_maj_par, tp3_tableau_json, date_envoi) " & _
|
|
||||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
cmd.CommandType = 1 ' adCmdText
|
|
||||||
|
|
||||||
Dim p
|
|
||||||
Set p = cmd.CreateParameter("nom", 202, 1, 255, nom) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("date_session", 202, 1, 50, dateSession) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("cas_choisi", 202, 1, 255, casChoisi) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("cas_cle", 202, 1, 100, casCle) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("reponses_json", 203, 1, Len(reponsesJSONStr), reponsesJSONStr) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("tp3_intitule", 202, 1, 500, metaIntitule) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("tp3_description", 202, 1, 1000, metaDescription) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("tp3_lieu", 202, 1, 500, metaLieu) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("tp3_debut", 202, 1, 50, metaDebut) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("tp3_realise_par", 202, 1, 255, metaRealise) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("tp3_verifie_par", 202, 1, 255, metaVerifie) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("tp3_maj_par", 202, 1, 255, metaMaj) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("tp3_tableau_json", 203, 1, Len(tableauJSONStr), tableauJSONStr) : cmd.Parameters.Append p
|
|
||||||
Set p = cmd.CreateParameter("date_envoi", 202, 1, 50, dateEnvoi) : cmd.Parameters.Append p
|
|
||||||
|
|
||||||
On Error Resume Next
|
|
||||||
cmd.Execute
|
|
||||||
If Err.Number <> 0 Then
|
|
||||||
conn.Close
|
|
||||||
SendJSON False, "Erreur lors de l'insertion : " & Err.Description
|
|
||||||
End If
|
|
||||||
On Error Goto 0
|
|
||||||
|
|
||||||
conn.Close
|
|
||||||
Set conn = Nothing
|
|
||||||
|
|
||||||
SendJSON True, ""
|
|
||||||
%>
|
|
||||||
Reference in New Issue
Block a user