Skip to content
Snippets Groups Projects
ProjectMembers.vue 11 KiB
Newer Older
    <h1 class="ui header">
      Gérer les membres
    </h1>
    <h4>Ajouter un membre</h4>

    <div
      id="form-feature-edit"
      class="ui form"
      name="add-member"
    >
      <div class="two fields">
        <div class="field">
          <Dropdown
            :options="userOptions"
            :selected="newMember.user.name"
            :selection.sync="newMember.user"
            :search="true"
            :clearable="true"
          />
          <ul
            id="errorlist"
            class="errorlist"
          >
            <li
              v-for="error in newMember.errors"
              :key="error"
            >
Timothee P's avatar
Timothee P committed
              {{ error }}
            </li>
          </ul>
        </div>
        <div class="field">
          <Dropdown
            :options="levelOptions"
            :selected="newMember.role.name"
            :selection.sync="newMember.role"
      <button
        type="button"
Timothee P's avatar
Timothee P committed
        class="ui green icon button"
        :disabled="!newMember.user.name"
        @click="addMember"
      >
Florent Lavelle's avatar
Florent Lavelle committed
        <i
          class="white add icon"
          aria-hidden="true"
        />
        <span class="padding-1">Ajouter</span>
      </button>

    <h4>Modifier le rôle d'un membre</h4>
    <div
      id="form-members"
      class="ui form"
    >
Timothee P's avatar
Timothee P committed
        aria-describedby="Table des membres du projet"
Florent Lavelle's avatar
Florent Lavelle committed
            <th scope="col">
Florent Lavelle's avatar
Florent Lavelle committed
              <i
                :class="{
                  down: isSortedAsc('member'),
                  up: isSortedDesc('member'),
                }"
                class="icon sort"
Florent Lavelle's avatar
Florent Lavelle committed
                aria-hidden="true"
Florent Lavelle's avatar
Florent Lavelle committed
            <th scope="col">
Florent Lavelle's avatar
Florent Lavelle committed
              <i
                :class="{
                  down: isSortedAsc('role'),
                  up: isSortedDesc('role'),
                }"
                class="icon sort"
Florent Lavelle's avatar
Florent Lavelle committed
                aria-hidden="true"
          <tr
            v-for="member in projectMembers"
            :key="member.username"
          >
Florent Lavelle's avatar
Florent Lavelle committed
              {{ member.user.last_name }} {{ member.user.first_name }}<br><em>{{ member.user.username }}</em>
              <div class="required field online">
                <Dropdown
                  :options="levelOptions"
                  :selected="member.userLevel.name"
                  :selection.sync="member.userLevel"
                  :search="true"
                />
                  class="ui icon button button-hover-red"
                  data-tooltip="Retirer ce membre"
                  @click="removeMember(member)"
Florent Lavelle's avatar
Florent Lavelle committed
                  <i
                    class="times icon"
                    aria-hidden="true"
                  />
      <div class="ui divider" />
      <button
        type="button"
        class="ui teal icon button"
        @click="saveMembers"
      >
Florent Lavelle's avatar
Florent Lavelle committed
        <i
          class="white save icon"
          aria-hidden="true"
        />&nbsp;Enregistrer les changements
import axios from '@/axios-client.js';
Florent Lavelle's avatar
Florent Lavelle committed

import { mapMutations, mapState } from 'vuex';
Florent Lavelle's avatar
Florent Lavelle committed

import Dropdown from '@/components/Dropdown.vue';
  name: 'ProjectMembers',
      projectUsers: [],
        { name: 'Utilisateur connecté', value: 'logged_user' },
        { name: 'Contributeur', value: 'contributor' },
        { name: 'Super Contributeur', value: 'super_contributor' },
        { name: 'Modérateur', value: 'moderator' },
        { name: 'Administrateur projet', value: 'admin' },
