Loading module/Oscar/src/Oscar/Controller/ProjectController.php +2 −16 Original line number Diff line number Diff line Loading @@ -791,25 +791,11 @@ class ProjectController extends AbstractOscarController if (strlen($search) < 2) { return $this->getResponseBadRequest("Not enough chars (2 required"); } $datas = $this->getProjectService()->search($search)->getQuery()->getResult(); $json = [ 'datas' => [] ]; /** @var Project $data */ foreach ($datas as $data) { $json['datas'][] = [ 'id' => $data->getId(), 'label' => $data->getLabel() . " - " . count($data->getActivities()) . " activité(s)", 'acronym' => $data->getAcronym(), 'description' => $data->getDescription(), ]; } $datas = $this->getProjectService()->search($search); $view = new JsonModel(); $view->setVariables($json); $view->setVariables($datas); return $view; } Loading module/Oscar/src/Oscar/Service/ProjectService.php +26 −6 Original line number Diff line number Diff line Loading @@ -413,13 +413,33 @@ class ProjectService implements UseServiceContainer */ public function search($search) { $params = [ 'q' => $search, 'page' => 1, 'rbp' => 50, 'sort' => 'hit', 'direction' => 'desc', 'filters' => [], 'project' => '1', 'restricted_ids' => false, 'exportable' => false, ]; // IDS des PROJETS $idsProject = $this->getProjectGrantService()->searchProject($search); $idsProjectsEmpty = $this->getProjectRepository()->getEmptyIds($search); $idsProject = array_merge($idsProject, $idsProjectsEmpty); return $qb = $this->getBaseQuery() ->where('p.id IN (:ids)') ->setParameter('ids', $idsProject); $datas = $this->getProjectGrantService()->search2($params); return $datas; // $idsProject = $this->getProjectGrantService()->searchProject($search); // $idsProjectsEmpty = $this->getProjectRepository()->getEmptyIds($search); // $idsProject = array_values(array_unique(array_merge($idsProject, $idsProjectsEmpty))); // // // Evite un IN() vide qui génère une erreur SQL (500) quand aucun projet ne correspond. // if (count($idsProject) === 0) { // $idsProject = [0]; // } // // return $qb = $this->getBaseQuery() // ->where('p.id IN (:ids)') // ->setParameter('ids', $idsProject); } /** Loading module/Oscar/view/oscar/project/project-selector.phtml +1 −1 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ }, processResults: function (data, page) { console.log(data); return {results: data.datas}; return {results: data.projects}; }, cache: true }, Loading ui/src/components/OrganizationAutoComplete.vue +219 −316 Original line number Diff line number Diff line <template> <div class="oscar-selector organization-selector" @click="handlerClick" @mouseleave="handlerMouseLeave" @mouseenter="handlerMouseEnter"> <div class="oscar-selector organization-selector"> <div class="input-group" style="position: relative;"> <div v-if="error" class="displayed-value text-danger" style=""> <i class="icon-attention-1" /> {{ error }} <i @click="error = ''" class="icon-cancel-circled-outline button-cancel-value"></i> </div> <div v-if="displayValue" class="displayed-value" style=""> {{ selectedLabel }} <i v-if="selectedValue" @click="handlerUnselect" class="icon-cancel-circled-outline button-cancel-value"></i> </div> <span class="input-group-addon"> <div class="autocomplete-with-left-icon"> <span class="autocomplete-left-icon"> <i v-show="loading" class="icon-spinner animate-spin"></i> <i v-show="!loading" class="icon-building-filled"></i> </span> <input type="text" v-model="searchFor" @keyup="handlerKeyUp" placeholder="Rechercher une organisation..." class="form-control"/> </div> <div class="options" v-show="showSelector && options.length"> <header> Résultat(s) : {{ options.length }} / <AutoComplete v-model="selectedOrganization" :suggestions="suggestions" optionLabel="displayLabel" dropdown forceSelection showClear class="form-control-autocomplete organization-autocomplete" placeholder="Rechercher une organisation..." @complete="handlerComplete" @item-select="handlerItemSelect" @clear="handlerClear" > <template #header> <div class="options-header" v-if="suggestions.length || error"> <span v-if="!error">Resultat(s) : {{ suggestions.length }}</span> <label for="hidder"> Afficher les structures fermées Afficher les structures fermees <input type="checkbox" id="hidder" value="on" v-model="hideOff" @change="handlerChangeHide" /> </label> </header> <div class="option" v-for="o, i in optionsFiltered" @mouseover="highlightedIndex = i" @click.prevent.stop="handlerSelectIndex(o.id)" @mousedown.prevent.stop="handlerSelectIndex(o.id)" :id="'item_'+i" :class="{ 'active': i == highlightedIndex, 'selected': o.id == selectedValue, 'closed': o.close }"> </div> </template> <template #option="slotProps"> <div class="option" :class="{ closed: slotProps.option.close === true }"> <div class="option-title"> <em class="cartouche code info"> {{ o.code }} {{ slotProps.option.code }} </em> <span class="fullname"> <strong> {{ o.shortname }} </strong> <em> {{ o.longname }} </em> <i class="icon-block" v-if="slotProps.option.close"></i> <strong>{{ slotProps.option.shortname }}</strong> <em>{{ slotProps.option.longname }}</em> </span> <span v-if="o.typeorg"> ({{o.typeorg}}) <span v-if="slotProps.option.typeorg"> ({{ slotProps.option.typeorg }}) </span> </div> <div class="option-infos location cartouche xs" v-if="o.city || o.country"> <div class="option-infos location cartouche xs" v-if="slotProps.option.city || slotProps.option.country"> <i class="icon-location"></i> <span class="location-segment city" v-if="o.city"> {{ o.city }} <span class="location-segment city" v-if="slotProps.option.city"> {{ slotProps.option.city }} </span> <span class="location-segment country" v-if="o.country"> {{ o.country }} <span class="location-segment country" v-if="slotProps.option.country"> {{ slotProps.option.country }} </span> </div> </div> </template> </AutoComplete> </div> </div> <div v-if="error" class="displayed-value text-danger"> <i class="icon-attention-1" /> {{ error }} <i @click="error = ''" class="icon-cancel-circled-outline button-cancel-value"></i> </div> </div> </template> <script> <script> import axios from "axios"; import AutoComplete from "primevue/autocomplete"; export default { components: { AutoComplete, }, props: { value: { default: null }, url: { default: '/organization', type: String, required: true }, url: { default: "/organization", type: String, required: true }, }, emits: ['update:value'], components: { }, emits: ["update:value", "change", "input"], data() { return { // Liste des structures options: [], lastUpdatedSearch: 0, delay: null, preloadedValue: false, selectedValue: null, selectedLabel : "", displayClosed: false, showSelector: false, searchFor: "", latency: null, highlightedIndex: null, selectedOrganization: null, suggestions: [], hideOff: false, loading: false, inside: false, displayValue: false, error: "", hideOff: false } lastQuery: "", }; }, computed: { filteredOptions(){ let opts = []; if( this.displayClosed ){ return this.options; } else { this.options.forEach(item => { if( !item.closed ){ opts.push(item); watch: { value: { immediate: true, handler(newValue) { if (!newValue) { this.selectedOrganization = null; return; } }); return opts; if (this.selectedOrganization && String(this.selectedOrganization.id) === String(newValue)) { return; } this.loadSelectedById(newValue); }, optionsFiltered(){ if( this.hideOff ){ return this.options; } else { let output = []; this.options.forEach(i => { if( !i.closed ){ output.push(i); } }); return output; } } }, mounted() { // Détection d'un valeur initiale document.addEventListener('click', this.handlerGlobalClick, true); 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; } }, // todo : unmount methods: { handlerChangeHide(){ console.log("Permuter ouvert/fermé"); this.search(); }, handlerGlobalClick(evt){ if( !this.inside ){ this.showSelector = false; this.displayValue = true; } else { this.displayValue = false; this.showSelector = true; } }, handlerMouseLeave(){ this.inside = false; }, handlerMouseEnter(){ this.inside = true; }, handlerClick(e){ this.displayValue = false; }, handlerUnselect(){ this.selectedValue = null; this.selectedLabel = ""; this.showSelector = false; this.displayValue = false; this.$emit('change', null ); this.$emit('update:value', null); this.$emit('input', null); mapOrganization(org) { const code = org.code || ""; const shortname = org.shortname || org.fullname || ""; return { ...org, displayLabel: `${code} ${shortname}`.trim(), }; }, handlerSelectIndex(id){ console.log(`handlerSelectIndex(${id})`); this.options.forEach(i => { if( i.id == id ){ this.selectedValue = i.id; this.selectedLabel = i.code +" " + i.shortname; this.showSelector = false; this.displayValue = true; handlerChangeHide() { if (this.lastQuery && this.lastQuery.length > 1) { this.search(this.lastQuery); } }); this.$emit('change', { id: this.selectedValue, label: this.selectedLabel} ); this.$emit('update:value', this.selectedValue); this.$emit('input', this.selectedValue); }, handlerSelectPrev( scroll = false ){ if( this.highlightedIndex == 0 ){ this.showSelector = false; } if( this.highlightedIndex > 0 ){ if(!this.showSelector) this.showSelector = true; this.highlightedIndex--; } if( scroll == true ){ let itemId = '#item_' + this.highlightedIndex; let item = this.$el.querySelector(itemId); let el = this.$el.querySelectorAll('.options')[0]; el.scrollTop = item.offsetTop; console.log("SCROLL", el.scrollTop); console.log("ITEM", itemId, item); handlerComplete(evt) { const query = (evt && evt.query ? evt.query : "").trim(); this.lastQuery = query; if (query.length < 2) { this.suggestions = []; return; } this.search(query); }, handlerSelectNext( scroll = false ){ if(!this.showSelector) { this.showSelector = true; } else { if( this.highlightedIndex < this.options.length - 1 ){ this.highlightedIndex++; } } if( scroll == true ){ let itemId = '#item_' + this.highlightedIndex; let item = this.$el.querySelector(itemId); let el = this.$el.querySelectorAll('.options')[0]; el.scrollTop = item.offsetTop; console.log("SCROLL", el.scrollTop); console.log("ITEM", itemId, item); handlerItemSelect(evt) { const selected = evt && evt.value ? evt.value : null; if (!selected) { return; } this.selectedOrganization = selected; const label = selected.displayLabel || `${selected.code || ""} ${selected.shortname || ""}`.trim(); this.$emit("change", { id: selected.id, label }); this.$emit("update:value", selected.id); this.$emit("input", selected.id); }, updateSelectedDate(){ }, handlerKeyUp(e){ switch(e.code){ case "ArrowUp": this.handlerSelectPrev(true); break; case "ArrowDown": this.handlerSelectNext(true); break; case "ArrowLeft": case "ArrowRight": break; case "Enter": e.preventDefault(); this.handlerSelectIndex(this.highlightedIndex); break; default: if( this.searchFor && this.searchFor.length > 1 ){ this.handlerChange(); } } handlerClear() { this.selectedOrganization = null; this.$emit("change", null); this.$emit("update:value", null); this.$emit("input", null); }, handlerChange(e){ // Système de retardement Eco+ if (this.latency != null) { clearTimeout(this.latency); } let delayFunction = function () { this.search(); clearTimeout(this.latency); }.bind(this); this.latency = setTimeout(delayFunction, 1000); loadSelectedById(id) { this.loading = true; this.error = ""; const params = new URLSearchParams({ q: `id:${id}`, active: "all", f: "json", }); axios .get(`${this.url}?${params.toString()}`) .then((ok) => { const organizations = (ok.data && ok.data.organizations) || []; const mapped = organizations.map(this.mapOrganization); const found = mapped.find((o) => String(o.id) === String(id)); this.selectedOrganization = found || null; }) .catch((ko) => { this.error = this.errorMessageFromResponse(ko); }) .finally(() => { this.loading = false; }); }, search(){ search(query) { this.loading = true; this.showSelector = false; this.error = ""; const params = { //'active': 1, 'q': this.searchFor ?? "", 'active': this.hideOff ? 'all' : '1', 'f': 'json' q: query, active: this.hideOff ? "all" : "1", f: "json", }; let urlBuilder = new URLSearchParams(params); let url = this.url + "?" + urlBuilder.toString(); axios.get(url).then( ok => { this.options = ok.data.organizations; if( this.options.length > 0 ){ this.highlightedIndex = 0; this.showSelector = true; } const urlBuilder = new URLSearchParams(params); axios .get(`${this.url}?${urlBuilder.toString()}`) .then((ok) => { this.suggestions = ((ok.data && ok.data.organizations) || []).map(this.mapOrganization); }) .catch((ko) => { this.suggestions = []; this.error = this.errorMessageFromResponse(ko); }) .finally(() => { this.loading = false; }); }, ko => { console.log(ko); switch(ko.status){ errorMessageFromResponse(ko) { const status = ko && ko.response ? ko.response.status : null; switch (status) { case 401: case 403: this.error = "Vous avez été déconnecté (actualisé votre page pour vous reconnecter)"; break; return "Vous avez ete deconnecte (actualisez la page pour vous reconnecter)"; case 500: this.error = "La recherche a provoqué une erreur"; break; return "La recherche a provoque une erreur"; default: this.error = "Un problème inconnu est survenu"; } return "Un probleme inconnu est survenu"; } ).then(foo => { this.loading = false; }) }, /** * Selection d'une option. * * @param selected */ setSelected(selected) { console.log("setSelected", selected); this.value = selected; this.$emit('change', this.value); //this.$emit('input', this.value); }, }; </script> /** * Déclenchement de la recherche (à la saisie). * * @param search * @param loading */ handlerSearchOrganisation(search, 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); <style scoped> .form-control-autocomplete { width: 100%; } this.delay = setTimeout(delayFunction, 1000); .autocomplete-with-left-icon { position: relative; width: 100%; } .autocomplete-left-icon { position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); z-index: 2; color: var(--bs-gray-700); pointer-events: none; } :deep(.organization-autocomplete .p-autocomplete-input) { padding-left: 2.25rem; } :global(.p-autocomplete-list .p-autocomplete-option) { margin: 0; padding: 0; } </script> No newline at end of file .options-header { margin-top: 0.5rem; display: flex; justify-content: space-between; align-items: center; gap: 1rem; } .option { width: 100%; border-bottom: thin solid var(--bs-gray-300); margin: 0; padding: .25em 0; } .option.closed { /*background: rgba(193, 210, 210, 0.5);*/ } .option.closed .fullname, .option.closed .code { text-decoration: line-through; } </style> ui/src/components/PersonAutoCompleter.vue +226 −132 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
module/Oscar/src/Oscar/Controller/ProjectController.php +2 −16 Original line number Diff line number Diff line Loading @@ -791,25 +791,11 @@ class ProjectController extends AbstractOscarController if (strlen($search) < 2) { return $this->getResponseBadRequest("Not enough chars (2 required"); } $datas = $this->getProjectService()->search($search)->getQuery()->getResult(); $json = [ 'datas' => [] ]; /** @var Project $data */ foreach ($datas as $data) { $json['datas'][] = [ 'id' => $data->getId(), 'label' => $data->getLabel() . " - " . count($data->getActivities()) . " activité(s)", 'acronym' => $data->getAcronym(), 'description' => $data->getDescription(), ]; } $datas = $this->getProjectService()->search($search); $view = new JsonModel(); $view->setVariables($json); $view->setVariables($datas); return $view; } Loading
module/Oscar/src/Oscar/Service/ProjectService.php +26 −6 Original line number Diff line number Diff line Loading @@ -413,13 +413,33 @@ class ProjectService implements UseServiceContainer */ public function search($search) { $params = [ 'q' => $search, 'page' => 1, 'rbp' => 50, 'sort' => 'hit', 'direction' => 'desc', 'filters' => [], 'project' => '1', 'restricted_ids' => false, 'exportable' => false, ]; // IDS des PROJETS $idsProject = $this->getProjectGrantService()->searchProject($search); $idsProjectsEmpty = $this->getProjectRepository()->getEmptyIds($search); $idsProject = array_merge($idsProject, $idsProjectsEmpty); return $qb = $this->getBaseQuery() ->where('p.id IN (:ids)') ->setParameter('ids', $idsProject); $datas = $this->getProjectGrantService()->search2($params); return $datas; // $idsProject = $this->getProjectGrantService()->searchProject($search); // $idsProjectsEmpty = $this->getProjectRepository()->getEmptyIds($search); // $idsProject = array_values(array_unique(array_merge($idsProject, $idsProjectsEmpty))); // // // Evite un IN() vide qui génère une erreur SQL (500) quand aucun projet ne correspond. // if (count($idsProject) === 0) { // $idsProject = [0]; // } // // return $qb = $this->getBaseQuery() // ->where('p.id IN (:ids)') // ->setParameter('ids', $idsProject); } /** Loading
module/Oscar/view/oscar/project/project-selector.phtml +1 −1 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ }, processResults: function (data, page) { console.log(data); return {results: data.datas}; return {results: data.projects}; }, cache: true }, Loading
ui/src/components/OrganizationAutoComplete.vue +219 −316 Original line number Diff line number Diff line <template> <div class="oscar-selector organization-selector" @click="handlerClick" @mouseleave="handlerMouseLeave" @mouseenter="handlerMouseEnter"> <div class="oscar-selector organization-selector"> <div class="input-group" style="position: relative;"> <div v-if="error" class="displayed-value text-danger" style=""> <i class="icon-attention-1" /> {{ error }} <i @click="error = ''" class="icon-cancel-circled-outline button-cancel-value"></i> </div> <div v-if="displayValue" class="displayed-value" style=""> {{ selectedLabel }} <i v-if="selectedValue" @click="handlerUnselect" class="icon-cancel-circled-outline button-cancel-value"></i> </div> <span class="input-group-addon"> <div class="autocomplete-with-left-icon"> <span class="autocomplete-left-icon"> <i v-show="loading" class="icon-spinner animate-spin"></i> <i v-show="!loading" class="icon-building-filled"></i> </span> <input type="text" v-model="searchFor" @keyup="handlerKeyUp" placeholder="Rechercher une organisation..." class="form-control"/> </div> <div class="options" v-show="showSelector && options.length"> <header> Résultat(s) : {{ options.length }} / <AutoComplete v-model="selectedOrganization" :suggestions="suggestions" optionLabel="displayLabel" dropdown forceSelection showClear class="form-control-autocomplete organization-autocomplete" placeholder="Rechercher une organisation..." @complete="handlerComplete" @item-select="handlerItemSelect" @clear="handlerClear" > <template #header> <div class="options-header" v-if="suggestions.length || error"> <span v-if="!error">Resultat(s) : {{ suggestions.length }}</span> <label for="hidder"> Afficher les structures fermées Afficher les structures fermees <input type="checkbox" id="hidder" value="on" v-model="hideOff" @change="handlerChangeHide" /> </label> </header> <div class="option" v-for="o, i in optionsFiltered" @mouseover="highlightedIndex = i" @click.prevent.stop="handlerSelectIndex(o.id)" @mousedown.prevent.stop="handlerSelectIndex(o.id)" :id="'item_'+i" :class="{ 'active': i == highlightedIndex, 'selected': o.id == selectedValue, 'closed': o.close }"> </div> </template> <template #option="slotProps"> <div class="option" :class="{ closed: slotProps.option.close === true }"> <div class="option-title"> <em class="cartouche code info"> {{ o.code }} {{ slotProps.option.code }} </em> <span class="fullname"> <strong> {{ o.shortname }} </strong> <em> {{ o.longname }} </em> <i class="icon-block" v-if="slotProps.option.close"></i> <strong>{{ slotProps.option.shortname }}</strong> <em>{{ slotProps.option.longname }}</em> </span> <span v-if="o.typeorg"> ({{o.typeorg}}) <span v-if="slotProps.option.typeorg"> ({{ slotProps.option.typeorg }}) </span> </div> <div class="option-infos location cartouche xs" v-if="o.city || o.country"> <div class="option-infos location cartouche xs" v-if="slotProps.option.city || slotProps.option.country"> <i class="icon-location"></i> <span class="location-segment city" v-if="o.city"> {{ o.city }} <span class="location-segment city" v-if="slotProps.option.city"> {{ slotProps.option.city }} </span> <span class="location-segment country" v-if="o.country"> {{ o.country }} <span class="location-segment country" v-if="slotProps.option.country"> {{ slotProps.option.country }} </span> </div> </div> </template> </AutoComplete> </div> </div> <div v-if="error" class="displayed-value text-danger"> <i class="icon-attention-1" /> {{ error }} <i @click="error = ''" class="icon-cancel-circled-outline button-cancel-value"></i> </div> </div> </template> <script> <script> import axios from "axios"; import AutoComplete from "primevue/autocomplete"; export default { components: { AutoComplete, }, props: { value: { default: null }, url: { default: '/organization', type: String, required: true }, url: { default: "/organization", type: String, required: true }, }, emits: ['update:value'], components: { }, emits: ["update:value", "change", "input"], data() { return { // Liste des structures options: [], lastUpdatedSearch: 0, delay: null, preloadedValue: false, selectedValue: null, selectedLabel : "", displayClosed: false, showSelector: false, searchFor: "", latency: null, highlightedIndex: null, selectedOrganization: null, suggestions: [], hideOff: false, loading: false, inside: false, displayValue: false, error: "", hideOff: false } lastQuery: "", }; }, computed: { filteredOptions(){ let opts = []; if( this.displayClosed ){ return this.options; } else { this.options.forEach(item => { if( !item.closed ){ opts.push(item); watch: { value: { immediate: true, handler(newValue) { if (!newValue) { this.selectedOrganization = null; return; } }); return opts; if (this.selectedOrganization && String(this.selectedOrganization.id) === String(newValue)) { return; } this.loadSelectedById(newValue); }, optionsFiltered(){ if( this.hideOff ){ return this.options; } else { let output = []; this.options.forEach(i => { if( !i.closed ){ output.push(i); } }); return output; } } }, mounted() { // Détection d'un valeur initiale document.addEventListener('click', this.handlerGlobalClick, true); 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; } }, // todo : unmount methods: { handlerChangeHide(){ console.log("Permuter ouvert/fermé"); this.search(); }, handlerGlobalClick(evt){ if( !this.inside ){ this.showSelector = false; this.displayValue = true; } else { this.displayValue = false; this.showSelector = true; } }, handlerMouseLeave(){ this.inside = false; }, handlerMouseEnter(){ this.inside = true; }, handlerClick(e){ this.displayValue = false; }, handlerUnselect(){ this.selectedValue = null; this.selectedLabel = ""; this.showSelector = false; this.displayValue = false; this.$emit('change', null ); this.$emit('update:value', null); this.$emit('input', null); mapOrganization(org) { const code = org.code || ""; const shortname = org.shortname || org.fullname || ""; return { ...org, displayLabel: `${code} ${shortname}`.trim(), }; }, handlerSelectIndex(id){ console.log(`handlerSelectIndex(${id})`); this.options.forEach(i => { if( i.id == id ){ this.selectedValue = i.id; this.selectedLabel = i.code +" " + i.shortname; this.showSelector = false; this.displayValue = true; handlerChangeHide() { if (this.lastQuery && this.lastQuery.length > 1) { this.search(this.lastQuery); } }); this.$emit('change', { id: this.selectedValue, label: this.selectedLabel} ); this.$emit('update:value', this.selectedValue); this.$emit('input', this.selectedValue); }, handlerSelectPrev( scroll = false ){ if( this.highlightedIndex == 0 ){ this.showSelector = false; } if( this.highlightedIndex > 0 ){ if(!this.showSelector) this.showSelector = true; this.highlightedIndex--; } if( scroll == true ){ let itemId = '#item_' + this.highlightedIndex; let item = this.$el.querySelector(itemId); let el = this.$el.querySelectorAll('.options')[0]; el.scrollTop = item.offsetTop; console.log("SCROLL", el.scrollTop); console.log("ITEM", itemId, item); handlerComplete(evt) { const query = (evt && evt.query ? evt.query : "").trim(); this.lastQuery = query; if (query.length < 2) { this.suggestions = []; return; } this.search(query); }, handlerSelectNext( scroll = false ){ if(!this.showSelector) { this.showSelector = true; } else { if( this.highlightedIndex < this.options.length - 1 ){ this.highlightedIndex++; } } if( scroll == true ){ let itemId = '#item_' + this.highlightedIndex; let item = this.$el.querySelector(itemId); let el = this.$el.querySelectorAll('.options')[0]; el.scrollTop = item.offsetTop; console.log("SCROLL", el.scrollTop); console.log("ITEM", itemId, item); handlerItemSelect(evt) { const selected = evt && evt.value ? evt.value : null; if (!selected) { return; } this.selectedOrganization = selected; const label = selected.displayLabel || `${selected.code || ""} ${selected.shortname || ""}`.trim(); this.$emit("change", { id: selected.id, label }); this.$emit("update:value", selected.id); this.$emit("input", selected.id); }, updateSelectedDate(){ }, handlerKeyUp(e){ switch(e.code){ case "ArrowUp": this.handlerSelectPrev(true); break; case "ArrowDown": this.handlerSelectNext(true); break; case "ArrowLeft": case "ArrowRight": break; case "Enter": e.preventDefault(); this.handlerSelectIndex(this.highlightedIndex); break; default: if( this.searchFor && this.searchFor.length > 1 ){ this.handlerChange(); } } handlerClear() { this.selectedOrganization = null; this.$emit("change", null); this.$emit("update:value", null); this.$emit("input", null); }, handlerChange(e){ // Système de retardement Eco+ if (this.latency != null) { clearTimeout(this.latency); } let delayFunction = function () { this.search(); clearTimeout(this.latency); }.bind(this); this.latency = setTimeout(delayFunction, 1000); loadSelectedById(id) { this.loading = true; this.error = ""; const params = new URLSearchParams({ q: `id:${id}`, active: "all", f: "json", }); axios .get(`${this.url}?${params.toString()}`) .then((ok) => { const organizations = (ok.data && ok.data.organizations) || []; const mapped = organizations.map(this.mapOrganization); const found = mapped.find((o) => String(o.id) === String(id)); this.selectedOrganization = found || null; }) .catch((ko) => { this.error = this.errorMessageFromResponse(ko); }) .finally(() => { this.loading = false; }); }, search(){ search(query) { this.loading = true; this.showSelector = false; this.error = ""; const params = { //'active': 1, 'q': this.searchFor ?? "", 'active': this.hideOff ? 'all' : '1', 'f': 'json' q: query, active: this.hideOff ? "all" : "1", f: "json", }; let urlBuilder = new URLSearchParams(params); let url = this.url + "?" + urlBuilder.toString(); axios.get(url).then( ok => { this.options = ok.data.organizations; if( this.options.length > 0 ){ this.highlightedIndex = 0; this.showSelector = true; } const urlBuilder = new URLSearchParams(params); axios .get(`${this.url}?${urlBuilder.toString()}`) .then((ok) => { this.suggestions = ((ok.data && ok.data.organizations) || []).map(this.mapOrganization); }) .catch((ko) => { this.suggestions = []; this.error = this.errorMessageFromResponse(ko); }) .finally(() => { this.loading = false; }); }, ko => { console.log(ko); switch(ko.status){ errorMessageFromResponse(ko) { const status = ko && ko.response ? ko.response.status : null; switch (status) { case 401: case 403: this.error = "Vous avez été déconnecté (actualisé votre page pour vous reconnecter)"; break; return "Vous avez ete deconnecte (actualisez la page pour vous reconnecter)"; case 500: this.error = "La recherche a provoqué une erreur"; break; return "La recherche a provoque une erreur"; default: this.error = "Un problème inconnu est survenu"; } return "Un probleme inconnu est survenu"; } ).then(foo => { this.loading = false; }) }, /** * Selection d'une option. * * @param selected */ setSelected(selected) { console.log("setSelected", selected); this.value = selected; this.$emit('change', this.value); //this.$emit('input', this.value); }, }; </script> /** * Déclenchement de la recherche (à la saisie). * * @param search * @param loading */ handlerSearchOrganisation(search, 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); <style scoped> .form-control-autocomplete { width: 100%; } this.delay = setTimeout(delayFunction, 1000); .autocomplete-with-left-icon { position: relative; width: 100%; } .autocomplete-left-icon { position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%); z-index: 2; color: var(--bs-gray-700); pointer-events: none; } :deep(.organization-autocomplete .p-autocomplete-input) { padding-left: 2.25rem; } :global(.p-autocomplete-list .p-autocomplete-option) { margin: 0; padding: 0; } </script> No newline at end of file .options-header { margin-top: 0.5rem; display: flex; justify-content: space-between; align-items: center; gap: 1rem; } .option { width: 100%; border-bottom: thin solid var(--bs-gray-300); margin: 0; padding: .25em 0; } .option.closed { /*background: rgba(193, 210, 210, 0.5);*/ } .option.closed .fullname, .option.closed .code { text-decoration: line-through; } </style>
ui/src/components/PersonAutoCompleter.vue +226 −132 File changed.Preview size limit exceeded, changes collapsed. Show changes