Create index.html
Browse files- 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>
|