Skip to content

Commit

Permalink
Merge pull request #88 from Robert-2/feature/36-select-search
Browse files Browse the repository at this point in the history
Ajoute un système de recherche dans le choix des bénéficiaires et techniciens (#36)
  • Loading branch information
polosson authored Jan 12, 2021
2 parents 4098963 + a8b68be commit 4b011a2
Show file tree
Hide file tree
Showing 23 changed files with 366 additions and 172 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Ce projet adhère au principe du [Semantic Versioning](https://semver.org/spec/v
- Améliorations internes de la validation des données.
- Ajoute une page de vue du matériel en détail.
- Utilise des onglets dans la page de vue du matériel.
- Dans l'édition d'événements, la recherche directe des bénéficiaires et techniciens dans le champ multiple permet de tous les retrouver (#36).

## 0.10.2 (2020-11-16)

Expand Down
110 changes: 32 additions & 78 deletions client/src/components/MultipleItem/MultipleItem.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
<template>
<div class="MultipleItem">
<div
v-for="(itemId, index) in itemsIds"
:key="itemId"
v-for="(itemData, index) in notSavedSelectedItems"
:key="itemData.id || `unknown-${index}`"
class="MultipleItem__item FormField"
>
<label class="FormField__label">
{{ $t(label) }} {{ index + 1 }}
</label>
<div class="MultipleItem__value-field">
<span
v-if="fieldOptions.length === 0"
class="MultipleItem__value-field--loading"
>
<i class="fas fa-spin fa-spinner" />
</span>
<span
v-if="fieldOptions.length > 0 && !getItemLabel(itemId)"
v-if="!itemData"
class="MultipleItem__value-field--error"
>
<i class="fas fa-exclamation-triangle" />
{{$t('item-not-found', { item: $t(label) })}}
</span>
<span v-else>{{ getItemLabel(itemId) }}</span>
<span v-else>{{ getItemLabel(itemData) || 'N/A' }}</span>
</div>
<button
class="MultipleItem__item-action-btn danger"
:title="$t('remove-item', { item: $t(label) })"
@click="(e) => { e.preventDefault(); removeItem(itemId); }"
@click="(e) => { e.preventDefault(); removeItem(itemData.id); }"
>
<i class="fas fa-trash-alt" />
</button>
Expand All @@ -41,9 +35,34 @@
</label>
<VueSelect
v-model="newItem"
:filterable="false"
:options="selectableOptions"
@search="handleSearch"
@input="insertNewItem"
/>
>
<template #no-options="{ search }">
<span v-if="search.length === 0">
{{ $t('start-typing-to-search') }}
</span>
<span v-if="search.length > 0 && search.length < minSearchCharacters">
{{ $t(
'type-at-least-count-chars-to-search',
{ count: minSearchCharacters - search.length },
minSearchCharacters - search.length,
) }}
</span>
<div v-if="search.length >= minSearchCharacters">
<p>{{ $t('no-result-found-try-another-search') }}</p>
<router-link
:to="createItemPath"
tag="button"
class="success"
>
{{ $t('create-select-item-label', { label: $t(label) }) }}
</router-link>
</div>
</template>
</VueSelect>
<button
class="MultipleItem__item-action-btn warning"
:title="$t('cancel-add-item', { item: $t(label) })"
Expand All @@ -70,69 +89,4 @@
@import './MultipleItem';
</style>

<script>
import VueSelect from 'vue-select';
export default {
name: 'MultipleItem',
components: { VueSelect },
props: {
label: String,
field: String,
fieldOptions: Array,
initialItemsIds: Array,
},
data() {
const defaultItem = { value: null, label: this.$t('please-choose') };
return {
itemsIds: [...this.initialItemsIds] || [],
askNewItem: false,
newItem: defaultItem,
defaultItem,
};
},
computed: {
selectableOptions() {
return this.fieldOptions.filter(
(option) => !this.itemsIds.includes(option.value),
);
},
},
methods: {
getItemLabel(id) {
const itemOption = this.fieldOptions.find(
(option) => option.value === id,
);
return itemOption ? itemOption.label : '';
},
startAddItem(e) {
e.preventDefault();
this.askNewItem = true;
},
insertNewItem() {
if (!this.newItem || !this.newItem.value) {
return;
}
this.itemsIds.push(this.newItem.value);
this.askNewItem = false;
this.newItem = this.defaultItem;
this.$emit('itemsUpdated', this.itemsIds);
},
cancelNewItem(e) {
e.preventDefault();
this.askNewItem = false;
this.newItem = this.defaultItem;
},
removeItem(id) {
this.itemsIds = this.itemsIds.filter((_id) => _id !== id);
this.$emit('itemsUpdated', this.itemsIds);
},
},
};
</script>
<script src="./index.js"></script>
97 changes: 97 additions & 0 deletions client/src/components/MultipleItem/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import VueSelect from 'vue-select';
import { debounce } from 'debounce';
import { DEBOUNCE_WAIT } from '@/config/constants';

export default {
name: 'MultipleItem',
components: { VueSelect },
props: {
fetchEntity: String,
fetchParams: Object,
label: String,
field: String,
selectedItems: Array,
createItemPath: String,
formatOptions: Function,
getItemLabel: Function,
},
data() {
const defaultItem = { value: null, label: this.$t('please-choose') };

return {
itemsIds: this.selectedItems.map((item) => item.id),
notSavedSelectedItems: [...this.selectedItems],
minSearchCharacters: 2,
askNewItem: false,
fieldOptions: [],
newItem: defaultItem,
defaultItem,
};
},
computed: {
selectableOptions() {
return this.fieldOptions.filter(
(option) => !this.itemsIds.includes(option.value),
);
},
},
methods: {
handleSearch(searchTerm, loading) {
if (searchTerm.length < this.minSearchCharacters) {
return;
}
loading(true);
this.search(loading, searchTerm);
},

// - We're not using arrow function here because we need access to 'this'
// eslint-disable-next-line func-names
search: debounce(function (loading, search) {
const params = { ...this.fetchParams, search, limit: 10 };
this.$http.get(this.fetchEntity, { params })
.then(({ data }) => {
this.fieldOptions = this.formatOptions(data.data);
})
.catch(console.error)
.finally(() => {
loading(false);
});
}, DEBOUNCE_WAIT),

startAddItem(e) {
e.preventDefault();
this.askNewItem = true;
},

insertNewItem() {
if (!this.newItem || !this.newItem.value) {
return;
}

const { value, label } = this.newItem;
this.itemsIds.push(value);
this.notSavedSelectedItems.push({ id: value, label });

this.askNewItem = false;
this.newItem = this.defaultItem;

this.$emit('itemsUpdated', this.itemsIds);
},

cancelNewItem(e) {
e.preventDefault();
this.askNewItem = false;
this.newItem = this.defaultItem;
},

removeItem(id) {
this.itemsIds = this.itemsIds.filter(
(_id) => _id !== id,
);
this.notSavedSelectedItems = this.notSavedSelectedItems.filter(
(item) => item.id !== id,
);
this.$emit('itemsUpdated', this.itemsIds);
},
},
};
7 changes: 7 additions & 0 deletions client/src/locale/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ export default {
'copied-in-clipboard': "Copied in clipboard!",

'please-choose': "Please choose...",
'start-typing-to-search': "Start typing to search...",
'type-at-least-count-chars-to-search': [
"Type again, at least {count} character to search...",
"Type again, at least {count} characters to search...",
],
'no-result-found-try-another-search': "No results. Try another search term.",
'create-select-item-label': "Create a {label}",
'add-item': "Add a {item}",
'remove-item': "Remove this {item}",
'cancel-add-item': "Cancel adding {item}",
Expand Down
7 changes: 7 additions & 0 deletions client/src/locale/fr/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ export default {
'copied-in-clipboard': "Copié dans le presse-papier\u00a0!",

'please-choose': "Veuillez choisir...",
'start-typing-to-search': "Commencez à écrire pour rechercher...",
'type-at-least-count-chars-to-search': [
"Entrez encore au moins {count} lettre pour rechercher...",
"Entrez encore au moins {count} lettres pour rechercher...",
],
'no-result-found-try-another-search': "Aucun résultat. Essayez avec une autre recherche.",
'create-select-item-label': "Créer un {label}",
'add-item': "Ajouter un {item}",
'remove-item': "Enlever ce {item}",
'cancel-add-item': "Annuler l'ajout de {item}",
Expand Down
8 changes: 6 additions & 2 deletions client/src/pages/Event/Step2/Step2.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
<MultipleItem
label="beneficiary"
field="full_name"
:field-options="beneficiariesOptions"
:initial-items-ids="beneficiariesIds"
fetchEntity="persons"
:fetchParams="fetchParams"
:selectedItems="event.beneficiaries"
@itemsUpdated="updateItems"
createItemPath="/beneficiaries/new"
:formatOptions="formatItemOptions"
:getItemLabel="getItemLabel"
/>
<p v-if="showBillingHelp" class="EventStep2__help">
<i class="fas fa-info-circle" />
Expand Down
26 changes: 10 additions & 16 deletions client/src/pages/Event/Step2/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Config from '@/config/globalConfig';
import MultipleItem from '@/components/MultipleItem/MultipleItem.vue';
import getPersonItemLabel from '@/utils/getPersonItemLabel';
import formatOptions from '@/utils/formatOptions';
import EventStore from '../EventStore';

Expand All @@ -10,30 +11,15 @@ export default {
data() {
return {
beneficiariesIds: this.event.beneficiaries.map((benef) => benef.id),
beneficiariesOptions: [],
showBillingHelp: Config.billingMode !== 'none',
fetchParams: { tags: [Config.beneficiaryTagName] },
errors: {},
};
},
mounted() {
this.getEntities();
EventStore.commit('setIsSaved', true);
},
methods: {
getEntities() {
this.$emit('loading');
const params = { tags: [Config.beneficiaryTagName] };
this.$http.get('persons', { params })
.then(({ data }) => {
this.beneficiariesOptions = formatOptions(
data.data,
['first_name', 'last_name', '−', 'company.legal_name', '−', 'locality'],
);
this.$emit('stopLoading');
})
.catch(this.displayError);
},

updateItems(ids) {
this.beneficiariesIds = ids;

Expand All @@ -45,6 +31,14 @@ export default {
EventStore.commit('setIsSaved', listDifference.length === 0);
},

formatItemOptions(data) {
return formatOptions(data, getPersonItemLabel);
},

getItemLabel(itemData) {
return getPersonItemLabel(itemData);
},

saveAndBack(e) {
e.preventDefault();
this.save({ gotoStep: false });
Expand Down
8 changes: 6 additions & 2 deletions client/src/pages/Event/Step3/Step3.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
<MultipleItem
label="technician"
field="full_name"
:field-options="assigneesOptions"
:initial-items-ids="assigneesIds"
fetchEntity="persons"
:fetchParams="fetchParams"
:selectedItems="event.assignees"
@itemsUpdated="updateItems"
createItemPath="/technicians/new"
:formatOptions="formatItemOptions"
:getItemLabel="getItemLabel"
/>
</section>
<section class="Form__actions">
Expand Down
Loading

0 comments on commit 4b011a2

Please sign in to comment.