Spaces:
Running
Running
Add 3 files
Browse files- README.md +6 -4
- index.html +1616 -18
- prompts.txt +0 -0
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: blue
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: ifsneoreviewer
|
| 3 |
+
emoji: 🐳
|
| 4 |
colorFrom: blue
|
| 5 |
+
colorTo: pink
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
|
@@ -1,19 +1,1617 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 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>Outil de Communication Reviewer/Auditeur - IFS Audit</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
+
<!-- JS Libraries for IFS Logic -->
|
| 10 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
| 11 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
| 12 |
+
<style>
|
| 13 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
| 14 |
+
body {
|
| 15 |
+
font-family: 'Inter', sans-serif;
|
| 16 |
+
}
|
| 17 |
+
.stat-card {
|
| 18 |
+
transition: all 0.3s ease;
|
| 19 |
+
cursor: pointer;
|
| 20 |
+
}
|
| 21 |
+
.stat-card:hover {
|
| 22 |
+
transform: translateY(-3px);
|
| 23 |
+
box-shadow: 0 10px 20px -5px rgba(0,0,0,0.1);
|
| 24 |
+
}
|
| 25 |
+
.sidebar {
|
| 26 |
+
transition: width 0.3s ease, transform 0.3s ease;
|
| 27 |
+
width: 16rem;
|
| 28 |
+
}
|
| 29 |
+
.sidebar-collapsed {
|
| 30 |
+
width: 4rem;
|
| 31 |
+
}
|
| 32 |
+
.sidebar-collapsed .sidebar-text {
|
| 33 |
+
display: none;
|
| 34 |
+
}
|
| 35 |
+
.sidebar-collapsed .justify-between {
|
| 36 |
+
justify-content: center;
|
| 37 |
+
}
|
| 38 |
+
.sidebar-collapsed .fa-chevron-left {
|
| 39 |
+
transform: rotate(180deg);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.nav-item:hover {
|
| 43 |
+
background-color: rgba(255, 255, 255, 0.1);
|
| 44 |
+
}
|
| 45 |
+
.progress-bar-custom {
|
| 46 |
+
height: 6px;
|
| 47 |
+
border-radius: 3px;
|
| 48 |
+
background-color: #e2e8f0;
|
| 49 |
+
}
|
| 50 |
+
.progress-fill-custom {
|
| 51 |
+
height: 100%;
|
| 52 |
+
border-radius: 3px;
|
| 53 |
+
transition: width 0.3s ease;
|
| 54 |
+
}
|
| 55 |
+
.tab-content-custom {
|
| 56 |
+
display: none;
|
| 57 |
+
}
|
| 58 |
+
.tab-content-custom.active {
|
| 59 |
+
display: block;
|
| 60 |
+
animation: fadeIn 0.3s ease;
|
| 61 |
+
}
|
| 62 |
+
@keyframes fadeIn {
|
| 63 |
+
from { opacity: 0; }
|
| 64 |
+
to { opacity: 1; }
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.sticky-header-custom {
|
| 68 |
+
position: sticky;
|
| 69 |
+
top: 0;
|
| 70 |
+
z-index: 10;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
/* Styles for JS-generated content */
|
| 74 |
+
.stat-card {
|
| 75 |
+
background: white;
|
| 76 |
+
padding: 1.25rem;
|
| 77 |
+
border-radius: 0.5rem;
|
| 78 |
+
text-align: center;
|
| 79 |
+
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
|
| 80 |
+
transition: transform 0.3s ease;
|
| 81 |
+
}
|
| 82 |
+
.stat-card:hover { transform: translateY(-2px); }
|
| 83 |
+
.stat-number { font-size: 2.2rem; font-weight: 700; color: #2a5298; margin-bottom: 0.5rem; }
|
| 84 |
+
.stat-label { font-size: 0.9rem; color: #6c757d; font-weight: 600; }
|
| 85 |
+
|
| 86 |
+
/* Styles pour les tableaux redimensionnables */
|
| 87 |
+
.data-table-custom {
|
| 88 |
+
width: 100%;
|
| 89 |
+
border-collapse: collapse;
|
| 90 |
+
margin-top: 1.25rem;
|
| 91 |
+
border-radius: 0.75rem;
|
| 92 |
+
overflow: hidden;
|
| 93 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
| 94 |
+
font-size: 0.9rem;
|
| 95 |
+
table-layout: fixed; /* Important pour le redimensionnement */
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.data-table-custom th, .data-table-custom td {
|
| 99 |
+
padding: 0.75rem;
|
| 100 |
+
text-align: left;
|
| 101 |
+
border-bottom: 1px solid #e5e7eb;
|
| 102 |
+
border-right: 1px solid #e5e7eb;
|
| 103 |
+
word-wrap: break-word;
|
| 104 |
+
overflow-wrap: break-word;
|
| 105 |
+
position: relative;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.data-table-custom th {
|
| 109 |
+
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
| 110 |
+
color: white;
|
| 111 |
+
font-weight: 600;
|
| 112 |
+
position: sticky;
|
| 113 |
+
top: 0;
|
| 114 |
+
user-select: none;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
/* Poignée de redimensionnement */
|
| 118 |
+
.data-table-custom th::after {
|
| 119 |
+
content: '';
|
| 120 |
+
position: absolute;
|
| 121 |
+
top: 0;
|
| 122 |
+
right: 0;
|
| 123 |
+
width: 5px;
|
| 124 |
+
height: 100%;
|
| 125 |
+
cursor: col-resize;
|
| 126 |
+
background: transparent;
|
| 127 |
+
border-right: 2px solid rgba(255,255,255,0.3);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.data-table-custom th:hover::after {
|
| 131 |
+
background: rgba(255,255,255,0.1);
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.data-table-custom th:last-child::after {
|
| 135 |
+
display: none;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.data-table-custom tr:hover {
|
| 139 |
+
background: #f9fafb;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.dark .data-table-custom tr:hover {
|
| 143 |
+
background: #374151;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.dark .data-table-custom th {
|
| 147 |
+
background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
.dark .data-table-custom td {
|
| 151 |
+
border-bottom-color: #4b5563;
|
| 152 |
+
border-right-color: #4b5563;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
/* Curseur de redimensionnement global */
|
| 156 |
+
.resizing {
|
| 157 |
+
cursor: col-resize !important;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.resizing * {
|
| 161 |
+
cursor: col-resize !important;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.score-badge { padding: 0.25rem 0.75rem; border-radius: 9999px; font-weight: 700; font-size: 0.75rem; color: white; }
|
| 165 |
+
.score-A { background: #28a745; }
|
| 166 |
+
.score-B { background: #ffc107; color: #212529; }
|
| 167 |
+
.score-C { background: #fd7e14; }
|
| 168 |
+
.score-D { background: #dc3545; }
|
| 169 |
+
.score-NA { background: #6c757d; }
|
| 170 |
+
|
| 171 |
+
.category-header-custom {
|
| 172 |
+
background: linear-gradient(135deg, #2a5298 0%, #1e3c72 100%);
|
| 173 |
+
color: white;
|
| 174 |
+
padding: 0.75rem 1rem;
|
| 175 |
+
margin: 1.25rem 0 0.625rem 0;
|
| 176 |
+
border-radius: 0.5rem;
|
| 177 |
+
font-weight: bold;
|
| 178 |
+
font-size: 1.1rem;
|
| 179 |
+
}
|
| 180 |
+
.dark .category-header-custom { background: linear-gradient(135deg, #374151 0%, #1f2937 100%);}
|
| 181 |
+
|
| 182 |
+
.profile-textarea-long, .profile-textarea-short {
|
| 183 |
+
width: 100%; border: 1px solid #d1d5db;
|
| 184 |
+
border-radius: 0.25rem; padding: 0.5rem;
|
| 185 |
+
font-family: inherit; resize: vertical;
|
| 186 |
+
}
|
| 187 |
+
.dark .profile-textarea-long, .dark .profile-textarea-short {
|
| 188 |
+
background-color: #374151;
|
| 189 |
+
border-color: #4b5563;
|
| 190 |
+
color: #e5e7eb;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
.profile-textarea-long { min-height: 80px; }
|
| 194 |
+
.profile-textarea-short { min-height: 40px; }
|
| 195 |
+
|
| 196 |
+
.value-display-long { max-height: 100px; overflow-y: auto; padding: 0.5rem; background: #f9fafb; border-radius: 0.25rem; word-wrap: break-word; font-size: 0.9rem; line-height: 1.4; }
|
| 197 |
+
.value-display-short { word-wrap: break-word; font-size: 0.9rem; }
|
| 198 |
+
.dark .value-display-long { background: #374151; }
|
| 199 |
+
|
| 200 |
+
.main-content {
|
| 201 |
+
margin-left: 16rem;
|
| 202 |
+
transition: margin-left 0.3s ease;
|
| 203 |
+
}
|
| 204 |
+
.main-content-collapsed {
|
| 205 |
+
margin-left: 4rem;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
@media (max-width: 768px) {
|
| 209 |
+
.sidebar {
|
| 210 |
+
position: fixed;
|
| 211 |
+
z-index: 40;
|
| 212 |
+
height: 100vh;
|
| 213 |
+
transform: translateX(-100%);
|
| 214 |
+
}
|
| 215 |
+
.sidebar.active {
|
| 216 |
+
transform: translateX(0);
|
| 217 |
+
width: 16rem;
|
| 218 |
+
}
|
| 219 |
+
.sidebar.sidebar-collapsed {
|
| 220 |
+
transform: translateX(-100%);
|
| 221 |
+
}
|
| 222 |
+
.sidebar.sidebar-collapsed.active {
|
| 223 |
+
transform: translateX(0);
|
| 224 |
+
width: 4rem;
|
| 225 |
+
}
|
| 226 |
+
.main-content, .main-content-collapsed {
|
| 227 |
+
margin-left: 0 !important;
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
</style>
|
| 231 |
+
</head>
|
| 232 |
+
<body class="bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
| 233 |
+
<!-- Dark Mode Toggle -->
|
| 234 |
+
<div class="fixed bottom-4 right-4 z-50">
|
| 235 |
+
<button id="darkModeToggle" class="p-3 bg-blue-600 text-white rounded-full shadow-lg hover:bg-blue-700 transition">
|
| 236 |
+
<i class="fas fa-moon"></i>
|
| 237 |
+
</button>
|
| 238 |
+
</div>
|
| 239 |
+
|
| 240 |
+
<!-- Nouvelle section pour le suivi des actions -->
|
| 241 |
+
<div id="actions" class="tab-content-custom">
|
| 242 |
+
<h2 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Suivi des Actions Correctives</h2>
|
| 243 |
+
<div id="actionItemsContainer">
|
| 244 |
+
<!-- Généré dynamiquement -->
|
| 245 |
+
</div>
|
| 246 |
+
<button onclick="exportActionPlan()" class="mt-4 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition">
|
| 247 |
+
<i class="fas fa-file-export mr-2"></i>Exporter Plan d'Actions
|
| 248 |
+
</button>
|
| 249 |
+
</div>
|
| 250 |
+
|
| 251 |
+
<!-- Sidebar -->
|
| 252 |
+
<div class="sidebar bg-blue-800 text-white fixed h-full overflow-y-auto">
|
| 253 |
+
<div class="p-4 flex items-center justify-between border-b border-blue-700">
|
| 254 |
+
<div class="flex items-center space-x-2">
|
| 255 |
+
<i class="fas fa-shield-alt text-xl"></i>
|
| 256 |
+
<span class="font-bold text-lg sidebar-text">IFS Review Tool</span>
|
| 257 |
+
</div>
|
| 258 |
+
<button id="sidebarCollapse" class="text-white hover:text-blue-200 focus:outline-none">
|
| 259 |
+
<i class="fas fa-chevron-left"></i>
|
| 260 |
+
</button>
|
| 261 |
+
</div>
|
| 262 |
+
<div class="p-4">
|
| 263 |
+
<div class="mb-6">
|
| 264 |
+
<h3 class="text-xs uppercase font-semibold text-blue-200 mb-2 sidebar-text">Navigation</h3>
|
| 265 |
+
<ul class="space-y-1">
|
| 266 |
+
<li class="nav-item">
|
| 267 |
+
<a href="#profil-section" class="nav-link block px-3 py-2 rounded-lg flex items-center space-x-3 text-blue-100 hover:text-white" data-tab-target="profil">
|
| 268 |
+
<i class="fas fa-user-tie w-5 text-center"></i>
|
| 269 |
+
<span class="sidebar-text">Profil Entreprise</span>
|
| 270 |
+
</a>
|
| 271 |
+
</li>
|
| 272 |
+
<li class="nav-item">
|
| 273 |
+
<a href="#checklist-section" class="nav-link block px-3 py-2 rounded-lg flex items-center space-x-3 text-blue-100 hover:text-white" data-tab-target="checklist">
|
| 274 |
+
<i class="fas fa-list-check w-5 text-center"></i>
|
| 275 |
+
<span class="sidebar-text">Checklist Complète</span>
|
| 276 |
+
</a>
|
| 277 |
+
</li>
|
| 278 |
+
<li class="nav-item">
|
| 279 |
+
<a href="#nonconformites-section" class="nav-link block px-3 py-2 rounded-lg flex items-center space-x-3 text-blue-100 hover:text-white" data-tab-target="nonconformites">
|
| 280 |
+
<i class="fas fa-exclamation-triangle w-5 text-center"></i>
|
| 281 |
+
<span class="sidebar-text">Non-Conformités</span>
|
| 282 |
+
</a>
|
| 283 |
+
</li>
|
| 284 |
+
</ul>
|
| 285 |
+
</div>
|
| 286 |
+
<div class="mb-6">
|
| 287 |
+
<h3 class="text-xs uppercase font-semibold text-blue-200 mb-2 sidebar-text">Actions Fichier</h3>
|
| 288 |
+
<div class="space-y-2">
|
| 289 |
+
<button id="newAuditBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded-lg flex items-center space-x-3 transition justify-center md:justify-start">
|
| 290 |
+
<i class="fas fa-file-medical w-5 text-center"></i>
|
| 291 |
+
<span class="sidebar-text">Nouveau Dossier</span>
|
| 292 |
+
</button>
|
| 293 |
+
<input type="file" id="fileInputInternal" accept=".ifs,.ifsr" style="display: none;">
|
| 294 |
+
<button id="loadAuditBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded-lg flex items-center space-x-3 transition justify-center md:justify-start">
|
| 295 |
+
<i class="fas fa-upload w-5 text-center"></i>
|
| 296 |
+
<span class="sidebar-text">Charger Fichier</span>
|
| 297 |
+
</button>
|
| 298 |
+
<button id="saveAuditBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded-lg flex items-center space-x-3 transition justify-center md:justify-start">
|
| 299 |
+
<i class="fas fa-save w-5 text-center"></i>
|
| 300 |
+
<span class="sidebar-text">Sauvegarder IFSR</span>
|
| 301 |
+
</button>
|
| 302 |
+
<button id="exportExcelBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded-lg flex items-center space-x-3 transition justify-center md:justify-start">
|
| 303 |
+
<i class="fas fa-file-excel w-5 text-center"></i>
|
| 304 |
+
<span class="sidebar-text">Exporter pour Auditeur</span>
|
| 305 |
+
</button>
|
| 306 |
+
<button id="exportAllDataBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded-lg flex items-center space-x-3 transition justify-center md:justify-start mt-2">
|
| 307 |
+
<i class="fas fa-archive w-5 text-center"></i>
|
| 308 |
+
<span class="sidebar-text">Export Brut Complet</span>
|
| 309 |
+
</button>
|
| 310 |
+
</div>
|
| 311 |
+
</div>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
|
| 315 |
+
<!-- Mobile Sidebar Toggle -->
|
| 316 |
+
<button id="mobileSidebarToggle" class="md:hidden fixed top-4 left-4 z-30 bg-blue-600 text-white p-2 rounded-lg shadow-lg">
|
| 317 |
+
<i class="fas fa-bars"></i>
|
| 318 |
+
</button>
|
| 319 |
+
|
| 320 |
+
<!-- Main Content -->
|
| 321 |
+
<div class="main-content min-h-screen">
|
| 322 |
+
<!-- Header -->
|
| 323 |
+
<header class="bg-white dark:bg-gray-800 shadow-sm sticky-header-custom">
|
| 324 |
+
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
|
| 325 |
+
<div class="flex items-center">
|
| 326 |
+
<button id="mobileSidebarToggleHeader" class="md:hidden text-gray-600 dark:text-gray-300 mr-3">
|
| 327 |
+
<i class="fas fa-bars text-xl"></i>
|
| 328 |
+
</button>
|
| 329 |
+
<h1 class="text-xl md:text-2xl font-bold text-blue-800 dark:text-blue-300">IFS Reviewer - <span id="currentAuditName">Aucun audit chargé</span></h1>
|
| 330 |
+
</div>
|
| 331 |
+
<div class="flex items-center space-x-4">
|
| 332 |
+
<div id="sessionInfoTop" class="text-sm text-gray-600 dark:text-gray-400">
|
| 333 |
+
<span id="storageTypeTop">Stockage: Fichier</span> | <span id="sessionIdTop">COID: ----</span>
|
| 334 |
+
</div>
|
| 335 |
+
</div>
|
| 336 |
+
</div>
|
| 337 |
+
</header>
|
| 338 |
+
|
| 339 |
+
<!-- Main Section for IFS Tool -->
|
| 340 |
+
<main class="container mx-auto px-4 py-6">
|
| 341 |
+
<!-- Tab Content Area -->
|
| 342 |
+
<div id="profil" class="tab-content-custom active">
|
| 343 |
+
<h2 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Profil de l'entreprise auditée</h2>
|
| 344 |
+
<div id="uploadZone" class="border-3 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-10 text-center mb-6 cursor-pointer hover:border-blue-500 dark:hover:border-blue-400 bg-white dark:bg-gray-800">
|
| 345 |
+
<div class="text-5xl text-gray-400 dark:text-gray-500 mb-4"><i class="fas fa-file-upload"></i></div>
|
| 346 |
+
<h3 class="text-xl font-semibold mb-2 text-gray-700 dark:text-gray-300">Démarrer votre travail de review</h3>
|
| 347 |
+
<p class="text-gray-600 dark:text-gray-400"><strong>Nouveau dossier :</strong> Glissez-déposez votre fichier <strong>.ifs</strong> (export NEO)</p>
|
| 348 |
+
<p class="text-gray-600 dark:text-gray-400"><strong>Reprendre travail :</strong> Glissez-déposez votre fichier <strong>.ifsr</strong> (travail sauvegardé)</p>
|
| 349 |
+
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">ou cliquez ici pour sélectionner un fichier</p>
|
| 350 |
+
<input type="file" id="fileInput" accept=".ifs,.ifsr" style="display: none;">
|
| 351 |
+
</div>
|
| 352 |
+
|
| 353 |
+
<div id="loading" class="text-center p-10 hidden">
|
| 354 |
+
<div class="spinner animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto mb-4"></div>
|
| 355 |
+
<p class="text-gray-700 dark:text-gray-300">Traitement et sauvegarde du fichier en cours...</p>
|
| 356 |
+
<div class="progress-bar-custom w-full mt-3">
|
| 357 |
+
<div id="progressFill" class="progress-fill-custom bg-blue-500" style="width: 0%"></div>
|
| 358 |
+
</div>
|
| 359 |
+
</div>
|
| 360 |
+
|
| 361 |
+
<div id="profilResults" class="hidden">
|
| 362 |
+
<div id="successAlert" class="p-4 mb-4 text-sm text-green-700 bg-green-100 rounded-lg dark:bg-green-200 dark:text-green-800" role="alert">
|
| 363 |
+
</div>
|
| 364 |
+
|
| 365 |
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
|
| 366 |
+
<div class="stat-card dark:bg-gray-800">
|
| 367 |
+
<div class="stat-number dark:text-blue-400" id="totalRequirements">0</div>
|
| 368 |
+
<div class="stat-label dark:text-gray-400">Exigences totales</div>
|
| 369 |
+
</div>
|
| 370 |
+
<div class="stat-card dark:bg-gray-800">
|
| 371 |
+
<div class="stat-number dark:text-green-400" id="conformCount">0</div>
|
| 372 |
+
<div class="stat-label dark:text-gray-400">Conformités (A)</div>
|
| 373 |
+
</div>
|
| 374 |
+
<div class="stat-card dark:bg-gray-800">
|
| 375 |
+
<div class="stat-number dark:text-red-400" id="nonConformCount">0</div>
|
| 376 |
+
<div class="stat-label dark:text-gray-400">Non-Conformités</div>
|
| 377 |
+
</div>
|
| 378 |
+
<div class="stat-card dark:bg-gray-800">
|
| 379 |
+
<div class="stat-number dark:text-blue-400" id="overallScore">0%</div>
|
| 380 |
+
<div class="stat-label dark:text-gray-400">Score global</div>
|
| 381 |
+
</div>
|
| 382 |
+
<div class="stat-card bg-gradient-to-r from-green-500 to-teal-500 text-white">
|
| 383 |
+
<div class="stat-number" id="progressPercentage">0%</div>
|
| 384 |
+
<div class="stat-label">Travail reviewer</div>
|
| 385 |
+
</div>
|
| 386 |
+
<div class="stat-card bg-gradient-to-r from-blue-500 to-indigo-500 text-white">
|
| 387 |
+
<div class="stat-number" id="commentsCount">0</div>
|
| 388 |
+
<div class="stat-label">Commentaires</div>
|
| 389 |
+
</div>
|
| 390 |
+
</div>
|
| 391 |
+
|
| 392 |
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
| 393 |
+
<h3 class="text-xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Informations de l'entreprise</h3>
|
| 394 |
+
<div id="companyProfileTable">
|
| 395 |
+
</div>
|
| 396 |
+
</div>
|
| 397 |
+
</div>
|
| 398 |
+
</div>
|
| 399 |
+
|
| 400 |
+
<div id="checklist" class="tab-content-custom">
|
| 401 |
+
<h2 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Checklist complète des exigences IFS</h2>
|
| 402 |
+
<div class="bg-white dark:bg-gray-800 p-4 rounded-lg shadow mb-6">
|
| 403 |
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
| 404 |
+
<div>
|
| 405 |
+
<label for="chapterFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Chapitre IFS</label>
|
| 406 |
+
<select id="chapterFilter" class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
| 407 |
+
<option value="">Tous</option>
|
| 408 |
+
<option value="1">1. Management</option>
|
| 409 |
+
<option value="2">2. Système management</option>
|
| 410 |
+
<option value="3">3. Ressources</option>
|
| 411 |
+
<option value="4">4. Planification</option>
|
| 412 |
+
<option value="5">5. Mesures et analyses</option>
|
| 413 |
+
<option value="6">6. Food Defense</option>
|
| 414 |
+
</select>
|
| 415 |
+
</div>
|
| 416 |
+
<div>
|
| 417 |
+
<label for="scoreFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Score</label>
|
| 418 |
+
<select id="scoreFilter" class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
| 419 |
+
<option value="">Tous</option>
|
| 420 |
+
<option value="A">A</option> <option value="B">B</option> <option value="C">C</option> <option value="D">D</option> <option value="NA">NA</option>
|
| 421 |
+
</select>
|
| 422 |
+
</div>
|
| 423 |
+
<div>
|
| 424 |
+
<label for="explanationFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Explications</label>
|
| 425 |
+
<select id="explanationFilter" class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
| 426 |
+
<option value="empty">Sans explications</option> <option value="with">Avec explications</option> <option value="all">Toutes</option>
|
| 427 |
+
</select>
|
| 428 |
+
</div>
|
| 429 |
+
<div>
|
| 430 |
+
<label for="searchInput" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Recherche</label>
|
| 431 |
+
<input type="text" id="searchInput" placeholder="Mots-clés..." class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
| 432 |
+
</div>
|
| 433 |
+
</div>
|
| 434 |
+
<div class="mt-4 flex justify-end">
|
| 435 |
+
<button onclick="showAll()" class="px-4 py-2 bg-gray-200 dark:bg-gray-600 text-gray-800 dark:text-gray-200 rounded-md hover:bg-gray-300 dark:hover:bg-gray-500 transition">Afficher Tout</button>
|
| 436 |
+
</div>
|
| 437 |
+
</div>
|
| 438 |
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-x-auto">
|
| 439 |
+
<table class="data-table-custom w-full" id="checklistTable">
|
| 440 |
+
<thead><tr><th>N° Exigence</th><th>Score</th><th>Explication</th><th>Explication détaillée</th><th>Commentaires Reviewer</th></tr></thead>
|
| 441 |
+
<tbody id="checklistTableBody"></tbody>
|
| 442 |
+
</table>
|
| 443 |
+
</div>
|
| 444 |
+
</div>
|
| 445 |
+
|
| 446 |
+
<div id="nonconformites" class="tab-content-custom">
|
| 447 |
+
<h2 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-gray-200">Non-Conformités & Non-Applicables</h2>
|
| 448 |
+
<div class="bg-white dark:bg-gray-800 p-4 rounded-lg shadow mb-6">
|
| 449 |
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
| 450 |
+
<div>
|
| 451 |
+
<label for="ncTypeFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Type</label>
|
| 452 |
+
<select id="ncTypeFilter" class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
| 453 |
+
<option value="">Tous</option> <option value="B,C,D">NC seulement</option> <option value="NA">NA seulement</option>
|
| 454 |
+
<option value="B">B</option> <option value="C">C</option> <option value="D">D</option>
|
| 455 |
+
</select>
|
| 456 |
+
</div>
|
| 457 |
+
<div>
|
| 458 |
+
<label for="ncChapterFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Chapitre</label>
|
| 459 |
+
<select id="ncChapterFilter" class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
| 460 |
+
<option value="">Tous</option>
|
| 461 |
+
<option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option>
|
| 462 |
+
</select>
|
| 463 |
+
</div>
|
| 464 |
+
<div>
|
| 465 |
+
<label for="correctionFilter" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Commentaires</label>
|
| 466 |
+
<select id="correctionFilter" class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
| 467 |
+
<option value="">Tous</option> <option value="with">Avec commentaires</option> <option value="without">Sans commentaires</option>
|
| 468 |
+
</select>
|
| 469 |
+
</div>
|
| 470 |
+
<div>
|
| 471 |
+
<label for="ncSearchInput" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Recherche</label>
|
| 472 |
+
<input type="text" id="ncSearchInput" placeholder="Mots-clés..." class="mt-1 block w-full py-2 px-3 border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
| 473 |
+
</div>
|
| 474 |
+
</div>
|
| 475 |
+
</div>
|
| 476 |
+
<div id="nonConformitiesStats" class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
| 477 |
+
<div class="stat-card dark:bg-gray-800"><div class="stat-number dark:text-blue-400" id="totalNC">0</div><div class="stat-label dark:text-gray-400">Total NC + NA</div></div>
|
| 478 |
+
<div class="stat-card dark:bg-gray-800"><div class="stat-number dark:text-yellow-400" id="scoreB">0</div><div class="stat-label dark:text-gray-400">Score B</div></div>
|
| 479 |
+
<div class="stat-card dark:bg-gray-800"><div class="stat-number dark:text-orange-400" id="scoreC">0</div><div class="stat-label dark:text-gray-400">Score C</div></div>
|
| 480 |
+
<div class="stat-card dark:bg-gray-800"><div class="stat-number dark:text-red-400" id="scoreD">0</div><div class="stat-label dark:text-gray-400">Score D</div></div>
|
| 481 |
+
<div class="stat-card dark:bg-gray-800"><div class="stat-number dark:text-gray-400" id="naCount">0</div><div class="stat-label dark:text-gray-400">Non-Applicables</div></div>
|
| 482 |
+
<div class="stat-card dark:bg-gray-800"><div class="stat-number dark:text-purple-400" id="commentsNCCount">0</div><div class="stat-label dark:text-gray-400">Commentaires NC/NA</div></div>
|
| 483 |
+
</div>
|
| 484 |
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow overflow-x-auto">
|
| 485 |
+
<table class="data-table-custom w-full" id="nonConformitiesTable">
|
| 486 |
+
<thead><tr><th>N° Exigence</th><th>Score</th><th>Explication</th><th>Explication détaillée</th><th>Commentaires Reviewer</th></tr></thead>
|
| 487 |
+
<tbody id="nonConformitiesTableBody"></tbody>
|
| 488 |
+
</table>
|
| 489 |
+
</div>
|
| 490 |
+
</div>
|
| 491 |
+
</main>
|
| 492 |
+
</div>
|
| 493 |
+
|
| 494 |
+
<script>
|
| 495 |
+
// --- Variables globales pour le redimensionnement ---
|
| 496 |
+
let isResizing = false;
|
| 497 |
+
let currentTable = null;
|
| 498 |
+
let currentColumn = null;
|
| 499 |
+
let startX = 0;
|
| 500 |
+
let startWidth = 0;
|
| 501 |
+
|
| 502 |
+
// --- Sidebar and Dark Mode logic ---
|
| 503 |
+
const sidebar = document.querySelector('.sidebar');
|
| 504 |
+
const sidebarCollapse = document.getElementById('sidebarCollapse');
|
| 505 |
+
const mobileSidebarToggle = document.getElementById('mobileSidebarToggle');
|
| 506 |
+
const mobileSidebarToggleHeader = document.getElementById('mobileSidebarToggleHeader');
|
| 507 |
+
const mainContent = document.querySelector('.main-content');
|
| 508 |
+
const sidebarTexts = document.querySelectorAll('.sidebar-text');
|
| 509 |
+
|
| 510 |
+
sidebarCollapse.addEventListener('click', () => {
|
| 511 |
+
sidebar.classList.toggle('sidebar-collapsed');
|
| 512 |
+
mainContent.classList.toggle('main-content-collapsed');
|
| 513 |
+
});
|
| 514 |
+
|
| 515 |
+
function toggleMobileSidebar() {
|
| 516 |
+
sidebar.classList.toggle('active');
|
| 517 |
+
}
|
| 518 |
+
mobileSidebarToggle.addEventListener('click', toggleMobileSidebar);
|
| 519 |
+
if(mobileSidebarToggleHeader) mobileSidebarToggleHeader.addEventListener('click', toggleMobileSidebar);
|
| 520 |
+
|
| 521 |
+
document.getElementById('darkModeToggle').addEventListener('click', () => {
|
| 522 |
+
document.documentElement.classList.toggle('dark');
|
| 523 |
+
const icon = document.getElementById('darkModeToggle').querySelector('i');
|
| 524 |
+
icon.classList.toggle('fa-moon');
|
| 525 |
+
icon.classList.toggle('fa-sun');
|
| 526 |
+
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark'));
|
| 527 |
+
});
|
| 528 |
+
|
| 529 |
+
if (localStorage.getItem('darkMode') === 'true' ||
|
| 530 |
+
(!('darkMode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
| 531 |
+
document.documentElement.classList.add('dark');
|
| 532 |
+
document.getElementById('darkModeToggle').querySelector('i').classList.replace('fa-moon', 'fa-sun');
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
// --- Fonctions de redimensionnement des colonnes ---
|
| 536 |
+
function initColumnResize() {
|
| 537 |
+
const tables = document.querySelectorAll('.data-table-custom');
|
| 538 |
+
|
| 539 |
+
tables.forEach(table => {
|
| 540 |
+
const headers = table.querySelectorAll('th');
|
| 541 |
+
|
| 542 |
+
headers.forEach((header, index) => {
|
| 543 |
+
if (index < headers.length - 1) { // Pas pour la dernière colonne
|
| 544 |
+
header.addEventListener('mousedown', (e) => {
|
| 545 |
+
const rect = header.getBoundingClientRect();
|
| 546 |
+
if (e.clientX >= rect.right - 10) { // Zone de 10px depuis le bord droit
|
| 547 |
+
startResize(e, table, index);
|
| 548 |
+
}
|
| 549 |
+
});
|
| 550 |
+
|
| 551 |
+
header.addEventListener('mousemove', (e) => {
|
| 552 |
+
const rect = header.getBoundingClientRect();
|
| 553 |
+
if (e.clientX >= rect.right - 10) {
|
| 554 |
+
header.style.cursor = 'col-resize';
|
| 555 |
+
} else {
|
| 556 |
+
header.style.cursor = 'default';
|
| 557 |
+
}
|
| 558 |
+
});
|
| 559 |
+
}
|
| 560 |
+
});
|
| 561 |
+
});
|
| 562 |
+
|
| 563 |
+
// Événements globaux pour le redimensionnement
|
| 564 |
+
document.addEventListener('mousemove', handleResize);
|
| 565 |
+
document.addEventListener('mouseup', stopResize);
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
function startResize(e, table, columnIndex) {
|
| 569 |
+
isResizing = true;
|
| 570 |
+
currentTable = table;
|
| 571 |
+
currentColumn = columnIndex;
|
| 572 |
+
startX = e.clientX;
|
| 573 |
+
|
| 574 |
+
const header = table.querySelectorAll('th')[columnIndex];
|
| 575 |
+
startWidth = header.offsetWidth;
|
| 576 |
+
|
| 577 |
+
document.body.classList.add('resizing');
|
| 578 |
+
e.preventDefault();
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
function handleResize(e) {
|
| 582 |
+
if (!isResizing) return;
|
| 583 |
+
|
| 584 |
+
const diff = e.clientX - startX;
|
| 585 |
+
const newWidth = Math.max(50, startWidth + diff); // Largeur minimum de 50px
|
| 586 |
+
|
| 587 |
+
// Appliquer la nouvelle largeur
|
| 588 |
+
const headers = currentTable.querySelectorAll('th');
|
| 589 |
+
const cells = currentTable.querySelectorAll('td');
|
| 590 |
+
|
| 591 |
+
headers[currentColumn].style.width = newWidth + 'px';
|
| 592 |
+
|
| 593 |
+
// Appliquer à toutes les cellules de cette colonne
|
| 594 |
+
const rows = currentTable.querySelectorAll('tr');
|
| 595 |
+
rows.forEach(row => {
|
| 596 |
+
const cell = row.cells[currentColumn];
|
| 597 |
+
if (cell) {
|
| 598 |
+
cell.style.width = newWidth + 'px';
|
| 599 |
+
}
|
| 600 |
+
});
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
function stopResize() {
|
| 604 |
+
if (isResizing) {
|
| 605 |
+
isResizing = false;
|
| 606 |
+
currentTable = null;
|
| 607 |
+
currentColumn = null;
|
| 608 |
+
document.body.classList.remove('resizing');
|
| 609 |
+
}
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
// --- Tab Navigation ---
|
| 613 |
+
const navLinks = document.querySelectorAll('.nav-link');
|
| 614 |
+
const tabContents = document.querySelectorAll('.tab-content-custom');
|
| 615 |
+
|
| 616 |
+
navLinks.forEach(link => {
|
| 617 |
+
link.addEventListener('click', (e) => {
|
| 618 |
+
e.preventDefault();
|
| 619 |
+
const targetTabId = link.dataset.tabTarget;
|
| 620 |
+
|
| 621 |
+
navLinks.forEach(l => {
|
| 622 |
+
l.classList.remove('bg-blue-700', 'dark:bg-blue-700');
|
| 623 |
+
l.classList.add('hover:text-white');
|
| 624 |
+
});
|
| 625 |
+
link.classList.add('bg-blue-700', 'dark:bg-blue-700');
|
| 626 |
+
link.classList.remove('hover:text-white');
|
| 627 |
+
|
| 628 |
+
tabContents.forEach(content => {
|
| 629 |
+
content.classList.remove('active');
|
| 630 |
+
});
|
| 631 |
+
const targetContent = document.getElementById(targetTabId);
|
| 632 |
+
if (targetContent) {
|
| 633 |
+
targetContent.classList.add('active');
|
| 634 |
+
if (targetTabId === 'checklist' && checklistData.length > 0) renderChecklistTable();
|
| 635 |
+
if (targetTabId === 'nonconformites' && checklistData.length > 0) renderNonConformitiesTable();
|
| 636 |
+
if (targetTabId === 'profil' && Object.keys(companyProfileData).length > 0) renderCompanyProfile();
|
| 637 |
+
|
| 638 |
+
// Réinitialiser le redimensionnement pour le nouveau tableau
|
| 639 |
+
setTimeout(() => {
|
| 640 |
+
initColumnResize();
|
| 641 |
+
}, 100);
|
| 642 |
+
}
|
| 643 |
+
if (sidebar.classList.contains('active')) {
|
| 644 |
+
toggleMobileSidebar();
|
| 645 |
+
}
|
| 646 |
+
});
|
| 647 |
+
});
|
| 648 |
+
|
| 649 |
+
function setDefaultTab() {
|
| 650 |
+
const defaultLink = document.querySelector('.nav-link[data-tab-target="profil"]');
|
| 651 |
+
if (defaultLink) {
|
| 652 |
+
defaultLink.classList.add('bg-blue-700', 'dark:bg-blue-700');
|
| 653 |
+
defaultLink.classList.remove('hover:text-white');
|
| 654 |
+
}
|
| 655 |
+
const defaultContent = document.getElementById('profil');
|
| 656 |
+
if (defaultContent) {
|
| 657 |
+
defaultContent.classList.add('active');
|
| 658 |
+
}
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
// --- IFS Logic ---
|
| 662 |
+
let auditData = null;
|
| 663 |
+
let checklistData = [];
|
| 664 |
+
let companyProfileData = {};
|
| 665 |
+
let comments = {};
|
| 666 |
+
let requirementNumberMapping = {};
|
| 667 |
+
let currentSession = { id: null, name: '', created: null, lastModified: null, data: null };
|
| 668 |
+
|
| 669 |
+
document.getElementById('newAuditBtn').addEventListener('click', createNewSession);
|
| 670 |
+
document.getElementById('saveAuditBtn').addEventListener('click', saveWorkInProgress);
|
| 671 |
+
document.getElementById('loadAuditBtn').addEventListener('click', () => document.getElementById('fileInputInternal').click());
|
| 672 |
+
document.getElementById('fileInputInternal').addEventListener('change', handleFileUpload);
|
| 673 |
+
document.getElementById('exportExcelBtn').addEventListener('click', exportForAuditor);
|
| 674 |
+
document.getElementById('exportAllDataBtn').addEventListener('click', exportAllData);
|
| 675 |
+
|
| 676 |
+
const uploadZone = document.getElementById('uploadZone');
|
| 677 |
+
const fileInput = document.getElementById('fileInput');
|
| 678 |
+
const loadingElement = document.getElementById('loading');
|
| 679 |
+
const profilResultsElement = document.getElementById('profilResults');
|
| 680 |
+
const successAlertElement = document.getElementById('successAlert');
|
| 681 |
+
|
| 682 |
+
if(uploadZone) {
|
| 683 |
+
uploadZone.addEventListener('click', () => document.getElementById('fileInputInternal').click());
|
| 684 |
+
uploadZone.addEventListener('dragover', (e) => {
|
| 685 |
+
e.preventDefault();
|
| 686 |
+
uploadZone.classList.add('border-blue-500', 'dark:border-blue-400', 'bg-gray-100', 'dark:bg-gray-700');
|
| 687 |
+
});
|
| 688 |
+
uploadZone.addEventListener('dragleave', () => {
|
| 689 |
+
uploadZone.classList.remove('border-blue-500', 'dark:border-blue-400', 'bg-gray-100', 'dark:bg-gray-700');
|
| 690 |
+
});
|
| 691 |
+
uploadZone.addEventListener('drop', (e) => {
|
| 692 |
+
e.preventDefault();
|
| 693 |
+
uploadZone.classList.remove('border-blue-500', 'dark:border-blue-400', 'bg-gray-100', 'dark:bg-gray-700');
|
| 694 |
+
const files = e.dataTransfer.files;
|
| 695 |
+
if (files.length > 0) {
|
| 696 |
+
document.getElementById('fileInputInternal').files = files;
|
| 697 |
+
handleFileUpload({ target: { files: files } });
|
| 698 |
+
}
|
| 699 |
+
});
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
function switchToolTab(tabName) {
|
| 703 |
+
console.log('Switching tool tab to:', tabName);
|
| 704 |
+
document.querySelectorAll('.tab-content-custom').forEach(tab => tab.classList.remove('active'));
|
| 705 |
+
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('bg-blue-700', 'dark:bg-blue-700'));
|
| 706 |
+
|
| 707 |
+
const targetTabElement = document.getElementById(tabName);
|
| 708 |
+
const targetNavLink = document.querySelector(`.nav-link[data-tab-target="${tabName}"]`);
|
| 709 |
+
|
| 710 |
+
if (targetTabElement) targetTabElement.classList.add('active');
|
| 711 |
+
if (targetNavLink) targetNavLink.classList.add('bg-blue-700', 'dark:bg-blue-700');
|
| 712 |
+
|
| 713 |
+
if (auditData) {
|
| 714 |
+
if (tabName === 'profil') renderCompanyProfile();
|
| 715 |
+
else if (tabName === 'checklist') renderChecklistTable();
|
| 716 |
+
else if (tabName === 'nonconformites') renderNonConformitiesTable();
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
setTimeout(() => {
|
| 720 |
+
initColumnResize();
|
| 721 |
+
}, 100);
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
function handleFileUpload(event) {
|
| 725 |
+
console.log('File upload started via internal input');
|
| 726 |
+
const file = event.target.files[0];
|
| 727 |
+
if (!file) return;
|
| 728 |
+
|
| 729 |
+
const fileName = file.name.toLowerCase();
|
| 730 |
+
const isIFSFile = fileName.endsWith('.ifs');
|
| 731 |
+
const isIFSRFile = fileName.endsWith('.ifsr');
|
| 732 |
+
|
| 733 |
+
if (!isIFSFile && !isIFSRFile) {
|
| 734 |
+
alert('❌ Type de fichier non supporté !\n\n✅ Formats acceptés :\n• .ifs (nouveau dossier depuis NEO)\n• .ifsr (travail en cours sauvegardé)');
|
| 735 |
+
return;
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
if (loadingElement) loadingElement.classList.remove('hidden');
|
| 739 |
+
if (uploadZone) uploadZone.classList.add('hidden');
|
| 740 |
+
if (profilResultsElement) profilResultsElement.classList.add('hidden');
|
| 741 |
+
simulateProgress();
|
| 742 |
+
|
| 743 |
+
const reader = new FileReader();
|
| 744 |
+
reader.onload = function(e) {
|
| 745 |
+
try {
|
| 746 |
+
const content = e.target.result;
|
| 747 |
+
const data = JSON.parse(content);
|
| 748 |
+
if (isIFSFile) processNewIFSFile(data);
|
| 749 |
+
else loadWorkInProgress(data);
|
| 750 |
+
} catch (error) {
|
| 751 |
+
console.error('Error during file processing:', error);
|
| 752 |
+
showError(`Erreur lors du traitement du fichier ${isIFSFile ? 'IFS' : 'IFSR'} : ` + error.message);
|
| 753 |
+
if (loadingElement) loadingElement.classList.add('hidden');
|
| 754 |
+
if (uploadZone) uploadZone.classList.remove('hidden');
|
| 755 |
+
if (profilResultsElement) profilResultsElement.classList.add('hidden');
|
| 756 |
+
} finally {
|
| 757 |
+
event.target.value = null;
|
| 758 |
+
}
|
| 759 |
+
};
|
| 760 |
+
reader.onerror = function(error) {
|
| 761 |
+
console.error('File reading error:', error);
|
| 762 |
+
showError('Erreur lors de la lecture du fichier');
|
| 763 |
+
if (loadingElement) loadingElement.classList.add('hidden');
|
| 764 |
+
if (uploadZone) uploadZone.classList.remove('hidden');
|
| 765 |
+
if (profilResultsElement) profilResultsElement.classList.add('hidden');
|
| 766 |
+
event.target.value = null;
|
| 767 |
+
};
|
| 768 |
+
reader.readAsText(file);
|
| 769 |
+
}
|
| 770 |
+
|
| 771 |
+
function simulateProgress() {
|
| 772 |
+
let progress = 0;
|
| 773 |
+
const progressFill = document.getElementById('progressFill');
|
| 774 |
+
if (!progressFill) {
|
| 775 |
+
setTimeout(() => {
|
| 776 |
+
if (loadingElement) loadingElement.classList.add('hidden');
|
| 777 |
+
if (auditData && profilResultsElement) profilResultsElement.classList.remove('hidden');
|
| 778 |
+
}, 1000);
|
| 779 |
+
return;
|
| 780 |
+
}
|
| 781 |
+
const interval = setInterval(() => {
|
| 782 |
+
progress += Math.random() * 15;
|
| 783 |
+
if (progress >= 100) {
|
| 784 |
+
progress = 100;
|
| 785 |
+
clearInterval(interval);
|
| 786 |
+
setTimeout(() => { if (progressFill) progressFill.style.width = '0%'; }, 500);
|
| 787 |
+
}
|
| 788 |
+
if (progressFill) progressFill.style.width = progress + '%';
|
| 789 |
+
}, 200);
|
| 790 |
+
}
|
| 791 |
+
|
| 792 |
+
function processNewIFSFile(data) {
|
| 793 |
+
auditData = data;
|
| 794 |
+
checklistData = []; companyProfileData = {}; comments = {}; requirementNumberMapping = {};
|
| 795 |
+
currentSession = { id: `IFS-${Date.now()}`, name: 'Nouvel Audit', created: new Date(), lastModified: new Date(), data: auditData };
|
| 796 |
+
processAuditDataLogic(auditData);
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
function loadWorkInProgress(workData) {
|
| 800 |
+
try {
|
| 801 |
+
if (!workData.auditData || !workData.version) throw new Error('Format de fichier IFSR invalide');
|
| 802 |
+
auditData = workData.auditData;
|
| 803 |
+
checklistData = workData.checklistData || [];
|
| 804 |
+
companyProfileData = workData.companyProfileData || {};
|
| 805 |
+
comments = workData.comments || {};
|
| 806 |
+
requirementNumberMapping = workData.requirementNumberMapping || {};
|
| 807 |
+
currentSession = {
|
| 808 |
+
id: workData.coid ? `IFS-${workData.coid}-${new Date(workData.savedDate).getTime()}` : `IFS-Loaded-${Date.now()}`,
|
| 809 |
+
name: `Audit ${workData.companyName || 'Inconnu'} (rechargé)`,
|
| 810 |
+
created: workData.savedDate ? new Date(workData.savedDate) : new Date(),
|
| 811 |
+
lastModified: new Date(), data: auditData
|
| 812 |
+
};
|
| 813 |
+
|
| 814 |
+
const companyName = companyProfileData['Nom du site à auditer'] || workData.companyName || 'Société inconnue';
|
| 815 |
+
const coid = companyProfileData['N° COID du portail'] || workData.coid || 'COID-XXXX';
|
| 816 |
+
const totalComments = Object.keys(comments).filter(key => comments[key]?.[0]?.content.trim() !== '').length;
|
| 817 |
+
|
| 818 |
+
updateSessionIdInUI(coid);
|
| 819 |
+
document.getElementById('currentAuditName').textContent = `IFS Reviewer - ${companyName}`;
|
| 820 |
+
|
| 821 |
+
if (auditData?.data?.modules?.food_8?.result?.overall) {
|
| 822 |
+
const overallResult = auditData.data.modules.food_8.result.overall;
|
| 823 |
+
updateElementTextContent('overallScore', overallResult.percent.toFixed(1) + '%');
|
| 824 |
+
}
|
| 825 |
+
updateElementTextContent('totalRequirements', checklistData.length);
|
| 826 |
+
updateElementTextContent('conformCount', checklistData.filter(item => item.score === 'A').length);
|
| 827 |
+
updateElementTextContent('nonConformCount', checklistData.filter(item => ['B', 'C', 'D'].includes(item.score)).length);
|
| 828 |
+
|
| 829 |
+
if(successAlertElement) successAlertElement.textContent = `📂 Travail rechargé : ${companyName} (${totalComments} commentaires sauvegardés)`;
|
| 830 |
+
|
| 831 |
+
finalizeUIUpdateAndRender();
|
| 832 |
+
alert(`✅ Travail chargé: ${companyName}\nCOID: ${coid}\nCommentaires: ${totalComments}`);
|
| 833 |
+
} catch (error) {
|
| 834 |
+
console.error('Error loading work in progress:', error);
|
| 835 |
+
showError('Erreur lors du chargement : ' + error.message);
|
| 836 |
+
resetToUploadState();
|
| 837 |
+
}
|
| 838 |
+
}
|
| 839 |
+
|
| 840 |
+
function saveWorkInProgress() {
|
| 841 |
+
if (!auditData) {
|
| 842 |
+
alert('❌ Aucune donnée à sauvegarder. Chargez un fichier .ifs d\'abord.');
|
| 843 |
+
return;
|
| 844 |
+
}
|
| 845 |
+
try {
|
| 846 |
+
const companyName = companyProfileData['Nom du site à auditer'] || 'Société inconnue';
|
| 847 |
+
const coid = companyProfileData['N° COID du portail'] || 'COID-XXXX';
|
| 848 |
+
const workPackage = {
|
| 849 |
+
version: '1.2', savedDate: new Date().toISOString(), companyName, coid,
|
| 850 |
+
auditData, checklistData, companyProfileData, comments, requirementNumberMapping,
|
| 851 |
+
stats: {
|
| 852 |
+
totalComments: Object.keys(comments).filter(k => comments[k]?.[0]?.content.trim()).length,
|
| 853 |
+
totalRequirements: checklistData.length,
|
| 854 |
+
progressPercentage: parseFloat(document.getElementById('progressPercentage')?.textContent) || 0
|
| 855 |
+
}
|
| 856 |
+
};
|
| 857 |
+
const dataStr = JSON.stringify(workPackage, null, 2);
|
| 858 |
+
const blob = new Blob([dataStr], {type: 'application/json'});
|
| 859 |
+
const link = document.createElement('a');
|
| 860 |
+
link.href = URL.createObjectURL(blob);
|
| 861 |
+
const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
| 862 |
+
link.download = `TRAVAIL_${coid}_${companyName.replace(/[^a-zA-Z0-9]/g, '_')}_${timestamp}.ifsr`;
|
| 863 |
+
link.click(); URL.revokeObjectURL(link.href);
|
| 864 |
+
alert(`✅ Travail sauvegardé : ${link.download}`);
|
| 865 |
+
} catch (error) {
|
| 866 |
+
console.error('Error saving work:', error);
|
| 867 |
+
alert('❌ Erreur sauvegarde : ' + error.message);
|
| 868 |
+
}
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
function processAuditDataLogic(dataFromIFSFile) {
|
| 872 |
+
console.log('Processing audit data logic...');
|
| 873 |
+
if (!dataFromIFSFile?.data?.modules?.food_8) {
|
| 874 |
+
showError('Format de fichier IFS non valide.');
|
| 875 |
+
resetToUploadState();
|
| 876 |
+
return;
|
| 877 |
+
}
|
| 878 |
+
const food8 = dataFromIFSFile.data.modules.food_8;
|
| 879 |
+
const matrixResult = food8.matrixResult;
|
| 880 |
+
const overallResult = food8.result.overall;
|
| 881 |
+
let totalA = 0, totalB = 0, totalC = 0, totalD = 0, totalNA = 0;
|
| 882 |
+
matrixResult.forEach(item => {
|
| 883 |
+
if (item.type === 'scoreCount') {
|
| 884 |
+
switch(item.scoreId) {
|
| 885 |
+
case 'A': totalA += item.count; break;
|
| 886 |
+
case 'B': totalB += item.count; break;
|
| 887 |
+
case 'C': totalC += item.count; break;
|
| 888 |
+
case 'D': totalD += item.count; break;
|
| 889 |
+
case 'NA': totalNA += item.count; break;
|
| 890 |
+
}
|
| 891 |
+
}
|
| 892 |
+
});
|
| 893 |
+
const totalRequirements = totalA + totalB + totalC + totalD + totalNA;
|
| 894 |
+
updateElementTextContent('totalRequirements', totalRequirements);
|
| 895 |
+
updateElementTextContent('conformCount', totalA);
|
| 896 |
+
updateElementTextContent('nonConformCount', totalB + totalC + totalD);
|
| 897 |
+
updateElementTextContent('overallScore', overallResult.percent.toFixed(1) + '%');
|
| 898 |
+
extractCompanyProfile(food8.questions);
|
| 899 |
+
processChecklistData(food8.checklists);
|
| 900 |
+
const companyName = food8.questions.companyName?.answer || 'Société inconnue';
|
| 901 |
+
const coid = food8.questions.companyCoid?.answer || 'COID-XXXX';
|
| 902 |
+
currentSession.id = `IFS-${coid}-${new Date().getTime()}`;
|
| 903 |
+
currentSession.name = `Audit ${companyName}`;
|
| 904 |
+
updateSessionIdInUI(coid);
|
| 905 |
+
document.getElementById('currentAuditName').textContent = `IFS Reviewer - ${companyName}`;
|
| 906 |
+
if(successAlertElement) successAlertElement.textContent = `✅ Nouveau dossier créé : ${companyName} (${totalRequirements} exigences)`;
|
| 907 |
+
finalizeUIUpdateAndRender();
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
function finalizeUIUpdateAndRender() {
|
| 911 |
+
if (loadingElement) loadingElement.classList.add('hidden');
|
| 912 |
+
if (profilResultsElement) profilResultsElement.classList.remove('hidden');
|
| 913 |
+
if (uploadZone) uploadZone.classList.add('hidden');
|
| 914 |
+
|
| 915 |
+
switchToolTab('profil');
|
| 916 |
+
|
| 917 |
+
setTimeout(() => {
|
| 918 |
+
console.log("Rendering components after data load/process...");
|
| 919 |
+
renderCompanyProfile();
|
| 920 |
+
renderChecklistTable();
|
| 921 |
+
renderNonConformitiesTable();
|
| 922 |
+
updateNonConformitiesStats();
|
| 923 |
+
updateProgressStats();
|
| 924 |
+
initColumnResize();
|
| 925 |
+
}, 100);
|
| 926 |
+
}
|
| 927 |
+
|
| 928 |
+
function resetToUploadState() {
|
| 929 |
+
if (loadingElement) loadingElement.classList.add('hidden');
|
| 930 |
+
if (uploadZone) uploadZone.classList.remove('hidden');
|
| 931 |
+
if (profilResultsElement) profilResultsElement.classList.add('hidden');
|
| 932 |
+
document.getElementById('currentAuditName').textContent = "Aucun audit chargé";
|
| 933 |
+
updateSessionIdInUI("----");
|
| 934 |
+
}
|
| 935 |
+
|
| 936 |
+
function extractCompanyProfile(questions) {
|
| 937 |
+
if (!questions) { companyProfileData = {}; return; }
|
| 938 |
+
companyProfileData = {
|
| 939 |
+
'Nom du site à auditer': questions.companyName?.answer || '',
|
| 940 |
+
'N° COID du portail': questions.companyCoid?.answer || '',
|
| 941 |
+
'Code GLN': questions.companyGln?.[0]?.rootQuestions?.companyGlnNumber?.answer || '',
|
| 942 |
+
'Rue': questions.companyStreetNo?.answer || '',
|
| 943 |
+
'Code postal': questions.companyZip?.answer || '',
|
| 944 |
+
'Nom de la ville': questions.companyCity?.answer || '',
|
| 945 |
+
'Pays': questions.companyCountry?.answer || '',
|
| 946 |
+
'Téléphone': questions.companyTelephone?.answer || '',
|
| 947 |
+
'Email': questions.companyEmail?.answer || '',
|
| 948 |
+
'Latitude': questions.companyGpsLatitude?.answer || '',
|
| 949 |
+
'Longitude': questions.companyGpsLongitude?.answer || '',
|
| 950 |
+
'Nom du siège social': questions.headquartersName?.answer || '',
|
| 951 |
+
'Rue (siège social)': questions.headquartersStreetNo?.answer || '',
|
| 952 |
+
'Nom de la ville (siège social)': questions.headquartersCity?.answer || '',
|
| 953 |
+
'Code postal (siège social)': questions.headquartersZip?.answer || '',
|
| 954 |
+
'Pays (siège social)': questions.headquartersCountry?.answer || '',
|
| 955 |
+
'Téléphone (siège social)': questions.headquartersTelephone?.answer || '',
|
| 956 |
+
'Surface couverte de l\'entreprise (m²)': questions.productionAreaSize?.answer || '',
|
| 957 |
+
'Nombre de bâtiments': questions.numberOfBuildings?.answer || '',
|
| 958 |
+
'Nombre de lignes de production': questions.numberOfProductionLines?.answer || '',
|
| 959 |
+
'Nombre d\'étages': questions.numberOfFloors?.answer || '',
|
| 960 |
+
'Nombre maximum d\'employés dans l\'année, au pic de production': questions.numberOfEmployeesForTimeCalculation?.answer || '',
|
| 961 |
+
'Commentaires employés': questions.numberOfEmployeesDescription?.answer || '',
|
| 962 |
+
'Structures décentralisées': questions.companyStructureDecentralisedDescription?.answer || '',
|
| 963 |
+
'Fonctions centralisées': questions.companyStructureMultiLocationProductionDescription?.answer || '',
|
| 964 |
+
'Langue parlée et écrite sur le site': questions.workingLanguage?.answer || '',
|
| 965 |
+
'Langue du système qualité': questions.qmsLanguage?.answer?.[0] || '',
|
| 966 |
+
'Audit scope EN': questions.scopeCertificateScopeDescription_en?.answer || '',
|
| 967 |
+
'Périmètre de l\'audit FR': questions.scopeAuditScopeDescription?.answer || '',
|
| 968 |
+
'Process et activités': questions.scopeProductGroupsDescription?.answer || '',
|
| 969 |
+
'Activité saisonnière ? (O/N)': questions.seasonalProduction?.answer || '',
|
| 970 |
+
'Une partie du procédé de fabrication est-elle sous traitée? (OUI/NON)': questions.partlyOutsourcedProcesses?.answer || '',
|
| 971 |
+
'Si oui lister les procédés sous-traités': questions.partlyOutsourcedProcessesDescription?.answer || '',
|
| 972 |
+
'Avez-vous des produits totalement sous-traités? (OUI/NON)': questions.fullyOutsourcedProducts?.answer || '',
|
| 973 |
+
'Si oui, lister les produits totalement sous-traités': questions.fullyOutsourcedProductsDescription?.answer || '',
|
| 974 |
+
'Avez-vous des produits de négoce? (OUI/NON)': questions.tradedProductsBrokerActivity?.answer || '',
|
| 975 |
+
'Si oui, lister les produits de négoce': questions.tradedProductsBrokerActivityDescription?.answer || '',
|
| 976 |
+
'Produits à exclure du champ d\'audit (OUI/NON)': questions.exclusions?.answer || '',
|
| 977 |
+
'Préciser les produits à exclure': questions.exclusionsDescription?.answer || ''
|
| 978 |
+
};
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
function renderCompanyProfile() {
|
| 982 |
+
const container = document.getElementById('companyProfileTable');
|
| 983 |
+
if (!container) return;
|
| 984 |
+
if (!companyProfileData || Object.keys(companyProfileData).length === 0) {
|
| 985 |
+
container.innerHTML = '<p class="text-center p-10 text-gray-500 dark:text-gray-400">Aucune donnée de profil. Chargez un fichier.</p>';
|
| 986 |
+
return;
|
| 987 |
+
}
|
| 988 |
+
const categories = {
|
| 989 |
+
'Informations générales': ['Nom du site à auditer', 'N° COID du portail', 'Code GLN'],
|
| 990 |
+
'Adresse du site': ['Rue', 'Code postal', 'Nom de la ville', 'Pays', 'Téléphone', 'Email', 'Latitude', 'Longitude'],
|
| 991 |
+
'Siège social': ['Nom du siège social', 'Rue (siège social)', 'Nom de la ville (siège social)', 'Code postal (siège social)', 'Pays (siège social)', 'Téléphone (siège social)'],
|
| 992 |
+
'Informations techniques': ['Surface couverte de l\'entreprise (m²)', 'Nombre de bâtiments', 'Nombre de lignes de production', 'Nombre d\'étages', 'Nombre maximum d\'employés dans l\'année, au pic de production', 'Commentaires employés'],
|
| 993 |
+
'Structure organisationnelle': ['Structures décentralisées', 'Fonctions centralisées'],
|
| 994 |
+
'Langues': ['Langue parlée et écrite sur le site', 'Langue du système qualité'],
|
| 995 |
+
'Périmètres d\'audit': ['Audit scope EN', 'Périmètre de l\'audit FR', 'Process et activités'],
|
| 996 |
+
'Activités spécifiques': ['Activité saisonnière ? (O/N)', 'Une partie du procédé de fabrication est-elle sous traitée? (OUI/NON)', 'Si oui lister les procédés sous-traités', 'Avez-vous des produits totalement sous-traités? (OUI/NON)', 'Si oui, lister les produits totalement sous-traités', 'Avez-vous des produits de négoce? (OUI/NON)', 'Si oui, lister les produits de négoce'],
|
| 997 |
+
'Exclusions': ['Produits à exclure du champ d\'audit (OUI/NON)', 'Préciser les produits à exclure']
|
| 998 |
+
};
|
| 999 |
+
let html = '';
|
| 1000 |
+
Object.entries(categories).forEach(([categoryName, fields]) => {
|
| 1001 |
+
html += `<div class="category-header-custom">${categoryName}</div>
|
| 1002 |
+
<div class="overflow-x-auto"><table class="data-table-custom w-full">
|
| 1003 |
+
<thead><tr><th>Information</th><th>Valeur</th><th>Commentaires Reviewer</th></tr></thead><tbody>`;
|
| 1004 |
+
fields.forEach(field => {
|
| 1005 |
+
if (companyProfileData.hasOwnProperty(field)) {
|
| 1006 |
+
const value = companyProfileData[field];
|
| 1007 |
+
const commentId = `profile-${field.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase()}`;
|
| 1008 |
+
const currentComment = comments[commentId]?.[0]?.content || '';
|
| 1009 |
+
const isLongText = ['Périmètre de l\'audit FR', 'Audit scope EN', 'Process et activités', 'Si oui lister les procédés sous-traités', 'Si oui, lister les produits totalement sous-traités', 'Si oui, lister les produits de négoce', 'Préciser les produits à exclure', 'Commentaires employés', 'Structures décentralisées', 'Fonctions centralisées'].includes(field);
|
| 1010 |
+
const inputField = `<textarea id="${commentId}" placeholder="Commentaire..." class="${isLongText ? 'profile-textarea-long' : 'profile-textarea-short'}" onchange="saveProfileComment('${commentId}')">${currentComment}</textarea>`;
|
| 1011 |
+
const displayValue = isLongText && value && value.length > 100 ? `<div class="value-display-long dark:bg-gray-700">${value || 'N/A'}</div>` : `<span class="value-display-short">${value || 'N/A'}</span>`;
|
| 1012 |
+
html += `<tr><td class="font-medium">${field}</td><td>${displayValue}</td><td>${inputField}</td></tr>`;
|
| 1013 |
+
}
|
| 1014 |
+
});
|
| 1015 |
+
html += `</tbody></table></div>`;
|
| 1016 |
+
});
|
| 1017 |
+
container.innerHTML = html;
|
| 1018 |
+
}
|
| 1019 |
+
|
| 1020 |
+
function saveProfileComment(commentId) {
|
| 1021 |
+
const textarea = document.getElementById(commentId);
|
| 1022 |
+
if (!textarea) return;
|
| 1023 |
+
const content = textarea.value.trim();
|
| 1024 |
+
if (content) comments[commentId] = [{ id: Date.now(), author: 'reviewer', content: content, date: new Date().toISOString() }];
|
| 1025 |
+
else delete comments[commentId];
|
| 1026 |
+
updateProgressStats();
|
| 1027 |
+
saveCurrentSession();
|
| 1028 |
+
}
|
| 1029 |
+
|
| 1030 |
+
function processChecklistData(checklists) {
|
| 1031 |
+
if (!checklists?.checklistFood8?.resultScorings) {
|
| 1032 |
+
checklistData = [];
|
| 1033 |
+
requirementNumberMapping = {};
|
| 1034 |
+
return;
|
| 1035 |
+
}
|
| 1036 |
+
const resultScorings = checklists.checklistFood8.resultScorings;
|
| 1037 |
+
checklistData = [];
|
| 1038 |
+
requirementNumberMapping = {};
|
| 1039 |
+
let chapterCounters = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0 };
|
| 1040 |
+
|
| 1041 |
+
for (const uuid in resultScorings) {
|
| 1042 |
+
const scoring = resultScorings[uuid];
|
| 1043 |
+
let chapter = scoring.requirement?.chapter?.number;
|
| 1044 |
+
let reqNumberFromData = scoring.requirement?.number;
|
| 1045 |
+
if (typeof chapter !== 'number' || chapter < 1 || chapter > 6) chapter = getChapterFromUUID(uuid);
|
| 1046 |
+
let reqNumber;
|
| 1047 |
+
if (reqNumberFromData) reqNumber = `${chapter}.${reqNumberFromData}`;
|
| 1048 |
+
else {
|
| 1049 |
+
chapterCounters[chapter]++;
|
| 1050 |
+
const mainNum = Math.floor((chapterCounters[chapter] -1) / 10) + 1;
|
| 1051 |
+
const subNum = ((chapterCounters[chapter] - 1) % 10) + 1;
|
| 1052 |
+
reqNumber = `${chapter}.${mainNum}.${subNum}`;
|
| 1053 |
+
}
|
| 1054 |
+
requirementNumberMapping[uuid] = reqNumber;
|
| 1055 |
+
checklistData.push({
|
| 1056 |
+
uuid,
|
| 1057 |
+
requirementNumber: reqNumber,
|
| 1058 |
+
chapter,
|
| 1059 |
+
score: scoring.score?.label || 'N/D',
|
| 1060 |
+
scoreValue: scoring.score?.value,
|
| 1061 |
+
explanation: scoring.answers?.explanationText || '',
|
| 1062 |
+
detailedExplanation: scoring.answers?.englishExplanationText || '',
|
| 1063 |
+
needsCorrection: scoring.isCorrectionRequired || false
|
| 1064 |
+
});
|
| 1065 |
+
}
|
| 1066 |
+
|
| 1067 |
+
checklistData.sort((a, b) => {
|
| 1068 |
+
const parseReqNum = (reqStr) => reqStr.split('.').map(Number);
|
| 1069 |
+
const [aChap, ...aRest] = parseReqNum(a.requirementNumber);
|
| 1070 |
+
const [bChap, ...bRest] = parseReqNum(b.requirementNumber);
|
| 1071 |
+
if (aChap !== bChap) return aChap - bChap;
|
| 1072 |
+
for(let i=0; i < Math.min(aRest.length, bRest.length); i++) {
|
| 1073 |
+
if(aRest[i] !== bRest[i]) return aRest[i] - bRest[i];
|
| 1074 |
+
}
|
| 1075 |
+
return aRest.length - bRest.length;
|
| 1076 |
+
});
|
| 1077 |
+
}
|
| 1078 |
+
|
| 1079 |
+
function renderChecklistTable() {
|
| 1080 |
+
const tbody = document.getElementById('checklistTableBody');
|
| 1081 |
+
if (!tbody) return;
|
| 1082 |
+
if (!checklistData || checklistData.length === 0) {
|
| 1083 |
+
tbody.innerHTML = '<tr><td colspan="5" class="text-center p-10 text-gray-500 dark:text-gray-400">Aucune donnée de checklist.</td></tr>';
|
| 1084 |
+
return;
|
| 1085 |
+
}
|
| 1086 |
+
let html = '';
|
| 1087 |
+
checklistData.forEach(item => {
|
| 1088 |
+
const commentId = `req-${item.uuid}`;
|
| 1089 |
+
const currentComment = comments[commentId]?.[0]?.content || '';
|
| 1090 |
+
html += `<tr data-chapter="${item.chapter}" data-score="${item.score}" data-explanation-empty="${!(item.explanation || item.detailedExplanation)}">
|
| 1091 |
+
<td class="font-medium">${item.requirementNumber}</td>
|
| 1092 |
+
<td><span class="score-badge score-${item.score}">${item.score}</span></td>
|
| 1093 |
+
<td>${item.explanation || ''}</td>
|
| 1094 |
+
<td>${item.detailedExplanation || ''}</td>
|
| 1095 |
+
<td><textarea id="${commentId}" placeholder="Commentaire..." class="profile-textarea-short w-full" onchange="saveRequirementComment('${commentId}')">${currentComment}</textarea></td>
|
| 1096 |
+
</tr>`;
|
| 1097 |
+
});
|
| 1098 |
+
tbody.innerHTML = html;
|
| 1099 |
+
filterChecklist();
|
| 1100 |
+
}
|
| 1101 |
+
|
| 1102 |
+
function renderNonConformitiesTable() {
|
| 1103 |
+
const tbody = document.getElementById('nonConformitiesTableBody');
|
| 1104 |
+
if (!tbody) return;
|
| 1105 |
+
if (!checklistData || checklistData.length === 0) {
|
| 1106 |
+
tbody.innerHTML = '<tr><td colspan="5" class="text-center p-10 text-gray-500 dark:text-gray-400">Aucune donnée de NC/NA.</td></tr>';
|
| 1107 |
+
return;
|
| 1108 |
+
}
|
| 1109 |
+
const nonConformItems = checklistData.filter(item => ['B', 'C', 'D', 'NA'].includes(item.score));
|
| 1110 |
+
if (nonConformItems.length === 0) {
|
| 1111 |
+
tbody.innerHTML = '<tr><td colspan="5" class="text-center p-10 text-green-500">🎉 Aucune NC/NA trouvée !</td></tr>';
|
| 1112 |
+
return;
|
| 1113 |
+
}
|
| 1114 |
+
let html = '';
|
| 1115 |
+
nonConformItems.forEach(item => {
|
| 1116 |
+
const commentId = `nc-${item.uuid}`;
|
| 1117 |
+
const currentComment = comments[commentId]?.[0]?.content || '';
|
| 1118 |
+
html += `<tr data-chapter="${item.chapter}" data-score="${item.score}" data-has-comment="${currentComment ? 'true' : 'false'}">
|
| 1119 |
+
<td class="font-medium">${item.requirementNumber}</td>
|
| 1120 |
+
<td><span class="score-badge score-${item.score}">${item.score}</span></td>
|
| 1121 |
+
<td class="whitespace-pre-wrap max-w-xs">${item.explanation || '-'}</td>
|
| 1122 |
+
<td class="whitespace-pre-wrap max-w-xs">${item.detailedExplanation || '-'}</td>
|
| 1123 |
+
<td><textarea id="${commentId}" placeholder="Commentaire..." class="profile-textarea-short w-full" onchange="saveRequirementComment('${commentId}')">${currentComment}</textarea></td>
|
| 1124 |
+
</tr>`;
|
| 1125 |
+
});
|
| 1126 |
+
tbody.innerHTML = html;
|
| 1127 |
+
filterNonConformities();
|
| 1128 |
+
}
|
| 1129 |
+
|
| 1130 |
+
function saveRequirementComment(commentId) {
|
| 1131 |
+
const textarea = document.getElementById(commentId);
|
| 1132 |
+
if (!textarea) return;
|
| 1133 |
+
const content = textarea.value.trim();
|
| 1134 |
+
if (content) comments[commentId] = [{ id: Date.now(), author: 'reviewer', content, date: new Date().toISOString() }];
|
| 1135 |
+
else delete comments[commentId];
|
| 1136 |
+
updateProgressStats();
|
| 1137 |
+
updateNonConformitiesStats();
|
| 1138 |
+
saveCurrentSession();
|
| 1139 |
+
|
| 1140 |
+
// Mettre à jour l'attribut data-has-comment pour le filtrage
|
| 1141 |
+
const row = textarea.closest('tr');
|
| 1142 |
+
if (row) {
|
| 1143 |
+
row.setAttribute('data-has-comment', content ? 'true' : 'false');
|
| 1144 |
+
}
|
| 1145 |
+
}
|
| 1146 |
+
|
| 1147 |
+
function updateElementTextContent(id, value) {
|
| 1148 |
+
const element = document.getElementById(id);
|
| 1149 |
+
if (element) element.textContent = value;
|
| 1150 |
+
else console.warn(`Element ${id} not found for text update.`);
|
| 1151 |
+
}
|
| 1152 |
+
|
| 1153 |
+
function updateProgressStats() {
|
| 1154 |
+
const totalComments = Object.keys(comments).filter(k=>comments[k]?.[0]?.content.trim()).length;
|
| 1155 |
+
const profileFields = Object.keys(companyProfileData).length;
|
| 1156 |
+
const ncNaFields = checklistData.filter(i => ['B', 'C', 'D', 'NA'].includes(i.score)).length;
|
| 1157 |
+
const totalFields = profileFields + ncNaFields;
|
| 1158 |
+
const progress = totalFields > 0 ? Math.round((totalComments / totalFields) * 100) : 0;
|
| 1159 |
+
updateElementTextContent('progressPercentage', progress + '%');
|
| 1160 |
+
updateElementTextContent('commentsCount', totalComments);
|
| 1161 |
+
return { totalComments, totalFields, progressPercentage: progress };
|
| 1162 |
+
}
|
| 1163 |
+
|
| 1164 |
+
function updateNonConformitiesStats() {
|
| 1165 |
+
if (!checklistData) return;
|
| 1166 |
+
const ncNaItems = checklistData.filter(i => ['B', 'C', 'D', 'NA'].includes(i.score));
|
| 1167 |
+
|
| 1168 |
+
// Analyse par chapitre
|
| 1169 |
+
const ncByChapter = {};
|
| 1170 |
+
checklistData.forEach(item => {
|
| 1171 |
+
if (['B', 'C', 'D'].includes(item.score)) {
|
| 1172 |
+
if (!ncByChapter[item.chapter]) ncByChapter[item.chapter] = 0;
|
| 1173 |
+
ncByChapter[item.chapter]++;
|
| 1174 |
+
}
|
| 1175 |
+
});
|
| 1176 |
+
|
| 1177 |
+
// Priorisation des NC
|
| 1178 |
+
const criticalNc = checklistData.filter(i => i.score === 'D' && !comments[`nc-${i.uuid}`]?.[0]?.content?.trim());
|
| 1179 |
+
|
| 1180 |
+
const ncComments = ncNaItems.filter(item => {
|
| 1181 |
+
const commentId = `nc-${item.uuid}`;
|
| 1182 |
+
return comments[commentId]?.[0]?.content?.trim();
|
| 1183 |
+
}).length;
|
| 1184 |
+
|
| 1185 |
+
// Mise à jour de l'UI avec les nouvelles métriques
|
| 1186 |
+
document.getElementById('ncByChapter').textContent = Object.entries(ncByChapter).map(([chap, count]) => `Ch.${chap}: ${count}`).join(' | ');
|
| 1187 |
+
document.getElementById('uncommentedCritical').textContent = criticalNc.length;
|
| 1188 |
+
|
| 1189 |
+
updateElementTextContent('totalNC', ncNaItems.length);
|
| 1190 |
+
updateElementTextContent('scoreB', checklistData.filter(i => i.score === 'B').length);
|
| 1191 |
+
updateElementTextContent('scoreC', checklistData.filter(i => i.score === 'C').length);
|
| 1192 |
+
updateElementTextContent('scoreD', checklistData.filter(i => i.score === 'D').length);
|
| 1193 |
+
updateElementTextContent('naCount', checklistData.filter(i => i.score === 'NA').length);
|
| 1194 |
+
updateElementTextContent('commentsNCCount', ncComments);
|
| 1195 |
+
updateProgressStats();
|
| 1196 |
+
}
|
| 1197 |
+
|
| 1198 |
+
function getChapterFromUUID(uuid) {
|
| 1199 |
+
const hashCode = uuid.split('').reduce((a, b) => (a = ((a << 5) - a) + b.charCodeAt(0), a & a), 0);
|
| 1200 |
+
return (Math.abs(hashCode) % 6) + 1;
|
| 1201 |
+
}
|
| 1202 |
+
|
| 1203 |
+
function getCommentForField(fieldId) {
|
| 1204 |
+
return comments[fieldId]?.[0]?.content || '';
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
+
function showError(message) {
|
| 1208 |
+
console.error('App Error:', message);
|
| 1209 |
+
if(successAlertElement) {
|
| 1210 |
+
successAlertElement.classList.remove('text-green-700', 'bg-green-100', 'dark:bg-green-200', 'dark:text-green-800');
|
| 1211 |
+
successAlertElement.classList.add('text-red-700', 'bg-red-100', 'dark:bg-red-200', 'dark:text-red-800');
|
| 1212 |
+
successAlertElement.textContent = `❌ Erreur: ${message}`;
|
| 1213 |
+
successAlertElement.classList.remove('hidden');
|
| 1214 |
+
} else {
|
| 1215 |
+
alert(`❌ Erreur: ${message}`);
|
| 1216 |
+
}
|
| 1217 |
+
}
|
| 1218 |
+
|
| 1219 |
+
function updateSessionIdInUI(coid) {
|
| 1220 |
+
document.getElementById('sessionIdTop').textContent = `COID: ${coid || '----'}`;
|
| 1221 |
+
}
|
| 1222 |
+
|
| 1223 |
+
let filterTimeout;
|
| 1224 |
+
function filterChecklist() {
|
| 1225 |
+
clearTimeout(filterTimeout);
|
| 1226 |
+
document.getElementById('checklistTableBody').classList.add('opacity-50');
|
| 1227 |
+
|
| 1228 |
+
filterTimeout = setTimeout(() => {
|
| 1229 |
+
const chapter = document.getElementById('chapterFilter')?.value;
|
| 1230 |
+
const score = document.getElementById('scoreFilter')?.value;
|
| 1231 |
+
// ... reste du code de filtrage ...
|
| 1232 |
+
|
| 1233 |
+
document.getElementById('checklistTableBody').classList.remove('opacity-50');
|
| 1234 |
+
}, 300);
|
| 1235 |
+
const explanation = document.getElementById('explanationFilter')?.value;
|
| 1236 |
+
const search = document.getElementById('searchInput')?.value.toLowerCase();
|
| 1237 |
+
const rows = document.querySelectorAll('#checklistTableBody tr');
|
| 1238 |
+
if (rows.length === 0 || (rows.length === 1 && rows[0].getElementsByTagName('td')[0].colSpan > 1) ) return;
|
| 1239 |
+
rows.forEach(row => {
|
| 1240 |
+
let show = true;
|
| 1241 |
+
if (chapter && row.dataset.chapter !== chapter) show = false;
|
| 1242 |
+
if (score && row.dataset.score !== score) show = false;
|
| 1243 |
+
if (explanation === 'empty' && row.dataset.explanationEmpty !== 'true') show = false;
|
| 1244 |
+
if (explanation === 'with' && row.dataset.explanationEmpty === 'true') show = false;
|
| 1245 |
+
if (search && !(row.textContent || row.innerText || "").toLowerCase().includes(search)) show = false;
|
| 1246 |
+
row.style.display = show ? '' : 'none';
|
| 1247 |
+
});
|
| 1248 |
+
}
|
| 1249 |
+
|
| 1250 |
+
['chapterFilter', 'scoreFilter', 'explanationFilter', 'searchInput'].forEach(id => {
|
| 1251 |
+
const el = document.getElementById(id);
|
| 1252 |
+
if(el) el.addEventListener(id === 'searchInput' ? 'keyup' : 'change', filterChecklist);
|
| 1253 |
+
});
|
| 1254 |
+
|
| 1255 |
+
function filterNonConformities() {
|
| 1256 |
+
const type = document.getElementById('ncTypeFilter')?.value;
|
| 1257 |
+
const chapter = document.getElementById('ncChapterFilter')?.value;
|
| 1258 |
+
const correction = document.getElementById('correctionFilter')?.value;
|
| 1259 |
+
const search = document.getElementById('ncSearchInput')?.value.toLowerCase();
|
| 1260 |
+
const rows = document.querySelectorAll('#nonConformitiesTableBody tr');
|
| 1261 |
+
if (rows.length === 0 || (rows.length === 1 && rows[0].getElementsByTagName('td')[0].colSpan > 1) ) return;
|
| 1262 |
+
rows.forEach(row => {
|
| 1263 |
+
let show = true;
|
| 1264 |
+
if (type) {
|
| 1265 |
+
const scores = type.split(',');
|
| 1266 |
+
if (!scores.includes(row.dataset.score)) show = false;
|
| 1267 |
+
}
|
| 1268 |
+
if (chapter && row.dataset.chapter !== chapter) show = false;
|
| 1269 |
+
if (correction === 'with' && row.dataset.hasComment !== 'true') show = false;
|
| 1270 |
+
if (correction === 'without' && row.dataset.hasComment === 'true') show = false;
|
| 1271 |
+
if (search && !(row.textContent || row.innerText || "").toLowerCase().includes(search)) show = false;
|
| 1272 |
+
row.style.display = show ? '' : 'none';
|
| 1273 |
+
});
|
| 1274 |
+
}
|
| 1275 |
+
|
| 1276 |
+
['ncTypeFilter', 'ncChapterFilter', 'correctionFilter', 'ncSearchInput'].forEach(id => {
|
| 1277 |
+
const el = document.getElementById(id);
|
| 1278 |
+
if(el) el.addEventListener(id === 'ncSearchInput' ? 'keyup' : 'change', filterNonConformities);
|
| 1279 |
+
});
|
| 1280 |
+
|
| 1281 |
+
function showAll() {
|
| 1282 |
+
document.getElementById('chapterFilter').value = '';
|
| 1283 |
+
document.getElementById('scoreFilter').value = '';
|
| 1284 |
+
document.getElementById('explanationFilter').value = 'all';
|
| 1285 |
+
document.getElementById('searchInput').value = '';
|
| 1286 |
+
filterChecklist();
|
| 1287 |
+
}
|
| 1288 |
+
|
| 1289 |
+
function createNewSession() {
|
| 1290 |
+
if (auditData && !confirm("⚠️ Créer un nouveau dossier ? Le travail non sauvegardé sera perdu.")) return;
|
| 1291 |
+
auditData = null; checklistData = []; companyProfileData = {}; comments = {}; requirementNumberMapping = {};
|
| 1292 |
+
currentSession = { id: null, name: 'Nouveau Dossier', created: new Date(), lastModified: new Date(), data: null };
|
| 1293 |
+
resetToUploadState();
|
| 1294 |
+
['totalRequirements', 'conformCount', 'nonConformCount', 'overallScore', 'progressPercentage', 'commentsCount']
|
| 1295 |
+
.forEach(id => updateElementTextContent(id, id === 'overallScore' || id === 'progressPercentage' ? '0%' : '0'));
|
| 1296 |
+
if(successAlertElement) successAlertElement.textContent = ''; successAlertElement.classList.add('hidden');
|
| 1297 |
+
|
| 1298 |
+
renderCompanyProfile(); renderChecklistTable(); renderNonConformitiesTable();
|
| 1299 |
+
updateNonConformitiesStats();
|
| 1300 |
+
switchToolTab('profil');
|
| 1301 |
+
alert('✅ Nouveau dossier prêt. Chargez un fichier .ifs ou .ifsr.');
|
| 1302 |
+
}
|
| 1303 |
+
|
| 1304 |
+
function saveCurrentSession(showAlert = false) {
|
| 1305 |
+
if (!currentSession.id && showAlert) {
|
| 1306 |
+
alert('💡 Utilisez "Sauvegarder IFSR" pour télécharger un fichier.');
|
| 1307 |
+
}
|
| 1308 |
+
console.log("saveCurrentSession (auto-save simulation).");
|
| 1309 |
+
}
|
| 1310 |
+
|
| 1311 |
+
async function exportForAuditor() {
|
| 1312 |
+
// Ajout d'un indicateur de chargement
|
| 1313 |
+
const btn = document.getElementById('exportExcelBtn');
|
| 1314 |
+
const originalHtml = btn.innerHTML;
|
| 1315 |
+
btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Génération en cours...';
|
| 1316 |
+
btn.disabled = true;
|
| 1317 |
+
|
| 1318 |
+
await new Promise(resolve => setTimeout(resolve, 100)); // Petite pause pour l'UI
|
| 1319 |
+
if (!auditData) { alert('Aucune donnée à exporter.'); return; }
|
| 1320 |
+
const progressStats = updateProgressStats(); const completionRate = progressStats.progressPercentage;
|
| 1321 |
+
if (completionRate < 10 && !confirm(`Review peu avancée (${completionRate.toFixed(0)}%). Continuer l'export ?`)) return;
|
| 1322 |
+
|
| 1323 |
+
const wb = XLSX.utils.book_new();
|
| 1324 |
+
const headerStyle = { font: { bold: true, sz: 12, color: { rgb: "FFFFFFFF"} }, fill: { fgColor: { rgb: "FF4F81BD" } }, alignment: { vertical: "center", horizontal: "center", wrapText: true } };
|
| 1325 |
+
const dataStyle = { alignment: { vertical: "top", wrapText: true } };
|
| 1326 |
+
const categoryStyle = { font: { bold: true, sz: 11 }, fill: { fgColor: { rgb: "FFE0E0E0" } }, alignment: { vertical: "top", wrapText: true } };
|
| 1327 |
+
|
| 1328 |
+
function applyStylesToSheet(ws, hasCatHeaders = false) {
|
| 1329 |
+
if (!ws || !ws['!ref']) return;
|
| 1330 |
+
const range = XLSX.utils.decode_range(ws['!ref']);
|
| 1331 |
+
for (let C_ = range.s.c; C_ <= range.e.c; ++C_) {
|
| 1332 |
+
const cell = ws[XLSX.utils.encode_cell({r: range.s.r, c: C_})];
|
| 1333 |
+
if (cell) cell.s = headerStyle;
|
| 1334 |
+
}
|
| 1335 |
+
for (let R_ = range.s.r + 1; R_ <= range.e.r; ++R_) {
|
| 1336 |
+
for (let C_ = range.s.c; C_ <= range.e.c; ++C_) {
|
| 1337 |
+
const cell_address = XLSX.utils.encode_cell({r:R_, c:C_});
|
| 1338 |
+
if (!ws[cell_address]) ws[cell_address] = { t:'s', v:'' };
|
| 1339 |
+
ws[cell_address].s = dataStyle;
|
| 1340 |
+
if (hasCatHeaders && C_ === range.s.c && ws[cell_address].v && String(ws[cell_address].v).startsWith("CATÉGORIE :")) {
|
| 1341 |
+
for (let CC_ = range.s.c; CC_ <= range.e.c; ++CC_) {
|
| 1342 |
+
const catCellAddr = XLSX.utils.encode_cell({r:R_, c:CC_});
|
| 1343 |
+
if (!ws[catCellAddr]) ws[catCellAddr] = {t:'s', v:''};
|
| 1344 |
+
ws[catCellAddr].s = categoryStyle;
|
| 1345 |
+
}
|
| 1346 |
+
}
|
| 1347 |
+
}
|
| 1348 |
+
}
|
| 1349 |
+
}
|
| 1350 |
+
|
| 1351 |
+
// Sheet 1: RÉSUMÉ AUDIT
|
| 1352 |
+
const summary = generateExecutiveSummary();
|
| 1353 |
+
const summarySheetData = [
|
| 1354 |
+
{ 'Information Clé': 'Entreprise auditée', 'Valeur': summary['Entreprise'] },
|
| 1355 |
+
{ 'Information Clé': 'N° COID', 'Valeur': summary['COID'] },
|
| 1356 |
+
{ 'Information Clé': 'Score global IFS', 'Valeur': summary['Score global'] },
|
| 1357 |
+
{ 'Information Clé': 'Total exigences', 'Valeur': summary['Total exigences'] },
|
| 1358 |
+
{ 'Information Clé': 'Conformités (A)', 'Valeur': summary['Conformités (A)'] },
|
| 1359 |
+
{ 'Information Clé': 'Score B', 'Valeur': summary['Score B'] },
|
| 1360 |
+
{ 'Information Clé': 'Score C', 'Valeur': summary['Score C'] },
|
| 1361 |
+
{ 'Information Clé': 'Score D', 'Valeur': summary['Score D'] },
|
| 1362 |
+
{ 'Information Clé': 'Total commentaires reviewer', 'Valeur': progressStats.totalComments },
|
| 1363 |
+
];
|
| 1364 |
+
const wsSummary = XLSX.utils.json_to_sheet(summarySheetData);
|
| 1365 |
+
wsSummary['!cols'] = [{ width: 40 }, { width: 30 }];
|
| 1366 |
+
applyStylesToSheet(wsSummary);
|
| 1367 |
+
XLSX.utils.book_append_sheet(wb, wsSummary, "RÉSUMÉ AUDIT");
|
| 1368 |
+
|
| 1369 |
+
// Sheet 2: PROFIL ENTREPRISE
|
| 1370 |
+
const profileCategories = {
|
| 1371 |
+
'Informations générales': ['Nom du site à auditer', 'N° COID du portail', 'Code GLN'],
|
| 1372 |
+
'Adresse du site': ['Rue', 'Code postal', 'Nom de la ville', 'Pays', 'Téléphone', 'Email', 'Latitude', 'Longitude'],
|
| 1373 |
+
'Siège social': ['Nom du siège social', 'Rue (siège social)', 'Nom de la ville (siège social)', 'Code postal (siège social)', 'Pays (siège social)', 'Téléphone (siège social)'],
|
| 1374 |
+
'Informations techniques': ["Surface couverte de l'entreprise (m²)", 'Nombre de bâtiments', 'Nombre de lignes de production', "Nombre d'étages", "Nombre maximum d'employés dans l'année, au pic de production", 'Commentaires employés'],
|
| 1375 |
+
'Structure organisationnelle': ['Structures décentralisées', 'Fonctions centralisées'],
|
| 1376 |
+
'Langues': ['Langue parlée et écrite sur le site', 'Langue du système qualité'],
|
| 1377 |
+
"Périmètres d'audit": ['Audit scope EN', "Périmètre de l'audit FR", 'Process et activités'],
|
| 1378 |
+
'Activités spécifiques': ["Activité saisonnière ? (O/N)", "Une partie du procédé de fabrication est-elle sous traitée? (OUI/NON)", 'Si oui lister les procédés sous-traités', "Avez-vous des produits totalement sous-traités? (OUI/NON)", 'Si oui, lister les produits totalement sous-traités', "Avez-vous des produits de négoce? (OUI/NON)", 'Si oui, lister les produits de négoce'],
|
| 1379 |
+
'Exclusions': ["Produits à exclure du champ d'audit (OUI/NON)", 'Préciser les produits à exclure']
|
| 1380 |
+
};
|
| 1381 |
+
const profileSheetData = [];
|
| 1382 |
+
Object.entries(profileCategories).forEach(([catName, fields]) => {
|
| 1383 |
+
profileSheetData.push({ 'Critère': `CATÉGORIE : ${catName.toUpperCase()}`, 'Valeur Déclarée': '', 'Commentaires Reviewer': '' });
|
| 1384 |
+
fields.forEach(field => {
|
| 1385 |
+
if (companyProfileData.hasOwnProperty(field)) {
|
| 1386 |
+
profileSheetData.push({
|
| 1387 |
+
'Critère': field,
|
| 1388 |
+
'Valeur Déclarée': companyProfileData[field] || 'N/R',
|
| 1389 |
+
'Commentaires Reviewer': getCommentForField(`profile-${field.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase()}`) || '-'
|
| 1390 |
+
});
|
| 1391 |
+
}
|
| 1392 |
+
});
|
| 1393 |
+
profileSheetData.push({ 'Critère': '', 'Valeur Déclarée': '', 'Commentaires Reviewer': '' });
|
| 1394 |
+
});
|
| 1395 |
+
const wsProfile = XLSX.utils.json_to_sheet(profileSheetData);
|
| 1396 |
+
wsProfile['!cols'] = [{ width: 40 }, { width: 40 }, { width: 60 }];
|
| 1397 |
+
applyStylesToSheet(wsProfile, true);
|
| 1398 |
+
XLSX.utils.book_append_sheet(wb, wsProfile, "PROFIL ENTREPRISE");
|
| 1399 |
+
|
| 1400 |
+
// Sheet 3: NON-CONFORMITÉS & NA (sans colonne Action requise)
|
| 1401 |
+
const ncSheetData = checklistData
|
| 1402 |
+
.filter(item => ['B', 'C', 'D', 'NA'].includes(item.score))
|
| 1403 |
+
.map(item => ({
|
| 1404 |
+
'Exigence': item.requirementNumber,
|
| 1405 |
+
'Score Auditeur': item.score,
|
| 1406 |
+
'Explication Auditeur': item.explanation || '-',
|
| 1407 |
+
'Détail Auditeur': item.detailedExplanation || '-',
|
| 1408 |
+
'Commentaires Reviewer': getCommentForField(`nc-${item.uuid}`) || '-'
|
| 1409 |
+
}));
|
| 1410 |
+
const wsNC = XLSX.utils.json_to_sheet(ncSheetData);
|
| 1411 |
+
wsNC['!cols'] = [{ width: 15 }, { width: 15 }, { width: 45 }, { width: 45 }, { width: 60 }];
|
| 1412 |
+
applyStylesToSheet(wsNC);
|
| 1413 |
+
XLSX.utils.book_append_sheet(wb, wsNC, "NON-CONFORMITÉS & NA");
|
| 1414 |
+
|
| 1415 |
+
// Sheet 4: CHECKLIST COMPLÈTE (sans colonne Action requise)
|
| 1416 |
+
const checklistSheetData = checklistData.map(item => ({
|
| 1417 |
+
'Exigence': item.requirementNumber,
|
| 1418 |
+
'Chap.': item.chapter,
|
| 1419 |
+
'Score Auditeur': item.score,
|
| 1420 |
+
'Explication Auditeur': item.explanation || '-',
|
| 1421 |
+
'Détail Auditeur': item.detailedExplanation || '-',
|
| 1422 |
+
'Commentaires Reviewer': getCommentForField(`req-${item.uuid}`) || (['B','C','D','NA'].includes(item.score) ? getCommentForField(`nc-${item.uuid}`) : '-')
|
| 1423 |
+
}));
|
| 1424 |
+
const wsChecklist = XLSX.utils.json_to_sheet(checklistSheetData);
|
| 1425 |
+
wsChecklist['!cols'] = [{ width: 15 }, { width: 10 }, { width: 15 }, { width: 40 }, { width: 40 }, { width: 60 }];
|
| 1426 |
+
applyStylesToSheet(wsChecklist);
|
| 1427 |
+
XLSX.utils.book_append_sheet(wb, wsChecklist, "CHECKLIST COMPLÈTE");
|
| 1428 |
+
|
| 1429 |
+
const companyName = companyProfileData['Nom du site à auditer'] || 'audit';
|
| 1430 |
+
const coid = companyProfileData['N° COID du portail'] || 'COID';
|
| 1431 |
+
const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
| 1432 |
+
const fileName = `RAPPORT_REVIEW_IFS_${coid}_${companyName.replace(/[^a-zA-Z0-9]/g, '_')}_${timestamp}.xlsx`;
|
| 1433 |
+
XLSX.writeFile(wb, fileName);
|
| 1434 |
+
alert(`Rapport pour auditeur généré : ${fileName}`);
|
| 1435 |
+
}
|
| 1436 |
+
|
| 1437 |
+
function exportAllData() {
|
| 1438 |
+
if (!auditData) { alert('Aucune donnée pour l\'export.'); return; }
|
| 1439 |
+
const wb = XLSX.utils.book_new();
|
| 1440 |
+
|
| 1441 |
+
// Sheet 1: Profil Entreprise
|
| 1442 |
+
const profileSheetData = [];
|
| 1443 |
+
const profileCategories = {
|
| 1444 |
+
'Informations générales': ['Nom du site à auditer', 'N° COID du portail', 'Code GLN'],
|
| 1445 |
+
'Adresse du site': ['Rue', 'Code postal', 'Nom de la ville', 'Pays', 'Téléphone', 'Email', 'Latitude', 'Longitude'],
|
| 1446 |
+
'Siège social': ['Nom du siège social', 'Rue (siège social)', 'Nom de la ville (siège social)', 'Code postal (siège social)', 'Pays (siège social)', 'Téléphone (siège social)'],
|
| 1447 |
+
'Informations techniques': ["Surface couverte de l'entreprise (m²)", 'Nombre de bâtiments', 'Nombre de lignes de production', "Nombre d'étages", "Nombre maximum d'employés dans l'année, au pic de production", 'Commentaires employés'],
|
| 1448 |
+
'Structure organisationnelle': ['Structures décentralisées', 'Fonctions centralisées'],
|
| 1449 |
+
'Langues': ['Langue parlée et écrite sur le site', 'Langue du système qualité'],
|
| 1450 |
+
"Périmètres d'audit": ['Audit scope EN', "Périmètre de l'audit FR", 'Process et activités'],
|
| 1451 |
+
'Activités spécifiques': ["Activité saisonnière ? (O/N)", "Une partie du procédé de fabrication est-elle sous traitée? (OUI/NON)", 'Si oui lister les procédés sous-traités', "Avez-vous des produits totalement sous-traités? (OUI/NON)", 'Si oui, lister les produits totalement sous-traités', "Avez-vous des produits de négoce? (OUI/NON)", 'Si oui, lister les produits de négoce'],
|
| 1452 |
+
'Exclusions': ["Produits à exclure du champ d'audit (OUI/NON)", 'Préciser les produits à exclure']
|
| 1453 |
+
};
|
| 1454 |
+
Object.entries(profileCategories).forEach(([catName, fields]) => {
|
| 1455 |
+
profileSheetData.push({ 'Catégorie': `=== ${catName.toUpperCase()} ===`, 'Information': '', 'Valeur': '', 'Commentaire Reviewer': '' });
|
| 1456 |
+
fields.forEach(field => {
|
| 1457 |
+
if (companyProfileData.hasOwnProperty(field)) {
|
| 1458 |
+
profileSheetData.push({
|
| 1459 |
+
'Catégorie': catName, 'Information': field,
|
| 1460 |
+
'Valeur': companyProfileData[field] || 'N/A',
|
| 1461 |
+
'Commentaire Reviewer': getCommentForField(`profile-${field.replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase()}`)
|
| 1462 |
+
});
|
| 1463 |
+
}
|
| 1464 |
+
});
|
| 1465 |
+
profileSheetData.push({ 'Catégorie': '', 'Information': '', 'Valeur': '', 'Commentaire Reviewer': '' });
|
| 1466 |
+
});
|
| 1467 |
+
const wsProfile = XLSX.utils.json_to_sheet(profileSheetData);
|
| 1468 |
+
wsProfile['!cols'] = [{wpx:150},{wpx:250},{wpx:200},{wpx:300}];
|
| 1469 |
+
XLSX.utils.book_append_sheet(wb, wsProfile, "Profil Entreprise Complet");
|
| 1470 |
+
|
| 1471 |
+
// Sheet 2: Checklist Complète (sans colonne Action requise)
|
| 1472 |
+
const checklistRaw = checklistData.map(item => ({
|
| 1473 |
+
'UUID': item.uuid,
|
| 1474 |
+
'N° Exigence': item.requirementNumber,
|
| 1475 |
+
'Chapitre': item.chapter,
|
| 1476 |
+
'Score': item.score,
|
| 1477 |
+
'Explication': item.explanation || '',
|
| 1478 |
+
'Détail': item.detailedExplanation || '',
|
| 1479 |
+
'Commentaire Reviewer': getCommentForField(`req-${item.uuid}`) || (['B','C','D','NA'].includes(item.score) ? getCommentForField(`nc-${item.uuid}`) : '')
|
| 1480 |
+
}));
|
| 1481 |
+
const wsChecklistRaw = XLSX.utils.json_to_sheet(checklistRaw);
|
| 1482 |
+
wsChecklistRaw['!cols'] = [{wpx:250},{wpx:100},{wpx:50},{wpx:50},{wpx:300},{wpx:300},{wpx:300}];
|
| 1483 |
+
XLSX.utils.book_append_sheet(wb, wsChecklistRaw, "Checklist Brute");
|
| 1484 |
+
|
| 1485 |
+
// Sheet 3: Tous les Commentaires
|
| 1486 |
+
const allCommentsSheet = [];
|
| 1487 |
+
for (const key in comments) {
|
| 1488 |
+
if (comments[key] && comments[key].length > 0) {
|
| 1489 |
+
comments[key].forEach(comment => {
|
| 1490 |
+
allCommentsSheet.push({
|
| 1491 |
+
'ID Commentaire': key,
|
| 1492 |
+
'Auteur': comment.author,
|
| 1493 |
+
'Date': new Date(comment.date).toLocaleString('fr-FR'),
|
| 1494 |
+
'Contenu': comment.content
|
| 1495 |
+
});
|
| 1496 |
+
});
|
| 1497 |
+
}
|
| 1498 |
+
}
|
| 1499 |
+
const wsComments = XLSX.utils.json_to_sheet(allCommentsSheet);
|
| 1500 |
+
wsComments['!cols'] = [{wpx:200},{wpx:100},{wpx:150},{wpx:500}];
|
| 1501 |
+
XLSX.utils.book_append_sheet(wb, wsComments, "Tous Commentaires");
|
| 1502 |
+
|
| 1503 |
+
const companyName = companyProfileData['Nom du site à auditer'] || 'audit';
|
| 1504 |
+
const coid = companyProfileData['N° COID du portail'] || 'COID';
|
| 1505 |
+
const timestamp = new Date().toISOString().split('T')[0].replace(/-/g, '');
|
| 1506 |
+
XLSX.writeFile(wb, `Export_Brut_Complet_${coid}_${companyName.replace(/[^a-zA-Z0-9]/g, '_')}_${timestamp}.xlsx`);
|
| 1507 |
+
alert("Export brut complet généré.");
|
| 1508 |
+
}
|
| 1509 |
+
|
| 1510 |
+
function exportChecklistToExcel() {
|
| 1511 |
+
if (!checklistData || checklistData.length === 0) {
|
| 1512 |
+
alert('Aucune donnée de checklist.');
|
| 1513 |
+
return;
|
| 1514 |
+
}
|
| 1515 |
+
const data = checklistData.map(item => ({
|
| 1516 |
+
'N° Exigence': item.requirementNumber,
|
| 1517 |
+
'Chapitre': item.chapter,
|
| 1518 |
+
'Score': item.score,
|
| 1519 |
+
'Explication': item.explanation || '',
|
| 1520 |
+
'Détail': item.detailedExplanation || '',
|
| 1521 |
+
'Commentaire Reviewer': getCommentForField(`req-${item.uuid}`)
|
| 1522 |
+
}));
|
| 1523 |
+
const ws = XLSX.utils.json_to_sheet(data);
|
| 1524 |
+
ws['!cols'] = [{wpx:100},{wpx:60},{wpx:50},{wpx:300},{wpx:300},{wpx:250}];
|
| 1525 |
+
const wb = XLSX.utils.book_new();
|
| 1526 |
+
XLSX.utils.book_append_sheet(wb, ws, "Checklist IFS");
|
| 1527 |
+
const companyName = companyProfileData['Nom du site à auditer'] || 'audit';
|
| 1528 |
+
const coid = companyProfileData['N° COID du portail'] || 'COID';
|
| 1529 |
+
const ts = new Date().toISOString().split('T')[0].replace(/-/g,'');
|
| 1530 |
+
XLSX.writeFile(wb, `Checklist_Export_${coid}_${companyName.replace(/[^a-zA-Z0-9]/g,'_')}_${ts}.xlsx`);
|
| 1531 |
+
alert("Checklist exportée.");
|
| 1532 |
+
}
|
| 1533 |
+
|
| 1534 |
+
function exportNonConformities() {
|
| 1535 |
+
if (!checklistData) {
|
| 1536 |
+
alert('Aucune donnée.');
|
| 1537 |
+
return;
|
| 1538 |
+
}
|
| 1539 |
+
const ncItems = checklistData.filter(item => ['B', 'C', 'D'].includes(item.score));
|
| 1540 |
+
if (ncItems.length === 0) {
|
| 1541 |
+
alert('Aucune NC (B,C,D) pour plan d\'action.');
|
| 1542 |
+
return;
|
| 1543 |
+
}
|
| 1544 |
+
const data = ncItems.map((item, idx) => ({
|
| 1545 |
+
'N°': idx + 1,
|
| 1546 |
+
'Exigence': item.requirementNumber,
|
| 1547 |
+
'Score': item.score,
|
| 1548 |
+
'Explication': item.explanation || '',
|
| 1549 |
+
'Détail': item.detailedExplanation || '',
|
| 1550 |
+
'Commentaire Reviewer': getCommentForField(`nc-${item.uuid}`),
|
| 1551 |
+
'Délai Suggéré': item.score === 'D' ? '7 jours' : item.score === 'C' ? '30 jours' : '90 jours',
|
| 1552 |
+
'Responsable': 'À définir',
|
| 1553 |
+
'Statut': 'Ouvert'
|
| 1554 |
+
}));
|
| 1555 |
+
const ws = XLSX.utils.json_to_sheet(data);
|
| 1556 |
+
ws['!cols'] = [{wpx:30},{wpx:100},{wpx:50},{wpx:250},{wpx:250},{wpx:200},{wpx:100},{wpx:100},{wpx:100}];
|
| 1557 |
+
const wb = XLSX.utils.book_new();
|
| 1558 |
+
XLSX.utils.book_append_sheet(wb, ws, "Plan Actions NC");
|
| 1559 |
+
const companyName = companyProfileData['Nom du site à auditer'] || 'audit';
|
| 1560 |
+
const coid = companyProfileData['N° COID du portail'] || 'COID';
|
| 1561 |
+
const ts = new Date().toISOString().split('T')[0].replace(/-/g,'');
|
| 1562 |
+
XLSX.writeFile(wb, `Plan_Actions_NC_${coid}_${companyName.replace(/[^a-zA-Z0-9]/g,'_')}_${ts}.xlsx`);
|
| 1563 |
+
alert("Plan d'actions NC exporté.");
|
| 1564 |
+
}
|
| 1565 |
+
|
| 1566 |
+
function generateExecutiveSummary() {
|
| 1567 |
+
if (!checklistData || !auditData) return {};
|
| 1568 |
+
const totalReq = checklistData.length;
|
| 1569 |
+
const conform = checklistData.filter(i => i.score === 'A').length;
|
| 1570 |
+
const bCount = checklistData.filter(i => i.score === 'B').length;
|
| 1571 |
+
const cCount = checklistData.filter(i => i.score === 'C').length;
|
| 1572 |
+
const dCount = checklistData.filter(i => i.score === 'D').length;
|
| 1573 |
+
const score = auditData?.data?.modules?.food_8?.result?.overall?.percent || 0;
|
| 1574 |
+
return {
|
| 1575 |
+
'Entreprise': companyProfileData['Nom du site à auditer'] || 'N/A',
|
| 1576 |
+
'COID': companyProfileData['N° COID du portail'] || 'N/A',
|
| 1577 |
+
'Ville': companyProfileData['Nom de la ville'] || 'N/A',
|
| 1578 |
+
'Pays': companyProfileData['Pays'] || 'N/A',
|
| 1579 |
+
'Score global': totalReq > 0 ? score.toFixed(1) + '%' : 'N/A',
|
| 1580 |
+
'Total exigences': totalReq,
|
| 1581 |
+
'Conformités (A)': conform,
|
| 1582 |
+
'Score B': bCount,
|
| 1583 |
+
'Score C': cCount,
|
| 1584 |
+
'Score D': dCount,
|
| 1585 |
+
'Taux de conformité': totalReq > 0 ? ((conform / totalReq) * 100).toFixed(1) + '%' : 'N/A'
|
| 1586 |
+
};
|
| 1587 |
+
}
|
| 1588 |
+
|
| 1589 |
+
function debugAndFix() {
|
| 1590 |
+
console.log('=== DEBUG AND FIX ===');
|
| 1591 |
+
if (auditData) {
|
| 1592 |
+
renderCompanyProfile();
|
| 1593 |
+
renderChecklistTable();
|
| 1594 |
+
renderNonConformitiesTable();
|
| 1595 |
+
updateNonConformitiesStats();
|
| 1596 |
+
updateProgressStats();
|
| 1597 |
+
}
|
| 1598 |
+
switchToolTab('profil');
|
| 1599 |
+
alert('Debug & Fix routine. Check console.');
|
| 1600 |
+
}
|
| 1601 |
+
|
| 1602 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 1603 |
+
console.log('DOM Loaded. Initializing IFS Reviewer Tool (New UI).');
|
| 1604 |
+
setDefaultTab();
|
| 1605 |
+
resetToUploadState();
|
| 1606 |
+
renderCompanyProfile();
|
| 1607 |
+
renderChecklistTable();
|
| 1608 |
+
renderNonConformitiesTable();
|
| 1609 |
+
updateNonConformitiesStats();
|
| 1610 |
+
updateProgressStats();
|
| 1611 |
+
initColumnResize();
|
| 1612 |
+
console.log('Application initialized with new UI.');
|
| 1613 |
+
});
|
| 1614 |
+
|
| 1615 |
+
</script>
|
| 1616 |
+
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MMOON/ifsneoreviewer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
| 1617 |
</html>
|
prompts.txt
ADDED
|
File without changes
|