Timothee P's avatar
Timothee P committed
        errors: [],
          name: '',
          value: '',
          name: 'Contributeur',
          value: 'contributor',
        column: '',
    ...mapState('projects', ['project']),

    userOptions: function () {
      return this.projectUsers
        .filter((el) => el.userLevel.value === 'logged_user')
          let name = el.user.first_name || '';
          if (el.user.last_name) {
            name = name + ' ' + el.user.last_name;
            name: [name, el.user.username],
    levelOptions: function () {
Timothee P's avatar
Timothee P committed
          (this.project && this.project.moderation ? el : el.value !== 'moderator') &&
          el.value !== 'logged_user'
Timothee P's avatar
Timothee P committed
      return this.projectUsers
        .filter((el) => el.userLevel.value !== 'logged_user')
Timothee P's avatar
Timothee P committed
        .sort((a, b) => {
          if (this.sort.column !== '') {
            if (this.sort.column === 'member') {
              const textA = a.user.username.toUpperCase();
              const textB = b.user.username.toUpperCase();
              if (this.sort.ascending) {
                return textA < textB ? -1 : textA > textB ? 1 : 0;
              } else {
                return textA > textB ? -1 : textA < textB ? 1 : 0;
              }
            } else if (this.sort.column === 'role') {
              const textA = a.userLevel.name.toUpperCase();
              const textB = b.userLevel.name.toUpperCase();
              if (this.sort.ascending) {
                return textA < textB ? -1 : textA > textB ? 1 : 0;
              } else {
                return textA > textB ? -1 : textA < textB ? 1 : 0;
              }
            }
Timothee P's avatar
Timothee P committed
          } else {
            return 0;
          }
        });
  created() {
    if (!this.project) {
Timothee P's avatar
Timothee P committed
      this.$store.dispatch('projects/GET_PROJECT', this.$route.params.slug);
      this.$store.dispatch('projects/GET_PROJECT_INFO', this.$route.params.slug);
    }
    this.populateMembers();
  },

  destroyed() {
    //* allow user to change page if ever stuck on loader
    ...mapMutations([
      'DISPLAY_MESSAGE',
      'DISPLAY_LOADER',
      'DISCARD_LOADER'
    ]),

Timothee P's avatar
Timothee P committed
    validateNewMember() {
      this.newMember.errors = [];
      if (!this.newMember.user.value) {
        this.newMember.errors.push('Veuillez compléter ce champ.');
Timothee P's avatar
Timothee P committed
        return false;
      }
      return true;
    },

    changeUserRole(id, role) {
      const indexOfUser = this.projectUsers.findIndex(
        (el) => el.user.id === id
      );
      //* modify its userLever
      this.projectUsers[indexOfUser].userLevel = role;
    },

Timothee P's avatar
Timothee P committed
      if (this.validateNewMember()) {
        this.changeUserRole(this.newMember.user.value, this.newMember.role);
Timothee P's avatar
Timothee P committed
        //* empty add form
        this.newMember.user = { value: '', name: '' };
Timothee P's avatar
Timothee P committed
      }
    },

    isSortedAsc(column) {
      return this.sort.column === column && this.sort.ascending;
    },
    isSortedDesc(column) {
      return this.sort.column === column && !this.sort.ascending;
    },

    changeSort(column) {
      if (this.sort.column === column) {
        //changer order
        this.sort.ascending = !this.sort.ascending;
      } else {
        this.sort.column = column;
        this.sort.ascending = true;
      }
    removeMember(member) {
      this.changeUserRole(member.user.id, {
        name: 'Utilisateur connecté',
        value: 'logged_user',
    /**
     * Saves the updated members and their roles for a project.
     * Displays a loader while the update is in progress and provides feedback upon completion or error.
     */
Timothee P's avatar
Timothee P committed
    saveMembers() {
      // Display a loader to indicate that the update process is ongoing
      this.DISPLAY_LOADER('Updating project members...');
      // Prepare the data to be sent in the API request
      const data = this.projectUsers.map((member) => {
        return {
          user: member.user,
          level: {
            display: member.userLevel.name,  // Display name of the user level
            codename: member.userLevel.value, // Codename of the user level
      // Make an API request to update the project members
      axios
        .put(
          `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.project.slug}/utilisateurs/`,
          data
        )
          // Check if the response status is 200 (OK)
          if (response.status === 200) {
            // Dispatch an action to update the user status in the top right menu
            this.$store.dispatch('GET_USER_LEVEL_PROJECTS');
            // Display a positive message indicating success
            this.DISPLAY_MESSAGE({ comment: 'Permissions updated successfully', level: 'positive' });
Timothee P's avatar
Timothee P committed
          } else {
            // Display a generic error message if the response status is not 200
            this.DISPLAY_MESSAGE({
              comment: "An error occurred while updating permissions",
              level: 'negative'
            });
          // Hide the loader regardless of the request result
          this.DISCARD_LOADER();
          // Hide the loader if an error occurs
          this.DISCARD_LOADER();
          // Determine the error message to display
          const errorMessage = error.response && error.response.data && error.response.data.error
            ? error.response.data.error
            : "An error occurred while updating permissions";
          // Display the error message
          this.DISPLAY_MESSAGE({
            comment: errorMessage,
            level: 'negative'
          });
          // Log the error to the console for debugging
          console.error(error);
Timothee P's avatar
Timothee P committed
    fetchMembers() {
      // todo: move function to a service
DESPRES Damien's avatar
DESPRES Damien committed
          `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/utilisateurs/`
        .then((response) => response.data)
    populateMembers() {
      this.DISPLAY_LOADER('Récupération des membres en cours...');
      this.fetchMembers().then((members) => {
        this.projectUsers = members.map((el) => {
            userLevel: { name: el.level.display, value: el.level.codename },
</script>

<style>
.padding-1 {
  padding: 0 1em;
}

i.icon.sort:not(.down):not(.up) {
  color: rgb(220, 220, 220);
}