MMOON commited on
Commit
d2ff234
·
verified ·
1 Parent(s): 3a8b5db

Upload glodeactioniplanv2.html

Browse files
Files changed (1) hide show
  1. glodeactioniplanv2.html +1471 -0
glodeactioniplanv2.html ADDED
@@ -0,0 +1,1471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Assistant VisiPilot pour Plan d'Actions IFS</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
9
+ <style>
10
+ /* General Styles */
11
+ body {
12
+ font-family: 'Inter', 'Roboto', sans-serif;
13
+ margin: 0;
14
+ padding: 0;
15
+ background-color: #f8fafd;
16
+ color: #333;
17
+ line-height: 1.6;
18
+ }
19
+
20
+ .container {
21
+ max-width: 1200px;
22
+ margin: 25px auto;
23
+ padding: 30px;
24
+ background-color: #ffffff;
25
+ border-radius: 12px;
26
+ box-shadow: 0 5px 25px rgba(0, 0, 0, 0.1);
27
+ }
28
+
29
+ /* Banner */
30
+ .banner {
31
+ background-image: url('https://raw.githubusercontent.com/M00N69/BUSCAR/main/logo%2002%20copie.jpg');
32
+ background-size: cover;
33
+ height: 120px;
34
+ background-position: center;
35
+ margin-bottom: 0;
36
+ border-radius: 10px 10px 0 0;
37
+ position: relative;
38
+ background-blend-mode: overlay;
39
+ background-color: rgba(26, 115, 232, 0.05);
40
+ }
41
+
42
+ .banner-overlay {
43
+ display: none;
44
+ }
45
+
46
+ /* Header sous la bannière */
47
+ .header-section {
48
+ background: linear-gradient(135deg, #1a73e8 0%, #004080 100%);
49
+ color: white;
50
+ text-align: center;
51
+ padding: 25px 20px;
52
+ margin-bottom: 30px;
53
+ border-radius: 0 0 10px 10px;
54
+ }
55
+
56
+ .main-header {
57
+ font-size: 32px;
58
+ font-weight: 700;
59
+ color: white;
60
+ margin-bottom: 8px;
61
+ letter-spacing: -0.5px;
62
+ }
63
+
64
+ .main-subtitle {
65
+ color: rgba(255, 255, 255, 0.9);
66
+ font-size: 18px;
67
+ margin: 0;
68
+ font-weight: 400;
69
+ }
70
+
71
+ /* Expander */
72
+ .expander-header {
73
+ background-color: #e8f0fe;
74
+ border: 1px solid #c5dafc;
75
+ border-radius: 8px;
76
+ padding: 18px 25px;
77
+ cursor: pointer;
78
+ display: flex;
79
+ justify-content: space-between;
80
+ align-items: center;
81
+ margin-bottom: 20px;
82
+ font-weight: 500;
83
+ color: #1a73e8;
84
+ transition: background-color 0.3s ease, box-shadow 0.2s ease;
85
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
86
+ }
87
+
88
+ .expander-header:hover {
89
+ background-color: #d2e3fc;
90
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
91
+ }
92
+
93
+ .expander-header::after {
94
+ content: '▼';
95
+ margin-left: 15px;
96
+ transition: transform 0.3s ease;
97
+ font-size: 0.9em;
98
+ }
99
+
100
+ .expander-header.expanded::after {
101
+ content: '▲';
102
+ transform: rotate(180deg);
103
+ }
104
+
105
+ .expander-content {
106
+ background-color: #f0f6ff;
107
+ border: 1px solid #d2e3fc;
108
+ border-top: none;
109
+ border-radius: 0 0 8px 8px;
110
+ padding: 25px;
111
+ margin-top: -15px;
112
+ display: none;
113
+ overflow: hidden;
114
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.03);
115
+ }
116
+
117
+ .expander-content ol {
118
+ padding-left: 25px;
119
+ margin-top: 15px;
120
+ }
121
+
122
+ .expander-content li {
123
+ margin-bottom: 10px;
124
+ }
125
+
126
+ .expander-content strong {
127
+ color: #004080;
128
+ }
129
+
130
+ /* Form Elements */
131
+ .form-group {
132
+ margin-bottom: 25px;
133
+ padding: 15px;
134
+ background-color: #f7f9fc;
135
+ border-radius: 8px;
136
+ border: 1px solid #e0e0e0;
137
+ }
138
+
139
+ .form-group label {
140
+ display: block;
141
+ margin-bottom: 10px;
142
+ font-weight: 500;
143
+ color: #5f6368;
144
+ font-size: 1.1em;
145
+ }
146
+
147
+ .form-input {
148
+ width: calc(100% - 24px);
149
+ padding: 12px;
150
+ border: 1px solid #dadce0;
151
+ border-radius: 8px;
152
+ font-size: 16px;
153
+ box-sizing: border-box;
154
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
155
+ }
156
+
157
+ .form-input:focus {
158
+ border-color: #1a73e8;
159
+ box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
160
+ outline: none;
161
+ }
162
+
163
+ /* File Drop Zone */
164
+ .file-drop-zone {
165
+ border: 2px dashed #dadce0;
166
+ border-radius: 12px;
167
+ padding: 32px;
168
+ text-align: center;
169
+ transition: all 0.3s ease;
170
+ cursor: pointer;
171
+ background: linear-gradient(135deg, #fafbfc 0%, #f8fafd 100%);
172
+ }
173
+
174
+ .file-drop-zone:hover {
175
+ border-color: #1a73e8;
176
+ background: linear-gradient(135deg, #e8f0fe 0%, #f0f6ff 100%);
177
+ transform: translateY(-2px);
178
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
179
+ }
180
+
181
+ .file-drop-icon {
182
+ font-size: 48px;
183
+ color: #1a73e8;
184
+ opacity: 0.7;
185
+ margin-bottom: 16px;
186
+ }
187
+
188
+ /* Buttons */
189
+ .btn {
190
+ display: inline-flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ gap: 8px;
194
+ padding: 12px 24px;
195
+ border: none;
196
+ border-radius: 8px;
197
+ font-size: 14px;
198
+ font-weight: 500;
199
+ cursor: pointer;
200
+ transition: all 0.3s ease;
201
+ text-transform: uppercase;
202
+ letter-spacing: 0.5px;
203
+ min-height: 44px;
204
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
205
+ }
206
+
207
+ .btn:hover {
208
+ transform: translateY(-2px);
209
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
210
+ }
211
+
212
+ .btn:disabled {
213
+ opacity: 0.6;
214
+ cursor: not-allowed;
215
+ transform: none !important;
216
+ }
217
+
218
+ .btn-primary {
219
+ background: linear-gradient(135deg, #004080 0%, #1a73e8 100%);
220
+ color: white;
221
+ }
222
+
223
+ .btn-success {
224
+ background: linear-gradient(135deg, #34a853 0%, #2e8b4e 100%);
225
+ color: white;
226
+ }
227
+
228
+ .btn-danger {
229
+ background: linear-gradient(135deg, #ea4335 0%, #d93025 100%);
230
+ color: white;
231
+ }
232
+
233
+ .btn-small {
234
+ padding: 8px 16px;
235
+ font-size: 12px;
236
+ min-height: 36px;
237
+ }
238
+
239
+ /* Messages */
240
+ .message {
241
+ padding: 16px 20px;
242
+ border-radius: 8px;
243
+ margin-bottom: 20px;
244
+ display: none;
245
+ align-items: center;
246
+ gap: 12px;
247
+ font-weight: 500;
248
+ }
249
+
250
+ .message.show {
251
+ display: flex;
252
+ }
253
+
254
+ .message-success {
255
+ background: #e6f4ea;
256
+ color: #1e8e3e;
257
+ border: 1px solid #c8e6c9;
258
+ }
259
+
260
+ .message-error {
261
+ background: #fce8e6;
262
+ color: #c5221f;
263
+ border: 1px solid #f9bdbb;
264
+ }
265
+
266
+ /* Data Table */
267
+ .data-table-container {
268
+ background: #ffffff;
269
+ border-radius: 12px;
270
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
271
+ overflow: hidden;
272
+ margin-top: 35px;
273
+ display: none;
274
+ }
275
+
276
+ .data-table-header {
277
+ background: linear-gradient(135deg, #004080 0%, #1a73e8 100%);
278
+ color: white;
279
+ padding: 20px 24px;
280
+ display: flex;
281
+ justify-content: space-between;
282
+ align-items: center;
283
+ flex-wrap: wrap;
284
+ gap: 12px;
285
+ }
286
+
287
+ .data-table-title {
288
+ font-size: 20px;
289
+ font-weight: 600;
290
+ }
291
+
292
+ .data-table {
293
+ width: 100%;
294
+ border-collapse: collapse;
295
+ }
296
+
297
+ .data-table th,
298
+ .data-table td {
299
+ border: 1px solid #e8eaed;
300
+ padding: 15px;
301
+ text-align: left;
302
+ vertical-align: top;
303
+ font-size: 15px;
304
+ }
305
+
306
+ .data-table th {
307
+ background-color: #f0f4f7;
308
+ color: #3c4043;
309
+ font-weight: 500;
310
+ }
311
+
312
+ .data-table tbody tr:nth-child(even) {
313
+ background-color: #fcfdff;
314
+ }
315
+
316
+ .data-table tbody tr:hover {
317
+ background-color: #f5f8fc;
318
+ }
319
+
320
+ .requirement-link {
321
+ color: #1a73e8;
322
+ text-decoration: none;
323
+ font-weight: 500;
324
+ transition: color 0.2s ease;
325
+ }
326
+
327
+ .requirement-link:hover {
328
+ color: #004080;
329
+ text-decoration: underline;
330
+ }
331
+
332
+ /* Bulk Generation */
333
+ .bulk-generation-container {
334
+ margin-top: 16px;
335
+ padding: 16px;
336
+ background: linear-gradient(135deg, #e8f0fe 0%, #f0f6ff 100%);
337
+ border: 1px solid #1a73e8;
338
+ border-radius: 8px;
339
+ text-align: center;
340
+ }
341
+
342
+ .generation-options {
343
+ display: flex;
344
+ gap: 12px;
345
+ align-items: center;
346
+ justify-content: center;
347
+ margin-top: 12px;
348
+ flex-wrap: wrap;
349
+ }
350
+
351
+ .option-select {
352
+ padding: 4px 8px;
353
+ border: 1px solid #dadce0;
354
+ border-radius: 4px;
355
+ font-size: 11px;
356
+ background: #ffffff;
357
+ }
358
+
359
+ .bulk-progress {
360
+ display: none;
361
+ margin-top: 16px;
362
+ padding: 16px;
363
+ background: #ffffff;
364
+ border-radius: 8px;
365
+ border: 1px solid #e8eaed;
366
+ }
367
+
368
+ .bulk-progress.show {
369
+ display: block;
370
+ }
371
+
372
+ .progress-header {
373
+ display: flex;
374
+ justify-content: space-between;
375
+ align-items: center;
376
+ margin-bottom: 12px;
377
+ }
378
+
379
+ .progress-counter {
380
+ background: #1a73e8;
381
+ color: white;
382
+ padding: 4px 12px;
383
+ border-radius: 12px;
384
+ font-size: 12px;
385
+ font-weight: 500;
386
+ }
387
+
388
+ .progress-bar-container {
389
+ background: #e0e0e0;
390
+ border-radius: 8px;
391
+ height: 8px;
392
+ overflow: hidden;
393
+ margin-bottom: 12px;
394
+ }
395
+
396
+ .progress-bar {
397
+ background: linear-gradient(90deg, #004080 0%, #1a73e8 100%);
398
+ height: 100%;
399
+ width: 0%;
400
+ transition: width 0.3s ease;
401
+ }
402
+
403
+ .current-item {
404
+ font-size: 13px;
405
+ color: #5f6368;
406
+ display: flex;
407
+ align-items: center;
408
+ gap: 8px;
409
+ }
410
+
411
+ .loading-spinner {
412
+ display: inline-block;
413
+ width: 16px;
414
+ height: 16px;
415
+ border: 2px solid rgba(26, 115, 232, 0.3);
416
+ border-radius: 50%;
417
+ border-top-color: #1a73e8;
418
+ animation: spin 1s ease-in-out infinite;
419
+ }
420
+
421
+ @keyframes spin {
422
+ to { transform: rotate(360deg); }
423
+ }
424
+
425
+ /* Modal */
426
+ .modal {
427
+ display: none;
428
+ position: fixed;
429
+ top: 0;
430
+ left: 0;
431
+ width: 100%;
432
+ height: 100%;
433
+ background: rgba(0, 0, 0, 0.6);
434
+ z-index: 1000;
435
+ justify-content: center;
436
+ align-items: center;
437
+ padding: 20px;
438
+ backdrop-filter: blur(4px);
439
+ }
440
+
441
+ .modal-content {
442
+ background: #ffffff;
443
+ border-radius: 12px;
444
+ max-width: 900px;
445
+ width: 100%;
446
+ max-height: 90vh;
447
+ overflow: hidden;
448
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
449
+ }
450
+
451
+ .modal-header {
452
+ background: linear-gradient(135deg, #004080 0%, #1a73e8 100%);
453
+ color: white;
454
+ padding: 24px;
455
+ display: flex;
456
+ justify-content: space-between;
457
+ align-items: center;
458
+ }
459
+
460
+ .modal-close {
461
+ background: none;
462
+ border: none;
463
+ color: white;
464
+ font-size: 24px;
465
+ cursor: pointer;
466
+ padding: 4px;
467
+ border-radius: 50%;
468
+ transition: background-color 0.2s ease;
469
+ }
470
+
471
+ .modal-close:hover {
472
+ background: rgba(255, 255, 255, 0.1);
473
+ }
474
+
475
+ .modal-body {
476
+ padding: 24px;
477
+ max-height: 60vh;
478
+ overflow-y: auto;
479
+ }
480
+
481
+ .modal-field {
482
+ margin-bottom: 20px;
483
+ }
484
+
485
+ .modal-field-label {
486
+ font-weight: 600;
487
+ color: #3c4043;
488
+ margin-bottom: 8px;
489
+ display: block;
490
+ }
491
+
492
+ .modal-textarea {
493
+ width: 100%;
494
+ min-height: 120px;
495
+ padding: 16px;
496
+ border: 2px solid #dadce0;
497
+ border-radius: 8px;
498
+ font-family: inherit;
499
+ font-size: 14px;
500
+ resize: vertical;
501
+ display: none;
502
+ transition: border-color 0.3s ease, box-shadow 0.3s ease;
503
+ }
504
+
505
+ .modal-textarea:focus {
506
+ border-color: #1a73e8;
507
+ box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1);
508
+ outline: none;
509
+ }
510
+
511
+ .textarea-toggle {
512
+ background: #ffffff;
513
+ border: 1px solid #dadce0;
514
+ border-radius: 6px;
515
+ padding: 8px 12px;
516
+ cursor: pointer;
517
+ color: #1a73e8;
518
+ font-size: 13px;
519
+ font-weight: 500;
520
+ display: inline-flex;
521
+ align-items: center;
522
+ gap: 6px;
523
+ transition: all 0.2s ease;
524
+ }
525
+
526
+ .textarea-toggle:hover {
527
+ background: #f8fafd;
528
+ border-color: #1a73e8;
529
+ }
530
+
531
+ .modal-actions {
532
+ padding: 24px;
533
+ border-top: 1px solid #e8eaed;
534
+ display: flex;
535
+ justify-content: flex-end;
536
+ gap: 12px;
537
+ }
538
+
539
+ .recommendation-content {
540
+ background: #ffffff;
541
+ border: 1px solid #e0e0e0;
542
+ padding: 15px;
543
+ border-radius: 8px;
544
+ min-height: 80px;
545
+ overflow-y: auto;
546
+ max-height: 250px;
547
+ margin-bottom: 10px;
548
+ color: #333;
549
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
550
+ }
551
+
552
+ /* Responsive */
553
+ @media (max-width: 768px) {
554
+ .container {
555
+ margin: 15px;
556
+ padding: 20px;
557
+ }
558
+
559
+ .banner {
560
+ height: 80px;
561
+ }
562
+
563
+ .main-header {
564
+ font-size: 24px;
565
+ }
566
+
567
+ .main-subtitle {
568
+ font-size: 16px;
569
+ }
570
+
571
+ .data-table th:nth-child(2),
572
+ .data-table td:nth-child(2) {
573
+ display: none;
574
+ }
575
+
576
+ .modal-content {
577
+ width: 95%;
578
+ }
579
+
580
+ .modal-body {
581
+ padding: 16px;
582
+ }
583
+
584
+ .modal-actions {
585
+ padding: 16px;
586
+ flex-direction: column;
587
+ }
588
+ }
589
+ </style>
590
+ </head>
591
+ <body>
592
+ <div class="banner"></div>
593
+
594
+ <div class="container">
595
+ <div class="header-section">
596
+ <h1 class="main-header">Assistant VisiPilot pour Plan d'Actions IFS</h1>
597
+ <p class="main-subtitle">Génération intelligente de plans d'actions IFS Food 8</p>
598
+ </div>
599
+
600
+ <div class="expander">
601
+ <div class="expander-header" id="howToUseHeader">
602
+ <span>Comment utiliser cette application</span>
603
+ </div>
604
+ <div class="expander-content" id="howToUseContent">
605
+ <p><strong>Étapes d'utilisation:</strong></p>
606
+ <ol>
607
+ <li><strong>Saisissez votre clé API Groq:</strong> Entrez votre clé API dans le champ dédié.</li>
608
+ <li><strong>Téléchargez votre plan d'actions IFS v8:</strong> Glissez-déposez votre fichier Excel.</li>
609
+ <li><strong>Générez des recommandations:</strong> Utilisez les boutons individuels ou la génération en lot.</li>
610
+ <li><strong>Affinez les propositions:</strong> Cliquez sur le numéro d'exigence pour éditer.</li>
611
+ <li><strong>Exportez en PDF:</strong> Générez un rapport complet.</li>
612
+ </ol>
613
+ </div>
614
+ </div>
615
+
616
+ <div class="form-group">
617
+ <label for="groqApiKey">Votre clé API Groq :</label>
618
+ <input type="password" id="groqApiKey" class="form-input" placeholder="Entrez votre clé API Groq">
619
+ </div>
620
+
621
+ <div class="form-group">
622
+ <label>Téléchargez votre plan d'action (fichier Excel) :</label>
623
+ <div class="file-drop-zone" id="fileDropZone">
624
+ <input type="file" id="fileInput" accept=".xlsx" style="display: none;">
625
+ <div class="file-drop-content">
626
+ <span class="material-icons file-drop-icon">upload_file</span>
627
+ <div>Glissez-déposez votre fichier Excel ici</div>
628
+ <div>ou cliquez pour sélectionner (.xlsx)</div>
629
+ </div>
630
+ </div>
631
+ </div>
632
+
633
+ <div class="message message-success" id="successMessage">
634
+ <span class="material-icons">check_circle</span>
635
+ <span id="successText"></span>
636
+ </div>
637
+
638
+ <div class="message message-error" id="errorMessage">
639
+ <span class="material-icons">error</span>
640
+ <span id="errorText"></span>
641
+ </div>
642
+
643
+ <div class="data-table-container" id="dataTableContainer">
644
+ <div class="data-table-header">
645
+ <h2 class="data-table-title">Plan d'Action IFS</h2>
646
+ <div style="display: flex; gap: 12px; align-items: center;">
647
+ <button class="btn btn-success" id="bulkGenerateBtn">
648
+ <span class="material-icons">auto_awesome</span>
649
+ Générer toutes les recommandations
650
+ </button>
651
+ <button class="btn btn-danger" id="exportPdfBtn">
652
+ <span class="material-icons">picture_as_pdf</span>
653
+ Exporter PDF
654
+ </button>
655
+ </div>
656
+ </div>
657
+
658
+ <div class="bulk-generation-container">
659
+ <div style="margin-bottom: 12px;">
660
+ <strong>Génération en lot :</strong> Générez automatiquement toutes les recommandations manquantes.
661
+ </div>
662
+
663
+ <div class="generation-options">
664
+ <div style="display: flex; align-items: center; gap: 6px;">
665
+ <label for="delaySelect">Délai :</label>
666
+ <select class="option-select" id="delaySelect">
667
+ <option value="5">5 secondes</option>
668
+ <option value="10" selected>10 secondes</option>
669
+ <option value="15">15 secondes</option>
670
+ </select>
671
+ </div>
672
+ </div>
673
+
674
+ <div class="bulk-progress" id="bulkProgress">
675
+ <div class="progress-header">
676
+ <div>Génération en cours...</div>
677
+ <div class="progress-counter" id="progressCounter">0 / 0</div>
678
+ </div>
679
+ <div class="progress-bar-container">
680
+ <div class="progress-bar" id="progressBar"></div>
681
+ </div>
682
+ <div class="current-item" id="currentItem">En attente...</div>
683
+ </div>
684
+ </div>
685
+
686
+ <table class="data-table" id="dataTable">
687
+ <thead>
688
+ <tr>
689
+ <th>Numéro d'exigence</th>
690
+ <th>Exigence IFS Food 8</th>
691
+ <th>Constat détaillé</th>
692
+ <th>Actions</th>
693
+ </tr>
694
+ </thead>
695
+ <tbody id="dataTableBody">
696
+ </tbody>
697
+ </table>
698
+ </div>
699
+ </div>
700
+
701
+ <div class="modal" id="detailModal">
702
+ <div class="modal-content">
703
+ <div class="modal-header">
704
+ <h2>Détail de la non-conformité</h2>
705
+ <button class="modal-close" id="modalClose">
706
+ <span class="material-icons">close</span>
707
+ </button>
708
+ </div>
709
+ <div class="modal-body">
710
+ <div class="modal-field">
711
+ <label class="modal-field-label">Numéro d'exigence:</label>
712
+ <div id="modalReqNum"></div>
713
+ </div>
714
+ <div class="modal-field">
715
+ <label class="modal-field-label">Exigence IFS Food 8:</label>
716
+ <div id="modalReqText"></div>
717
+ </div>
718
+ <div class="modal-field">
719
+ <label class="modal-field-label">Constat détaillé:</label>
720
+ <div id="modalExplanation"></div>
721
+ </div>
722
+ <div class="modal-field">
723
+ <label class="modal-field-label">Recommandation IA:</label>
724
+ <div class="recommendation-content" id="modalRenderedRecommendation"></div>
725
+ </div>
726
+ <div class="modal-field">
727
+ <label class="modal-field-label">Modifier la recommandation:</label>
728
+ <button class="textarea-toggle" id="textareaToggle">
729
+ <span class="material-icons">edit</span>
730
+ Modifier
731
+ </button>
732
+ <textarea class="modal-textarea" id="modalTextarea"></textarea>
733
+ </div>
734
+ </div>
735
+ <div class="modal-actions">
736
+ <button class="btn btn-primary" id="modalCancel">Fermer</button>
737
+ <button class="btn btn-success" id="modalGenerate">
738
+ <span class="material-icons">auto_awesome</span>
739
+ Générer/Améliorer
740
+ </button>
741
+ </div>
742
+ </div>
743
+ </div>
744
+
745
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js"></script>
746
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
747
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.3/html2pdf.bundle.min.js"></script>
748
+
749
+ <script>
750
+ // Configuration
751
+ var CONFIG = {
752
+ GROQ_API_ENDPOINT: "https://api.groq.com/openai/v1/chat/completions",
753
+ GROQ_MODEL: "openai/gpt-oss-120b",
754
+ GUIDE_CSV_URL: "https://raw.githubusercontent.com/M00N69/Action-planGroq/main/Guide%20Checklist_IFS%20Food%20V%208%20-%20CHECKLIST.csv",
755
+ EXPECTED_HEADERS: ["requirementNo", "requirementText", "requirementExplanation"],
756
+ HEADER_ROW_INDEX: 11,
757
+ DATA_START_INDEX: 13
758
+ };
759
+
760
+ // État global
761
+ var AppState = {
762
+ apiKey: '',
763
+ actionPlanData: [],
764
+ guideData: [],
765
+ recommendations: {},
766
+ currentModalIndex: -1,
767
+ isLoading: false,
768
+ bulkGeneration: {
769
+ isRunning: false,
770
+ currentIndex: 0,
771
+ total: 0,
772
+ completed: 0,
773
+ failed: []
774
+ }
775
+ };
776
+
777
+ // Éléments DOM
778
+ var DOM = {
779
+ apiKeyInput: document.getElementById('groqApiKey'),
780
+ fileDropZone: document.getElementById('fileDropZone'),
781
+ fileInput: document.getElementById('fileInput'),
782
+ successMessage: document.getElementById('successMessage'),
783
+ errorMessage: document.getElementById('errorMessage'),
784
+ successText: document.getElementById('successText'),
785
+ errorText: document.getElementById('errorText'),
786
+ dataTableContainer: document.getElementById('dataTableContainer'),
787
+ dataTableBody: document.getElementById('dataTableBody'),
788
+ bulkGenerateBtn: document.getElementById('bulkGenerateBtn'),
789
+ bulkProgress: document.getElementById('bulkProgress'),
790
+ progressBar: document.getElementById('progressBar'),
791
+ progressCounter: document.getElementById('progressCounter'),
792
+ currentItem: document.getElementById('currentItem'),
793
+ delaySelect: document.getElementById('delaySelect'),
794
+ exportPdfBtn: document.getElementById('exportPdfBtn'),
795
+ detailModal: document.getElementById('detailModal'),
796
+ modalClose: document.getElementById('modalClose'),
797
+ modalCancel: document.getElementById('modalCancel'),
798
+ modalGenerate: document.getElementById('modalGenerate'),
799
+ modalReqNum: document.getElementById('modalReqNum'),
800
+ modalReqText: document.getElementById('modalReqText'),
801
+ modalExplanation: document.getElementById('modalExplanation'),
802
+ modalRenderedRecommendation: document.getElementById('modalRenderedRecommendation'),
803
+ modalTextarea: document.getElementById('modalTextarea'),
804
+ textareaToggle: document.getElementById('textareaToggle'),
805
+ howToUseHeader: document.getElementById('howToUseHeader'),
806
+ howToUseContent: document.getElementById('howToUseContent')
807
+ };
808
+
809
+ // Utilitaires
810
+ var Utils = {
811
+ showMessage: function(type, text) {
812
+ var messageEl = DOM[type + 'Message'];
813
+ var textEl = DOM[type + 'Text'];
814
+
815
+ textEl.textContent = text;
816
+ messageEl.classList.add('show');
817
+
818
+ setTimeout(function() {
819
+ messageEl.classList.remove('show');
820
+ }, 5000);
821
+ },
822
+
823
+ parseCSV: function(csvText) {
824
+ var lines = csvText.split('\n');
825
+ var headers = lines[0].split(';').map(function(h) {
826
+ return h.trim();
827
+ });
828
+ var data = [];
829
+
830
+ for (var i = 1; i < lines.length; i++) {
831
+ var currentLine = lines[i].split(';');
832
+ if (currentLine.length === headers.length) {
833
+ var row = {};
834
+ headers.forEach(function(header, index) {
835
+ row[header] = currentLine[index] ? currentLine[index].trim() : '';
836
+ });
837
+ data.push(row);
838
+ }
839
+ }
840
+ return data;
841
+ }
842
+ };
843
+
844
+ // Gestionnaire API
845
+ var APIManager = {
846
+ loadGuideData: function() {
847
+ fetch(CONFIG.GUIDE_CSV_URL)
848
+ .then(function(response) {
849
+ if (!response.ok) throw new Error('HTTP ' + response.status);
850
+ return response.text();
851
+ })
852
+ .then(function(csvText) {
853
+ AppState.guideData = Utils.parseCSV(csvText);
854
+ console.log('Guide IFS chargé:', AppState.guideData.length, 'entrées');
855
+ })
856
+ .catch(function(error) {
857
+ console.error('Erreur chargement guide:', error);
858
+ Utils.showMessage('error', 'Impossible de charger le guide IFS');
859
+ });
860
+ },
861
+
862
+ getGuideInfo: function(reqNumber) {
863
+ if (!AppState.guideData.length || !reqNumber) return null;
864
+ return AppState.guideData.find(function(row) {
865
+ return row['NUM_REQ'] && reqNumber && String(row['NUM_REQ']).includes(String(reqNumber));
866
+ });
867
+ },
868
+
869
+ generateRecommendation: function(index, isRegeneration) {
870
+ return new Promise(function(resolve, reject) {
871
+ if (!AppState.apiKey) {
872
+ Utils.showMessage('error', 'Veuillez configurer votre clé API Groq');
873
+ reject(new Error('Pas de clé API'));
874
+ return;
875
+ }
876
+
877
+ var nonConformity = AppState.actionPlanData[index];
878
+ if (!nonConformity) {
879
+ Utils.showMessage('error', 'Non-conformité introuvable');
880
+ reject(new Error('Non-conformité introuvable'));
881
+ return;
882
+ }
883
+
884
+ var guideInfo = APIManager.getGuideInfo(nonConformity["Numéro d'exigence"]);
885
+
886
+ var prompt = "En tant qu'expert en sécurité alimentaire et IFS Food 8, analysez cette non-conformité :\n\n" +
887
+ "Numéro d'exigence: " + nonConformity["Numéro d'exigence"] + "\n" +
888
+ "Description: " + nonConformity["Exigence IFS Food 8"] + "\n" +
889
+ "Constat détaillé: " + nonConformity["Explication (par l'auditeur/l'évaluateur)"] + "\n\n";
890
+
891
+ if (guideInfo && guideInfo['Good practice']) {
892
+ prompt += "Référence Guide IFS v8:\n" +
893
+ "Bonnes pratiques: " + (guideInfo['Good practice'] || 'Non disponible') + "\n" +
894
+ "Éléments à vérifier: " + (guideInfo['Elements to check'] || 'Non disponible') + "\n\n";
895
+ }
896
+
897
+ prompt += "Fournissez une recommandation structurée en Markdown incluant:\n\n" +
898
+ "## Correction Immédiate\n" +
899
+ "## Type de Preuve Requise\n" +
900
+ "## Cause Probable\n" +
901
+ "## Actions Correctives\n" +
902
+ "## Conclusion et Bonnes Pratiques";
903
+
904
+ var messages = [
905
+ {
906
+ role: "system",
907
+ content: "Vous êtes un expert en sécurité alimentaire et en norme IFS Food 8. Fournissez des recommandations précises et actionables."
908
+ },
909
+ {
910
+ role: "user",
911
+ content: prompt
912
+ }
913
+ ];
914
+
915
+ fetch(CONFIG.GROQ_API_ENDPOINT, {
916
+ method: 'POST',
917
+ headers: {
918
+ 'Content-Type': 'application/json',
919
+ 'Authorization': 'Bearer ' + AppState.apiKey
920
+ },
921
+ body: JSON.stringify({
922
+ messages: messages,
923
+ model: CONFIG.GROQ_MODEL,
924
+ temperature: 0.7,
925
+ max_tokens: 2000
926
+ })
927
+ })
928
+ .then(function(response) {
929
+ if (!response.ok) {
930
+ throw new Error('API Error ' + response.status);
931
+ }
932
+ return response.json();
933
+ })
934
+ .then(function(data) {
935
+ var recommendation = data.choices[0].message.content;
936
+ AppState.recommendations[index] = recommendation;
937
+ resolve(recommendation);
938
+ })
939
+ .catch(function(error) {
940
+ console.error('Erreur génération recommandation:', error);
941
+ Utils.showMessage('error', 'Erreur: ' + error.message);
942
+ reject(error);
943
+ });
944
+ });
945
+ }
946
+ };
947
+
948
+ // Gestionnaire de fichiers
949
+ var FileManager = {
950
+ init: function() {
951
+ DOM.fileDropZone.addEventListener('click', function() {
952
+ DOM.fileInput.click();
953
+ });
954
+ DOM.fileDropZone.addEventListener('dragover', FileManager.handleDragOver);
955
+ DOM.fileDropZone.addEventListener('dragleave', FileManager.handleDragLeave);
956
+ DOM.fileDropZone.addEventListener('drop', FileManager.handleDrop);
957
+ DOM.fileInput.addEventListener('change', FileManager.handleFileSelect);
958
+ },
959
+
960
+ handleDragOver: function(e) {
961
+ e.preventDefault();
962
+ DOM.fileDropZone.style.borderColor = '#1a73e8';
963
+ DOM.fileDropZone.style.background = 'linear-gradient(135deg, #e8f0fe 0%, #f0f6ff 100%)';
964
+ },
965
+
966
+ handleDragLeave: function(e) {
967
+ e.preventDefault();
968
+ DOM.fileDropZone.style.borderColor = '#dadce0';
969
+ DOM.fileDropZone.style.background = 'linear-gradient(135deg, #fafbfc 0%, #f8fafd 100%)';
970
+ },
971
+
972
+ handleDrop: function(e) {
973
+ e.preventDefault();
974
+ DOM.fileDropZone.style.borderColor = '#dadce0';
975
+ DOM.fileDropZone.style.background = 'linear-gradient(135deg, #fafbfc 0%, #f8fafd 100%)';
976
+
977
+ var files = e.dataTransfer.files;
978
+ if (files.length > 0) {
979
+ FileManager.processFile(files[0]);
980
+ }
981
+ },
982
+
983
+ handleFileSelect: function(e) {
984
+ var file = e.target.files[0];
985
+ if (file && file.name.endsWith('.xlsx')) {
986
+ FileManager.processFile(file);
987
+ } else {
988
+ Utils.showMessage('error', 'Veuillez sélectionner un fichier Excel (.xlsx)');
989
+ }
990
+ },
991
+
992
+ processFile: function(file) {
993
+ var reader = new FileReader();
994
+ reader.onload = function(e) {
995
+ try {
996
+ FileManager.parseExcelFile(e.target.result);
997
+ } catch (error) {
998
+ console.error('Erreur lecture fichier:', error);
999
+ Utils.showMessage('error', 'Erreur lors de la lecture du fichier Excel');
1000
+ }
1001
+ };
1002
+ reader.readAsArrayBuffer(file);
1003
+ },
1004
+
1005
+ parseExcelFile: function(arrayBuffer) {
1006
+ var workbook = XLSX.read(arrayBuffer, { type: 'array' });
1007
+ var sheetName = workbook.SheetNames[0];
1008
+ var worksheet = workbook.Sheets[sheetName];
1009
+
1010
+ var allRows = XLSX.utils.sheet_to_json(worksheet, { header: 1, raw: false });
1011
+
1012
+ if (allRows.length < CONFIG.DATA_START_INDEX + 1) {
1013
+ Utils.showMessage('error', 'Le fichier Excel ne contient pas suffisamment de données');
1014
+ return;
1015
+ }
1016
+
1017
+ var headerRow = allRows[CONFIG.HEADER_ROW_INDEX] || [];
1018
+ var columnMap = FileManager.mapColumns(headerRow);
1019
+ if (!columnMap) return;
1020
+
1021
+ var dataRows = allRows.slice(CONFIG.DATA_START_INDEX);
1022
+ var processedData = FileManager.processDataRows(dataRows, columnMap);
1023
+
1024
+ if (!processedData.length) {
1025
+ Utils.showMessage('error', 'Aucune donnée valide trouvée');
1026
+ return;
1027
+ }
1028
+
1029
+ AppState.actionPlanData = processedData;
1030
+ DataTableManager.render();
1031
+ Utils.showMessage('success', 'Fichier chargé avec succès! ' + processedData.length + ' non-conformités trouvées.');
1032
+ },
1033
+
1034
+ mapColumns: function(headerRow) {
1035
+ var columnMap = { reqNumber: -1, reqText: -1, explanation: -1 };
1036
+
1037
+ headerRow.forEach(function(cell, index) {
1038
+ var cellValue = String(cell || '').trim();
1039
+ if (cellValue === CONFIG.EXPECTED_HEADERS[0]) {
1040
+ columnMap.reqNumber = index;
1041
+ } else if (cellValue === CONFIG.EXPECTED_HEADERS[1]) {
1042
+ columnMap.reqText = index;
1043
+ } else if (cellValue === CONFIG.EXPECTED_HEADERS[2]) {
1044
+ columnMap.explanation = index;
1045
+ }
1046
+ });
1047
+
1048
+ var missingColumns = [];
1049
+ if (columnMap.reqNumber === -1) missingColumns.push('requirementNo');
1050
+ if (columnMap.reqText === -1) missingColumns.push('requirementText');
1051
+ if (columnMap.explanation === -1) missingColumns.push('requirementExplanation');
1052
+
1053
+ if (missingColumns.length > 0) {
1054
+ Utils.showMessage('error', 'Colonnes manquantes: ' + missingColumns.join(', '));
1055
+ return null;
1056
+ }
1057
+
1058
+ return columnMap;
1059
+ },
1060
+
1061
+ processDataRows: function(dataRows, columnMap) {
1062
+ return dataRows
1063
+ .map(function(row) {
1064
+ return {
1065
+ "Numéro d'exigence": String(row[columnMap.reqNumber] || '').trim(),
1066
+ "Exigence IFS Food 8": String(row[columnMap.reqText] || '').trim(),
1067
+ "Explication (par l'auditeur/l'évaluateur)": String(row[columnMap.explanation] || '').trim()
1068
+ };
1069
+ })
1070
+ .filter(function(item) {
1071
+ return item["Numéro d'exigence"] && item["Exigence IFS Food 8"];
1072
+ });
1073
+ }
1074
+ };
1075
+
1076
+ // Gestionnaire de la table
1077
+ var DataTableManager = {
1078
+ render: function() {
1079
+ DOM.dataTableBody.innerHTML = '';
1080
+
1081
+ AppState.actionPlanData.forEach(function(item, index) {
1082
+ DataTableManager.createDataRow(item, index);
1083
+ });
1084
+
1085
+ DOM.dataTableContainer.style.display = 'block';
1086
+ },
1087
+
1088
+ createDataRow: function(item, index) {
1089
+ var row = document.createElement('tr');
1090
+ row.innerHTML =
1091
+ '<td><a href="#" class="requirement-link" data-index="' + index + '">' +
1092
+ item["Numéro d'exigence"] + '</a></td>' +
1093
+ '<td>' + item["Exigence IFS Food 8"] + '</td>' +
1094
+ '<td>' + item["Explication (par l'auditeur/l'évaluateur)"] + '</td>' +
1095
+ '<td><button class="btn btn-primary btn-small generate-btn" data-index="' + index + '">' +
1096
+ '<span class="material-icons">auto_awesome</span> Générer</button></td>';
1097
+
1098
+ DOM.dataTableBody.appendChild(row);
1099
+
1100
+ row.querySelector('.requirement-link').addEventListener('click', function(e) {
1101
+ e.preventDefault();
1102
+ ModalManager.open(index);
1103
+ });
1104
+
1105
+ row.querySelector('.generate-btn').addEventListener('click', function() {
1106
+ DataTableManager.generateRecommendation(index);
1107
+ });
1108
+ },
1109
+
1110
+ generateRecommendation: function(index) {
1111
+ APIManager.generateRecommendation(index)
1112
+ .then(function(recommendation) {
1113
+ if (recommendation) {
1114
+ Utils.showMessage('success', 'Recommandation générée avec succès!');
1115
+ }
1116
+ })
1117
+ .catch(function(error) {
1118
+ console.error('Erreur génération:', error);
1119
+ });
1120
+ }
1121
+ };
1122
+
1123
+ // Gestionnaire de génération en lot
1124
+ var BulkGenerationManager = {
1125
+ init: function() {
1126
+ DOM.bulkGenerateBtn.addEventListener('click', function(event) {
1127
+ event.preventDefault();
1128
+ event.stopPropagation();
1129
+ BulkGenerationManager.startBulkGeneration();
1130
+ });
1131
+ },
1132
+
1133
+ startBulkGeneration: function() {
1134
+ if (!AppState.apiKey) {
1135
+ Utils.showMessage('error', 'Veuillez configurer votre clé API Groq');
1136
+ return;
1137
+ }
1138
+
1139
+ if (AppState.actionPlanData.length === 0) {
1140
+ Utils.showMessage('error', 'Aucun plan d\'action chargé');
1141
+ return;
1142
+ }
1143
+
1144
+ var itemsToProcess = AppState.actionPlanData
1145
+ .map(function(item, index) {
1146
+ return { item: item, index: index };
1147
+ })
1148
+ .filter(function(obj) {
1149
+ return !AppState.recommendations[obj.index];
1150
+ });
1151
+
1152
+ if (itemsToProcess.length === 0) {
1153
+ Utils.showMessage('success', 'Toutes les recommandations ont déjà été générées !');
1154
+ return;
1155
+ }
1156
+
1157
+ var delay = parseInt(DOM.delaySelect.value) * 1000;
1158
+ var estimatedTime = Math.round((itemsToProcess.length * (delay + 5000)) / 60000);
1159
+
1160
+ var confirmed = confirm(
1161
+ 'Vous allez générer ' + itemsToProcess.length + ' recommandations.\n' +
1162
+ 'Délai entre chaque génération: ' + (delay/1000) + 's\n' +
1163
+ 'Temps estimé: ' + estimatedTime + ' minutes\n\n' +
1164
+ 'Continuer ?'
1165
+ );
1166
+
1167
+ if (!confirmed) return;
1168
+
1169
+ AppState.bulkGeneration = {
1170
+ isRunning: true,
1171
+ currentIndex: 0,
1172
+ total: itemsToProcess.length,
1173
+ completed: 0,
1174
+ failed: [],
1175
+ itemsToProcess: itemsToProcess
1176
+ };
1177
+
1178
+ BulkGenerationManager.updateUI(true);
1179
+ BulkGenerationManager.processBulk(itemsToProcess, delay);
1180
+ },
1181
+
1182
+ processBulk: function(itemsToProcess, delay) {
1183
+ var processNext = function(i) {
1184
+ if (i >= itemsToProcess.length || !AppState.bulkGeneration.isRunning) {
1185
+ BulkGenerationManager.completeBulkGeneration();
1186
+ return;
1187
+ }
1188
+
1189
+ var obj = itemsToProcess[i];
1190
+ AppState.bulkGeneration.currentIndex = i;
1191
+
1192
+ BulkGenerationManager.updateProgress();
1193
+ BulkGenerationManager.updateCurrentItem(obj.item["Numéro d'exigence"]);
1194
+
1195
+ APIManager.generateRecommendation(obj.index)
1196
+ .then(function(recommendation) {
1197
+ if (recommendation) {
1198
+ AppState.bulkGeneration.completed++;
1199
+ } else {
1200
+ AppState.bulkGeneration.failed.push(obj.item["Numéro d'exigence"]);
1201
+ }
1202
+ })
1203
+ .catch(function(error) {
1204
+ console.error('Erreur pour ' + obj.item["Numéro d'exigence"] + ':', error);
1205
+ AppState.bulkGeneration.failed.push(obj.item["Numéro d'exigence"]);
1206
+ })
1207
+ .finally(function() {
1208
+ if (i < itemsToProcess.length - 1) {
1209
+ setTimeout(function() {
1210
+ processNext(i + 1);
1211
+ }, delay);
1212
+ } else {
1213
+ processNext(i + 1);
1214
+ }
1215
+ });
1216
+ };
1217
+
1218
+ processNext(0);
1219
+ },
1220
+
1221
+ updateUI: function(isRunning) {
1222
+ DOM.bulkGenerateBtn.disabled = isRunning;
1223
+ DOM.bulkProgress.classList.toggle('show', isRunning);
1224
+
1225
+ if (isRunning) {
1226
+ DOM.bulkGenerateBtn.innerHTML = '<span class="loading-spinner"></span> Génération en cours...';
1227
+ } else {
1228
+ DOM.bulkGenerateBtn.innerHTML = '<span class="material-icons">auto_awesome</span> Générer toutes les recommandations';
1229
+ }
1230
+ },
1231
+
1232
+ updateProgress: function() {
1233
+ var current = AppState.bulkGeneration.currentIndex;
1234
+ var total = AppState.bulkGeneration.total;
1235
+ var percentage = total > 0 ? ((current + 1) / total) * 100 : 0;
1236
+
1237
+ DOM.progressBar.style.width = percentage + '%';
1238
+ DOM.progressCounter.textContent = (current + 1) + ' / ' + total;
1239
+ },
1240
+
1241
+ updateCurrentItem: function(reqNumber) {
1242
+ DOM.currentItem.innerHTML =
1243
+ '<span class="loading-spinner"></span> Génération pour l\'exigence ' + reqNumber + '...';
1244
+ },
1245
+
1246
+ completeBulkGeneration: function() {
1247
+ var completed = AppState.bulkGeneration.completed;
1248
+ var failed = AppState.bulkGeneration.failed;
1249
+ var total = AppState.bulkGeneration.total;
1250
+
1251
+ AppState.bulkGeneration.isRunning = false;
1252
+ BulkGenerationManager.updateUI(false);
1253
+
1254
+ var message = 'Génération terminée : ' + completed + '/' + total + ' recommandations générées';
1255
+ if (failed.length > 0) {
1256
+ message += '\nÉchecs : ' + failed.join(', ');
1257
+ Utils.showMessage('error', message);
1258
+ } else {
1259
+ message += ' avec succès !';
1260
+ Utils.showMessage('success', message);
1261
+ }
1262
+
1263
+ DOM.currentItem.innerHTML =
1264
+ '<span class="material-icons" style="color: #34a853;">check_circle</span> Génération terminée !';
1265
+
1266
+ setTimeout(function() {
1267
+ DOM.bulkProgress.classList.remove('show');
1268
+ }, 3000);
1269
+ }
1270
+ };
1271
+
1272
+ // Gestionnaire de modal
1273
+ var ModalManager = {
1274
+ init: function() {
1275
+ DOM.modalClose.addEventListener('click', ModalManager.close);
1276
+ DOM.modalCancel.addEventListener('click', ModalManager.close);
1277
+ DOM.modalGenerate.addEventListener('click', ModalManager.generateRecommendation);
1278
+ DOM.textareaToggle.addEventListener('click', ModalManager.toggleTextarea);
1279
+
1280
+ DOM.detailModal.addEventListener('click', function(e) {
1281
+ if (e.target === DOM.detailModal) ModalManager.close();
1282
+ });
1283
+ },
1284
+
1285
+ toggleTextarea: function() {
1286
+ var isVisible = DOM.modalTextarea.style.display !== 'none';
1287
+ if (isVisible) {
1288
+ DOM.modalTextarea.style.display = 'none';
1289
+ DOM.textareaToggle.innerHTML = '<span class="material-icons">edit</span> Modifier';
1290
+ } else {
1291
+ DOM.modalTextarea.style.display = 'block';
1292
+ DOM.textareaToggle.innerHTML = '<span class="material-icons">close</span> Fermer';
1293
+ DOM.modalTextarea.focus();
1294
+ }
1295
+ },
1296
+
1297
+ open: function(index) {
1298
+ AppState.currentModalIndex = index;
1299
+ var item = AppState.actionPlanData[index];
1300
+
1301
+ DOM.modalReqNum.textContent = item["Numéro d'exigence"];
1302
+ DOM.modalReqText.textContent = item["Exigence IFS Food 8"];
1303
+ DOM.modalExplanation.textContent = item["Explication (par l'auditeur/l'évaluateur)"];
1304
+
1305
+ var recommendation = AppState.recommendations[index];
1306
+ if (recommendation) {
1307
+ DOM.modalTextarea.value = recommendation;
1308
+ DOM.modalRenderedRecommendation.innerHTML = marked.parse(recommendation);
1309
+ } else {
1310
+ DOM.modalTextarea.value = '';
1311
+ DOM.modalRenderedRecommendation.innerHTML = '<p>Aucune recommandation générée.</p>';
1312
+ }
1313
+
1314
+ DOM.modalTextarea.style.display = 'none';
1315
+ DOM.textareaToggle.innerHTML = '<span class="material-icons">edit</span> Modifier';
1316
+ DOM.detailModal.style.display = 'flex';
1317
+ },
1318
+
1319
+ close: function() {
1320
+ DOM.detailModal.style.display = 'none';
1321
+ AppState.currentModalIndex = -1;
1322
+ },
1323
+
1324
+ generateRecommendation: function() {
1325
+ if (AppState.currentModalIndex === -1) return;
1326
+
1327
+ APIManager.generateRecommendation(AppState.currentModalIndex, DOM.modalTextarea.value.trim() !== '')
1328
+ .then(function(recommendation) {
1329
+ if (recommendation) {
1330
+ DOM.modalTextarea.value = recommendation;
1331
+ DOM.modalRenderedRecommendation.innerHTML = marked.parse(recommendation);
1332
+ Utils.showMessage('success', 'Recommandation générée/améliorée avec succès!');
1333
+ }
1334
+ })
1335
+ .catch(function(error) {
1336
+ console.error('Erreur génération modal:', error);
1337
+ });
1338
+ }
1339
+ };
1340
+
1341
+ // Gestionnaire d'export PDF
1342
+ var PDFManager = {
1343
+ init: function() {
1344
+ DOM.exportPdfBtn.addEventListener('click', PDFManager.exportToPDF);
1345
+ },
1346
+
1347
+ exportToPDF: function() {
1348
+ var pdfContainer = document.createElement('div');
1349
+ pdfContainer.style.fontFamily = 'Arial, sans-serif';
1350
+ pdfContainer.style.fontSize = '11px';
1351
+ pdfContainer.style.padding = '15px';
1352
+ pdfContainer.style.background = 'white';
1353
+
1354
+ var title = document.createElement('h1');
1355
+ title.style.textAlign = 'center';
1356
+ title.style.color = '#004080';
1357
+ title.style.marginBottom = '20px';
1358
+ title.style.fontSize = '18px';
1359
+ title.textContent = 'Rapport de Plan d\'Actions IFS avec Recommandations IA';
1360
+ pdfContainer.appendChild(title);
1361
+
1362
+ AppState.actionPlanData.forEach(function(item, index) {
1363
+ var recommendation = AppState.recommendations[index];
1364
+ if (!recommendation) return;
1365
+
1366
+ var section = document.createElement('div');
1367
+ section.style.marginBottom = '25px';
1368
+ section.style.border = '1px solid #e0e0e0';
1369
+ section.style.borderRadius = '8px';
1370
+ section.style.padding = '15px';
1371
+ section.style.background = '#fafafa';
1372
+
1373
+ var header = document.createElement('div');
1374
+ header.style.background = 'linear-gradient(135deg, #004080, #1a73e8)';
1375
+ header.style.color = 'white';
1376
+ header.style.padding = '10px 15px';
1377
+ header.style.margin = '-15px -15px 15px -15px';
1378
+ header.style.borderRadius = '7px 7px 0 0';
1379
+ header.innerHTML = '<h2 style="margin: 0; font-size: 14px;">Exigence ' +
1380
+ item["Numéro d'exigence"] + ' - ' + item["Exigence IFS Food 8"] + '</h2>';
1381
+ section.appendChild(header);
1382
+
1383
+ var constatDiv = document.createElement('div');
1384
+ constatDiv.style.marginBottom = '15px';
1385
+ constatDiv.style.padding = '10px';
1386
+ constatDiv.style.background = 'white';
1387
+ constatDiv.style.borderLeft = '4px solid #ea4335';
1388
+ constatDiv.innerHTML = '<strong style="color: #ea4335;">Constat:</strong><br>' +
1389
+ item["Explication (par l'auditeur/l'évaluateur)"];
1390
+ section.appendChild(constatDiv);
1391
+
1392
+ var recommendationDiv = document.createElement('div');
1393
+ recommendationDiv.style.background = 'white';
1394
+ recommendationDiv.style.padding = '15px';
1395
+ recommendationDiv.style.borderRadius = '6px';
1396
+ recommendationDiv.style.border = '1px solid #d0d0d0';
1397
+ recommendationDiv.innerHTML =
1398
+ '<h3 style="color: #1a73e8; margin-top: 0; font-size: 12px;">Recommandation IA</h3>' +
1399
+ '<div style="font-size: 10px;">' + marked.parse(recommendation) + '</div>';
1400
+ section.appendChild(recommendationDiv);
1401
+
1402
+ pdfContainer.appendChild(section);
1403
+ });
1404
+
1405
+ var options = {
1406
+ margin: [10, 10, 10, 10],
1407
+ filename: 'Plan_Action_IFS_' + new Date().toISOString().split('T')[0] + '.pdf',
1408
+ image: { type: 'jpeg', quality: 0.95 },
1409
+ html2canvas: { scale: 1.5, logging: false, useCORS: true },
1410
+ jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
1411
+ };
1412
+
1413
+ Utils.showMessage('success', 'Génération du PDF en cours...');
1414
+ html2pdf().from(pdfContainer).set(options).save()
1415
+ .then(function() {
1416
+ Utils.showMessage('success', 'PDF généré et téléchargé avec succès!');
1417
+ })
1418
+ .catch(function(error) {
1419
+ console.error('Erreur export PDF:', error);
1420
+ Utils.showMessage('error', 'Erreur lors de la génération du PDF');
1421
+ });
1422
+ }
1423
+ };
1424
+
1425
+ // Gestionnaire UI
1426
+ var UIManager = {
1427
+ init: function() {
1428
+ DOM.apiKeyInput.addEventListener('input', function(e) {
1429
+ var key = e.target.value.trim();
1430
+ if (key) {
1431
+ AppState.apiKey = key;
1432
+ Utils.showMessage('success', 'Clé API configurée avec succès');
1433
+ }
1434
+ });
1435
+
1436
+ DOM.howToUseHeader.addEventListener('click', function() {
1437
+ var isExpanded = DOM.howToUseContent.style.display === 'block';
1438
+ DOM.howToUseContent.style.display = isExpanded ? 'none' : 'block';
1439
+
1440
+ if (isExpanded) {
1441
+ DOM.howToUseHeader.classList.remove('expanded');
1442
+ } else {
1443
+ DOM.howToUseHeader.classList.add('expanded');
1444
+ }
1445
+ });
1446
+ }
1447
+ };
1448
+
1449
+ // Initialisation
1450
+ document.addEventListener('DOMContentLoaded', function() {
1451
+ console.log('Initialisation Assistant VisiPilot IFS');
1452
+
1453
+ marked.setOptions({
1454
+ gfm: true,
1455
+ breaks: true,
1456
+ sanitize: false
1457
+ });
1458
+
1459
+ UIManager.init();
1460
+ FileManager.init();
1461
+ ModalManager.init();
1462
+ PDFManager.init();
1463
+ BulkGenerationManager.init();
1464
+
1465
+ APIManager.loadGuideData();
1466
+
1467
+ console.log('Application initialisée');
1468
+ });
1469
+ </script>
1470
+ </body>
1471
+ </html>