Loading front/package-lock.json +5 −0 Original line number Diff line number Diff line Loading @@ -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", front/package.json +3 −0 Original line number Diff line number Diff line Loading @@ -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" } } front/src/ActivitySearchItem.vue +18 −13 Original line number Diff line number Diff line Loading @@ -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"> Loading @@ -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"> Loading @@ -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> Loading @@ -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"> Loading @@ -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> Loading @@ -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> Loading @@ -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 front/src/ActivitySearchUi.vue +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"> Loading @@ -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…</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 Loading @@ -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 = ""; Loading @@ -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> front/src/components/OrganizationAutoCompleter.vue 0 → 100644 +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
front/package-lock.json +5 −0 Original line number Diff line number Diff line Loading @@ -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",
front/package.json +3 −0 Original line number Diff line number Diff line Loading @@ -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" } }
front/src/ActivitySearchItem.vue +18 −13 Original line number Diff line number Diff line Loading @@ -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"> Loading @@ -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"> Loading @@ -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> Loading @@ -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"> Loading @@ -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> Loading @@ -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> Loading @@ -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
front/src/ActivitySearchUi.vue +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"> Loading @@ -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…</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 Loading @@ -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 = ""; Loading @@ -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>
front/src/components/OrganizationAutoCompleter.vue 0 → 100644 +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