Skip to content
Snippets Groups Projects
ProjectEdit.vue 18.7 KiB
Newer Older
Florent Lavelle's avatar
Florent Lavelle committed
  <div
    id="project-edit"
    class="page"
  >
    <div
      :class="{ active: loading }"
      class="ui inverted dimmer"
    >
Florent's avatar
Florent committed
      <div class="ui text loader">
        Projet en cours de création. Vous allez être redirigé.
      </div>
    </div>
    <form
      id="form-project-edit"
      class="ui form"
    >
        <span
          v-if="action === 'edit'"
        >Édition du projet "{{ form.title }}"</span>
        <span v-else-if="action === 'create'">Création d'un projet</span>
      </h1>

      <div class="ui horizontal divider">
        INFORMATIONS
      </div>

      <div class="two fields">
        <div class="required field">
          <label for="title">Titre</label>
          <input
            id="title"
            v-model="form.title"
          >
          <ul
            id="errorlist-title"
            class="errorlist"
          >
            <li
              v-for="error in errors.title"
              :key="error"
            >
Timothee P's avatar
Timothee P committed
              {{ error }}
            </li>
          </ul>
        </div>
        <div class="field">
          <label>Illustration du projet</label>
          <img
Florent Lavelle's avatar
Florent Lavelle committed
            v-if="thumbnailFileSrc.length || form.thumbnail.length"
            class="ui small image"
            :src="
              thumbnailFileSrc
                ? thumbnailFileSrc
                : DJANGO_BASE_URL + form.thumbnail
            "
Florent Lavelle's avatar
Florent Lavelle committed
            alt="Thumbnail du projet"
          >
          <label
            class="ui icon button"
            for="thumbnail"
          >
Florent Lavelle's avatar
Florent Lavelle committed
            <i
              class="file icon"
              aria-hidden="true"
            />
            <span class="label">{{
              form.thumbnail_name ? form.thumbnail_name : fileToImport.name
            }}</span>
          </label>
          <input
            id="thumbnail"
            class="file-selection"
            type="file"
            accept="image/jpeg, image/png"
            style="display: none"
            name="thumbnail"
            @change="onFileChange"
          >
          <ul
            v-if="errorThumbnail.length"
            id="errorlist-thumbnail"
            class="errorlist"
          >
            <li>
              {{ errorThumbnail[0] }}
            </li>
          </ul>
        </div>
      </div>
      <div class="field">
        <label for="description">Description</label>
        <textarea
          v-model="form.description"
          name="description"
          rows="5"
      <div class="ui horizontal divider">
        PARAMÈTRES
      </div>
      <div class="two fields">
        <!-- <div class="field">
          <label for="archive_feature">Délai avant archivage</label>
          <div class="ui right labeled input">
            <input
              id="archive_feature"
              v-model="form.archive_feature"
Timothee P's avatar
Timothee P committed
              oninput="validity.valid||(value=0);"
Timothee P's avatar
Timothee P committed
              @blur="checkEmpty"
            >
            <div class="ui label">
              jour(s)
            </div>
          <ul
            v-if="errors_archive_feature.length"
            id="errorlist-achivage"
            class="errorlist"
          >
            <li>
              {{ errors_archive_feature[0] }}
            </li>
          </ul>
        </div>
        <div class="field">
          <label for="delete_feature">Délai avant suppression</label>
          <div class="ui right labeled input">
            <input
              id="delete_feature"
              v-model="form.delete_feature"
Timothee P's avatar
Timothee P committed
              oninput="validity.valid||(value=0);"
Timothee P's avatar
Timothee P committed
              @blur="checkEmpty"
            >
            <div class="ui label">
              jour(s)
            </div>
          <label
            for="access_level_pub_feature"
          >Visibilité des signalements publiés</label>
leandro's avatar
leandro committed
            :options="levelPermissionsPub"
            :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"
            >
Timothee P's avatar
Timothee P committed
              {{ error }}
            </li>
          </ul>
        </div>
        <div class="required field">
          <label for="access_level_arch_feature">
            Visibilité des signalements archivés
          </label>
          <Dropdown
            :options="levelPermissions"
            :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"
            >
Timothee P's avatar
Timothee P committed
              {{ error }}
            </li>
          </ul>
        </div>
      </div>

      <div class="field">
        <div class="ui checkbox">
          <input
            id="moderation"
