diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index bd01e22edd47c77eb35b09eff7b41198f1463721..8ae10fb420e5c853a1b2b129380a52dffff24a5c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -25,16 +25,14 @@ build testing docker image:
   only:
     - develop
   tags:
-    - build
-  image:
-    name: gcr.io/kaniko-project/executor:debug
-    entrypoint: [""]
+    - build_docker
+  variables:
+    DOCKER_TAG: testing
   script:
-    - mkdir -p /kaniko/.docker
-    - export
-    - echo "{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
-    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination neogeo/geocontrib-front:testing
-    - echo Image docker neogeo/geocontrib-front:testing livrée
+    - cat $DOCKER_PASSWORD | docker login --username $DOCKER_LOGIN --password-stdin
+    - docker-compose build geocontrib-front
+    - docker-compose push geocontrib-front
+    - echo Image docker neogeo/geocontrib-front:${DOCKER_TAG} livrée
 
 deploy testing docker image:
   stage: deploy
@@ -52,36 +50,30 @@ build stable docker image:
   only:
     - master
   tags:
-    - build
-  image:
-    name: gcr.io/kaniko-project/executor:debug
-    entrypoint: [""]
+    - build_docker
+  variables:
+    DOCKER_TAG: latest
   script:
-    - mkdir -p /kaniko/.docker
-    - echo "{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
-
-    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination neogeo/geocontrib-front:latest
-    - echo Image docker neogeo/geocontrib:latest livrée
-
+    - cat $DOCKER_PASSWORD | docker login --username $DOCKER_LOGIN --password-stdin
+    - docker-compose build geocontrib-front
+    - docker-compose push geocontrib-front
+    - echo Image docker neogeo/geocontrib-front:${DOCKER_TAG} livrée
 
 build tagged docker image:
   stage: build
   only:
     - tags
   tags:
-    - build
-  image:
-    name: gcr.io/kaniko-project/executor:debug
-    entrypoint: [""]
+    - build_docker
+  variables:
+    DOCKER_TAG: $CI_COMMIT_TAG
   script:
     # Don't build tag id package.json as wrong version
     - grep "\"version\":.\"$CI_COMMIT_TAG\"" package.json
-    - mkdir -p /kaniko/.docker
-    - echo "{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"$DOCKER_AUTH\"}}}" > /kaniko/.docker/config.json
-
-    - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination neogeo/geocontrib-front:$CI_COMMIT_TAG
-    - echo Image docker neogeo/geocontrib-front:$CI_COMMIT_TAG livrée
-
+    - cat $DOCKER_PASSWORD | docker login --username $DOCKER_LOGIN --password-stdin
+    - docker-compose build geocontrib-front
+    - docker-compose push geocontrib-front
+    - echo Image docker neogeo/geocontrib-front:${DOCKER_TAG} livrée
 
 sonarqube-check:
   image:
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 18f164891441f5ecbbaa1bbf86ba056f826570f6..a250dbb007c4693ba0d4c4e033be50cc1ed6fa28 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -2,7 +2,7 @@
 version: "3"
 services:
   geocontrib-front:
-    image: neogeo/geocontrib-front:geocontrib-latest
+    image: neogeo/geocontrib-front:${DOCKER_TAG:-testing}
     build: .
     environment:
       - BASE_URL=${BASE_URL}
diff --git a/package-lock.json b/package-lock.json
index 0910a9aac51e1d483ac0dfd470833f00f33d1a22..d597b30628b53ba076f5b3085493f3d10e2a5a1c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "geocontrib-frontend",
-  "version": "3.0.2",
+  "version": "3.1.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -2562,6 +2562,16 @@
           "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
           "dev": true
         },
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
         "array-union": {
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@@ -2571,6 +2581,34 @@
             "array-uniq": "^1.0.1"
           }
         },
+        "chalk": {
+          "version": "4.1.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true,
+          "optional": true
+        },
         "dir-glob": {
           "version": "2.2.2",
           "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
@@ -2642,6 +2680,13 @@
             "slash": "^2.0.0"
           }
         },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true,
+          "optional": true
+        },
         "ignore": {
           "version": "4.0.6",
           "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -2679,6 +2724,28 @@
           "requires": {
             "minipass": "^3.1.1"
           }
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        },
+        "vue-loader-v16": {
+          "version": "npm:vue-loader@16.8.3",
+          "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
+          "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "chalk": "^4.1.0",
+            "hash-sum": "^2.0.0",
+            "loader-utils": "^2.0.0"
+          }
         }
       }
     },
@@ -3561,8 +3628,7 @@
     "bluebird": {
       "version": "3.7.2",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
-      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
-      "dev": true
+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
     },
     "bn.js": {
       "version": "5.2.0",
@@ -5207,6 +5273,16 @@
         }
       }
     },
+    "csvtojson": {
+      "version": "2.0.10",
+      "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz",
+      "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==",
+      "requires": {
+        "bluebird": "^3.5.1",
+        "lodash": "^4.17.3",
+        "strip-bom": "^2.0.0"
+      }
+    },
     "cyclist": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@@ -8321,6 +8397,11 @@
       "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
       "dev": true
     },
+    "is-utf8": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+      "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q=="
+    },
     "is-weakref": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -12273,6 +12354,14 @@
         }
       }
     },
+    "strip-bom": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+      "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==",
+      "requires": {
+        "is-utf8": "^0.2.0"
+      }
+    },
     "strip-comments": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz",
@@ -13257,75 +13346,6 @@
         }
       }
     },
-    "vue-loader-v16": {
-      "version": "npm:vue-loader@16.8.3",
-      "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
-      "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
-      "dev": true,
-      "optional": true,
-      "requires": {
-        "chalk": "^4.1.0",
-        "hash-sum": "^2.0.0",
-        "loader-utils": "^2.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.2",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-          "dev": true,
-          "optional": true
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-          "dev": true,
-          "optional": true
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-          "dev": true,
-          "optional": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
-      }
-    },
     "vue-multiselect": {
       "version": "2.1.6",
       "resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-2.1.6.tgz",
diff --git a/package.json b/package.json
index e354da93825ab0839f017a113dacfe453884496e..0ec65a9cb1fbf857590a549731a5ea76fd2682db 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "geocontrib-frontend",
-  "version": "3.1.0",
+  "version": "3.2.0",
   "private": true,
   "scripts": {
     "serve": "npm run init-proxy & npm run init-serve",
@@ -21,6 +21,7 @@
     "@turf/helpers": "^6.5.0",
     "axios": "^0.21.1",
     "core-js": "^3.20.2",
+    "csvtojson": "^2.0.10",
     "lodash": "^4.17.21",
     "ol": "6.8.1",
     "ol-mapbox-style": "^6.8.3",
diff --git a/src/assets/js/utils.js b/src/assets/js/utils.js
index 0680fcdfad82586d6325e4cc501bf9873abf4d7c..85e7a1b11496402eb57d688f13dcd2d187a90665 100644
--- a/src/assets/js/utils.js
+++ b/src/assets/js/utils.js
@@ -12,30 +12,4 @@ export function fileConvertSizeToMo(aSize){
   aSize = Math.abs(parseInt(aSize, 10));
   const def = [1024*1024, 'Mo', 1];
   return (aSize/def[0]).toFixed(def[2]);
-}
-
-export function csvToJson(csv, delimiter) {
-  const result = [];
-
-  const allLines = csv.split('\n');
-  const headers = allLines[0].split(delimiter).map(el => {
-    return el.replace('\r', '');
-  });
-  const [, ...lines] = allLines;
-
-  for (const line of lines) {
-    if (line) {
-      const obj = {};
-      const currentLine = line.split(delimiter).map(el => {
-        return el.replace('\r', '');
-      });
-
-      for (let i = 0; i < headers.length; i++) {
-        obj[headers[i]] = currentLine[i];
-      }
-
-      result.push(obj);
-    }
-  }
-  return JSON.parse(JSON.stringify(result));
-}
+}
\ No newline at end of file
diff --git a/src/components/Account/UserProjectsList.vue b/src/components/Account/UserProjectsList.vue
index 3d7302a19b701d5d3d7363424e1459f849b69017..4e26440a571bfb10b66cb2d96ac28b4d09b26378 100644
--- a/src/components/Account/UserProjectsList.vue
+++ b/src/components/Account/UserProjectsList.vue
@@ -47,11 +47,11 @@
             <div class="description">
               <p>{{ project.description }}</p>
             </div>
-            <div class="meta">
+            <div class="meta top">
               <span
                 class="right floated"
               >
-                Projet {{ project.moderation ? "" : "non" }} modéré
+                <strong>Projet {{ project.moderation ? "" : "non" }} modéré</strong>
               </span>
               <span>
                 Niveau d'autorisation requis : {{ project.access_level_pub_feature }}
@@ -228,6 +228,12 @@ export default {
   }
 }
 
+.description {
+  p {
+    text-align: justify;
+  }
+}
+
 @media only screen and (min-width: 767px) {
   .item-content-wrapper {
     align-items: flex-start;
@@ -235,6 +241,12 @@ export default {
     .middle.aligned.content {
       width: 100%;
       padding: 0 0 0 1.5em;
+
+      .meta.top {
+        span {
+          line-height: 1.2em;
+        }
+      }
     }
   }
 }
@@ -244,8 +256,25 @@ export default {
     align-items: center;
 
     .middle.aligned.content {
-      width: 70%;
+      width: 80%;
       padding: 1.5em 0 0;
+
+      .meta.top {
+        display: flex;
+        flex-direction: column;
+        align-items: flex-start;
+        justify-content: center;
+
+        .right.floated {
+          float: none !important;
+          margin-left: 0 !important;
+          margin-bottom: 0.5em;
+        }
+
+        span {
+          margin: 0.15em 0;
+        }
+      }
     }
   }
 }
