From 82bdb4961b4e9a58823c7b5dad2db8b00a16c681 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Poussard?= <tpoussard@neogeo.fr>
Date: Fri, 3 Sep 2021 15:41:52 +0200
Subject: [PATCH] Add import of feature_type from geoJSON

---
 src/components/Dropdown.vue                   |  8 +-
 .../feature_type/FeatureTypeCustomForm.vue    | 85 +++++++++-------
 src/store/modules/feature_type.js             | 27 +++---
 .../feature_type/Feature_type_detail.vue      | 52 +---------
 src/views/feature_type/Feature_type_edit.vue  | 97 +++++++++----------
 src/views/project/Project_detail.vue          |  7 +-
 6 files changed, 124 insertions(+), 152 deletions(-)

diff --git a/src/components/Dropdown.vue b/src/components/Dropdown.vue
index bcd926b4..4e94c073 100644
--- a/src/components/Dropdown.vue
+++ b/src/components/Dropdown.vue
@@ -103,4 +103,10 @@ export default {
     window.removeEventListener("mousedown", this.clickOutsideDropdown);
   },
 };
-</script>
\ No newline at end of file
+</script>
+
+<style scoped>
+.ui.selection.dropdown .menu > .item {
+  white-space: nowrap;
+}
+</style>
\ No newline at end of file
diff --git a/src/components/feature_type/FeatureTypeCustomForm.vue b/src/components/feature_type/FeatureTypeCustomForm.vue
index 693af72e..1e3b18a8 100644
--- a/src/components/feature_type/FeatureTypeCustomForm.vue
+++ b/src/components/feature_type/FeatureTypeCustomForm.vue
@@ -66,7 +66,7 @@
           }}</label>
           <Dropdown
             :disabled="!form.label.value || !form.name.value"
-            :options="form.field_type.field.choices"
+            :options="fieldTypeChoices"
             :selected="selected"
             :selection.sync="selected"
           />
@@ -81,9 +81,8 @@
             :maxlength="form.options.field.max_length"
             :name="form.options.html_name"
             :id="form.options.id_for_label"
-            v-model="form.options.value"
+            v-model="arrayOption"
             class="options-field"
-            @input="updateOptions"
           />
           <small>{{ form.help_text }}</small>
           {{ form.options.errors }}