Timothee P's avatar
Timothee P committed
            v-model="form.moderation"
          <label for="moderation">Modération</label>
        </div>
      </div>

      <div class="field">
        <div class="ui checkbox">
          <input
            id="is_project_type"
Timothee P's avatar
Timothee P committed
            v-model="form.is_project_type"
          <label for="is_project_type">Est un projet type</label>
        </div>
      </div>

      <div class="field">
        <div class="ui checkbox">
Timothee P's avatar
Timothee P committed
          <input
            id="generate_share_link"
            v-model="form.generate_share_link"
            class="hidden"
            type="checkbox"
            name="generate_share_link"
          >
          <label for="generate_share_link">Génération d'un lien de partage externe</label>
        </div>
      <div class="ui divider" />
      <button
        type="button"
        class="ui teal icon button"
        @click="postForm"
      >
Florent Lavelle's avatar
Florent Lavelle committed
        <i
          class="white save icon"
          aria-hidden="true"
        /> Enregistrer les changements
import axios from '@/axios-client.js';
import Dropdown from '@/components/Dropdown.vue';
import { mapState, mapActions } from 'vuex';
  name: 'ProjectEdit',
Florent's avatar
Florent committed
      loading: false,
      action: 'create',
        name: 'Sélectionner une image ...',
      errors_archive_feature: [],
Timothee P's avatar
Timothee P committed
      errors: {
        title: [],
        access_level_pub_feature: [],
        access_level_arch_feature: [],
      },
      errorThumbnail: [],
        title: '',
        slug: '',
        created_on: '',
        updated_on: '',
        description: '',
        thumbnail: '', // todo : utiliser l'image par défaut
        thumbnail_name: '', // todo: delete after getting image in jpg or png instead of data64 (require post to django)
        access_level_pub_feature: { name: '', value: '' },
        access_level_arch_feature: { name: '', value: '' },
        archive_feature: 0,
        delete_feature: 0,
        nb_features: 0,
        nb_published_features: 0,
        nb_comments: 0,
        nb_published_features_comments: 0,
        nb_contributors: 0,
        is_project_type: false,
        generate_share_link: false,
      thumbnailFileSrc: '',
leandro's avatar
leandro committed
    ...mapState([
      'levelsPermissions',
leandro's avatar
leandro committed
    ]),
    ...mapState('projects', ['project']),
Timothee P's avatar
Timothee P committed
    DJANGO_BASE_URL: function () {
      return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
    },
