Commit 988b523c authored by Stephane Bouvry's avatar Stephane Bouvry
Browse files

Refonte recherche des activités en cours (en travaux)

parent ce457664
Loading
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -15754,6 +15754,11 @@
        "got": ">=8.0 <12.0"
      }
    },
    "vue-select": {
      "version": "3.20.0",
      "resolved": "https://registry.npmjs.org/vue-select/-/vue-select-3.20.0.tgz",
      "integrity": "sha512-Qau4BzpgAC+/9jM5oTlOrfA81ONdtTFH6PqeSDKvIB56f1F6EbIR8qAotpUxrIiNVppyPFjvVDkyriMfHjWBQA=="
    },
    "vue-style-loader": {
      "version": "4.1.3",
      "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
+3 −0
Original line number Diff line number Diff line
@@ -26,5 +26,8 @@
    "vue-loader": "^15.9.8",
    "vue-resource": "^1.5.3",
    "vue-template-compiler": "^2.6.14"
  },
  "dependencies": {
    "vue-select": "^3.20.0"
  }
}
+18 −13
Original line number Diff line number Diff line
@@ -32,12 +32,10 @@

    <div class="card-content">
      <div class="row metas">
                <div class="col-sm-4">
        <div class="col-sm-12">
          Signature :
          <strong v-if="activity.dateSigned">{{ activity.dateSigned | fullDate }}</strong>
          <strong v-else>Non signé</strong>
                    <br />

          <span v-if="activity.dateStart || activity.dateEnd">
                        Active
                        <span v-if="activity.dateStart">
@@ -47,14 +45,16 @@
                        <span v-if="activity.dateEnd">
                            au <time>{{ activity.dateEnd | fullDate }}</time>
                        </span>
                        <br />
                    </span>

                    Créé le <time>{{ activity.dateCreated | fullDate }}</time><br>
                    Dernière mise à jour : <time>{{ activity.dateUpdated | fullDate }}</time>
          Créé le
          <time>{{ activity.dateCreated | fullDate }}</time>
          Dernière mise à jour :
          <time>{{ activity.dateUpdated | fullDate }}</time>
        </div>

                <div class="col-sm-4">
      </div>
      <div class="row metas" v-if="!compact">
        <div class="col-sm-6">
          <div v-for="persons, role in activity.persons_primary">
            <i :class="'icon-' + role | slugify"></i>{{ role }} :
            <a v-for="person in persons" :href="'/person/show/' +person.id" class="person" :title="person.affectation">
@@ -64,10 +64,11 @@
          </div>
        </div>

                <div class="col-sm-4">
        <div class="col-sm-6">
          <div v-for="organizations, role in activity.organizations_primary">
            <i :class="'icon-' + role | slugify"></i>{{ role }} :
                        <a v-for="organization in organizations" :href="'/organization/show/' +organization.id" class="organization">
            <a v-for="organization in organizations" :href="'/organization/show/' +organization.id"
               class="organization">
              <i :class="'icon-' + (organization.spot == 'activity' ? 'cube' : 'cubes')"></i>
              {{ organization.displayName }}
            </a>
@@ -75,7 +76,7 @@
        </div>
      </div>

            <p class="text-highlight"  v-if="activity.persons">
      <p class="text-highlight" v-if="activity.persons && !compact">
        <i class="icon-user grey"></i>Membres :
        <span v-for="persons, role in activity.persons">
            <span v-for="person in persons" class="person cartouche xs" :title="person.affectation">
@@ -86,7 +87,8 @@
        </span>
      </p>

            <p class="text-highlight" v-if="activity.organizations"><i class="icon-building-filled grey"></i>Partenaires :
      <p class="text-highlight" v-if="activity.organizations && !compact">
        <i class="icon-building-filled grey"></i>Partenaires :
        <span v-for="organizations, role in activity.organizations">
            <span v-for="organization in organizations" class="organization cartouche xs">
                <i :class="'icon-' + (organization.spot == 'activity' ? 'cube' : 'cubes')"></i>