diff --git a/src/components/Feature/Detail/FeatureComments.vue b/src/components/Feature/Detail/FeatureComments.vue
index a6d633779638868f9b562d7c435c2776e45f2502..c47c5ec5659b72699b3dffe4bcae00a6f853c892 100644
--- a/src/components/Feature/Detail/FeatureComments.vue
+++ b/src/components/Feature/Detail/FeatureComments.vue
@@ -207,10 +207,12 @@ export default {
       'user',
       'isOnline',
     ]),
-
     ...mapGetters([
       'permissions',
     ]),
+    ...mapState('feature', [
+      'currentFeature',
+    ]),
 
     DJANGO_BASE_URL() {
       return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
@@ -231,14 +233,14 @@ export default {
       if (this.validateForm()) {
         featureAPI
           .postComment({
-            featureId: this.$route.params.slug_signal,
+            featureId: this.currentFeature.feature_id,
             comment: this.comment_form.comment.value,
           })
           .then((response) => {
             if (response && this.comment_form.attachment_file.file) {
               featureAPI
                 .postCommentAttachment({
-                  featureId: this.$route.params.slug_signal,
+                  featureId: this.currentFeature.feature_id,
                   file: this.comment_form.attachment_file.file,
                   fileName: this.comment_form.attachment_file.fileName,
                   title: this.comment_form.attachment_file.title,
diff --git a/src/components/Feature/Detail/FeatureHeader.vue b/src/components/Feature/Detail/FeatureHeader.vue
index d1ab51b94908bffa2dae66f0f15f177802f1c326..c3784191e44c1abd730f914ceadb8f5189003dc2 100644
--- a/src/components/Feature/Detail/FeatureHeader.vue
+++ b/src/components/Feature/Detail/FeatureHeader.vue
@@ -2,152 +2,163 @@
   <div>
     <h1 class="ui header">
       <div class="content">
-        <span
-          v-if="fastEditionMode && form"
-          class="form ui half-block"
-        >
-          <input
-            id="feature_detail_title_input"
-            :value="form.title"
-            type="text"
-            required
-            maxlength="128"
-            name="title"
-            @blur="updateTitle"
-          >
-        </span>
-        <span v-else>
-          {{ currentFeature.title || currentFeature.feature_id }}
-        </span>
-
-        <div class="ui icon right floated compact buttons">
-          <router-link
-            v-if="displayToListButton"
-            id="feature-detail-to-features-list"
-            :to="{
-              name: 'liste-signalements',
-              params: { slug: $route.params.slug },
-            }"
-            custom
-          >
-            <div class="ui button tiny-margin teal">
-              <i class="ui icon arrow right" />
-              Retour à la liste des signalements
-            </div>
-          </router-link>
-          <span
-            v-if="featuresCount"
-            id="feature-count"
-            class="ui button tiny-margin basic"
-          >
-            {{ parseInt($route.query.offset) + 1 }} sur {{ featuresCount }}
-          </span>
-          <button
-            v-if="queryparams"
-            id="previous-feature"
-            :class="['ui button button-hover-green tiny-margin', { disabled: queryparams.previous < 0 }]"
-            data-tooltip="Voir le précédent signalement"
-            data-position="bottom center"
-            @click="toFeature('previous')"
+        <div class="two-block">
+          <div
+            v-if="fastEditionMode && form && canEditFeature"
+            class="form ui half-block"
           >
-            <i
-              class="angle left fitted icon"  
-              aria-hidden="true"
-            />
-          </button>
-          <button
-            v-if="queryparams"
-            id="next-feature"
-            :class="[
-              'ui button button-hover-green tiny-margin',
-              { disabled: queryparams.next >= featuresCount }
-            ]"
-            data-tooltip="Voir le prochain signalement"
-            data-position="bottom center"
-            @click="toFeature('next')"
+            <input
+              id="feature_detail_title_input"
+              :value="form.title"
+              type="text"
+              required
+              maxlength="128"
+              name="title"
+              @blur="updateTitle"
+            >
+          </div>
+          <div
+            v-else
+            class="ellipsis"
           >
-            <i
-              class="angle right fitted icon"
-              aria-hidden="true"
-            />
-          </button>
+            {{ currentFeature.title || currentFeature.feature_id }}
+          </div>
 
-          <button
-            v-if="fastEditionMode && userCanFastEdit"
-            id="previous-feature"
-            :class="['ui button button-hover-orange tiny-margin', { disabled: false }]"
-            data-tooltip="Enregistrer les modifications"
-            data-position="bottom center"
-            @click="$store.dispatch('feature/SEND_FEATURE', $route.name)"
+          <div
+            id="feature-actions"
+            class="ui icon compact buttons"
           >
-            <i
-              class="save fitted icon"  
-              aria-hidden="true"
-            />
-          </button>
+            <div>
+              <router-link
+                v-if="displayToListButton"
+                id="feature-detail-to-features-list"
+                :to="{
+                  name: 'liste-signalements',
+                  params: { slug: $route.params.slug },
+                }"
+                custom
+              >
+                <div class="ui button tiny-margin teal">
+                  <i class="ui icon arrow right" />
+                  Retour à la liste des signalements
+                </div>
+              </router-link>
+            </div>
+            <div>
+              <span
+                v-if="featuresCount"
+                id="feature-count"
+                class="ui button tiny-margin basic disabled no-opacity"
+              >
+                {{ parseInt($route.query.offset) + 1 }} sur {{ featuresCount }}
+              </span>
+              <button
+                v-if="queryparams"
+                id="previous-feature"
+                :class="['ui button button-hover-green tiny-margin', { disabled: queryparams.previous < 0 }]"
+                data-tooltip="Voir le précédent signalement"
+                data-position="bottom center"
+                @click="toFeature('previous')"
+              >
+                <i
+                  class="angle left fitted icon"
+                  aria-hidden="true"
+                />
+              </button>
+              <button
+                v-if="queryparams"
+                id="next-feature"
+                :class="[
+                  'ui button button-hover-green tiny-margin',
+                  { disabled: queryparams.next >= featuresCount }
+                ]"
+                data-tooltip="Voir le prochain signalement"
+                data-position="bottom center"
+                @click="toFeature('next')"
+              >
+                <i
+                  class="angle right fitted icon"
+                  aria-hidden="true"
+                />
+              </button>
+            </div>
+            <div>
+              <button
+                v-if="fastEditionMode && canEditFeature"
+                id="save-fast-edit"
+                :class="['ui button button-hover-orange tiny-margin', { disabled: false }]"
+                data-tooltip="Enregistrer les modifications"
+                data-position="bottom center"
+                @click="validateFastEdition"
+              >
+                <i
+                  class="save fitted icon"
+                  aria-hidden="true"
+                />
+              </button>
 
-          <router-link
-            v-if="permissions && permissions.can_create_feature"
-            id="add-feature"
-            :to="{
-              name: 'ajouter-signalement',
-              params: {
-                slug_type_signal: $route.params.slug_type_signal || featureType.slug,
-              },
-            }"
-            class="ui button button-hover-green tiny-margin"
-            data-tooltip="Ajouter un signalement"
-            data-position="bottom center"
-          >
-            <i
-              class="plus icon"
-              aria-hidden="true"
-            />
-          </router-link>
+              <router-link
+                v-if="permissions && permissions.can_create_feature"
+                id="add-feature"
+                :to="{
+                  name: 'ajouter-signalement',
+                  params: {
+                    slug_type_signal: $route.params.slug_type_signal || featureType.slug,
+                  },
+                }"
+                class="ui button button-hover-green tiny-margin"
+                data-tooltip="Ajouter un signalement"
+                data-position="bottom center"
+              >
+                <i
+                  class="plus icon"
+                  aria-hidden="true"
+                />
+              </router-link>
 
-          <router-link
-            v-if="slugSignal &&
-              ((permissions && permissions.can_update_feature) ||
-                isFeatureCreator ||
-                isModerator)
-            "
-            id="edit-feature"
-            :to="{
-              name: 'editer-signalement',
-              params: {
-                slug_signal: slugSignal,
-                slug_type_signal: $route.params.slug_type_signal || featureType.slug,
-              },
-              query: $route.query
-            }"
-            class="ui button button-hover-orange tiny-margin"
-            data-tooltip="Éditer le signalement"
-            data-position="bottom center"
-          >
-            <i
-              class="inverted grey pencil alternate icon"
-              aria-hidden="true"
-            />
-          </router-link>
+              <router-link
+                v-if="slugSignal && canEditFeature"
+                id="edit-feature"
+                :to="{
+                  name: 'editer-signalement',
+                  params: {
+                    slug_signal: slugSignal,
+                    slug_type_signal: $route.params.slug_type_signal || featureType.slug,
+                  },
+                  query: $route.query
+                }"
+                class="ui button button-hover-orange tiny-margin"
+                data-tooltip="Éditer le signalement"
+                data-position="bottom center"
+              >
+                <i
+                  class="inverted grey pencil alternate icon"
+                  aria-hidden="true"
+                />
+              </router-link>
 