leandro's avatar
leandro committed
    levelPermissions(){
Florent Lavelle's avatar
Florent Lavelle committed
      const levels = new Array();
      if(this.levelsPermissions) {
        this.levelsPermissions.forEach((item) => {
Florent Lavelle's avatar
Florent Lavelle committed
          if (item.user_type_id !== 'super_contributor') {
            levels.push({
              name: this.translateRoleToFrench(item.user_type_id), 
              value: item.user_type_id,
            });
Florent Lavelle's avatar
Florent Lavelle committed
          }
          if (!this.form.moderation && item.user_type_id == 'moderator') {
            levels.pop();
          }
        });
leandro's avatar
leandro committed
      }
      return levels;
    },
    levelPermissionsPub(){
Florent Lavelle's avatar
Florent Lavelle committed
      const levels = new Array();
      if (this.levelsPermissions) {
        this.levelsPermissions.forEach((item) => {
Florent Lavelle's avatar
Florent Lavelle committed
          if (
            item.user_type_id !== 'super_contributor' &&
            item.user_type_id !== 'admin' &&
            item.user_type_id !== 'moderator'
          ) {
            levels.push({
              name: this.translateRoleToFrench(item.user_type_id), 
              value: item.user_type_id,
            });
leandro's avatar
leandro committed
          }
leandro's avatar
leandro committed
        });
leandro's avatar
leandro committed
      }
      return levels;
  watch: {
    'form.moderation': function (newValue){
      if(!newValue && this.form.access_level_arch_feature.value === 'moderator') {
        this.form.access_level_arch_feature = { name: '', value: '' };
      }
    }
  },
  created() {
    this.definePageType();
    if (this.action === 'create') {
      this.thumbnailFileSrc = require('@/assets/img/default.png');
    } else if (this.action === 'edit' || this.action === 'create_from') {
      if (!this.project) {
        this.$store.dispatch('projects/GET_PROJECT', this.$route.params.slug)
          .then((projet) => {
Florent Lavelle's avatar
Florent Lavelle committed
            if (projet) {
              this.fillProjectForm();
            }
          });
      } else {
        this.fillProjectForm();
      }
Florent Lavelle's avatar
Florent Lavelle committed
    ...mapActions('projects', [
      'GET_ALL_PROJECTS'
    ]),
      if (this.$router.history.current.name === 'project_create') {
        this.action = 'create';
      } else if (this.$router.history.current.name === 'project_edit') {
        this.action = 'edit';
      } else if (this.$router.history.current.name === 'project_create_from') {
        this.action = 'create_from';
    translateRoleToFrench(role){
      switch (role) {
      case 'admin':
        return 'Administrateur projet';
      case 'moderator':
        return 'Modérateur';
      case 'contributor':
        return 'Contributeur';
      case 'logged_user':
        return 'Utilisateur connecté';
      case 'anonymous':
        return 'Utilisateur anonyme';
      }
leandro's avatar
leandro committed
    },
Florent Lavelle's avatar
Florent Lavelle committed
      const ext = n.substring(n.lastIndexOf('.') + 1, n.length).toLowerCase();
      let filename = n.replace('.' + ext, '');
      filename = filename.substr(0, len) + (n.length > len ? '[...]' : '');
      return filename + '.' + ext;
    validateImgFile(files, handleFile) {
Florent Lavelle's avatar
Florent Lavelle committed
      const url = window.URL || window.webkitURL;
      const image = new Image();
      image.onload = function () {
        handleFile(true);
        URL.revokeObjectURL(image.src);
      };
      image.onerror = function () {
        handleFile(false);
        URL.revokeObjectURL(image.src);
      };
      image.src = url.createObjectURL(files);
    },

    onFileChange(e) {
      // * read image file
      const files = e.target.files || e.dataTransfer.files;

      const _this = this; //* 'this' is different in onload function
      function handleFile(isValid) {
        if (isValid) {
          _this.fileToImport = files[0]; //* store the file to post later
Florent Lavelle's avatar
Florent Lavelle committed
          const reader = new FileReader(); //* read the file to display in the page
          reader.onload = function (e) {
            _this.thumbnailFileSrc = e.target.result;
          };
          reader.readAsDataURL(_this.fileToImport);
          _this.errorThumbnail = [];
        } else {
          _this.errorThumbnail.push(
            "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu."
          );
        }
      }

      if (files.length) {
        //* check if file is an image and pass callback to handle file
        this.validateImgFile(files[0], handleFile);
      }
Timothee P's avatar
Timothee P committed
    checkEmpty() {
      //* forbid empty fields
Florent Lavelle's avatar
Florent Lavelle committed
      if (!this.form.archive_feature) {
        this.form.archive_feature = 0;
      }
      if (!this.form.delete_feature) {
        this.form.delete_feature = 0;
      }
Timothee P's avatar
Timothee P committed
        this.$store.dispatch('GET_USER_LEVEL_PROJECTS'), //* refresh projects user levels
        this.$store.dispatch('GET_USER_LEVEL_PERMISSIONS'), //* refresh projects permissions
Florent Lavelle's avatar
Florent Lavelle committed
        this.GET_ALL_PROJECTS(), //* & refresh project list
      ]).then(() =>
        // * go back to project list
        this.$router.push({
          name: 'project_detail',
      );
    },

    postProjectThumbnail(projectSlug) {
      //* send img to the backend when feature_type is created
      if (this.fileToImport) {
Florent Lavelle's avatar
Florent Lavelle committed
        const formData = new FormData();
        formData.append('file', this.fileToImport);
        const url =
          this.$store.state.configuration.VUE_APP_DJANGO_API_BASE +
          'projects/' +
          projectSlug +
          '/thumbnail/';
        return axios
          .put(url, formData, {
            headers: {
              'Content-Type': 'multipart/form-data',
            },
          })
          .then((response) => {
            if (response && response.status === 200) {
              this.goBackNrefresh(projectSlug);
            }
          })
          .catch((error) => {
            let err_msg =
              "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu.";
Florent Lavelle's avatar
Florent Lavelle committed
            if (error.response.data[0]) {
              err_msg = error.response.data[0];
            }
            this.errorThumbnail.push(err_msg);
            throw error;
          });
      }
Timothee P's avatar
Timothee P committed
    checkForm() {
      if (this.form.archive_feature > this.form.delete_feature) {
        this.errors_archive_feature.push(
          "Le délais de suppression doit être supérieur au délais d'archivage."
        );
        return false;
      }
Timothee P's avatar
Timothee P committed
      for (const key in this.errors) {
        if ((key === 'title' && this.form[key]) || this.form[key].value) {
Timothee P's avatar
Timothee P committed
          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.'
Timothee P's avatar
Timothee P committed
          );
          document
            .getElementById(`errorlist-${key}`)
            .scrollIntoView({ block: 'end', inline: 'nearest' });
Timothee P's avatar
Timothee P committed
          return false;
        }
      }
      return true;
    },

Florent Lavelle's avatar
Florent Lavelle committed
      if (!this.checkForm()) {
        return;
      }
      const projectData = {
        title: this.form.title,
        description: this.form.description,
        access_level_arch_feature: this.form.access_level_arch_feature.value,
        access_level_pub_feature: this.form.access_level_pub_feature.value,
        archive_feature: this.form.archive_feature,
        delete_feature: this.form.delete_feature,
        is_project_type: this.form.is_project_type,
        generate_share_link: this.form.generate_share_link,
Timothee P's avatar
Timothee P committed
      let url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`;
      if (this.action === 'edit') {
Timothee P's avatar
Timothee P committed
          .put((url += `${this.project.slug}/`), projectData)
Timothee P's avatar
Timothee P committed
            if (response && response.status === 200) {
              //* send thumbnail after feature_type was updated
Timothee P's avatar
Timothee P committed
              if (this.fileToImport.size > 0) {
Timothee P's avatar
Timothee P committed
                this.postProjectThumbnail(this.project.slug);
Timothee P's avatar
Timothee P committed
              } else {
Timothee P's avatar
Timothee P committed
                this.goBackNrefresh(this.project.slug);
            if (error.response && error.response.data.title[0]) {
Timothee P's avatar
Timothee P committed
              this.errors.title.push(error.response.data.title[0]);
            }
Timothee P's avatar
Timothee P committed
      } else {
        if (this.action === 'create_from') {
Timothee P's avatar
Timothee P committed
          url += `${this.project.slug}/duplicate/`;
        }
        this.loading = true;
Timothee P's avatar
Timothee P committed
          .post(url, projectData)
Timothee P's avatar
Timothee P committed
            if (response && response.status === 201 && response.data) {
              //* send thumbnail after feature_type was created
              if (this.fileToImport.size > 0) {
Timothee P's avatar
Timothee P committed
                this.postProjectThumbnail(response.data.slug);
Timothee P's avatar
Timothee P committed
                this.goBackNrefresh(response.data.slug);
Timothee P's avatar
Timothee P committed
            this.loading = false;
            if (error.response && error.response.data.title[0]) {
Timothee P's avatar
Timothee P committed
              this.errors.title.push(error.response.data.title[0]);
            }
Timothee P's avatar
Timothee P committed
            this.loading = false;
    fillProjectForm() {
      this.form = { ...this.project }; //* create a new object to avoid modifying original one
      if (this.action === 'create_from') { //* if duplication of project, generate new name
        this.form.title =
          this.project.title +
          ` (Copie-${new Date()
            .toLocaleString()
            .slice(0, -3)
            .replace(',', '')})`;
      //* transform string values to objects for dropdowns display (could be in a computed)
      if (this.levelPermissionsPub) {
Florent Lavelle's avatar
Florent Lavelle committed
        const value = this.levelPermissionsPub.find(
          (el) => el.name === this.project.access_level_pub_feature
        );
leandro's avatar
leandro committed
        if(value){
          this.form.access_level_pub_feature = {
            name: this.project.access_level_pub_feature,
            value: value.value ,
          };
        }
      }
      if (this.levelPermissions) {
Florent Lavelle's avatar
Florent Lavelle committed
        const value = this.levelPermissions.find(
          (el) => el.name === this.project.access_level_arch_feature
        );
leandro's avatar
leandro committed
        if(value){
          this.form.access_level_arch_feature = {
            name: this.project.access_level_arch_feature,
            value: value.value ,
          };
        }
      }
};
</script>

<style media="screen">
#form-input-file-logo {
  margin-left: auto;
  margin-right: auto;
}

.close.icon:hover {
  cursor: pointer;
}
</style>