@@ -108,22 +107,17 @@ export default {
 
   props: ["customForm"],
 
-  computed: {
-    selected: {
-      // getter
-      get() {
-        return this.form.field_type.value;
-      },
-      // setter
-      set(newValue) {
-        this.form.field_type.value = newValue;
-        this.updateStore();
-      },
-    },
-  },
-
   data() {
     return {
+      fieldTypeChoices: [
+        { name: "Booléen", value: "boolean" },
+        { name: "Chaîne de caractères", value: "char" },
+        { name: "Date", value: "date" },
+        { name: "Liste de valeurs", value: "list" },
+        { name: "Nombre entier", value: "integer" },
+        { name: "Nombre décimal", value: "decimal" },
+        { name: "Texte multiligne", value: "text" },
+      ],
       form: {
         label: {
           errors: null,
@@ -169,17 +163,8 @@ export default {
           help_text: "",
           field: {
             max_length: 50,
-            choices: [
-              "Booléen",
-              "Chaîne de caractères",
-              "Date",
-              "Liste de valeurs",
-              "Nombre entier",
-              "Nombre décimal",
-              "Texte multiligne",
-            ],
           },
-          value: null,
+          value: null, //* field to send to the backend
         },
         options: {
           errors: null,
@@ -190,11 +175,43 @@ export default {
           field: {
             max_length: 256,
           },
-          value: null,
+          value: [],
         },
       },
     };
   },
+
+  computed: {
+    selected: {
+      // getter
+      get() {
+        const currentFieldType = this.fieldTypeChoices.find(
+          (el) => el.value === this.form.field_type.value
+        );
+        if (currentFieldType) {
+          return currentFieldType.name;
+        }
+        return null;
+      },
+      // setter
+      set(newValue) {
+        this.form.field_type.value = newValue.value;
+        this.form = { ...this.form }; // ! quick & dirty fix for getter not updating because of Vue caveat https://vuejs.org/v2/guide/reactivity.html#For-Objects
+        // Vue.set(this.form.field_type, "value", newValue.value); // ? vue.set didn't work, maybe should flatten form ?
+        this.updateStore();
+      },
+    },
+    arrayOption: { // * because backend expects an array
+      get() {
+        return [this.form.options.value]
+      },
+      set(newValue) {
+        this.form.options.value = [newValue];
+        this.updateOptions()
+      }
+    }
+  },
+
   methods: {
     removeCustomForm() {
       this.$store.commit(
@@ -217,14 +234,16 @@ export default {
       this.$store.commit("feature_type/UPDATE_COLOR_STYLE");
     },
   },
-  // beforeDestroy(){
-  //   this.$store.commit("feature_type/EMPTY_CUSTOM_FORM");
-    
-  // },
+   beforeDestroy(){
+     this.$store.commit("feature_type/EMPTY_CUSTOM_FORMS");
+  },
   mounted() {
     for (let el in this.customForm) {
-      if (el && this.form[el]) this.form[el].value = this.customForm[el].value;
+      if (el && this.form[el] && this.customForm[el]) {
+        this.form[el].value = this.customForm[el].value;
+      }
     }
+    this.updateStore();
   },
 };
 </script>
diff --git a/src/store/modules/feature_type.js b/src/store/modules/feature_type.js
index e4fdf2cb..954199b3 100644
--- a/src/store/modules/feature_type.js
+++ b/src/store/modules/feature_type.js
@@ -4,7 +4,7 @@ import axios from "axios"
 // export const RESET = 'RESET';
 
 
-/* const initialState = () => ({
+/* const initialState = () => ({ // ? closure ?
   form: null,
   colorsStyleList: [],
   customForms: [],
@@ -59,6 +59,9 @@ const feature_type = {
     },
     SET_IMPORT_FEATURE_TYPES_DATA(state, payload) {
       state.importFeatureTypeData = payload;
+    },
+    EMPTY_CUSTOM_FORMS(state) {
+      state.customForms = [];
     }
   },
   getters: {
@@ -81,11 +84,11 @@ const feature_type = {
         //'project': state.form.project.value,
         'customfield_set': state.customForms.map(el => {
           return {
-            'position': el.position.value,
-            'label': el.label.value,
-            'name': el.name.value,
-            'field_type': el.field_type.value,
-            'options': el.options.value,
+            'position': el.position,
+            'label': el.label,
+            'name': el.name,
+            'field_type': el.field_type,
+            'options': el.options,
           }
         }),
         //'is_editable': true,
@@ -96,26 +99,21 @@ const feature_type = {
         .post(`${process.env.VUE_APP_DJANGO_API_BASE}feature-types/`, data)
         .then((response) => {
           console.log(response)
-          // const routerHistory = this.$router.options.routerHistory
-          //store.commit("SET_USER", response.data.user);
-          // this.$router.push(routerHistory[routerHistory.length - 1] || "/")
-          //store.dispatch("GET_USER_LEVEL_PROJECTS");
         })
         .catch((error) => {
           console.error(error);
-          // store.commit("SET_USER", false)
         });
     },
 
-    POST_FEATURES_FROM_GEOJSON({ rootGetters, dispatch }, payload) {
-      const { feature_type_slug, filenameToImport } = payload
+    POST_FEATURES_FROM_GEOJSON({ dispatch }, payload) {
+      const { slug, feature_type_slug, filenameToImport } = payload
 
       if (filenameToImport.size > 0) {
         var formData = new FormData();
         formData.append("json_file", filenameToImport);
         let url =
           process.env.VUE_APP_URL_BASE +
-          "projet/" + rootGetters.project.slug +
+          "projet/" + slug +
           "/type-signalement/" + feature_type_slug +
           "/importer-geojson/";
         axios
@@ -146,7 +144,6 @@ const feature_type = {
         .get(url)
         .then((response) => {
           commit("SET_IMPORT_FEATURE_TYPES_DATA", response.data);
-          //commit("SET_IMPORT_TASK_DATA", response.data);
         })
         .catch((err) => {
           console.log(err);
diff --git a/src/views/feature_type/Feature_type_detail.vue b/src/views/feature_type/Feature_type_detail.vue
index 4f251e2b..311c5ee7 100644
--- a/src/views/feature_type/Feature_type_detail.vue
+++ b/src/views/feature_type/Feature_type_detail.vue
@@ -209,11 +209,7 @@ export default {
   watch: {
     structure(newVal, oldVal) {
       if (newVal !== oldVal) {
-        //this.getImports();
-        this.$store.dispatch(
-          "feature_type/GET_IMPORTS",
-          this.structure.slug
-        );
+        this.$store.dispatch("feature_type/GET_IMPORTS", this.structure.slug);
       }
     },
   },
@@ -227,7 +223,6 @@ export default {
       fileToImport: {},
       showImport: false,
       showExport: true,
-      //dataImport: [],
     };
   },
 
@@ -240,54 +235,11 @@ export default {
 
     importGeoJson() {
       this.$store.dispatch("feature_type/POST_FEATURES_FROM_GEOJSON", {
-        //slug: this.$route.params.slug,
+        slug: this.$route.params.slug,
         feature_type_slug: this.$route.params.feature_type_slug,
         filenameToImport: this.filenameToImport,
       });
-      /*  if (this.filenameToImport.size > 0) {
-        var formData = new FormData();
-        formData.append("json_file", this.filenameToImport);
-        let url =
-          process.env.VUE_APP_URL_BASE +
-          "projet/" +
-          this.$route.params.slug +
-          "/type-signalement/" +
-          this.$route.params.feature_type_slug +
-          "/importer-geojson/";
-        axios
-          .post(url, formData, {
-            headers: {
-              "Content-Type": "multipart/form-data",
-            },
-          })
-          .then((response) => {
-            if (response.status == 200) {
-              this.getImports();
-              // TODO : RELOAD DERNIER SIGNALEMENTS
-            }
-          })
-          .catch((err) => {
-            // TODO : HANDLER ERROR
-            console.log(err);
-          });
-      } */
     },
-
-    /*  getImports() {
-      let url =
-        process.env.VUE_APP_DJANGO_API_BASE +
-        "import-tasks?feature_type_id=" +
-        this.structure.feature_type;
-      axios
-        .get(url)
-        .then((response) => {
-          this.dataImport = response.data;
-        })
-        .catch((err) => {
-          console.log(err);
-        });
-    }, */
-
     exportFeatures() {
       console.log("TEST", this.$store);
       this.$store.dispatch(
diff --git a/src/views/feature_type/Feature_type_edit.vue b/src/views/feature_type/Feature_type_edit.vue
index c40c7c87..f7a80d8e 100644
--- a/src/views/feature_type/Feature_type_edit.vue
+++ b/src/views/feature_type/Feature_type_edit.vue
@@ -32,7 +32,7 @@
               v-model="form.title.value"
               @blur="updateStore"
             />
-            <ul class="errorlist">
+            <ul id="errorlist" class="errorlist">
               <li v-for="error in form.title.errors" :key="error">
                 {{ error }}
               </li>
@@ -43,9 +43,9 @@
               form.geom_type.label
             }}</label>
             <Dropdown
-              :options="form.geom_type.field.choices"
-              :selected="selected_geom_type"
-              :selection.sync="selected_geom_type"
+              :options="geomTypeChoices"
+              :selected="selectedGeomType"
+              :selection.sync="selectedGeomType"
             />
             <!-- {{ form.geom_type.errors }} -->
           </div>
@@ -156,6 +156,11 @@ export default {
     return {
       action: "create",
       dataKey: 0,
+      geomTypeChoices: [
+        { value: "linestring", name: "Ligne" },
+        { value: "point", name: "Point" },
+        { value: "polygon", name: "Polygone" },
+      ],
       form: {
         colors_style: {
           value: null,
@@ -185,7 +190,7 @@ export default {
           id_for_label: "geom_type",
           label: "Type de géométrie",
           field: {
-            choices: ["Ligne", "Point", "Polygone"],
+            //choices: ["Ligne", "Point", "Polygone"],
             max_length: 128, // ! Vérifier la valeur dans django
           },
           html_name: "geom_type",
@@ -203,16 +208,6 @@ export default {
         "deletion_on",
         "feature_type",
       ],
-
-      typeDict: {
-        boolean: "Booléen",
-        string: "Chaîne de caractères",
-        date: "Date",
-        list: "Liste de valeurs",
-        integer: "Nombre entier",
-        decimal: "Nombre décimal",
-        text: "Texte multiligne",
-      },
     };
   },
   props: ["geojson"],
@@ -221,12 +216,19 @@ export default {
     ...mapGetters(["project"]),
     ...mapState("feature_type", ["customForms", "colorsStyleList"]),
     ...mapGetters("feature_type", ["feature_type"]),
-    selected_geom_type: {
+    selectedGeomType: {
       get() {
-        return this.form.geom_type.value;
+        const currentGeomType = this.geomTypeChoices.find(
+          (el) => el.value === this.form.geom_type.value
+        );
+        if (currentGeomType) {
+          return currentGeomType ? currentGeomType.name : null;
+        }
+        return null;
       },
       set(newValue) {
-        this.form.geom_type.value = newValue;
+        this.form.geom_type.value = newValue.value;
+        this.form = { ...this.form }; // ! quick & dirty fix for getter not updating because of Vue caveat https://vuejs.org/v2/guide/reactivity.html#For-Objects
         this.updateStore();
       },
     },
@@ -275,7 +277,7 @@ export default {
         dataKey: this.dataKey,
       };
       if (customForm) {
-        newCustomForm = { newCustomForm, ...customForm };
+        newCustomForm = { ...newCustomForm, ...customForm };
       }
       this.$store.commit("feature_type/ADD_CUSTOM_FORM", newCustomForm); // * create an object with the counter in store
     },
@@ -302,6 +304,9 @@ export default {
         !this.form.title.errors.includes("Veuillez compléter ce champ.") // TODO : Gérer les autres champs
       ) {
         this.form.title.errors.push("Veuillez compléter ce champ.");
+        document
+          .getElementById("errorlist")
+          .scrollIntoView({ block: "end", inline: "nearest" });
       }
     },
     postFeatureTypeNfeatures() {
@@ -327,67 +332,59 @@ export default {
     },
     translateLabel(value) {
       if (value == "LineString") {
-        return "Ligne";
+        return "linestring";
       } else if (value == "Polygon") {
-        return "Polygone";
+        return "polygon";
       }
       return "point";
-
-      //("linestring", "Ligne"),
- //("point", "Point"),     
- //("polygon", "Polygone"),
     },
 
     transformProperties(prop) {
       const type = typeof prop;
+      const date = new Date(prop);
       if (type === "boolean") {
-        return "Booléen";
+        return "boolean";
       } else if (type === "number") {
-        return "Nombre entier";
+        return "integer";
       } else if (type === "string") {
-        //* check if string is not convertible to a decimal
-        if (isNaN(parseFloat(prop))) {
-          if (new Date(prop) !== "Invalid Date") {
-            return "Date";
-          } /* else if (prop.includes(",")) {
-            return "Liste de valeurs";
-          } */ 
-          else {
-            return "Chaîne de caractères";
-          }
-        } else {
-          return "Nombre décimal";
+        //* check if string is convertible to a number, then it should be a decimal
+        if (date instanceof Date && !isNaN(date.valueOf())) {
+          return "date";
+        } else if (!isNaN(parseFloat(prop))) {
+          return "decimal";
         }
       }
-      return null;
-    },
-
-    getFieldType(val) {
-      if (val in this.typeDict) return this.typeDict[val];
+      return "char"; //* string by default, most accepted type in database
     },
 
     importGeoJson() {
-      this.updateStore()
       // TODO : VALIDATION IF A JSONDICT HAS NOT FEATURES
-      if (this.geojson.features) {
+      if (this.geojson.features && this.geojson.features.length) {
         //* in order to get feature_type properties, the first feature is enough
         const { properties, geometry } = this.geojson.features[0];
         this.form.title.value = properties.feature_type;
         this.form.geom_type.value = this.translateLabel(geometry.type);
+        this.updateStore(); //* register title & geom_type in store
 
         //* loop properties to create a customForm for each of them
         for (const [key, val] of Object.entries(properties)) {
-          //* check that the property is not a keyword from the backend or map style (to add)
+          //* check that the property is not a keyword from the backend or map style
+          // todo: add map style keywords)
           if (!this.reservedKeywords.includes(key)) {
             const customForm = {
               label: { value: key || "" },
               name: { value: key || "" },
               // todo : increment  position
-              position: { value: 0 }, // * not available in export 
+              position: { value: this.dataKey }, // * use dataKey incremented at addCustomForm
               field_type: { value: this.transformProperties(val) }, // * guessed from the type
-              options: { value: null }, // * not available in export
+              options: { value: [] }, // * not available in export
             };
-            console.log("customForm", customForm, this.transformProperties(val), val);
+            console.log(
+              "val",
+              val,
+              "transformProperties",
+              this.transformProperties(val)
+            );
             this.addCustomForm(customForm);
           }
         }
diff --git a/src/views/project/Project_detail.vue b/src/views/project/Project_detail.vue
index d1c9a206..2c6f54c6 100644
--- a/src/views/project/Project_detail.vue
+++ b/src/views/project/Project_detail.vue
@@ -193,7 +193,8 @@
                 <label class="ui" for="json_file">
                   <i class="ui plus icon"></i>
                   <span class="label"
-                    >Créer un nouveau type de signalement à partir d'un GeoJSON</span
+                    >Créer un nouveau type de signalement à partir d'un
+                    GeoJSON</span
                   >
                 </label>
                 <input
@@ -486,8 +487,8 @@ export default {
     ...mapState(["last_comments", "user"]),
     BASE_URL: () => process.env.VUE_APP_BASE_URL,
     last_features: function () {
-      // TODO : Filter les dernières
-      return this.$store.state.feature.features;
+      // * limit to last five element of array (looks sorted chronologically, but not sure...)
+      return this.$store.state.feature.features.slice(-5);
     },
   },
   created() {
-- 
GitLab