-          <a
-            v-if="((permissions && permissions.can_update_feature) || isFeatureCreator) && isOnline"
-            id="currentFeature-delete"
-            class="ui button button-hover-red tiny-margin"
-            data-tooltip="Supprimer le signalement"
-            data-position="bottom right"
-            @click="$emit('setIsCancelling')"
-          >
-            <i
-              class="inverted grey trash alternate icon"
-              aria-hidden="true"
-            />
-          </a>
+              <a
+                v-if="canDeleteFeature && isOnline"
+                id="currentFeature-delete"
+                class="ui button button-hover-red tiny-margin"
+                data-tooltip="Supprimer le signalement"
+                data-position="bottom right"
+                @click="$emit('setIsDeleting')"
+              >
+                <i
+                  class="inverted grey trash alternate icon"
+                  aria-hidden="true"
+                />
+              </a>
+            </div>
+          </div>
         </div>
-        <div class="ui hidden divider" />
+
+        <!-- <div class="ui hidden divider" /> -->
+
         <div class="sub header prewrap">
           <span
-            v-if="fastEditionMode && form"
+            v-if="fastEditionMode && canEditFeature && form"
             class="form ui half-block"
           >
             <textarea
@@ -196,12 +207,23 @@ export default {
       type: Boolean,
       default: false,
     },
+    isFeatureCreator: {
+      type: Boolean,
+      default: false,
+    },
+    canEditFeature: {
+      type: Boolean,
+      default: false,
+    },
+    canDeleteFeature: {
+      type: Boolean,
+      default: false,
+    },
   },
 
   computed: {
     ...mapState([
       'user',
-      'USER_LEVEL_PROJECTS',
       'isOnline',
     ]),
     ...mapState('feature', [
@@ -212,24 +234,6 @@ export default {
       'permissions',
     ]),
 
-    isFeatureCreator() {
-      if (this.currentFeature && this.user) {
-        return this.currentFeature.creator === this.user.id;
-      }
-      return false;
-    },
-
-    isModerator() {
-      return this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.$route.params.slug] === 'Modérateur';
-    },
-
-    userCanFastEdit() {
-      const superiorRoles = ['contributor', 'super_contributor', 'moderator', 'admin'];
-      return this.USER_LEVEL_PROJECTS &&
-        superiorRoles.includes(this.USER_LEVEL_PROJECTS[this.$route.params.slug]) ||
-          this.user.is_superuser;
-    },
-
     queryparams() {
       return this.$route.query.offset >= 0 ? {
         previous: parseInt(this.$route.query.offset) - 1,
@@ -258,26 +262,45 @@ export default {
 
     updateDescription(e) {
       this.$store.commit('feature/UPDATE_FORM_FIELD', { name: 'description', value: e.target.value });
+    },
+
+    validateFastEdition() {
+      this.$store.dispatch('feature/SEND_FEATURE', this.$route.name)
+        .then(() => this.$emit('updateEvents'));
     }
   }
 };
 </script>
 
 <style>
-#next-feature {
-  margin-right: .5rem !important;
-}
 #feature-detail-to-features-list {
   line-height: 0;
   margin-right: 5px;
 }
-.half-block {
-  display: inline-block;
-  width: 50%;
-}
 #feature_detail_title_input {
   font-weight: bold;
   font-size: 2em;
   padding: .25em;
 }
+.two-block {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: .5em;
+}
+#feature-actions > div {
+  margin-left: .5rem;
+}
+#feature-actions .no-opacity {
+  opacity: 1 !important; /* overide disabled low opacity to customize button style */
+}
+
+@media screen and (max-width: 700px) {
+  .two-block {
+    flex-direction: column-reverse;
+  }
+  #feature-actions.ui.buttons {
+    flex-direction: column;
+    align-items: flex-end;
+  }
+}
 </style>
\ No newline at end of file
diff --git a/src/components/Feature/Detail/FeatureTable.vue b/src/components/Feature/Detail/FeatureTable.vue
index fd124755b3a821ca21da6b3eaab97b48a6e1a627..b0e4a8b8e963be24bac5a544dd6464c03d244929 100644
--- a/src/components/Feature/Detail/FeatureTable.vue
+++ b/src/components/Feature/Detail/FeatureTable.vue
@@ -23,7 +23,8 @@
           <td>
             <strong class="ui form">
               <span
-                v-if="fastEditionMode && extra_forms.length > 0"
+                v-if="fastEditionMode && canEditFeature && extra_forms.length > 0"
+                :id="field.label"
               >
                 <FeatureExtraForm
                   :field="getExtraForm(field)"
@@ -60,7 +61,7 @@
               aria-hidden="true"
             />
             <FeatureEditStatusField
-              v-if="fastEditionMode && form"
+              v-if="fastEditionMode && canEditFeature && form"
               :status="form.status.value"
               class="inline"
             />
@@ -115,6 +116,13 @@
             {{ link.feature_to.created_on }})
           </td>
         </tr>
+        <tr v-if="linked_features.length === 0">
+          <td>
+            <em>
+              Aucune liaison associée au signalement.
+            </em>
+          </td>
+        </tr>
       </tbody>
     </table>
   </div>
@@ -154,7 +162,11 @@ export default {
     fastEditionMode: {
       type: Boolean,
       default: false,
-    }
+    },
+    canEditFeature: {
+      type: Boolean,
+      default: false,
+    },
   },
 
   computed: {
diff --git a/src/components/Feature/Edit/FeatureExtraForm.vue b/src/components/Feature/Edit/FeatureExtraForm.vue
index c578e5c75f6642764c7c6204a8e61e0713462c49..36f4130b18a0fb6a3503c35eb256e1ef209d3bd7 100644
--- a/src/components/Feature/Edit/FeatureExtraForm.vue
+++ b/src/components/Feature/Edit/FeatureExtraForm.vue
@@ -1,9 +1,7 @@
 <template>
-  <div
-    v-if="field && field.field_type === 'char'"
-  >
+  <div v-if="field && field.field_type === 'char'">
     <label
-      v-if="$route.name === 'editer-signalement'"
+      v-if="displayLabels"
       :for="field.name"
     >
       {{ field.label }}
@@ -17,11 +15,9 @@
     >
   </div>
 
-  <div
-    v-else-if="field && field.field_type === 'list'"
-  >
+  <div v-else-if="field && field.field_type === 'list'">
     <label
-      v-if="$route.name === 'editer-signalement'"
+      v-if="displayLabels"
       :for="field.name"
     >
       {{ field.label }}
@@ -32,11 +28,9 @@
       :selection.sync="selected_extra_form_list"
     />
   </div>
-  <div
-    v-else-if="field && field.field_type === 'integer'"
-  >
+  <div v-else-if="field && field.field_type === 'integer'">
     <label
-      v-if="$route.name === 'editer-signalement'"
+      v-if="displayLabels"
       :for="field.name"
     >
       {{ field.label }}
@@ -52,9 +46,7 @@
       >
     </div>
   </div>
-  <div
-    v-else-if="field && field.field_type === 'boolean'"
-  >
+  <div v-else-if="field && field.field_type === 'boolean'">
     <div class="ui checkbox">
       <input
         :id="field.name"
@@ -63,19 +55,14 @@
         :name="field.name"
         @change="updateStore_extra_form"
       >
-      <label
-        v-if="$route.name === 'editer-signalement'"
-        :for="field.name"
-      >
-        {{ field.label }}
+      <label :for="field.name">
+        {{ displayLabels ? field.label : '' }}
       </label>
     </div>
   </div>
-  <div
-    v-else-if="field && field.field_type === 'date'"
-  >
+  <div v-else-if="field && field.field_type === 'date'">
     <label
-      v-if="$route.name === 'editer-signalement'"
+      v-if="displayLabels"
       :for="field.name"
     >
       {{ field.label }}
@@ -88,11 +75,9 @@
       @blur="updateStore_extra_form"
     >
   </div>
-  <div
-    v-else-if="field && field.field_type === 'decimal'"
-  >
+  <div v-else-if="field && field.field_type === 'decimal'">
     <label
-      v-if="$route.name === 'editer-signalement'"
+      v-if="displayLabels"
       :for="field.name"
     >
       {{ field.label }}
@@ -108,11 +93,9 @@
       >
     </div>
   </div>
-  <div
-    v-else-if="field && field.field_type === 'text'"
-  >
+  <div v-else-if="field && field.field_type === 'text'">
     <label
-      v-if="$route.name === 'editer-signalement'"
+      v-if="displayLabels"
       :for="field.name"
     >
       {{ field.label }}
@@ -155,6 +138,10 @@ export default {
         this.$store.commit('feature/UPDATE_EXTRA_FORM', newExtraForm);
       },
     },
