diff --git a/package.json b/package.json
index 9c54f9e0edfd6ca848a1c84a8bcde9746b2e0b28..3aab9d9ac4a43cd270fd2e234771ef76aede3a57 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "geocontrib-frontend",
-  "version": "2.3.1",
+  "version": "2.3.2-rc1",
   "private": true,
   "scripts": {
     "serve": "npm run init-proxy & npm run init-serve",
diff --git a/src/App.vue b/src/App.vue
index b356b5c01613825879292d3e248f95963068fff8..349930d3af73b77c5b6e5aea03345776cb02a346 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -151,6 +151,7 @@
     </header>
     <main>
       <div id="content" class="ui stackable grid centered container">
+        <transition name="fadeDownUp">
         <div v-if="messages && messages.length > 0" class="row">
           <div class="fourteen wide column">
             <div
@@ -169,6 +170,7 @@
             </div>
           </div>
         </div>
+        </transition>
         <div :class="{ active: loader.isLoading }" class="ui inverted dimmer">
           <div class="ui text loader">
             {{ loader.message }}
@@ -373,5 +375,54 @@ footer {
   box-shadow: none !important;
   transition: none !important;
 }
+
+
+.bounce-enter-active {
+  animation: bounce-in .5s;
+}
+.bounce-leave-active {
+  animation: bounce-in .5s reverse;
+}
+@keyframes bounce-in {
+  0% {
+    transform: scale(0);
+  }
+  50% {
+    transform: scale(1.5);
+  }
+  100% {
+    transform: scale(1);
+  }
+}
+
+.fadeDownUp-enter-active {
+  animation: fadeInDown .5s;
+}
+.fadeDownUp-leave-active {
+  animation: fadeOutUp .5s;
+}
+@keyframes fadeOutUp {
+  0% {
+    opacity: 1;
+  }
+
+  100% {
+    opacity: 0;
+    transform: translate3d(0, -100%, 0);
+  }
+}
+
+@keyframes fadeInDown {
+  from {
+    opacity: 0;
+    transform: translate3d(0, -100%, 0);
+  }
+
+  to {
+    opacity: 1;
+    transform: translate3d(0, 0, 0);
+  }
+}
+
 </style>
  
\ No newline at end of file
diff --git a/src/assets/js/map-util.js b/src/assets/js/map-util.js
index 007e92077be4966ccd56b99d188e39ba24dc9faa..10535ded16b11cdcfff46c84a9313c101265f0d2 100644
--- a/src/assets/js/map-util.js
+++ b/src/assets/js/map-util.js
@@ -49,7 +49,7 @@ L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({
 
   getFeatureInfo: function (evt) {
     if (this.wmsParams.basemapId != undefined) {
-      const queryableLayerSelected = document.getElementById(`queryable-layers-selector-${this.wmsParams.basemapId}`).getElementsByClassName('selected')[0].innerHTML;
+      const queryableLayerSelected = document.getElementById(`queryable-layers-selector-${this.wmsParams.basemapId}`).getElementsByClassName('selected')[0].textContent;
       if (queryableLayerSelected.trim() === this.wmsParams.title.trim()) {
         // Make an AJAX request to the server and hope for the best
         var params = this.getFeatureInfoUrl(evt.latlng);
@@ -181,7 +181,7 @@ const mapUtil = {
       ],
       !zoom ? mapDefaultViewZoom : zoom
     );
-
+    map.setMaxBounds(  [[-90,-180],   [90,180]]  )
     if (zoomControl) {
       L.control
         .zoom({
@@ -226,6 +226,7 @@ const mapUtil = {
       layers.forEach((layer) => {
         if (layer) {
           const options = layer.options;
+          options.noWrap=true;
           if (options) {
             options.opacity = layer.opacity;
 
@@ -250,6 +251,7 @@ const mapUtil = {
         }
       });
     } else {
+      optionsMap.noWrap=true;
       L.tileLayer(serviceMap, optionsMap).addTo(map);
     }
   },
@@ -307,6 +309,7 @@ const mapUtil = {
 
   addVectorTileLayer: function (url, project_slug, featureTypes, form_filters) {
     layerMVT = L.vectorGrid.protobuf(url, {
+      noWrap:true,
       vectorTileLayerStyles: {
         "default": (properties) => {
           const featureType = featureTypes.find((x) => x.slug.split('-')[0] === '' + properties.feature_type_id);
diff --git a/src/components/ImportTask.vue b/src/components/ImportTask.vue
index 693df72ea8f4d5af0308f219582ccd5022f4bcf4..8c15409397ac5d0d13a97a7f65e8633ab81fbc6c 100644
--- a/src/components/ImportTask.vue
+++ b/src/components/ImportTask.vue
@@ -103,7 +103,8 @@ export default {
         feature_type: this.$route.params.feature_type_slug
       });
       this.$store.dispatch('feature/GET_PROJECT_FEATURES', {
-        project_slug:  this.$route.params.slug
+        project_slug:  this.$route.params.slug,
+        feature_type__slug:  this.$route.params.feature_type_slug
       })
       //* show that the action was triggered, could be improved with animation (doesn't work)
       this.ready = false;
diff --git a/src/components/feature/FeatureListTable.vue b/src/components/feature/FeatureListTable.vue
index dfba1b87b37cd5777d577bf6c5c9d0bc09068156..5817a4fa2ab3f7acb9aadf77cef804e68c27e0be 100644
--- a/src/components/feature/FeatureListTable.vue
+++ b/src/components/feature/FeatureListTable.vue
@@ -10,33 +10,33 @@
             Statut
             <i
               :class="{
-                down: isSortedAsc('statut'),
-                up: isSortedDesc('statut'),
+                down: isSortedAsc('status'),
+                up: isSortedDesc('status'),
               }"
               class="icon sort"
-              @click="changeSort('statut')"
+              @click="changeSort('status')"
             />
           </th>
           <th class="center">
             Type
             <i
               :class="{
-                down: isSortedAsc('type'),
-                up: isSortedDesc('type'),
+                down: isSortedAsc('feature_type'),
+                up: isSortedDesc('feature_type'),
               }"
               class="icon sort"
-              @click="changeSort('type')"
+              @click="changeSort('feature_type')"
             />
           </th>
           <th class="center">
             Nom
             <i
               :class="{
-                down: isSortedAsc('nom'),
-                up: isSortedDesc('nom'),
+                down: isSortedAsc('title'),
+                up: isSortedDesc('title'),
               }"
               class="icon sort"
-              @click="changeSort('nom')"
+              @click="changeSort('title')"
             />
           </th>
           <th class="center">
@@ -75,7 +75,7 @@
         </tr>
       </thead>
       <tbody>
-        <tr v-for="(feature, index) in sortedFeatures" :key="index">
+        <tr v-for="(feature, index) in paginatedFeatures" :key="index">
           <td class="center">
             <div
               class="ui checkbox"
@@ -153,7 +153,6 @@
             >
           </td>
           <td class="center">
-            <!-- |date:'Ymd' -->
             {{ feature.properties.updated_on }}
           </td>
           <td class="center" v-if="user">
@@ -187,7 +186,7 @@
       class="dataTables_paginate paging_simple_numbers"
     >
       <a
-        @click="$emit('update:page', 'previous');"
+        @click="$emit('update:page', 'previous')"
         id="table-features_previous"
         :class="[
           'paginate_button previous',
@@ -199,8 +198,20 @@
         >Précédent</a
       >
       <span>
+        <span v-if="pagination.currentPage >= 5">
+          <a
+            key="page1"
+            @click="$emit('update:page', 1)"
+            class="paginate_button"
+            aria-controls="table-features"
+            data-dt-idx="1"
+            tabindex="0"
+            >{{ 1 }}</a
+          >
+          <span class="ellipsis">…</span>
+        </span>
         <a
-          v-for="pageNumber in pageNumbers"
+          v-for="pageNumber in displayedPageNumbers"
           :key="'page' + pageNumber"
           @click="$emit('update:page', pageNumber)"
           :class="[
@@ -212,8 +223,19 @@
           tabindex="0"
           >{{ pageNumber }}</a
         >
+        <span v-if="(lastPageNumber - pagination.currentPage) >= 4">
+          <span class="ellipsis">…</span>
+          <a
+            :key="'page' + lastPageNumber"
+            @click="$emit('update:page', lastPageNumber)"
+            class="paginate_button"
+            aria-controls="table-features"
+            data-dt-idx="1"
+            tabindex="0"
+            >{{ lastPageNumber }}</a
+          >
+        </span>
       </span>
-
       <a
         id="table-features_next"
         :class="[
@@ -223,7 +245,7 @@
         aria-controls="table-features"
         data-dt-idx="7"
         tabindex="0"
-        @click="$emit('update:page', 'next');"
+        @click="$emit('update:page', 'next')"
         >Suivant</a
       >
     </div>
@@ -241,18 +263,9 @@ export default {
     "checkedFeatures",
     "featuresCount",
     "pagination",
-    "pageNumbers",
+    "sort",
   ],
 
-  data() {
-    return {
-      sort: {
-        column: "",
-        ascending: true,
-      },
-    };
-  },
-
   computed: {
     ...mapState(["user"]),
     ...mapGetters(["project", "permissions"]),
@@ -261,50 +274,6 @@ export default {
       return this.permissions.is_project_administrator;
     },
 
-    sortedFeatures() {
-      let sortedFeatures = [...this.paginatedFeatures];
-      // Ajout du tri
-      if (this.sort.column !== "") {
-        sortedFeatures = sortedFeatures.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 sortedFeatures;
-    },
-
     checked: {
       get() {
         return this.checkedFeatures;
@@ -319,6 +288,36 @@ export default {
         ? this.featuresCount
         : this.pagination.end;
     },
+
+    pageNumbers() {
+      const totalPages = Math.ceil(
+        this.featuresCount / this.pagination.pagesize
+      );
+      return [...Array(totalPages).keys()].map((pageNumb) => {
+        ++pageNumb;
+        return pageNumb;
+      });
+    },
+
+    lastPageNumber() {
+      return this.pageNumbers.slice(-1)[0];
+    },
+
+    displayedPageNumbers() {
+      //* si la page courante est inférieur à 5, la liste commence à l'index 0 et on retourne 5 pages
+      let firstPageInList = 0;
+      let pagesQuantity = 5;
+        //* à partir de la 5ième page et jusqu'à la 4ième page avant la fin : n'afficher que 3 page entre les ellipses et la page courante doit être au milieu
+      if (this.pagination.currentPage >= 5 && !(this.lastPageNumber - this.pagination.currentPage < 4)) {
+        firstPageInList = this.pagination.currentPage - 2;
+        pagesQuantity = 3
+      }
+      //* a partir de 4 résultat avant la fin afficher seulement les 5 derniers résultats
+      if (this.lastPageNumber - this.pagination.currentPage < 4) {
+        firstPageInList = this.lastPageNumber - 5;
+      }
+      return this.pageNumbers.slice(firstPageInList, firstPageInList + pagesQuantity);
+    },
   },
 
   methods: {
@@ -342,10 +341,14 @@ export default {
     changeSort(column) {
       if (this.sort.column === column) {
         //changer order
-        this.sort.ascending = !this.sort.ascending;
+        this.$emit("update:sort", {
+          column: this.sort.column,
+          ascending: !this.sort.ascending,
+        });
       } else {
         this.sort.column = column;
         this.sort.ascending = true;
+        this.$emit("update:sort", { column, ascending: true });
       }
     },
   },
@@ -433,6 +436,10 @@ export default {
   box-shadow: none;
 }
 
+.dataTables_wrapper .dataTables_paginate .ellipsis {
+  padding: 0 1em;
+}
+
 i.icon.sort:not(.down):not(.up) {
   color: rgb(220, 220, 220);
 }
diff --git a/src/components/feature_type/SymbologySelector.vue b/src/components/feature_type/SymbologySelector.vue
index 73d5ab4279e35eef88cc55a5d8c209f53da08460..7e730930a5b193d63e737131ce32735c825e764c 100644
--- a/src/components/feature_type/SymbologySelector.vue
+++ b/src/components/feature_type/SymbologySelector.vue
@@ -114,7 +114,7 @@ export default {
 
   created() {
     this.form.color.value = this.initColor;
-    this.form.icon = this.initIcon;
+    if (this.initIcon) this.form.icon = this.initIcon;
     this.$emit('set', {
       name: this.title,
       value: this.form
diff --git a/src/store/modules/feature.js b/src/store/modules/feature.js
index 30735fef4da3ee7b0715a189b148848cbe7284aa..427e005e781c9649968f5d0f1c4959e8775ae8ee 100644
--- a/src/store/modules/feature.js
+++ b/src/store/modules/feature.js
@@ -1,12 +1,6 @@
 import axios from '@/axios-client.js';
 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;
-// })('csrftoken');
-
 
 const feature = {
   namespaced: true,
@@ -98,7 +92,6 @@ const feature = {
     },
 
     ADD_ATTACHMENT_TO_DELETE(state, attachementId) {
-      // state.attachmentFormset = state.attachmentFormset.filter(el => el.id !== attachementId);
       state.attachmentsToDelete.push(attachementId);
     },
 
@@ -144,7 +137,6 @@ const feature = {
             commit("SET_FEATURES", features);
             const features_count = response.data.count;
             commit("SET_FEATURES_COUNT", features_count);
-            //dispatch("map/ADD_FEATURES", null, { root: true }); //todo: should check if map was initiated
           }
           return response;
         })
@@ -180,7 +172,6 @@ const feature = {
     SEND_FEATURE({ state, rootState, commit, dispatch }, routeName) {
       commit("DISPLAY_LOADER", "Le signalement est en cours de création", { root: true })
       const message = routeName === "editer-signalement" ? "Le signalement a été mis à jour" : "Le signalement a été crée";
-
       function redirect(featureId) {
         dispatch(
           'GET_PROJECT_FEATURE',
@@ -228,110 +219,66 @@ const feature = {
         }
       }
 
+      let url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/`
       if (routeName === "editer-signalement") {
-        return axios
-          .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) {
-                handleOtherForms(response.data.id)
-              } else {
-                redirect(response.data.id)
-              }
-            }
-          })
-          .catch((error) => {
-            commit("DISCARD_LOADER", null, { root: true })
-            if (error.message === "Network Error" || window.navigator.onLine === false) {
-              let arraysOffline = [];
-              let localStorageArray = localStorage.getItem("geocontrib_offline");
-              if (localStorageArray) {
-                arraysOffline = JSON.parse(localStorageArray);
-              }
-              let updateMsg = {
-                project: rootState.project_slug,
-                type: 'put',
-                featureId: state.form.feature_id,
-                geojson: geojson
-              };
-              arraysOffline.push(updateMsg);
-              localStorage.setItem("geocontrib_offline", JSON.stringify(arraysOffline));
-              router.push({
-                name: "offline-signalement",
-                params: {
-                  slug_type_signal: rootState.feature_type.current_feature_type_slug
-                },
-              });
+        url += `${state.form.feature_id}/?` +
+        `feature_type__slug=${rootState.feature_type.current_feature_type_slug}` +
+        `&project__slug=${rootState.project_slug}`
+      }
 
+      return axios({
+        url,
+        method: routeName === "editer-signalement" ? "PUT" : "POST",
+        data: geojson
+      }).then((response) => {
+          if ((response.status === 200 || response.status === 201) && response.data) {
+            if (state.attachmentFormset.length > 0 || state.linkedFormset.length > 0 || state.attachmentFormset.length > 0 || state.attachmentsToDelete.length > 0) {
+              handleOtherForms(response.data.id)
+            } else {
+              redirect(response.data.id)
             }
-            else {
-              console.log(error)
-              throw error;
+          }
+        })
+        .catch((error) => {
+          commit("DISCARD_LOADER", null, { root: true })
+          if (error.message === "Network Error" || window.navigator.onLine === false) {
+            let arraysOffline = [];
+            let localStorageArray = localStorage.getItem("geocontrib_offline");
+            if (localStorageArray) {
+              arraysOffline = JSON.parse(localStorageArray);
             }
-
+            let updateMsg = {
+              project: rootState.project_slug,
+              type: 'put',
+              featureId: state.form.feature_id,
+              geojson: geojson
+            };
+            arraysOffline.push(updateMsg);
+            localStorage.setItem("geocontrib_offline", JSON.stringify(arraysOffline));
+            router.push({
+              name: "offline-signalement",
+              params: {
+                slug_type_signal: rootState.feature_type.current_feature_type_slug
+              },
+            });
+          }
+          else {
+            console.error(error)
             throw error;
-          });
-      } else {
-        return axios
-          .post(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/`, geojson)
-          .then((response) => {
-            if (response.status === 201 && response.data) {
-              if (state.attachmentFormset.length > 0 || state.linkedFormset.length > 0) {
-                handleOtherForms(response.data.id)
-              } else {
-                redirect(response.data.id)
-              }
-            }
-          })
-          .catch((error) => {
-            commit("DISCARD_LOADER", null, { root: true })
-            if (error.message === "Network Error" || window.navigator.onLine === false) {
-              let arraysOffline = [];
-              let localStorageArray = localStorage.getItem("geocontrib_offline");
-              if (localStorageArray) {
-                arraysOffline = JSON.parse(localStorageArray);
-              }
-              let updateMsg = {
-                project: rootState.project_slug,
-                type: 'post',
-                geojson: geojson
-              };
-              arraysOffline.push(updateMsg);
-              localStorage.setItem("geocontrib_offline", JSON.stringify(arraysOffline));
-              router.push({
-                name: "offline-signalement",
-                params: {
-                  slug_type_signal: rootState.feature_type.current_feature_type_slug
-                },
-              });
-
-            }
-            else {
-              console.log(error)
-              throw error;
-            }
-
-          });
-      }
-      // this.$store.dispatch("GET_ALL_PROJECTS"), //* & refresh project list
+          }
+          throw error;
+        });
     },
 
     async SEND_ATTACHMENTS({ state, rootState, dispatch }, featureId) {
       const DJANGO_API_BASE = rootState.configuration.VUE_APP_DJANGO_API_BASE;
-
+      
       function addFile(attachment, attchmtId) {
         let formdata = new FormData();
         formdata.append("file", attachment.fileToImport, attachment.fileToImport.name);
         return axios
           .put(`${DJANGO_API_BASE}features/${featureId}/attachments/${attchmtId}/upload-file/`, formdata)
           .then((response) => {
-            console.log(response)
-            if (response && response.status === 200) {
-              console.log(response.status)
-            }
             return response;
           })
           .catch((error) => {
@@ -345,37 +292,25 @@ const feature = {
         formdata.append("title", attachment.title);
         formdata.append("info", attachment.info);
 
-        if (!attachment.id) { //* used to check if doesn't exist in DB and should be send through post (useless now)
-          return axios
-            .post(`${DJANGO_API_BASE}features/${featureId}/attachments/`, formdata)
-            .then((response) => {
-              console.log(response)
-              if (response && response.status === 201 && attachment.fileToImport) {
-                console.log(response.status)
-                return addFile(attachment, response.data.id);
-              }
-              return response
-            })
-            .catch((error) => {
-              console.error(error);
-              return error
-            });
-        } else {
-          return axios
-            .put(`${DJANGO_API_BASE}features/${featureId}/attachments/${attachment.id}/`, formdata)
-            .then((response) => {
-              console.log(response)
-              if (response && response.status === 200 && attachment.fileToImport) {
-                console.log(response.status)
-                return addFile(attachment, response.data.id);
-              }
-            })
-            .catch((error) => {
-              console.error(error);
-              return error
-            });
-
+        let url = `${DJANGO_API_BASE}features/${featureId}/attachments/`
+        if (attachment.id) {
+          url += `${attachment.id}/`
         }
+
+        return axios({
+          url,
+          method: attachment.id ? "PUT" : "POST",
+          data: formdata
+        }).then((response) => {
+          if (response && (response.status === 200 || response.status === 201) && attachment.fileToImport) {
+            return addFile(attachment, response.data.id);
+          }
+          return response
+        })
+        .catch((error) => {
+          console.error(error);
+          return error
+        });
       }
 
       function deleteAttachement(attachmentsId, featureId) {
@@ -386,6 +321,7 @@ const feature = {
         return dispatch("DELETE_ATTACHMENTS", payload)
           .then((response) => response);
       }
+
       const promisesResult = await Promise.all([
         ...state.attachmentFormset.map((attachment) => putOrPostAttachement(attachment)),
         ...state.attachmentsToDelete.map((attachmentsId) => deleteAttachement(attachmentsId, featureId))
diff --git a/src/store/modules/feature_type.js b/src/store/modules/feature_type.js
index bfe624e5e33a984ec4457f05345015daa2d8538d..5ad047abd1203dfde68efcb5bd30c3d692334c3b 100644
--- a/src/store/modules/feature_type.js
+++ b/src/store/modules/feature_type.js
@@ -1,16 +1,20 @@
 import axios from '@/axios-client.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;
-// })('csrftoken');
-
 const getColorsStyles = (customForms) => customForms.filter(customForm => customForm.options && customForm.options.length).map(el => {
   //* in dropdown, value is the name and name is the label to be displayed, could be changed...
   return { value: el.name, name: el.label, options: el.options }
 });
 
+const pending2draftFeatures = (features) => {
+  let result = []
+  for (let el of features) {
+    if (el.properties.status === "pending") {
+      el.properties.status = "draft"
+    }
+    result.push(el)
+  }
+  return result;
+}
 
 const feature_type = {
   namespaced: true,
@@ -159,12 +163,25 @@ const feature_type = {
         });
     },
 
-    SEND_FEATURES_FROM_GEOJSON({ state, dispatch }, payload) {
+    async SEND_FEATURES_FROM_GEOJSON({ state, dispatch, rootGetters }, payload) {
       const { feature_type_slug } = payload;
 
       if (state.fileToImport.size > 0) {
         let formData = new FormData();
-        formData.append('json_file', state.fileToImport);
+
+        if (!rootGetters.project.moderation) {
+          const textFile = await state.fileToImport.text();
+          const geojson = JSON.parse(textFile);
+          const unmoderatedFeatures = pending2draftFeatures(geojson.features);
+          const newGeojson= {
+            "type": "FeatureCollection", "features": unmoderatedFeatures
+          };
+          const newFile = new File([JSON.stringify(newGeojson)], state.fileToImport.name, {type: state.fileToImport.type});
+          formData.append('json_file', newFile);
+        } else {
+          formData.append('json_file', state.fileToImport);
+        }
+
         formData.append('feature_type_slug', feature_type_slug);
         let url =
           this.state.configuration.VUE_APP_DJANGO_API_BASE +
diff --git a/src/views/Index.vue b/src/views/Index.vue
index 33ced862f9a5074b1d1184ce1d75dbb4877b4ecd..96f4a9b021ac0c93a4ba879913061afbbf09986c 100644
--- a/src/views/Index.vue
+++ b/src/views/Index.vue
@@ -62,7 +62,7 @@
               >Niveau d'autorisation requis :
               {{ project.access_level_pub_feature }}</span
             ><br />
-            <span>
+            <span v-if="user">
               Mon niveau d'autorisation :
               <span v-if="USER_LEVEL_PROJECTS && project">{{
                 USER_LEVEL_PROJECTS[project.slug]
diff --git a/src/views/feature/Feature_detail.vue b/src/views/feature/Feature_detail.vue
index 9a8b3e83a9bc8c6a63b76c810a75bf08e48ed5cb..f14f24213bdcbb6724d3199aa492664df66e6f0c 100644
--- a/src/views/feature/Feature_detail.vue
+++ b/src/views/feature/Feature_detail.vue
@@ -238,20 +238,22 @@
           </div>
 
           <div
-            v-if="permissions && permissions.can_create_feature"
+            v-if="permissions && permissions.can_create_feature && isOffline() !== true"
             class="ui segment"
           >
             <form
               id="form-comment"
               class="ui form"
-              method="POST"
-              enctype="multipart/form-data"
             >
               <div class="required field">
                 <label :for="comment_form.comment.id_for_label"
                   >Ajouter un commentaire</label
                 >
-                {{ comment_form.comment.errors }}
+                <ul v-if="comment_form.comment.errors" class="errorlist">
+                  <li>
+                    {{ comment_form.comment.errors }}
+                  </li>
+                </ul>
                 <textarea
                   v-model="comment_form.comment.value"
                   :name="comment_form.comment.html_name"
@@ -280,12 +282,12 @@
                 </div>
                 <div class="field">
                   <input
-                    v-model="comment_form.title.value"
+                    v-model="comment_form.attachment_file.title"
                     type="text"
-                    :name="comment_form.title.html_name"
-                    :id="comment_form.title.id_for_label"
+                    name="title"
+                    id="title"
                   />
-                  {{ comment_form.title.errors }}
+                  {{ comment_form.attachment_file.errors }}
                 </div>
               </div>
               <ul v-if="comment_form.attachment_file.errors" class="errorlist">
@@ -294,7 +296,6 @@
                 </li>
               </ul>
               <button
-                v-if="isOffline() !== true"
                 @click="postComment"
                 type="button"
                 class="ui compact green icon button"
@@ -351,12 +352,6 @@ import { mapUtil } from "@/assets/js/map-util.js";
 import featureAPI from "@/services/feature-api";
 import axios from '@/axios-client.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;
-// })('csrftoken');
-
 export default {
   name: "Feature_detail",
 
@@ -372,22 +367,15 @@ export default {
       comment_form: {
         attachment_file: {
           errors: null,
-          value: null,
+          title: "",
           file: null,
         },
-        title: {
-          id_for_label: "title",
-          html_name: "title",
-          errors: null,
-          value: null,
-        },
         comment: {
           id_for_label: "add-comment",
           html_name: "add-comment",
-          errors: null,
+          errors: "",
           value: null,
         },
-        non_field_errors: [],
       },
     };
   },
@@ -467,8 +455,18 @@ export default {
       this.addFeatureToMap();
     },
 
+    validateForm() {
+      this.comment_form.comment.errors = ""
+      if (!this.comment_form.comment.value) {
+        this.comment_form.comment.errors = "Le commentaire ne peut pas être vide"
+        return false;
+      }
+      return true;
+    },
+
     postComment() {
-      featureAPI
+      if (this.validateForm()) {
+        featureAPI
         .postComment({
           featureId: this.$route.params.slug_signal,
           comment: this.comment_form.comment.value,
@@ -479,9 +477,9 @@ export default {
               .postCommentAttachment({
                 featureId: this.$route.params.slug_signal,
                 file: this.comment_form.attachment_file.file,
-                fileName: this.comment_form.title.file,
+                fileName: this.comment_form.attachment_file.fileName,
+                title: this.comment_form.attachment_file.title,
                 commentId: response.data.id,
-                title: response.data.comment,
               })
               .then(() => {
                 this.confirmComment();
@@ -490,15 +488,15 @@ export default {
             this.confirmComment();
           }
         });
+      }
     },
 
     confirmComment() {
       this.$store.commit("DISPLAY_MESSAGE", "Ajout du commentaire confirmé");
       this.getFeatureEvents(); //* display new comment on the page
       this.comment_form.attachment_file.file = null;
-      this.comment_form.attachment_file.value = null;
-      this.comment_form.title.file = null;
-      this.comment_form.title.value = null;
+      this.comment_form.attachment_file.fileName = "";
+      this.comment_form.attachment_file.title = "";
       this.comment_form.comment.value = null;
     },
 
@@ -520,19 +518,19 @@ export default {
       // * read image file
       const files = e.target.files || e.dataTransfer.files;
 
-      const _this = this; //* 'this' is different in onload function
-      function handleFile(isValid) {
+      const handleFile = (isValid) => {
         if (isValid) {
-          const period = files[0].name.lastIndexOf(".");
-          const fileName = files[0].name.substring(0, period);
-          const fileExtension = files[0].name.substring(period + 1);
-          const shortName = fileName.slice(0, 10) + "[...]." + fileExtension;
-          _this.comment_form.attachment_file.file = files[0]; //* store the file to post later
-          _this.comment_form.attachment_file.value = shortName; //* for display
-          _this.comment_form.title.value = shortName;
-          _this.comment_form.attachment_file.errors = null;
+          this.comment_form.attachment_file.file = files[0]; //* store the file to post afterwards
+          let title = files[0].name
+          this.comment_form.attachment_file.fileName = title; //* name of the file
+          const fileExtension = title.substring(title.lastIndexOf(".") + 1);
+          if ((title.length - fileExtension.length) > 11) {
+            title = title.slice(0, 10) + "[...]." + fileExtension;
+          }
+          this.comment_form.attachment_file.title = title; //* title for display
+          this.comment_form.attachment_file.errors = null;
         } else {
-          _this.comment_form.attachment_file.errors =
+          this.comment_form.attachment_file.errors =
             "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu.";
         }
       }
diff --git a/src/views/feature/Feature_list.vue b/src/views/feature/Feature_list.vue
index 408091df72d8955bc4a332ddd4fef83f14128038..c31ad0879058b2a0a117cd71012851e6df2bb8c3 100644
--- a/src/views/feature/Feature_list.vue
+++ b/src/views/feature/Feature_list.vue
@@ -86,7 +86,6 @@
       <div class="field wide four column no-margin-mobile">
         <label>Type</label>
         <Dropdown
-          v-on:update:selection="updateTypeFeatures"
           :options="featureTypeChoices"
           :selected="form.type.selected"
           :selection.sync="form.type.selected"
@@ -98,7 +97,6 @@
         <label>Statut</label>
         <!--  //* giving an object mapped on key name -->
         <Dropdown
-          v-on:update:selection="updateStatusFeatures"
           :options="statusChoices"
           :selected="form.status.selected.name"
           :selection.sync="form.status.selected"
@@ -141,11 +139,12 @@
     <FeatureListTable
       v-show="!showMap"
       v-on:update:page="handlePageChange"
+      v-on:update:sort="handleSortChange"
       :paginatedFeatures="paginatedFeatures"
       :checkedFeatures.sync="checkedFeatures"
       :featuresCount="featuresCount"
       :pagination="pagination"
-      :pageNumbers="pageNumbers"
+      :sort="sort"
     />
 
     <!-- MODAL ALL DELETE FEATURE TYPE -->
@@ -201,17 +200,12 @@ export default {
 
   data() {
     return {
-      modalAllDeleteOpen: false,
       form: {
         type: {
-          selected: null,
-          choices: [],
+          selected: "",
         },
         status: {
-          selected: {
-            name: null,
-            value: null,
-          },
+          selected: "",
           choices: [
             {
               name: "Brouillon",
@@ -233,26 +227,40 @@ export default {
         },
         title: null,
       },
-      paginatedFeatures: [],
       baseUrl: this.$store.state.configuration.BASE_URL,
+      modalAllDeleteOpen: false,
       map: null,
       zoom: null,
       lat: null,
       lng: null,
       featuresCount: 0,
-      next: null,
-      previous: null,
+      paginatedFeatures: [],
       pagination: {
         currentPage: 1,
         pagesize: 15,
         start: 0,
         end: 15,
       },
+      previous: null,
+      next: null,
+      sort: {
+        column: "",
+        ascending: true,
+      },
       showMap: true,
       showAddFeature: false,
     };
   },
 
+  watch: {
+    "form.type.selected"() {
+      this.fetchPagedFeatures();
+    },
+    "form.status.selected.value"() {
+      this.fetchPagedFeatures();
+    },
+  },
+
   computed: {
     ...mapGetters(["project", "permissions"]),
     ...mapState("feature", ["checkedFeatures"]),
@@ -273,16 +281,6 @@ export default {
     featureTypeChoices() {
       return this.feature_types.map((el) => el.title);
     },
-
-    pageNumbers() {
-      const totalPages = Math.ceil(
-        this.featuresCount / this.pagination.pagesize
-      );
-      return [...Array(totalPages).keys()].map((pageNumb) => {
-        ++pageNumb;
-        return pageNumb;
-      });
-    },
   },
 
   methods: {
@@ -349,7 +347,7 @@ export default {
         mapDefaultViewZoom,
       });
 
-      this.getBbox2FIt();
+      this.fetchBboxNfit();
 
       document.addEventListener("change-layers-order", (event) => {
         // Reverse is done because the first layer in order has to be added in the map in last.
@@ -372,7 +370,7 @@ export default {
       }, 1000);
     },
 
-    getBbox2FIt(queryParams) {
+    fetchBboxNfit(queryParams) {
       featureAPI
         .getFeaturesBbox(this.project.slug, queryParams)
         .then((bbox) => {
@@ -388,86 +386,42 @@ export default {
       return featureType ? featureType.slug : null;
     },
 
-    buildFilterParams({ filterType, filterValue }) {
-      let params = "";
-      let typeFilter, statusFilter;
-      //*** feature type ***//
-      if (filterType === "featureType") {
-        if (filterValue === "" && !this.form.type.selected) {
-          //* s'il y n'avait pas de filtre et qu'il a été supprimé --> ne pas mettre à jour les features
-          return "abort";
-        } else if (filterValue !== undefined && filterValue !== null) {
-          //* s'il y a un nouveau filtre --> ajouter une params
-          typeFilter = this.getFeatureTypeSlug(filterValue);
-        } //* sinon il n'y a pas de param ajouté, ce qui supprime la query
-
-        //*** status ***//
-      } else if (filterType === "status") {
-        if (filterValue === "" && !this.form.status.selected.value) {
-          return "abort";
-        } else if (filterValue !== undefined && filterValue !== null) {
-          statusFilter = filterValue.value;
-        }
-      }
-
-      //* after possibilities of aborting features fetch, empty geojson to make sure even no result would update
-
-      if (
-        (filterType === undefined || filterType === "status") &&
-        this.form.type.selected
-      ) {
-        //* s'il y a déjà un filtre sélectionné, maintenir le params
-        typeFilter = this.getFeatureTypeSlug(this.form.type.selected);
-      }
-      if (
-        (filterType === undefined || filterType === "featureType") &&
-        this.form.status.selected.value
-      ) {
-        statusFilter = this.form.status.selected.value;
-      }
-
-      if (typeFilter) {
-        let typeParams = `&feature_type_slug=${typeFilter}`;
-        params += typeParams;
-      }
-      if (statusFilter) {
-        let statusParams = `&status__value=${statusFilter}`;
-        params += statusParams;
-      }
-
-      //*** title ***//
-      if (this.form.title) {
-        params += `&title=${this.form.title}`;
+    getAvalaibleField(orderField) {
+      let result = orderField;
+      if (orderField === "display_creator") {
+        result = "creator";
+      } else if (orderField === "display_last_editor") {
+        result = "last_editor";
       }
-      this.getBbox2FIt(params);
-      return params;
-    },
-
-    updateTypeFeatures(filterValue) {
-      //* only update:selection custom event can trigger the filter update,
-      //* but it happens before the value is updated, thus using selected value from event to update query
-      this.fetchPagedFeatures({ filterType: "featureType", filterValue });
+      return result;
     },
 
-    updateStatusFeatures(filterValue) {
-      this.fetchPagedFeatures({ filterType: "status", filterValue });
+    buildQueryString() {
+      let urlParams = "";
+      let typeFilter = this.getFeatureTypeSlug(this.form.type.selected);
+      let statusFilter = this.form.status.selected.value;
+
+      if (typeFilter) urlParams += `&feature_type_slug=${typeFilter}`;
+      if (statusFilter) urlParams += `&status__value=${statusFilter}`;
+      if (this.form.title) urlParams += `&title=${this.form.title}`;
+      if (this.sort.column) {
+        urlParams += `&ordering=${
+          this.sort.ascending ? "-" : ""
+        }${this.getAvalaibleField(this.sort.column)}`;
+      }
+      return urlParams;
     },
 
-    fetchPagedFeatures(params) {
-      this.onFilterChange(); //* temporary, use paginated event to watch change in filters, to modify geojson on map
+    fetchPagedFeatures(newUrl) {
+      this.onFilterChange(); //* use paginated event to watch change in filters and modify features on map
       let url = `${this.API_BASE_URL}projects/${this.$route.params.slug}/feature-paginated/?output=geojson&limit=${this.pagination.pagesize}&offset=${this.pagination.start}`;
-
-      if (params) {
-        if (typeof params === "object") {
-          const filterParams = this.buildFilterParams(params);
-          if (filterParams === "abort") return;
-          url += filterParams;
-        } else {
-          //console.error("ONLY FOR DEV !!!!!!!!!!!!!");
-          //params = params.replace("8000", "8010"); //* for dev uncomment to use proxy link
-          url = params;
-        }
+      if (newUrl && typeof newUrl === "string") {
+        //* if receiving next & previous url
+        //newUrl = newUrl.replace("8000", "8010"); //* for dev uncomment to use proxy link
+        url = newUrl;
       }
+      const queryString = this.buildQueryString();
+      url += queryString;
 
       this.$store.commit(
         "DISPLAY_LOADER",
@@ -480,6 +434,8 @@ export default {
           this.next = data.next;
           this.paginatedFeatures = data.results.features;
         }
+        //* bbox needs to be updated with the same filters
+        if (queryString) this.fetchBboxNfit(queryString);
         this.$store.commit("DISCARD_LOADER");
       });
     },
@@ -497,6 +453,14 @@ export default {
       }
     },
 
+    handleSortChange(sort) {
+      this.sort = sort;
+      this.fetchPagedFeatures({
+        filterType: undefined,
+        filterValue: undefined,
+      });
+    },
+
     toPage(pageNumber) {
       const toAddOrRemove =
         (pageNumber - this.pagination.currentPage) * this.pagination.pagesize;
diff --git a/src/views/feature_type/Feature_type_detail.vue b/src/views/feature_type/Feature_type_detail.vue
index 7e74e6aa5c6e973acefe9d8666909a179f229c36..07dbfc9c86a745b70a2737aefed54f505a5c0641 100644
--- a/src/views/feature_type/Feature_type_detail.vue
+++ b/src/views/feature_type/Feature_type_detail.vue
@@ -298,7 +298,7 @@ export default {
       this.showImport = !this.showImport;
       if (this.showImport) {
         this.$store.dispatch("feature_type/GET_IMPORTS", {
-          feature_type: this.structure.slug
+          feature_type: this.$route.params.feature_type_slug
         });
       }
     },
@@ -411,7 +411,7 @@ export default {
       const response = await 
         this.$store.dispatch('feature/GET_PROJECT_FEATURES', {
           project_slug:  this.$route.params.slug,
-          feature_type__slug : this.structure.slug,
+          feature_type__slug : this.$route.params.feature_type_slug,
           ordering: '-created_on',
           limit: '5'
         })
@@ -425,7 +425,7 @@ export default {
     'structure'(newValue){
       if (newValue.slug){
         this.$store.dispatch("feature_type/GET_IMPORTS", {
-          feature_type: this.structure.slug
+          feature_type: this.$route.params.feature_type_slug
         });
       }
     }
diff --git a/src/views/feature_type/Feature_type_symbology.vue b/src/views/feature_type/Feature_type_symbology.vue
index f1d82fe21f8c5f0471e52bc4b8dd09090414b227..67d0c7efa1956e69ced868544e0cc3702da6071f 100644
--- a/src/views/feature_type/Feature_type_symbology.vue
+++ b/src/views/feature_type/Feature_type_symbology.vue
@@ -23,6 +23,7 @@
         <SymbologySelector
           v-if="feature_type"
           :initColor="feature_type.color"
+          :initIcon="feature_type.icon"
           :geomType="feature_type.geom_type"
           @set="setDefaultStyle"
         />
@@ -60,7 +61,9 @@
           >
             <SymbologySelector
               :title="option"
-              :initColor="feature_type.colors_style.value.colors[option].value"
+              :initColor="feature_type.colors_style.value.colors[option] ?
+                feature_type.colors_style.value.colors[option].value :
+                feature_type.colors_style.value.colors[option]"
               :initIcon="feature_type.colors_style.value.icons[option]"
               :geomType="feature_type.customfield_set.geomType"
               @set="setColorsStyle"
diff --git a/src/views/project/Project_detail.vue b/src/views/project/Project_detail.vue
index ad24ad07663e5f0b2635d1e019ddabf63c7c8236..0b65164ca8331b3de91a551c4cba825a69f955ea 100644
--- a/src/views/project/Project_detail.vue
+++ b/src/views/project/Project_detail.vue
@@ -108,7 +108,7 @@
           <h3 class="ui header">Types de signalements</h3>
           <div class="ui middle aligned divided list">
             <div
-              :class="{ active: featureTypeLoading }"
+              :class="{ active : projectInfoLoading }"
               class="ui inverted dimmer"
             >
               <div class="ui text loader">
@@ -221,15 +221,15 @@
                   <div v-else v-frag>
                   <router-link
                     :to="{
-                      name: 'editer-type-signalement',
+                      name: 'editer-symbologie-signalement',
                       params: { slug_type_signal: type.slug },
                     }"
                     v-if="
                       project &&
-                      type.is_editable &&
+                      type.geom_type === 'point' &&
                       permissions &&
                       permissions.can_create_feature_type &&
-                      isOffline() !== true
+                      isOffline() != true
                     "
                     class="
                       ui
@@ -240,24 +240,23 @@
                       floated
                       button button-hover-green
                     "
-                    data-tooltip="Éditer le type de signalement"
+                    data-tooltip="Éditer la symbologie du type de signalement"
                     data-position="left center"
                     data-variation="mini"
                   >
-                    <i class="inverted grey pencil alternate icon"></i>
+                    <i class="inverted grey paint brush alternate icon"></i>
                   </router-link>
                   <router-link
                     :to="{
-                      name: 'editer-symbologie-signalement',
+                      name: 'editer-type-signalement',
                       params: { slug_type_signal: type.slug },
                     }"
                     v-if="
                       project &&
                       type.is_editable &&
-                      type.geom_type === 'point' &&
                       permissions &&
                       permissions.can_create_feature_type &&
-                      isOffline() != true
+                      isOffline() !== true
                     "
                     class="
                       ui
@@ -268,11 +267,11 @@
                       floated
                       button button-hover-green
                     "
-                    data-tooltip="Éditer la symbologie du type de signalement"
+                    data-tooltip="Éditer le type de signalement"
                     data-position="left center"
                     data-variation="mini"
                   >
-                    <i class="inverted grey paint brush alternate icon"></i>
+                    <i class="inverted grey pencil alternate icon"></i>
                   </router-link>
                   </div>
                 </div>
@@ -407,6 +406,14 @@
               <div class="content">
                 <div class="center aligned header">Derniers commentaires</div>
                 <div class="center aligned description">
+                  <div
+                    :class="{ active: projectInfoLoading }"
+                    class="ui inverted dimmer"
+                  >
+                    <div class="ui text loader">
+                      Récupération des commentaires en cours...
+                    </div>
+                  </div>
                   <div class="ui relaxed list">
                     <div
                       v-for="(item, index) in last_comments"
@@ -627,7 +634,7 @@ export default {
       isModalOpen: false,
       is_suscriber: false,
       tempMessage: null,
-      featureTypeLoading: true,
+      projectInfoLoading: true,
       featureTypeImporting: false,
       featuresLoading: true,
       isFileSizeModalOpen: false
@@ -912,11 +919,11 @@ export default {
   mounted() {
     this.GET_PROJECT_INFO(this.slug)
       .then(() => {
-        this.featureTypeLoading = false;
+        this.projectInfoLoading = false;
         setTimeout(this.initMap, 1000);
       })
       .catch(() => {
-        this.featureTypeLoading = false;
+        this.projectInfoLoading = false;
       });
     this.GET_PROJECT_FEATURES({
       project_slug: this.slug,
@@ -938,6 +945,10 @@ export default {
       setTimeout(() => (this.tempMessage = null), 5000); //* hide message after 5 seconds
     }
   },
+
+  destroyed() {
+     this.CLEAR_RELOAD_INTERVAL_ID();
+  },
 };
 </script>
 
diff --git a/src/views/registration/Login.vue b/src/views/registration/Login.vue
index 0f62078398f1fe07998e86e91d73c4c2601cc8cf..f26884b1113a2d6bb9728c6e84c49ed48826a2b5 100644
--- a/src/views/registration/Login.vue
+++ b/src/views/registration/Login.vue
@@ -2,10 +2,7 @@
   <div>
     <div class="row">
       <div class="fourteen wide column">
-        <img
-          class="ui centered small image"
-          :src="logo"
-        />
+        <img class="ui centered small image" :src="logo" />
         <h2 class="ui center aligned icon header">
           <div class="content">
             {{ APPLICATION_NAME }}
@@ -60,7 +57,6 @@
 </template>
 
 <script>
-
 export default {
   name: "Login",
   data() {
@@ -101,5 +97,15 @@ export default {
         .catch();
     },
   },
+
+  mounted() {
+    if (this.$store.state.user) {
+      this.$store.commit(
+        "DISPLAY_MESSAGE",
+        "Vous êtes déjà connecté, vous allez être redirigé vers la page d'accueil."
+      );
+      setTimeout(() => this.$router.push("/"), 3100);
+    }
+  },
 };
 </script>
\ No newline at end of file