diff --git a/src/components/feature/FeatureAttachmentForm.vue b/src/components/feature/FeatureAttachmentForm.vue index eb9d0ef402eab0ff57f5623e7088c0c10637ee4b..c75905f2db595f7cee272a5d245ede78a522024f 100644 --- a/src/components/feature/FeatureAttachmentForm.vue +++ b/src/components/feature/FeatureAttachmentForm.vue @@ -11,7 +11,7 @@ <i class="ui times icon"></i> </button> </h4> - {{ form.errors }} + <!-- {{ form.errors }} --> <div class="visible-fields"> <div class="two fields"> <div class="required field"> @@ -25,7 +25,11 @@ v-model="form.title.value" @change="updateStore" /> - {{ form.title.errors }} + <ul :id="form.title.id_for_error" class="errorlist"> + <li v-for="error in form.title.errors" :key="error"> + {{ error }} + </li> + </ul> </div> <div class="required field"> <label>Fichier (PDF, PNG, JPEG)</label> @@ -46,7 +50,11 @@ :name="form.attachment_file.html_name" :id="'attachment_file' + attachmentForm.dataKey" /> - {{ form.attachment_file.errors }} + <ul :id="form.attachment_file.id_for_error" class="errorlist"> + <li v-for="error in form.attachment_file.errors" :key="error"> + {{ error }} + </li> + </ul> </div> </div> <div class="field"> @@ -57,7 +65,7 @@ v-model="form.info.value" @change="updateStore" ></textarea> - {{ form.info.errors }} + <!-- {{ form.info.errors }} --> </div> </div> </div> @@ -75,7 +83,8 @@ export default { fileToImport: null, form: { title: { - errors: null, + errors: [], + id_for_error: `errorlist-title-${this.attachmentForm.dataKey}`, id_for_label: "titre", field: { max_length: 30, // todo : vérifier dans django @@ -85,10 +94,11 @@ export default { value: "", }, attachment_file: { - errors: null, + errors: [], + id_for_error: `errorlist-file-${this.attachmentForm.dataKey}`, html_name: "titre", label: "Titre", - value: "", + value: null, }, info: { value: "", @@ -109,19 +119,22 @@ export default { initForm(attachmentForm) { for (let el in attachmentForm) { if (el && this.form[el]) { - this.form[el].value = - el === "attachment_file" - ? attachmentForm[el].split("/").pop() //* keep only the file name, not the path - : attachmentForm[el]; + if (el === "attachment_file" && attachmentForm[el]) { + this.form[el].value = attachmentForm[el].split("/").pop(); //* keep only the file name, not the path + } else { + this.form[el].value = attachmentForm[el]; + } } } }, + removeAttachmentFormset() { this.$store.commit( "feature/REMOVE_ATTACHMENT_FORM", this.attachmentForm.dataKey ); }, + updateStore() { this.$store.commit("feature/UPDATE_ATTACHMENT_FORM", { dataKey: this.attachmentForm.dataKey, @@ -131,6 +144,7 @@ export default { fileToImport: this.fileToImport, }); }, + onFileChange(e) { const files = e.target.files || e.dataTransfer.files; if (!files.length) return; @@ -138,6 +152,28 @@ export default { this.form.attachment_file.value = files[0].name; //* add name to the form for display, in order to match format return from API this.updateStore(); }, + + checkForm() { + let isValid = true; + if (this.form.title.value === "") { + this.form.title.errors = ["Veuillez compléter ce champ."]; + document + .getElementById(this.form.title.id_for_error) + .scrollIntoView({ block: "start", inline: "nearest" }); + isValid = false; + } else if (this.form.attachment_file.value === null) { + this.form.attachment_file.errors = ["Veuillez compléter ce champ."]; + this.form.title.errors = []; + document + .getElementById(this.form.attachment_file.id_for_error) + .scrollIntoView({ block: "start", inline: "nearest" }); + isValid = false; + } else { + this.form.title.errors = []; + this.form.attachment_file.errors = []; + } + return isValid; + }, }, mounted() { diff --git a/src/components/feature/FeatureLinkedForm.vue b/src/components/feature/FeatureLinkedForm.vue index 960f63371b388c965bfe623add3ba30b190030e4..80f10d776a4754d01cdcfb0d1568a566964d5e8b 100644 --- a/src/components/feature/FeatureLinkedForm.vue +++ b/src/components/feature/FeatureLinkedForm.vue @@ -10,7 +10,9 @@ <i class="ui times icon"></i> </button> </h4> - {{ form.errors }} + <ul id="errorlist-links" class="errorlist"> + <li v-for="error in form.errors" :key="error" v-html="error"></li> + </ul> <div class="visible-fields"> <div class="two fields"> <div class="required field"> @@ -53,8 +55,10 @@ export default { }, computed: { - featureOptions: function() { - return this.features.map(el=> `${el.title} (${el.display_creator} - ${el.created_on})`) + featureOptions: function () { + return this.features.map( + (el) => `${el.title} (${el.display_creator} - ${el.created_on})` + ); }, selected_relation_type: { // getter @@ -92,7 +96,7 @@ export default { }, html_name: "relation_type", label: "Type de liaison", - value: "", + value: "Doublon", }, feature_to: { errors: null, @@ -118,6 +122,19 @@ export default { feature_to: this.form.feature_to.value, }); }, + checkForm() { + if (this.form.feature_to.value === "") { + this.form.errors = [ + "<strong>Choisir un signalement lié</strong><br/> Pourriez-vous choisir un signalement pour la nouvelle liaison ?", + ]; + document + .getElementById("errorlist-links") + .scrollIntoView({ block: "start", inline: "nearest" }); + return false; + } + this.form.errors = []; + return true; + }, }, }; </script> \ No newline at end of file diff --git a/src/components/feature_type/FeatureTypeCustomForm.vue b/src/components/feature_type/FeatureTypeCustomForm.vue index fcdf282ddc87b864f5ade32573d867e6c2cf2455..140a62b0765e06417bb45fb7e03a1283aebac863 100644 --- a/src/components/feature_type/FeatureTypeCustomForm.vue +++ b/src/components/feature_type/FeatureTypeCustomForm.vue @@ -271,8 +271,6 @@ export default { return string.replace(/\s*,\s*/gi, ","); }, checkCustomForm() { - console.log("checkCustomForm"); - console.log(this.form); if (this.form.label.value === null) { this.form.label.errors = ["Veuillez compléter ce champ."]; return false; diff --git a/src/store/modules/feature.js b/src/store/modules/feature.js index df638cf266dfce9e82646000d260747c0adbc853..fd9ee2833a5d85f36bf828c538cf948cb6bff8a2 100644 --- a/src/store/modules/feature.js +++ b/src/store/modules/feature.js @@ -1,5 +1,5 @@ const axios = require("axios"); -//import router from '../../router' +import router from '../../router' const feature = { @@ -93,19 +93,31 @@ const feature = { .put(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/${state.form.feature_id}/`, geojson) .then((response) => { if (response.status === 200 && response.data) { - console.log(response, response.data) + router.push({ + name: "project_detail", + params: { + slug: rootState.project_slug, + message: "Le signalement a été mis à jour", + }, + }); } }) .catch((error) => { throw error; }); - } else { - axios + } else { + axios .post(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/`, geojson) .then((response) => { if (response.status === 201 && response.data) { - console.log(response, response.data) dispatch("SEND_ATTACHMENTS", response.data.id) + router.push({ + name: "project_detail", + params: { + slug: rootState.project_slug, + message: "Le signalement a été crée", + }, + }); } }) .catch((error) => { diff --git a/src/views/feature/Feature_edit.vue b/src/views/feature/Feature_edit.vue index c62a5b7c91bc52af32a0f6be8cdc0688ec9b9918..c13fb8bfeb046d852fde7a6f9c5d7e4c653b490f 100644 --- a/src/views/feature/Feature_edit.vue +++ b/src/views/feature/Feature_edit.vue @@ -30,7 +30,11 @@ v-model="form.title.value" @blur="updateStore" /> - {{ form.title.errors }} + <ul id="errorlist-title" class="errorlist"> + <li v-for="error in form.title.errors" :key="error"> + {{ error }} + </li> + </ul> </div> <div class="required field"> <label :for="form.status.id_for_label">{{ @@ -41,8 +45,6 @@ :selected="selected_status.name" :selection.sync="selected_status" /> - - {{ form.status.errors }} </div> </div> <div class="field"> @@ -55,7 +57,6 @@ v-model="form.description.value" @blur="updateStore" ></textarea> - {{ form.description.errors }} </div> <!-- Geom Field --> @@ -138,8 +139,11 @@ <br /> </span> </div> - - {{ form.geom.errors }} + <ul id="errorlist-geom" class="errorlist"> + <li v-for="error in form.geom.errors" :key="error"> + {{ error }} + </li> + </ul> <!-- Map --> <input type="hidden" @@ -172,14 +176,12 @@ <!-- Pièces jointes --> <div class="ui horizontal divider">PIÈCES JOINTES</div> - <!-- {{ attachment_formset.non_form_errors }} --> - <div id="formsets-attachment"> - <!-- {{ attachment_formset.management_form }} --> <FeatureAttachmentForm v-for="form in attachmentFormset" :key="form.dataKey" :attachmentForm="form" + ref="attachementForm" /> </div> @@ -194,15 +196,13 @@ <!-- Signalements liés --> <div class="ui horizontal divider">SIGNALEMENTS LIÉS</div> - <!-- {{ linked_formset.non_form_errors }} --> <div id="formsets-link"> - <!-- {{ linked_formset.management_form }} --> - <FeatureLinkedForm v-for="form in linkedFormset" :key="form.dataKey" :linkedForm="form" :features="features" + ref="linkedForm" /> </div> <button @@ -275,7 +275,7 @@ export default { ], form: { title: { - errors: null, + errors: [], id_for_label: "name", field: { max_length: 30, @@ -285,23 +285,25 @@ export default { value: "", }, status: { - errors: null, id_for_label: "status", html_name: "status", label: "Statut", - value: "Brouillon", + value: { + value: "draft", + name: "Brouillon", + }, }, description: { - errors: null, + errors: [], id_for_label: "description", html_name: "description", label: "Description", value: "", }, geom: { + errors: [], label: "Localisation", value: null, - errors: null, }, }, }; @@ -318,13 +320,13 @@ export default { ]), ...mapGetters("feature_type", ["feature_type"]), - field_title(){ - if(this.feature_type){ - if(this.feature_type.title_optional){ - return 'field'; + field_title() { + if (this.feature_type) { + if (this.feature_type.title_optional) { + return "field"; } } - return 'required field'; + return "required field"; }, currentRouteName() { @@ -365,15 +367,15 @@ export default { methods: { initForm() { if (this.currentRouteName === "editer-signalement") { - for (let el in this.feature) { - if (el && this.form[el]) { - if (el === "status") { - const value = this.feature[el]; - this.form[el].value = this.statusChoices.find( - (el) => el.value === value + for (let key in this.feature) { + if (key && this.form[key]) { + if (key === "status") { + const value = this.feature[key]; + this.form[key].value = this.statusChoices.find( + (key) => key.value === value ); } else { - this.form[el].value = this.feature[el]; + this.form[key].value = this.feature[key]; } } } @@ -481,6 +483,56 @@ export default { feature_id: this.feature ? this.feature.feature_id : "", }); }, + + checkFormTitle() { + if (this.form.title.value) { + this.form.title.errors = []; + return true; + } else if ( + !this.form.title.errors.includes("Veuillez compléter ce champ.") + ) { + this.form.title.errors.push("Veuillez compléter ce champ."); + document + .getElementById("errorlist-title") + .scrollIntoView({ block: "end", inline: "nearest" }); + } + return false; + }, + + checkFormGeom() { + if (this.form.geom.value) { + this.form.geom.errors = []; + return true; + } else if ( + !this.form.geom.errors.includes("Valeur géométrique non valide.") + ) { + this.form.geom.errors.push("Valeur géométrique non valide."); + document + .getElementById("errorlist-geom") + .scrollIntoView({ block: "end", inline: "nearest" }); + } + return false; + }, + + checkAddedForm() { + let isValid = true; //* fallback if all customForms returned true + if (this.$refs.attachementForm) { + for (const attachementForm of this.$refs.attachementForm) { + if (attachementForm.checkForm() === false) { + isValid = false; + } + } + } + if (this.$refs.linkedForm) { + for (const linkedForm of this.$refs.linkedForm) { + if (linkedForm.checkForm() === false) { + isValid = false; + } + } + } + return isValid; + }, + goBackToProject(message) { this.$router.push({ name: "project_detail", @@ -490,35 +542,25 @@ export default { }, }); }, - async postForm() { - if (!this.feature_type.title_optional){ - if (this.form.title.value && this.form.geom.value) { - this.form.title.errors = null; - await - this.$store.dispatch("feature/SEND_FEATURE") - .then(() => { - this.goBackToProject("Le signalement a été crée"); - }); - } else { - this.form.title.errors = "Veuillez compléter ce champ."; - this.form.geom.errors = "Veuillez compléter ce champ."; - } + + postForm() { + let is_valid = true; + if (!this.feature_type.title_optional) { + is_valid = + this.checkFormTitle() && + this.checkFormGeom() && + this.checkAddedForm(); + } else { + is_valid = this.checkFormGeom() && this.checkAddedForm(); } - else{ - if (this.form.geom.value) { - this.form.title.errors = null; - await - this.$store.dispatch("feature/SEND_FEATURE") - .then(() => { - this.goBackToProject("Le signalement a été crée"); - }); - } else { - this.form.geom.errors = "Veuillez compléter ce champ."; - } - + + if (is_valid) { + this.$store.dispatch("feature/SEND_FEATURE", this.currentRouteName); } }, + //* ************* MAP *************** *// + onFeatureTypeLoaded() { var geomLeaflet = { point: "circlemarker", @@ -797,13 +839,14 @@ export default { "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG", this.$route.params.slug_type_signal ); - // todo : mutualize in store with feature_detail.vue + // todo : mutualize in store with feature_detail.vue if (this.$route.params.slug_signal) { featureAPI .getFeatureAttachments(this.$route.params.slug_signal) .then((data) => this.addExistingAttachementFormset(data)); - } else { //* be sure that previous attachemntFormset has been cleared for creation + } else { + //* be sure that previous attachemntFormset has been cleared for creation this.$store.commit("feature/CLEAR_ATTACHMENT_FORM"); } }, @@ -813,8 +856,6 @@ export default { this.initMap(); }, }; - -// TODO : add script from django and convert: </script> <style> diff --git a/src/views/feature_type/Feature_type_edit.vue b/src/views/feature_type/Feature_type_edit.vue index bd5791b9de65c0ca081ea1eb7ee8f1a3030b7fd2..af4f1bb3af32c801c6e2525cfcb350b4725dda71 100644 --- a/src/views/feature_type/Feature_type_edit.vue +++ b/src/views/feature_type/Feature_type_edit.vue @@ -325,7 +325,7 @@ export default { // * find feature_type and fill form values if (this.form[el]) this.form[el].value = formData[el]; } - //! add custom fields using ONLY this function, incrementing dataKey for Vue updating correctly components + //! add custom fields using ONLY this function, incrementing dataKey for Vue to correctly update components formData.customfield_set.forEach((el) => this.addCustomForm(el)); this.updateStore(); }, @@ -359,7 +359,7 @@ export default { this.form.title.errors = []; return this.checkCustomForms(); //* if customForms are ok, validate, if get out function } else if ( - !this.form.title.errors.includes("Veuillez compléter ce champ.") // TODO : Gérer les autres champs + !this.form.title.errors.includes("Veuillez compléter ce champ.") ) { this.form.title.errors.push("Veuillez compléter ce champ."); document @@ -519,10 +519,10 @@ export default { } if (this.action === "duplicate") { //* replace original name with new default title - this.form.title.value += ` (Copie ${new Date() + this.form.title.value += ` (Copie-${new Date() .toLocaleString() .slice(0, -3) - .replace(",", "")} )`; + .replace(",", "")})`; this.updateStore(); // * initialize form in store in case this.form would not be modified } } diff --git a/src/views/project/Project_edit.vue b/src/views/project/Project_edit.vue index 5f809f620d8295acfcbadd826439c8a331f377fa..980c4466ae1b23c98de00b13326a4908b01a2c9a 100644 --- a/src/views/project/Project_edit.vue +++ b/src/views/project/Project_edit.vue @@ -13,8 +13,6 @@ <div class="two fields"> <div class="required field"> <label for="title">Titre</label> - <!-- <small>{{ form.title.help_text }}</small - > --><!-- | safe // ? utile ? --> <input type="text" required @@ -23,7 +21,11 @@ id="title" v-model="form.title" /> - <!-- {{ form.title.errors }} --> + <ul id="errorlist-title" class="errorlist"> + <li v-for="error in errors.title" :key="error"> + {{ error }} + </li> + </ul> </div> <div class="field"> <label>Illustration du projet</label> @@ -81,7 +83,6 @@ /> <div class="ui label">jour(s)</div> </div> - <!-- {{ form.archive_feature.errors }} --> </div> <div class="field"> <label for="delete_feature">Délai avant suppression</label> @@ -97,7 +98,6 @@ /> <div class="ui label">jour(s)</div> </div> - <!-- {{ form.delete_feature.errors }} --> </div> <div class="required field"> <label for="access_level_pub_feature" @@ -108,6 +108,11 @@ :selected="form.access_level_pub_feature.name" :selection.sync="form.access_level_pub_feature" /> + <ul id="errorlist-access_level_pub_feature" class="errorlist"> + <li v-for="error in errors.access_level_pub_feature" :key="error"> + {{ error }} + </li> + </ul> </div> <div class="required field"> <label for="access_level_arch_feature"> @@ -118,6 +123,11 @@ :selected="form.access_level_arch_feature.name" :selection.sync="form.access_level_arch_feature" /> + <ul id="errorlist-access_level_arch_feature" class="errorlist"> + <li v-for="error in errors.access_level_arch_feature" :key="error"> + {{ error }} + </li> + </ul> </div> </div> @@ -181,6 +191,11 @@ export default { name: "Sélectionner une image ...", size: 0, }, + errors: { + title: [], + access_level_pub_feature: [], + access_level_arch_feature: [], + }, form: { title: "", slug: "", @@ -208,7 +223,7 @@ export default { computed: { ...mapGetters(["project"]), - DJANGO_BASE_URL:function () { + DJANGO_BASE_URL: function () { return this.$store.state.configuration.VUE_APP_DJANGO_BASE; }, }, @@ -224,8 +239,8 @@ export default { } }, truncate(n, len) { - var ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase(); - var filename = n.replace("." + ext, ""); + let ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase(); + let filename = n.replace("." + ext, ""); if (filename.length <= len) { return n; } @@ -288,7 +303,27 @@ export default { } }, + checkForm() { + for (const key in this.errors) { + if ((key === "title" && this.form[key]) || this.form[key].value) { + this.errors[key] = []; + } else if (!this.errors[key].length) { + this.errors[key].push( + key === "title" + ? "Veuillez compléter ce champ." + : "Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux disponibles." + ); + document + .getElementById(`errorlist-${key}`) + .scrollIntoView({ block: "end", inline: "nearest" }); + return false; + } + } + return true; + }, + async postForm() { + if (!this.checkForm()) return; // todo: check form //let url = `${configuration.VUE_APP_DJANGO_API_BASE}projects/`; const projectData = { @@ -304,13 +339,16 @@ export default { if (this.action === "create" || this.action === "duplicate") { await axios - .post(`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`, projectData) + .post( + `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`, + projectData + ) .then((response) => { if (response && response.status === 201 && response.data) { //* send thumbnail after feature_type was created - if (this.fileToImport.size > 0){ + if (this.fileToImport.size > 0) { this.postProjectThumbnail(response.data.slug); - }else { + } else { this.goBackNrefresh(response.data.slug); } } @@ -342,15 +380,23 @@ export default { created() { this.definePageType(); + console.log(this.action); if (this.action === "create") { this.thumbnailFileSrc = require("@/assets/img/default.png"); - } else if (this.action === "edit") { + } else if (this.action === "edit" || this.action === "create_from") { if (!this.project) { this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug); } - this.form = this.project; - /* this.form.thumbnail = //* add api base to display image src - configuration.VUE_APP_DJANGO_BASE + this.form.thumbnail; */ + this.form = { ...this.project }; //* create a new object to avoid modifying original one + if (this.action === "create_from") { + this.form.title = + this.project.title + + ` (Copie-${new Date() + .toLocaleString() + .slice(0, -3) + .replace(",", "")})`; + this.form.is_project_type = false; + } //* transform string values to objects for dropdowns display (could be in a computed) this.form.access_level_pub_feature = { name: this.project.access_level_pub_feature, diff --git a/src/views/project/Project_type_list.vue b/src/views/project/Project_type_list.vue index 3909eab7ac7c1f54d274d63744467eda05df91a0..1af8b5d584bdd52d5995c7d18c5824b4da86128d 100644 --- a/src/views/project/Project_type_list.vue +++ b/src/views/project/Project_type_list.vue @@ -21,7 +21,7 @@ :to="{ name: 'project_create_from', params: { - slug: project.title, + slug: project.slug, }, }" >{{ project.title }}</router-link