+
+    displayLabels() {
+      return this.$route.name === 'editer-signalement' || this.$route.name === 'ajouter-signalement';
+    }
   },
 
   methods: {
diff --git a/src/components/Feature/FeatureEditStatusField.vue b/src/components/Feature/FeatureEditStatusField.vue
index 806182c3a835cd04a9b8a3d36c3531b7bb7b94bb..3c4133b5c7ff9b8a459ebca9733bd74268604c98 100644
--- a/src/components/Feature/FeatureEditStatusField.vue
+++ b/src/components/Feature/FeatureEditStatusField.vue
@@ -1,5 +1,8 @@
 <template>
-  <div class="field">
+  <div
+    id="status"
+    class="field"
+  >
     <Dropdown
       v-if="selectedStatus"
       :options="allowedStatusChoices"
@@ -56,7 +59,7 @@ export default {
     allowedStatusChoices() {
       if (this.project && this.currentFeature && this.user) {
         const isModerate = this.project.moderation;
-        const userStatus = this.USER_LEVEL_PROJECTS[this.project.slug];
+        const userStatus = this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.project.slug];
         const isOwnFeature = this.currentFeature.creator === this.user.id; //* si le contributeur est l'auteur du signalement
         return allowedStatus2change(this.user, isModerate, userStatus, isOwnFeature, /* this.currentRouteName */);
       }
diff --git a/src/components/FeatureType/FeatureTypeCustomForm.vue b/src/components/FeatureType/FeatureTypeCustomForm.vue
index d064ee6ca7a27c946f98d3543e65621a946da83b..51f068a1460a50452a0bd20da3def1f4302fb3ea 100644
--- a/src/components/FeatureType/FeatureTypeCustomForm.vue
+++ b/src/components/FeatureType/FeatureTypeCustomForm.vue
@@ -312,7 +312,7 @@ export default {
 
     fillCustomFormData(customFormData) {
       for (const el in customFormData) {
-        if (el && this.form[el] && customFormData[el]) {
+        if (el && this.form[el] && customFormData[el] !== undefined && customFormData[el] !== null) {
           //* check if is an object, because data from api is a string, while import from django is an object
           this.form[el].value = customFormData[el].value
             ? customFormData[el].value
@@ -365,53 +365,46 @@ export default {
       return occurences.length === 1;
     },
 
-    checkFilledOptions() {
-      if (this.form.field_type.value === 'list') {
-        if (this.form.options.value.length < 1) {
-          return false;
-        } else if (
-          this.form.options.value.length === 1 &&
-          this.form.options.value[0] === ''
-        ) {
-          return false;
-        }
-      }
-      return true;
+    checkListOptions() {
+      if (this.form.field_type.value !== 'list') return true;
+      return this.form.options.value.length >= 2 && !this.form.options.value.includes('');
     },
 
     checkCustomForm() {
       this.form.label.errors = [];
       this.form.name.errors = [];
       this.form.options.errors = [];
+      let isValid = true;
       if (!this.form.label.value) {
         //* vérifier que le label est renseigné
         this.form.label.errors = ['Veuillez compléter ce champ.'];
-        return false;
+        isValid = false;
       } else if (!this.form.name.value) {
         //* vérifier que le nom est renseigné
         this.form.name.errors = ['Veuillez compléter ce champ.'];
-        return false;
+        isValid = false;
       } else if (!this.hasRegularCharacters(this.form.name.value)) {
         //* vérifier qu'il n'y a pas de caractères spéciaux
         this.form.name.errors = [
           'Veuillez utiliser seulement les caratères autorisés.',
         ];
-        return false;
+        isValid = false;
       } else if (!this.checkUniqueName()) {
         //* vérifier si les noms sont pas dupliqués
         this.form.name.errors = [
           'Les champs personnalisés ne peuvent pas avoir des noms similaires.',
         ];
-        return false;
-      } else if (!this.checkFilledOptions()) {
+        isValid = false;
+      } else if (!this.checkListOptions()) {
         //* s'il s'agit d'un type liste, vérifier que le champ option est bien renseigné
         this.form.options.errors = ['Veuillez compléter ce champ.'];
-        return false;
+        isValid = false;
       } else if (this.hasDuplicateOptions()) {
         //* pour le cas d'options dupliqués
-        return false;
+        isValid = false;
       }
-      return true;
+      if (!isValid) document.getElementById(`custom_form-${this.form.position.value}`).scrollIntoView({ block: 'start', inline: 'nearest' });
+      return isValid;
     },
   },
 };
diff --git a/src/components/ImportTask.vue b/src/components/ImportTask.vue
index 026b7cbfe30e8b8ffcc4e7354b33642f2a7b13ec..c5792c73764138b2c6d00795e1109a1222dda73f 100644
--- a/src/components/ImportTask.vue
+++ b/src/components/ImportTask.vue
@@ -26,7 +26,9 @@
           <td>
             <h4 class="ui header align-right">
               <div :data-tooltip="importFile.geojson_file_name">
-                {{ importFile.geojson_file_name | subString }}
+                <div class="ellipsis">
+                  {{ importFile.geojson_file_name | subString }}
+                </div>
                 <div class="sub header">
                   ajouté le {{ importFile.created_on | setDate }}
                 </div>
@@ -234,5 +236,9 @@ and also iPads specifically.
   .margin-left {
     margin-left: 94%;
   }
+  h4.ui.header {
+    margin-left: 60px;
+    white-space: nowrap;
+  }
 }
 </style>
diff --git a/src/components/Project/Detail/ProjectFeatureTypes.vue b/src/components/Project/Detail/ProjectFeatureTypes.vue
index 7cb7968533a7c1f3a17d9d919486a52e05b3075b..f98acb30e2b0eb3b59d8be7f9adca67a805e03a8 100644
--- a/src/components/Project/Detail/ProjectFeatureTypes.vue
+++ b/src/components/Project/Detail/ProjectFeatureTypes.vue
@@ -426,10 +426,11 @@
 </template>
 
 <script>
+import { csv } from 'csvtojson';
 
 import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
 
-import { fileConvertSizeToMo, csvToJson } from '@/assets/js/utils';
+import { fileConvertSizeToMo } from '@/assets/js/utils';
 import FeatureTypeLink from '@/components/FeatureType/FeatureTypeLink';
 
 export default {
@@ -633,7 +634,6 @@ export default {
         try {
           fr.readAsText(this.csvFileToImport);
           fr.onloadend = () => {
-
             // Find csv delimiter
             const commaDelimited = fr.result.split('\n')[0].includes(',');
             const semicolonDelimited = fr.result.split('\n')[0].includes(';');
@@ -644,19 +644,16 @@ export default {
               this.featureTypeImporting = false;
               return;
             }
-
             // Check if file contains 'lat' and 'long' fields
-            const headersLine =
-              fr.result
-                .split('\n')[0]
-                .split(delimiter)
-                .map(el => {
-                  return el.replace('\r', '');
-                })
-                .filter(el => {
-                  return el === 'lat' || el === 'lon';
-                });
-
+            const headers = fr.result
+              .split('\n')[0]
+              .split(delimiter)
+              .map(el => {
+                return el.replace('\r', '');
+              });
+            const headersCoord = headers.filter(el => {
+              return el === 'lat' || el === 'lon';
+            });
             // Look for 2 decimal fields in first line of csv
             // corresponding to lon and lat
             const sampleLine =
@@ -667,9 +664,13 @@ export default {
                   return !isNaN(el) && el.indexOf('.') !== -1;
                 })
                 .filter(Boolean);
-            if (sampleLine.length > 1 && headersLine.length === 2) {
+            if (sampleLine.length > 1 && headersCoord.length === 2) {
               this.csvError = null;
-              this.csvImport = csvToJson(fr.result, delimiter);
+              csv()
+                .fromString(fr.result)
+                .then((jsonObj)=>{
+                  this.csvImport = jsonObj;
+                });
               this.featureTypeImporting = false;
               //* stock filename to import features afterward
               this.SET_FILE_TO_IMPORT(this.csvFileToImport);
diff --git a/src/components/Project/FeaturesListAndMap/FeatureListTable.vue b/src/components/Project/FeaturesListAndMap/FeatureListTable.vue
index fff79a991ecb1479945455bb91747a99b68c5ed4..69cd85d84687014a27a3c979b22cff889214d757 100644
--- a/src/components/Project/FeaturesListAndMap/FeatureListTable.vue
+++ b/src/components/Project/FeaturesListAndMap/FeatureListTable.vue
@@ -207,6 +207,7 @@
                     feature_type_slug: feature.feature_type.slug,
                   },
                 }"
+                class="ellipsis space-left"
               >
                 {{ feature.feature_type.title }}
               </router-link>
@@ -220,6 +221,7 @@
                   },
                   query: { ...queryparams, offset: queryparams.offset + index }
                 }"
+                class="ellipsis space-left"
               >
                 {{ feature.title || feature.feature_id }}
               </router-link>
@@ -465,7 +467,9 @@ export default {
         Contributeur : ['draft', 'pending', 'published'],
       };
 
-      if (this.userStatus === 'Contributeur' && feature.display_creator !== this.user.username) {
+      if (this.user.is_superuser) {
+        return true;
+      } else if (this.userStatus === 'Contributeur' && feature.display_creator !== `${this.user.first_name} ${this.user.last_name}`) {
         return false;
       } else if (permissions[this.userStatus]) {
         return permissions[this.userStatus].includes(feature.status);
@@ -725,6 +729,11 @@ and also iPads specifically.
     text-align: center;
     margin: .5em 0;
   }
+  .space-left {
+    max-width: 100%;
+    display: inline-block;
+    padding-left: 3em;
+  }
 }
 @media only screen and (max-width: 410px) {
   .ui.table tr td {
diff --git a/src/components/Projects/ProjectsListItem.vue b/src/components/Projects/ProjectsListItem.vue
index 983186fe97b8adbe9363b8071e465896aad1ddeb..fe3e4d4e908ed04287a73b0e99a2c57f616d1c44 100644
--- a/src/components/Projects/ProjectsListItem.vue
+++ b/src/components/Projects/ProjectsListItem.vue
@@ -23,7 +23,7 @@
       <div class="description">
         <p>{{ project.description }}</p>
       </div>
-      <div class="meta">
+      <div class="meta top">
         <span class="right floated">
           <strong v-if="project.moderation">Projet modéré</strong>
           <strong v-else>Projet non modéré</strong>
@@ -110,3 +110,36 @@ export default {
 };
 
 </script>
+
+<style lang="less" scoped>
+
+.description {
+  p {
+    text-align: justify;
+  }
+}
+
+@media screen and (max-width: 767px) {
+  .content {
+    width: 90% !important;
+
+    .meta.top {
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+      justify-content: center;
+
+      .right.floated {
+        float: none !important;
+        margin-left: 0 !important;
+        margin-bottom: 0.5em;
+      }
+
+      span {
+        margin: 0.15em 0;
+      }
+    }
+  }
+}
+
+</style>
diff --git a/src/components/Projects/ProjectsMenu.vue b/src/components/Projects/ProjectsMenu.vue
index 442ab3a5a7e68e24e0c26a5a4ec8cad38f6d0a64..ece71b68de5d7d37283af90a410e65280395f4a9 100644
--- a/src/components/Projects/ProjectsMenu.vue
+++ b/src/components/Projects/ProjectsMenu.vue
@@ -1,18 +1,21 @@
 <template>
-  <div class="filters-container">
-    <div class="ui styled accordion">
+  <div id="filters-container">
+    <div
+      class="ui styled accordion"
+      @click="displayFilters = !displayFilters"
+    >
       <div
         id="filters"
         class="title collapsible-filters"
       >
         FILTRES
         <i
-          class="ui icon caret right down"
+          :class="['ui icon customcaret', { 'collapsed': !displayFilters }]"
           aria-hidden="true"
         />
       </div>
     </div>
-    <div class="ui menu filters hidden">
+    <div :class="['ui menu filters', { 'hidden': displayFilters }]">
       <div class="item">
         <label>
           Niveau d'autorisation requis
@@ -40,7 +43,7 @@
           v-on="$listeners"
         />
       </div>
-      <div class="right item">
+      <div class="item">
         <label>
           Recherche par nom
         </label>
@@ -69,6 +72,7 @@ export default {
 
   data() {
     return {
+      displayFilters: false,
       moderationOptions: [
         {
           label: 'Tous',
@@ -157,26 +161,10 @@ export default {
     }
   },
 
-  mounted() {
-    const el = document.getElementsByClassName('collapsible-filters');
-
-    el[0].addEventListener('click', function() {
-      const icon = document.getElementsByClassName('caret');
-      icon[0].classList.toggle('right');
-      const content = document.getElementsByClassName('filters');
-      content[0].classList.toggle('hidden');
-      if (content[0].style.maxHeight){
-        content[0].style.maxHeight = null;
-      } else {
-        content[0].style.maxHeight = content[0].scrollHeight + 5 + 'px';
-      }
-    });
-  },
-
   methods: {
     ...mapActions('projects', [
       'SEARCH_PROJECTS'
-    ])
+    ]),
   }
 };
 </script>
@@ -189,7 +177,7 @@ export default {
   transition: @arguments;
 }
 
-.filters-container {
+#filters-container {
 	width: 100%;
 	display: flex;
 	flex-direction: column;
@@ -200,6 +188,23 @@ export default {
 		.collapsible-filters {
 			font-size: 1.25em;
 			padding-right: 0;
+      .customcaret{
+        transition: transform .2s ease;
+        &.collapsed {
+          transform: rotate(180deg);
+        }
+        &::before{
+          position: relative;
+          right: 0;
+          top: 65%;
+          color: #999;
+          margin-top: 4px;
+          border-color: #999 transparent transparent;
+          border-style: solid;
+          border-width: 5px 5px 0;
+          content: "";
+        }
+      }
 		}
 	}
 	.filters {
@@ -207,16 +212,16 @@ export default {
 		height:auto;
 		min-height: 0;
 		max-height:75px;
+    opacity: 1;
 		margin: 0 0 1em 0;
     border: none;
     box-shadow: none;
-		.transition-properties(max-height 0.2s ease-out;);
+		.transition-properties(all 0.2s ease-out;);
 		.item {
 			display: flex;
 			flex-direction: column;
 			align-items: flex-start !important;
-
-			padding: 0.4em 0.6em 0.4em 0;
+			padding: 0.5em;
 
 			label {
 				margin-bottom: 0.2em;
@@ -230,20 +235,43 @@ export default {
     .item::before {
 			width: 0;
 		}
-    .right.item {
-      padding-right: 0;
-      #search-projects {
-        width: 100%;
-      }
+    #search-projects {
+      width: 100%;
     }
-		.right.item::before {
-			width: 0;
-		}
 	}
 	.filters.hidden {
-		max-height: 0;
 		overflow: hidden;
-		border: none;
+    opacity: 0;
+    max-height: 0;
 	}
 }
+
+@media screen and (min-width: 701px) {
+  .item {
+    &:first-child {
+      padding-left: 0;
+    }
+    &:last-child {
+      padding-right: 0;
+    }
+  }
+}
+
+@media screen and (max-width: 700px) {
+  #filters-container {
+
+    .filters {
+      display: flex;
+      flex-direction: column;
+      max-height: 275px;
+      .transition-properties(all 0.2s ease-out;);
+
+      .item {
+        width: 100%;
+        padding-right: 0;
+        padding-left: 0;
+      }
+    }
+  }
+}
 </style>
diff --git a/src/main.js b/src/main.js
index 3b9bb6b8e077630ae2382f9dbcdb3cd6aa34df91..cabb80a8ddc6293333db579cbb747d5852b9590b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -44,7 +44,7 @@ const onConfigLoaded = function(config){
   store.commit('SET_CONFIG', config);
   setInterval(() => { //* check if navigator is online
     store.commit('SET_IS_ONLINE', navigator.onLine);
-  }, 5000);
+  }, 2000);
 
   // set title and favico
   document.title = `${config.VUE_APP_APPLICATION_NAME} ${config.VUE_APP_APPLICATION_ABSTRACT}`;
diff --git a/src/services/map-service.js b/src/services/map-service.js
index 0bb14fa3a7276e2d9ce9a354d9eb4e0e3e584e2a..63e4204c00689cba3b4120faf5aa77b7abc72ef1 100644
--- a/src/services/map-service.js
+++ b/src/services/map-service.js
@@ -51,6 +51,7 @@ const mapService = {
       lng,
       mapDefaultViewCenter,
       mapDefaultViewZoom,
+      maxZoom,
       zoom,
       zoomControl = true,
       interactions = { doubleClickZoom: false, mouseWheelZoom: false, dragPan: true },
@@ -60,7 +61,7 @@ const mapService = {
       el.innerHTML = '';
     }
 
-    this.map = new Map({
+    const mapOptions = {
       layers: [],
       target: el,
       controls: [
@@ -70,14 +71,17 @@ const mapService = {
         })],
       interactions: defaults(interactions),
       view: new View({
-        center: transform([
-          !lng ? mapDefaultViewCenter[1] : lng,
-          !lat ? mapDefaultViewCenter[0] : lat,
+        center: transform([ //* since 0 is considered false, check for number instead of just defined (though boolean will pass through)
+          Number(lng) ? lng : mapDefaultViewCenter[1],
+          Number(lat) ? lat : mapDefaultViewCenter[0],
 
         ], 'EPSG:4326', 'EPSG:3857'),
-        zoom: !zoom ? mapDefaultViewZoom : zoom
+        zoom: Number(mapDefaultViewZoom) ? mapDefaultViewZoom : zoom,
+        maxZoom
       }),
-    });
+    };
+
+    this.map = new Map(mapOptions);
 
     if (zoomControl) {
       this.map.addControl(new Zoom({ zoomInTipLabel: 'Zoomer', zoomOutTipLabel: 'Dézoomer' }));
@@ -557,7 +561,12 @@ const mapService = {
                   </div>
                   ${author}
                   `;
-    const featureId = feature.getProperties ? feature.getProperties().feature_id || feature.getId() : feature.id; //* feature.id was used with leaflet, with ol feature.getId replace it, but keeping it as fallback can prevent regression
+    const featureId =
+      feature.getId() ?
+        feature.getId() :
+        feature.getProperties ?
+          feature.getProperties().feature_id :
+          feature.id;
     return { html, feature_type, featureId };
   },
 
diff --git a/src/store/modules/feature.store.js b/src/store/modules/feature.store.js
index ef93083a8691c11c3de37a5dbd79082f3058b67f..2466c7c6ad6e4b2c81188aadf9fe694b73d96e6f 100644
--- a/src/store/modules/feature.store.js
+++ b/src/store/modules/feature.store.js
@@ -215,7 +215,6 @@ const feature = {
               params: {
                 slug_type_signal: rootState['feature-type'].current_feature_type_slug,
                 slug_signal: featureId,
-                message: routeName === 'ajouter-signalement' ? 'Le signalement a été crée' : 'Le signalement a été mis à jour'
               },
             });
           });
@@ -232,7 +231,6 @@ const feature = {
         for (const field of state.extra_forms) {
           extraFormObject[field.name] = field.value;
         }
-        //const feature = state.form || state.currentFeature;
         return {
           id: state.form.feature_id || state.currentFeature.feature_id,
           type: 'Feature',
diff --git a/src/store/modules/map.store.js b/src/store/modules/map.store.js
index a8b3f37b29b4f49fef161c4d5f47d192182e7030..3ae7994329dc93751cd400d5c39892bf842b2850 100644
--- a/src/store/modules/map.store.js
+++ b/src/store/modules/map.store.js
@@ -104,12 +104,13 @@ const map = {
         });
     },
 
-    INITIATE_MAP({ commit }, el) { //todo: since this function is not anymore called in different components, it would better to move it in project_details.vue
+    INITIATE_MAP({ commit, rootState }, el) { //todo: since this function is not anymore called in different components, it would better to move it in project_details.vue
       const mapDefaultViewCenter = [46, 2]; // defaultMapView.center;
       const mapDefaultViewZoom = 5; // defaultMapView.zoom;
       mapService.createMap(el, {
         mapDefaultViewCenter: mapDefaultViewCenter,
         mapDefaultViewZoom: mapDefaultViewZoom,
+        maxZoom: rootState.projects.project.map_max_zoom_level,
       });
       const map = { ...mapService.getMap() };
       commit('SET_MAP', map);
diff --git a/src/utils/index.js b/src/utils/index.js
index 432cd2970a33e4be66ba5293a3501b9e7f76b2d5..a89354b8305aaae4b1d12e04003990b7e25cda55 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -58,4 +58,29 @@ export function allowedStatus2change(user, isModerate, userStatus, isOwnFeature,
     }
   }
   return [];
+}
+
+export function transformProperties(prop) {
+  const type = typeof prop;
+  const date = new Date(prop);
+  const regInteger = /^-*?\d+$/;
+  const regFloat = /^-*?\d*?\.\d+$/;
+  const regText = /[\r\n]/;
+  if (type === 'boolean' || prop.toLowerCase() === 'true' || prop.toLowerCase() === 'False') {
+    return 'boolean';
+  } else if (regInteger.test(prop) || Number.isSafeInteger(prop)) {
+    return 'integer';
+  } else if (
+    type === 'string' &&
+    ['/', ':', '-'].some((el) => prop.includes(el)) && // check for chars found in datestring
+    date instanceof Date &&
+    !isNaN(date.valueOf())
+  ) {
+    return 'date';
+  } else if (regFloat.test(prop) ||  type === 'number' && !isNaN(parseFloat(prop))) {
+    return 'decimal';
+  } else if (regText.test(prop)) {
+    return 'text';
+  }
+  return 'char'; //* string by default, most accepted type in database
 }
\ No newline at end of file
diff --git a/src/views/Feature/FeatureDetail.vue b/src/views/Feature/FeatureDetail.vue
index dc39b0e4733573a96363fa432c3dd587fb2ad6e7..fd547578dd6184020837ccb692fce61e37dcd08f 100644
--- a/src/views/Feature/FeatureDetail.vue
+++ b/src/views/Feature/FeatureDetail.vue
@@ -13,8 +13,12 @@
             :feature-type="featureType"
             :fast-edition-mode="project.fast_edition_mode"
             :display-to-list-button="displayToListButton"
-            @setIsCancelling="isCanceling = true"
+            :is-feature-creator="isFeatureCreator"
+            :can-edit-feature="canEditFeature"
+            :can-delete-feature="canDeleteFeature"
+            @setIsDeleting="isDeleting = true"
             @tofeature="pushNgo"
+            @updateEvents="getFeatureEvents"
           />
         </div>
       </div>
@@ -24,6 +28,7 @@
             v-if="project"
             :feature-type="featureType"
             :fast-edition-mode="project.fast_edition_mode"
+            :can-edit-feature="canEditFeature"
             @tofeature="pushNgo"
           />
         </div>
@@ -60,28 +65,33 @@
           />
         </div>
       </div>
+
       <div
-        v-if="isCanceling"
+        v-if="isDeleting"
         class="ui dimmer modals visible active"
       >
         <div
           :class="[
-            'ui mini modal subscription',
-            { 'active visible': isCanceling },
+            'ui mini modal',
+            { 'active visible': isDeleting },
           ]"
         >
           <i
             class="close icon"
             aria-hidden="true"
-            @click="isCanceling = false"
+            @click="isDeleting = false"
           />
-          <div class="ui icon header">
+          <div
+            v-if="isDeleting"
+            class="ui icon header"
+          >
             <i
               class="trash alternate icon"
               aria-hidden="true"
             />
             Supprimer le signalement
           </div>
+
           <div class="actions">
             <button
               type="button"
@@ -93,7 +103,61 @@
           </div>
         </div>
       </div>
+
+      <div
+        v-if="isLeaving"
+        class="ui dimmer modals visible active"
+      >
+        <div
+          :class="[
+            'ui mini modal',
+            { 'active visible': isLeaving },
+          ]"
+        >
+          <i
+            class="close icon"
+            aria-hidden="true"
+            @click="isLeaving = false"
+          />
+          <div class="ui icon header">
+            <i
+              :class="[project.fast_edition_mode && hasUnsavedChange ? 'sign-out' : 'random', 'icon']"
+              aria-hidden="true"
+            />
+            Abandonner {{
+              project.fast_edition_mode && hasUnsavedChange ?
+                'les modifications' :
+                'la vue signalement filtré'
+            }}
+          </div>
+          <div class="content">
+            {{
+              project.fast_edition_mode && hasUnsavedChange ?
+                'Les modifications apportées au signalement ne seront pas sauvegardées, continuer ?':
+                `Vous allez quittez la vue signalement filtré,
+                  l\'ordre des signalements pourrait changer après édition d\'un signalement.`
+            }}
+          </div>
+          <div class="actions">
+            <button
+              type="button"
+              class="ui green compact button"
+              @click="stayOnPage"
+            >
+              Annuler
+            </button>
+            <button
+              type="button"
+              class="ui red compact button"
+              @click="leavePage"
+            >
+              Continuer
+            </button>
+          </div>
+        </div>
+      </div>
     </div>
+
     <div v-else>
       Pas de signalement correspondant trouvé
     </div>
@@ -101,7 +165,7 @@
 </template>
 
 <script>
-import { mapState, mapActions, mapMutations } from 'vuex';
+import { mapState, mapActions, mapMutations, mapGetters } from 'vuex';
 import mapService from '@/services/map-service';
 
 import axios from '@/axios-client.js';
@@ -136,19 +200,19 @@ export default {
   },
 
   beforeRouteUpdate (to, from, next) {
-    let leaving = true; // by default navigate to next route
     if (this.hasUnsavedChange) {
-      leaving = this.confirmLeave(); // prompt user that there is unsaved changes or that features order might change
+      this.confirmLeave(next); // prompt user that there is unsaved changes or that features order might change
+    } else {
+      next(); // continue navigation
     }
-    next(leaving);
   },
 
   beforeRouteLeave (to, from, next) {
-    let leaving = true; // by default navigate to next route
     if (this.hasUnsavedChange || (from.query.offset >= 0 && to.name === 'editer-signalement')) {
-      leaving = this.confirmLeave(); // prompt user that there is unsaved changes or that features order might change
+      this.confirmLeave(next); // prompt user that there is unsaved changes or that features order might change
+    } else {
+      next(); // continue navigation
     }
-    next(leaving);
   },
 
   data() {
@@ -170,27 +234,37 @@ export default {
       events: [],
       featureType: {},
       featuresCount: null,
-      isCanceling: false,
+      isDeleting: false,
+      isLeaving: false,
       slugSignal: '',
       displayToListButton: false,
     };
   },
 
   computed: {
+    ...mapState([
+      'USER_LEVEL_PROJECTS',
+      'user'
+    ]),
     ...mapState('projects', [
       'project'
     ]),
     ...mapState('feature-type', [
       'feature_types',
-      'feature_type',
     ]),
     ...mapState('feature', [
       'currentFeature',
       'form',
     ]),
+    ...mapGetters('feature-type', [
+      'feature_type',
+    ]),
+    ...mapGetters([
+      'permissions',
+    ]),
 
     hasUnsavedChange() {
-      if (this.form) {
+      if (this.project.fast_edition_mode && this.form && this.currentFeature) {
         if (this.form.title !== this.currentFeature.title) return true;
         if (this.form.description.value !== this.currentFeature.description) return true;
         if (this.form.status.value !== this.currentFeature.status) return true;
@@ -200,6 +274,36 @@ export default {
         }
       }
       return false;
+    },
+
+    isFeatureCreator() {
+      if (this.currentFeature && this.user) {
+        return this.currentFeature.creator === this.user.id;
+      }
+      return false;
+    },
+
+    isModerator() {
+      return this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.$route.params.slug] === 'Modérateur';
+    },
+
+    isAdministrator() {
+      return this.USER_LEVEL_PROJECTS && this.USER_LEVEL_PROJECTS[this.$route.params.slug] === 'Administrateur projet';
+    },
+
+    canEditFeature() {
+      return (this.permissions && this.permissions.can_update_feature) ||
+                this.isFeatureCreator ||
+                this.isModerator ||
+                this.user.is_superuser;
+    },
+
+    canDeleteFeature() {
+      return (this.permissions && this.permissions.can_delete_feature && this.isFeatureCreator) ||
+                this.isFeatureCreator ||
+                this.isModerator ||
+                this.isAdministrator ||
+                this.user.is_superuser;
     }
   },
 
@@ -283,10 +387,18 @@ export default {
       }
     },
 
-    confirmLeave() {
-      return window.confirm(this.project.fast_edition_mode && this.hasUnsavedChange ?
-        'Les modifications apportées au signalement ne seront pas sauvegardées, continuer ?':
-        'Vous allez quittez la vue signalement filtré, l\'ordre des signalements pourrait changer après édition d\'un signalement.');
+    confirmLeave(next) {
+      this.next = next;
+      this.isLeaving = true;
+    },
+
+    stayOnPage() {
+      this.isLeaving = false;
+    },
+
+    leavePage() {
+      this.isLeaving = false;
+      this.next();
     },
 
     async reloadPage() {
@@ -354,6 +466,7 @@ export default {
       this.map = mapService.createMap(this.$refs.map, {
         mapDefaultViewCenter,
         mapDefaultViewZoom,
+        maxZoom: this.project.map_max_zoom_level,
         interactions : {
           doubleClickZoom :false,
           mouseWheelZoom: false,
diff --git a/src/views/Feature/FeatureEdit.vue b/src/views/Feature/FeatureEdit.vue
index ce39380330ef83d5ac10aa38060f513bc8308e56..35eaa28cc17c4b6e0c15fdfb9ed527e20c95f8b0 100644
--- a/src/views/Feature/FeatureEdit.vue
+++ b/src/views/Feature/FeatureEdit.vue
@@ -264,9 +264,12 @@
       <div
         v-for="(field, index) in orderedCustomFields"
         :key="field.field_type + index"
-        class="field"
       >
-        <FeatureExtraForm :field="field" />
+        <FeatureExtraForm
+          :id="field.label"
+          :field="field"
+          class="field"
+        />
         {{ field.errors }}
       </div>
 
@@ -860,6 +863,7 @@ export default {
       this.map = mapService.createMap(this.$refs.map, {
         mapDefaultViewCenter,
         mapDefaultViewZoom,
+        maxZoom: this.project.map_max_zoom_level,
         interactions : { doubleClickZoom :false, mouseWheelZoom:true, dragPan:true }
       });
       const currentFeatureId = this.$route.params.slug_signal;
diff --git a/src/views/FeatureType/FeatureTypeDetail.vue b/src/views/FeatureType/FeatureTypeDetail.vue
index 06b302ae1983a657f27122ef07ea442b8d111e6c..97db52d8babd1925b82899592d1a4b3ee069e619 100644
--- a/src/views/FeatureType/FeatureTypeDetail.vue
+++ b/src/views/FeatureType/FeatureTypeDetail.vue
@@ -370,12 +370,14 @@
 </template>
 
 <script>
+import { csv } from 'csvtojson';
+
 import { mapActions, mapMutations, mapGetters, mapState } from 'vuex';
-import { formatStringDate } from '@/utils';
+import { formatStringDate, transformProperties } from '@/utils';
 import ImportTask from '@/components/ImportTask';
 import featureAPI from '@/services/feature-api';
 
-import { fileConvertSizeToMo, csvToJson } from '@/assets/js/utils';
+import { fileConvertSizeToMo } from '@/assets/js/utils'; // TODO: refactor with above utils, those files are similar
 
 export default {
   name: 'FeatureTypeDetail',
@@ -557,26 +559,6 @@ export default {
       }
     },
 
-    transformProperties(prop) {
-      const type = typeof prop;
-      const date = new Date(prop);
-      if (type === 'boolean') {
-        return 'boolean';
-      } else if (Number.isSafeInteger(prop)) {
-        return 'integer';
-      } else if (
-        type === 'string' &&
-        ['/', ':', '-'].some((el) => prop.includes(el)) && // check for chars found in datestring
-        date instanceof Date &&
-        !isNaN(date.valueOf())
-      ) {
-        return 'char';
-      } else if (type === 'number' && !isNaN(parseFloat(prop))) {
-        return 'decimal';
-      }
-      return 'char'; //* string by default, most accepted type in database
-    },
-
     checkJsonValidity(json) {
       this.importError = '';
       const fields = this.structure.customfield_set.map((el) => {
@@ -588,9 +570,10 @@ export default {
       });
       for (const feature of json.features) {
         for (const { name, field_type, options } of fields) {
-          if (name in feature.properties) {
-            const fieldInFeature = feature.properties[name];
-            const customType = this.transformProperties(fieldInFeature);
+          const properties = feature.properties || feature;
+          if (name in properties) {
+            const fieldInFeature = properties[name];
+            const customType = transformProperties(fieldInFeature);
             //* if custom field value is not null, then check validity of field
             if (fieldInFeature !== null) {
               //* if field type is list, it's not possible to guess from value type
@@ -613,22 +596,20 @@ export default {
       return true;
     },
 
-    checkCsvValidity(csv) {
+    async checkCsvValidity(csvString) {
       this.importError = '';
-
-      // Find csv delimiter
-      const commaDelimited = csv.split('\n')[0].includes(',');
-      const semicolonDelimited = csv.split('\n')[0].includes(';');
+      // Find csvString delimiter
+      const commaDelimited = csvString.split('\n')[0].includes(',');
+      const semicolonDelimited = csvString.split('\n')[0].includes(';');
       const delimiter = commaDelimited && !semicolonDelimited ? ',' : semicolonDelimited ?  ';' : false;
 
       if ((commaDelimited && semicolonDelimited) || !delimiter) {
         this.importError = `Le fichier ${this.csvFileToImport.name} n'est pas formaté correctement`;
         return false;
       }
-
       // Check if file contains 'lat' and 'long' fields
       const headersLine =
-        csv
+        csvString
           .split('\n')[0]
           .replace(/(\r\n|\n|\r)/gm, '')
           .split(delimiter)
@@ -640,7 +621,7 @@ export default {
         return false;
       }
       const sampleLine =
-        csv
+        csvString
           .split('\n')[1]
           .split(delimiter)
           .map(el => {
@@ -648,55 +629,8 @@ export default {
           })
           .filter(Boolean);
       if (sampleLine.length > 1 && headersLine.length === 2) {
-        const fields = this.structure.customfield_set.map((el) => {
-          return {
-            name: el.name,
-            field_type: el.field_type,
-            options: el.options,
-          };
-        });
-        const csvFeatures = csvToJson(csv, delimiter);
-        for (const feature of csvFeatures) {
-          for (let { name, field_type, options } of fields) {
-            if (name in feature) {
-              const fieldInFeature = feature[name];
-
-              // overide some specific cases on date type data
-              if (
-                typeof fieldInFeature === 'string' &&
-                ['/', ':', '-'].some((el) => fieldInFeature.includes(el)) &&
-                (new Date(fieldInFeature)) instanceof Date &&
-                !isNaN((new Date(fieldInFeature)).valueOf())
-              ) {
-                field_type = 'char';
-              } else if (
-                field_type === 'date' &&
-                ((new Date(fieldInFeature)) instanceof Date)
-              ) {
-                field_type = 'char';
-              }
-
-              const customType = this.transformProperties(fieldInFeature);
-              //* if custom field value is not null, then check validity of field
-              if (fieldInFeature !== null) {
-                //* if field type is list, it's not possible to guess from value type
-                if (field_type === 'list') {
-                  //*then check if the value is an available option
-                  if (fieldInFeature && !options.includes(fieldInFeature)) {
-                    this.importError = `Le fichier est invalide: la valeur [ ${fieldInFeature} ] n'est pas une option valide 
-                      pour le champ personnalisé "${name}".`;
-                    return false;
-                  }
-                } else if (customType !== field_type) {
-                  //* check if custom field value match
-                  this.importError = `Le fichier est invalide: Un champ de type ${field_type} ne peut pas avoir la valeur [ ${fieldInFeature} ]`;
-                  return false;
-                }
-              }
-            }
-          }
-        }
-        return true;
+        const features = await csv().fromString(csvString);
+        return this.checkJsonValidity({ features });
       } else {
         return false;
       }
diff --git a/src/views/FeatureType/FeatureTypeEdit.vue b/src/views/FeatureType/FeatureTypeEdit.vue
index 5682366fa74a2f7e5540ddcefbd9f87884d11fda..47bad6dceb3668063f9c76a1b9de51d7c13fdc7c 100644
--- a/src/views/FeatureType/FeatureTypeEdit.vue
+++ b/src/views/FeatureType/FeatureTypeEdit.vue
@@ -159,6 +159,7 @@ import { mapGetters, mapState, mapMutations, mapActions } from 'vuex';
 
 import Dropdown from '@/components/Dropdown.vue';
 import FeatureTypeCustomForm from '@/components/FeatureType/FeatureTypeCustomForm.vue';
+import { transformProperties } from'@/utils';
 
 export default {
   name: 'FeatureTypeEdit',
@@ -247,6 +248,7 @@ export default {
         'archived_on',
         'deletion_on',
         'feature_type',
+        'feature_id',
         'display_creator',
         'display_last_editor',
         'project',
@@ -638,23 +640,21 @@ export default {
       return 'point';
     },
 
-    transformProperties(prop) {
-      const type = typeof prop;
-      const date = new Date(prop);
-      if (type === 'boolean') {
-        return 'boolean';
-      } else if (Number.isSafeInteger(prop)) {
-        return 'integer';
-      } else if (
-        type === 'string' &&
-        date instanceof Date &&
-        !isNaN(date.valueOf())
-      ) {
-        return 'date';
-      } else if (type === 'number' && !isNaN(parseFloat(prop))) {
-        return 'decimal';
+    buildCustomForm(properties) {
+      for (const [key, val] of Object.entries(properties)) {
+        //* check that the property is not a keyword from the backend or map style
+        // todo: add map style keywords
+        if (!this.reservedKeywords.includes(key)) {
+          const customForm = {
+            label: { value: key || '' },
+            name: { value: key || '' },
+            position: this.dataKey, // * use dataKey already incremented by addCustomForm
+            field_type: { value: transformProperties(val) }, // * guessed from the type
+            options: { value: [] }, // * not available in export
+          };
+          this.addCustomForm(customForm);
+        }
       }
-      return 'char'; //* string by default, most accepted type in database
     },
 
     importGeoJsonFeatureType() {
@@ -664,93 +664,16 @@ export default {
         this.form.title.value = properties.feature_type;
         this.form.geom_type.value = this.translateLabel(geometry.type);
         this.updateStore(); //* register title & geom_type in store
-
-        //* loop properties to create a customForm for each of them
-        for (const [key, val] of Object.entries(properties)) {
-          //* check that the property is not a keyword from the backend or map style
-          // todo: add map style keywords
-          if (!this.reservedKeywords.includes(key)) {
-            const customForm = {
-              label: { value: key || '' },
-              name: { value: key || '' },
-              position: this.dataKey, // * use dataKey already incremented by addCustomForm
-              field_type: { value: this.transformProperties(val) }, // * guessed from the type
-              options: { value: [] }, // * not available in export
-            };
-            this.addCustomForm(customForm);
-          }
-        }
+        this.buildCustomForm(properties);
       }
     },
 
     importCSVFeatureType() {
       if (this.csv.length) {
         this.updateStore(); //* register title & geom_type in store
-        // List fileds for user to select coords fields
-        // this.csvFields =
-        //   Object.keys(this.csv[0])
-        //     .map(el => {
-        //       return {
-        //         field: el,
-        //         x: false,
-        //         y:false
-        //       };
-        //     });
-        for (const [key, val] of Object.entries(this.csv[0])) {
-          //* check that the property is not a keyword from the backend or map style
-          // todo: add map style keywords
-          if (!this.reservedKeywords.includes(key)) {
-            const customForm = {
-              label: { value: key || '' },
-              name: { value: key || '' },
-              position: this.dataKey, // * use dataKey already incremented by addCustomForm
-              field_type: { value: this.transformProperties(val) }, // * guessed from the type
-              options: { value: [] }, // * not available in export
-            };
-            this.addCustomForm(customForm);
-          }
-        }
+        this.buildCustomForm(this.csv[0]);
       }
     },
-
-    // pickXcsvCoordField(e) {
-    //   this.csvFields.forEach(el => {
-    //     if (el.field === e.field) {
-    //       el.x = true;
-    //     } else {
-    //       el.x = false;
-    //     }
-    //   });
-    // },
-    // pickYcsvCoordField(e) {
-    //   this.csvFields.forEach(el => {
-    //     if (el.field === e.field) {
-    //       el.y = true;
-    //     } else {
-    //       el.y = false;
-    //     }
-    //   });
-    // },
-    // setCSVCoordsFields() {
-    //   const xField = this.csvFields.find(el => el.x === true).field;
-    //   const yField = this.csvFields.find(el => el.y === true).field;
-    //   this.csvFields = null;
-
-    //   for (const [key, val] of Object.entries(this.csv[0])) {
-    //     //* check that the property is not a keyword from the backend or map style
-    //     // todo: add map style keywords
-    //     if (!this.reservedKeywords.includes(key) && key !== xField && key !== yField) {
-    //       const customForm = {
-    //         label: { value: key || '' },
-    //         name: { value: key || '' },
-    //         position: this.dataKey, // * use dataKey already incremented by addCustomForm
-    //         field_type: { value: this.transformProperties(val) }, // * guessed from the type
-    //         options: { value: [] }, // * not available in export
-    //       };
-    //       this.addCustomForm(customForm);
-    //     }
-    //   }
-    // }
   },
 };
 </script>
diff --git a/src/views/Project/FeaturesListAndMap.vue b/src/views/Project/FeaturesListAndMap.vue
index f86c9244e9e77fd13ad692680ef7208400f8ab04..c48c19edff621efe27fed3c0309026c3e0463d23 100644
--- a/src/views/Project/FeaturesListAndMap.vue
+++ b/src/views/Project/FeaturesListAndMap.vue
@@ -62,7 +62,7 @@
       >
         <div
           :class="[
-            'ui mini modal subscription',
+            'ui mini modal',
             { 'active visible': isDeleteModalOpen },
           ]"
         >
@@ -180,10 +180,11 @@ export default {
   mounted() {
     if (!this.project) {
       // Chargements des features et infos projet en cas d'arrivée directe sur la page ou de refresh
-      this.$store.dispatch('projects/GET_PROJECT', this.projectSlug);
-      this.$store
-        .dispatch('projects/GET_PROJECT_INFO', this.projectSlug)
-        .then(() => this.initMap());
+      Promise.all([
+        this.$store.dispatch('projects/GET_PROJECT', this.projectSlug),
+        this.$store.dispatch('projects/GET_PROJECT_INFO', this.projectSlug)
+      ])
+        .then(()=> this.initMap());
     } else {
       this.initMap();
     }
@@ -303,6 +304,7 @@ export default {
         lng: this.lng,
         mapDefaultViewCenter,
         mapDefaultViewZoom,
+        maxZoom: this.project.map_max_zoom_level,
         interactions : { doubleClickZoom :false,mouseWheelZoom:true,dragPan:true }
       });
 
@@ -485,6 +487,9 @@ export default {
   margin-left: 50%;
   visibility: hidden;
   position: absolute;
+  #map {
+    min-height: 0;
+  }
 }
 .map-container.visible {
   visibility: visible;
diff --git a/src/views/Project/ProjectEdit.vue b/src/views/Project/ProjectEdit.vue
index 9a6de25196772b62b9d9f30eb3fea53d3fc7eb72..0e788f3f84a33b747d170945eb5d315fef982b37 100644
--- a/src/views/Project/ProjectEdit.vue
+++ b/src/views/Project/ProjectEdit.vue
@@ -156,55 +156,74 @@
         </div>
       </div>
 
-      <div class="field">
-        <div class="ui checkbox">
-          <input
-            id="moderation"
-            v-model="form.moderation"
-            class="hidden"
-            type="checkbox"
-            name="moderation"
-          >
-          <label for="moderation">Modération</label>
-        </div>
-      </div>
-
-      <div class="field">
-        <div class="ui checkbox">
-          <input
-            id="is_project_type"
-            v-model="form.is_project_type"
-            class="hidden"
-            type="checkbox"
-            name="is_project_type"
-          >
-          <label for="is_project_type">Est un projet type</label>
-        </div>
-      </div>
-
-      <div class="field">
-        <div class="ui checkbox">
-          <input
-            id="generate_share_link"
-            v-model="form.generate_share_link"
-            class="hidden"
-            type="checkbox"
-            name="generate_share_link"
-          >
-          <label for="generate_share_link">Génération d'un lien de partage externe</label>
+      <div class="two fields">
+        <div class="fields grouped">
+          <div class="field">
+            <div class="ui checkbox">
+              <input
+                id="moderation"
+                v-model="form.moderation"
+                class="hidden"
+                type="checkbox"
+                name="moderation"
+              >
+              <label for="moderation">Modération</label>
+            </div>
+          </div>
+
+          <div class="field">
+            <div class="ui checkbox">
+              <input
+                id="is_project_type"
+                v-model="form.is_project_type"
+                class="hidden"
+                type="checkbox"
+                name="is_project_type"
+              >
+              <label for="is_project_type">Est un projet type</label>
+            </div>
+          </div>
+
+          <div class="field">
+            <div class="ui checkbox">
+              <input
+                id="generate_share_link"
+                v-model="form.generate_share_link"
+                class="hidden"
+                type="checkbox"
+                name="generate_share_link"
+              >
+              <label for="generate_share_link">Génération d'un lien de partage externe</label>
+            </div>
+          </div>
+
+          <div class="field">
+            <div class="ui checkbox">
+              <input
+                id="fast_edition_mode"
+                v-model="form.fast_edition_mode"
+                class="hidden"
+                type="checkbox"
+                name="fast_edition_mode"
+              >
+              <label for="fast_edition_mode">Mode d'édition rapide de signalements</label>
+            </div>
+          </div>
         </div>
-      </div>
 
-      <div class="field">
-        <div class="ui checkbox">
-          <input
-            id="fast_edition_mode"
-            v-model="form.fast_edition_mode"
-            class="hidden"
-            type="checkbox"
-            name="fast_edition_mode"
-          >
-          <label for="fast_edition_mode">Mode d'édition rapide de signalements</label>
+        <div class="field required">
+          <label>Niveau de zoom maximum de la carte</label>
+          <div class="range-container">
+            <input
+              v-model="form.map_max_zoom_level"
+              type="range"
+              min="0"
+              max="22"
+              step="1"
+            ><output class="range-output-bubble">{{
+              form.map_max_zoom_level
+            }}</output>
+          </div>
         </div>
       </div>
 
@@ -267,6 +286,7 @@ export default {
         access_level_arch_feature: { name: '', value: '' },
         archive_feature: 0,
         delete_feature: 0,
+        map_max_zoom_level: 22,
         nb_features: 0,
         nb_published_features: 0,
         nb_comments: 0,
@@ -520,6 +540,7 @@ export default {
         access_level_pub_feature: this.form.access_level_pub_feature.value,
         archive_feature: this.form.archive_feature,
         delete_feature: this.form.delete_feature,
+        map_max_zoom_level: this.form.map_max_zoom_level,
         is_project_type: this.form.is_project_type,
         generate_share_link: this.form.generate_share_link,
         fast_edition_mode: this.form.fast_edition_mode,
diff --git a/src/views/Project/ProjectMembers.vue b/src/views/Project/ProjectMembers.vue
index 679f9a59f0296d909dc87961b118b035cbe6dad2..adf13ec5c98f698da9682758e614b4c514ee75c4 100644
--- a/src/views/Project/ProjectMembers.vue
+++ b/src/views/Project/ProjectMembers.vue
@@ -301,6 +301,11 @@ export default {
     },
 
     saveMembers() {
+      this.$store.commit(
+        'DISPLAY_LOADER',
+        'Mise à jour des membres du projet en cours ...'
+      );
+
       const data = this.projectUsers.map((member) => {
         return {
           user: member.user,
@@ -329,8 +334,10 @@ export default {
               }
             );
           }
+          this.$store.commit('DISCARD_LOADER');
         })
         .catch((error) => {
+          this.$store.commit('DISCARD_LOADER');
           throw error;
         });
     },