소스 검색

implement questionnaire customisation

tripeur 4 년 전
부모
커밋
512a9b002e

+ 257 - 159
package-lock.json

@@ -95,9 +95,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -339,6 +339,21 @@
       "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==",
       "dev": true
     },
+    "@types/lodash-es": {
+      "version": "4.17.4",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.4.tgz",
+      "integrity": "sha512-BBz79DCJbD2CVYZH67MBeHZRX++HF+5p8Mo5MzjZi64Wac39S3diedJYHZtScbRVf4DjZyN6LzA0SB0zy+HSSQ==",
+      "dev": true,
+      "requires": {
+        "@types/lodash": "*"
+      }
+    },
+    "@types/marked": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.3.tgz",
+      "integrity": "sha512-lbhSN1rht/tQ+dSWxawCzGgTfxe9DB31iLgiT1ZVT5lshpam/nyOA1m3tKHRoNPctB2ukSL22JZI5Fr+WI/zYg==",
+      "dev": true
+    },
     "@types/materialize-css": {
       "version": "1.0.9",
       "resolved": "https://registry.npmjs.org/@types/materialize-css/-/materialize-css-1.0.9.tgz",
@@ -1333,9 +1348,9 @@
           }
         },
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -1976,9 +1991,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -3467,9 +3482,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -3516,9 +3531,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -3647,9 +3662,9 @@
           }
         },
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -3719,9 +3734,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -3768,9 +3783,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -4276,12 +4291,20 @@
       "dev": true
     },
     "domhandler": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
-      "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz",
+      "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==",
       "dev": true,
       "requires": {
-        "domelementtype": "1"
+        "domelementtype": "^2.2.0"
+      },
+      "dependencies": {
+        "domelementtype": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
+          "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
+          "dev": true
+        }
       }
     },
     "domutils": {
@@ -6072,34 +6095,43 @@
       }
     },
     "htmlparser2": {
-      "version": "3.10.1",
-      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
-      "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
+      "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
       "dev": true,
       "requires": {
-        "domelementtype": "^1.3.1",
-        "domhandler": "^2.3.0",
-        "domutils": "^1.5.1",
-        "entities": "^1.1.1",
-        "inherits": "^2.0.1",
-        "readable-stream": "^3.1.1"
+        "domelementtype": "^2.0.1",
+        "domhandler": "^4.0.0",
+        "domutils": "^2.5.2",
+        "entities": "^2.0.0"
       },
       "dependencies": {
-        "entities": {
-          "version": "1.1.2",
-          "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
-          "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
+        "dom-serializer": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
+          "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+          "dev": true,
+          "requires": {
+            "domelementtype": "^2.0.1",
+            "domhandler": "^4.2.0",
+            "entities": "^2.0.0"
+          }
+        },
+        "domelementtype": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
+          "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
           "dev": true
         },
-        "readable-stream": {
-          "version": "3.6.0",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
-          "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+        "domutils": {
+          "version": "2.7.0",
+          "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
+          "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
           "dev": true,
           "requires": {
-            "inherits": "^2.0.3",
-            "string_decoder": "^1.1.1",
-            "util-deprecate": "^1.0.1"
+            "dom-serializer": "^1.0.1",
+            "domelementtype": "^2.2.0",
+            "domhandler": "^4.2.0"
           }
         }
       }
@@ -6254,9 +6286,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -6913,7 +6945,7 @@
       "dev": true
     },
     "jc-timeline": {
-      "version": "git+http://gitlab.jaquin.fr/clovis/jc-timeline.git#f957903ec989ec620726571e18b74f84ebbe727f",
+      "version": "git+http://gitlab.jaquin.fr/clovis/jc-timeline.git#c45e5a9aa49e073855420055c7204d5f9cd718b7",
       "from": "git+http://gitlab.jaquin.fr/clovis/jc-timeline.git",
       "requires": {
         "dayjs": "^1.10.4",
@@ -7184,6 +7216,11 @@
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
     },
+    "lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
     "lodash.camelcase": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@@ -7317,6 +7354,11 @@
         "object-visit": "^1.0.0"
       }
     },
+    "marked": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
+      "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA=="
+    },
     "md5.js": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -8653,9 +8695,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -8694,9 +8736,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -8738,9 +8780,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -8781,9 +8823,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -8818,9 +8860,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -8855,9 +8897,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -8892,9 +8934,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -8982,9 +9024,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9022,9 +9064,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9070,9 +9112,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9119,9 +9161,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9165,9 +9207,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9213,9 +9255,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9259,9 +9301,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9366,9 +9408,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9406,9 +9448,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9444,9 +9486,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9482,9 +9524,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9519,9 +9561,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9558,9 +9600,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9604,9 +9646,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9650,9 +9692,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9695,9 +9737,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9740,9 +9782,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9785,9 +9827,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9831,9 +9873,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9875,9 +9917,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9920,9 +9962,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -9966,9 +10008,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -10006,9 +10048,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -10063,9 +10105,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -10108,9 +10150,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",
@@ -10533,16 +10575,16 @@
       "dev": true
     },
     "renderkid": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.5.tgz",