@@ -96,6 +98,7 @@
        </span>
      </p>

      <div class="content-expand" v-if="!compact">
        <div class="text-highlight" v-if="activity.project">
          Project <span><a :href="'/project/show/' + activity.project.id" class="project">
                  <i class="icon-cubes"></i>{{ activity.project.displayName }} </a></span>
@@ -104,12 +107,14 @@
          <i class="icon-attention-1"></i> Cette activité n'a pas de projet
        </div>
      </div>
    </div>
  </article>
</template>
<script>
export default {
  props: {
            activity: { required: true }
    activity: {required: true},
    compact: {type: Boolean, default: false}
  }
}
</script>
 No newline at end of file
+273 −18
Original line number Diff line number Diff line
<template>
  <div>
    <div class="overlay" v-if="error">
      <div class="overlay-content">
        <a href="#" @click="error = ''">Fermer</a>
        {{ error }}
      </div>
    </div>
    <transition name="fade">
      <div class="vue-loader" v-show="loaderMsg">
        <div class="content-loader">
@@ -9,34 +15,153 @@
      </div>
    </transition>

        <oscargrowl />
    <h1>
      <i class="icon-cube"></i>
      {{ title }}
    </h1>

        <form action="" @submit.prevent="handlerSubmit">
            <div class="input-group input-group-lg">
                <input placeholder="Rechercher dans l'intitulé, code PFI...…" class="form-control input-lg" name="q" v-model="search" type="search">

    <hr>

    <hr>
    {{ filters_obj }}

    <form action="">


      <div class="input-group input-group-lg">
        <input placeholder="Rechercher dans l'intitulé, code PFI...…"
               class="form-control input-lg" name="q"
               v-model="search" type="search">
        <span class="input-group-btn">
            <button type="submit" class="btn btn-primary">Rechercher</button>
        </span>
      </div>
      <h3>Critères de recherche</h3>
      <div class="row">
        <div class="col-md-4">
          <h5>Filtres</h5>
          <select class="form-control" @change="handlerSelectFilter" v-model="selecting_filter">
            <option value="">Ajouter un filtre&hellip;</option>
            <option :value="f" v-for="label,f in filters">{{ label }}</option>
          </select>
        </div>
        <div class="col-md-2">
          <h5>Status</h5>
          <super-select :options="status"
                        :name="'st'"
                        @change="updateSelected"
                        v-model="used_status"
                        style="min-width: 250px"/>
        </div>
        <div class="col-md-2">
          <h5>Trier par</h5>
          <select v-model="sorter" name="t" class="form-control">
            <option :value="s" v-for="text, s in sortters">{{ text }}</option>
          </select>
        </div>
        <div class="col-md-2">
          <h5>Ordre</h5>
          <select v-model="direction" name="d" class="form-control">
            <option :value="s" v-for="text, s in directions">{{s}} - {{ text }}</option>
          </select>
        </div>
        <div class="col-md-2">
          <h5>Ignorer si null</h5>

          <label for="ui_vuecompact" class="label-primary">
            Mode compact
            <input id="ui_vuecompact" name="ui_vuecompact" v-model="ui_vuecompact" type="checkbox">
          </label>

        </div>
      </div>

      <hr>

      <section v-for="f in filters_obj">
        <a-s-filter-person v-if="f.type == 'ap'" :type="'ap'"
                           :value1="f.value1" :value2="f.value2"
                           :roles_values="roles_person"
                           :error="f.error"
                           @delete="handlerDeleteFilter(f)"/>

        <a-s-filter-person v-else-if="f.type == 'pm'" :type="'pm'"
                           :value1="f.value1" :value2="f.value2"
                           :multiple="true"
                           :roles_values="roles_person"
                           :error="f.error"
                           @delete="handlerDeleteFilter(f)"/>

        <a-s-filter-organization v-else-if="f.type == 'ao'" :type="'ao'"
                           :value1="f.value1" :value2="f.value2"
                           :roles_values="roles_organizations"
                           :error="f.error"
                           @delete="handlerDeleteFilter(f)"/>

        <a-s-filter-organization v-else-if="f.type == 'so'" :type="'so'"
                                 :label="'N\'impliquant pas'"
                                 :value1="f.value1" :value2="f.value2"
                                 :roles_values="roles_organizations"
                                 :error="f.error"
                                 @delete="handlerDeleteFilter(f)"/>

        <a-s-filter-person v-else-if="f.type == 'sp'" :type="'sp'"
                           :value1="f.value1" :value2="f.value2"
                           :label="'N\'impliquant pas'"
                           :roles_values="roles_person"
                           :error="f.error"
                           @delete="handlerDeleteFilter(f)"/>

        <a-s-filter-select v-else-if="f.type == 'cnt'" :type="'cnt'"
                           :value1="f.value1"
                           :label="'Pays (d\'une organisation)'"

                           :error="f.error"
                           @delete="handlerDeleteFilter(f)"/>

        <div v-else class="card critera">
          non géré {{ f }}
        </div>
      </section>

      <nav class="text-right">
        <button type="reset" class="btn btn-default">
          Réinitialiser le recherche
        </button>
        <button type="submit" class="btn btn-primary">
          Actualiser la recherche
        </button>
      </nav>

    </form>
    <pre>{{ urlSearch }} </pre>

    <section v-if="search !== null">
      <h2 class="text-right">{{ totalResultQuery }} résultat(s)</h2>
      <transition-group name="list" tag="div">
                <activity :activity="activity" v-for="activity in activities" :key="activity.id" />
        <activity :activity="activity" v-for="activity in activities" :key="activity.id" :compact="ui_vuecompact"/>
      </transition-group>
    </section>

    <pre>{{ filters_obj }}</pre>

  </div>
