diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..f28a3ef193b40d1b2526b32324afe28cdcaceffe
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,41 @@
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+package-lock.json
+.env
+.env.*
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Vuejs ###
+# Recommended template: Node.gitignore
+
+node_modules/
+dist/
+npm-debug.log
+yarn-error.log
+
+*.swp
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4f302c5b7464dd50f695130e848a4be3bc237a00..b5cd343d96bc1009e1d2203399c337a20b907548 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -35,9 +35,11 @@ build stable docker image:
   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
 
+
 build tagged docker image:
   stage: build
   only:
@@ -53,12 +55,11 @@ build tagged docker image:
     - 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
 
 
-
-
 sonarqube-check:
   image:
     name: sonarsource/sonar-scanner-cli:latest
diff --git a/Dockerfile b/Dockerfile
index ef7caca960b3e9167edcd6160da4998c68be19d5..307c845f6c400793a1dab711dd633c2842587108 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,14 +6,14 @@ RUN npm install -g npm@latest
 COPY package*.json ./
 RUN npm install
 
-#COPY dist dist
 COPY . .
 RUN npm run build
 
 
 FROM nginx
 
-COPY --from=builder /app/dist /usr/share/nginx/html
+RUN mkdir /usr/share/nginx/html/geocontrib
+COPY --from=builder /app/dist /usr/share/nginx/html/geocontrib
 COPY nginx.conf /etc/nginx/conf.d/default.conf 
 
 EXPOSE 80
diff --git a/README.md b/README.md
index 5c42268f7abe4da68b8857ae90307c199b0a9d56..d7998595b03edb03ad4559cf179a5632d7693f02 100644
--- a/README.md
+++ b/README.md
@@ -10,20 +10,46 @@ Créer un fichier .env à la racine du projet puis ajouter les variables comme i
 
 
 ```
-DOMAIN=http://localhost:8010/
-
+BASE_URL=/geocontrib/
 NODE_ENV=development
-VUE_APP_LOCALE=fr-FR
-
-VUE_APP_APPLICATION_NAME=GéoContrib
-VUE_APP_APPLICATION_ABSTRACT=Application de saisie d'informations géographiques contributive
-VUE_APP_LOGO_PATH=@/assets/img/logo-neogeo-circle.png
 
-VUE_APP_DJANGO_API_BASE=${DOMAIN}api/
-VUE_APP_DJANGO_BASE=${DOMAIN}
+```
 
+### Configuration par le config.json
+
+```json
+{
+    "BASE_URL":"/geocontrib/",
+    "DOMAIN":"/geocontrib/",
+    "VUE_APP_LOCALE":"fr-FR",
+    "VUE_APP_APPLICATION_NAME":"GéoContrib",
+    "VUE_APP_APPLICATION_ABSTRACT":"Application de saisie d'informations géographiques contributive",
+    "VUE_APP_LOGO_PATH":"@/assets/img/logo-neogeo-circle.png",
+    "VUE_APP_DJANGO_BASE":"",
+    "VUE_APP_DJANGO_API_BASE":"/geocontrib/api/",
+    "DEFAULT_BASE_MAP":{
+        "SERVICE": "https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
+        "OPTIONS": {
+            "attribution": "&copy; contributeurs d'<a href='https://osm.org/copyright'>OpenStreetMap</a>",
+            "maxZoom": 20
+        }
+    },
+    "DEFAULT_MAP_VIEW" : {
+        "center": [47.0, 1.0],
+        "zoom": 4
+    },
+    "GEOCODER_PROVIDERS" : {
+        "ADDOK": "addok",
+        "NOMINATIM": "nominatim",
+        "PHOTON": "photon"
+    },
+    "SELECTED_GEOCODER" : {
+        "PROVIDER": "addok"
+    }
+}
 ```
 
+
 ### Compiles and hot-reloads for development
 
 Pour éviter les problèmes de Cors en dév, (car backend sur serveur différent de frontend, différent en prod car même serveur), on utilise un proxy qui s'occupe de renseigner les CORS: https://github.com/garmeeh/local-cors-proxy#local-cors-proxy  
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 8d088e2155cd0ffc8f4efe76edaa5035716f9f21..18f164891441f5ecbbaa1bbf86ba056f826570f6 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -2,8 +2,10 @@
 version: "3"
 services:
   geocontrib-front:
-    image: neogeo/geocontrib-front
+    image: neogeo/geocontrib-front:geocontrib-latest
     build: .
+    environment:
+      - BASE_URL=${BASE_URL}
     ports:
       - 8080:80
     volumes:
diff --git a/nginx.conf b/nginx.conf
index e3df6890ec542b7dcd6f1bd3c5a3b99a85cd37df..776e3c89295e49b3d4b87021fd177ae6a60ad06e 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -6,10 +6,11 @@ server {
     listen 80;
     charset utf-8;
     server_name  127.0.0.1;
+    root /usr/share/nginx/html;
 
     client_max_body_size 4G;
 
-    location /api {
+    location /geocontrib/api {
         proxy_pass_header Set-Cookie;
         proxy_set_header X-NginX-Proxy true;
         proxy_set_header X-Real-IP $remote_addr;
@@ -21,7 +22,7 @@ server {
         proxy_pass http://geocontrib_site;
     }
 
-    location /admin {
+    location /geocontrib/admin {
         proxy_pass_header Set-Cookie;
         proxy_set_header X-NginX-Proxy true;
         proxy_set_header X-Real-IP $remote_addr;
@@ -33,18 +34,17 @@ server {
         proxy_pass http://geocontrib_site;
     }
 
-    location /static/ {
+    location /geocontrib/static/ {
         alias /opt/geocontrib/static/;
     }
 
-    location /media/ {
+    location /geocontrib/media/ {
         alias /opt/geocontrib/media/;
     }
 
-    location / {
-        root /usr/share/nginx/html;
+    location /geocontrib/ {
         index index.html;
-        try_files $uri $uri/ /index.html;
+        try_files $uri $uri/ /geocontrib/index.html;
     }
    
 
diff --git a/package.json b/package.json
index a37b2ee31482094e486de48225d88da9e6d2f090..0f0843731dba2192fdb958a3b2d3b750015d43fd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "geocontrib-frontend",
-  "version": "2.1.0",
+  "version": "2.1.1",
   "private": true,
   "scripts": {
     "serve": "npm run init-proxy & npm run init-serve",
@@ -33,6 +33,7 @@
     "eslint-plugin-vue": "^6.2.2",
     "less": "^3.0.4",
     "less-loader": "^5.0.0",
+    "local-cors-proxy": "^1.1.0",
     "vue-template-compiler": "^2.6.11"
   }
 }
diff --git a/public/config/config.json b/public/config/config.json
index 729d914f4468eee39121a36acb68e0db2478181b..859464c3401983ee073000a70601cb67c0223024 100644
--- a/public/config/config.json
+++ b/public/config/config.json
@@ -1,5 +1,5 @@
 {
-    "BASE_URL":"/",
+    "BASE_URL":"/geocontrib/",
     "DOMAIN":"http://localhost:8010/",
     "NODE_ENV":"development",
     "VUE_APP_LOCALE":"fr-FR",
diff --git a/public/index.html b/public/index.html
index 81a966830c494ff1621342f894af73bc9a031577..ea00bc1ca40b8b9ee130af4f1734bd1841264e30 100644
--- a/public/index.html
+++ b/public/index.html
@@ -7,6 +7,7 @@
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <!-- import not working for roboto condensed inside component style, only in head https://stackoverflow.com/questions/36724268/roboto-condensed-google-font-falls-back-in-all-browsers -->
     <style>@import url('https://fonts.googleapis.com/css?family=Roboto Condensed:400,700,400italic,700italic&subset=latin');</style>
+    <base href="<%= htmlWebpackPlugin.files.publicPath %>">
     <title><%= htmlWebpackPlugin.options.title %></title>
   </head>
   <body>
diff --git a/src/App.vue b/src/App.vue
index a84aec7f1ace93de9ef2a775992f0f47bf2b5999..343ccfe54930b931fc6bc1111399d9ee6d749a6a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -111,7 +111,7 @@
               {{ userFullname || user.username || "Utilisateur inconnu" }}
             </router-link>
             <div
-              v-if="user && user.is_administrator"
+              v-if="user || user.is_administrator"
               class="item ui label vertical no-hover"
             >
               <span v-if="USER_LEVEL_PROJECTS && project">
@@ -132,8 +132,8 @@
       </div>
     </header>
     <main>
-      <div id="messages" class="ui stackable grid centered container">
-        <div v-if="messages" class="row">
+      <div id="content" class="ui stackable grid centered container">
+        <div v-if="messages && messages.length > 0" class="row">
           <div class="fourteen wide column">
             <div
               v-for="(message, index) in messages"
@@ -264,6 +264,11 @@ footer {
   flex-direction: column;
   justify-content: center;
 }
+
+.leaflet-container {
+  background: white !important;
+}
+
 @media screen and (min-width: 560px) {
   .mobile {
     display: none !important;
diff --git a/src/components/Dropdown.vue b/src/components/Dropdown.vue
index 7fb68bf91cb66f9d5583e6576056ba5b2d1b5c95..b63ebe80e5432298cc6b9851b9c2f6e30f37d69a 100644
--- a/src/components/Dropdown.vue
+++ b/src/components/Dropdown.vue
@@ -10,18 +10,18 @@
   >
     <input
       v-if="search"
+      v-model="input"
+      v-on:keyup.enter="select(0)"
+      v-on:keyup.esc="toggleDropdown(false)"
       class="search"
       autocomplete="off"
       tabindex="0"
-      @input="handelInput"
-      v-on:keyup.enter="select(0)"
-      v-model="input"
-      :placeholder="placeholder"
+      :placeholder="placehold"
+      ref="input"
     />
-    <!-- {{placeholder}} -->
-    <div class="default text">{{ selected || placeholder }}</div>
+    <div v-if="!input" class="default text">{{ selected }}</div>
     <i
-      :class="['dropdown icon', { clear: search && selected }]"
+      :class="['dropdown icon', { clear: clearable && selected }]"
       @click="clear"
     ></i>
     <div :class="['menu', { 'visible transition': isOpen }]">
@@ -44,19 +44,30 @@
 export default {
   name: "Dropdown",
 
-  props: ["options", "selected", "disabled", "search", "placeholder"],
+  props: [
+    "options",
+    "selected",
+    "disabled",
+    "search",
+    "placeholder",
+    "clearable",
+  ],
 
   computed: {
     processedOptions: function () {
       //* si un objet {name, value}
       let options = this.options.map((el) =>
-        el.constructor == Object ? el.name : el
+        el.constructor === Object ? el.name : el
       );
       if (this.search && this.input !== "") {
         options = this.searchOptions(options);
       }
       return options.length > 0 ? options : null;
     },
+
+    placehold() {
+      return this.input ? "" : this.placeholder;
+    },
   },
 
   data() {
@@ -66,47 +77,53 @@ export default {
       identifier: 0,
     };
   },
+
   methods: {
-    toggleDropdown() {
-      this.isOpen = !this.isOpen;
+    toggleDropdown(val) {
+      if (this.isOpen) {
+        this.input = ""; // * clear input field when closing dropdown
+      } else if (this.search) {
+        //* focus on input if is a search dropdown
+        this.$refs.input.focus({
+          preventScroll: true,
+        });
+      } else if (this.clearable && val.target && this.selected) {
+        this.clear(); //* clear selected and input
+      }
+      this.isOpen = typeof val === "boolean" ? val : !this.isOpen;
     },
+
     select(index) {
+      // * toggle dropdown is called several time, timeout delay this function to be the last
       setTimeout(() => {
-        this.isOpen = false; // * quick & dirty, car toggle dropdown est rappelé plusieurs fois aileurs, à creuser...
-      }, 500);
+        this.isOpen = false;
+      }, 0);
       this.$emit("update:selection", this.options[index]);
       this.input = "";
     },
+
     searchOptions(options) {
       return options.filter((el) =>
         el.toLowerCase().includes(this.input.toLowerCase())
       );
     },
+
     clear() {
-      if (this.search) {
+      if (this.clearable) {
         this.input = "";
-        this.clearSelected();
+        this.$emit("update:selection", "");
+        if (this.isOpen) this.toggleDropdown(false);
       }
     },
-    clearSelected() {
-      this.$emit("update:selection", "");
-    },
-    handelInput() {
-      this.isOpen = true;
-      this.clearSelected();
-    },
+
     clickOutsideDropdown(e) {
       if (!e.target.closest(`#custom-dropdown${this.identifier}`))
-        this.isOpen = false;
+        this.toggleDropdown(false);
     },
   },
 
   created() {
-    let randomnum =
-      Math.floor(
-        Math.random() * 10000
-      );
-    this.identifier = randomnum;
+    this.identifier = Math.floor(Math.random() * 10000);
     window.addEventListener("mousedown", this.clickOutsideDropdown);
   },
 
diff --git a/src/components/ImportTask.vue b/src/components/ImportTask.vue
index 8e085eddf482e6623cc70d4f1d53f4a3cf65c36f..b33b4ff3cc1120b273f47d1771c26d816de445de 100644
--- a/src/components/ImportTask.vue
+++ b/src/components/ImportTask.vue
@@ -27,21 +27,21 @@
               class="ui icon margin-left"
             >
               <i
-                v-if="importFile.status == 'processing'"
+                v-if="importFile.status === 'processing'"
                 class="orange hourglass half icon"
               ></i>
               <i
-                v-else-if="importFile.status == 'finished'"
+                v-else-if="importFile.status === 'finished'"
                 class="green check circle outline icon"
               ></i>
               <i
-                v-else-if="importFile.status == 'failed'"
+                v-else-if="importFile.status === 'failed'"
                 class="red x icon"
               ></i>
               <i v-else class="red ban icon"></i>
             </span>
             <span
-              v-if="importFile.status == 'pending'"
+              v-if="importFile.status === 'pending'"
               data-tooltip="Statut en attente. Clickez pour rafraichir."
             >
               <i
diff --git a/src/components/feature/FeatureAttachmentForm.vue b/src/components/feature/FeatureAttachmentForm.vue
index c75905f2db595f7cee272a5d245ede78a522024f..83343c7935dab9d69dee36ccbde5b2315927da43 100644
--- a/src/components/feature/FeatureAttachmentForm.vue
+++ b/src/components/feature/FeatureAttachmentForm.vue
@@ -23,7 +23,7 @@
               :name="form.title.html_name"
               :id="form.title.id_for_label"
               v-model="form.title.value"
-              @change="updateStore"
+              
             />
             <ul :id="form.title.id_for_error" class="errorlist">
               <li v-for="error in form.title.errors" :key="error">
@@ -63,7 +63,6 @@
             name="form.info.html_name"
             rows="5"
             v-model="form.info.value"
-            @change="updateStore"
           ></textarea>
           <!-- {{ form.info.errors }} -->
         </div>
@@ -80,8 +79,14 @@ export default {
 
   data() {
     return {
+      newAttachementIds: [],
       fileToImport: null,
       form: {
+        id: {
+          value: "",
+          errors: null,
+          label: "ID",
+        },
         title: {
           errors: [],
           id_for_error: `errorlist-title-${this.attachmentForm.dataKey}`,
@@ -113,8 +118,21 @@ export default {
     attachmentForm(newValue) {
       this.initForm(newValue);
     },
+    "form.title.value": function (newValue, oldValue) {
+      if (oldValue != ''){
+        if (newValue != oldValue){
+          this.updateStore();
+        }
+      }
+    },
+    "form.info.value": function (newValue, oldValue) {
+       if (oldValue != ''){
+        if (newValue != oldValue){
+          this.updateStore();
+        }
+      }
+    },
   },
-
   methods: {
     initForm(attachmentForm) {
       for (let el in attachmentForm) {
@@ -133,16 +151,29 @@ export default {
         "feature/REMOVE_ATTACHMENT_FORM",
         this.attachmentForm.dataKey
       );
+      if (this.form.id.value)
+        this.$store.commit(
+          "feature/DELETE_ATTACHMENTS",
+          this.form.id.value
+        );
     },
 
     updateStore() {
-      this.$store.commit("feature/UPDATE_ATTACHMENT_FORM", {
+      const data = {
+        id: this.form.id.value,
         dataKey: this.attachmentForm.dataKey,
         title: this.form.title.value,
         attachment_file: this.form.attachment_file.value,
         info: this.form.info.value,
         fileToImport: this.fileToImport,
-      });
+      }
+      this.$store.commit("feature/UPDATE_ATTACHMENT_FORM", data);
+      if (data.id){
+        this.$store.commit(
+          "feature/PUT_ATTACHMENTS",
+          data
+        );
+      }
     },
 
     onFileChange(e) {
diff --git a/src/components/feature/FeatureListTable.vue b/src/components/feature/FeatureListTable.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ed7f0dac060041a1badf0b1f4137cf458d8ba7b6
--- /dev/null
+++ b/src/components/feature/FeatureListTable.vue
@@ -0,0 +1,508 @@
+
+<template>
+  <div data-tab="list" class="dataTables_wrapper no-footer">
+    <table id="table-features" class="ui compact table">
+      <thead>
+        <tr>
+          <th class="center"></th>
+
+          <th class="center">
+            Statut
+            <i
+              :class="{
+                down: isSortedAsc('statut'),
+                up: isSortedDesc('statut'),
+              }"
+              class="icon sort"
+              @click="changeSort('statut')"
+            />
+          </th>
+          <th class="center">
+            Type
+            <i
+              :class="{
+                down: isSortedAsc('type'),
+                up: isSortedDesc('type'),
+              }"
+              class="icon sort"
+              @click="changeSort('type')"
+            />
+          </th>
+          <th class="center">
+            Nom
+            <i
+              :class="{
+                down: isSortedAsc('nom'),
+                up: isSortedDesc('nom'),
+              }"
+              class="icon sort"
+              @click="changeSort('nom')"
+            />
+          </th>
+          <th class="center">
+            Dernière modification
+            <i
+              :class="{
+                down: isSortedAsc('updated_on'),
+                up: isSortedDesc('updated_on'),
+              }"
+              class="icon sort"
+              @click="changeSort('updated_on')"
+            />
+          </th>
+          <th class="center" v-if="user">
+            Auteur
+            <i
+              :class="{
+                down: isSortedAsc('display_creator'),
+                up: isSortedDesc('display_creator'),
+              }"
+              class="icon sort"
+              @click="changeSort('display_creator')"
+            />
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr v-for="(feature, index) in paginatedFeatures" :key="index">
+          <td class="center">
+            <div class="ui checkbox">
+              <input
+                type="checkbox"
+                :id="feature.id"
+                :value="feature.id"
+                v-model="checkedFeatures"
+                :checked="checkedFeatures[feature.id]"
+              />
+              <label></label>
+            </div>
+          </td>
+
+          <td class="center">
+            <div
+              v-if="feature.properties.status.value === 'archived'"
+              data-tooltip="Archivé"
+            >
+              <i class="grey archive icon"></i>
+            </div>
+            <div
+              v-else-if="feature.properties.status.value === 'pending'"
+              data-tooltip="En attente de publication"
+            >
+              <i class="teal hourglass outline icon"></i>
+            </div>
+            <div
+              v-else-if="feature.properties.status.value === 'published'"
+              data-tooltip="Publié"
+            >
+              <i class="olive check icon"></i>
+            </div>
+            <div
+              v-else-if="feature.properties.status.value === 'draft'"
+              data-tooltip="Brouillon"
+            >
+              <i class="orange pencil alternate icon"></i>
+            </div>
+          </td>
+          <td class="center">
+            <router-link
+              :to="{
+                name: 'details-type-signalement',
+                params: {
+                  feature_type_slug: feature.properties.feature_type.slug,
+                },
+              }"
+            >
+              {{ feature.properties.feature_type.title }}
+            </router-link>
+          </td>
+          <td class="center">
+            <router-link
+              :to="{
+                name: 'details-signalement',
+                params: {
+                  slug_type_signal: feature.properties.feature_type.slug,
+                  slug_signal: feature.properties.slug || feature.id,
+                },
+              }"
+              >{{ getFeatureDisplayName(feature) }}</router-link
+            >
+          </td>
+          <td class="center">
+            <!-- |date:'Ymd' -->
+            {{ feature.properties.updated_on }}
+          </td>
+          <td class="center" v-if="user">
+            {{ feature.properties.creator.username || " ---- " }}
+          </td>
+        </tr>
+        <tr v-if="filteredFeatures.length === 0" class="odd">
+          <td colspan="5" class="dataTables_empty" valign="top">
+            Aucune donnée disponible
+          </td>
+        </tr>
+      </tbody>
+    </table>
+    <div
+      v-if="nbPages.length > 1"
+      id="table-features_info"
+      class="dataTables_info"
+      role="status"
+      aria-live="polite"
+    >
+      Affichage de l'élément {{ pagination.start + 1 }} à
+      {{ displayedPageEnd }}
+      sur {{ filteredFeatures.length }} éléments
+    </div>
+    <div
+      v-if="nbPages.length > 1"
+      id="table-features_paginate"
+      class="dataTables_paginate paging_simple_numbers"
+    >
+      <a
+        @click="toPreviousPage"
+        id="table-features_previous"
+        :class="[
+          'paginate_button previous',
+          { disabled: pagination.currentPage === 1 },
+        ]"
+        aria-controls="table-features"
+        data-dt-idx="0"
+        tabindex="0"
+        >Précédent</a
+      >
+      <span>
+        <a
+          v-for="pageNumber in nbPages"
+          :key="'page' + pageNumber"
+          @click="toPage(pageNumber)"
+          :class="[
+            'paginate_button',
+            { current: pageNumber === pagination.currentPage },
+          ]"
+          aria-controls="table-features"
+          data-dt-idx="1"
+          tabindex="0"
+          >{{ pageNumber }}</a
+        >
+      </span>
+      <!-- // TODO : <span v-if="nbPages > 4" class="ellipsis">...</span> -->
+      <a
+        id="table-features_next"
+        :class="[
+          'paginate_button next',
+          { disabled: pagination.currentPage === nbPages.length },
+        ]"
+        aria-controls="table-features"
+        data-dt-idx="7"
+        tabindex="0"
+        @click="toNextPage"
+        >Suivant</a
+      >
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "FeatureListTable",
+
+  props: ["filteredFeatures", "user", "checkedFeatures"],
+
+  data() {
+    return {
+      pagination: {
+        currentPage: 1,
+        pagesize: 15,
+        start: 0,
+        end: 15,
+      },
+      sort: {
+        column: "",
+        ascending: true,
+      },
+    };
+  },
+
+  computed: {
+    paginatedFeatures() {
+      let filterdFeatures = [...this.filteredFeatures];
+      // Ajout du tri
+      if (this.sort.column != "") {
+        filterdFeatures = filterdFeatures.sort((a, b) => {
+          let aProp = this.getFeatureDisplayName(a);
+          let bProp = this.getFeatureDisplayName(b);
+          if (this.sort.column === "statut") {
+            aProp = a.properties.status.value;
+            bProp = b.properties.status.value;
+          } else if (this.sort.column === "type") {
+            aProp = a.properties.feature_type.title;
+            bProp = b.properties.feature_type.title;
+          } else if (this.sort.column === "updated_on") {
+            aProp = a.properties.updated_on;
+            bProp = b.properties.updated_on;
+          } else if (this.sort.column === "display_creator") {
+            aProp = a.properties.display_creator;
+            bProp = b.properties.display_creator;
+          }
+          //ascending
+          if (this.sort.ascending) {
+            if (aProp < bProp) {
+              return -1;
+            }
+            if (aProp > bProp) {
+              return 1;
+            }
+            return 0;
+          } else {
+            //descending
+            if (aProp < bProp) {
+              return 1;
+            }
+            if (aProp > bProp) {
+              return -1;
+            }
+            return 0;
+          }
+        });
+      }
+      return filterdFeatures.slice(this.pagination.start, this.pagination.end);
+    },
+
+    nbPages() {
+      let N = Math.round(
+        this.filteredFeatures.length / this.pagination.pagesize
+      );
+      const arr = [...Array(N).keys()].map(function (x) {
+        ++x;
+        return x;
+      });
+      return arr;
+    },
+
+    displayedPageEnd() {
+      return this.filteredFeatures.length <= this.pagination.end
+        ? this.filteredFeatures.length
+        : this.pagination.end;
+    },
+  },
+
+  methods: {
+    getFeatureDisplayName(feature) {
+      return feature.properties.title || feature.id;
+    },
+
+    isSortedAsc(column) {
+      return this.sort.column === column && this.sort.ascending;
+    },
+    isSortedDesc(column) {
+      return this.sort.column === column && !this.sort.ascending;
+    },
+
+    changeSort(column) {
+      if (this.sort.column === column) {
+        //changer order
+        this.sort.ascending = !this.sort.ascending;
+      } else {
+        this.sort.column = column;
+        this.sort.ascending = true;
+      }
+    },
+
+    toPage(pageNumber) {
+      const toAddOrRemove =
+        (pageNumber - this.pagination.currentPage) * this.pagination.pagesize;
+      this.pagination.start += toAddOrRemove;
+      this.pagination.end += toAddOrRemove;
+      this.pagination.currentPage = pageNumber;
+    },
+
+    toPreviousPage() {
+      if (this.pagination.start > 0) {
+        this.pagination.start -= this.pagination.pagesize;
+        this.pagination.end -= this.pagination.pagesize;
+        this.pagination.currentPage -= 1;
+      }
+    },
+
+    toNextPage() {
+      if (this.pagination.end < this.filteredFeatures.length) {
+        this.pagination.start += this.pagination.pagesize;
+        this.pagination.end += this.pagination.pagesize;
+        this.pagination.currentPage += 1;
+      }
+    },
+  },
+};
+</script>
+
+<style scoped>
+/* datatables */
+.dataTables_wrapper {
+  position: relative;
+  clear: both;
+}
+.dataTables_wrapper .dataTables_length,
+.dataTables_wrapper .dataTables_filter,
+.dataTables_wrapper .dataTables_info,
+.dataTables_wrapper .dataTables_processing,
+.dataTables_wrapper .dataTables_paginate {
+  color: #333;
+}
+.dataTables_wrapper .dataTables_info {
+  clear: both;
+  float: left;
+  padding-top: 0.755em;
+}
+.dataTables_wrapper .dataTables_paginate {
+  float: right;
+  text-align: right;
+  padding-top: 0.25em;
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button.current,
+.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
+  color: #333 !important;
+  border: 1px solid #979797;
+  background-color: white;
+  background: -webkit-gradient(
+    linear,
+    left top,
+    left bottom,
+    color-stop(0%, #fff),
+    color-stop(100%, #dcdcdc)
+  );
+  background: -webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);
+  background: -moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);
+  background: -ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);
+  background: -o-linear-gradient(top, #fff 0%, #dcdcdc 100%);
+  background: linear-gradient(to bottom, #fff 0%, #dcdcdc 100%);
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button {
+  box-sizing: border-box;
+  display: inline-block;
+  min-width: 1.5em;
+  padding: 0.5em 1em;
+  margin-left: 2px;
+  text-align: center;
+  text-decoration: none !important;
+  cursor: pointer;
+  color: #333 !important;
+  border: 1px solid transparent;
+  border-radius: 2px;
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
+  color: white !important;
+  border: 1px solid #111;
+  background-color: #585858;
+  background: -webkit-gradient(
+    linear,
+    left top,
+    left bottom,
+    color-stop(0%, #585858),
+    color-stop(100%, #111)
+  );
+  background: -webkit-linear-gradient(top, #585858 0%, #111 100%);
+  background: -moz-linear-gradient(top, #585858 0%, #111 100%);
+  background: -ms-linear-gradient(top, #585858 0%, #111 100%);
+  background: -o-linear-gradient(top, #585858 0%, #111 100%);
+  background: linear-gradient(to bottom, #585858 0%, #111 100%);
+}
+.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,
+.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,
+.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
+  cursor: default;
+  color: #666 !important;
+  border: 1px solid transparent;
+  background: transparent;
+  box-shadow: none;
+}
+
+i.icon.sort:not(.down):not(.up) {
+  color: rgb(220, 220, 220);
+}
+
+/* 
+Max width before this PARTICULAR table gets nasty
+This query will take effect for any screen smaller than 760px
+and also iPads specifically.
+*/
+@media only screen and (max-width: 760px),
+  (min-device-width: 768px) and (max-device-width: 1024px) {
+  /* Force table to not be like tables anymore */
+  table,
+  thead,
+  tbody,
+  th,
+  td,
+  tr {
+    display: block;
+  }
+
+  /* Hide table headers (but not display: none;, for accessibility) */
+  thead tr {
+    position: absolute;
+    top: -9999px;
+    left: -9999px;
+  }
+
+  tr {
+    border: 1px solid #ccc;
+  }
+
+  td {
+    /* Behave  like a "row" */
+    border: none;
+    border-bottom: 1px solid #eee;
+    position: relative;
+    padding-left: 50%;
+  }
+
+  td:before {
+    /* Now like a table header */
+    position: absolute;
+    /* Top/left values mimic padding */
+    /* top: 6px; */
+    left: 6px;
+    /* width: 45%; */
+    padding-right: 10px;
+    white-space: nowrap;
+  }
+
+  /*
+	Label the data
+	*/
+  td:nth-of-type(1):before {
+    content: "";
+  }
+  td:nth-of-type(2):before {
+    content: "Statut";
+  }
+  td:nth-of-type(3):before {
+    content: "Type";
+  }
+  td:nth-of-type(4):before {
+    content: "Nom";
+  }
+  td:nth-of-type(5):before {
+    content: "Dernière modification";
+  }
+  td:nth-of-type(6):before {
+    content: "Auteur";
+  }
+
+  .center {
+    text-align: right !important;
+  }
+
+  #table-features {
+    margin-left: 1em;
+    width: calc(100% - 1em);
+  }
+
+  .ui.checkbox {
+    position: absolute;
+    left: -1.75em;
+    top: 5em;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/components/feature_type/FeatureTypeCustomForm.vue b/src/components/feature_type/FeatureTypeCustomForm.vue
index 83ac53b0dbfd5732968ce09ece09d991449c544c..39d1349eacbe108c3e4679165da14a850583b338 100644
--- a/src/components/feature_type/FeatureTypeCustomForm.vue
+++ b/src/components/feature_type/FeatureTypeCustomForm.vue
@@ -283,6 +283,28 @@ export default {
       return true;
     },
 
+    checkUniqueName() {
+      console.log(this.$store);
+      console.log(this.$store.state);
+      console.log(this.$store.state.feature_type);
+      if (this.form.name.value) {
+        const occurences = this.$store.state.feature_type.customForms
+          .map((el) => el.name)
+          .filter((el) => el === this.form.name.value);
+        console.log("occurences", occurences);
+        console.log(occurences.length);
+        if (occurences.length > 1) {
+          console.log("duplicate", this.form.name.value);
+          this.form.name.errors = [
+            "Les champs personnalisés ne peuvent pas avoir des noms similaires.",
+          ];
+          return false;
+        }
+      }
+      this.form.name.errors = [];
+      return true;
+    },
+
     checkCustomForm() {
       if (this.form.label.value === null) {
         this.form.label.errors = ["Veuillez compléter ce champ."];
@@ -297,10 +319,11 @@ export default {
         ];
         this.form.label.errors = [];
         return false;
+      } else if (this.checkUniqueName()) {
+        this.form.label.errors = [];
+        this.form.name.errors = [];
+        return true;
       }
-      this.form.label.errors = [];
-      this.form.name.errors = [];
-      return true;
     },
   },
 
diff --git a/src/components/map-layers/SidebarLayers.vue b/src/components/map-layers/SidebarLayers.vue
index d1120637b947ea8df5d629c4f1133823b27fcfe2..dace42eff5f205a37bc614755fcb71dde1d1253d 100644
--- a/src/components/map-layers/SidebarLayers.vue
+++ b/src/components/map-layers/SidebarLayers.vue
@@ -47,7 +47,6 @@
 
     <div
       v-for="basemap in baseMaps"
-      
       :key="`list-${basemap.id}`"
       class="basemaps-items ui accordion styled"
     >
@@ -58,15 +57,17 @@
       >
         {{ basemap.title }}
       </div>
-      <div :id="`queryable-layers-selector-${basemap.id}`" v-if="isQueryable(basemap)">
+      <div
+        :id="`queryable-layers-selector-${basemap.id}`"
+        v-if="isQueryable(basemap)"
+      >
         <b>Couche requêtable</b>
-        <Dropdown 
+        <Dropdown
           @update:selection="onQueryLayerChange($event)"
           :options="getQueryableLayers(basemap)"
           :selected="selectedQueryLayer"
           :search="true"
         />
-
       </div>
       <div
         :class="{ active: isActive(basemap) }"
@@ -77,14 +78,14 @@
         <div
           v-for="(layer, index) in basemap.layers"
           :key="basemap.id + '-' + layer.id + '-' + index"
-          class="layer-item transition visible item list-group-item" :data-id="layer.id"
+          class="layer-item transition visible item list-group-item"
+          :data-id="layer.id"
         >
           <p class="layer-handle-sort">
             <i class="th icon"></i>{{ layer.title }}
           </p>
           <label>Opacité &nbsp;<span>(%)</span></label>
           <div class="range-container">
-            <!-- // todo : rendre réactif les valeurs et connectés avec store/Map -->
             <input
               @change="updateOpacity($event, layer)"
               type="range"
@@ -104,168 +105,201 @@
 </template>
 
 <script>
-import { mapUtil } from "@/assets/js/map-util.js";
+import { mapState } from "vuex";
+import Sortable from "sortablejs";
 import Dropdown from "@/components/Dropdown.vue";
-import Sortable from 'sortablejs';
+import { mapUtil } from "@/assets/js/map-util.js";
 
 export default {
   name: "SidebarLayers",
+
   components: {
     Dropdown,
   },
+
   data() {
     return {
-      selectedQueryLayer:null,
-      activeBasemap:null,
+      selectedQueryLayer: null,
+      activeBasemap: null,
       baseMaps: [],
-      layers: [],
       expanded: false,
     };
   },
+
+  computed: {
+    ...mapState("map", ["availableLayers"]),
+  },
+
   methods: {
     isActive(basemap) {
-      return basemap.active != undefined && basemap.active;
+      return basemap.active !== undefined && basemap.active;
     },
+
     activateGroup(basemap) {
       this.baseMaps.forEach((basemap) => (basemap.active = false));
       basemap.active = true;
-      this.activeBasemap=basemap;
+      this.activeBasemap = basemap;
       basemap.title += " "; //weird!! Force refresh
       this.addLayers(basemap);
 
-      let mapOptions = localStorage.getItem('geocontrib-map-options') || {};
-        mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {};
-      let project=this.$route.params.slug;
-        mapOptions[project] = {
-          ...mapOptions[project],
-          'current-basemap-index': basemap.id
-        };
-        localStorage.setItem('geocontrib-map-options', JSON.stringify(mapOptions));
-
-
+      let mapOptions = localStorage.getItem("geocontrib-map-options") || {};
+      mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {};
+      let project = this.$route.params.slug;
+      mapOptions[project] = {
+        ...mapOptions[project],
+        "current-basemap-index": basemap.id,
+      };
+      localStorage.setItem(
+        "geocontrib-map-options",
+        JSON.stringify(mapOptions)
+      );
     },
+
     updateOpacity(event, layer) {
       console.log(event.target.value, layer);
       mapUtil.updateOpacity(layer.id, event.target.value);
       layer.opacity = event.target.value;
     },
+
     getOpacity(opacity) {
       return Math.round(parseFloat(opacity) * 100);
     },
-    onQueryLayerChange(layer){
+
+    onQueryLayerChange(layer) {
       console.log(layer);
-      this.selectedQueryLayer=layer.name;
+      this.selectedQueryLayer = layer.name;
     },
-    isQueryable(baseMap){
-      let queryableLayer=baseMap.layers.filter(l => l.queryable === true);
-      return queryableLayer.length>0;
+
+    isQueryable(baseMap) {
+      let queryableLayer = baseMap.layers.filter((l) => l.queryable === true);
+      return queryableLayer.length > 0;
     },
-    onlayerMove(event){
-      console.log(event)
-      // Get the names of the current layers in order.
-      const currentLayersNamesInOrder = Array.from(document.getElementsByClassName('layer-item transition visible')).map(el => el.children[0].innerText);
 
+    onlayerMove(event) {
+      console.log(event);
+      // Get the names of the current layers in order.
+      const currentLayersNamesInOrder = Array.from(
+        document.getElementsByClassName("layer-item transition visible")
+      ).map((el) => el.children[0].innerText);
       // Create an array to put the layers in order.
       let movedLayers = [];
-             
+
       for (const layerName of currentLayersNamesInOrder) {
-        movedLayers.push(this.activeBasemap.layers.filter(el => el.title === layerName)[0]);
+        movedLayers.push(
+          this.activeBasemap.layers.filter((el) => el.title === layerName)[0]
+        );
       }
-
       // Remove existing layers undefined
-      movedLayers = movedLayers.filter(function(x) {
+      movedLayers = movedLayers.filter(function (x) {
         return x !== undefined;
       });
-      const eventOrder = new CustomEvent('change-layers-order', {
+      const eventOrder = new CustomEvent("change-layers-order", {
         detail: {
-          layers: movedLayers
-        }
-      })
+          layers: movedLayers,
+        },
+      });
       document.dispatchEvent(eventOrder);
       // Save the basemaps options into the localstorage
-      console.log(this.baseMaps)
-      this.setLocalstorageMapOptions(this.baseMaps)
+      console.log(this.baseMaps);
+      this.setLocalstorageMapOptions(this.baseMaps);
     },
+
     setLocalstorageMapOptions(basemaps) {
-      let mapOptions = localStorage.getItem('geocontrib-map-options') || {};
+      let mapOptions = localStorage.getItem("geocontrib-map-options") || {};
       mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {};
-      let project=this.$route.params.slug;
+      let project = this.$route.params.slug;
       mapOptions[project] = {
         ...mapOptions[project],
-        'basemaps': basemaps
+        basemaps: basemaps,
       };
-      localStorage.setItem('geocontrib-map-options', JSON.stringify(mapOptions));
+      localStorage.setItem(
+        "geocontrib-map-options",
+        JSON.stringify(mapOptions)
+      );
     },
 
-    initSortable(){
+    initSortable() {
       this.baseMaps.forEach((basemap) => {
-         new Sortable(document.getElementById(`list-${basemap.id}`), {
-            animation: 150,
-            handle: '.layer-handle-sort', // The element that is active to drag
-            ghostClass: 'blue-background-class',
-            dragClass: 'white-opacity-background-class',
-            onEnd: this.onlayerMove.bind(this)
-          });
+        new Sortable(document.getElementById(`list-${basemap.id}`), {
+          animation: 150,
+          handle: ".layer-handle-sort", // The element that is active to drag
+          ghostClass: "blue-background-class",
+          dragClass: "white-opacity-background-class",
+          onEnd: this.onlayerMove.bind(this),
+        });
       });
     },
-
     // Check if there are changes in the basemaps settings. Changes are detected if:
-  // - one basemap has been added or deleted
-  // - one layer has been added or deleted to a basemap
-  areChangesInBasemaps(basemapFromServer, basemapFromLocalstorage={}) {
-    let isSameBasemaps = false;
-    let isSameLayers = true;
-    let isSameTitles = true;
-
-    // Compare the length and the id values of the basemaps
-    const idBasemapsServer = basemapFromServer.map(b => b.id).sort();
-    const idBasemapsLocalstorage = basemapFromLocalstorage.length ? basemapFromLocalstorage.map(b => b.id).sort() : {};
-    isSameBasemaps = (idBasemapsServer.length === idBasemapsLocalstorage.length
-      && idBasemapsServer.every((value, index) => value === idBasemapsLocalstorage[index]))
-
-    // For each basemap, compare the length and id values of the layers
-    outer_block: {
-      for(let basemapServer of basemapFromServer) {
-        let idLayersServer = basemapServer.layers.map(b => b.id).sort();
-        if (basemapFromLocalstorage.length){
-          for (let basemapLocalstorage of basemapFromLocalstorage) {
-            if (basemapServer.id === basemapLocalstorage.id) {
-              let idLayersLocalstorage = basemapLocalstorage.layers.map(b => b.id).sort();
-              isSameLayers = (idLayersServer.length === idLayersLocalstorage.length
-                && idLayersServer.every((value, index) => value === idLayersLocalstorage[index]));
-              if (!isSameLayers) {
-                break outer_block;
+    // - one basemap has been added or deleted
+    // - one layer has been added or deleted to a basemap
+    areChangesInBasemaps(basemapFromServer, basemapFromLocalstorage = {}) {
+      let isSameBasemaps = false;
+      let isSameLayers = true;
+      let isSameTitles = true;
+      // Compare the length and the id values of the basemaps
+      const idBasemapsServer = basemapFromServer.map((b) => b.id).sort();
+      const idBasemapsLocalstorage = basemapFromLocalstorage.length
+        ? basemapFromLocalstorage.map((b) => b.id).sort()
+        : {};
+      isSameBasemaps =
+        idBasemapsServer.length === idBasemapsLocalstorage.length &&
+        idBasemapsServer.every(
+          (value, index) => value === idBasemapsLocalstorage[index]
+        );
+      // For each basemap, compare the length and id values of the layers
+      outer_block: {
+        for (let basemapServer of basemapFromServer) {
+          let idLayersServer = basemapServer.layers.map((b) => b.id).sort();
+          if (basemapFromLocalstorage.length) {
+            for (let basemapLocalstorage of basemapFromLocalstorage) {
+              if (basemapServer.id === basemapLocalstorage.id) {
+                let idLayersLocalstorage = basemapLocalstorage.layers
+                  .map((b) => b.id)
+                  .sort();
+                isSameLayers =
+                  idLayersServer.length === idLayersLocalstorage.length &&
+                  idLayersServer.every(
+                    (value, index) => value === idLayersLocalstorage[index]
+                  );
+                if (!isSameLayers) {
+                  break outer_block;
+                }
               }
             }
           }
         }
-      }
-
-      // Compare basemaps titles
-      const titlesBasemapsServer = basemapFromServer.map(b => b.title).sort();
-      const titlesBasemapsLocalstorage = basemapFromLocalstorage.length ? basemapFromLocalstorage.map(b => b.title).sort() : {};
+        // Compare basemaps titles
+        const titlesBasemapsServer = basemapFromServer
+          .map((b) => b.title)
+          .sort();
+        const titlesBasemapsLocalstorage = basemapFromLocalstorage.length
+          ? basemapFromLocalstorage.map((b) => b.title).sort()
+          : {};
 
-      isSameTitles = titlesBasemapsServer.every((title, index) => title === titlesBasemapsLocalstorage[index]);
+        isSameTitles = titlesBasemapsServer.every(
+          (title, index) => title === titlesBasemapsLocalstorage[index]
+        );
 
-      if (!isSameTitles) {
-        break outer_block;
+        if (!isSameTitles) {
+          break outer_block;
+        }
       }
-    }
-    return !(isSameBasemaps && isSameLayers && isSameTitles);
-  },
-    getQueryableLayers(baseMap){
-      let queryableLayer=baseMap.layers.filter(l => l.queryable === true);
-      return queryableLayer.map(x=>{
+      return !(isSameBasemaps && isSameLayers && isSameTitles);
+    },
+
+    getQueryableLayers(baseMap) {
+      let queryableLayer = baseMap.layers.filter((l) => l.queryable === true);
+      return queryableLayer.map((x) => {
         return {
-          name:x.title,
-          value:x
-        }
+          name: x.title,
+          value: x,
+        };
       });
     },
+
     addLayers(baseMap) {
-     
       baseMap.layers.forEach((layer) => {
-        var layerOptions = this.layers.find((l) => l.id == layer.id);
+        var layerOptions = this.availableLayers.find((l) => l.id === layer.id);
         console.log(layerOptions);
         layer = Object.assign(layer, layerOptions);
         layer.options.basemapId = baseMap.id;
@@ -276,35 +310,39 @@ export default {
       mapUtil.addLayers(baseMap.layers.slice().reverse(), null, null);
     },
   },
+
   mounted() {
     this.baseMaps = this.$store.state.map.basemaps;
-    let project=this.$route.params.slug;
-    const mapOptions = JSON.parse(localStorage.getItem('geocontrib-map-options')) || {};
-    if (mapOptions 
-        && mapOptions[project]) {
-
-      // If already in the storage, we need to check if the admin did some 
+    let project = this.$route.params.slug;
+    const mapOptions =
+      JSON.parse(localStorage.getItem("geocontrib-map-options")) || {};
+    if (mapOptions && mapOptions[project]) {
+      // If already in the storage, we need to check if the admin did some
       // modification in the basemaps on the server side. The rule is: if one layer has been added
       // or deleted in the server, then we reset the localstorage.
-      const baseMapsFromLocalstorage = mapOptions[project]['basemaps'];
-      const areChanges = this.areChangesInBasemaps(this.baseMaps, baseMapsFromLocalstorage);
+      const baseMapsFromLocalstorage = mapOptions[project]["basemaps"];
+      const areChanges = this.areChangesInBasemaps(
+        this.baseMaps,
+        baseMapsFromLocalstorage
+      );
 
       if (areChanges) {
         mapOptions[project] = {
-          'map-options': this.baseMaps,
-          'current-basemap-index': 0,
+          "map-options": this.baseMaps,
+          "current-basemap-index": 0,
         };
-        localStorage.setItem('geocontrib-map-options', JSON.stringify(mapOptions));
+        localStorage.setItem(
+          "geocontrib-map-options",
+          JSON.stringify(mapOptions)
+        );
       } else {
         this.baseMaps = baseMapsFromLocalstorage;
       }
     }
 
-    
-    this.layers = this.$store.state.map.layers;
     if (this.baseMaps.length > 0) {
       this.baseMaps[0].active = true;
-       this.activeBasemap=this.baseMaps[0];
+      this.activeBasemap = this.baseMaps[0];
       this.addLayers(this.baseMaps[0]);
     } else {
       mapUtil.addLayers(
@@ -313,11 +351,7 @@ export default {
         this.$store.state.configuration.DEFAULT_BASE_MAP.OPTIONS
       );
     }
-    setTimeout(this.initSortable.bind(this),1000)
-    
-   
-
-
+    setTimeout(this.initSortable.bind(this), 1000);
   },
 };
 </script>
diff --git a/src/components/project/ProjectMappingContextLayer.vue b/src/components/project/ProjectMappingContextLayer.vue
index 02837647f558390fbc411a13f495f80d20e86b50..567d47ab2a9d226110ddd3b4574467c24247e24e 100644
--- a/src/components/project/ProjectMappingContextLayer.vue
+++ b/src/components/project/ProjectMappingContextLayer.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="ui segment layer-item">
+  <div :id="layer.dataKey" class="ui segment layer-item">
     <div class="ui divided form">
       <div class="field" data-type="layer-field">
         <label for="form.layer.id_for_label" class="layer-handle-sort">
@@ -11,7 +11,7 @@
         <!--   {{ form.layer }} -->
         <!-- {% else %} -->
         <Dropdown
-          :options="availableLayers"
+          :options="availableLayerOptions"
           :selected="selectedLayer.name"
           :selection.sync="selectedLayer"
           :search="true"
@@ -70,23 +70,16 @@ export default {
   },
 
   computed: {
-    ...mapState("map", ["layers", "availableLayers"]),
+    ...mapState("map", ["availableLayers"]),
 
     selectedLayer: {
       get() {
-        const matchingLayer = this.retrieveLayer(this.layer.title);
-        if (matchingLayer != undefined) {
-          return {
-            name: matchingLayer != undefined ? matchingLayer.service : "",
-            value: this.layer ? this.layer.title : "",
-          };
-        }
-        return [];
+        return this.retrieveLayer(this.layer.title) || [];
       },
 
       set(newValue) {
         const matchingLayer = this.retrieveLayer(newValue.title);
-        if (matchingLayer != undefined) {
+        if (matchingLayer !== undefined) {
           this.updateLayer({
             ...this.layer,
             service: newValue.name,
@@ -109,11 +102,11 @@ export default {
       },
     },
 
-    availableLayers: function () {
-      return this.layers.map((el) => {
+    availableLayerOptions: function () {
+      return this.availableLayers.map((el) => {
         return {
           id: el.id,
-          name: el.service,
+          name: `${el.title} - ${el.service}`,
           value: el.title,
           title: el.title,
         };
@@ -122,14 +115,14 @@ export default {
 
     placeholder: function () {
       return this.selectedLayer && this.selectedLayer.name
-        ? this.selectedLayer.name
+        ? ""
         : "Choisissez une couche";
     },
   },
 
   methods: {
     retrieveLayer(title) {
-      return this.layers.find((el) => el.title === title);
+      return this.availableLayerOptions.find((el) => el.title === title);
     },
 
     removeLayer() {
@@ -150,7 +143,7 @@ export default {
 
   mounted() {
     const matchingLayer = this.retrieveLayer(this.layer.title);
-    if (matchingLayer != undefined) {
+    if (matchingLayer !== undefined) {
       this.updateLayer({
         ...this.layer,
         service: matchingLayer.service,
diff --git a/src/components/project/project_mapping_basemap.vue b/src/components/project/project_mapping_basemap.vue
index 39e2421d00da3064499835e9d843880f7ea5a96e..99fb2a408625294a27b56e5e1c3a3524b0ae66e4 100644
--- a/src/components/project/project_mapping_basemap.vue
+++ b/src/components/project/project_mapping_basemap.vue
@@ -20,7 +20,11 @@
     </div>
 
     <div class="nested">
-      <div v-if="basemap.layers" class="ui segments layers-container">
+      <div
+        :id="`list-${basemap.id}`"
+        v-if="basemap.layers"
+        class="ui segments layers-container"
+      >
         <ProjectMappingContextLayer
           v-for="layer in basemap.layers"
           :key="'layer-' + layer.dataKey"
@@ -60,6 +64,7 @@
 </template>
 
 <script>
+import Sortable from "sortablejs";
 import ProjectMappingContextLayer from "@/components/project/ProjectMappingContextLayer.vue";
 
 export default {
@@ -127,7 +132,49 @@ export default {
         }),
       });
     },
+
+    //* drag & drop *//
+
+    onlayerMove(event) {
+      console.log(event);
+      //* Get the names of the current layers in order.
+      const currentLayersNamesInOrder = Array.from(
+        document.getElementsByClassName("layer-item")
+      ).map((el) => el.id);
+
+      //* increment value 'order' in this.basemap.layers looping over layers from template ^
+      let order = 0;
+      let movedLayers = [];
+      for (let id of currentLayersNamesInOrder) {
+        let matchingLayer = this.basemap.layers.find(
+          (el) => el.dataKey === Number(id)
+        );
+        if (matchingLayer) {
+          matchingLayer["order"] = order;
+          movedLayers.push(matchingLayer);
+          order += 1;
+        }
+      }
+      //* update the store
+      this.$store.commit("map/UPDATE_BASEMAP", {
+        layers: movedLayers,
+        id: this.basemap.id,
+        title: this.basemap.title,
+        errors: this.basemap.errors,
+      });
+    },
+
+    initSortable() {
+      new Sortable(document.getElementById(`list-${this.basemap.id}`), {
+        animation: 150,
+        handle: ".layer-handle-sort", // The element that is active to drag
+        ghostClass: "blue-background-class",
+        dragClass: "white-opacity-background-class",
+        onEnd: this.onlayerMove.bind(this),
+      });
+    },
   },
+
   watch: {
     "basemap.title": {
       deep: true,
@@ -153,14 +200,16 @@ export default {
   //   this.errors = [];
   // }
 
-  // mounted() { //* not present in original
-  //   if (!this.basemap.title) {
-  //     this.$store.commit("map/UPDATE_BASEMAP", {
-  //       id: this.basemap.id,
-  //       title: newValue,
-  //     });
-  //   }
-  // },
+  mounted() {
+    //* not present in original
+    this.initSortable();
+    //   if (!this.basemap.title) {
+    //     this.$store.commit("map/UPDATE_BASEMAP", {
+    //       id: this.basemap.id,
+    //       title: newValue,
+    //     });
+    //   }
+  },
 };
 </script>
 
diff --git a/src/main.js b/src/main.js
index 99f5a839aff8c43fc987af795ab0fdeb28fa788f..4a1f3128cd2ba8ca6a755be82f949b99d29851ad 100644
--- a/src/main.js
+++ b/src/main.js
@@ -28,7 +28,7 @@ let onConfigLoaded = function(config){
     store.dispatch("GET_ALL_PROJECTS"),
     store.dispatch("GET_STATIC_PAGES"),
     store.dispatch("GET_USER_LEVEL_PROJECTS"),
-    store.dispatch("map/GET_LAYERS"),
+    store.dispatch("map/GET_AVAILABLE_LAYERS"),
     store.dispatch("GET_USER_LEVEL_PERMISSIONS"),
   ]).then(axios.spread(function () {
     new Vue({
@@ -40,7 +40,7 @@ let onConfigLoaded = function(config){
 
 }
 
-axios.get("/config/config.json")
+axios.get("./config/config.json")
   .catch((error)=>{
     console.log(error);
     console.log("try to get From Localstorage");
diff --git a/src/router/index.js b/src/router/index.js
index fcb3d72739587214fa6b2a9e427e29f4d8916959..f0eb56d71e8a8442ffd6d914291a1114e2eb3281 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -125,7 +125,7 @@ const routes = [
 
 const router = new VueRouter({
   mode: 'history',
-  base: process.env.BASE_URL,
+  base: '/geocontrib/',
   routes,
   routerHistory: [],
   scrollBehavior(to, from, savedPosition) { //* record each route change to turn back to origin after redirect
diff --git a/src/services/project-api.js b/src/services/project-api.js
index fd46c39241a72d1928e49af49f081730d497da9c..b918ad2270c8dbcd20cd0d46f8c903af3176a3a4 100644
--- a/src/services/project-api.js
+++ b/src/services/project-api.js
@@ -5,7 +5,7 @@ import store from '../store'
 axios.defaults.headers.common['X-CSRFToken'] = (name => {
   var re = new RegExp(name + "=([^;]+)");
   var value = re.exec(document.cookie);
-  return (value != null) ? unescape(value[1]) : null;
+  return (value !== null) ? unescape(value[1]) : null;
 })('csrftoken');
 
 
diff --git a/src/store/index.js b/src/store/index.js
index 42f245251c46b413c6b305d07fdf62ed1e6da0b4..eef57b2b52cbdccab9fc2c740888b4b501b446f8 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -10,7 +10,7 @@ import map from "./modules/map"
 axios.defaults.headers.common['X-CSRFToken'] = (name => {
   var re = new RegExp(name + "=([^;]+)");
   var value = re.exec(document.cookie);
-  return (value != null) ? unescape(value[1]) : null;
+  return (value !== null) ? unescape(value[1]) : null;
 })('csrftoken');
 
 
@@ -22,7 +22,7 @@ function updateAxiosHeader() {
   axios.defaults.headers.common['X-CSRFToken'] = (name => {
     var re = new RegExp(name + "=([^;]+)");
     var value = re.exec(document.cookie);
-    return (value != null) ? unescape(value[1]) : null;
+    return (value !== null) ? unescape(value[1]) : null;
   })('csrftoken');
 }
 // ! À vérifier s'il y a un changement de token pendant l'éxécution de l'appli
@@ -47,7 +47,13 @@ export default new Vuex.Store({
     SSO_SETTED: false,
     USER_LEVEL_PROJECTS: null,
     user_permissions: null,
-    messages: []
+    messages: [],
+    events: null
+    // events: {
+    //   'events': null,
+    //   'features': null,
+    //   'comments': null
+    // }
   },
 
   mutations: {
@@ -90,9 +96,15 @@ export default new Vuex.Store({
     SET_USER_PERMISSIONS(state, userPermissions) {
       state.user_permissions = userPermissions;
     },
+    SET_EVENTS(state, events) {
+      state.events = events;
+    },
     DISPLAY_MESSAGE(state, comment) {
       state.messages = [{ comment }, ...state.messages];
       document.getElementById("messages").scrollIntoView({ block: "start", inline: "nearest" });
+      setTimeout(() => {
+        state.messages = [];
+      }, 3000);
     },
     CLEAR_MESSAGES(state) {
       state.messages = [];
@@ -180,6 +192,23 @@ export default new Vuex.Store({
       }
     },
 
+    USER_EVENTS({ commit }) {
+      return new Promise((resolve, reject) => {
+        axios
+          .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}events`)
+          .then((response) => {
+            if (response && response.status === 200) {
+              const events = response.data;
+              commit("SET_EVENTS", events);
+              resolve(response.data);
+            }
+          })
+          .catch(() => {
+            reject("Error");
+          });
+      });
+    },
+
     LOGOUT({ commit, dispatch }) {
       // const pageNoRedirect = ["liste-signalements", "details-type-signalement", "details-signalement", "project_detail", "mentions", "aide", "index"]
       axios
diff --git a/src/store/modules/feature.js b/src/store/modules/feature.js
index a239cada37018e3606eecc7486c574382cb91830..055f6e30d8cfdc51001690d19cc95e755e5b25ae 100644
--- a/src/store/modules/feature.js
+++ b/src/store/modules/feature.js
@@ -4,7 +4,7 @@ import router from '../../router'
 axios.defaults.headers.common['X-CSRFToken'] = (name => {
   var re = new RegExp(name + "=([^;]+)");
   var value = re.exec(document.cookie);
-  return (value != null) ? unescape(value[1]) : null;
+  return (value !== null) ? unescape(value[1]) : null;
 })('csrftoken');
 
 
@@ -12,6 +12,8 @@ const feature = {
   namespaced: true,
   state: {
     attachmentFormset: [],
+    attachmentsToDelete: [],
+    attachmentsToPut: [],
     linkedFormset: [],
     features: [],
     form: null,
@@ -63,6 +65,20 @@ const feature = {
     CLEAR_LINKED_FORM(state) {
       state.linkedFormset = [];
     },
+    PUT_ATTACHMENTS(state, attachement) {
+      state.attachmentsToPut = state.attachmentsToPut.filter(el => el.id !== attachement.id);
+      state.attachmentsToPut.push(attachement);
+    },
+    DELETE_ATTACHMENTS(state, attachementId) {
+      // state.attachmentFormset = state.attachmentFormset.filter(el => el.id !== attachementId);
+      state.attachmentsToDelete.push(attachementId);
+    },
+    REMOVE_ATTACHMENTS_ID_TO_PUT(state, attachement) {
+      state.attachmentsToPut = state.attachmentsToPut.filter(el => el.id !== attachement.id);
+    },
+    REMOVE_ATTACHMENTS_ID_TO_DELETE(state, attachementId) {
+      state.attachmentsToDelete = state.attachmentsToDelete.filter(el => el !== attachementId);
+    },
   },
   getters: {
   },
@@ -118,10 +134,12 @@ const feature = {
           ...extraFormObject
         }
       }
-
       if (routeName === "editer-signalement") {
         axios
-          .put(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${state.form.feature_id}/`, geojson)
+          .put(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${state.form.feature_id}/?` +
+            `feature_type__slug=${rootState.feature_type.current_feature_type_slug}` + 
+            `&project__slug=${rootState.project_slug}` 
+          , geojson)
           .then((response) => {
             if (response.status === 200 && response.data) {
               if (state.attachmentFormset.length > 0 || state.linkedFormset.length > 0) {
@@ -202,29 +220,97 @@ const feature = {
       }
     },
 
-    SEND_ATTACHMENTS({ state, rootState }, featureId) {
-      for (let attacht of state.attachmentFormset) {
-        if (attacht.fileToImport) { //* if no new file imported abort, until beeing able to do PUT
+    async SEND_ATTACHMENTS({ state, rootState, dispatch }, featureId) {
+      const DJANGO_API_BASE = rootState.configuration.VUE_APP_DJANGO_API_BASE;
+
+      function postAttachement(attachment) {
+        if (!attachment.id) {
           let formdata = new FormData();
-          formdata.append("file", attacht.fileToImport, attacht.fileToImport.name);
+          formdata.append("file", attachment.fileToImport, attachment.fileToImport.name);
           const data = {
-            title: attacht.title,
-            info: attacht.info,
+            title: attachment.title,
+            info: attachment.info,
           }
           formdata.append("data", JSON.stringify(data));
           axios
-            .post(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${featureId}/attachments/`, formdata)
-            .then((response) => {
-              if (response.status === 200 && response.data) {
-                console.log(response, response.data)
-                return "La pièce jointe a bien été ajouté"
-              }
-            })
+            .post(`${DJANGO_API_BASE}features/${featureId}/attachments/`, formdata)
+            .then((response) => response)
             .catch((error) => {
-              throw error;
+              console.error(error);
+              return error
             });
+            
+        }
+      }
+
+      function putAttachement(attachment, featureId) {
+        let formdataToUpdate = new FormData();
+        if (attachment.fileToImport)
+          formdataToUpdate.append("file", attachment.fileToImport, attachment.fileToImport.name);
+        const data = {}
+        if (attachment.title)
+          data['title'] = attachment.title
+        if (attachment.title)
+          data['info'] = attachment.info
+        formdataToUpdate.append("data", JSON.stringify(data));
+        let payload ={
+          'attachmentsId': attachment.id,
+          'featureId': featureId,
+          'formdataToUpdate': formdataToUpdate
         }
+        return dispatch("PUT_ATTACHMENTS", payload)
+          .then((response) => response);
       }
+
+      function deleteAttachement(attachmentsId, featureId) {
+        let payload ={
+          'attachmentsId': attachmentsId,
+          'featureId': featureId
+        }
+        return dispatch("DELETE_ATTACHMENTS", payload)
+          .then((response) => response);
+      }
+      const promisesResult = await Promise.all([
+        ...state.attachmentFormset.map((attachment) => postAttachement(attachment)),
+        ...state.attachmentsToPut.map((attachments) => putAttachement(attachments, featureId)),
+        ...state.attachmentsToDelete.map((attachmentsId) => deleteAttachement(attachmentsId, featureId))
+        ]
+      );
+      state.attachmentsToDelete = []
+      state.attachmentsToPut = []
+      return promisesResult
+    },
+
+    PUT_ATTACHMENTS({ commit }, payload) {
+      let url = `${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/${payload.featureId}/attachments/${payload.attachmentsId}/`
+      return axios
+        .put(url, payload.formdataToUpdate)
+        .then((response) => {
+          if (response && response.status === 204) {
+            commit("REMOVE_ATTACHMENTS_ID_TO_PUT", payload.attachmentsId)
+            return response
+          }
+        })
+        .catch((error) => {
+          console.error(error);
+          return error
+        });
+    },
+
+    DELETE_ATTACHMENTS({ commit }, payload) {
+      let url = `${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/${payload.featureId}/attachments/${payload.attachmentsId}/`
+      return axios
+        .delete(url)
+        .then((response) => {
+          if (response && response.status === 204) {
+            commit("REMOVE_ATTACHMENTS_ID_TO_DELETE", payload.attachmentsId)
+            return response
+          }
+        })
+        .catch((error) => {
+          console.error(error);
+          return error
+        });
     },
 
     PUT_LINKED_FEATURES({ state, rootState }, featureId) {
@@ -243,7 +329,9 @@ const feature = {
 
     DELETE_FEATURE({ state, rootState }, feature_id) {
       console.log("Deleting feature:", feature_id, state)
-      const url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${feature_id}`;
+      const url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${feature_id}/?` +
+      `feature_type__slug=${rootState.feature_type.current_feature_type_slug}` + 
+      `&project__slug=${rootState.project_slug}`;
       return axios
         .delete(url)
         .then((response) => response)
diff --git a/src/store/modules/feature_type.js b/src/store/modules/feature_type.js
index 1ef68a93448748fdd2c13dc533fba5f02c6714df..a763e047ceb1110cf6576881b9f42d6403e425ed 100644
--- a/src/store/modules/feature_type.js
+++ b/src/store/modules/feature_type.js
@@ -3,7 +3,7 @@ import axios from "axios"
 axios.defaults.headers.common['X-CSRFToken'] = (name => {
   var re = new RegExp(name + "=([^;]+)");
   var value = re.exec(document.cookie);
-  return (value != null) ? unescape(value[1]) : null;
+  return (value !== null) ? unescape(value[1]) : null;
 })('csrftoken');
 
 
diff --git a/src/store/modules/map.js b/src/store/modules/map.js
index 76cfc0bcdd942f9380b417c3e73cc9014587ccce..d47afc9ef721f6a3201ff1db473b051c92552a1e 100644
--- a/src/store/modules/map.js
+++ b/src/store/modules/map.js
@@ -4,7 +4,7 @@ import { mapUtil } from "@/assets/js/map-util.js";
 axios.defaults.headers.common['X-CSRFToken'] = (name => {
   var re = new RegExp(name + "=([^;]+)");
   var value = re.exec(document.cookie);
-  return (value != null) ? unescape(value[1]) : null;
+  return (value !== null) ? unescape(value[1]) : null;
 })('csrftoken');
 
 
@@ -15,11 +15,11 @@ const map = {
     basemapsToDelete: [],
     features: [],
     geojsonFeatures: null,
-    layers: null,
+    availableLayers: null,
   },
   mutations: {
-    SET_LAYERS(state, layers) {
-      state.layers = layers;
+    SET_LAYERS(state, availableLayers) {
+      state.availableLayers = availableLayers;
     },
     SET_GEOJSON_FEATURES(state, geojsonFeatures) {
       state.geojsonFeatures = geojsonFeatures;
@@ -87,7 +87,7 @@ const map = {
   },
 
   actions: {
-    GET_LAYERS({ commit }) {
+    GET_AVAILABLE_LAYERS({ commit }) {
       return axios
         .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}layers/`)
         .then((response) => (commit("SET_LAYERS", response.data)))
@@ -138,7 +138,7 @@ const map = {
             : 0;
         layersToLoad = state.baseMaps[basemapIndex].layers;
         layersToLoad.forEach((layerToLoad) => {
-          state.layers.forEach((layer) => {
+          state.availableLayers.forEach((layer) => {
             if (layer.id === layerToLoad.id) {
               layerToLoad = Object.assign(layerToLoad, layer);
             }
diff --git a/src/views/My_account.vue b/src/views/My_account.vue
index e9f4e9249a7cb07239e52a13d15ba80dcde4900f..8967257b25442a46f9958b202ce1d76486e78d01 100644
--- a/src/views/My_account.vue
+++ b/src/views/My_account.vue
@@ -309,6 +309,21 @@ export default {
     refreshId() {
       return "?ver=" + Math.random();
     },
+    setEvents(data){
+      this.events = data.events;
+      this.features = data.features;
+      this.comments = data.comments;
+    },
+    getEvents(){
+      this.$store
+        .dispatch("USER_EVENTS")
+        .then((data)=>{
+          this.setEvents(data)
+        })
+    }
   },
+  created(){
+    this.getEvents();
+  }
 };
 </script>
\ No newline at end of file
diff --git a/src/views/feature/Feature_detail.vue b/src/views/feature/Feature_detail.vue
index 13a143f5b916939bedad6b305b8513e95c8b635c..62203ff90acf0230451cf02f0f3e22c85579c717 100644
--- a/src/views/feature/Feature_detail.vue
+++ b/src/views/feature/Feature_detail.vue
@@ -8,7 +8,7 @@
               {{ feature.title || feature.feature_id }}
               <div class="ui icon right floated compact buttons">
                 <router-link
-                  v-if="permissions.can_create_feature"
+                  v-if="permissions && permissions.can_create_feature"
                   :to="{
                     name: 'ajouter-signalement',
                     params: {
@@ -22,7 +22,10 @@
                   <i class="plus fitted icon"></i>
                 </router-link>
                 <router-link
-                  v-if="permissions.can_update_feature"
+                  v-if="
+                    (permissions && permissions.can_update_feature) ||
+                    isFeatureCreator
+                  "
                   :to="{
                     name: 'editer-signalement',
                     params: {
@@ -35,7 +38,7 @@
                   <i class="inverted grey pencil alternate icon"></i>
                 </router-link>
                 <a
-                  v-if="permissions.can_delete_feature"
+                  v-if="permissions && permissions.can_delete_feature"
                   @click="isCanceling = true"
                   id="feature-delete"
                   class="ui button button-hover-red"
@@ -95,34 +98,22 @@
                 <td>Statut</td>
                 <td>
                   <i
-                    v-if="feature.status === 'archived'"
-                    class="grey archive icon"
-                  ></i>
-                  <i
-                    v-else-if="feature.status === 'pending'"
-                    class="teal hourglass outline icon"
-                  ></i>
-                  <i
-                    v-else-if="feature.status === 'published'"
-                    class="olive check icon"
+                    v-if="feature.status"
+                    :class="getIconLabelStatus(feature.status, 'icon')"
                   ></i>
-                  <i
-                    v-else-if="feature.status === 'draft'"
-                    class="orange pencil alternate icon"
-                  ></i>
-                  {{ feature.get_status_display }}
+                  {{ getIconLabelStatus(feature.status, 'label') }}
                 </td>
               </tr>
               <tr>
                 <td>Date de création</td>
                 <td v-if="feature.created_on">
-                  {{ feature.created_on }}
+                  {{ feature.created_on | formatDate }}
                 </td>
               </tr>
               <tr>
                 <td>Date de dernière modification</td>
                 <td v-if="feature.updated_on">
-                  {{ feature.updated_on }}
+                  {{ feature.updated_on | formatDate }}
                 </td>
               </tr>
               <tr>
@@ -147,10 +138,13 @@
                 v-for="(link, index) in linked_features"
                 :key="link.feature_to.title + index"
               >
-                <td>
+                <td v-if="link.feature_to.feature_type_slug">
                   {{ link.relation_type_display }}
-                  <router-link
-                    v-if="link.feature_to.feature_type_slug"
+                  <a @click="pushNgo(link)">{{ link.feature_to.title }} </a>
+                  ({{ link.feature_to.display_creator }} -
+                  {{ link.feature_to.created_on }})
+                  <!--  <router-link
+                    :key="$route.fullPath"
                     :to="{
                       name: 'details-signalement',
                       params: {
@@ -162,6 +156,7 @@
                   >
                   ({{ link.feature_to.display_creator }} -
                   {{ link.feature_to.created_on }})
+                  -->
                 </td>
               </tr>
             </tbody>
@@ -267,7 +262,10 @@
             </div>
           </div>
 
-          <div v-if="permissions.can_create_feature" class="ui segment">
+          <div
+            v-if="permissions && permissions.can_create_feature"
+            class="ui segment"
+          >
             <form
               id="form-comment"
               class="ui form"
@@ -416,13 +414,64 @@ export default {
     },
 
     feature: function () {
-      return this.$store.state.feature.features.find(
+      const result = this.$store.state.feature.features.find(
         (el) => el.feature_id === this.$route.params.slug_signal
       );
+      console.log("result", result);
+      return result;
+    },
+
+    isFeatureCreator() {
+      if (this.feature && this.user) {
+        return this.feature.creator === this.user.id;
+      }
+      return false;
+    },
+  },
+
+  filters: {
+    formatDate(value) {
+      let date = new Date(value);
+      date = date.toLocaleString().replace(",", "");
+      return date.substr(0, date.length - 3); //* quick & dirty way to remove seconds from date
     },
   },
 
   methods: {
+    getIconLabelStatus(status, type){
+      if (status  === 'archived')
+        if (type == 'icon')
+          return "grey archive icon";
+        else return 'Archivé';
+      else if (status === 'pending')
+        if (type == 'icon')
+          return "teal hourglass outline icon";
+        else return 'En attente de publication';
+      else if (status === 'published')
+        if (type == 'icon')
+          return "olive check icon";
+        else return 'Publié';
+      else if (status === 'draft')
+        if (type == 'icon')
+          return "orange pencil alternate icon";
+        else return 'Brouillon';
+    },
+    pushNgo(link) {
+      this.$router.push({
+        name: "details-signalement",
+        params: {
+          slug_type_signal: link.feature_to.feature_type_slug,
+          slug_signal: link.feature_to.feature_id,
+        },
+      });
+      this.getFeatureEvents();
+      this.getFeatureAttachments();
+      this.getLinkedFeatures();
+      this.addFeatureToMap();
+      //this.initMap();
+      //this.$router.go();
+    },
+
     postComment() {
       featureAPI
         .postComment({
@@ -484,7 +533,7 @@ export default {
         .dispatch("feature/DELETE_FEATURE", this.feature.feature_id)
         .then((response) => {
           if (response.status === 204) {
-            this.$store.dispatch("feature/GET_PROJECT_FEATURES");
+            this.$store.dispatch("feature/GET_PROJECT_FEATURES", this.$route.params.slug);
             this.goBackToProject();
           }
         });
@@ -514,7 +563,7 @@ export default {
       // - if not, load the default map and service options
       let layersToLoad = null;
       var baseMaps = this.$store.state.map.basemaps;
-      var layers = this.$store.state.map.layers;
+      var layers = this.$store.state.map.availableLayers;
       if (baseMaps && baseMaps.length > 0) {
         const basemapIndex = 0;
         layersToLoad = baseMaps[basemapIndex].layers;
@@ -536,8 +585,13 @@ export default {
       mapUtil.getMap().dragging.disable();
       mapUtil.getMap().doubleClickZoom.disable();
       mapUtil.getMap().scrollWheelZoom.disable();
-      var currentFeatureId = this.$route.params.slug_signal;
-      const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}features/${currentFeatureId}/?output=geojson&project__slug=${this.$route.params.slug}`;
+
+      this.addFeatureToMap();
+    },
+
+    addFeatureToMap() {
+      const currentFeatureId = this.$route.params.slug_signal;
+      const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}features/${currentFeatureId}/?feature_type__slug=${this.$route.params.slug_type_signal}&output=geojson`;
       axios
         .get(url)
         .then((response) => {
@@ -545,7 +599,7 @@ export default {
           if (feature) {
             const currentFeature = [feature];
             const featureGroup = mapUtil.addFeatures(currentFeature);
-            mapUtil.getMap().fitBounds(featureGroup.getBounds());
+            mapUtil.getMap().fitBounds(featureGroup.getBounds(), { padding: [25, 25] });
           }
         })
         .catch((error) => {
@@ -575,9 +629,9 @@ export default {
   },
 
   created() {
-    if (!this.project) {
-      this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
-    }
+    // if (!this.project) {
+    //   this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
+    // }
     this.$store.commit(
       "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
       this.$route.params.slug_type_signal
@@ -588,12 +642,16 @@ export default {
   },
 
   mounted() {
-    this.$store
-      .dispatch("GET_PROJECT_INFO", this.$route.params.slug)
-      .then((data) => {
-        console.log(data);
-        this.initMap();
-      });
+    if (!this.project) {
+      this.$store
+        .dispatch("GET_PROJECT_INFO", this.$route.params.slug)
+        .then((data) => {
+          console.log(data);
+          this.initMap();
+        });
+    } else {
+      this.initMap();
+    }
   },
 
   beforeDestroy() {
diff --git a/src/views/feature/Feature_edit.vue b/src/views/feature/Feature_edit.vue
index eced8898b42946d691371c15a95b435ecea50825..7554435541d5a8062cab1c0af123650d2a7a129b 100644
--- a/src/views/feature/Feature_edit.vue
+++ b/src/views/feature/Feature_edit.vue
@@ -250,7 +250,7 @@ import flip from "@turf/flip";
 axios.defaults.headers.common["X-CSRFToken"] = ((name) => {
   var re = new RegExp(name + "=([^;]+)");
   var value = re.exec(document.cookie);
-  return value != null ? unescape(value[1]) : null;
+  return value !== null ? unescape(value[1]) : null;
 })("csrftoken");
 
 export default {
@@ -271,7 +271,6 @@ export default {
   data() {
     return {
       map: null,
-      feature_type: null,
       baseUrl: this.$store.state.configuration.BASE_URL,
       file: null,
       showGeoRef: false,
@@ -336,10 +335,13 @@ export default {
 
   computed: {
     ...mapGetters(["project"]),
+    ...mapGetters("feature_type", ["feature_type"]),
     ...mapState(["user", "USER_LEVEL_PROJECTS"]),
     ...mapState("map", ["basemaps"]),
     ...mapState("feature", [
       "attachmentFormset",
+      "attachmentsToDelete",
+      "attachmentsToPut",
       "linkedFormset",
       "features",
       "extra_form",
@@ -386,21 +388,22 @@ export default {
       if (this.project) {
         const isModerate = this.project.moderation;
         const userStatus = this.USER_LEVEL_PROJECTS[this.project.slug];
-        //* si admin ou modérateur, statuts toujours disponible :  	Brouillon, Publié, Archivé
+        const isOwnFeature = this.feature //* prevent undefined feature
+          ? this.feature.creator === this.user.id
+          : false; //* si le contributeur est l'auteur du signalement
         if (
+          //* si admin ou modérateur, statuts toujours disponible :  	Brouillon, Publié, Archivé
           userStatus === "Modérateur" ||
           userStatus === "Administrateur projet"
         ) {
           return this.statusChoices.filter((el) => el.value !== "pending");
         } else if (userStatus === "Contributeur") {
+          //* cas particuliers du contributeur
           if (
-            this.currentRouteName === "editer-signalement" &&
-            this.project.creator === this.user.id
+            this.currentRouteName === "ajouter-signalement" ||
+            !isOwnFeature
           ) {
-            //* IF création de project
-            return this.statusChoices.filter((el) => el.value === "draft");
-          } else {
-            //* ELSE édition de projet
+            //* même cas à l'ajout d'une feature ou si feature n'a pas été créé par le contributeur
             return isModerate
               ? this.statusChoices.filter(
                   (el) => el.value === "draft" || el.value === "pending"
@@ -408,22 +411,22 @@ export default {
               : this.statusChoices.filter(
                   (el) => el.value === "draft" || el.value === "published"
                 );
+          } else {
+            //* à l'édition d'une feature et si le contributeur est l'auteur de la feature
+            return isModerate
+              ? this.statusChoices.filter(
+                  (el) => el.value !== "published" //* toutes sauf "Publié"
+                )
+              : this.statusChoices.filter(
+                  (el) => el.value !== "pending" //* toutes sauf "En cours de publication"
+                );
           }
         }
       }
-      return []
+      return [];
     },
   },
 
-  /*   watch: {
-    feature(newValue) {
-      if (this.$route.name === "editer-signalement") {
-        this.initForm();
-        this.initExtraForms(newValue);
-      }
-    },
-  }, */
-
   methods: {
     initForm() {
       if (this.currentRouteName === "editer-signalement") {
@@ -538,6 +541,7 @@ export default {
       }); // * create an object with the counter in store
       this.attachmentDataKey += 1; // * increment counter for key in v-for
     },
+
     addAttachment(attachment) {
       console.log(attachment);
       this.$store.commit("feature/ADD_ATTACHMENT_FORM", {
@@ -550,6 +554,7 @@ export default {
       });
       this.attachmentDataKey += 1;
     },
+
     addExistingAttachementFormset(attachementFormset) {
       for (const attachment of attachementFormset) {
         this.addAttachment(attachment);
@@ -847,7 +852,7 @@ export default {
         } else if (geomType === "polygon") {
           L.polygon(geomJSON.coordinates).addTo(this.drawnItems);
         }
-        this.map.fitBounds(this.drawnItems.getBounds());
+        this.map.fitBounds(this.drawnItems.getBounds(), { padding: [25, 25] });
       } else {
         this.map.setView(
           this.$store.state.configuration.DEFAULT_MAP_VIEW.center,
@@ -875,7 +880,7 @@ export default {
       });
       const currentFeatureId = this.$route.params.slug_signal;
 
-      const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
+      const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?feature_type__slug=${this.$route.params.slug_type_signal}&output=geojson`;
       axios
         .get(url)
         .then((response) => {
@@ -932,14 +937,6 @@ export default {
   },
 
   created() {
-    /*     if (!this.project) {
-      console.log(this.$store.state.projects);
-      this.project = this.$store.state.projects.find(
-        (project) => project.slug === this.$store.state.project_slug
-      );
-      this.filterStatusChoicesF();
-    } */
-
     this.$store.commit(
       "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
       this.$route.params.slug_type_signal
@@ -952,17 +949,12 @@ export default {
   },
 
   mounted() {
-    let ftSlug = this.$route.params.slug_type_signal;
     this.$store
       .dispatch("GET_PROJECT_INFO", this.$route.params.slug)
       .then((data) => {
         console.log(data);
         this.initForm();
         this.initMap();
-        console.log(this.$store.state.feature_type.feature_types);
-        this.feature_type = this.$store.state.feature_type.feature_types.find(
-          (el) => el.slug === ftSlug
-        );
         this.onFeatureTypeLoaded();
         this.initExtraForms();
 
diff --git a/src/views/feature/Feature_list.vue b/src/views/feature/Feature_list.vue
index 8b125aa0203caa31995d95746c451f8e232aea16..208a89bf9f3f6632aa24ee4e640abc85d926bd8b 100644
--- a/src/views/feature/Feature_list.vue
+++ b/src/views/feature/Feature_list.vue
@@ -1,5 +1,6 @@
 <template>
   <div class="fourteen wide column">
+    <div class="ui dimmer" :class="[{ active: featureLoading }]"></div>
     <script
       type="application/javascript"
       :src="
@@ -12,9 +13,7 @@
         <h1>Signalements</h1>
       </div>
       <div class="twelve-wide column no-padding-mobile mobile-fullwidth">
-        <div class="ui dimmer" :class="[{ active: featureLoading }]">
-          <div class="ui large text loader">Chargement</div>
-        </div>
+        <div class="ui large text loader">Chargement</div>
         <div class="ui secondary menu no-margin">
           <a
             @click="showMap = true"
@@ -94,6 +93,7 @@
           :selected="form.type.selected"
           :selection.sync="form.type.selected"
           :search="true"
+          :clearable="true"
         />
       </div>
       <div class="field wide four column no-padding-mobile no-margin-mobile">
@@ -105,6 +105,7 @@
           :selected="form.status.selected.name"
           :selection.sync="form.status.selected"
           :search="true"
+          :clearable="true"
         />
       </div>
       <div class="field wide four column">
@@ -138,192 +139,13 @@
       <div id="map"></div>
       <SidebarLayers v-if="baseMaps && map" />
     </div>
+    <FeatureListTable
+      v-show="!showMap"
+      :filteredFeatures="filteredFeatures"
+      :user="user"
+      :checkedFeatures.sync="checkedFeatures"
+    />
 
-    <div v-show="!showMap" data-tab="list" class="dataTables_wrapper no-footer">
-      <table id="table-features" class="ui compact table">
-        <thead>
-          <tr>
-            <th class="center"></th>
-
-            <th class="center">
-              Statut
-              <i
-                :class="{
-                  down: isSortedAsc('statut'),
-                  up: isSortedDesc('statut'),
-                }"
-                class="icon sort"
-                @click="changeSort('statut')"
-              />
-            </th>
-            <th class="center">
-              Type
-              <i
-                :class="{ down: isSortedAsc('type'), up: isSortedDesc('type') }"
-                class="icon sort"
-                @click="changeSort('type')"
-              />
-            </th>
-            <th class="center">
-              Nom
-              <i
-                :class="{ down: isSortedAsc('nom'), up: isSortedDesc('nom') }"
-                class="icon sort"
-                @click="changeSort('nom')"
-              />
-            </th>
-            <th class="center">
-              Dernière modification
-              <i
-                :class="{
-                  down: isSortedAsc('updated_on'),
-                  up: isSortedDesc('updated_on'),
-                }"
-                class="icon sort"
-                @click="changeSort('updated_on')"
-              />
-            </th>
-            <th class="center" v-if="user">
-              Auteur
-              <i
-                :class="{
-                  down: isSortedAsc('display_creator'),
-                  up: isSortedDesc('display_creator'),
-                }"
-                class="icon sort"
-                @click="changeSort('display_creator')"
-              />
-            </th>
-          </tr>
-        </thead>
-        <tbody>
-          <tr v-for="(feature, index) in paginatedFeatures" :key="index">
-            <td class="center">
-              <div class="ui checkbox">
-                <input
-                  type="checkbox"
-                  :id="feature.id"
-                  :value="feature.id"
-                  v-model="checkedFeatures"
-                  :checked="checkedFeatures[feature.id]"
-                />
-                <label></label>
-              </div>
-            </td>
-
-            <td class="center">
-              <div
-                v-if="feature.properties.status.value == 'archived'"
-                data-tooltip="Archivé"
-              >
-                <i class="grey archive icon"></i>
-              </div>
-              <div
-                v-else-if="feature.properties.status.value == 'pending'"
-                data-tooltip="En attente de publication"
-              >
-                <i class="teal hourglass outline icon"></i>
-              </div>
-              <div
-                v-else-if="feature.properties.status.value == 'published'"
-                data-tooltip="Publié"
-              >
-                <i class="olive check icon"></i>
-              </div>
-              <div
-                v-else-if="feature.properties.status.value == 'draft'"
-                data-tooltip="Brouillon"
-              >
-                <i class="orange pencil alternate icon"></i>
-              </div>
-            </td>
-            <td class="center">
-              <router-link
-                :to="{
-                  name: 'details-type-signalement',
-                  params: {
-                    feature_type_slug: feature.properties.feature_type.slug,
-                  },
-                }"
-              >
-                {{ feature.properties.feature_type.title }}
-              </router-link>
-            </td>
-            <td class="center">
-              <router-link
-                :to="{
-                  name: 'details-signalement',
-                  params: {
-                    slug_type_signal: feature.properties.feature_type.slug,
-                    slug_signal: feature.properties.slug || feature.id,
-                  },
-                }"
-                >{{ getFeatureDisplayName(feature) }}</router-link
-              >
-            </td>
-            <td class="center">
-              <!-- |date:'Ymd' -->
-              {{ feature.properties.updated_on }}
-            </td>
-            <td class="center" v-if="user">
-              {{ feature.properties.creator.username || " ---- " }}
-            </td>
-          </tr>
-          <tr v-if="filteredFeatures.length === 0" class="odd">
-            <td colspan="5" class="dataTables_empty" valign="top">
-              Aucune donnée disponible
-            </td>
-          </tr>
-        </tbody>
-      </table>
-      <div
-        class="dataTables_info"
-        id="table-features_info"
-        role="status"
-        aria-live="polite"
-      >
-        Affichage de l'élément {{ pagination.start + 1 }} à
-        {{ pagination.end + 1 }} sur {{ filteredFeatures.length }} éléments
-      </div>
-      <div
-        class="dataTables_paginate paging_simple_numbers"
-        id="table-features_paginate"
-      >
-        <a
-          @click="toPreviousPage"
-          class="paginate_button previous disabled"
-          aria-controls="table-features"
-          data-dt-idx="0"
-          tabindex="0"
-          id="table-features_previous"
-          >Précédent</a
-        >
-        <span>
-          <a
-            v-for="(page, index) in nbPages"
-            :key="'page' + index"
-            :class="[
-              'paginate_button',
-              { current: page.value === pagination.currentPage },
-            ]"
-            aria-controls="table-features"
-            data-dt-idx="1"
-            tabindex="0"
-            >{{ page.value }}</a
-          >
-        </span>
-        <!-- // TODO : <span v-if="nbPages > 4" class="ellipsis">...</span> -->
-        <a
-          class="paginate_button next"
-          aria-controls="table-features"
-          data-dt-idx="7"
-          tabindex="0"
-          id="table-features_next"
-          @click="toNextPage"
-          >Suivant</a
-        >
-      </div>
-    </div>
     <!-- MODAL ALL DELETE FEATURE TYPE -->
     <div
       v-if="modalAllDeleteOpen"
@@ -340,7 +162,7 @@
         <div class="ui icon header">
           <i class="trash alternate icon"></i>
           Êtes-vous sûr de vouloir effacer
-          <span v-if="checkedFeatures.length == 1"> un signalement ? </span>
+          <span v-if="checkedFeatures.length === 1"> un signalement ? </span>
           <span v-else> ces {{ checkedFeatures.length }} signalements ? </span>
         </div>
         <div class="actions">
@@ -361,6 +183,7 @@
 import { mapGetters, mapState } from "vuex";
 import { mapUtil } from "@/assets/js/map-util.js";
 import SidebarLayers from "@/components/map-layers/SidebarLayers";
+import FeatureListTable from "@/components/feature/FeatureListTable";
 import Dropdown from "@/components/Dropdown.vue";
 const axios = require("axios");
 
@@ -370,6 +193,7 @@ export default {
   components: {
     SidebarLayers,
     Dropdown,
+    FeatureListTable,
   },
 
   data() {
@@ -407,16 +231,7 @@ export default {
         },
         title: null,
       },
-      pagination: {
-        currentPage: 1,
-        pagesize: 15,
-        start: 0,
-        end: 14,
-      },
-      sort: {
-        column: "",
-        ascending: true,
-      },
+
       geojsonFeatures: [],
       featureLoading: false,
       filterStatus: null,
@@ -441,24 +256,6 @@ export default {
       return this.$store.state.map.basemaps;
     },
 
-    nbPages() {
-      let N = Math.round(
-        this.filteredFeatures.length / this.pagination.pagesize
-      );
-      let rest = Math.round(
-        this.filteredFeatures.length % this.pagination.pagesize
-      );
-      if (rest > 0) N++;
-      const arr = [...Array(N).keys()].map(function (x) {
-        ++x;
-        return {
-          index: x,
-          value: x,
-        };
-      });
-      return arr;
-    },
-
     filteredFeatures() {
       let results = this.geojsonFeatures;
       if (this.filterType) {
@@ -484,50 +281,6 @@ export default {
       }
       return results;
     },
-
-    paginatedFeatures() {
-      let filterdFeatures = [...this.filteredFeatures];
-      // Ajout du tri
-      if (this.sort.column != "") {
-        filterdFeatures = filterdFeatures.sort((a, b) => {
-          let aProp = this.getFeatureDisplayName(a);
-          let bProp = this.getFeatureDisplayName(b);
-          if (this.sort.column == "statut") {
-            aProp = a.properties.status.value;
-            bProp = b.properties.status.value;
-          } else if (this.sort.column == "type") {
-            aProp = a.properties.feature_type.title;
-            bProp = b.properties.feature_type.title;
-          } else if (this.sort.column == "updated_on") {
-            aProp = a.properties.updated_on;
-            bProp = b.properties.updated_on;
-          } else if (this.sort.column == "display_creator") {
-            aProp = a.properties.display_creator;
-            bProp = b.properties.display_creator;
-          }
-          //ascending
-          if (this.sort.ascending) {
-            if (aProp < bProp) {
-              return -1;
-            }
-            if (aProp > bProp) {
-              return 1;
-            }
-            return 0;
-          } else {
-            //descending
-            if (aProp < bProp) {
-              return 1;
-            }
-            if (aProp > bProp) {
-              return -1;
-            }
-            return 0;
-          }
-        });
-      }
-      return filterdFeatures.slice(this.pagination.start, this.pagination.end);
-    },
   },
 
   methods: {
@@ -558,27 +311,6 @@ export default {
       this.modalAllDelete();
     },
 
-    getFeatureDisplayName(feature) {
-      return feature.properties.title || feature.id;
-    },
-
-    isSortedAsc(column) {
-      return this.sort.column == column && this.sort.ascending;
-    },
-    isSortedDesc(column) {
-      return this.sort.column == column && !this.sort.ascending;
-    },
-
-    changeSort(column) {
-      if (this.sort.column == column) {
-        //changer order
-        this.sort.ascending = !this.sort.ascending;
-      } else {
-        this.sort.column = column;
-        this.sort.ascending = true;
-      }
-    },
-
     onFilterStatusChange(newvalue) {
       this.filterStatus = null;
       if (newvalue) {
@@ -603,24 +335,59 @@ export default {
       this.featureGroup.clearLayers();
       this.featureGroup = mapUtil.addFeatures(features, {});
       if (features.length > 0) {
-        mapUtil.getMap().fitBounds(this.featureGroup.getBounds());
+        mapUtil
+          .getMap()
+          .fitBounds(this.featureGroup.getBounds(), { padding: [25, 25] });
       }
     },
 
-    toPreviousPage() {
-      if (this.pagination.start > 0) {
-        this.pagination.start -= this.pagination.pagesize;
-        this.pagination.end -= this.pagination.pagesize;
-        this.pagination.currentPage -= 1;
-      }
-    },
+    initMap() {
+      this.zoom = this.$route.query.zoom || "";
+      this.lat = this.$route.query.lat || "";
+      this.lng = this.$route.query.lng || "";
+      var mapDefaultViewCenter =
+        this.$store.state.configuration.DEFAULT_MAP_VIEW.center;
+      var mapDefaultViewZoom =
+        this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom;
+
+      this.map = mapUtil.createMap({
+        zoom: this.zoom,
+        lat: this.lat,
+        lng: this.lng,
+        mapDefaultViewCenter,
+        mapDefaultViewZoom,
+      });
+
+      document.addEventListener("change-layers-order", (event) => {
+        // Reverse is done because the first layer in order has to be added in the map in last.
+        // Slice is done because reverse() changes the original array, so we make a copy first
+        mapUtil.updateOrder(event.detail.layers.slice().reverse());
+      });
 
-    toNextPage() {
-      if (this.pagination.end < this.filteredFeatures.length) {
-        this.pagination.start += this.pagination.pagesize;
-        this.pagination.end += this.pagination.pagesize;
-        this.pagination.currentPage += 1;
+      // --------- End sidebar events ----------
+      if (this.$store.state.map.geojsonFeatures) {
+        this.loadFeatures(this.$store.state.map.geojsonFeatures);
+      } else {
+        const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
+        this.featureLoading = true;
+        axios
+          .get(url)
+          .then((response) => {
+            this.loadFeatures(response.data.features);
+            this.featureLoading = false;
+          })
+          .catch((error) => {
+            this.featureLoading = false;
+            throw error;
+          });
       }
+
+      setTimeout(
+        function () {
+          mapUtil.addGeocoders(this.$store.state.configuration);
+        }.bind(this),
+        1000
+      );
     },
 
     loadFeatures(features) {
@@ -639,7 +406,9 @@ export default {
         (this.lat === "" || this.lng === "" || this.zoom === "") &&
         this.geojsonFeatures.length > 0
       ) {
-        mapUtil.getMap().fitBounds(this.featureGroup.getBounds());
+        mapUtil
+          .getMap()
+          .fitBounds(this.featureGroup.getBounds(), { padding: [25, 25] });
       }
       this.form.type.choices = [
         //* converting Set to an Array with spread "..."
@@ -658,52 +427,7 @@ export default {
   },
 
   mounted() {
-    this.zoom = this.$route.query.zoom || "";
-    this.lat = this.$route.query.lat || "";
-    this.lng = this.$route.query.lng || "";
-    var mapDefaultViewCenter =
-      this.$store.state.configuration.DEFAULT_MAP_VIEW.center;
-    var mapDefaultViewZoom =
-      this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom;
-
-    this.map = mapUtil.createMap({
-      zoom: this.zoom,
-      lat: this.lat,
-      lng: this.lng,
-      mapDefaultViewCenter,
-      mapDefaultViewZoom,
-    });
-
-    document.addEventListener("change-layers-order", (event) => {
-      // Reverse is done because the first layer in order has to be added in the map in last.
-      // Slice is done because reverse() changes the original array, so we make a copy first
-      mapUtil.updateOrder(event.detail.layers.slice().reverse());
-    });
-
-    // --------- End sidebar events ----------
-    if (this.$store.state.map.geojsonFeatures) {
-      this.loadFeatures(this.$store.state.map.geojsonFeatures);
-    } else {
-      const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
-      this.featureLoading = true;
-      axios
-        .get(url)
-        .then((response) => {
-          this.loadFeatures(response.data.features);
-          this.featureLoading = false;
-        })
-        .catch((error) => {
-          this.featureLoading = false;
-          throw error;
-        });
-    }
-
-    setTimeout(
-      function () {
-        mapUtil.addGeocoders(this.$store.state.configuration);
-      }.bind(this),
-      1000
-    );
+    this.initMap();
   },
 };
 </script>
@@ -778,151 +502,5 @@ export default {
     width: 100%;
   }
 }
-/* datatables */
-.dataTables_wrapper {
-  position: relative;
-  clear: both;
-}
-.dataTables_wrapper .dataTables_length,
-.dataTables_wrapper .dataTables_filter,
-.dataTables_wrapper .dataTables_info,
-.dataTables_wrapper .dataTables_processing,
-.dataTables_wrapper .dataTables_paginate {
-  color: #333;
-}
-.dataTables_wrapper .dataTables_info {
-  clear: both;
-  float: left;
-  padding-top: 0.755em;
-}
-.dataTables_wrapper .dataTables_paginate {
-  float: right;
-  text-align: right;
-  padding-top: 0.25em;
-}
-.dataTables_wrapper .dataTables_paginate .paginate_button.current,
-.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
-  color: #333 !important;
-  border: 1px solid #979797;
-  background-color: white;
-  background: -webkit-gradient(
-    linear,
-    left top,
-    left bottom,
-    color-stop(0%, #fff),
-    color-stop(100%, #dcdcdc)
-  );
-  background: -webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);
-  background: -moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);
-  background: -ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);
-  background: -o-linear-gradient(top, #fff 0%, #dcdcdc 100%);
-  background: linear-gradient(to bottom, #fff 0%, #dcdcdc 100%);
-}
-.dataTables_wrapper .dataTables_paginate .paginate_button {
-  box-sizing: border-box;
-  display: inline-block;
-  min-width: 1.5em;
-  padding: 0.5em 1em;
-  margin-left: 2px;
-  text-align: center;
-  text-decoration: none !important;
-  cursor: pointer;
-  color: #333 !important;
-  border: 1px solid transparent;
-  border-radius: 2px;
-}
-.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,
-.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active {
-  cursor: default;
-  color: #666 !important;
-  border: 1px solid transparent;
-  background: transparent;
-  box-shadow: none;
-}
-
-/* 
-Max width before this PARTICULAR table gets nasty
-This query will take effect for any screen smaller than 760px
-and also iPads specifically.
-*/
-@media only screen and (max-width: 760px),
-  (min-device-width: 768px) and (max-device-width: 1024px) {
-  /* Force table to not be like tables anymore */
-  table,
-  thead,
-  tbody,
-  th,
-  td,
-  tr {
-    display: block;
-  }
-
-  /* Hide table headers (but not display: none;, for accessibility) */
-  thead tr {
-    position: absolute;
-    top: -9999px;
-    left: -9999px;
-  }
-
-  tr {
-    border: 1px solid #ccc;
-  }
-
-  td {
-    /* Behave  like a "row" */
-    border: none;
-    border-bottom: 1px solid #eee;
-    position: relative;
-    padding-left: 50%;
-  }
-
-  td:before {
-    /* Now like a table header */
-    position: absolute;
-    /* Top/left values mimic padding */
-    /* top: 6px; */
-    left: 6px;
-    /* width: 45%; */
-    padding-right: 10px;
-    white-space: nowrap;
-  }
-
-  /*
-	Label the data
-	*/
-  td:nth-of-type(1):before {
-    content: "";
-  }
-  td:nth-of-type(2):before {
-    content: "Statut";
-  }
-  td:nth-of-type(3):before {
-    content: "Type";
-  }
-  td:nth-of-type(4):before {
-    content: "Nom";
-  }
-  td:nth-of-type(5):before {
-    content: "Dernière modification";
-  }
-  td:nth-of-type(6):before {
-    content: "Auteur";
-  }
-
-  .center {
-    text-align: right !important;
-  }
-
-  #table-features {
-    margin-left: 1em;
-    width: calc(100% - 1em);
-  }
-
-  .ui.checkbox {
-    position: absolute;
-    left: -1.75em;
-    top: 5em;
-  }
-}
 </style>
 
diff --git a/src/views/feature_type/Feature_type_detail.vue b/src/views/feature_type/Feature_type_detail.vue
index 39e36dd8add944dfab3bb6b8e6ec166b81d0b9ae..37a732a06ff1fba6c92e8eeb21af894c752b39a8 100644
--- a/src/views/feature_type/Feature_type_detail.vue
+++ b/src/views/feature_type/Feature_type_detail.vue
@@ -4,17 +4,17 @@
       <div class="ui attached secondary segment">
         <h1 class="ui center aligned header">
           <img
-            v-if="structure.geom_type == 'point'"
+            v-if="structure.geom_type === 'point'"
             class="ui medium image"
             src="@/assets/img/marker.png"
           />
           <img
-            v-if="structure.geom_type == 'linestring'"
+            v-if="structure.geom_type === 'linestring'"
             class="ui medium image"
             src="@/assets/img/line.png"
           />
           <img
-            v-if="structure.geom_type == 'polygon'"
+            v-if="structure.geom_type === 'polygon'"
             class="ui medium image"
             src="@/assets/img/polygon.png"
           />
@@ -35,8 +35,8 @@
           <h3 class="ui header">Champs</h3>
           <div class="ui divided list">
             <div
-              v-for="field in structure.customfield_set"
-              :key="field.label"
+              v-for="(field, index) in structure.customfield_set"
+              :key="field.name + index"
               class="item"
             >
               <div class="right floated content">
@@ -74,7 +74,7 @@
                 />
               </div>
               <button
-                :disabled="fileToImport.size == 0"
+                :disabled="fileToImport.size === 0"
                 @click="importGeoJson"
                 class="ui fluid teal icon button"
               >
@@ -118,19 +118,19 @@
         :key="feature.feature_id + index"
         class="ui small header"
       >
-        <span v-if="feature.status == 'archived'" data-tooltip="Archivé">
+        <span v-if="feature.status === 'archived'" data-tooltip="Archivé">
           <i class="grey archive icon"></i>
         </span>
         <span
-          v-else-if="feature.status == 'pending'"
+          v-else-if="feature.status === 'pending'"
           data-tooltip="En attente de publication"
         >
           <i class="teal hourglass outline icon"></i>
         </span>
-        <span v-else-if="feature.status == 'published'" data-tooltip="Publié">
+        <span v-else-if="feature.status === 'published'" data-tooltip="Publié">
           <i class="olive check icon"></i>
         </span>
-        <span v-else-if="feature.status == 'draft'" data-tooltip="Brouillon">
+        <span v-else-if="feature.status === 'draft'" data-tooltip="Brouillon">
           <i class="orange pencil alternate icon"></i>
         </span>
         <router-link
diff --git a/src/views/feature_type/Feature_type_edit.vue b/src/views/feature_type/Feature_type_edit.vue
index 69b931dd72496527d5b20f6d97f9f18e8732668d..691443a6d7d5ce34aa95be04ca40851e3e536a85 100644
--- a/src/views/feature_type/Feature_type_edit.vue
+++ b/src/views/feature_type/Feature_type_edit.vue
@@ -446,9 +446,9 @@ export default {
     },
 
     translateLabel(value) {
-      if (value == "LineString") {
+      if (value === "LineString") {
         return "linestring";
-      } else if (value == "Polygon" || value == "MultiPolygon") {
+      } else if (value === "Polygon" || value === "MultiPolygon") {
         return "polygon";
       }
       return "point";
diff --git a/src/views/project/Project_detail.vue b/src/views/project/Project_detail.vue
index af79636802f604ed034139149365442acab5ecf2..0c50bf909bf19bcea74c9c2a932dc2c6f25d3765 100644
--- a/src/views/project/Project_detail.vue
+++ b/src/views/project/Project_detail.vue
@@ -96,6 +96,7 @@
 
       <div class="row">
         <div class="seven wide column">
+          <h3 class="ui header">Types de signalements</h3>
           <div class="ui middle aligned divided list">
             <div
               v-for="(type, index) in feature_types"
@@ -110,17 +111,17 @@
                   }"
                 >
                   <img
-                    v-if="type.geom_type == 'point'"
+                    v-if="type.geom_type === 'point'"
                     class="list-image-type"
                     src="@/assets/img/marker.png"
                   />
                   <img
-                    v-if="type.geom_type == 'linestring'"
+                    v-if="type.geom_type === 'linestring'"
                     class="list-image-type"
                     src="@/assets/img/line.png"
                   />
                   <img
-                    v-if="type.geom_type == 'polygon'"
+                    v-if="type.geom_type === 'polygon'"
                     class="list-image-type"
                     src="@/assets/img/polygon.png"
                   />
@@ -163,7 +164,9 @@
                     params: { slug_type_signal: type.slug },
                   }"
                   v-if="
-                    project && permissions && permissions.can_create_feature
+                    project &&
+                    permissions &&
+                    permissions.can_create_feature_type
                   "
                   class="
                     ui
@@ -185,7 +188,12 @@
                     name: 'editer-type-signalement',
                     params: { slug_type_signal: type.slug },
                   }"
-                  v-if="project && type.is_editable"
+                  v-if="
+                    project &&
+                    type.is_editable &&
+                    permissions &&
+                    permissions.can_create_feature_type
+                  "
                   class="
                     ui
                     compact
@@ -252,7 +260,7 @@
             <br />
             <div id="button-import" v-if="fileToImport.size > 0">
               <button
-                :disabled="fileToImport.size == 0"
+                :disabled="fileToImport.size === 0"
                 @click="toNewFeatureType"
                 class="ui fluid teal icon button"
               >
@@ -658,7 +666,9 @@ export default {
           const features = response.data.features;
           const featureGroup = mapUtil.addFeatures(features);
           if (featureGroup && featureGroup.getLayers().length > 0) {
-            mapUtil.getMap().fitBounds(featureGroup.getBounds());
+            mapUtil
+              .getMap()
+              .fitBounds(featureGroup.getBounds(), { padding: [25, 25] });
             self.$store.commit("map/SET_GEOJSON_FEATURES", features);
           }
         })
diff --git a/src/views/project/Project_edit.vue b/src/views/project/Project_edit.vue
index 8a398bd174b71581e4388f04c1641152c9d12d7a..7bbf384af7270a9e3657214bde60d19468d5887d 100644
--- a/src/views/project/Project_edit.vue
+++ b/src/views/project/Project_edit.vue
@@ -1,9 +1,6 @@
 <template>
   <div class="fourteen wide column">
-    <div
-      :class="{active: loading}"
-      class="ui inverted dimmer"
-    >
+    <div :class="{ active: loading }" class="ui inverted dimmer">
       <div class="ui text loader">
         Projet en cours de création. Vous allez être redirigé.
       </div>
@@ -202,7 +199,7 @@ import { mapGetters } from "vuex";
 axios.defaults.headers.common["X-CSRFToken"] = ((name) => {
   var re = new RegExp(name + "=([^;]+)");
   var value = re.exec(document.cookie);
-  return value != null ? unescape(value[1]) : null;
+  return value !== null ? unescape(value[1]) : null;
 })("csrftoken");
 
 export default {
@@ -346,7 +343,7 @@ export default {
 
     postProjectThumbnail(projectSlug) {
       //* send img to the backend when feature_type is created
-      if (this.isValidImage(this.fileToImport)) {
+      if (this.fileToImport) {
         let formData = new FormData();
         formData.append("file", this.fileToImport);
         const url =
@@ -413,7 +410,7 @@ export default {
         moderation: this.form.moderation,
       };
 
-      if (this.action === "create" || this.action === "duplicate") {
+      if (this.action === "create" || this.action === "create_from") {
         this.loading = true;
         await axios
           .post(
@@ -432,7 +429,7 @@ export default {
             this.loading = false;
           })
           .catch((error) => {
-            if (error.response.data.title[0]) {
+            if (error.response && error.response.data.title[0]) {
               this.errors.title.push(error.response.data.title[0]);
             }
             this.loading = false;
@@ -455,7 +452,7 @@ export default {
             }
           })
           .catch((error) => {
-            if (error.response.data.title[0]) {
+            if (error.response && error.response.data.title[0]) {
               this.errors.title.push(error.response.data.title[0]);
             }
             throw error;
diff --git a/src/views/project/Project_mapping.vue b/src/views/project/Project_mapping.vue
index fb34bc264794a3096c4aa2ed2018489024b1eac0..34397ecb7b5f37acb1814dac5b3e6236cb799654 100644
--- a/src/views/project/Project_mapping.vue
+++ b/src/views/project/Project_mapping.vue
@@ -83,12 +83,17 @@ export default {
       this.newBasemapIds.push(this.basemapMaxId + 1); //* register new basemaps to seperate post and put
       this.$store.commit("map/CREATE_BASEMAP", this.basemapMaxId + 1);
     },
+    
     checkTitles() {
       let isValid = true;
       this.basemaps.forEach((basemap) => {
+        console.log(basemap);
         if (basemap.title === null || basemap.title === "") {
           basemap.errors = "Veuillez compléter ce champ.";
           isValid = false;
+        } else if (basemap.layers.length === 0) {
+          basemap.errors = "Veuillez ajouter au moins un layer.";
+          isValid = false;
         } else {
           basemap.errors = "";
         }
@@ -102,7 +107,8 @@ export default {
           .dispatch("map/SAVE_BASEMAPS", this.newBasemapIds)
           .then((response) => {
             const errors = response.filter(
-              (res) => res.status !== 200 && res.status !== 204
+              (res) =>
+                res.status === 200 && res.status === 201 && res.status === 204
             );
             if (errors.length === 0) {
               this.infoMessage.push({
diff --git a/src/views/project/Project_members.vue b/src/views/project/Project_members.vue
index bb03c3d8e0f18ab5e9b3b6b4364ba77b8db70b35..a838d755e80c0131efa9de35a3b5b1c63955eea9 100644
--- a/src/views/project/Project_members.vue
+++ b/src/views/project/Project_members.vue
@@ -107,6 +107,12 @@ export default {
         .then((response) => {
           if (response.status === 200) {
             this.$store.dispatch("GET_USER_LEVEL_PROJECTS"); //* update user status in top right menu
+            this.$store.commit("DISPLAY_MESSAGE", "Permissions mises à jour");
+          } else {
+            this.$store.commit(
+              "DISPLAY_MESSAGE",
+              "Une erreur s'est produite pendant la mises à jour des permissions"
+            );
           }
         })
         .catch((error) => {
diff --git a/vue.config.js b/vue.config.js
index a66931e600da40984b9b93e32439cd76f65a3548..6081a6e63b9827e2f5e87b50f47d4ab606aa0944 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -3,7 +3,7 @@ const fs = require('fs')
 const packageJson = fs.readFileSync('./package.json')
 const version = JSON.parse(packageJson).version || 0
 module.exports = {
-    publicPath: '/',
+    publicPath: '/geocontrib',
     devServer: {
         proxy: {
             '^/api': {
@@ -35,4 +35,4 @@ module.exports = {
         ]
     },
 // the rest of your original module.exports code goes here
-}
\ No newline at end of file
+}