Update index.html
Browse files- index.html +114 -35
index.html
CHANGED
|
@@ -6,7 +6,9 @@
|
|
| 6 |
<title>BuSCA - Veille Sanitaire</title>
|
| 7 |
|
| 8 |
<!-- Bibliothèques externes -->
|
|
|
|
| 9 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
|
|
|
| 10 |
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
| 11 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
| 12 |
|
|
@@ -133,6 +135,7 @@
|
|
| 133 |
</head>
|
| 134 |
<body>
|
| 135 |
<div class="app-container">
|
|
|
|
| 136 |
<aside class="sidebar" id="sidebar">
|
| 137 |
<div class="sidebar-header">
|
| 138 |
<div class="logo-title-group">
|
|
@@ -159,13 +162,14 @@
|
|
| 159 |
</div>
|
| 160 |
</aside>
|
| 161 |
|
|
|
|
| 162 |
<div class="content-wrapper">
|
| 163 |
<div class="top-bar">
|
| 164 |
<button class="open-sidebar-btn" id="openSidebarBtn" aria-label="Ouvrir le menu">☰</button>
|
| 165 |
<h1 class="top-bar-title">Veille Sanitaire BuSCA</h1>
|
| 166 |
</div>
|
| 167 |
<main class="main-content">
|
| 168 |
-
<div class="alert"><strong>
|
| 169 |
<section class="stats-container" id="statsContainer"></section>
|
| 170 |
<section class="results-section">
|
| 171 |
<h2>📑 Derniers bulletins</h2>
|
|
@@ -177,6 +181,7 @@
|
|
| 177 |
|
| 178 |
<script>
|
| 179 |
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
| 180 |
const ui = {
|
| 181 |
sidebar: document.getElementById('sidebar'),
|
| 182 |
openSidebarBtn: document.getElementById('openSidebarBtn'),
|
|
@@ -191,49 +196,78 @@
|
|
| 191 |
keywords: document.getElementById('keywordsFilter'),
|
| 192 |
}
|
| 193 |
};
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
| 196 |
const PROXY_URL = `https://corsproxy.io/?${encodeURIComponent(REAL_DATA_URL)}`;
|
|
|
|
| 197 |
let allData = [];
|
| 198 |
let filteredData = [];
|
|
|
|
|
|
|
| 199 |
function initializeApp() {
|
| 200 |
setupEventListeners();
|
| 201 |
updateData();
|
| 202 |
}
|
|
|
|
|
|
|
| 203 |
async function updateData() {
|
| 204 |
-
ui.resultsContainer.innerHTML = '<div class="loading-overlay">Chargement des données...</div>';
|
| 205 |
ui.updateDataBtn.disabled = true;
|
| 206 |
ui.updateDataBtn.querySelector('i').classList.add('fa-spin');
|
|
|
|
| 207 |
try {
|
|
|
|
| 208 |
const response = await fetch(PROXY_URL);
|
| 209 |
if (!response.ok) {
|
| 210 |
throw new Error(`Erreur réseau: ${response.status} ${response.statusText}`);
|
| 211 |
}
|
|
|
|
|
|
|
| 212 |
const arrayBuffer = await response.arrayBuffer();
|
| 213 |
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
const rawJsonData = XLSX.utils.sheet_to_json(worksheet);
|
|
|
|
| 216 |
if (!rawJsonData || !Array.isArray(rawJsonData)) {
|
| 217 |
-
throw new Error("
|
| 218 |
}
|
| 219 |
-
|
| 220 |
-
//
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
showNotification('Données mises à jour avec succès !', 'success');
|
| 233 |
processData();
|
|
|
|
| 234 |
} catch (error) {
|
| 235 |
console.error('Erreur lors de la mise à jour:', error);
|
| 236 |
-
ui.resultsContainer.innerHTML = `
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
showNotification('Erreur de mise à jour.', 'error');
|
| 238 |
} finally {
|
| 239 |
ui.updateDataBtn.disabled = false;
|
|
@@ -241,18 +275,20 @@
|
|
| 241 |
}
|
| 242 |
}
|
| 243 |
|
|
|
|
| 244 |
function processData() {
|
| 245 |
applyFilters();
|
| 246 |
}
|
|
|
|
| 247 |
function applyFilters() {
|
| 248 |
const criteria = {
|
| 249 |
keywords: ui.filters.keywords.value.toLowerCase().split(',').map(k => k.trim()).filter(Boolean)
|
| 250 |
};
|
|
|
|
| 251 |
if (criteria.keywords.length === 0) {
|
| 252 |
filteredData = allData;
|
| 253 |
} else {
|
| 254 |
filteredData = allData.filter(item => {
|
| 255 |
-
// On cherche dans les clés internes 'Danger' et 'Matrice'
|
| 256 |
const itemText = (item.Titre + ' ' + item.Texte + ' ' + item.Danger + ' ' + item.Matrice).toLowerCase();
|
| 257 |
return criteria.keywords.every(k => itemText.includes(k));
|
| 258 |
});
|
|
@@ -267,27 +303,35 @@
|
|
| 267 |
applyFilters();
|
| 268 |
showNotification('Filtres réinitialisés.', 'info');
|
| 269 |
}
|
|
|
|
| 270 |
function displayResults() {
|
| 271 |
if (filteredData.length === 0) {
|
| 272 |
ui.resultsContainer.innerHTML = '<div class="empty-state">Aucun bulletin ne correspond à vos critères.</div>';
|
| 273 |
return;
|
| 274 |
}
|
|
|
|
|
|
|
| 275 |
const sortedData = [...filteredData].sort((a, b) => b.BuSCA - a.BuSCA);
|
| 276 |
-
|
| 277 |
ui.resultsContainer.innerHTML = sortedData.map(item => `
|
| 278 |
<article class="bulletin-card">
|
| 279 |
<div class="bulletin-header" data-toggle="content">
|
| 280 |
-
<h3>${item.Titre || '
|
| 281 |
<span class="bulletin-number">BuSCA n°${item.BuSCA}</span>
|
| 282 |
</div>
|
| 283 |
<div class="bulletin-content">
|
| 284 |
-
<p class="bulletin-summary"><strong>Texte:</strong><br>${item.Texte.replace(/\n/g, '<br>') || 'Contenu non disponible.'}</p>
|
| 285 |
<div class="bulletin-meta">
|
| 286 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
</div>
|
| 288 |
-
<
|
| 289 |
-
|
| 290 |
-
${item.
|
|
|
|
| 291 |
</div>
|
| 292 |
</div>
|
| 293 |
</article>`).join('');
|
|
@@ -298,28 +342,45 @@
|
|
| 298 |
const foundBulletins = filteredData.length;
|
| 299 |
const uniqueMatrices = new Set(filteredData.map(i => i.Matrice)).size;
|
| 300 |
const uniqueDangers = new Set(filteredData.map(i => i.Danger)).size;
|
|
|
|
| 301 |
ui.statsContainer.innerHTML = `
|
| 302 |
-
<div class="stat-card"><div class="stat-number">${foundBulletins} / ${totalBulletins}</div><div class="stat-label">Bulletins
|
| 303 |
<div class="stat-card"><div class="stat-number">${uniqueMatrices}</div><div class="stat-label">Matrices Concernées</div></div>
|
| 304 |
<div class="stat-card"><div class="stat-number">${uniqueDangers}</div><div class="stat-label">Dangers Identifiés</div></div>
|
| 305 |
`;
|
| 306 |
}
|
|
|
|
|
|
|
| 307 |
function setupEventListeners() {
|
| 308 |
ui.openSidebarBtn.addEventListener('click', () => ui.sidebar.classList.remove('collapsed'));
|
| 309 |
ui.sidebarToggle.addEventListener('click', () => ui.sidebar.classList.add('collapsed'));
|
|
|
|
| 310 |
ui.updateDataBtn.addEventListener('click', updateData);
|
|
|
|
| 311 |
ui.applyFiltersBtn.addEventListener('click', () => {
|
| 312 |
applyFilters();
|
| 313 |
showNotification('Filtres appliqués !', 'success');
|
| 314 |
});
|
|
|
|
| 315 |
ui.clearFiltersBtn.addEventListener('click', clearFilters);
|
| 316 |
ui.exportCsvBtn.addEventListener('click', exportToCSV);
|
| 317 |
|
|
|
|
| 318 |
ui.resultsContainer.addEventListener('click', (e) => {
|
| 319 |
const header = e.target.closest('[data-toggle="content"]');
|
| 320 |
-
if (header)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
});
|
| 322 |
|
|
|
|
| 323 |
document.addEventListener('keydown', (e) => {
|
| 324 |
if (e.key === 'Escape' && !ui.sidebar.classList.contains('collapsed')) {
|
| 325 |
ui.sidebar.classList.add('collapsed');
|
|
@@ -330,42 +391,60 @@
|
|
| 330 |
}
|
| 331 |
});
|
| 332 |
}
|
|
|
|
| 333 |
function showNotification(message, type = 'info') {
|
| 334 |
const notification = document.createElement('div');
|
| 335 |
notification.className = `notification-toast ${type}`;
|
| 336 |
notification.textContent = message;
|
| 337 |
document.body.appendChild(notification);
|
|
|
|
|
|
|
| 338 |
requestAnimationFrame(() => notification.classList.add('show'));
|
|
|
|
| 339 |
setTimeout(() => {
|
| 340 |
notification.classList.remove('show');
|
| 341 |
notification.addEventListener('transitionend', () => notification.remove());
|
| 342 |
}, 3000);
|
| 343 |
}
|
|
|
|
| 344 |
function exportToCSV() {
|
| 345 |
if (filteredData.length === 0) {
|
| 346 |
showNotification('Aucune donnée à exporter', 'error');
|
| 347 |
return;
|
| 348 |
}
|
|
|
|
| 349 |
const headers = ['BuSCA', 'Titre', 'Matrice', 'Danger', 'Texte', 'Lien', 'Lien2'];
|
| 350 |
const escapeCsv = (str) => `"${String(str || '').replace(/"/g, '""')}"`;
|
|
|
|
| 351 |
const csvContent = [
|
| 352 |
headers.join(','),
|
| 353 |
...filteredData.map(item => [
|
| 354 |
-
item.BuSCA,
|
| 355 |
-
escapeCsv(item.
|
| 356 |
-
escapeCsv(item.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
].join(','))
|
| 358 |
].join('\n');
|
|
|
|
| 359 |
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
| 360 |
const link = document.createElement('a');
|
| 361 |
-
|
| 362 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
document.body.appendChild(link);
|
| 364 |
link.click();
|
| 365 |
document.body.removeChild(link);
|
|
|
|
| 366 |
showNotification('Export CSV généré !', 'success');
|
| 367 |
}
|
| 368 |
|
|
|
|
| 369 |
initializeApp();
|
| 370 |
});
|
| 371 |
</script>
|
|
|
|
| 6 |
<title>BuSCA - Veille Sanitaire</title>
|
| 7 |
|
| 8 |
<!-- Bibliothèques externes -->
|
| 9 |
+
<!-- SheetJS pour lire le fichier Excel -->
|
| 10 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
| 11 |
+
<!-- Polices et Icônes -->
|
| 12 |
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
| 13 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
| 14 |
|
|
|
|
| 135 |
</head>
|
| 136 |
<body>
|
| 137 |
<div class="app-container">
|
| 138 |
+
<!-- BARRE LATÉRALE -->
|
| 139 |
<aside class="sidebar" id="sidebar">
|
| 140 |
<div class="sidebar-header">
|
| 141 |
<div class="logo-title-group">
|
|
|
|
| 162 |
</div>
|
| 163 |
</aside>
|
| 164 |
|
| 165 |
+
<!-- CONTENU PRINCIPAL -->
|
| 166 |
<div class="content-wrapper">
|
| 167 |
<div class="top-bar">
|
| 168 |
<button class="open-sidebar-btn" id="openSidebarBtn" aria-label="Ouvrir le menu">☰</button>
|
| 169 |
<h1 class="top-bar-title">Veille Sanitaire BuSCA</h1>
|
| 170 |
</div>
|
| 171 |
<main class="main-content">
|
| 172 |
+
<div class="alert"><strong>Info :</strong> Les données sont chargées depuis le fichier "Download" de la plateforme SCA.</div>
|
| 173 |
<section class="stats-container" id="statsContainer"></section>
|
| 174 |
<section class="results-section">
|
| 175 |
<h2>📑 Derniers bulletins</h2>
|
|
|
|
| 181 |
|
| 182 |
<script>
|
| 183 |
document.addEventListener('DOMContentLoaded', () => {
|
| 184 |
+
// --- ÉLÉMENTS UI ---
|
| 185 |
const ui = {
|
| 186 |
sidebar: document.getElementById('sidebar'),
|
| 187 |
openSidebarBtn: document.getElementById('openSidebarBtn'),
|
|
|
|
| 196 |
keywords: document.getElementById('keywordsFilter'),
|
| 197 |
}
|
| 198 |
};
|
| 199 |
+
|
| 200 |
+
// --- CONFIGURATION DE L'URL ---
|
| 201 |
+
// Nouvelle URL de téléchargement direct
|
| 202 |
+
const REAL_DATA_URL = 'https://www.plateforme-sca.fr/media/398/download';
|
| 203 |
+
// Utilisation d'un proxy CORS pour permettre au navigateur de lire le fichier distant
|
| 204 |
const PROXY_URL = `https://corsproxy.io/?${encodeURIComponent(REAL_DATA_URL)}`;
|
| 205 |
+
|
| 206 |
let allData = [];
|
| 207 |
let filteredData = [];
|
| 208 |
+
|
| 209 |
+
// --- INITIALISATION ---
|
| 210 |
function initializeApp() {
|
| 211 |
setupEventListeners();
|
| 212 |
updateData();
|
| 213 |
}
|
| 214 |
+
|
| 215 |
+
// --- CHARGEMENT DES DONNÉES ---
|
| 216 |
async function updateData() {
|
| 217 |
+
ui.resultsContainer.innerHTML = '<div class="loading-overlay">Chargement des données depuis la plateforme SCA...</div>';
|
| 218 |
ui.updateDataBtn.disabled = true;
|
| 219 |
ui.updateDataBtn.querySelector('i').classList.add('fa-spin');
|
| 220 |
+
|
| 221 |
try {
|
| 222 |
+
// Fetch via le proxy
|
| 223 |
const response = await fetch(PROXY_URL);
|
| 224 |
if (!response.ok) {
|
| 225 |
throw new Error(`Erreur réseau: ${response.status} ${response.statusText}`);
|
| 226 |
}
|
| 227 |
+
|
| 228 |
+
// Lecture du fichier binaire (Excel)
|
| 229 |
const arrayBuffer = await response.arrayBuffer();
|
| 230 |
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
| 231 |
+
|
| 232 |
+
// On prend la première feuille du classeur
|
| 233 |
+
const firstSheetName = workbook.SheetNames[0];
|
| 234 |
+
const worksheet = workbook.Sheets[firstSheetName];
|
| 235 |
+
|
| 236 |
+
// Conversion en JSON
|
| 237 |
const rawJsonData = XLSX.utils.sheet_to_json(worksheet);
|
| 238 |
+
|
| 239 |
if (!rawJsonData || !Array.isArray(rawJsonData)) {
|
| 240 |
+
throw new Error("Le fichier Excel est vide ou illisible.");
|
| 241 |
}
|
| 242 |
+
|
| 243 |
+
// --- MAPPING ET NETTOYAGE ---
|
| 244 |
+
// On s'assure de lire les bonnes colonnes même si les noms varient légèrement (Pluriel/Singulier)
|
| 245 |
+
allData = rawJsonData.map(item => {
|
| 246 |
+
return {
|
| 247 |
+
'BuSCA': parseInt(item['BuSCA'], 10) || 0,
|
| 248 |
+
'Titre': String(item['Titre'] || '').trim(),
|
| 249 |
+
'Texte': String(item['Texte'] || '').trim(),
|
| 250 |
+
// Gestion flexible des noms de colonnes : 'Matrices' ou 'Matrice'
|
| 251 |
+
'Matrice': String(item['Matrices'] || item['Matrice'] || 'Non spécifié').trim(),
|
| 252 |
+
// Gestion flexible des noms de colonnes : 'Dangers' ou 'Danger'
|
| 253 |
+
'Danger': String(item['Dangers'] || item['Danger'] || 'Non spécifié').trim(),
|
| 254 |
+
'Lien': String(item['Lien'] || '').trim(),
|
| 255 |
+
'Lien2': String(item['Lien2'] || '').trim()
|
| 256 |
+
};
|
| 257 |
+
}).filter(item => item.BuSCA > 0 && item.Titre); // On garde seulement les lignes avec un N° BuSCA et un titre
|
| 258 |
+
|
| 259 |
showNotification('Données mises à jour avec succès !', 'success');
|
| 260 |
processData();
|
| 261 |
+
|
| 262 |
} catch (error) {
|
| 263 |
console.error('Erreur lors de la mise à jour:', error);
|
| 264 |
+
ui.resultsContainer.innerHTML = `
|
| 265 |
+
<div class="empty-state" style="color: #dc3545;">
|
| 266 |
+
<i class="fas fa-exclamation-triangle fa-2x" style="margin-bottom:15px;"></i><br>
|
| 267 |
+
<strong>Échec du chargement.</strong><br>
|
| 268 |
+
Le fichier source est inaccessible ou le format a changé.<br>
|
| 269 |
+
<small>${error.message}</small>
|
| 270 |
+
</div>`;
|
| 271 |
showNotification('Erreur de mise à jour.', 'error');
|
| 272 |
} finally {
|
| 273 |
ui.updateDataBtn.disabled = false;
|
|
|
|
| 275 |
}
|
| 276 |
}
|
| 277 |
|
| 278 |
+
// --- TRAITEMENT ET AFFICHAGE ---
|
| 279 |
function processData() {
|
| 280 |
applyFilters();
|
| 281 |
}
|
| 282 |
+
|
| 283 |
function applyFilters() {
|
| 284 |
const criteria = {
|
| 285 |
keywords: ui.filters.keywords.value.toLowerCase().split(',').map(k => k.trim()).filter(Boolean)
|
| 286 |
};
|
| 287 |
+
|
| 288 |
if (criteria.keywords.length === 0) {
|
| 289 |
filteredData = allData;
|
| 290 |
} else {
|
| 291 |
filteredData = allData.filter(item => {
|
|
|
|
| 292 |
const itemText = (item.Titre + ' ' + item.Texte + ' ' + item.Danger + ' ' + item.Matrice).toLowerCase();
|
| 293 |
return criteria.keywords.every(k => itemText.includes(k));
|
| 294 |
});
|
|
|
|
| 303 |
applyFilters();
|
| 304 |
showNotification('Filtres réinitialisés.', 'info');
|
| 305 |
}
|
| 306 |
+
|
| 307 |
function displayResults() {
|
| 308 |
if (filteredData.length === 0) {
|
| 309 |
ui.resultsContainer.innerHTML = '<div class="empty-state">Aucun bulletin ne correspond à vos critères.</div>';
|
| 310 |
return;
|
| 311 |
}
|
| 312 |
+
|
| 313 |
+
// Tri par numéro BuSCA décroissant (les plus récents en premier)
|
| 314 |
const sortedData = [...filteredData].sort((a, b) => b.BuSCA - a.BuSCA);
|
| 315 |
+
|
| 316 |
ui.resultsContainer.innerHTML = sortedData.map(item => `
|
| 317 |
<article class="bulletin-card">
|
| 318 |
<div class="bulletin-header" data-toggle="content">
|
| 319 |
+
<h3>${item.Titre || 'Sans titre'}</h3>
|
| 320 |
<span class="bulletin-number">BuSCA n°${item.BuSCA}</span>
|
| 321 |
</div>
|
| 322 |
<div class="bulletin-content">
|
|
|
|
| 323 |
<div class="bulletin-meta">
|
| 324 |
+
<span style="background: #e3f2fd; color: #1565C0; padding: 4px 8px; border-radius: 4px; margin-right: 10px;">
|
| 325 |
+
<i class="fas fa-vial"></i> <strong>Matrice:</strong> ${item.Matrice}
|
| 326 |
+
</span>
|
| 327 |
+
<span style="background: #ffebee; color: #c62828; padding: 4px 8px; border-radius: 4px;">
|
| 328 |
+
<i class="fas fa-exclamation-circle"></i> <strong>Danger:</strong> ${item.Danger}
|
| 329 |
+
</span>
|
| 330 |
</div>
|
| 331 |
+
<p class="bulletin-summary"><strong>Résumé :</strong><br>${item.Texte.replace(/\n/g, '<br>') || 'Contenu non disponible.'}</p>
|
| 332 |
+
<div class="bulletin-links" style="margin-top: 15px; border-top: 1px dashed #eee; padding-top: 10px;">
|
| 333 |
+
${item.Lien ? `<a href="${item.Lien}" class="nav-button" style="display:inline-flex; background:#1E88E5; width:auto; font-size:0.8em;" target="_blank">🔗 Lien principal</a>` : ''}
|
| 334 |
+
${item.Lien2 ? `<a href="${item.Lien2}" class="nav-button" style="display:inline-flex; background:#555; width:auto; font-size:0.8em; margin-left: 10px;" target="_blank">🔗 Lien secondaire</a>` : ''}
|
| 335 |
</div>
|
| 336 |
</div>
|
| 337 |
</article>`).join('');
|
|
|
|
| 342 |
const foundBulletins = filteredData.length;
|
| 343 |
const uniqueMatrices = new Set(filteredData.map(i => i.Matrice)).size;
|
| 344 |
const uniqueDangers = new Set(filteredData.map(i => i.Danger)).size;
|
| 345 |
+
|
| 346 |
ui.statsContainer.innerHTML = `
|
| 347 |
+
<div class="stat-card"><div class="stat-number">${foundBulletins} <small style="font-size:0.5em; color:#999">/ ${totalBulletins}</small></div><div class="stat-label">Bulletins Affichés</div></div>
|
| 348 |
<div class="stat-card"><div class="stat-number">${uniqueMatrices}</div><div class="stat-label">Matrices Concernées</div></div>
|
| 349 |
<div class="stat-card"><div class="stat-number">${uniqueDangers}</div><div class="stat-label">Dangers Identifiés</div></div>
|
| 350 |
`;
|
| 351 |
}
|
| 352 |
+
|
| 353 |
+
// --- ÉVÉNEMENTS ---
|
| 354 |
function setupEventListeners() {
|
| 355 |
ui.openSidebarBtn.addEventListener('click', () => ui.sidebar.classList.remove('collapsed'));
|
| 356 |
ui.sidebarToggle.addEventListener('click', () => ui.sidebar.classList.add('collapsed'));
|
| 357 |
+
|
| 358 |
ui.updateDataBtn.addEventListener('click', updateData);
|
| 359 |
+
|
| 360 |
ui.applyFiltersBtn.addEventListener('click', () => {
|
| 361 |
applyFilters();
|
| 362 |
showNotification('Filtres appliqués !', 'success');
|
| 363 |
});
|
| 364 |
+
|
| 365 |
ui.clearFiltersBtn.addEventListener('click', clearFilters);
|
| 366 |
ui.exportCsvBtn.addEventListener('click', exportToCSV);
|
| 367 |
|
| 368 |
+
// Délégation d'événement pour l'accordéon des résultats
|
| 369 |
ui.resultsContainer.addEventListener('click', (e) => {
|
| 370 |
const header = e.target.closest('[data-toggle="content"]');
|
| 371 |
+
if (header) {
|
| 372 |
+
const content = header.nextElementSibling;
|
| 373 |
+
const isActive = content.classList.contains('active');
|
| 374 |
+
|
| 375 |
+
// Optionnel : fermer les autres si on veut un accordéon strict
|
| 376 |
+
// document.querySelectorAll('.bulletin-content').forEach(c => c.classList.remove('active'));
|
| 377 |
+
|
| 378 |
+
if (!isActive) content.classList.add('active');
|
| 379 |
+
else content.classList.remove('active');
|
| 380 |
+
}
|
| 381 |
});
|
| 382 |
|
| 383 |
+
// Raccourcis clavier
|
| 384 |
document.addEventListener('keydown', (e) => {
|
| 385 |
if (e.key === 'Escape' && !ui.sidebar.classList.contains('collapsed')) {
|
| 386 |
ui.sidebar.classList.add('collapsed');
|
|
|
|
| 391 |
}
|
| 392 |
});
|
| 393 |
}
|
| 394 |
+
|
| 395 |
function showNotification(message, type = 'info') {
|
| 396 |
const notification = document.createElement('div');
|
| 397 |
notification.className = `notification-toast ${type}`;
|
| 398 |
notification.textContent = message;
|
| 399 |
document.body.appendChild(notification);
|
| 400 |
+
|
| 401 |
+
// Animation
|
| 402 |
requestAnimationFrame(() => notification.classList.add('show'));
|
| 403 |
+
|
| 404 |
setTimeout(() => {
|
| 405 |
notification.classList.remove('show');
|
| 406 |
notification.addEventListener('transitionend', () => notification.remove());
|
| 407 |
}, 3000);
|
| 408 |
}
|
| 409 |
+
|
| 410 |
function exportToCSV() {
|
| 411 |
if (filteredData.length === 0) {
|
| 412 |
showNotification('Aucune donnée à exporter', 'error');
|
| 413 |
return;
|
| 414 |
}
|
| 415 |
+
|
| 416 |
const headers = ['BuSCA', 'Titre', 'Matrice', 'Danger', 'Texte', 'Lien', 'Lien2'];
|
| 417 |
const escapeCsv = (str) => `"${String(str || '').replace(/"/g, '""')}"`;
|
| 418 |
+
|
| 419 |
const csvContent = [
|
| 420 |
headers.join(','),
|
| 421 |
...filteredData.map(item => [
|
| 422 |
+
item.BuSCA,
|
| 423 |
+
escapeCsv(item.Titre),
|
| 424 |
+
escapeCsv(item.Matrice),
|
| 425 |
+
escapeCsv(item.Danger),
|
| 426 |
+
escapeCsv(item.Texte),
|
| 427 |
+
escapeCsv(item.Lien),
|
| 428 |
+
escapeCsv(item.Lien2)
|
| 429 |
].join(','))
|
| 430 |
].join('\n');
|
| 431 |
+
|
| 432 |
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
| 433 |
const link = document.createElement('a');
|
| 434 |
+
const url = URL.createObjectURL(blob);
|
| 435 |
+
|
| 436 |
+
link.setAttribute('href', url);
|
| 437 |
+
link.setAttribute('download', `busca_export_${new Date().toISOString().split('T')[0]}.csv`);
|
| 438 |
+
link.style.visibility = 'hidden';
|
| 439 |
+
|
| 440 |
document.body.appendChild(link);
|
| 441 |
link.click();
|
| 442 |
document.body.removeChild(link);
|
| 443 |
+
|
| 444 |
showNotification('Export CSV généré !', 'success');
|
| 445 |
}
|
| 446 |
|
| 447 |
+
// Lancement de l'app
|
| 448 |
initializeApp();
|
| 449 |
});
|
| 450 |
</script>
|