</template>
<script>
import ActivitySearchItem from './ActivitySearchItem.vue';
    import OscarGrowl from './OscarGrowl.vue';
    import OscarBus from './OscarBus.js';
import SuperSelect from './components/SuperSelect.vue';
import PersonAutoCompleter from "./components/PersonAutoCompleter2";
import OrganizationAutoCompleter from "./components/OrganizationAutoCompleter";
import vSelect from 'vue-select';

// Filtres
import ASFilterPerson from "./searchfilters/ASFilterPerson";
import ASFilterOrganization from "./searchfilters/ASFilterOrganization";
import ASFilterSelect from "./searchfilters/ASFilterSelect";

// import OscarGrowl from './OscarGrowl.vue';
// import OscarBus from './OscarBus.js';

/******************************************************************************************************************/
/* ! DEVELOPPEUR
@@ -53,51 +178,166 @@
  props: {
    url: {required: true},
    first: {required: true, typ: Boolean},
            title: { default: "Activités de recherche" }
    title: {default: "Activités de recherche"},
    sortters: {required: true},
    filters: {required: true},
    directions: {required: true},
    direction: { default: 'desc' },
    sorter: { default: 'hit' },
    status: {required: true},
    roles_person: {required: true},
    roles_organizations: {required: true},
    search: { require: false },
    selected_status: { default: [] },
    options_pays: { default: [] },
    used_filters: { require: false, default: []},
    used_status: { require: false, default: []},

    selectedOrganization: null
  },

  components: {
    ASFilterOrganization,
    activity: ActivitySearchItem,
            oscargrowl: OscarGrowl,
    SuperSelect,
    PersonAutoCompleter,
    vSelect,
    OrganizationAutoCompleter,
    ASFilterPerson,
    ASFilterSelect
  },

  data() {
    return {
      loaderMsg: "",
                page: 0,
      page: 1,
      totalPages: 0,
      totalResultQuery: 0,
      previous: null,
                search: null,
                activities: []
      activities: [],
      ui_vuecompact: true,
      filters_obj: [],
      selecting_filter: ""
    }
  },

  computed: {
    displayedFilters(){
      return [];
    },

    urlSearch() {
      // Filtres
      let filters = [];
      this.filters_obj.forEach(f => {
        filters.push('f[]=' +f.type +';' +f.value1 +';' +f.value2);
      })


      return this.url
          + "?q=" + this.search
          + "&p=" + this.page
          + "&t=" + this.sorter
          + "&d=" + this.direction
          + '&st=' + this.used_status
          + '&' +filters.join('&');
    }
  },

  methods: {

    ///////////////////////////////////////////////////////////
    // Capture des interactions
    handlerSelectPerson(dataPerson, filter) {
      console.log(dataPerson, filter);
      filter.value1 = dataPerson.id;
      filter.value1displayed = dataPerson.displayname;
    },

    handlerDeleteFilter(filter) {
      this.filters_obj.splice(this.filters_obj.indexOf(filter), 1);
    },

    handlerSelectPersonRole( role, filter ){
      filter.value2 = role.target.value;
    },

    handlerSelectFilter() {
      this.addNewFilter(this.selecting_filter);
      this.selecting_filter = "";
    },

    handlerSubmit() {
      this.performSearch(this.search, 1, 'Recherche...')
    },

    addNewFilter(filterKey, value1 = "", value2 = "") {
      console.log('Ajout du filtre', filterKey);
      this.filters_obj.push({
        type: filterKey,
        value1: value1,
        value2: value2
      })
    },

    updateSelected(evt) {
      console.log('evt', evt);
    },

    /**
     * Retourne le filtre (objet) en fonction de l'entrée.
     *
     * @param str
     * @return {null|*}
     */
    getFilterByStr( str ){
      for( let i = 0; i<this.filters_obj.length; i++ ){
        if( this.filters_obj[i].input == str ){
          return this.filters_obj[i];
        }
      }
      return null;
    },

    /**
     * Déclenche la recherche.
     *
     * @param what
     * @param page
     * @param msg
     */
    performSearch(what, page, msg) {
      this.loaderMsg = msg;

      this.search = what === null ? '' : what;

                this.$http.get(this.url +"?q=" +this.search +"&p=" +page).then(
      this.$http.get(this.urlSearch).then(
          (ok) => {
            if (ok.body.page == 1) {
              this.activities = [];
            }
            this.activities = this.activities.concat(ok.body.datas.content);
                        this.totalResultQuery = ok.body.totalResultQuery;
            this.totalResultQuery = ok.body.result_total;
            this.totalPages = ok.body.totalPages;
            this.page = ok.body.page;

            ok.body.filters_infos.forEach( info => {
              if( info.error ){
                let f = this.getFilterByStr(info.input);
                if( f ){
                  f.error = info.error;
                }
              }
            });
          },
          (ko) => {
                        OscarBus.message("Impossible de charger le résultat de la recherche !", 'error');
            console.log(ko);
            this.error = "Impossible de charger le résultat de la recherche !";
            this.activities = [];
            this.totalResultQuery = 0;
            this.totalPages = 0;
            this.page = 0;
            if (ko.status == 403) {
              this.error += " Vous avez probablement été déconnecté de l'application"
            }
          }
      ).then(foo => {
        this.loaderMsg = "";
@@ -114,15 +354,30 @@

  mounted() {

    let params = new URLSearchParams(window.location.search);

    // Filtres
    this.used_filters.forEach(filterStr => {
      let spt = filterStr.split(';');
      let obj = {
        type: spt[0],
        value1: spt[1],
        value2: spt[2],
        input: filterStr
      };
      this.filters_obj.push(obj);
    });

    this.handlerSubmit();

    window.onscroll = () => {
      let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight === document.documentElement.offsetHeight;

      if (bottomOfWindow) {
        this.loadNextPage();
      }
    };


  }
}
</script>
+166 −0
Original line number Diff line number Diff line
<template>
  <v-select @search="handlerSearchOrganisation"
            placeholder="Rechercher une structure"
            :options="filteredOptions"
            label="label"
            v-model="value"
            :reduce="item => item.id"
            @input="setSelected"
  >
    <template #list-header>
      <li style="text-align: center">
        <label for="display_closed">
          Afficher les structures fermées
          <input type="checkbox" v-model="displayClosed" id="display_closed" />
        </label>
      </li>
    </template>

    <template #option="{ id,code, shortname, longname, city, country, label, closed, email, phone }"
              class="result-item">
      <div class="result-item" :class="{'organization-closed': closed }">
        <h4 style="margin: 0">
          <code v-if="code">{{ code }}</code>
          <span>
            <strong v-if="shortname">{{ shortname }}</strong>
            <em v-if="longname">{{ longname }}</em>
          </span>
        </h4>
        <div v-if="email || phone" class="infos">
          <span v-if="email"><i class="icon-mail"></i> {{ email }}</span>
          <span v-if="phone"><i class="icon-phone-outline"></i> {{ phone }}</span>
        </div>
        <div v-if="country || city" class="location">
          <i class="icon-location"></i>
          <strong v-if="city">{{ city }}</strong>
          <em v-if="city">{{ country }}</em>
        </div>
      </div>
    </template>
  </v-select>
</template>
<script>

import vSelect from 'vue-select';

export default {

  props: {
    value: {default: null}
  },

  components: {
    vSelect
  },

  data() {
    return {
      // Liste des structures
      options: [],

      lastUpdatedSearch: 0,
      delay: null,
      preloadedValue: false,
      selectedValue: null,
      displayClosed: false,
      last_search: "",
      loading_ref: null
    }
  },


  mounted() {
    // Détection d'un valeur initiale
    if (this.value) {
      this.selectedValue = this.value;
      this.options.push({
        id: this.value,
        label: "Waiting for data"
      })
      this.value = null;
      this.searchOrganization(null, 'id:' + this.selectedValue);
    } else {
      this.preloadedValue = true;
    }
  },

  computed: {
    filteredOptions(){
      let opts = [];
      if( this.displayClosed ){
        return this.options;
      } else {
        this.options.forEach(item => {
          if( !item.closed ){
            opts.push(item);
          }
        });
        return opts;
      }
    }
  },

  methods: {

    /**
     * Selection d'une option.
     *
     * @param selected
     */
    setSelected(selected) {
      this.value = selected;
      this.$emit('change', this.value);
      this.$emit('input', this.value);
    },

    /**
     * Déclenchement de la recherche (à la saisie).
     *
     * @param search
     * @param loading
     */
    handlerSearchOrganisation(search, loading) {
      this.last_search = search;
      this.loading_ref = loading;
      if (search.length) {
        loading(true);
        // Système de retardement Eco+
        let delayFunction = function () {
          this.searchOrganization(loading, search, this);
          this.delay = null;
        }.bind(this);
        if (this.delay != null) {
          clearTimeout(this.delay);
        }
        this.delay = setTimeout(delayFunction, 1000);
      }
    },

    /**
     * Recherche via l'API
     * @param loading
     * @param search
     * @param vm
     */
    searchOrganization(loading, search, vm) {
      let closeOpt = this.displayClosed ? '&active=' : '&active=ON'
      this.$http.get('/organization?l=m&q=' + encodeURI(search)).then(
          ok => {
            if (this.preloadedValue == false) {
              this.preloadedValue = true;
              this.value = this.selectedValue;
              this.options[0].label = ok.data.datas[0].label;
            } else {
              this.options = ok.data.datas;
            }
            if (loading)
              loading(false);
          },
          ko => {
            // TODO
          }
      )
    },
  }
}
</script>
 No newline at end of file
Loading