MMOON commited on
Commit
e19584f
·
verified ·
1 Parent(s): d5639e4

Upload 2 files

Browse files
Files changed (2) hide show
  1. index.html +1692 -0
  2. translation.js +212 -0
index.html ADDED
@@ -0,0 +1,1692 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>FDA Risk Analyzer - Outil d'Analyse des Risques Alimentaires</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
+ <style>
9
+ :root {
10
+ --primary-color: #003366;
11
+ --primary-light: #0055a4;
12
+ --accent-color: #fdb81e;
13
+ --danger-color: #d93025;
14
+ --warning-color: #f59e0b;
15
+ --success-color: #1e8e3e;
16
+ --bg-color: #f8f9fa;
17
+ --surface-color: #ffffff;
18
+ --text-primary: #202124;
19
+ --text-secondary: #5f6368;
20
+ --border-color: #dee2e6;
21
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
22
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
23
+ --border-radius: 8px;
24
+ --transition: all 0.3s ease-in-out;
25
+ }
26
+
27
+ * {
28
+ box-sizing: border-box;
29
+ margin: 0;
30
+ padding: 0;
31
+ }
32
+
33
+ body {
34
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
35
+ background-color: var(--bg-color);
36
+ color: var(--text-primary);
37
+ line-height: 1.5;
38
+ }
39
+
40
+ .header {
41
+ background: var(--surface-color);
42
+ border-bottom: 1px solid var(--border-color);
43
+ padding: 1rem 0;
44
+ position: sticky;
45
+ top: 0;
46
+ z-index: 1000;
47
+ box-shadow: var(--shadow-sm);
48
+ }
49
+
50
+ .header-content {
51
+ max-width: 1600px;
52
+ margin: 0 auto;
53
+ padding: 0 2rem;
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: space-between;
57
+ }
58
+
59
+ .logo {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 0.75rem;
63
+ color: var(--primary-color);
64
+ font-weight: 600;
65
+ font-size: 1.25rem;
66
+ }
67
+
68
+ .logo-icon {
69
+ width: 32px;
70
+ height: 32px;
71
+ background-color: var(--primary-color);
72
+ border-radius: 6px;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ color: white;
77
+ font-size: 1rem;
78
+ }
79
+
80
+ .guide-btn {
81
+ background-color: var(--primary-color);
82
+ color: white;
83
+ border: none;
84
+ padding: 0.6rem 1.2rem;
85
+ border-radius: var(--border-radius);
86
+ cursor: pointer;
87
+ font-size: 0.9rem;
88
+ transition: var(--transition);
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 0.5rem;
92
+ }
93
+
94
+ .guide-btn:hover {
95
+ background-color: var(--primary-light);
96
+ }
97
+
98
+ .container {
99
+ max-width: 1600px;
100
+ margin: 2rem auto;
101
+ padding: 0 2rem;
102
+ }
103
+
104
+ .welcome-guide {
105
+ background-color: #e7f3ff;
106
+ border: 1px solid #a8c7fa;
107
+ border-radius: var(--border-radius);
108
+ padding: 1.5rem;
109
+ margin-bottom: 2rem;
110
+ position: relative;
111
+ animation: fadeIn 0.5s;
112
+ }
113
+
114
+ .methodology-info {
115
+ background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
116
+ border: 1px solid #0ea5e9;
117
+ border-radius: var(--border-radius);
118
+ margin-bottom: 2rem;
119
+ overflow: hidden;
120
+ box-shadow: var(--shadow-sm);
121
+ }
122
+
123
+ .methodology-header {
124
+ background: var(--primary-color);
125
+ color: white;
126
+ padding: 1rem 1.5rem;
127
+ cursor: pointer;
128
+ display: flex;
129
+ justify-content: space-between;
130
+ align-items: center;
131
+ transition: var(--transition);
132
+ }
133
+
134
+ .methodology-header:hover {
135
+ background-color: var(--primary-light);
136
+ }
137
+
138
+ .methodology-title {
139
+ font-size: 1.1rem;
140
+ font-weight: 600;
141
+ margin: 0;
142
+ display: flex;
143
+ align-items: center;
144
+ gap: 0.5rem;
145
+ }
146
+
147
+ .methodology-toggle {
148
+ font-size: 1.2rem;
149
+ transition: transform 0.3s ease;
150
+ }
151
+
152
+ .methodology-toggle.expanded {
153
+ transform: rotate(180deg);
154
+ }
155
+
156
+ .methodology-content {
157
+ max-height: 0;
158
+ overflow: hidden;
159
+ transition: max-height 0.5s ease-in-out, padding 0.5s ease-in-out;
160
+ padding: 0 2rem;
161
+ }
162
+
163
+ .methodology-content.expanded {
164
+ max-height: 3000px;
165
+ padding: 2rem;
166
+ }
167
+
168
+ .methodology-inner {
169
+ /* Padding supprimé - maintenant géré par methodology-content */
170
+ }
171
+
172
+ .methodology-section {
173
+ margin-bottom: 2rem;
174
+ }
175
+
176
+ .methodology-section:last-child {
177
+ margin-bottom: 0;
178
+ }
179
+
180
+ .methodology-section h4 {
181
+ color: var(--primary-color);
182
+ font-size: 1.1rem;
183
+ margin: 0 0 1rem 0;
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 0.5rem;
187
+ }
188
+
189
+ .methodology-section p {
190
+ margin-bottom: 1rem;
191
+ line-height: 1.6;
192
+ }
193
+
194
+ .methodology-section ul {
195
+ padding-left: 1.5rem;
196
+ margin-bottom: 1rem;
197
+ }
198
+
199
+ .methodology-section li {
200
+ margin-bottom: 0.5rem;
201
+ line-height: 1.5;
202
+ }
203
+
204
+ .highlight-box {
205
+ background: rgba(251, 191, 36, 0.1);
206
+ border-left: 4px solid #f59e0b;
207
+ padding: 1rem;
208
+ margin: 1rem 0;
209
+ border-radius: 0 var(--border-radius) var(--border-radius) 0;
210
+ }
211
+
212
+ .reference-section {
213
+ background: var(--bg-color);
214
+ border-radius: var(--border-radius);
215
+ padding: 1.5rem;
216
+ margin-top: 1.5rem;
217
+ }
218
+
219
+ .reference-section h5 {
220
+ color: var(--primary-color);
221
+ margin: 0 0 1rem 0;
222
+ font-size: 1rem;
223
+ }
224
+
225
+ .reference-list {
226
+ list-style: none;
227
+ padding: 0;
228
+ margin: 0;
229
+ }
230
+
231
+ .reference-list li {
232
+ margin-bottom: 0.75rem;
233
+ padding-left: 1.5rem;
234
+ position: relative;
235
+ font-size: 0.9rem;
236
+ }
237
+
238
+ .reference-list li:before {
239
+ content: "📄";
240
+ position: absolute;
241
+ left: 0;
242
+ }
243
+
244
+ .reference-list a {
245
+ color: var(--primary-light);
246
+ text-decoration: none;
247
+ }
248
+
249
+ .reference-list a:hover {
250
+ text-decoration: underline;
251
+ }
252
+
253
+ .haccp-integration {
254
+ background: rgba(30, 142, 62, 0.1);
255
+ border: 2px solid var(--success-color);
256
+ border-radius: var(--border-radius);
257
+ padding: 1.5rem;
258
+ margin: 1.5rem 0;
259
+ }
260
+
261
+ .haccp-integration h5 {
262
+ color: var(--success-color);
263
+ margin: 0 0 1rem 0;
264
+ display: flex;
265
+ align-items: center;
266
+ gap: 0.5rem;
267
+ }
268
+
269
+ .welcome-guide h3 {
270
+ margin: 0 0 1rem 0;
271
+ color: var(--primary-color);
272
+ }
273
+
274
+ .guide-steps {
275
+ display: grid;
276
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
277
+ gap: 1.5rem;
278
+ }
279
+
280
+ .guide-step {
281
+ font-size: 0.9rem;
282
+ }
283
+
284
+ .guide-step strong {
285
+ color: var(--text-primary);
286
+ }
287
+
288
+ .close-guide {
289
+ position: absolute;
290
+ top: 0.5rem;
291
+ right: 0.5rem;
292
+ background: none;
293
+ border: none;
294
+ font-size: 1.5rem;
295
+ cursor: pointer;
296
+ color: var(--text-secondary);
297
+ }
298
+
299
+ .controls-panel {
300
+ background: var(--surface-color);
301
+ border-radius: var(--border-radius);
302
+ padding: 1.5rem;
303
+ margin-bottom: 2rem;
304
+ box-shadow: var(--shadow-md);
305
+ border: 1px solid var(--border-color);
306
+ }
307
+
308
+ .view-selector {
309
+ display: flex;
310
+ flex-wrap: wrap;
311
+ gap: 0.5rem;
312
+ border-bottom: 1px solid var(--border-color);
313
+ padding-bottom: 1rem;
314
+ margin-bottom: 1rem;
315
+ }
316
+
317
+ .view-btn {
318
+ padding: 0.6rem 1.2rem;
319
+ border: 1px solid transparent;
320
+ background: none;
321
+ color: var(--text-secondary);
322
+ cursor: pointer;
323
+ font-size: 0.95rem;
324
+ font-weight: 500;
325
+ border-bottom: 3px solid transparent;
326
+ border-radius: 6px;
327
+ transition: var(--transition);
328
+ }
329
+
330
+ .view-btn.active {
331
+ color: var(--primary-light);
332
+ background-color: #e7f3ff;
333
+ border-color: var(--primary-light);
334
+ }
335
+
336
+ .view-btn:hover:not(.active) {
337
+ background-color: #f0f7ff;
338
+ }
339
+
340
+ #main-view-controls {
341
+ display: grid;
342
+ grid-template-columns: 1fr auto;
343
+ gap: 1rem;
344
+ align-items: center;
345
+ }
346
+
347
+ #comparison-view-controls {
348
+ display: grid;
349
+ grid-template-columns: 1fr 1fr;
350
+ gap: 1rem;
351
+ align-items: center;
352
+ }
353
+
354
+ .search-container {
355
+ position: relative;
356
+ }
357
+
358
+ .search-input {
359
+ width: 100%;
360
+ padding: 0.75rem 1rem 0.75rem 2.5rem;
361
+ border: 1px solid var(--border-color);
362
+ border-radius: var(--border-radius);
363
+ font-size: 1rem;
364
+ transition: var(--transition);
365
+ }
366
+
367
+ .search-input:focus {
368
+ outline: none;
369
+ border-color: var(--primary-light);
370
+ box-shadow: 0 0 0 3px rgba(0, 85, 164, 0.1);
371
+ }
372
+
373
+ .search-icon {
374
+ position: absolute;
375
+ left: 0.75rem;
376
+ top: 50%;
377
+ transform: translateY(-50%);
378
+ color: var(--text-secondary);
379
+ }
380
+
381
+ .filter-select {
382
+ padding: 0.75rem;
383
+ border: 1px solid var(--border-color);
384
+ border-radius: var(--border-radius);
385
+ background: var(--surface-color);
386
+ color: var(--text-primary);
387
+ font-size: 0.9rem;
388
+ width: 100%;
389
+ }
390
+
391
+ .dashboard {
392
+ display: grid;
393
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
394
+ gap: 1.5rem;
395
+ margin-bottom: 2rem;
396
+ }
397
+
398
+ .metric-card {
399
+ background: var(--surface-color);
400
+ padding: 1.5rem;
401
+ border-radius: var(--border-radius);
402
+ box-shadow: var(--shadow-sm);
403
+ border: 1px solid var(--border-color);
404
+ text-align: center;
405
+ }
406
+
407
+ .metric-label {
408
+ font-size: 0.85rem;
409
+ color: var(--text-secondary);
410
+ margin-bottom: 0.5rem;
411
+ }
412
+
413
+ .metric-value {
414
+ font-size: 1.75rem;
415
+ font-weight: 600;
416
+ color: var(--text-primary);
417
+ }
418
+
419
+ .metric-card.high-risk {
420
+ border-top: 4px solid var(--danger-color);
421
+ }
422
+
423
+ .metric-card.medium-risk {
424
+ border-top: 4px solid var(--warning-color);
425
+ }
426
+
427
+ .metric-card.low-risk {
428
+ border-top: 4px solid var(--success-color);
429
+ }
430
+
431
+ #detail-panel {
432
+ max-height: 0;
433
+ overflow: hidden;
434
+ transition: max-height 0.5s ease-in-out, margin 0.5s ease-in-out;
435
+ margin-bottom: 0;
436
+ }
437
+
438
+ #detail-panel.visible {
439
+ max-height: 1000px;
440
+ margin-bottom: 2rem;
441
+ }
442
+
443
+ .detail-card {
444
+ background-color: var(--surface-color);
445
+ border-radius: var(--border-radius);
446
+ border: 1px solid var(--border-color);
447
+ box-shadow: var(--shadow-md);
448
+ padding: 1.5rem;
449
+ }
450
+
451
+ .detail-layout {
452
+ display: grid;
453
+ grid-template-columns: 1fr 1fr;
454
+ gap: 2rem;
455
+ align-items: start;
456
+ }
457
+
458
+ .chart-title {
459
+ font-weight: 600;
460
+ color: var(--text-primary);
461
+ margin: 0 0 1rem 0;
462
+ font-size: 1.1rem;
463
+ word-break: break-word;
464
+ }
465
+
466
+ .chart-canvas-container {
467
+ position: relative;
468
+ height: 320px;
469
+ }
470
+
471
+ #comparison-chart-container {
472
+ min-height: 600px;
473
+ height: auto;
474
+ }
475
+
476
+ #takeaways-section h4 {
477
+ margin: 0 0 1rem 0;
478
+ color: var(--primary-color);
479
+ }
480
+
481
+ #takeaways-list {
482
+ list-style: none;
483
+ padding: 0;
484
+ margin: 0;
485
+ display: flex;
486
+ flex-direction: column;
487
+ gap: 0.75rem;
488
+ }
489
+
490
+ #takeaways-list li {
491
+ display: flex;
492
+ align-items: flex-start;
493
+ gap: 0.75rem;
494
+ font-size: 0.9rem;
495
+ }
496
+
497
+ #takeaways-list li .icon {
498
+ color: var(--warning-color);
499
+ font-weight: bold;
500
+ margin-top: 2px;
501
+ }
502
+
503
+ .table-container {
504
+ background: var(--surface-color);
505
+ border-radius: var(--border-radius);
506
+ box-shadow: var(--shadow-md);
507
+ border: 1px solid var(--border-color);
508
+ overflow: hidden;
509
+ }
510
+
511
+ .table-header {
512
+ padding: 1rem 1.5rem;
513
+ border-bottom: 1px solid var(--border-color);
514
+ display: flex;
515
+ justify-content: space-between;
516
+ align-items: center;
517
+ }
518
+
519
+ .table-title {
520
+ font-weight: 600;
521
+ margin: 0;
522
+ }
523
+
524
+ .results-count {
525
+ color: var(--text-secondary);
526
+ font-size: 0.9rem;
527
+ }
528
+
529
+ .table-wrapper {
530
+ overflow-x: auto;
531
+ }
532
+
533
+ table {
534
+ width: 100%;
535
+ border-collapse: collapse;
536
+ }
537
+
538
+ th, td {
539
+ padding: 1rem 1.5rem;
540
+ text-align: left;
541
+ border-bottom: 1px solid var(--border-color);
542
+ white-space: nowrap;
543
+ }
544
+
545
+ tbody tr:last-child td {
546
+ border-bottom: none;
547
+ }
548
+
549
+ th {
550
+ background: var(--bg-color);
551
+ font-weight: 600;
552
+ color: var(--text-secondary);
553
+ font-size: 0.85rem;
554
+ text-transform: uppercase;
555
+ }
556
+
557
+ th.sortable {
558
+ cursor: pointer;
559
+ position: relative;
560
+ }
561
+
562
+ th.sortable:hover {
563
+ color: var(--text-primary);
564
+ }
565
+
566
+ th.sortable::after {
567
+ content: '↕';
568
+ position: absolute;
569
+ right: 0.75rem;
570
+ opacity: 0.3;
571
+ }
572
+
573
+ th.sort-asc::after {
574
+ content: '↑';
575
+ opacity: 1;
576
+ }
577
+
578
+ th.sort-desc::after {
579
+ content: '↓';
580
+ opacity: 1;
581
+ }
582
+
583
+ tbody tr.clickable {
584
+ cursor: pointer;
585
+ }
586
+
587
+ tbody tr:hover {
588
+ background-color: #e9ecef;
589
+ }
590
+
591
+ tbody tr.selected {
592
+ background-color: #dbeafe !important;
593
+ font-weight: 500;
594
+ }
595
+
596
+ .score-badge {
597
+ display: inline-block;
598
+ padding: 0.2rem 0.6rem;
599
+ border-radius: 1rem;
600
+ font-size: 0.8rem;
601
+ font-weight: 700;
602
+ }
603
+
604
+ .score-high {
605
+ background-color: #fdeded;
606
+ color: #a50e0e;
607
+ }
608
+
609
+ .score-medium {
610
+ background-color: #fff8e1;
611
+ color: #6f4f12;
612
+ }
613
+
614
+ .score-low {
615
+ background-color: #e6f4ea;
616
+ color: #1b5e20;
617
+ }
618
+
619
+ #loading-overlay {
620
+ position: fixed;
621
+ top: 0;
622
+ left: 0;
623
+ width: 100%;
624
+ height: 100%;
625
+ background: rgba(255, 255, 255, 0.9);
626
+ z-index: 3000;
627
+ display: flex;
628
+ align-items: center;
629
+ justify-content: center;
630
+ text-align: center;
631
+ font-size: 1.2rem;
632
+ color: var(--primary-color);
633
+ transition: opacity 0.3s;
634
+ }
635
+
636
+ .spinner {
637
+ width: 50px;
638
+ height: 50px;
639
+ border: 5px solid rgba(0, 51, 102, 0.2);
640
+ border-top: 5px solid var(--primary-color);
641
+ border-radius: 50%;
642
+ animation: spin 1s linear infinite;
643
+ margin: 0 auto 1rem;
644
+ }
645
+
646
+ @keyframes spin {
647
+ 0% { transform: rotate(0deg); }
648
+ 100% { transform: rotate(360deg); }
649
+ }
650
+
651
+ .modal {
652
+ display: none;
653
+ position: fixed;
654
+ z-index: 2000;
655
+ left: 0;
656
+ top: 0;
657
+ width: 100%;
658
+ height: 100%;
659
+ overflow: auto;
660
+ background-color: rgba(0,0,0,0.5);
661
+ backdrop-filter: blur(5px);
662
+ }
663
+
664
+ .modal-content {
665
+ background-color: var(--surface-color);
666
+ margin: 5% auto;
667
+ padding: 2.5rem;
668
+ border: 1px solid var(--border-color);
669
+ width: 90%;
670
+ max-width: 800px;
671
+ border-radius: var(--border-radius);
672
+ animation: fadeIn 0.3s;
673
+ }
674
+
675
+ .modal-header {
676
+ display: flex;
677
+ justify-content: space-between;
678
+ align-items: center;
679
+ border-bottom: 1px solid var(--border-color);
680
+ padding-bottom: 1rem;
681
+ margin-bottom: 1.5rem;
682
+ }
683
+
684
+ .modal-title {
685
+ font-size: 1.5rem;
686
+ color: var(--primary-color);
687
+ margin: 0;
688
+ }
689
+
690
+ .close-modal {
691
+ font-size: 2rem;
692
+ font-weight: bold;
693
+ color: var(--text-secondary);
694
+ cursor: pointer;
695
+ }
696
+
697
+ .close-modal:hover {
698
+ color: var(--text-primary);
699
+ }
700
+
701
+ .criteria-grid {
702
+ display: grid;
703
+ grid-template-columns: 1fr 1fr;
704
+ gap: 1rem;
705
+ margin-top: 1rem;
706
+ }
707
+
708
+ .criteria-card {
709
+ background: var(--bg-color);
710
+ padding: 1rem;
711
+ border-radius: var(--border-radius);
712
+ }
713
+
714
+ .criteria-title {
715
+ font-weight: 600;
716
+ display: block;
717
+ margin-bottom: 0.25rem;
718
+ }
719
+
720
+ .criteria-desc {
721
+ font-size: 0.9rem;
722
+ color: var(--text-secondary);
723
+ }
724
+
725
+ .modal-footer {
726
+ margin-top: 2rem;
727
+ padding-top: 1rem;
728
+ border-top: 1px solid var(--border-color);
729
+ text-align: center;
730
+ font-size: 0.9rem;
731
+ }
732
+
733
+ .modal-footer a {
734
+ color: var(--primary-light);
735
+ }
736
+
737
+ @keyframes fadeIn {
738
+ from { opacity: 0; transform: translateY(-20px); }
739
+ to { opacity: 1; transform: translateY(0); }
740
+ }
741
+
742
+ @media (max-width: 1200px) {
743
+ #main-view-controls, #comparison-view-controls {
744
+ grid-template-columns: 1fr;
745
+ }
746
+ .detail-layout {
747
+ grid-template-columns: 1fr;
748
+ }
749
+ }
750
+
751
+ @media (max-width: 768px) {
752
+ .header-content, .container {
753
+ padding: 0 1rem;
754
+ }
755
+ .view-selector {
756
+ flex-direction: column;
757
+ }
758
+ .dashboard {
759
+ grid-template-columns: 1fr;
760
+ }
761
+ }
762
+
763
+ .risk-indicator {
764
+ display: inline-block;
765
+ width: 12px;
766
+ height: 12px;
767
+ border-radius: 50%;
768
+ margin-right: 8px;
769
+ }
770
+
771
+ .risk-high {
772
+ background-color: var(--danger-color);
773
+ }
774
+
775
+ .risk-medium {
776
+ background-color: var(--warning-color);
777
+ }
778
+
779
+ .risk-low {
780
+ background-color: var(--success-color);
781
+ }
782
+
783
+ .footer {
784
+ text-align: center;
785
+ padding: 2rem;
786
+ color: var(--text-secondary);
787
+ font-size: 0.9rem;
788
+ border-top: 1px solid var(--border-color);
789
+ margin-top: 2rem;
790
+ }
791
+
792
+ .error-message {
793
+ color: var(--danger-color);
794
+ text-align: center;
795
+ padding: 2rem;
796
+ font-weight: 500;
797
+ }
798
+ </style>
799
+ </head>
800
+ <body>
801
+ <div id="loading-overlay">
802
+ <div>
803
+ <div class="spinner"></div>
804
+ <h2>Chargement des données...</h2>
805
+ <p>Veuillez patienter.</p>
806
+ </div>
807
+ </div>
808
+
809
+ <header class="header">
810
+ <div class="header-content">
811
+ <div class="logo">
812
+ <div class="logo-icon">🛡️</div>
813
+ <span>FDA Risk Analyzer</span>
814
+ </div>
815
+ <button class="guide-btn" id="guide-btn">
816
+ <i class="fas fa-book"></i>
817
+ Guide d'Utilisation
818
+ </button>
819
+ </div>
820
+ </header>
821
+
822
+ <div class="container">
823
+ <div class="welcome-guide" id="welcome-guide">
824
+ <button class="close-guide" id="close-guide">×</button>
825
+ <h3>Guide Rapide de l'Outil</h3>
826
+ <div class="guide-steps">
827
+ <div class="guide-step">
828
+ <strong>1. Choisissez une Vue</strong><br>
829
+ Utilisez les boutons ci-dessous pour sélectionner votre niveau d'analyse.
830
+ </div>
831
+ <div class="guide-step">
832
+ <strong>2. Filtrez les Données</strong><br>
833
+ Utilisez la recherche et les filtres pour affiner les résultats.
834
+ </div>
835
+ <div class="guide-step">
836
+ <strong>3. Analysez en Profondeur</strong><br>
837
+ Cliquez sur les lignes du tableau ou utilisez la vue comparative.
838
+ </div>
839
+ </div>
840
+ </div>
841
+
842
+ <div class="methodology-info" id="methodology-info">
843
+ <div class="methodology-header" id="methodology-header">
844
+ <h3 class="methodology-title">
845
+ 🧬 Comprendre la Méthodologie FDA de Classement des Risques
846
+ </h3>
847
+ <span class="methodology-toggle" id="methodology-toggle">▼</span>
848
+ </div>
849
+ <div class="methodology-content" id="methodology-content">
850
+ <div class="methodology-inner">
851
+ <div class="methodology-section">
852
+ <h4>🎯 Qu'est-ce que le Modèle de Classement des Risques de la FDA ?</h4>
853
+ <p>
854
+ Le <strong>FDA Risk-Ranking Model</strong> est un outil scientifique développé par l'administration américaine (FDA) pour <strong>prioriser les efforts de surveillance</strong> et de contrôle dans l'industrie alimentaire. Il permet d'identifier quels couples produit/danger représentent les risques les plus élevés pour la santé publique.
855
+ </p>
856
+ <div class="highlight-box">
857
+ <strong>🔍 Objectif principal :</strong> Aider les autorités sanitaires et les professionnels de l'alimentaire à concentrer leurs ressources limitées sur les risques qui comptent vraiment.
858
+ </div>
859
+ </div>
860
+
861
+ <div class="methodology-section">
862
+ <h4>⚙️ Comment ça fonctionne : Les 7 Critères d'Évaluation</h4>
863
+ <p>Chaque couple produit/pathogène est évalué selon 7 critères scientifiques, notés de 1 à 9 :</p>
864
+ <ul>
865
+ <li><strong>C1 - Gravité :</strong> Sévérité des effets sur la santé (mortalité, hospitalisation)</li>
866
+ <li><strong>C2 - Exposition :</strong> Fréquence et quantité de consommation du produit</li>
867
+ <li><strong>C3 - Dose-Réponse :</strong> Quantité minimale de pathogène nécessaire pour déclencher la maladie</li>
868
+ <li><strong>C4 - Croissance :</strong> Capacité du pathogène à survivre/proliférer dans l'aliment</li>
869
+ <li><strong>C5 - Traitement :</strong> Efficacité des procédés de décontamination</li>
870
+ <li><strong>C6 - Contamination :</strong> Probabilité de contamination à la production</li>
871
+ <li><strong>C7 - Consommation :</strong> Mode de préparation (cru, cuit, transformé)</li>
872
+ </ul>
873
+ <p>Le <strong>score final</strong> est calculé en multipliant ces 7 critères, créant un classement objectif des risques.</p>
874
+ </div>
875
+
876
+ <div class="methodology-section">
877
+ <h4>📊 Pourquoi c'est révolutionnaire ?</h4>
878
+ <p>Avant ce modèle, les décisions étaient souvent basées sur :</p>
879
+ <ul>
880
+ <li>❌ L'intuition ou l'expérience personnelle</li>
881
+ <li>❌ Les crises médiatiques du moment</li>
882
+ <li>❌ Des approches fragmentées par produit OU par pathogène</li>
883
+ </ul>
884
+ <p>Le modèle FDA apporte :</p>
885
+ <ul>
886
+ <li>✅ Une approche <strong>scientifique et quantitative</strong></li>
887
+ <li>✅ Une vision <strong>globale</strong> produit × pathogène</li>
888
+ <li>✅ Une <strong>transparence</strong> dans les décisions de priorisation</li>
889
+ <li>✅ Une <strong>comparabilité</strong> entre différents risques</li>
890
+ </ul>
891
+ </div>
892
+
893
+ <div class="haccp-integration">
894
+ <h5>🛡️ Intégration avec votre Plan HACCP</h5>
895
+ <p><strong>Ce modèle ne remplace pas HACCP, il le renforce !</strong></p>
896
+ <ul>
897
+ <li><strong>Analyse des Dangers :</strong> Utilisez les scores pour prioriser vos dangers significatifs (étape 1 HACCP)</li>
898
+ <li><strong>Points Critiques :</strong> Concentrez vos CCP sur les couples à haut score</li>
899
+ <li><strong>Surveillance :</strong> Adaptez la fréquence de monitoring selon les scores de risque</li>
900
+ <li><strong>Validation :</strong> Justifiez scientifiquement vos choix de maîtrise</li>
901
+ <li><strong>Audits :</strong> Orientez vos vérifications sur les zones à plus haut risque</li>
902
+ </ul>
903
+ </div>
904
+
905
+ <div class="methodology-section">
906
+ <h4>💼 Applications Pratiques en Industrie</h4>
907
+ <ul>
908
+ <li><strong>Responsables Qualité :</strong> Priorisation des plans de surveillance et contrôles</li>
909
+ <li><strong>Acheteurs :</strong> Évaluation et sélection des fournisseurs selon les risques</li>
910
+ <li><strong>R&D :</strong> Conception de produits avec maîtrise des risques intégrée</li>
911
+ <li><strong>Direction :</strong> Allocation des budgets sécurité alimentaire basée sur les risques réels</li>
912
+ <li><strong>Auditeurs :</strong> Focus des audits sur les couples produit/danger critiques</li>
913
+ </ul>
914
+ </div>
915
+
916
+ <div class="methodology-section">
917
+ <h4>🌍 Food Traceability List (FTL)</h4>
918
+ <p>
919
+ La <strong>Food Traceability List</strong> est une liste des aliments à plus haut risque, identifiés grâce à ce modèle.
920
+ Ces produits sont soumis à des <strong>exigences de traçabilité renforcée</strong> aux États-Unis depuis janvier 2026.
921
+ </p>
922
+ <div class="highlight-box">
923
+ <strong>💡 Conseil :</strong> Même en Europe, surveiller ces produits FTL peut vous donner un avantage concurrentiel et anticiper les évolutions réglementaires.
924
+ </div>
925
+ </div>
926
+
927
+ <div class="reference-section">
928
+ <h5>📚 Sources et Références Officielles</h5>
929
+ <ul class="reference-list">
930
+ <li>
931
+ <a href="https://www.fda.gov/food/cfsan-constituent-updates/fda-releases-risk-ranking-model-food-tracing-final-rule" target="_blank">
932
+ FDA Risk-Ranking Model for Food Tracing Final Rule (2022)
933
+ </a>
934
+ </li>
935
+ <li>
936
+ <a href="https://hfpappexternal.fda.gov/scripts/FDARiskRankingModelforFoodTracingfinalrule/" target="_blank">
937
+ Outil Officiel FDA Risk-Ranking Model (Interface Interactive)
938
+ </a>
939
+ </li>
940
+ <li>
941
+ <a href="https://www.federalregister.gov/documents/2022/11/21/2022-24417/requirements-for-additional-traceability-records-for-certain-foods" target="_blank">
942
+ Federal Register - Règlement Traçabilité Alimentaire (21 CFR 204)
943
+ </a>
944
+ </li>
945
+ <li>
946
+ <a href="https://www.fda.gov/media/161908/download" target="_blank">
947
+ Document Technique FDA : "Risk-Ranking Model for Food Tracing" (PDF)
948
+ </a>
949
+ </li>
950
+ <li>
951
+ <strong>Publications Scientifiques :</strong> Journal of Food Protection, Risk Analysis, Food Control - rechercher "FDA risk-ranking model"
952
+ </li>
953
+ </ul>
954
+ </div>
955
+ </div>
956
+ </div>
957
+ </div>
958
+
959
+ <div class="controls-panel">
960
+ <div class="view-selector">
961
+ <button class="view-btn active" id="btn-ftl">Vue FTL</button>
962
+ <button class="view-btn" id="btn-all">Vue Globale</button>
963
+ <button class="view-btn" id="btn-pairs">Analyse Détaillée</button>
964
+ <button class="view-btn" id="btn-comparison">Analyse Comparative</button>
965
+ </div>
966
+
967
+ <div id="main-controls">
968
+ <div class="controls-grid">
969
+ <div class="search-container">
970
+ <span class="search-icon">🔍</span>
971
+ <input type="search" class="search-input" id="searchInput" placeholder="Rechercher...">
972
+ </div>
973
+ <div id="hazard-filter-container" style="display: none;">
974
+ <select class="filter-select" id="hazard-filter">
975
+ <option value="">Tous les Dangers</option>
976
+ </select>
977
+ </div>
978
+ </div>
979
+ </div>
980
+
981
+ <div id="comparison-controls" style="display: none;">
982
+ <div class="controls-grid" style="grid-template-columns: 1fr 1fr;">
983
+ <select class="filter-select" id="commodity-filter"></select>
984
+ <select class="filter-select" id="comparison-hazard-filter">
985
+ <option value="All">Tous les Dangers</option>
986
+ </select>
987
+ </div>
988
+ </div>
989
+ </div>
990
+
991
+ <div id="main-view-content" class="active">
992
+ <div class="dashboard">
993
+ <div class="metric-card">
994
+ <div class="metric-label">Produits Affichés</div>
995
+ <div class="metric-value" id="total-products">-</div>
996
+ </div>
997
+ <div class="metric-card high-risk">
998
+ <div class="metric-label">Haut Risque (≥400)</div>
999
+ <div class="metric-value" id="high-risk-count">-</div>
1000
+ </div>
1001
+ <div class="metric-card">
1002
+ <div class="metric-label">Catégories Uniques</div>
1003
+ <div class="metric-value" id="categories-count">-</div>
1004
+ </div>
1005
+ <div class="metric-card">
1006
+ <div class="metric-label">Score Moyen</div>
1007
+ <div class="metric-value" id="avg-score">-</div>
1008
+ </div>
1009
+ </div>
1010
+
1011
+ <div id="detail-panel">
1012
+ <div class="detail-card">
1013
+ <div class="detail-layout">
1014
+ <div>
1015
+ <h3 class="chart-title" id="criteria-chart-title"></h3>
1016
+ <div class="chart-canvas-container">
1017
+ <canvas id="criteriaChart"></canvas>
1018
+ </div>
1019
+ </div>
1020
+ <div id="takeaways-section">
1021
+ <h4>Points de Vigilance & Actions</h4>
1022
+ <ul id="takeaways-list"></ul>
1023
+ </div>
1024
+ </div>
1025
+ </div>
1026
+ </div>
1027
+
1028
+ <div class="table-container">
1029
+ <div class="table-header">
1030
+ <h3 class="table-title" id="table-title"></h3>
1031
+ <div class="results-count" id="results-count"></div>
1032
+ </div>
1033
+ <div class="table-wrapper">
1034
+ <table id="dataTable">
1035
+ <thead id="tableHead"></thead>
1036
+ <tbody id="tableBody"></tbody>
1037
+ </table>
1038
+ </div>
1039
+ </div>
1040
+ </div>
1041
+
1042
+ <div id="comparison-view-content" style="display:none;">
1043
+ <div class="chart-container">
1044
+ <h3 class="chart-title" id="stacked-chart-title">Analyse des Critères Pondérés</h3>
1045
+ <div class="chart-canvas-container" id="comparison-chart-container">
1046
+ <canvas id="stackedBarChart"></canvas>
1047
+ </div>
1048
+ </div>
1049
+ </div>
1050
+ </div>
1051
+
1052
+ <div class="footer">
1053
+ <p>FDA Risk Analyzer - Outil d'Analyse des Risques Alimentaires basé sur le modèle de la FDA</p>
1054
+ <p>© 2023 Tous droits réservés</p>
1055
+ </div>
1056
+
1057
+ <div id="guide-modal" class="modal">
1058
+ <div class="modal-content">
1059
+ <div class="modal-header">
1060
+ <h2 class="modal-title">Guide d'Utilisation</h2>
1061
+ <span class="close-modal" id="close-guide-btn">&times;</span>
1062
+ </div>
1063
+
1064
+ <h3>Comment utiliser cet outil</h3>
1065
+ <p>Cet outil vous aide à explorer le modèle de classement des risques de la FDA pour prioriser vos efforts en sécurité alimentaire.</p>
1066
+
1067
+ <h4>Les Vues d'Analyse</h4>
1068
+ <ul>
1069
+ <li><strong>Vue FTL</strong> Se concentre sur les produits à plus haut risque de la Food Traceability List. Idéal pour les audits.</li>
1070
+ <li><strong>Vue Globale</strong> Affiche tous les produits de la base de données pour une vue d'ensemble.</li>
1071
+ <li><strong>Analyse Détaillée</strong> Montre les couples produit/danger. Cliquez sur une ligne pour une analyse approfondie (graphique radar et actions recommandées).</li>
1072
+ <li><strong>Analyse Comparative</strong> Permet de comparer visuellement les contributions des différents facteurs de risque pour un produit et ses dangers.</li>
1073
+ </ul>
1074
+
1075
+ <h3>Les 7 Critères d'Évaluation de la FDA</h3>
1076
+ <div class="criteria-grid">
1077
+ <div class="criteria-card">
1078
+ <div class="criteria-title">C1: Gravité</div>
1079
+ <div class="criteria-desc">Gravité des effets sur la santé (mortalité, morbidité, hospitalisation).</div>
1080
+ </div>
1081
+ <div class="criteria-card">
1082
+ <div class="criteria-title">C2: Exposition</div>
1083
+ <div class="criteria-desc">Probabilité d'exposition au danger (fréquence de consommation, quantité).</div>
1084
+ </div>
1085
+ <div class="criteria-card">
1086
+ <div class="criteria-title">C3: Dose-Réponse</div>
1087
+ <div class="criteria-desc">Dose infectieuse minimale requise pour provoquer la maladie.</div>
1088
+ </div>
1089
+ <div class="criteria-card">
1090
+ <div class="criteria-title">C4: Croissance</div>
1091
+ <div class="criteria-desc">Capacité du pathogène à croître/survivre dans l'aliment.</div>
1092
+ </div>
1093
+ <div class="criteria-card">
1094
+ <div class="criteria-title">C5: Traitement</div>
1095
+ <div class="criteria-desc">Efficacité des traitements de décontamination appliqués.</div>
1096
+ </div>
1097
+ <div class="criteria-card">
1098
+ <div class="criteria-title">C6: Contamination</div>
1099
+ <div class="criteria-desc">Probabilité de contamination à la production (sources, environnement).</div>
1100
+ </div>
1101
+ <div class="criteria-card">
1102
+ <div class="criteria-title">C7: Consommation</div>
1103
+ <div class="criteria-desc">Mode de préparation/consommation (cru, cuit, transformé).</div>
1104
+ </div>
1105
+ </div>
1106
+
1107
+ <div class="modal-footer">
1108
+ Source officielle :
1109
+ <a href="https://hfpappexternal.fda.gov/scripts/FDARiskRankingModelforFoodTracingfinalrule/" target="_blank" rel="noopener noreferrer">
1110
+ FDA Risk-Ranking Model
1111
+ </a>.
1112
+ </div>
1113
+ </div>
1114
+ </div>
1115
+
1116
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
1117
+ <script>
1118
+ document.addEventListener('DOMContentLoaded', function() {
1119
+ // --- SCRIPT COMPLET ET CORRIGÉ ---
1120
+ const state = {
1121
+ currentDataset: 'ftl',
1122
+ searchTerm: '',
1123
+ hazardFilter: '',
1124
+ sortColumn: 'agg_score',
1125
+ sortDirection: 'desc',
1126
+ selectedCommodity: '',
1127
+ selectedHazard: 'All',
1128
+ charts: {}
1129
+ };
1130
+
1131
+ // Données réelles chargées depuis les fichiers JSON
1132
+ let fdaData = {
1133
+ aggregated_ftl: [],
1134
+ aggregated_all: [],
1135
+ pairs_all: []
1136
+ };
1137
+
1138
+ const viewConfig = {
1139
+ 'ftl': {
1140
+ dataKey: 'aggregated_ftl',
1141
+ title: 'Vue FTL (Produits à Traçabilité Renforcée)',
1142
+ columns: [
1143
+ { key: 'commodity', label: 'Produit', sortable: true },
1144
+ { key: 'commodity_category', label: 'Catégorie', sortable: true },
1145
+ { key: 'agg_score', label: 'Score', sortable: true, type: 'score' }
1146
+ ],
1147
+ scoreColumn: 'agg_score'
1148
+ },
1149
+ 'all': {
1150
+ dataKey: 'aggregated_all',
1151
+ title: 'Vue Globale (Tous les Produits)',
1152
+ columns: [
1153
+ { key: 'commodity', label: 'Produit', sortable: true },
1154
+ { key: 'commodity_category', label: 'Catégorie', sortable: true },
1155
+ { key: 'agg_score', label: 'Score', sortable: true, type: 'score' },
1156
+ { key: 'ftl', label: 'FTL', sortable: true, type: 'boolean' }
1157
+ ],
1158
+ scoreColumn: 'agg_score'
1159
+ },
1160
+ 'pairs': {
1161
+ dataKey: 'pairs_all',
1162
+ title: 'Analyse Détaillée (Produit x Danger)',
1163
+ columns: [
1164
+ { key: 'commodity', label: 'Produit', sortable: true },
1165
+ { key: 'hazardRef', label: 'Danger', sortable: true },
1166
+ { key: 'risk_score', label: 'Score', sortable: true, type: 'score' }
1167
+ ],
1168
+ scoreColumn: 'risk_score'
1169
+ }
1170
+ };
1171
+
1172
+ const el = {
1173
+ loadingOverlay: document.getElementById('loading-overlay'),
1174
+ mainViewContent: document.getElementById('main-view-content'),
1175
+ comparisonViewContent: document.getElementById('comparison-view-content'),
1176
+ mainControls: document.getElementById('main-controls'),
1177
+ comparisonControls: document.getElementById('comparison-controls'),
1178
+ searchInput: document.getElementById('searchInput'),
1179
+ hazardFilter: document.getElementById('hazard-filter'),
1180
+ hazardFilterContainer: document.getElementById('hazard-filter-container'),
1181
+ commodityFilter: document.getElementById('commodity-filter'),
1182
+ comparisonHazardFilter: document.getElementById('comparison-hazard-filter'),
1183
+ buttons: {
1184
+ ftl: document.getElementById('btn-ftl'),
1185
+ all: document.getElementById('btn-all'),
1186
+ pairs: document.getElementById('btn-pairs'),
1187
+ comparison: document.getElementById('btn-comparison')
1188
+ },
1189
+ metrics: {
1190
+ totalProducts: document.getElementById('total-products'),
1191
+ highRiskCount: document.getElementById('high-risk-count'),
1192
+ categoriesCount: document.getElementById('categories-count'),
1193
+ avgScore: document.getElementById('avg-score')
1194
+ },
1195
+ detailPanel: document.getElementById('detail-panel'),
1196
+ criteriaChartTitle: document.getElementById('criteria-chart-title'),
1197
+ criteriaCanvas: document.getElementById('criteriaChart'),
1198
+ takeawaysSection: document.getElementById('takeaways-section'),
1199
+ takeawaysList: document.getElementById('takeaways-list'),
1200
+ tableTitle: document.getElementById('table-title'),
1201
+ tableHead: document.getElementById('tableHead'),
1202
+ tableBody: document.getElementById('tableBody'),
1203
+ resultsCount: document.getElementById('results-count'),
1204
+ stackedBarChartCanvas: document.getElementById('stackedBarChart'),
1205
+ stackedChartTitle: document.getElementById('stacked-chart-title'),
1206
+ welcomeGuide: document.getElementById('welcome-guide'),
1207
+ closeGuide: document.getElementById('close-guide'),
1208
+ guideModal: document.getElementById('guide-modal'),
1209
+ guideBtn: document.getElementById('guide-btn'),
1210
+ closeGuideBtn: document.getElementById('close-guide-btn')
1211
+ };
1212
+
1213
+ const formatCell = (value, type) => {
1214
+ if (value === undefined || value === null) return '';
1215
+ const cleanValue = String(value);
1216
+ if (type === 'score') return `<span class="score-badge ${parseInt(cleanValue) >= 400 ? 'score-high' : parseInt(cleanValue) >= 200 ? 'score-medium' : 'score-low'}">${cleanValue}</span>`;
1217
+ if (type === 'boolean') return cleanValue === 'true' ? '<span style="color: var(--success-color); font-weight: 600;">✓</span>' : '<span style="color: var(--text-secondary);">-</span>';
1218
+ return cleanValue;
1219
+ };
1220
+
1221
+ const filterAndSortData = () => {
1222
+ const config = viewConfig[state.currentDataset];
1223
+ if (!config) return [];
1224
+ let data = [...fdaData[config.dataKey]];
1225
+
1226
+ if (state.searchTerm) {
1227
+ const search = state.searchTerm.toLowerCase();
1228
+ data = data.filter(row =>
1229
+ Object.values(row).some(v =>
1230
+ String(v).toLowerCase().includes(search)
1231
+ )
1232
+ );
1233
+ }
1234
+
1235
+ if (state.currentDataset === 'pairs' && state.hazardFilter) {
1236
+ data = data.filter(row => row.hazardRef === state.hazardFilter);
1237
+ }
1238
+
1239
+ if (state.sortColumn) {
1240
+ const isNumeric = config.columns.find(c => c.key === state.sortColumn)?.type === 'score';
1241
+ data.sort((a, b) => {
1242
+ const valA = a[state.sortColumn];
1243
+ const valB = b[state.sortColumn];
1244
+
1245
+ if (isNumeric) {
1246
+ return state.sortDirection === 'asc' ?
1247
+ (valA || 0) - (valB || 0) :
1248
+ (valB || 0) - (valA || 0);
1249
+ } else {
1250
+ const strA = String(valA || '').toLowerCase();
1251
+ const strB = String(valB || '').toLowerCase();
1252
+ return state.sortDirection === 'asc' ?
1253
+ strA.localeCompare(strB) :
1254
+ strB.localeCompare(strA);
1255
+ }
1256
+ });
1257
+ }
1258
+
1259
+ return data;
1260
+ };
1261
+
1262
+ const render = () => {
1263
+ const isComparisonView = state.currentDataset === 'comparison';
1264
+ el.mainViewContent.style.display = isComparisonView ? 'none' : 'block';
1265
+ el.comparisonViewContent.style.display = isComparisonView ? 'block' : 'none';
1266
+ el.mainControls.style.display = isComparisonView ? 'none' : 'block';
1267
+ el.comparisonControls.style.display = isComparisonView ? 'block' : 'none';
1268
+
1269
+ Object.values(el.buttons).forEach(btn => btn.classList.remove('active'));
1270
+ el.buttons[state.currentDataset].classList.add('active');
1271
+
1272
+ if (isComparisonView) {
1273
+ renderComparisonView();
1274
+ } else {
1275
+ renderMainView();
1276
+ }
1277
+ };
1278
+
1279
+ const renderMainView = () => {
1280
+ const config = viewConfig[state.currentDataset];
1281
+ const data = filterAndSortData();
1282
+
1283
+ el.tableTitle.textContent = config.title;
1284
+ el.resultsCount.textContent = `${data.length} résultat(s)`;
1285
+
1286
+ // Générer l'en-tête du tableau
1287
+ el.tableHead.innerHTML = `<tr>${config.columns.map(c =>
1288
+ `<th class="${c.sortable ? 'sortable' : ''}" data-column="${c.key}">${c.label}</th>`
1289
+ ).join('')}</tr>`;
1290
+
1291
+ // Mettre à jour l'indicateur de tri
1292
+ document.querySelectorAll('#tableHead th').forEach(th => {
1293
+ th.classList.remove('sort-asc', 'sort-desc');
1294
+ if (th.dataset.column === state.sortColumn) {
1295
+ th.classList.add(`sort-${state.sortDirection}`);
1296
+ }
1297
+ });
1298
+
1299
+ // Générer le corps du tableau
1300
+ el.tableBody.innerHTML = data.length === 0 ?
1301
+ `<tr><td colspan="${config.columns.length}" style="text-align:center;">🚫 Aucun résultat</td></tr>` :
1302
+ data.map((row, index) =>
1303
+ `<tr class="${state.currentDataset === 'pairs' ? 'clickable' : ''}" data-index="${index}">
1304
+ ${config.columns.map(col =>
1305
+ `<td>${formatCell(row[col.key], col.type)}</td>`
1306
+ ).join('')}
1307
+ </tr>`
1308
+ ).join('');
1309
+
1310
+ updateDashboard(data);
1311
+ el.hazardFilterContainer.style.display = state.currentDataset === 'pairs' ? 'flex' : 'none';
1312
+
1313
+ if (state.currentDataset !== 'pairs') {
1314
+ el.detailPanel.classList.remove('visible');
1315
+ }
1316
+ };
1317
+
1318
+ const renderComparisonView = () => {
1319
+ let data = fdaData.pairs_all.filter(p => p.commodity === state.selectedCommodity);
1320
+ if (state.selectedHazard !== 'All') {
1321
+ data = data.filter(p => p.hazardRef === state.selectedHazard);
1322
+ }
1323
+
1324
+ el.stackedChartTitle.textContent = `Analyse des Critères Pondérés pour : ${state.selectedCommodity}`;
1325
+
1326
+ if (state.charts.stackedBar) {
1327
+ state.charts.stackedBar.destroy();
1328
+ }
1329
+
1330
+ const criteria = ['C1w', 'C2w', 'C3w', 'C4w', 'C5w', 'C6w', 'C7w'];
1331
+ const labels = [
1332
+ 'C1: Gravité',
1333
+ 'C2: Exposition',
1334
+ 'C3: Dose-Réponse',
1335
+ 'C4: Croissance',
1336
+ 'C5: Traitement',
1337
+ 'C6: Contamination',
1338
+ 'C7: Consommation'
1339
+ ];
1340
+ const colors = [
1341
+ '#C62828', '#AD1457', '#6A1B9A',
1342
+ '#4527A0', '#283593', '#1565C0', '#0277BD'
1343
+ ];
1344
+
1345
+ if (data.length === 0) {
1346
+ document.getElementById('comparison-chart-container').innerHTML = '<p style="text-align:center; padding:2rem;">Aucune donnée disponible pour cette sélection</p>';
1347
+ return;
1348
+ }
1349
+
1350
+ state.charts.stackedBar = new Chart(el.stackedBarChartCanvas, {
1351
+ type: 'bar',
1352
+ data: {
1353
+ labels: data.map(d => d.hazardRef),
1354
+ datasets: criteria.map((c, i) => ({
1355
+ label: labels[i],
1356
+ data: data.map(d => d[c]),
1357
+ backgroundColor: colors[i]
1358
+ }))
1359
+ },
1360
+ options: {
1361
+ indexAxis: 'y',
1362
+ responsive: true,
1363
+ maintainAspectRatio: false,
1364
+ scales: {
1365
+ x: {
1366
+ stacked: true,
1367
+ title: {
1368
+ display: true,
1369
+ text: 'Score Pondéré'
1370
+ }
1371
+ },
1372
+ y: {
1373
+ stacked: true
1374
+ }
1375
+ },
1376
+ plugins: {
1377
+ legend: {
1378
+ position: 'top'
1379
+ }
1380
+ }
1381
+ }
1382
+ });
1383
+ };
1384
+
1385
+ const updateDashboard = (data) => {
1386
+ const scoreCol = viewConfig[state.currentDataset].scoreColumn;
1387
+ const scores = data.map(i => i[scoreCol]).filter(s => typeof s === 'number');
1388
+
1389
+ el.metrics.totalProducts.textContent = data.length;
1390
+ el.metrics.highRiskCount.textContent = scores.filter(s => s >= 400).length;
1391
+ el.metrics.categoriesCount.textContent = new Set(data.map(i => i.commodity_category).filter(Boolean)).size;
1392
+ el.metrics.avgScore.textContent = scores.length ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 'N/A';
1393
+ };
1394
+
1395
+ const showDetailPanel = (rowIndex) => {
1396
+ const data = filterAndSortData()[rowIndex];
1397
+ if (!data || !('C1' in data)) return;
1398
+
1399
+ el.detailPanel.classList.add('visible');
1400
+ el.criteriaCanvas.style.display = 'block';
1401
+ el.takeawaysSection.style.display = 'block';
1402
+ el.criteriaChartTitle.textContent = `Analyse de "${data.commodity}"`;
1403
+
1404
+ if (state.charts.criteria) {
1405
+ state.charts.criteria.destroy();
1406
+ }
1407
+
1408
+ // Nettoyer les balises HTML dans le nom du produit
1409
+ const cleanedProduct = data.commodity.replace(/<[^>]+>/g, '');
1410
+ const cleanedHazard = data.hazardRef.replace(/<[^>]+>/g, '');
1411
+ el.criteriaChartTitle.textContent = `Analyse "${cleanedProduct}" × Danger "${cleanedHazard}"`;
1412
+
1413
+ state.charts.criteria = new Chart(el.criteriaCanvas, {
1414
+ type: 'radar',
1415
+ data: {
1416
+ labels: [
1417
+ 'C1-Gravité',
1418
+ 'C2-Exposition',
1419
+ 'C3-Dose-Réponse',
1420
+ 'C4-Croissance',
1421
+ 'C5-Traitement',
1422
+ 'C6-Contamination',
1423
+ 'C7-Consommation'
1424
+ ],
1425
+ datasets: [{
1426
+ label: `${cleanedHazard}`,
1427
+ data: [data.C1, data.C2, data.C3, data.C4, data.C5, data.C6, data.C7],
1428
+ backgroundColor: 'rgba(0, 51, 102, 0.2)',
1429
+ borderColor: 'rgba(0, 51, 102, 1)',
1430
+ pointBackgroundColor: 'rgba(0, 51, 102, 1)'
1431
+ }]
1432
+ },
1433
+ options: {
1434
+ responsive: true,
1435
+ maintainAspectRatio: false,
1436
+ scales: {
1437
+ r: {
1438
+ beginAtZero: true,
1439
+ max: 9,
1440
+ ticks: {
1441
+ stepSize: 3
1442
+ }
1443
+ }
1444
+ },
1445
+ plugins: {
1446
+ legend: {
1447
+ display: false
1448
+ }
1449
+ }
1450
+ }
1451
+ });
1452
+
1453
+ const takeaways = [];
1454
+ if (data.C1 > 6) takeaways.push("Le danger associé est <strong>sévère</strong>. La maîtrise du CCP est critique.");
1455
+ if (data.C2 > 6) takeaways.push("Le produit est <strong>largement consommé</strong>. L'impact d'un rappel serait majeur.");
1456
+ if (data.C4 > 6) takeaways.push("Le pathogène peut <strong>proliférer rapidement</strong>. <u>Action</u>: Valider la chaîne du froid et les barèmes de DLC/DDM.");
1457
+ if (data.C5 < 3) takeaways.push("Pas d'<strong>étape d'assainissement</strong> efficace. <u>Action</u>: Maîtrise des matières premières essentielle.");
1458
+ if (data.C6 > 6) takeaways.push("Risque de <strong>contamination à la source élevé</strong>. <u>Action</u>: Renforcer audits fournisseurs et contrôles à réception.");
1459
+ if (data.C7 > 6) takeaways.push("Souvent <strong>consommé cru</strong>. Contrôles microbiologiques du produit fini à considérer.");
1460
+
1461
+ el.takeawaysList.innerHTML = takeaways.length ?
1462
+ takeaways.map(t => `<li><span class="icon">⚠️</span><span>${t}</span></li>`).join('') :
1463
+ "<li><span class='icon'>✓</span><span>Le risque est équilibré.</span></li>";
1464
+
1465
+ document.querySelectorAll('#tableBody tr').forEach(tr => tr.classList.remove('selected'));
1466
+ document.querySelector(`#tableBody tr[data-index="${rowIndex}"]`)?.classList.add('selected');
1467
+ el.detailPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });
1468
+ };
1469
+
1470
+ function populateFilters() {
1471
+ const mainHazards = [...new Set(fdaData.pairs_all.map(item => item.hazardRef))].filter(Boolean).sort();
1472
+ el.hazardFilter.innerHTML = '<option value="">Tous les Dangers</option>' +
1473
+ mainHazards.map(h => `<option value="${h}">${h}</option>`).join('');
1474
+
1475
+ const commodities = [...new Set(fdaData.pairs_all.map(item => item.commodity))].sort();
1476
+ el.commodityFilter.innerHTML = commodities.map(c => `<option value="${c}">${c}</option>`).join('');
1477
+
1478
+ if (commodities.length > 0) {
1479
+ state.selectedCommodity = commodities[0];
1480
+ el.commodityFilter.value = state.selectedCommodity;
1481
+ populateComparisonHazardFilter();
1482
+ }
1483
+ }
1484
+
1485
+ function populateComparisonHazardFilter(){
1486
+ const hazards = [...new Set(
1487
+ fdaData.pairs_all
1488
+ .filter(p => p.commodity === state.selectedCommodity)
1489
+ .map(p => p.hazardRef)
1490
+ )].sort();
1491
+
1492
+ el.comparisonHazardFilter.innerHTML = '<option value="All">Tous les Dangers</option>' +
1493
+ hazards.map(h => `<option value="${h}">${h}</option>`).join('');
1494
+ }
1495
+
1496
+ function setupEventListeners() {
1497
+ // Vérification de sécurité pour les éléments
1498
+ if (!el.searchInput || !el.hazardFilter) {
1499
+ console.error('Certains éléments DOM ne sont pas trouvés');
1500
+ return;
1501
+ }
1502
+
1503
+ // Debug: vérifier tous les éléments methodology
1504
+ console.log('Methodology elements check:');
1505
+ console.log('Header:', el.methodologyHeader);
1506
+ console.log('Content:', el.methodologyContent);
1507
+ console.log('Toggle:', el.methodologyToggle);
1508
+
1509
+ el.searchInput.addEventListener('input', () => {
1510
+ state.searchTerm = el.searchInput.value;
1511
+ render();
1512
+ });
1513
+
1514
+ el.hazardFilter.addEventListener('change', () => {
1515
+ state.hazardFilter = el.hazardFilter.value;
1516
+ render();
1517
+ });
1518
+
1519
+ el.tableHead.addEventListener('click', (e) => {
1520
+ const th = e.target.closest('th[data-column]');
1521
+ if (th) {
1522
+ state.sortColumn = th.dataset.column;
1523
+ state.sortDirection = state.sortDirection === 'asc' ? 'desc' : 'asc';
1524
+ render();
1525
+ }
1526
+ });
1527
+
1528
+ el.tableBody.addEventListener('click', (e) => {
1529
+ if (state.currentDataset === 'pairs') {
1530
+ const row = e.target.closest('tr');
1531
+ if (row) showDetailPanel(row.dataset.index);
1532
+ }
1533
+ });
1534
+
1535
+ Object.keys(el.buttons).forEach(key => {
1536
+ el.buttons[key].addEventListener('click', () => {
1537
+ state.currentDataset = key;
1538
+ if (key !== 'comparison') {
1539
+ state.sortColumn = viewConfig[key].scoreColumn;
1540
+ }
1541
+ Object.assign(state, {
1542
+ searchTerm: '',
1543
+ hazardFilter: '',
1544
+ sortDirection: 'desc'
1545
+ });
1546
+ el.searchInput.value = '';
1547
+ el.hazardFilter.value = '';
1548
+ el.detailPanel.classList.remove('visible');
1549
+ render();
1550
+ });
1551
+ });
1552
+
1553
+ el.commodityFilter.addEventListener('change', (e) => {
1554
+ state.selectedCommodity = e.target.value;
1555
+ state.selectedHazard = 'All';
1556
+ el.comparisonHazardFilter.value = 'All';
1557
+ populateComparisonHazardFilter();
1558
+ renderComparisonView();
1559
+ });
1560
+
1561
+ el.comparisonHazardFilter.addEventListener('change', (e) => {
1562
+ state.selectedHazard = e.target.value;
1563
+ renderComparisonView();
1564
+ });
1565
+
1566
+ el.guideBtn.addEventListener('click', () => {
1567
+ el.guideModal.style.display = 'block';
1568
+ });
1569
+
1570
+ el.closeGuideBtn.addEventListener('click', () => {
1571
+ el.guideModal.style.display = 'none';
1572
+ });
1573
+
1574
+ window.addEventListener('click', (event) => {
1575
+ if (event.target == el.guideModal) {
1576
+ el.guideModal.style.display = 'none';
1577
+ }
1578
+ });
1579
+
1580
+ if (localStorage.getItem('hideWelcomeGuide')) {
1581
+ el.welcomeGuide.style.display = 'none';
1582
+ }
1583
+
1584
+ if (el.closeGuide) {
1585
+ el.closeGuide.addEventListener('click', () => {
1586
+ el.welcomeGuide.style.display = 'none';
1587
+ try {
1588
+ localStorage.setItem('hideWelcomeGuide', 'true');
1589
+ } catch (e) {}
1590
+ });
1591
+ }
1592
+
1593
+ // Toggle methodology info avec fallback
1594
+ if (el.methodologyHeader && el.methodologyContent && el.methodologyToggle) {
1595
+ console.log('Setting up methodology toggle');
1596
+ el.methodologyHeader.addEventListener('click', () => {
1597
+ console.log('Methodology header clicked');
1598
+ const isExpanded = el.methodologyContent.classList.contains('expanded');
1599
+ console.log('Is expanded:', isExpanded);
1600
+
1601
+ if (isExpanded) {
1602
+ el.methodologyContent.classList.remove('expanded');
1603
+ el.methodologyToggle.classList.remove('expanded');
1604
+ console.log('Collapsed');
1605
+ } else {
1606
+ el.methodologyContent.classList.add('expanded');
1607
+ el.methodologyToggle.classList.add('expanded');
1608
+ console.log('Expanded');
1609
+ }
1610
+ });
1611
+ } else {
1612
+ console.error('Methodology elements missing:', {
1613
+ header: !!el.methodologyHeader,
1614
+ content: !!el.methodologyContent,
1615
+ toggle: !!el.methodologyToggle
1616
+ });
1617
+
1618
+ // Fallback: essayer de nouveau dans 100ms
1619
+ setTimeout(() => {
1620
+ const header = document.getElementById('methodology-header');
1621
+ const content = document.getElementById('methodology-content');
1622
+ const toggle = document.getElementById('methodology-toggle');
1623
+
1624
+ if (header && content && toggle) {
1625
+ console.log('Retry: Setting up methodology toggle');
1626
+ header.addEventListener('click', () => {
1627
+ console.log('Methodology header clicked (retry)');
1628
+ const isExpanded = content.classList.contains('expanded');
1629
+
1630
+ if (isExpanded) {
1631
+ content.classList.remove('expanded');
1632
+ toggle.classList.remove('expanded');
1633
+ } else {
1634
+ content.classList.add('expanded');
1635
+ toggle.classList.add('expanded');
1636
+ }
1637
+ });
1638
+ } else {
1639
+ console.error('Methodology elements still not found after retry');
1640
+ }
1641
+ }, 100);
1642
+ }
1643
+ }
1644
+
1645
+ // Fonction de chargement des données depuis les fichiers JSON avec les noms réels
1646
+ async function loadAllData() {
1647
+ try {
1648
+ // Charger les données depuis les fichiers JSON avec les noms exacts visibles dans HuggingFace
1649
+ const [ftlResponse, allResponse, pairsResponse] = await Promise.all([
1650
+ fetch('fda_aggregated_ftl_20250813_164803.json'),
1651
+ fetch('fda_aggregated_all_20250813_164803.json'),
1652
+ fetch('fda_pairs_all_20250813_164803.json')
1653
+ ]);
1654
+
1655
+ if (!ftlResponse.ok || !allResponse.ok || !pairsResponse.ok) {
1656
+ throw new Error('Erreur lors du chargement des fichiers JSON');
1657
+ }
1658
+
1659
+ fdaData.aggregated_ftl = await ftlResponse.json();
1660
+ fdaData.aggregated_all = await allResponse.json();
1661
+ fdaData.pairs_all = await pairsResponse.json();
1662
+
1663
+ // Initialiser les filtres et afficher les données
1664
+ populateFilters();
1665
+ render();
1666
+
1667
+ // Masquer l'écran de chargement
1668
+ el.loadingOverlay.style.display = 'none';
1669
+ } catch (error) {
1670
+ console.error('Erreur lors du chargement des données:', error);
1671
+ el.loadingOverlay.innerHTML = `
1672
+ <div class="error-message">
1673
+ <h2>Erreur de chargement</h2>
1674
+ <p>Impossible de charger les fichiers de données.</p>
1675
+ <p>Vérifiez que les fichiers suivants sont présents dans le répertoire:</p>
1676
+ <ul style="text-align: left; margin: 1rem auto; max-width: 400px;">
1677
+ <li>fda_aggregated_ftl_20250813_164803.json</li>
1678
+ <li>fda_aggregated_all_20250813_164803.json</li>
1679
+ <li>fda_pairs_all_20250813_164803.json</li>
1680
+ </ul>
1681
+ <p>Message d'erreur : ${error.message}</p>
1682
+ </div>
1683
+ `;
1684
+ }
1685
+ }
1686
+
1687
+ setupEventListeners();
1688
+ loadAllData();
1689
+ });
1690
+ </script>
1691
+ </body>
1692
+ </html>
translation.js ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const translations = {
2
+ fr: {
3
+ "Chargement des données...": "Loading data...",
4
+ "Veuillez patienter.": "Please wait.",
5
+ "Guide d'Utilisation": "Usage Guide",
6
+ "Guide Rapide de l'Outil": "Quick Tool Guide",
7
+ "1. Choisissez une Vue": "1. Choose a View",
8
+ "Utilisez les boutons ci-dessous pour sélectionner votre niveau d'analyse.": "Use the buttons below to select your analysis level.",
9
+ "2. Filtrez les Données": "2. Filter Data",
10
+ "Utilisez la recherche et les filtres pour affiner les résultats.": "Use search and filters to refine results.",
11
+ "3. Analysez en Profondeur": "3. Analyze in Depth",
12
+ "Cliquez sur les lignes du tableau ou utilisez la vue comparative.": "Click on table rows or use the comparative view.",
13
+ "Comprendre la Méthodologie FDA de Classement des Risques": "Understanding the FDA Risk Ranking Methodology",
14
+ "Qu'est-ce que le Modèle de Classement des Risques de la FDA ?": "What is the FDA Risk Ranking Model?",
15
+ "Le FDA Risk-Ranking Model est un outil scientifique développé par l'administration américaine (FDA) pour prioriser les efforts de surveillance et de contrôle dans l'industrie alimentaire. Il permet d'identifier quels couples produit/danger représentent les risques les plus élevés pour la santé publique.": "The FDA Risk-Ranking Model is a scientific tool developed by the U.S. administration (FDA) to prioritize surveillance and control efforts in the food industry. It identifies which product/hazard pairs represent the highest risks to public health.",
16
+ "Objectif principal :": "Main Objective:",
17
+ "Aider les autorités sanitaires et les professionnels de l'alimentaire à concentrer leurs ressources limitées sur les risques qui comptent vraiment.": "Help health authorities and food professionals focus their limited resources on the risks that truly matter.",
18
+ "Comment ça fonctionne : Les 7 Critères d'Évaluation": "How it Works: The 7 Evaluation Criteria",
19
+ "Chaque couple produit/pathogène est évalué selon 7 critères scientifiques, notés de 1 à 9 :": "Each product/pathogen pair is evaluated according to 7 scientific criteria, rated from 1 to 9:",
20
+ "C1 - Gravité : Sévérité des effets sur la santé (mortalité, hospitalisation)": "C1 - Severity: Severity of health effects (mortality, hospitalization)",
21
+ "C2 - Exposition : Fréquence et quantité de consommation du produit": "C2 - Exposure: Frequency and quantity of product consumption",
22
+ "C3 - Dose-Réponse : Quantité minimale de pathogène nécessaire pour déclencher la maladie": "C3 - Dose-Response: Minimum amount of pathogen needed to trigger illness",
23
+ "C4 - Croissance : Capacité du pathogène à survivre/proliférer dans l'aliment": "C4 - Growth: Ability of the pathogen to survive/proliferate in food",
24
+ "C5 - Traitement : Efficacité des procédés de décontamination": "C5 - Treatment: Effectiveness of decontamination processes",
25
+ "C6 - Contamination : Probabilité de contamination à la production": "C6 - Contamination: Probability of contamination during production",
26
+ "C7 - Consommation : Mode de préparation (cru, cuit, transformé)": "C7 - Consumption: Preparation method (raw, cooked, processed)",
27
+ "Le score final est calculé en multipliant ces 7 critères, créant un classement objectif des risques.": "The final score is calculated by multiplying these 7 criteria, creating an objective risk ranking.",
28
+ "Pourquoi c'est révolutionnaire ?": "Why is it revolutionary?",
29
+ "Avant ce modèle, les décisions étaient souvent basées sur :": "Before this model, decisions were often based on:",
30
+ "L'intuition ou l'expérience personnelle": "Intuition or personal experience",
31
+ "Les crises médiatiques du moment": "Media crises of the moment",
32
+ "Des approches fragmentées par produit OU par pathogène": "Fragmented approaches by product OR by pathogen",
33
+ "Le modèle FDA apporte :": "The FDA model brings:",
34
+ "Une approche scientifique et quantitative": "A scientific and quantitative approach",
35
+ "Une vision globale produit × pathogène": "A global product × pathogen view",
36
+ "Une transparence dans les décisions de priorisation": "Transparency in prioritization decisions",
37
+ "Une comparabilité entre différents risques": "Comparability between different risks",
38
+ "Intégration avec votre Plan HACCP": "Integration with your HACCP Plan",
39
+ "Ce modèle ne remplace pas HACCP, il le renforce !": "This model does not replace HACCP, it strengthens it!",
40
+ "Analyse des Dangers : Utilisez les scores pour prioriser vos dangers significatifs (étape 1 HACCP)": "Hazard Analysis: Use scores to prioritize your significant hazards (HACCP step 1)",
41
+ "Points Critiques : Concentrez vos CCP sur les couples à haut score": "Critical Control Points: Focus your CCPs on high-scoring pairs",
42
+ "Surveillance : Adaptez la fréquence de monitoring selon les scores de risque": "Monitoring: Adjust monitoring frequency according to risk scores",
43
+ "Validation : Justifiez scientifiquement vos choix de maîtrise": "Validation: Scientifically justify your control choices",
44
+ "Audits : Orientez vos vérifications sur les zones à plus haut risque": "Audits: Focus your verifications on higher-risk areas",
45
+ "Applications Pratiques en Industrie": "Practical Applications in Industry",
46
+ "Responsables Qualité : Priorisation des plans de surveillance et contrôles": "Quality Managers: Prioritization of surveillance and control plans",
47
+ "Acheteurs : Évaluation et sélection des fournisseurs selon les risques": "Buyers: Evaluation and selection of suppliers based on risks",
48
+ "R&D : Conception de produits avec maîtrise des risques intégrée": "R&D: Product design with integrated risk control",
49
+ "Direction : Allocation des budgets sécurité alimentaire basée sur les risques réels": "Management: Allocation of food safety budgets based on real risks",
50
+ "Auditeurs : Focus des audits sur les couples produit/danger critiques": "Auditors: Focus audits on critical product/hazard pairs",
51
+ "Food Traceability List (FTL)": "Food Traceability List (FTL)",
52
+ "La Food Traceability List est une liste des aliments à plus haut risque, identifiés grâce à ce modèle. Ces produits sont soumis à des exigences de traçabilité renforcée aux États-Unis depuis janvier 2026.": "The Food Traceability List is a list of higher-risk foods, identified using this model. These products are subject to enhanced traceability requirements in the United States since January 2026.",
53
+ "Conseil :": "Tip:",
54
+ "Même en Europe, surveiller ces produits FTL peut vous donner un avantage concurrentiel et anticiper les évolutions réglementaires.": "Even in Europe, monitoring these FTL products can give you a competitive advantage and anticipate regulatory changes.",
55
+ "Sources et Références Officielles": "Official Sources and References",
56
+ "Publications Scientifiques :": "Scientific Publications:",
57
+ "Journal of Food Protection, Risk Analysis, Food Control - rechercher \"FDA risk-ranking model\"": "Journal of Food Protection, Risk Analysis, Food Control - search for \"FDA risk-ranking model\"",
58
+ "Vue FTL": "FTL View",
59
+ "Vue Globale": "Global View",
60
+ "Analyse Détaillée": "Detailed Analysis",
61
+ "Analyse Comparative": "Comparative Analysis",
62
+ "Rechercher...": "Search...",
63
+ "Tous les Dangers": "All Hazards",
64
+ "Produits Affichés": "Products Displayed",
65
+ "Haut Risque (≥400)": "High Risk (≥400)",
66
+ "Catégories Uniques": "Unique Categories",
67
+ "Score Moyen": "Average Score",
68
+ "Points de Vigilance & Actions": "Vigilance Points & Actions",
69
+ "Analyse des Critères Pondérés": "Weighted Criteria Analysis",
70
+ "FDA Risk Analyzer - Outil d'Analyse des Risques Alimentaires basé sur le modèle de la FDA": "FDA Risk Analyzer - Food Risk Analysis Tool based on the FDA model",
71
+ "© 2023 Tous droits réservés": "© 2023 All rights reserved",
72
+ "Comment utiliser cet outil": "How to use this tool",
73
+ "Cet outil vous aide à explorer le modèle de classement des risques de la FDA pour prioriser vos efforts en sécurité alimentaire.": "This tool helps you explore the FDA risk ranking model to prioritize your food safety efforts.",
74
+ "Les Vues d'Analyse": "Analysis Views",
75
+ "Se concentre sur les produits à plus haut risque de la Food Traceability List. Idéal pour les audits.": "Focuses on higher-risk products from the Food Traceability List. Ideal for audits.",
76
+ "Affiche tous les produits de la base de données pour une vue d'ensemble.": "Displays all products in the database for an overview.",
77
+ "Montre les couples produit/danger. Cliquez sur une ligne pour une analyse approfondie (graphique radar et actions recommandées).": "Shows product/hazard pairs. Click on a row for in-depth analysis (radar chart and recommended actions).",
78
+ "Permet de comparer visuellement les contributions des différents facteurs de risque pour un produit et ses dangers.": "Allows visual comparison of the contributions of different risk factors for a product and its hazards.",
79
+ "Les 7 Critères d'Évaluation de la FDA": "The 7 FDA Evaluation Criteria",
80
+ "C1: Gravité": "C1: Severity",
81
+ "Gravité des effets sur la santé (mortalité, morbidité, hospitalisation).": "Severity of health effects (mortality, morbidity, hospitalization).",
82
+ "C2: Exposition": "C2: Exposure",
83
+ "Probabilité d'exposition au danger (fréquence de consommation, quantité).": "Probability of exposure to the hazard (frequency of consumption, quantity).",
84
+ "C3: Dose-Réponse": "C3: Dose-Response",
85
+ "Dose infectieuse minimale requise pour provoquer la maladie.": "Minimum infectious dose required to cause illness.",
86
+ "C4: Croissance": "C4: Growth",
87
+ "Capacité du pathogène à croître/survivre dans l'aliment.": "Ability of the pathogen to grow/survive in food.",
88
+ "C5: Traitement": "C5: Treatment",
89
+ "Efficacité des traitements de décontamination appliqués.": "Effectiveness of decontamination treatments applied.",
90
+ "C6: Contamination": "C6: Contamination",
91
+ "Probabilité de contamination à la production (sources, environnement).": "Probability of contamination during production (sources, environment).",
92
+ "C7: Consommation": "C7: Consumption",
93
+ "Mode de préparation/consommation (cru, cuit, transformé).": "Preparation/consumption method (raw, cooked, processed).",
94
+ "Source officielle :": "Official source:",
95
+ "FDA Risk-Ranking Model": "FDA Risk-Ranking Model",
96
+ "Erreur de chargement": "Loading error",
97
+ "Impossible de charger les fichiers de données.": "Could not load data files.",
98
+ "Vérifiez que les fichiers suivants sont présents dans le répertoire:": "Verify that the following files are present in the directory:",
99
+ "Message d'erreur :": "Error message:",
100
+ "Produit": "Product",
101
+ "Catégorie": "Category",
102
+ "Score": "Score",
103
+ "Danger": "Hazard",
104
+ "Analyse de ": "Analysis of ",
105
+ "× Danger ": "× Hazard ",
106
+ "Le danger associé est sévère. La maîtrise du CCP est critique.": "The associated hazard is severe. CCP control is critical.",
107
+ "Le produit est largement consommé. L'impact d'un rappel serait majeur.": "The product is widely consumed. The impact of a recall would be major.",
108
+ "Le pathogène peut proliférer rapidement. Action: Valider la chaîne du froid et les barèmes de DLC/DDM.": "The pathogen can proliferate rapidly. Action: Validate the cold chain and shelf-life/use-by dates.",
109
+ "Pas d'étape d'assainissement efficace. Action: Maîtrise des matières premières essentielle.": "No effective sanitation step. Action: Essential raw material control.",
110
+ "Risque de contamination à la source élevé. Action: Renforcer audits fournisseurs et contrôles à réception.": "High risk of source contamination. Action: Strengthen supplier audits and reception controls.",
111
+ "Souvent consommé cru. Contrôles microbiologiques du produit fini à considérer.": "Often consumed raw. Microbiological controls of the finished product to be considered.",
112
+ "Le risque est équilibré.": "The risk is balanced.",
113
+ "Analyse des Critères Pondérés pour : ": "Weighted Criteria Analysis for: ",
114
+ "Aucune donnée disponible pour cette sélection": "No data available for this selection",
115
+ "Score Pondéré": "Weighted Score",
116
+ "FDA Risk Analyzer": "FDA Risk Analyzer",
117
+ "Vue FTL (Produits à Traçabilité Renforcée)": "FTL View (Enhanced Traceability Products)",
118
+ "Vue Globale (Tous les Produits)": "Global View (All Products)",
119
+ "Analyse Détaillée (Produit x Danger)": "Detailed Analysis (Product x Hazard)"
120
+ },
121
+ en: {
122
+ // This will be populated dynamically if we switch to French and back, or if we want to add more direct English text
123
+ }
124
+ };
125
+
126
+ let currentLanguage = localStorage.getItem('currentLanguage') || 'fr';
127
+
128
+ const applyTranslations = () => {
129
+ const elements = document.querySelectorAll('[data-translate-key]');
130
+ elements.forEach(element => {
131
+ const key = element.getAttribute('data-translate-key');
132
+ if (translations[currentLanguage][key]) {
133
+ element.textContent = translations[currentLanguage][key];
134
+ }
135
+ });
136
+
137
+ // Translate elements that don't have a direct data-translate-key but have identifiable text
138
+ const textNodes = [];
139
+ const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
140
+ let node;
141
+ while ((node = walk.nextNode())) {
142
+ if (node.parentNode.nodeName !== 'SCRIPT' && node.parentNode.nodeName !== 'STYLE') {
143
+ textNodes.push(node);
144
+ }
145
+ }
146
+
147
+ textNodes.forEach(node => {
148
+ let originalText = node.nodeValue.trim();
149
+ // Handle cases where the original text might have HTML entities that are not visible in the dictionary
150
+ originalText = originalText.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
151
+
152
+ if (originalText && translations[currentLanguage][originalText]) {
153
+ node.nodeValue = translations[currentLanguage][originalText];
154
+ }
155
+ });
156
+
157
+ // Update placeholders
158
+ const searchInput = document.getElementById('searchInput');
159
+ if (searchInput && translations[currentLanguage]["Rechercher..."]) {
160
+ searchInput.placeholder = translations[currentLanguage]["Rechercher..."];
161
+ }
162
+
163
+ // Update dynamic content that might not be in the initial DOM
164
+ const updateDynamicContent = () => {
165
+ document.getElementById('table-title').textContent = translations[currentLanguage][document.getElementById('table-title').textContent] || document.getElementById('table-title').textContent;
166
+ // Update other dynamic elements if necessary
167
+ if (document.getElementById('results-count')) {
168
+ const resultsText = document.getElementById('results-count').textContent;
169
+ const match = resultsText.match(/(\d+)\srésultat\(s\)/);
170
+ if (match && translations[currentLanguage]["résultat(s)"]) {
171
+ document.getElementById('results-count').textContent = match[1] + ' ' + translations[currentLanguage]["résultat(s)"];
172
+ }
173
+ }
174
+ };
175
+ updateDynamicContent();
176
+ };
177
+
178
+ const switchLanguage = (lang) => {
179
+ currentLanguage = lang;
180
+ localStorage.setItem('currentLanguage', lang);
181
+ location.reload(); // Simple reload to re-apply translations
182
+ };
183
+
184
+ // Add a language toggle button to the header
185
+ document.addEventListener('DOMContentLoaded', () => {
186
+ const headerContent = document.querySelector('.header-content');
187
+ if (headerContent) {
188
+ const langToggleContainer = document.createElement('div');
189
+ langToggleContainer.style.display = 'flex';
190
+ langToggleContainer.style.gap = '10px';
191
+ langToggleContainer.style.alignItems = 'center';
192
+
193
+ const frButton = document.createElement('button');
194
+ frButton.textContent = 'FR';
195
+ frButton.className = 'guide-btn'; // Re-use existing button style
196
+ frButton.style.backgroundColor = currentLanguage === 'fr' ? 'var(--primary-light)' : 'var(--primary-color)';
197
+ frButton.onclick = () => switchLanguage('fr');
198
+ langToggleContainer.appendChild(frButton);
199
+
200
+ const enButton = document.createElement('button');
201
+ enButton.textContent = 'EN';
202
+ enButton.className = 'guide-btn'; // Re-use existing button style
203
+ enButton.style.backgroundColor = currentLanguage === 'en' ? 'var(--primary-light)' : 'var(--primary-color)';
204
+ enButton.onclick = () => switchLanguage('en');
205
+ langToggleContainer.appendChild(enButton);
206
+
207
+ headerContent.appendChild(langToggleContainer);
208
+ }
209
+
210
+ // Apply translations on DOMContentLoaded
211
+ applyTranslations();
212
+ });