MMOON commited on
Commit
e39fcc3
·
verified ·
1 Parent(s): f037d39

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +482 -0
index.html ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>BuSCA - Veille Sanitaire 2026</title>
7
+
8
+ <!-- SheetJS pour Excel -->
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
10
+ <!-- FontAwesome & Google Fonts -->
11
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
13
+
14
+ <style>
15
+ :root {
16
+ --primary: #1E88E5; --dark: #2E3B4E; --light: #f8f9fa;
17
+ --white: #fff; --grey: #e0e0e0; --success: #28a745; --danger: #dc3545;
18
+ }
19
+ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Roboto', sans-serif; }
20
+ body { background: var(--light); color: #333; display: flex; height: 100vh; overflow: hidden; }
21
+ /* --- SIDEBAR --- */
22
+ aside {
23
+ width: 280px; background: var(--dark); color: #ccc;
24
+ display: flex; flex-direction: column; border-right: 1px solid #444;
25
+ flex-shrink: 0;
26
+ }
27
+ .brand { padding: 15px; border-bottom: 1px solid #444; display: flex; align-items: center; gap: 10px; color: white; }
28
+ .brand img { width: 35px; border-radius: 5px; }
29
+
30
+ .controls { padding: 15px; display: flex; flex-direction: column; gap: 10px; overflow-y: auto; }
31
+
32
+ button, .btn-upload {
33
+ background: #3a4a60; border: none; color: white; padding: 10px; border-radius: 5px;
34
+ cursor: pointer; display: flex; align-items: center; gap: 10px; font-size: 0.9em;
35
+ transition: 0.2s; text-decoration: none; justify-content: flex-start;
36
+ }
37
+ button:hover, .btn-upload:hover { background: #4a5a70; transform: translateX(3px); }
38
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
39
+ .btn-primary { background: var(--primary); }
40
+ .btn-success { background: var(--success); }
41
+ .btn-warning { background: #ffc107; color: #333; animation: pulse 2s infinite; }
42
+
43
+ @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(255, 193, 7, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); } }
44
+ /* ZONE FILTRES */
45
+ .filter-box { margin-top: 15px; background: rgba(0,0,0,0.2); padding: 10px; border-radius: 5px; }
46
+ .filter-box label { display: block; margin-bottom: 5px; font-size: 0.85em; color: #aaa; }
47
+ textarea {
48
+ width: 100%; background: #222; border: 1px solid #555; color: white; padding: 8px;
49
+ border-radius: 4px; resize: vertical; min-height: 80px;
50
+ }
51
+ textarea:focus { outline: 1px solid var(--primary); }
52
+ /* --- MAIN CONTENT --- */
53
+ main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
54
+
55
+ header {
56
+ height: 60px; background: white; border-bottom: 1px solid var(--grey);
57
+ display: flex; align-items: center; padding: 0 20px; font-size: 1.2em; font-weight: 500;
58
+ }
59
+ .scroll-area { flex: 1; overflow-y: auto; padding: 20px; }
60
+ /* STATS */
61
+ .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-bottom: 20px; }
62
+ .stat-item { background: white; padding: 15px; border-radius: 8px; text-align: center; box-shadow: 0 2px 5px rgba(0,0,0,0.05); border-top: 4px solid var(--primary); }
63
+ .stat-val { font-size: 1.8em; font-weight: bold; color: var(--dark); }
64
+ .stat-label { font-size: 0.8em; color: #666; }
65
+ /* CARDS */
66
+ .card { background: white; border-radius: 8px; margin-bottom: 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); overflow: hidden; }
67
+ .card-header {
68
+ padding: 15px; background: linear-gradient(to right, #1E88E5, #42A5F5); color: white;
69
+ cursor: pointer; display: flex; justify-content: space-between; align-items: center;
70
+ }
71
+ .card-body { padding: 20px; display: none; border-top: 1px solid #eee; }
72
+ .card-body.open { display: block; animation: fadeIn 0.3s; }
73
+ @keyframes fadeIn { from { opacity:0; transform:translateY(-5px); } to { opacity:1; transform:translateY(0); } }
74
+ .tags { margin-bottom: 15px; }
75
+ .tag { padding: 3px 8px; border-radius: 4px; font-size: 0.85em; margin-right: 5px; display: inline-block; }
76
+ .tag-danger { background: #ffebee; color: #c62828; border: 1px solid #ffcdd2; }
77
+ .tag-matrix { background: #e3f2fd; color: #1565C0; border: 1px solid #bbdefb; }
78
+ /* NOTIFICATIONS & LOADERS */
79
+ .loader { text-align: center; padding: 50px; color: #666; }
80
+ .msg-box { padding: 15px; border-radius: 5px; margin-bottom: 20px; text-align: center; }
81
+ .msg-block { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; border-radius: 5px; padding: 15px; margin-bottom: 20px; text-align: center; }
82
+ .msg-block i { font-size: 2em; display: block; margin-bottom: 10px; }
83
+ .msg-block strong { font-size: 1.1em; }
84
+ .msg-warn { background: #fff3cd; color: #856404; border: 1px solid #ffeeba; border-radius: 5px; padding: 15px; margin-bottom: 20px; text-align: center; }
85
+ .msg-info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; border-radius: 5px; padding: 15px; margin-bottom: 20px; text-align: center; }
86
+ .msg-success-block { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; border-radius: 5px; padding: 15px; margin-bottom: 20px; text-align: center; }
87
+ /* OVERLAY BLOCAGE */
88
+ .overlay-block {
89
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
90
+ background: rgba(0,0,0,0.5); z-index: 9999;
91
+ display: flex; align-items: center; justify-content: center;
92
+ }
93
+ .overlay-block .modal-block {
94
+ background: white; padding: 30px; border-radius: 12px;
95
+ max-width: 500px; text-align: center; box-shadow: 0 10px 40px rgba(0,0,0,0.3);
96
+ }
97
+ .overlay-block .modal-block i { font-size: 3em; color: #dc3545; display: block; margin-bottom: 15px; }
98
+ .overlay-block .modal-block h3 { color: var(--dark); margin-bottom: 10px; }
99
+ .overlay-block .modal-block p { color: #666; margin-bottom: 15px; line-height: 1.5; }
100
+ .overlay-block .modal-block button { background: var(--danger); color: white; border: none; padding: 10px 25px; border-radius: 5px; cursor: pointer; font-size: 1em; }
101
+ .overlay-block .modal-block .close-btn { background: #6c757d; margin-right: 10px; }
102
+ .overlay-block .modal-block .close-btn:hover { background: #5a6268; }
103
+ </style>
104
+ </head>
105
+ <body>
106
+
107
+ <!-- BARRE LATERALE -->
108
+ <aside>
109
+ <div class="brand">
110
+ <i class="fas fa-search-plus"></i> <span>BuSCA 2026</span>
111
+ </div>
112
+
113
+ <div class="controls">
114
+ <!-- Bouton Web -->
115
+ <button id="btnWeb" class="btn-primary">
116
+ <i class="fas fa-cloud-download-alt"></i> Web Auto (Proxy)
117
+ </button>
118
+
119
+ <!-- Bouton Manuel -->
120
+ <label for="inputFile" id="btnManual" class="btn-upload">
121
+ <i class="fas fa-folder-open"></i> Ouvrir Fichier Excel
122
+ </label>
123
+ <input type="file" id="inputFile" accept=".xlsx, .xls" style="display:none;">
124
+
125
+ <!-- Section Filtres -->
126
+ <div class="filter-box">
127
+ <label><i class="fas fa-filter"></i> Filtrer par mots-clés :</label>
128
+ <textarea id="txtFilter" placeholder="ex: Salmonella, Lait... (séparés par virgule)"></textarea>
129
+ <button id="btnApply" class="btn-success" style="width:100%; margin-top:10px; justify-content:center;">
130
+ <i class="fas fa-check"></i> Appliquer
131
+ </button>
132
+ <button id="btnClear" style="width:100%; margin-top:5px; justify-content:center; background: #555;">
133
+ <i class="fas fa-eraser"></i> Effacer
134
+ </button>
135
+ </div>
136
+
137
+ <div style="margin-top:auto; border-top:1px solid #444; padding-top:10px;">
138
+ <button id="btnExport" style="width:100%"><i class="fas fa-file-csv"></i> Exporter CSV</button>
139
+ </div>
140
+ </div>
141
+ </aside>
142
+
143
+ <!-- CONTENU PRINCIPAL -->
144
+ <main>
145
+ <header>
146
+ <div id="headerTitle">Veille Sanitaire</div>
147
+ </header>
148
+
149
+ <div class="scroll-area">
150
+ <!-- Statistiques -->
151
+ <div class="stats" id="statsArea"></div>
152
+
153
+ <!-- Résultats -->
154
+ <div id="resultsArea">
155
+ <div class="loader">
156
+ <i class="fas fa-info-circle"></i><br><br>
157
+ Chargez les données via le bouton bleu (Web) ou gris (Fichier).
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </main>
162
+
163
+ <script>
164
+ // --- FILTRE WARNINGS CONSOLE ---
165
+ const _origWarn = console.warn;
166
+ console.warn = function(...args) {
167
+ if (typeof args[0] === 'string' && args[0].includes('Unrecognized feature')) return;
168
+ _origWarn.apply(console, args);
169
+ };
170
+ // --- VARIABLES GLOBALES ---
171
+ let allData = []; // Toutes les données chargées
172
+ let filteredData = []; // Données affichées après filtre
173
+ // URLS
174
+ const URL_SOURCE = 'https://www.plateforme-sca.fr/media/398/download';
175
+ const PROXIES = [
176
+ `https://corsproxy.io/?url=${encodeURIComponent(URL_SOURCE)}`,
177
+ `https://api.allorigins.win/raw?url=${encodeURIComponent(URL_SOURCE)}`,
178
+ `https://api.codetabs.com/v1/proxy?quest=${encodeURIComponent(URL_SOURCE)}`,
179
+ ];
180
+ let currentProxyIndex = 0;
181
+ // --- INIT ---
182
+ document.addEventListener('DOMContentLoaded', () => {
183
+ setupEvents();
184
+ // On tente le chargement auto au démarrage
185
+ fetchDataWeb();
186
+ });
187
+ // --- 1. CHARGEMENT DES DONNÉES ---
188
+
189
+ // Méthode Web (Proxy avec fallback)
190
+ async function fetchDataWeb() {
191
+ showLoader(`Connexion au serveur SCA... (proxy ${currentProxyIndex + 1}/${PROXIES.length})`);
192
+ document.getElementById('btnWeb').disabled = true;
193
+ document.getElementById('btnWeb').innerHTML = '<i class="fas fa-spinner fa-spin"></i> Connexion...';
194
+ while (currentProxyIndex < PROXIES.length) {
195
+ const proxyUrl = PROXIES[currentProxyIndex];
196
+ try {
197
+ const controller = new AbortController();
198
+ const timeoutId = setTimeout(() => controller.abort(), 15000);
199
+ const response = await fetch(proxyUrl, { signal: controller.signal });
200
+ clearTimeout(timeoutId);
201
+ if (!response.ok) {
202
+ currentProxyIndex++;
203
+ if (currentProxyIndex < PROXIES.length) {
204
+ showLoader(`Proxy ${currentProxyIndex} échoué. Tentative suivante...`);
205
+ await new Promise(r => setTimeout(r, 1000));
206
+ }
207
+ continue;
208
+ }
209
+ const buffer = await response.arrayBuffer();
210
+ currentProxyIndex = 0;
211
+ document.getElementById('btnWeb').disabled = false;
212
+ document.getElementById('btnWeb').innerHTML = '<i class="fas fa-cloud-download-alt"></i> Web Auto (Proxy)';
213
+ parseExcel(buffer);
214
+ return;
215
+ } catch (e) {
216
+ currentProxyIndex++;
217
+ if (currentProxyIndex < PROXIES.length) {
218
+ showLoader(`Proxy ${currentProxyIndex} échoué. Tentative suivante...`);
219
+ await new Promise(r => setTimeout(r, 1000));
220
+ }
221
+ }
222
+ }
223
+ document.getElementById('btnWeb').disabled = false;
224
+ document.getElementById('btnWeb').innerHTML = '<i class="fas fa-cloud-download-alt"></i> Web Auto (Proxy)';
225
+ showBlock("🚫 Tous les Proxys Échoués", "Impossible de contacter le serveur SCA via aucun proxy.<br>Le serveur bloque l'accès automatique.<br>Veuillez <strong>télécharger le fichier manuellement</strong> :<br><a href=\"https://www.plateforme-sca.fr/media/398/download\" target=\"_blank\"><strong>Lien de téléchargement</strong></a><br>puis utilisez le bouton <strong>Ouvrir Fichier Excel</strong>.");
226
+ }
227
+ // Méthode Manuelle (Fichier Local)
228
+ function handleFile(e) {
229
+ const file = e.target.files[0];
230
+ if (!file) return;
231
+
232
+ // Blocage : vérification extension
233
+ const validExt = ['xlsx', 'xls'];
234
+ const ext = file.name.split('.').pop().toLowerCase();
235
+ if (!validExt.includes(ext)) {
236
+ showBlock("📄 Format Non Accepté", "Le fichier <strong>\"" + file.name + "\"</strong> n'est pas un fichier Excel valide.<br>Veuillez sélectionner un fichier .xlsx ou .xls.");
237
+ e.target.value = '';
238
+ return;
239
+ }
240
+
241
+ // Blocage : taille du fichier
242
+ if (file.size > 50 * 1024 * 1024) {
243
+ showBlock("📦 Fichier Trop Lourd", "Le fichier fait <strong>" + (file.size / 1024 / 1024).toFixed(1) + " Mo</strong>.<br>La limite est de 50 Mo.<br>Veuillez réduire la taille du fichier.");
244
+ e.target.value = '';
245
+ return;
246
+ }
247
+
248
+ // Blocage : fichier vide
249
+ if (file.size === 0) {
250
+ showBlock("⚠️ Fichier Vide", "Le fichier sélectionné est vide (0 octet).<br>Veuillez sélectionner un fichier Excel contenant des données.");
251
+ e.target.value = '';
252
+ return;
253
+ }
254
+
255
+ showLoader("Lecture du fichier...");
256
+ document.getElementById('btnManual').disabled = true;
257
+ const reader = new FileReader();
258
+ reader.onload = (evt) => {
259
+ try {
260
+ parseExcel(evt.target.result);
261
+ } catch (err) {
262
+ showBlock("💥 Erreur de Lecture", "Impossible de lire le fichier <strong>\"" + file.name + "\"</strong>.<br>Le fichier est peut-être corrompu ou protégé par mot de passe.");
263
+ }
264
+ document.getElementById('btnManual').disabled = false;
265
+ };
266
+ reader.onerror = () => {
267
+ showBlock("💥 Erreur de Lecture", "Une erreur est survenue lors de la lecture du fichier.<br>Vérifiez que le fichier n'est pas ouvert dans une autre application.");
268
+ document.getElementById('btnManual').disabled = false;
269
+ };
270
+ reader.readAsArrayBuffer(file);
271
+ e.target.value = ''; // Reset input
272
+ }
273
+ // Lecture Excel commune
274
+ function parseExcel(buffer) {
275
+ document.getElementById('btnWeb').disabled = false;
276
+ document.getElementById('btnWeb').innerHTML = '<i class="fas fa-cloud-download-alt"></i> Web Auto (Proxy)';
277
+ try {
278
+ const wb = XLSX.read(buffer, {type: 'array'});
279
+ const ws = wb.Sheets[wb.SheetNames[0]];
280
+ const json = XLSX.utils.sheet_to_json(ws);
281
+ if (!json || json.length === 0) {
282
+ showBlock("📭 Données Vides", "Le fichier Excel est ouvert mais ne contient aucune donnée.<br>Vérifiez que les feuilles ne sont pas vides ou que le bon onglet est sélectionné.");
283
+ return;
284
+ }
285
+ // Mapping des colonnes
286
+ const requiredCols = ['BuSCA', 'Titre', 'Texte', 'Matrices', 'Dangers'];
287
+ const missingCols = requiredCols.filter(col => !Object.keys(json[0]).includes(col));
288
+ if (missingCols.length > 0) {
289
+ showBlock("📋 Colonnes Manquantes", "Le fichier Excel ne contient pas les colonnes requises :<br><strong>" + missingCols.join(', ') + "</strong>.<br>Veuillez utiliser le modèle officiel du fichier BuSCA.");
290
+ return;
291
+ }
292
+ allData = json.map(row => ({
293
+ buscA: parseInt(row['BuSCA'] || 0),
294
+ titre: String(row['Titre'] || '').trim(),
295
+ texte: String(row['Texte'] || '').trim(),
296
+ matrice: String(row['Matrices'] || row['Matrice'] || 'Non spécifié').trim(),
297
+ danger: String(row['Dangers'] || row['Danger'] || 'Non spécifié').trim(),
298
+ lien1: row['Lien'] || '',
299
+ lien2: row['Lien2'] || ''
300
+ })).filter(x => x.buscA > 0 && x.titre !== "");
301
+ if (allData.length === 0) {
302
+ showBlock("⚠️ Aucune Donnée Valide", "Le fichier a été lu mais aucun bulletin valide (BuSCA > 0) n'a été trouvé.<br>Vérifiez la structure du fichier.");
303
+ return;
304
+ }
305
+ // Initialisation : Tout afficher au début
306
+ filteredData = [...allData];
307
+
308
+ // Réinitialiser les filtres visuels
309
+ document.getElementById('txtFilter').value = '';
310
+
311
+ // Mettre à jour l'interface
312
+ document.getElementById('btnManual').classList.remove('btn-warning');
313
+ document.getElementById('btnManual').disabled = false;
314
+ updateUI();
315
+ showSuccess(allData.length + " bulletins chargés avec succès !");
316
+ } catch (err) {
317
+ console.error(err);
318
+ showBlock("💥 Erreur Critique", "Impossible d'analyser le fichier Excel.<br>Erreur : " + err.message + "<br>Vérifiez le format du fichier et réessayez.");
319
+ }
320
+ }
321
+ // --- 2. GESTION DES FILTRES ---
322
+
323
+ function applyFilters() {
324
+ if (allData.length === 0) {
325
+ showBlock("🔒 Aucune Donnée", "Aucun fichier n'a été chargé.<br>Veuillez d'abord charger des données via le bouton <strong>Web Auto</strong> ou <strong>Ouvrir Fichier Excel</strong>.");
326
+ return;
327
+ }
328
+ const input = document.getElementById('txtFilter').value;
329
+
330
+ // On découpe par virgule, on enlève les espaces, on met en minuscule
331
+ const keywords = input.toLowerCase().split(',').map(k => k.trim()).filter(k => k !== '');
332
+ if (keywords.length === 0) {
333
+ filteredData = [...allData];
334
+ } else {
335
+ // Sinon on filtre
336
+ filteredData = allData.filter(item => {
337
+ const fullText = (item.titre + " " + item.texte + " " + item.matrice + " " + item.danger).toLowerCase();
338
+ return keywords.every(key => fullText.includes(key));
339
+ });
340
+ }
341
+ if (filteredData.length === 0 && keywords.length > 0) {
342
+ showBlock("🔍 Aucun Résultat", "Les filtres <strong>\"" + input + "\"</strong> ne correspondent à aucun bulletin.<br>Essayez d'autres mots-clés ou cliquez sur <strong>Effacer</strong> pour tout afficher.");
343
+ }
344
+ updateUI();
345
+ }
346
+ // --- 3. AFFICHAGE (RENDERING) ---
347
+ function updateUI() {
348
+ const container = document.getElementById('resultsArea');
349
+ const stats = document.getElementById('statsArea');
350
+ // Mise à jour des Stats
351
+ const nbBulletins = filteredData.length;
352
+ const nbMatrices = new Set(filteredData.map(i => i.matrice)).size;
353
+ const nbDangers = new Set(filteredData.map(i => i.danger)).size;
354
+ stats.innerHTML = `
355
+ <div class="stat-item"><div class="stat-val">${nbBulletins}</div><div class="stat-label">Bulletins</div></div>
356
+ <div class="stat-item" style="border-color:#17a2b8"><div class="stat-val">${nbMatrices}</div><div class="stat-label">Matrices</div></div>
357
+ <div class="stat-item" style="border-color:#ffc107"><div class="stat-val">${nbDangers}</div><div class="stat-label">Dangers</div></div>
358
+ `;
359
+ // Affichage Liste
360
+ if (filteredData.length === 0) {
361
+ if (allData.length === 0) {
362
+ container.innerHTML = `<div class="msg-block"><i class="fas fa-lock"></i><strong>Aucune Donnée Chargée</strong><br>Chargez les données pour commencer.</div>`;
363
+ } else {
364
+ container.innerHTML = `<div class="msg-block"><i class="fas fa-search"></i><strong>Aucun Résultat</strong><br>Modifiez vos filtres pour voir des résultats.</div>`;
365
+ }
366
+ return;
367
+ }
368
+ // Tri par N° BuSCA décroissant
369
+ const sorted = [...filteredData].sort((a,b) => b.buscA - a.buscA);
370
+ container.innerHTML = sorted.map(item => `
371
+ <article class="card">
372
+ <div class="card-header" onclick="this.nextElementSibling.classList.toggle('open')">
373
+ <div style="font-weight:bold;">${item.titre}</div>
374
+ <div style="font-size:0.8em; background:rgba(255,255,255,0.2); padding:2px 6px; border-radius:4px;">N° ${item.buscA}</div>
375
+ </div>
376
+ <div class="card-body">
377
+ <div class="tags">
378
+ <span class="tag tag-matrix"><i class="fas fa-vial"></i> ${item.matrice}</span>
379
+ <span class="tag tag-danger"><i class="fas fa-biohazard"></i> ${item.danger}</span>
380
+ </div>
381
+ <p style="line-height:1.6; color:#444; margin-bottom:15px;">${item.texte.replace(/\n/g, '<br>')}</p>
382
+ <div style="border-top:1px dashed #ddd; padding-top:10px;">
383
+ ${item.lien1 ? `<a href="${item.lien1}" target="_blank" style="color:var(--primary); font-weight:bold; margin-right:15px; text-decoration:none;">🔗 Lien Principal</a>` : ''}
384
+ ${item.lien2 ? `<a href="${item.lien2}" target="_blank" style="color:#555; text-decoration:none;">🔗 Lien Secondaire</a>` : ''}
385
+ </div>
386
+ </div>
387
+ </article>
388
+ `).join('');
389
+ }
390
+ // --- 4. UTILITAIRES ---
391
+ function showBlock(title, message) {
392
+ document.getElementById('btnWeb').disabled = false;
393
+ document.getElementById('btnWeb').innerHTML = '<i class="fas fa-cloud-download-alt"></i> Web Auto (Proxy)';
394
+ document.getElementById('btnManual').disabled = false;
395
+ const existing = document.getElementById('blockOverlay');
396
+ if (existing) existing.remove();
397
+ const overlay = document.createElement('div');
398
+ overlay.className = 'overlay-block';
399
+ overlay.id = 'blockOverlay';
400
+ overlay.innerHTML = `
401
+ <div class="modal-block">
402
+ <i class="fas fa-lock"></i>
403
+ <h3>${title}</h3>
404
+ <p>${message}</p>
405
+ <button class="close-btn" onclick="this.parentElement.parentElement.remove()">Fermer</button>
406
+ </div>
407
+ `;
408
+ overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
409
+ document.body.appendChild(overlay);
410
+ }
411
+ function showSuccess(msg) {
412
+ const container = document.getElementById('resultsArea');
413
+ container.innerHTML = `<div class="msg-success-block"><i class="fas fa-check-circle"></i> <strong>Succès</strong><br>${msg}</div>` + `
414
+ <div class="loader"><i class="fas fa-info-circle"></i><br><br>Chargez les données via le bouton bleu (Web) ou gris (Fichier).</div>`;
415
+ }
416
+ function showError() {
417
+ document.getElementById('btnWeb').disabled = false;
418
+ document.getElementById('btnWeb').innerHTML = '<i class="fas fa-cloud-download-alt"></i> Web Auto (Proxy)';
419
+ const existing = document.getElementById('blockOverlay');
420
+ if (existing) existing.remove();
421
+ const overlay = document.createElement('div');
422
+ overlay.className = 'overlay-block';
423
+ overlay.id = 'blockOverlay';
424
+ overlay.innerHTML = `
425
+ <div class="modal-block">
426
+ <i class="fas fa-ban"></i>
427
+ <h3>Accès Web Bloqué (Sécurité Serveur)</h3>
428
+ <p>Le serveur SCA refuse l'accès automatique.<br><br>
429
+ <a href="${URL_SOURCE}" target="_blank" style="color:var(--primary); font-weight:bold;">📥 Téléchargez le fichier Excel ici</a><br>
430
+ <small>puis cliquez sur le bouton jaune "Ouvrir Fichier Excel" à gauche.</small></p>
431
+ <button class="close-btn" onclick="this.parentElement.parentElement.remove()">Fermer</button>
432
+ </div>
433
+ `;
434
+ overlay.onclick = (e) => { if (e.target === overlay) overlay.remove(); };
435
+ document.body.appendChild(overlay);
436
+ }
437
+ function showLoader(msg) {
438
+ document.getElementById('resultsArea').innerHTML = `<div class="loader"><i class="fas fa-circle-notch fa-spin fa-2x"></i><br><br>${msg}</div>`;
439
+ }
440
+ // --- 5. ÉVÉNEMENTS ---
441
+ function setupEvents() {
442
+ document.getElementById('btnWeb').onclick = fetchDataWeb;
443
+ document.getElementById('inputFile').onchange = handleFile;
444
+
445
+ // Filtres
446
+ document.getElementById('btnApply').onclick = applyFilters;
447
+ document.getElementById('btnClear').onclick = () => {
448
+ document.getElementById('txtFilter').value = '';
449
+ applyFilters();
450
+ };
451
+
452
+ // Touche Entrée dans la zone de texte
453
+ document.getElementById('txtFilter').addEventListener('keyup', (e) => {
454
+ if (e.key === 'Enter') applyFilters();
455
+ });
456
+ // Export avec blocage
457
+ document.getElementById('btnExport').onclick = () => {
458
+ if (allData.length === 0) {
459
+ showBlock("🔒 Rien à Exporter", "Aucune donnée n'a été chargée.<br>Veuillez d'abord charger un fichier Excel ou attendre le chargement Web.");
460
+ return;
461
+ }
462
+ if (filteredData.length === 0) {
463
+ showBlock("🔒 Rien à Exporter", "Aucun résultat à exporter.<br>Vos filtres ne correspondent à aucun bulletin.<br>Modifiez les filtres ou cliquez sur <strong>Effacer</strong>.");
464
+ return;
465
+ }
466
+ const csv = ["BuSCA,Titre,Matrice,Danger,Texte"].concat(
467
+ filteredData.map(d => `${d.buscA},"${d.titre}","${d.matrice}","${d.danger}","${d.texte.replace(/"/g,'""')}"`)
468
+ ).join('\n');
469
+ const a = document.createElement('a');
470
+ a.href = URL.createObjectURL(new Blob([csv], {type:'text/csv'}));
471
+ a.download = 'busca_export_' + new Date().toISOString().slice(0,10) + '.csv';
472
+ a.click();
473
+ };
474
+ }
475
+ // --- INIT ---
476
+ document.addEventListener('DOMContentLoaded', () => {
477
+ setupEvents();
478
+ fetchDataWeb();
479
+ });
480
+ </script>
481
+ </body>
482
+ </html>