-      "integrity": "sha512-ccqoLg+HLOHq1vdfYNm4TBeaCDIi1FLt3wGojTDSvdewUv65oTmI3cnT2E4hRjl1gzKZIPK+KZrXzlUYKnR+vQ==",
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz",
+      "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==",
       "dev": true,
       "requires": {
-        "css-select": "^2.0.2",
-        "dom-converter": "^0.2",
-        "htmlparser2": "^3.10.1",
-        "lodash": "^4.17.20",
-        "strip-ansi": "^3.0.0"
+        "css-select": "^4.1.3",
+        "dom-converter": "^0.2.0",
+        "htmlparser2": "^6.1.0",
+        "lodash": "^4.17.21",
+        "strip-ansi": "^3.0.1"
       },
       "dependencies": {
         "ansi-regex": {
@@ -10551,6 +10593,62 @@
           "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
           "dev": true
         },
+        "css-select": {
+          "version": "4.1.3",
+          "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz",
+          "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==",
+          "dev": true,
+          "requires": {
+            "boolbase": "^1.0.0",
+            "css-what": "^5.0.0",
+            "domhandler": "^4.2.0",
+            "domutils": "^2.6.0",
+            "nth-check": "^2.0.0"
+          }
+        },
+        "css-what": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz",
+          "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==",
+          "dev": true
+        },
+        "dom-serializer": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz",
+          "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==",
+          "dev": true,
+          "requires": {
+            "domelementtype": "^2.0.1",
+            "domhandler": "^4.2.0",
+            "entities": "^2.0.0"
+          }
+        },
+        "domelementtype": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
+          "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
+          "dev": true
+        },
+        "domutils": {
+          "version": "2.7.0",
+          "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz",
+          "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==",
+          "dev": true,
+          "requires": {
+            "dom-serializer": "^1.0.1",
+            "domelementtype": "^2.2.0",
+            "domhandler": "^4.2.0"
+          }
+        },
+        "nth-check": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
+          "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
+          "dev": true,
+          "requires": {
+            "boolbase": "^1.0.0"
+          }
+        },
         "strip-ansi": {
           "version": "3.0.1",
           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@@ -11870,9 +11968,9 @@
       },
       "dependencies": {
         "postcss": {
-          "version": "7.0.35",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
-          "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
+          "version": "7.0.36",
+          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz",
+          "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==",
           "dev": true,
           "requires": {
             "chalk": "^2.4.2",

+ 4 - 0
package.json

@@ -14,6 +14,8 @@
     "fuse.js": "^6.4.6",
     "jc-timeline": "git+http://gitlab.jaquin.fr/clovis/jc-timeline.git",
     "lodash": "^4.17.21",
+    "lodash-es": "^4.17.21",
+    "marked": "^2.1.3",
     "uuid": "^8.3.2",
     "vue": "^3.0.0",
     "vue-class-component": "^8.0.0-0",
@@ -22,6 +24,8 @@
   },
   "devDependencies": {
     "@types/lodash": "^4.14.168",
+    "@types/lodash-es": "^4.17.4",
+    "@types/marked": "^2.0.3",
     "@types/materialize-css": "^1.0.9",
     "@types/uuid": "^8.3.0",
     "@typescript-eslint/eslint-plugin": "^4.18.0",

+ 17 - 113
src/Inscription.vue

@@ -4,50 +4,14 @@
     <dots v-if="status == 'Loading'" />
     <h2 v-if="status.startsWith('Error')">{{ status }}</h2>
     <div v-if="status == 'Loaded'" class="inscription-container">
-      <h2>Formulaire d'inscription en tant que bénévole pour {{ evtName }}</h2>
-      <div class="persona">
-        <styled-input class="s6" label="Prénom" id="last_name" type="text" v-model="name" />
-        <styled-input class="s6" label="Nom" id="family_name" type="text" v-model="surname" />
-        <styled-input class="s6" label="Email" id="email" type="email" v-model="email" />
-        <styled-input class="s6" label="Téléphone" id="phone" type="text" v-model="phone" />
-        <styled-input label="Commentaire" id="comment" type="textarea" v-model="comment" />
-      </div>
-      <div>
-        <h3>Fanfares</h3>
-        <check-box
-          v-for="f in fanfares"
-          :help="f.description"
-          :id="f.id"
-          :label="f.name"
-          :key="f.id"
-          @input="toggle($event, f.id)"
-        />
-      </div>
-      <div>
-        <h3>Preference</h3>
-        <check-box
-          v-for="f in preferences"
-          :help="f.description"
-          :id="f.id"
-          :label="f.name"
-          :key="f.id"
-          @input="toggle($event, f.id)"
-        />
-      </div>
-      <div>
-        <h3>Competences & contrainte</h3>
-        <check-box
-          v-for="f in contraintes"
-          :help="f.description"
-          :id="f.id"
-          :label="f.name"
-          :key="f.id"
-          @input="toggle($event, f.id)"
-        />
-      </div>
-      <button class="btn success small" @click="saveBenevole">
-        <i class="material-icons">send</i>S'inscrire
-      </button>
+      <questionnaire
+        :competences="competences"
+        :introduction="intrtoduction"
+        :questions="questions"
+        :uuid="uuid"
+        :evtName="evtName"
+        :emailList="emailList"
+      />
     </div>
   </div>
   <c-footer />
@@ -58,32 +22,26 @@ import { defineComponent } from "vue";
 import { validate as validateUuid } from "uuid";
 import cHeader from "@/components/Header.vue";
 import cFooter from "@/components/Footer.vue";
-import styledInput from "@/components/input.vue";
-import checkBox from "@/components/checkBox.vue";
-
+import Questionnaire from "@/components/Questionaire.vue";
 import dots from "@/components/Dots.vue";
 
 import { StateJSON } from "@/store/State";
 import Benevole, { BenevoleJSON } from "@/models/Benevole";
-import Toast from "./utils/Toast";
-import Competence, { ICompetence } from "@/models/Competence";
+import Competence from "@/models/Competence";
+import getQuestionnaire from "./mixins/getQuestionnaire";
 
 const API_URL = process.env.VUE_APP_API_URL;
 
 export default defineComponent({
-  components: { cHeader, cFooter, styledInput, checkBox, dots },
+  components: { cHeader, cFooter, dots, Questionnaire },
+  mixins: [getQuestionnaire],
   data() {
     return {
       uuid: location.pathname.split("/").pop(),
       status: "Loading",
       data: null as StateJSON | null,
       registration: [] as Array<Benevole>,
-      name: "",
-      surname: "",
-      phone: "",
-      email: "",
-      comment: "",
-      competenceIdList: [] as Array<number>,
+
       competences: [] as Array<Competence>,
     };
   },
@@ -91,63 +49,20 @@ export default defineComponent({
     evtName(): string {
       return this.data?.evenement.name ?? "";
     },
-    fanfares(): Array<ICompetence> {
-      return this.competences.filter((o) => o.isFanfare);
-    },
-    preferences(): Array<ICompetence> {
-      return this.competences.filter((o) => !o.isFanfare && o.isPreference);
-    },
-    contraintes(): Array<ICompetence> {
-      return this.competences.filter((o) => !o.isFanfare && !o.isPreference);
+    emailList(): Array<string> {
+      return this.registration.map((b) => b.email);
     },
   },
   methods: {
-    toggle(value: boolean, id: number) {
-      if (value) {
-        this.competenceIdList.push(id);
-      } else {
-        this.competenceIdList = this.competenceIdList.filter((o) => o != id);
-      }
-    },
     addBenevoles(data: Array<BenevoleJSON>) {
       this.registration.push(...data.map((o) => Benevole.fromJSON(o)));
     },
-    saveBenevole() {
-      const benevole: BenevoleJSON = {
-        name: this.name,
-        surname: this.surname,
-        phone: this.phone,
-        email: this.email,
-        comment: this.comment,
-        competenceIdList: this.competenceIdList,
-      };
-      fetch(`${API_URL}api/inscription/${this.uuid}`, {
-        method: "PUT",
-        body: JSON.stringify(benevole),
-        headers: { "Content-Type": "application/json" },
-      })
-        .then((res) => {
-          if (res.status == 200) {
-            return res.json();
-          } else {
-            if (res.status == 409) {
-              Toast({
-                html: "Cette addresse mail à déjà été utiliser pour une autre inscription",
-                classes: "error",
-              });
-            }
-            throw new Error(res.statusText);
-          }
-        })
-        .then((data: BenevoleJSON) => {
-          this.registration.push(Benevole.fromJSON(data));
-        });
-    },
   },
   mounted() {
     // Get event data
     const uuid = this.uuid;
     if (uuid && validateUuid(uuid)) {
+      this.getQuestionnaire(uuid);
       fetch(`${API_URL}api/evenements/${uuid}`)
         .then((res) => {
           if (res.status == 200) {
@@ -185,15 +100,4 @@ export default defineComponent({
   max-width: 600px;
   padding: 8px;
 }
-.persona {
-  display: flex;
-  flex-wrap: wrap;
-  justify-content: space-between;
-}
-.s6 {
-  width: calc(50% - 8px);
-}
-#comment {
-  width: 100%;
-}
 </style>

+ 1 - 0
src/PlannerApp.vue

@@ -61,6 +61,7 @@ const tabs: Array<HeaderLink> = [
   { to: "/planner/contraintes", name: "Gestion des contraintes" },
   { to: "/planner/benevoles", name: "Gestion des bénévoles" },
   { to: "/planner/planningIndividuel", name: "Planning Individuel" },
+  { to: "/planner/planningImprimmable", name: "Planning Terrain" },
 ];
 export default defineComponent({
   components: { cHeader: Header, cFooter },

+ 53 - 0
src/assets/css/button-group.css

@@ -0,0 +1,53 @@
+.btn-group {
+  color: inherit;
+  border: 0;
+  cursor: pointer;
+  margin: 0;
+  display: -webkit-inline-box;
+  display: inline-flex;
+  outline: 0;
+  padding: calc(0.5rem - 1px) 1rem;
+  font-size: 0.875rem;
+  min-width: 2rem;
+  box-sizing: border-box;
+  min-height: 1.5rem;
+  text-align: center;
+  -webkit-box-align: center;
+  align-items: center;
+  font-family: Roboto, Helvetica Neue, Helvetica, Arial, sans-serif;
+  font-weight: 700;
+  line-height: 1.5rem;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  white-space: nowrap;
+  border-radius: 3px;
+  text-transform: none;
+  vertical-align: middle;
+  -moz-appearance: none;
+  -webkit-box-pack: center;
+  justify-content: center;
+  text-decoration: none;
+  background-color: transparent;
+  -webkit-appearance: none;
+  -webkit-tap-highlight-color: transparent;
+  border: solid 1px var(--color-neutral-100);
+}
+
+.btn-group.small {
+  padding: calc(0.125rem + 1px) 0.7rem;
+  font-size: 0.875rem;
+  min-width: 2rem;
+  line-height: 1.5rem;
+}
+.btn-group.left {
+  border-radius: 3px 0 0 3px;
+}
+.btn-group.right {
+  border-radius: 0 3px 3px 0;
+}
+.btn-group.selected {
+  background: var(--color-neutral-100);
+  color: #fff;
+}

+ 330 - 0
src/assets/css/markdown.css

@@ -0,0 +1,330 @@
+.markdown-body .anchor {
+  float: left;
+  line-height: 1;
+  margin-left: -20px;
+  padding-right: 4px;
+}
+
+.markdown-body .anchor:focus {
+  outline: none;
+}
+
+.markdown-body code,
+.markdown-body kbd,
+.markdown-body pre {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+.markdown-body a {
+  color: var(--color-accent-600);
+  text-decoration: none;
+}
+
+.markdown-body a:hover {
+  text-decoration: underline;
+}
+
+.markdown-body strong {
+  font-weight: 600;
+}
+
+.markdown-body hr {
+  height: 0;
+  margin: 15px 0;
+  overflow: hidden;
+  background: transparent;
+  border: 0;
+  border-bottom: 1px solid var(--color-neutral-800);
+}
+
+.markdown-body hr:after,
+.markdown-body hr:before {
+  display: table;
+  content: "";
+}
+
+.markdown-body table {
+  border-spacing: 0;
+  border-collapse: collapse;
+}
+
+.markdown-body td,
+.markdown-body th {
+  padding: 0;
+}
+
+.markdown-body details summary {
+  cursor: pointer;
+}
+
+.markdown-body kbd {
+  display: inline-block;
+  padding: 3px 5px;
+  font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+  line-height: 10px;
+  color: #444d56;
+  vertical-align: middle;
+  background-color: #fafbfc;
+  border: 1px solid #d1d5da;
+  border-radius: 3px;
+  box-shadow: inset 0 -1px 0 #d1d5da;
+}
+
+.markdown-body blockquote {
+  margin: 0;
+}
+
+.markdown-body ol,
+.markdown-body ul {
+  padding-left: 0;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.markdown-body ol ol,
+.markdown-body ul ol {
+  list-style-type: lower-roman;
+}
+
+.markdown-body ol ol ol,
+.markdown-body ol ul ol,
+.markdown-body ul ol ol,
+.markdown-body ul ul ol {
+  list-style-type: lower-alpha;
+}
+
+.markdown-body dd {
+  margin-left: 0;
+}
+
+.markdown-body code,
+.markdown-body pre {
+  font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+  font-size: 12px;
+}
+
+.markdown-body pre {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.markdown-body input::-webkit-inner-spin-button,
+.markdown-body input::-webkit-outer-spin-button {
+  margin: 0;
+  -webkit-appearance: none;
+  appearance: none;
+}
+
+.markdown-body :checked + .radio-label {
+  position: relative;
+  z-index: 1;
+  border-color: #0366d6;
+}
+
+.markdown-body .border {
+  border: 1px solid #e1e4e8 !important;
+}
+
+.markdown-body .border-0 {
+  border: 0 !important;
+}
+
+.markdown-body .border-bottom {
+  border-bottom: 1px solid #e1e4e8 !important;
+}
+
+.markdown-body hr {
+  border-bottom-color: #eee;
+}
+
+.markdown-body kbd {
+  display: inline-block;
+  padding: 3px 5px;
+  font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
+  line-height: 10px;
+  color: #444d56;
+  vertical-align: middle;
+  background-color: #fafbfc;
+  border: 1px solid #d1d5da;
+  border-radius: 3px;
+  box-shadow: inset 0 -1px 0 #d1d5da;
+}
+.markdown-body > :first-child {
+  margin-top: 0 !important;
+}
+
+.markdown-body > :last-child {
+  margin-bottom: 0 !important;
+}
+
+.markdown-body a:not([href]) {
+  color: inherit;
+  text-decoration: none;
+}
+
+.markdown-body blockquote,
+.markdown-body details,
+.markdown-body dl,
+.markdown-body ol,
+.markdown-body p,
+.markdown-body pre,
+.markdown-body table,
+.markdown-body ul {
+  margin-top: 0;
+  margin-bottom: 16px;
+}
+
+.markdown-body hr {
+  height: 0.25em;
+  padding: 0;
+  margin: 24px 0;
+  background-color: #e1e4e8;
+  border: 0;
+}
+
+.markdown-body blockquote {
+  padding: 0 1em;
+  color: #6a737d;
+  border-left: 0.25em solid #dfe2e5;
+}
+
+.markdown-body blockquote > :first-child {
+  margin-top: 0;
+}
+
+.markdown-body blockquote > :last-child {
+  margin-bottom: 0;
+}
+.markdown-body ol,
+.markdown-body ul {
+  padding-left: 2em;
+}
+
+.markdown-body ol ol,
+.markdown-body ol ul,
+.markdown-body ul ol,
+.markdown-body ul ul {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+.markdown-body li {
+  word-wrap: break-all;
+}
+
+.markdown-body li > p {
+  margin-top: 16px;
+}
+
+.markdown-body li + li {
+  margin-top: 0.25em;
+}
+
+.markdown-body dl {
+  padding: 0;
+}
+
+.markdown-body dl dt {
+  padding: 0;
+  margin-top: 16px;
+  font-size: 1em;
+  font-style: italic;
+  font-weight: 600;
+}
+
+.markdown-body dl dd {
+  padding: 0 16px;
+  margin-bottom: 16px;
+}
+
+.markdown-body table {
+  display: block;
+  width: 100%;
+  overflow: auto;
+}
+
+.markdown-body table th {
+  font-weight: 600;
+}
+
+.markdown-body table td,
+.markdown-body table th {
+  padding: 6px 13px;
+  border: 1px solid #dfe2e5;
+}
+
+.markdown-body table tr {
+  background-color: #fff;
+  border-top: 1px solid #c6cbd1;
+}
+
+.markdown-body table tr:nth-child(2n) {
+  background-color: #f6f8fa;
+}
+
+.markdown-body img {
+  max-width: 100%;
+  box-sizing: initial;
+  background-color: #fff;
+}
+
+.markdown-body img[align="right"] {
+  padding-left: 20px;
+}
+
+.markdown-body img[align="left"] {
+  padding-right: 20px;
+}
+
+.markdown-body code {
+  padding: 0.2em 0.4em;
+  margin: 0;
+  font-size: 85%;
+  background-color: rgba(27, 31, 35, 0.05);
+  border-radius: 3px;
+}
+
+.markdown-body pre {
+  word-wrap: normal;
+}
+
+.markdown-body pre > code {
+  padding: 0;
+  margin: 0;
+  font-size: 100%;
+  word-break: normal;
+  white-space: pre;
+  background: transparent;
+  border: 0;
+}
+
+.markdown-body .highlight {
+  margin-bottom: 16px;
+}
+
+.markdown-body .highlight pre {
+  margin-bottom: 0;
+  word-break: normal;
+}
+
+.markdown-body .highlight pre,
+.markdown-body pre {
+  padding: 16px;
+  overflow: auto;
+  font-size: 85%;
+  line-height: 1.45;
+  background-color: #f6f8fa;
+  border-radius: 3px;
+}
+
+.markdown-body pre code {
+  display: inline;
+  max-width: auto;
+  padding: 0;
+  margin: 0;
+  overflow: visible;
+  line-height: inherit;
+  word-wrap: normal;
+  background-color: initial;
+  border: 0;
+}

+ 10 - 8
src/components/EditeurBenevole.vue

@@ -24,14 +24,7 @@
         type="text"
         :modelValue="benevole.id"
         disabled
-      />
-      <styled-input
-        label="Telephone"
-        class="s9"
-        id="phone"
-        type="text"
-        :modelValue="benevole.phone"
-        @input="inputListener($event, 'phone')"
+        v-show="false"
       />
       <styled-input
         class="s6"
@@ -50,12 +43,21 @@
         @input="inputListener($event, 'surname')"
       />
       <styled-input
+        class="s6"
         label="Email"
         id="email"
         type="email"
         :modelValue="benevole.email"
         @input="inputListener($event, 'email')"
       />
+      <styled-input
+        label="Telephone"
+        class="s6"
+        id="phone"
+        type="text"
+        :modelValue="benevole.phone"
+        @input="inputListener($event, 'phone')"
+      />
       <h3 style="padding-bottom: 0.8rem">Propriétés</h3>
       <chips-input
         label="Préference & compétences"

+ 1 - 1
src/components/EditeurCompetence.vue

@@ -24,9 +24,9 @@
         type="text"
         :modelValue="competence.id"
         disabled
+        v-show="false"
       />
       <styled-input
-        class="s9"
         label="Titre"
         id="name"
         type="text"

+ 1 - 1
src/components/EditeurCreneau.vue

@@ -29,7 +29,7 @@
         :modelValue="creneau.title"
         @input="inputListener($event, 'title')"
       />
-      <date-Picker class="s6" title="Date" id="creneauDate" lang="fr" target="day" v-model="jour" />
+      <date-picker class="s6" title="Date" id="creneauDate" lang="fr" target="day" v-model="jour" />
       <styled-input
         class="s6"
         label="Heure"

+ 257 - 0
src/components/EditeurQuestionnaire.vue

@@ -0,0 +1,257 @@
+<template>
+  <div class="questionnaire-container">
+    <div class="questionnaire-editor">
+      <h4>Edition du questionnaire</h4>
+      <div></div>
+      <div class="editor">
+        <textarea
+          ref="textarea"
+          :value="introduction"
+          @input="updateIntro"
+          placeholder="Vous pouvez écrire un message en utilisant le format markdown qui sera affiché en en-tête du sondage"
+        ></textarea>
+      </div>
+      <div class="question" v-for="(q, idx) in questions" :key="idx">
+        <v-question
+          :potentialCompetences="getPotentialValues(idx)"
+          :modelValue="q"
+          @update:modelValue="updateQuestion($event, idx)"
+          @delete="deleteQuestion(idx)"
+        />
+        <hr />
+      </div>
+      <div class="actions">
+        <button class="btn small success" @click="createQuestion">
+          <i class="material-icons">create</i>Rajouter une question
+        </button>
+        <a v-if="inscription" class="btn primary small" :href="inscription">
+          <i class="material-icons">launch</i>Lien vers le formulaire d'inscription
+        </a>
+      </div>
+    </div>
+    <div class="questionnaire-preview">
+      <h4>Previsualisation du questionnaire</h4>
+      <div class="v-questionnaire">
+        <v-questionnaire
+          :introduction="introduction"
+          :questions="questions"
+          :uuid="uuid"
+          :evtName="evtName"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType } from "vue";
+import { v4 as uuidv4 } from "uuid";
+import debounce from "lodash-es/debounce";
+
+import vQuestion from "@/components/EditeurQuestionnaireQuestion.vue";
+import vQuestionnaire from "@/components/Questionaire.vue";
+
+import { Question } from "@/models/Questionnaire";
+import Competence from "@/models/Competence";
+import getQuestionnaire from "@/mixins/getQuestionnaire";
+import Toast from "@/utils/Toast";
+
+const API_URL = process.env.VUE_APP_API_URL;
+
+export default defineComponent({
+  components: { vQuestion, vQuestionnaire },
+  mixins: [getQuestionnaire],
+  props: {
+    competences: { type: Array as PropType<Array<Competence>>, default: () => [] },
+    uuid: { type: String },
+  },
+  data() {
+    return {
+      loading: true,
+      savedIntro: true,
+      savedQuestions: true,
+      savingQuestions: {} as { [k: string]: Question },
+    };
+  },
+  computed: {
+    inscription(): string {
+      return this.uuid ? `/inscription/${this.uuid}` : "";
+    },
+    evtName(): string {
+      return this.$store.state.evenement.name;
+    },
+  },
+  watch: {
+    introduction() {
+      this.$nextTick(() => {
+        const a = this.$refs.textarea as HTMLElement;
+        a.style.height = "auto";
+        a.style.height = a.scrollHeight + "px";
+      });
+    },
+  },
+  methods: {
+    updateIntro(e: InputEvent) {
+      this.introduction = (e.target as HTMLInputElement).value;
+      this.savedIntro = false;
+
+      this.saveIntro();
+    },
+    saveIntro() {
+      if (!this.loading) {
+        fetch(`${API_URL}api/questionnaire/${this.uuid}`, {
+          method: "POST",
+          body: JSON.stringify({ uuid: this.uuid, introduction: this.introduction }),
+          headers: { "Content-Type": "application/json" },
+        }).then(() => (this.savedIntro = true));
+      }
+    },
+    getPotentialValues(idx: number): Array<Competence> {
+      const usedIds = this.questions
+        .filter((_, index) => index != idx)
+        .flatMap((o) => o.competenceList.map((c) => c.id));
+      return this.competences.filter((c) => !usedIds.includes(c.id));
+    },
+    updateQuestion(e: Question, idx: number) {
+      this.questions[idx] = e;
+      this.savedQuestions = false;
+      this.savingQuestions[e.uuid] = e;
+      this.saveQuestions();
+    },
+    saveQuestions() {
+      if (!this.loading) {
+        Object.keys(this.savingQuestions).forEach((k) =>
+          this.saveQuestion(this.savingQuestions[k])
+        );
+        this.savingQuestions = {};
+        this.savedQuestions = true;
+      }
+    },
+    saveQuestion(e: Question) {
+      fetch(`${API_URL}api/questionnaire/${this.uuid}/questions/${e.uuid}`, {
+        method: "POST",
+        body: JSON.stringify(e),
+        headers: { "Content-Type": "application/json" },
+      }).then((res) => {
+        if (res.status !== 200) {
+          Toast({
+            html: "Erreur lors de la sauvegarde de la question <br>" + res.statusText,
+            classes: "error",
+          });
+        }
+      });
+    },
+    createQuestion() {
+      if (this.uuid == undefined) {
+        return;
+      }
+      const evtUuid = this.uuid as string;
+      const newQ: Question = {
+        evtUuid,
+        uuid: uuidv4(),
+        order: this.questions.length,
+        title: "Question " + (this.questions.length + 1),
+        subTitle: "",
+        type: "competence",
+        mandatory: true,
+        mandatoryText: "Aucune des réponses ci-dessus",
+        competenceList: [],
+      };
+      fetch(`${API_URL}api/questionnaire/${this.uuid}/questions/${newQ.uuid}`, {
+        method: "PUT",
+        body: JSON.stringify(newQ),
+        headers: { "Content-Type": "application/json" },
+      }).then((res) => {
+        if (res.status !== 200) {
+          Toast({
+            html: "Erreur lors de la sauvegarde de la question <br>" + res.statusText,
+            classes: "error",
+          });
+        }
+      });
+      this.questions.push(newQ);
+    },
+    deleteQuestion(idx: number) {
+      const q = this.questions.splice(idx, 1)[0];
+      fetch(`${API_URL}api/questionnaire/${this.uuid}/questions/${q.uuid}`, {
+        method: "DELETE",
+      }).then((res) => {
+        if (res.status == 404) {
+          Toast({
+            html: "Erreur lors de la suppression de la question",
+            classes: "error",
+          });
+        }
+      });
+    },
+  },
+  mounted() {
+    this.saveIntro = debounce(this.saveIntro, 5000);
+    this.updateIntro = debounce(this.updateIntro, 300);
+    this.saveQuestions = debounce(this.saveQuestions, 1000);
+    if (this.uuid) this.getQuestionnaire(this.uuid);
+    setTimeout(() => (this.loading = false), 5000);
+  },
+  unmounted() {
+    if (!this.savedIntro) this.saveIntro();
+    if (!this.savedQuestions) this.saveQuestions();
+  },
+});
+</script>
+
+<style scoped>
+.questionnaire-container {
+  flex-direction: column;
+  display: flex;
+  justify-content: space-between;
+  margin: 0 16px;
+}
+@media (min-width: 1000px) {
+  .questionnaire-container {
+    flex-direction: row;
+  }
+}
+.questionnaire-editor {
+  display: flex;
+  flex-direction: column;
+  max-width: 800px;
+}
+.v-questionnaire {
+  box-shadow: 0 0 2px 2px var(--color-neutral-600);
+  padding: 8px;
+  padding-top: 0;
+}
+.editor {
+  margin: 0;
+  height: 100%;
+  font-family: "Helvetica Neue", Arial, sans-serif;
+  color: #333;
+}
+
+.editor textarea {
+  display: inline-block;
+  width: 100%;
+  height: 100%;
+  vertical-align: top;
+  box-sizing: border-box;
+  padding: 0 20px;
+}
+
+.editor textarea {
+  border: none;
+  border-right: 1px solid #ccc;
+  resize: none;
+  outline: none;
+  background-color: #f6f6f6;
+  font-size: 14px;
+  font-family: "Monaco", courier, monospace;
+  padding: 20px;
+}
+.question {
+  margin: 8px 0;
+}
+
+.actions {
+  margin-top: 8px;
+}
+</style>

+ 199 - 0
src/components/EditeurQuestionnaireQuestion.vue

@@ -0,0 +1,199 @@
+<template>
+  <div>
+    <styled-input label="Titre" v-model="title" />
+    <styled-input label="Description" v-model="subTitle" />
+    <div>
+      <div class="formcontrol-label">Type de question</div>
+      <button
+        class="btn-group small left"
+        :class="{ selected: type == 'competence' }"
+        @click="type = 'competence'"
+      >
+        Options
+      </button>
+      <button
+        class="btn-group small right"
+        :class="{ selected: type == 'text' }"
+        @click="type = 'text'"
+      >
+        Text libre
+      </button>
+      <button
+        style="margin-left: 8px"
+        class="btn-group small left"
+        :class="{ selected: mandatory }"
+        @click="mandatory = true"
+      >
+        Obligatoire
+      </button>
+      <button
+        class="btn-group small right"
+        :class="{ selected: mandatory == false }"
+        @click="mandatory = false"
+      >
+        Facultative
+      </button>
+    </div>
+    <div v-if="type == 'competence'">
+      <chips-input
+        label="Ajouter une option"
+        placeholder="Choisir une option"
+        secondary-placeholder="+ option"
+        :autocompleteList="autocompleteList"
+        :strict-autocomplete="true"
+        v-model="competenceIdList"
+      />
+      <styled-input
+        v-for="(c, idx) in competences"
+        :key="idx"
+        :helpText="competenceLabel(c.id)"
+        :modelValue="c.text"
+        @input="updateCompetenceText($event, idx)"
+      />
+      <styled-input v-if="mandatory" helpText="Réponse par défaut" v-model="mandatoryText" />
+      <button class="btn small error" @click="$emit('delete')">
+        <i class="material-icons">delete_forever</i>Supprimer
+      </button>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType } from "vue";
+import { Question, QuestionCompetence, QuestionType } from "@/models/Questionnaire";
+
+import styledInput from "@/components/input.vue";
+import chipsInput from "@/components/SelectChipInput.vue";
+import Competence from "@/models/Competence";
+import AutocompleteValues from "@/models/AutocompleteOptions";
+import "@/assets/css/button-group.css";
+
+export default defineComponent({
+  components: { styledInput, chipsInput },
+  data() {
+    return {
+      title: "",
+      subTitle: "",
+      type: "competence" as QuestionType,
+      mandatory: false,
+      mandatoryText: "",
+      competences: [] as Array<QuestionCompetence>,
+      competenceIdList: [] as Array<string>,
+    };
+  },
+  props: {
+    modelValue: {
+      type: Object as PropType<Question>,
+      default: () => ({
+        title: "",
+        subTitle: "",
+        type: "competence",
+        mandatory: false,
+        competenceList: [] as Array<string>,
+        order: -1,
+      }),
+    },
+    potentialCompetences: {
+      type: Array as PropType<Array<Competence>>,
+      default: () => [],
+    },
+  },
+  watch: {
+    modelValue: {
+      handler() {
+        this.updateValue();
+      },
+      deep: true,
+    },
+    competenceIdList(val: Array<string>) {
+      const ids = val.map((i) => parseInt(i));
+      if (val.length != this.competences.length) {
+        this.competences = this.competences.filter((c) => ids.includes(c.id));
+        if (ids.length > this.competences.length) {
+          const existingId = this.competences.map((c) => c.id);
+          ids
+            .filter((i) => !existingId.includes(i))
+            .forEach((id) =>
+              this.competences.push({
+                id,
+                text: this.potentialCompetences.find((c) => c.id == id)?.name ?? "",
+              })
+            );
+        }
+        this.updateParent();
+      }
+    },
+    title(val: string) {
+      if (val != this.modelValue.title) this.updateParent();
+    },
+    subTitle(val: string) {
+      if (val != this.modelValue.subTitle) this.updateParent();
+    },
+    type(val: string) {
+      if (val != this.modelValue.type) this.updateParent();
+    },
+    mandatory(val: boolean) {
+      if (val != this.modelValue.mandatory) this.updateParent();
+    },
+    mandatoryText(val: string) {
+      if (val != this.modelValue.mandatoryText) this.updateParent();
+    },
+  },
+  methods: {
+    updateValue() {
+      const val = this.modelValue;
+      if (this.title != val.title) this.title = val.title;
+      if (this.subTitle != val.subTitle) this.subTitle = val.subTitle;
+      if (this.type != val.type) this.type = val.type;
+      if (this.mandatory != val.mandatory) this.mandatory = val.mandatory;
+      if (this.mandatoryText != val.mandatoryText) this.mandatoryText = val.mandatoryText;
+      if (
+        this.competences
+          .map((c) => c.id)
+          .sort()
+          .join("") !=
+        val.competenceList
+          .map((c) => c.id)
+          .sort()
+          .join("")
+      )
+        this.competences = val.competenceList;
+      this.competenceIdList = this.competences.map((i) => i.id.toString());
+    },
+    updateParent() {
+      const newQ: Question = {
+        evtUuid: this.modelValue.evtUuid,
+        uuid: this.modelValue.uuid,
+        order: this.modelValue.order,
+        title: this.title,
+        subTitle: this.subTitle,
+        type: this.type,
+        mandatory: this.mandatory,
+        competenceList: this.competences,
+        mandatoryText: this.mandatoryText,
+      };
+      this.$emit("update:modelValue", newQ);
+    },
+    updateCompetenceText(e: InputEvent, idx: number) {
+      const newVal = (e.target as HTMLInputElement).value;
+      if (newVal != this.competences[idx].text) {
+        this.competences[idx].text = newVal;
+        this.updateParent();
+      }
+    },
+    competenceLabel(id: number): string {
+      return this.potentialCompetences.find((c) => c.id == id)?.name ?? "";
+    },
+  },
+  computed: {
+    autocompleteList(): Array<AutocompleteValues> {
+      return this.potentialCompetences.map((o) => ({ id: o.id.toString(), name: o.name }));
+    },
+  },
+  mounted() {
+    this.updateValue();
+  },
+});
+</script>
+
+<style scoped></style>

+ 1 - 0
src/components/Footer.vue

@@ -51,6 +51,7 @@ export default defineComponent({
   padding: 16px;
   margin-top: 16px;
   background: #fff;
+  z-index: 1;
 }
 .footer > a {
   text-decoration: none;

+ 89 - 33
src/components/ManageRegistration.vue

@@ -1,36 +1,55 @@
 <template>
   <dots v-if="loading" />
-  <div v-else>
-    <h3>Liste de demande d'inscription des bénévoles</h3>
-    <div class="actions">
-      <button class="btn small primary" @click="importSelected">
-        <i class="material-icons">input</i>Importer la selection
-      </button>
-      <button class="btn small error" @click="deleteSelected">
-        <i class="material-icons">delete_forever</i>Supprimer la selection
-      </button>
-    </div>
+  <div v-else class="container">
+    <div>
+      <h3>Liste de demande d'inscription des bénévoles</h3>
+      <div class="actions">
+        <button class="btn small primary" @click="importSelected">
+          <i class="material-icons">input</i>Importer la selection
+        </button>
+        <button class="btn small error" @click="deleteSelected">
+          <i class="material-icons">delete_forever</i>Supprimer la selection
+        </button>
+      </div>
 
-    <table>
-      <thead>
-        <tr>
-          <th @click="selectAll">
-            <i class="material-icons">{{ topBox }}</i>
-          </th>
-          <th>Nom</th>
-          <th>Email</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr v-for="b in inscriptions" :key="b.id">
-          <td @click="toggle(b.id)">
-            <i class="material-icons">{{ checkbox(b.id) }}</i>
-          </td>
-          <td>{{ b.name }}, {{ b.surname }}</td>
-          <td>{{ b.email }}</td>
-        </tr>
-      </tbody>
-    </table>
+      <table>
+        <thead>
+          <tr>
+            <th @click="selectAll">
+              <i class="material-icons">{{ topBox }}</i>
+            </th>
+            <th>Nom</th>
+            <th>Email</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="b in inscriptions" :key="b.id">
+            <td @click="toggle(b.id)">
+              <i class="material-icons">{{ checkbox(b.id) }}</i>
+            </td>
+            <td @click="display(b.id)">{{ b.name }}, {{ b.surname }}</td>
+            <td @click="display(b.id)">{{ b.email }}</td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <div class="benevole-preview">
+      <div v-if="benevole">
+        <h3>Demande d'inscription</h3>
+        <div class="field"><span>Nom</span>{{ benevole.surname }}</div>
+        <div class="field"><span>Prénom</span>{{ benevole.name }}</div>
+        <div class="field"><span>Téléphone</span>{{ benevole.phone }}</div>
+        <div class="field"><span>Email</span>{{ benevole.email }}</div>
+        <div class="field"><span>Commentaire</span><br />{{ benevole.comment }}</div>
+        <div>
+          <h4>Competences & Préférence</h4>
+          <div class="chip" v-for="id in benevole.competenceIdList" :key="id">
+            <span class="chip-label">{{ getCompetenceName(id) }}</span>
+          </div>
+        </div>
+      </div>
+      <div v-else>Cliquer sur un ligne</div>
+    </div>
   </div>
 </template>
 
@@ -50,6 +69,7 @@ export default defineComponent({
       inscriptions: [] as Array<BenevoleJSON>,
       loading: true,
       selected: [] as Array<number>,
+      benevole: null as null | BenevoleJSON,
     };
   },
   methods: {
@@ -60,6 +80,9 @@ export default defineComponent({
         this.selected.push(id);
       }
     },
+    display(id: number) {
+      this.benevole = this.inscriptions.find((b) => b.id == id) ?? null;
+    },
     checkbox(id: number) {
       return this.selected.includes(id) ? "check_box" : "check_box_outline_blank";
     },
@@ -93,6 +116,9 @@ export default defineComponent({
       this.inscriptions = this.inscriptions.filter((b) => b.id && !this.selected.includes(b.id));
       this.selected = [];
     },
+    getCompetenceName(id: number): string {
+      return this.$store.getters.getCompetenceById(id)?.name ?? "";
+    },
   },
   computed: {
     uuid(): string {
@@ -131,6 +157,31 @@ export default defineComponent({
 </script>
 
 <style scoped>
+.container {
+  display: flex;
+  gap: 32px;
+  justify-content: center;
+}
+.benevole-preview {
+  padding: 12px;
+  width: 400px;
+  box-shadow: 0 0 2px 2px var(--color-neutral-600);
+}
+.benevole-preview > div > div {
+  padding: 8px;
+}
+.field > span {
+  font-weight: bold;
+  display: inline-block;
+  min-width: 100px;
+}
+.field > span:after {
+  content: " :";
+}
+.chip {
+  margin-right: 4px;
+  margin-bottom: 4px;
+}
 .actions {
   display: inline-flex;
   justify-content: left;
@@ -151,14 +202,19 @@ th {
 }
 tr > *:first-child {
   width: calc(24px + 1rem);
+  cursor: pointer;
 }
 tr {
   border-bottom: 1px solid #ddd;
 }
-thead > tr {
-  border-bottom-width: 2px;
-}
 tr:nth-child(even) {
   background-color: #eeeeee;
 }
+tr:hover {
+  background: var(--color-neutral-800);
+}
+
+thead > tr {
+  border-bottom-width: 2px;
+}
 </style>

+ 271 - 0
src/components/Questionaire.vue

@@ -0,0 +1,271 @@
+<template>
+  <div class="inscription-container">
+    <h2>Formulaire d'inscription en tant que bénévole pour {{ evtName }}</h2>
+    <div class="intro-contrainer">
+      <div class="intro markdown-body" v-html="compiledMarkdown"></div>
+    </div>
+    <div class="persona">
+      <styled-input
+        class="s6"
+        label="Prénom"
+        id="last_name"
+        type="text"
+        v-model="name"
+        :validateInput="validateString"
+      />
+      <styled-input
+        class="s6"
+        label="Nom"
+        id="family_name"
+        type="text"
+        v-model="surname"
+        :validateInput="validateString"
+      />
+      <styled-input
+        class="s6"
+        label="Email"
+        id="email"
+        type="email"
+        v-model="email"
+        :validateInput="validateString"
+      />
+      <styled-input
+        class="s6"
+        label="Téléphone"
+        id="phone"
+        type="text"
+        v-model="phone"
+        :validateInput="validateString"
+      />
+    </div>
+    <div v-for="(q, idx) in questions" :key="idx">
+      <h3 :class="{ invalid: !isQuestionValid(q) }">
+        {{ q.title }} <span class="mandatory" v-if="q.mandatory">*</span>
+      </h3>
+      <div v-if="q.subTitle">{{ q.subtitle }}</div>
+      <template v-if="q.type == 'competence'">
+        <check-box
+          v-for="c in q.competenceList"
+          :help="getDescription(c.id)"
+          :id="c.id"
+          :label="c.text"
+          :key="c.id"
+          @input="toggle($event, c.id)"
+        />
+        <check-box
+          v-if="q.mandatory"
+          :label="q.mandatoryText"
+          @input="checkDefault($event, q.uuid)"
+        />
+      </template>
+      <styled-input
+        v-if="q.type == 'text'"
+        type="text"
+        modelValue=""
+        @input="saveComment($event, q.uuid)"
+      />
+    </div>
+    <button style="margin-top: 8px" class="btn success small" @click="saveBenevole">
+      <i class="material-icons">send</i>S'inscrire
+    </button>
+  </div>
+</template>
+
+<script lang="ts">
+import { Question } from "@/models/Questionnaire";
+import { defineComponent, PropType } from "vue";
+
+import marked from "marked";
+import Benevole, { BenevoleJSON } from "@/models/Benevole";
+import Toast from "@/utils/Toast";
+import checkBox from "@/components/checkBox.vue";
+import styledInput from "@/components/input.vue";
+import Competence from "@/models/Competence";
+import "@/assets/css/markdown.css";
+
+const API_URL = process.env.VUE_APP_API_URL;
+
+export default defineComponent({
+  components: { checkBox, styledInput },
+  data() {
+    return {
+      name: "",
+      surname: "",
+      phone: "",
+      email: "",
+      comment: "",
+      competenceIdList: [] as Array<number>,
+      checkDone: false,
+      questionsChecked: [] as Array<string>,
+      commentObject: {} as { [k: string]: string },
+    };
+  },
+  computed: {
+    compiledMarkdown(): string {
+      return marked(this.introduction);
+    },
+    areQuestionValid(): boolean {
+      return !this.questions.some((q) => !this.isQuestionValid(q));
+    },
+  },
+  props: {
+    uuid: { type: String, default: "" },
+    evtName: { type: String, default: "" },
+    introduction: { type: String, default: () => "" },
+    questions: { type: Array as PropType<Array<Question>>, default: () => [] },
+    competences: { type: Array as PropType<Array<Competence>>, default: () => [] },
+    emailList: { type: Array as PropType<Array<string>>, default: () => [] },
+  },
+  methods: {
+    getDescription(id: number) {
+      return this.competences.find((c) => c.id == id)?.description ?? "";
+    },
+    isQuestionValid(q: Question) {
+      if (this.checkDone && q.mandatory) {
+        if (q.type == "competence") {
+          return (
+            q.competenceList.some((c) => this.competenceIdList.includes(c.id)) ||
+            this.questionsChecked.includes(q.uuid)
+          );
+        }
+        if (q.type == "text") {
+          return this.commentObject[q.uuid] != "";
+        }
+      }
+      return true;
+    },
+    validateString(s: string) {
+      return this.checkDone ? s.length - 1 : 0;
+    },
+    toggle(value: boolean, id: number) {
+      if (value) {
+        this.competenceIdList.push(id);
+      } else {
+        this.competenceIdList = this.competenceIdList.filter((o) => o != id);
+      }
+    },
+    checkDefault(value: boolean, uuid: string) {
+      if (value) {
+        this.questionsChecked.push(uuid);
+      } else {
+        this.questionsChecked = this.questionsChecked.filter((o) => o != uuid);
+      }
+    },
+    saveComment(e: InputEvent, uuid: string) {
+      this.commentObject[uuid] = (e.target as HTMLInputElement).value;
+    },
+    saveBenevole() {
+      this.checkDone = true;
+      if (!this.areQuestionValid) {
+        return;
+      }
+      if (this.name == "") {
+        Toast({ html: "Nom invalide", classes: "error" });
+        return;
+      }
+      if (this.surname == "") {
+        Toast({ html: "Prénom invalide", classes: "error" });
+        return;
+      }
+      if (this.phone == "") {
+        Toast({ html: "Numéro de téléphone invalide", classes: "error" });
+        return;
+      }
+      if (this.email == "") {
+        Toast({ html: "Email invalide", classes: "error" });
+        return;
+      }
+      if (this.emailList.includes(this.email)) {
+        Toast({
+          html: "Cette addresse mail à déjà été utiliser pour une autre inscription",
+          classes: "error",
+        });
+        return;
+      }
+      this.comment = this.questions
+        .filter((q) => q.type == "text")
+        .map((q) => q.title + " : " + this.commentObject[q.uuid])
+        .join("\n\r");
+      const benevole: BenevoleJSON = {
+        name: this.name,
+        surname: this.surname,
+        phone: this.phone,
+        email: this.email,
+        comment: this.comment,
+        competenceIdList: this.competenceIdList,
+      };
+      fetch(`${API_URL}api/inscription/${this.uuid}`, {
+        method: "PUT",
+        body: JSON.stringify(benevole),
+        headers: { "Content-Type": "application/json" },
+      })
+        .then((res) => {
+          if (res.status == 200) {
+            Toast({
+              html: "Inscription Validée",
+              classes: "success",
+            });
+            return res.json();
+          } else {
+            if (res.status == 409) {
+              Toast({
+                html: "Cette addresse mail à déjà été utiliser pour une autre inscription",
+                classes: "error",
+              });
+            }
+            throw new Error(res.statusText);
+          }
+        })
+        .then((data: BenevoleJSON) => {
+          this.$emit("newRegistration", Benevole.fromJSON(data));
+        });
+    },
+  },
+});
+</script>
+
+<style scoped>
+.inscription-container {
+  max-width: 600px;
+  padding: 8px;
+}
+.intro-contrainer {
+  margin: 0;
+  height: auto;
+  font-family: "Helvetica Neue", Arial, sans-serif;
+  color: #333;
+}
+
+.intro {
+  display: inline-block;
+  width: 100%;
+  height: 100%;
+  vertical-align: top;
+  box-sizing: border-box;
+  padding: 0 20px;
+}
+span.mandatory {
+  color: var(--color-primary-200);
+  font-variant-position: super;
+}
+.persona {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+.s6 {
+  width: calc(50% - 8px);
+}
+.invalid {
+  text-decoration: wavy underline var(--color-primary-400);
+}
+
+.intro blockquote {
+  border-left: 8px solid #fafafa;
+}
+
+.intro pre,
+.intro code {
+  background-color: #fafafa;
+}
+</style>

+ 1 - 1
src/components/SelectChipInput.vue

@@ -81,7 +81,7 @@ export default defineComponent({
     },
     secondaryPlaceholder: {
       type: String,
-      default: "+ ",
+      default: "add element",
     },
     // Number of selected item displayed before displaying an ellipsis appear
     maxItemDisplayed: {

+ 5 - 5
src/components/date-picker.vue

@@ -214,11 +214,11 @@ export default defineComponent({
     },
   },
   watch: {
-    modelValue: function (val) {
-      if (["object", "string", "number"].includes(typeof val)) {
-        const d = dayjs(val);
+    modelValue(val) {
+      const d = dayjs(val);
+      if (d.isValid()) {
         if (!this.valueObject?.isSame(d)) {
-          this.valueObject = dayjs(val);
+          this.valueObject = d;
         }
       }
     },
@@ -316,7 +316,7 @@ export default defineComponent({
       }
     },
   },
-  mounted: function () {
+  mounted() {
     if (this.modelValue) {
       if (["object", "string", "number"].includes(typeof this.modelValue)) {
         this.valueObject = dayjs(this.modelValue);

+ 1 - 1
src/mixins/ImportJsonState.ts

@@ -4,7 +4,7 @@ import Creneau from "@/models/Creneau";
 import Evenement from "@/models/Evenement";
 import { MutationTypes } from "@/store/Mutations";
 import { StateJSON } from "@/store/State";
-import { IRessource, Ressource, RessourceJSON } from "jc-timeline";
+import { Ressource } from "jc-timeline";
 import { defineComponent } from "vue";
 
 const keyofEvent: Array<keyof Evenement> = ["name", "uuid", "start", "end"];

+ 49 - 0
src/mixins/getQuestionnaire.ts

@@ -0,0 +1,49 @@
+import { Question } from "@/models/Questionnaire";
+import Toast from "@/utils/Toast";
+import { defineComponent } from "vue";
+
+const API_URL = process.env.VUE_APP_API_URL;
+
+export default defineComponent({
+  data() {
+    return { introduction: "", questions: [] as Array<Question> };
+  },
+  methods: {
+    getQuestionnaire(uuid: string) {
+      // Get previously saved introduction
+      fetch(`${API_URL}api/questionnaire/${uuid}`, {
+        method: "GET",
+      })
+        .then((res) => {
+          if (res.status == 200) {
+            return res.json();
+          }
+          if (res.status == 404) {
+            Toast({
+              html: "Pas de donnée existante pour l'introduction du questionnaire",
+              classes: "error",
+            });
+          }
+          throw new Error("Failed");
+        })
+        .then((t) => (this.introduction = t.introduction ?? ""));
+      // Get previously saved questions
+      fetch(`${API_URL}api/questionnaire/${uuid}/questions`, {
+        method: "GET",
+      })
+        .then((res) => {
+          if (res.status == 200) {
+            return res.json();
+          }
+          if (res.status == 404) {
+            Toast({
+              html: "Pas de question associeé à ce questionnaire",
+              classes: "error",
+            });
+          }
+          throw new Error("Failed");
+        })
+        .then((data: Array<Question>) => (this.questions = data.sort((a, b) => a.order - b.order)));
+    },
+  },
+});

+ 22 - 0
src/models/Questionnaire.ts

@@ -0,0 +1,22 @@
+export interface QuestionCompetence {
+  id: number;
+  text: string;
+}
+export type QuestionType = "competence" | "text";
+export type Question = {
+  evtUuid: string;
+  uuid: string;
+  order: number;
+  title: string;
+  subTitle: string;
+  type: QuestionType;
+  mandatory: boolean;
+  mandatoryText: string;
+  competenceList: Array<QuestionCompetence>;
+};
+
+type Questionnaire = {
+  introduction: string;
+  questions: Array<Question>;
+};
+export default Questionnaire;

+ 6 - 0
src/router/main.ts

@@ -4,6 +4,7 @@ import Planning from "@/views/Planning.vue";
 import CompetenceManager from "@/views/CompetenceManager.vue";
 import BenevoleManager from "@/views/BenevoleManager.vue";
 import PlanningPersonnel from "@/views/PlanningPersonnel.vue";
+import PrintablePlanning from "@/views/PrintablePlanning.vue";
 import Help from "@/views/Help.vue";
 import Home from "@/views/Home.vue";
 
@@ -38,6 +39,11 @@ const routes: Array<RouteRecordRaw> = [
     name: "planningIndividuel",
     component: PlanningPersonnel,
   },
+  {
+    path: "/planner/planningImprimmable",
+    name: "PlanningImprimmable",
+    component: PrintablePlanning,
+  },
   {
     path: "/planner/docs",
     name: "docs",

+ 75 - 30
src/views/BenevoleManager.vue

@@ -1,26 +1,50 @@
 <template>
-  <div class="page">
-    <data-table
-      class="data-table"
-      title="Liste des bénévoles"
-      :data="data"
-      :clickable="true"
-      :loadingAnimation="loading"
-      :exportable="false"
-      locale="fr"
-      @row-click="onRowClick"
-      :customButtons="customButton"
-    ></data-table>
-    <editeur-benevole
-      :benevole="currentBenevole"
-      @create="createBenevole"
-      @delete="deleteBenevole"
-    />
-    <input type="file" ref="loadcsv" @change="importBenevoleTemplate" style="display: none" />
-  </div>
-  <div v-if="showModal" class="modal" @click="closeModal">
-    <div class="modal-content" ref="modal">
-      <manage-registration />
+  <div class="benevole-page">
+    <div class="panel">
+      <div class="panel-item" :class="{ selected: show == 'benevole' }" @click="show = 'benevole'">
+        Bénévoles
+      </div>
+      <div
+        class="panel-item"
+        :class="{ selected: show == 'questionnaire' }"
+        @click="show = 'questionnaire'"
+      >
+        Gestion du questionnaire
+      </div>
+      <div
+        class="panel-item"
+        :class="{ selected: show == 'inscriptions' }"
+        @click="show = 'inscriptions'"
+      >
+        Validations des inscriptions
+      </div>
+    </div>
+    <div class="benevole-page-main">
+      <div class="page" v-show="show == 'benevole'">
+        <data-table
+          class="data-table"
+          title="Liste des bénévoles"
+          :data="data"
+          :clickable="true"
+          :loadingAnimation="loading"
+          :exportable="false"
+          locale="fr"
+          @row-click="onRowClick"
+          :customButtons="customButton"
+        ></data-table>
+        <editeur-benevole
+          :benevole="currentBenevole"
+          @create="createBenevole"
+          @delete="deleteBenevole"
+        />
+        <input type="file" ref="loadcsv" @change="importBenevoleTemplate" style="display: none" />
+      </div>
+      <editeur-questionnaire
+        v-if="show == 'questionnaire'"
+        :competences="competenceList"
+        :uuid="uuid"
+      />
+      <manage-registration v-if="show == 'inscriptions'" />
     </div>
   </div>
 </template>
@@ -28,6 +52,7 @@
 <script lang="ts">
 import Competence from "@/models/Competence";
 import EditeurBenevole from "@/components/EditeurBenevole.vue";
+import EditeurQuestionnaire from "@/components/EditeurQuestionnaire.vue";
 import ManageRegistration from "@/components/ManageRegistration.vue";
 import DataTable, {
   CustomButton,
@@ -44,9 +69,9 @@ import Toast from "@/utils/Toast";
 const csvDefaultcolumn = "id,prenom,nom,telephone,email,commentaire";
 export default defineComponent({
   name: "EditeurCreneau",
-  components: { DataTable, EditeurBenevole, ManageRegistration },
+  components: { DataTable, EditeurBenevole, ManageRegistration, EditeurQuestionnaire },
   data: () => ({
-    showModal: false,
+    show: "inscriptions",
     columns: [
       {
         label: "Id",
@@ -91,6 +116,9 @@ export default defineComponent({
     customButton: [] as Array<CustomButton>,
   }),
   computed: {
+    uuid(): string {
+      return this.$store.state.evenement.uuid;
+    },
     csvInputElt(): HTMLInputElement {
       return this.$refs.loadcsv as HTMLInputElement;
     },
@@ -119,11 +147,6 @@ export default defineComponent({
     },
   },
   methods: {
-    closeModal(event: MouseEvent) {
-      if (!(this.$refs.modal as HTMLElement).contains(event.target as HTMLElement)) {
-        this.showModal = false;
-      }
-    },
     getFanfareForBenevole(benevole: Benevole): string {
       if (benevole.competenceIdList.length == 0) {
         return "Exte";
@@ -249,7 +272,6 @@ export default defineComponent({
   },
   mounted() {
     this.loading = false;
-    this.customButton.push({ icon: "list", hide: false, onclick: () => (this.showModal = true) });
     this.customButton.push({ icon: "file_download", hide: false, onclick: this.getImportTemplate });
     this.customButton.push({
       icon: "file_upload",
@@ -263,6 +285,13 @@ export default defineComponent({
 </script>
 
 <style scoped>
+.benevole-page {
+  width: 100%;
+  position: relative;
+}
+.benevole-page-main {
+  margin-left: 240px;
+}
 .page {
   display: flex;
   flex-direction: column;
@@ -287,4 +316,20 @@ export default defineComponent({
     margin-top: 32px;
   }
 }
+
+.panel {
+  height: calc(100% - 3.5rem);
+  border-right: solid 1px var(--color-neutral-800);
+  position: fixed;
+  margin: -8px 8px 0 -8px;
+}
+.panel-item {
+  padding: 16px;
+  white-space: nowrap;
+  cursor: pointer;
+}
+.panel-item.selected {
+  background: var(--color-accent-600);
+  color: var(--color-neutral-100);
+}
 </style>

+ 3 - 3
src/views/Planning.vue

@@ -61,14 +61,14 @@
 import { v4 as uuidv4 } from "uuid";
 import { defineComponent } from "vue";
 import Timeline from "jc-timeline/lib/Timeline";
-import { Event as jcEvent } from "jc-timeline/lib/Event";
+import { Ressource } from "jc-timeline";
+import { Event as jcEvent } from "jc-timeline";
+import Selectable from "jc-timeline/lib/utils/selectable";
 import EditeurCreneau from "@/components/EditeurCreneau.vue";
 import EditeurLigne from "@/components/EditeurCreneauGroup.vue";
-import { Ressource } from "jc-timeline";
 
 import Creneau from "@/models/Creneau";
 import { MutationTypes } from "@/store/Mutations";
-import Selectable from "node_modules/jc-timeline/lib/utils/selectable";
 import toast from "@/utils/Toast";
 
 import dayjs from "dayjs";

+ 146 - 0
src/views/PrintablePlanning.vue

@@ -0,0 +1,146 @@
+<template>
+  <div class="container">
+    <div class="options">
+      <date-picker label="Début" id="start" lang="fr" target="hour" v-model="start" />
+      <date-picker label="Fin" id="end" lang="fr" target="hour" v-model="end" />
+    </div>
+    <jc-timeline
+      ref="timeline"
+      :slotduration="slotduration"
+      :legendspan="legendspan"
+      :slotwidth="slotwidth"
+      :start="start.toDate().toISOString()"
+    ></jc-timeline>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from "vue";
+import Timeline from "jc-timeline/lib/Timeline";
+import DatePicker from "@/components/date-picker.vue";
+import { Event as jcEvent } from "jc-timeline/lib/Event";
+import dayjs from "dayjs";
+import Creneau from "@/models/Creneau";
+import { Ressource } from "jc-timeline";
+import { v4 as uuidv4 } from "uuid";
+
+function hashCode(str: string) {
+  let hash = 5381;
+  for (var i = 0; i < str.length; i++) {
+    hash = str.charCodeAt(i) + ((hash << 5) - hash);
+  }
+  return hash;
+}
+
+function pickColor2(str: string) {
+  let hue = hashCode(str) % 360;
+  if (hue < 0) hue += 360;
+  return `hsl(${hue}, 75%, 40%)`;
+}
+function pickColor(str: string) {
+  return "#" + ("000000" + (hashCode(str) & 0x00ffffff).toString(16)).slice(-6);
+}
+export default defineComponent({
+  name: "PrintablePlanning",
+  components: { DatePicker },
+  data() {
+    return {
+      start: dayjs().startOf("hour"),
+      end: dayjs().add(1, "days").subtract(1, "h").endOf("h"),
+      slotduration: 30,
+      legendspan: 2,
+      slotwidth: 50,
+    };
+  },
+  watch: {
+    start(val: dayjs.Dayjs) {
+      if (this.end.diff(val) < 0) this.end = val.add(1, "d");
+      this.updateTimeline();
+    },
+    end(val: dayjs.Dayjs) {
+      if (this.start.diff(val) > 0) this.start = val.subtract(1, "d");
+      this.timeline.end = val.subtract(1, "h").endOf("h").toISOString();
+      this.updateTimeline();
+    },
+    eventList() {
+      this.updateTimeline();
+    },
+  },
+  computed: {
+    timeline(): Timeline {
+      const output = this.$refs["timeline"];
+      return output as Timeline;
+    },
+    filteredCreneau(): Array<Creneau> {
+      const s = this.start.toDate();
+      const e = this.end.toDate();
+      return this.$store.state.creneauList.filter(
+        (c) => (s <= c.start && c.start <= e) || (s <= c.end && c.end <= e)
+      );
+    },
+    filteredRessource(): Array<Ressource> {
+      const set = new Set();
+      this.filteredCreneau.forEach((c) => set.add(c.ressourceId));
+      const arr = Array.from(set);
+      return this.$store.state.creneauGroupList.filter((r) => arr.includes(r.id));
+    },
+    eventList(): Array<jcEvent> {
+      return this.filteredCreneau.flatMap((c) =>
+        Array.from(new Set(c.benevoleIdList)).map((id) => {
+          const b = this.$store.getters.getBenevoleById(id);
+          return jcEvent.fromJSON({
+            ressourceId: c.ressourceId,
+            start: c.start.toISOString(),
+            end: c.end.toISOString(),
+            title: b?.shortame,
+            editable: false,
+            ressourceEditable: false,
+            id: uuidv4(),
+            bgColor: pickColor2(b?.shortame ?? ""),
+          });
+        })
+      );
+    },
+  },
+  methods: {
+    updateTimeline(): void {
+      this.$nextTick(() => {
+        this.timeline.getRessources().forEach((c) => this.timeline.removeRessourceById(c.id));
+        this.timeline.addRessources(this.filteredRessource);
+        this.timeline.addEvents(this.eventList);
+        this.timeline.requestUpdate();
+      });
+    },
+  },
+  mounted() {
+    this.timeline.setLegendUnitFormat("d", "dddd D MMMM");
+    this.$nextTick(() => {
+      this.start = dayjs(this.$store.state.evenement.startingDate);
+      this.end = this.start.add(1, "d");
+    });
+  },
+});
+</script>
+
+<style scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  margin: 0 8px;
+  max-width: 100%;
+}
+.options {
+  max-width: 400px;
+  display: flex;
+  flex-wrap: wrap;
+}
+.options > * {
+  width: calc(50% - 4px);
+}
+jc-timeline {
+  max-width: 100%;
+  padding: 8px;
+}
+</style>