|
|
@@ -1,5 +1,5 @@
|
|
|
<!--
|
|
|
-Champs autocomplete dédié à la recherche des access d'une structure
|
|
|
+Champs autocomplete dédié à la recherche des Accesses d'une structure
|
|
|
|
|
|
@see https://vuetifyjs.com/en/components/autocompletes/#usage
|
|
|
-->
|
|
|
@@ -28,18 +28,23 @@ Champs autocomplete dédié à la recherche des access d'une structure
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import type { PropType } from '@vue/runtime-core'
|
|
|
-import { computed } from '@vue/reactivity'
|
|
|
import type { ComputedRef, Ref } from '@vue/reactivity'
|
|
|
-import type { AnyJson, AssociativeArray } from '~/types/data'
|
|
|
+import { computed } from '@vue/reactivity'
|
|
|
+import type { AssociativeArray } from '~/types/data'
|
|
|
import { useEntityFetch } from '~/composables/data/useEntityFetch'
|
|
|
import Access from '~/models/Access/Access'
|
|
|
-import { useEntityManager } from '~/composables/data/useEntityManager'
|
|
|
-import ArrayUtils from '~/services/utils/arrayUtils'
|
|
|
import * as _ from 'lodash-es'
|
|
|
+import Query from '~/services/data/Query'
|
|
|
+import OrderBy from '~/services/data/Filters/OrderBy'
|
|
|
+import {ORDER_BY_DIRECTION, SEARCH_STRATEGY} from '~/types/enum/data'
|
|
|
+import PageFilter from '~/services/data/Filters/PageFilter';
|
|
|
+import InArrayFilter from '~/services/data/Filters/InArrayFilter';
|
|
|
+import SearchFilter from '~/services/data/Filters/SearchFilter';
|
|
|
+import UserSearchItem from '~/models/Custom/Search/UserSearchItem';
|
|
|
|
|
|
const props = defineProps({
|
|
|
/**
|
|
|
- * v-model
|
|
|
+ * v-model, ici les ids des Access sélectionnés
|
|
|
*/
|
|
|
modelValue: {
|
|
|
type: [Object, Array],
|
|
|
@@ -48,6 +53,7 @@ const props = defineProps({
|
|
|
},
|
|
|
/**
|
|
|
* Filtres à transmettre à la source de données
|
|
|
+ * TODO: voir si à adapter maintenant que les filtres sont des objets Query
|
|
|
*/
|
|
|
filters: {
|
|
|
type: Object as PropType<Ref<AssociativeArray>>,
|
|
|
@@ -126,101 +132,87 @@ const props = defineProps({
|
|
|
/**
|
|
|
* Element de la liste autocomplete
|
|
|
*/
|
|
|
-interface AccessListItem {
|
|
|
+interface UserListItem {
|
|
|
id: number | string
|
|
|
title: string
|
|
|
}
|
|
|
|
|
|
const { fetchCollection } = useEntityFetch()
|
|
|
-const { em } = useEntityManager()
|
|
|
const i18n = useI18n()
|
|
|
|
|
|
/**
|
|
|
* Génère un AccessListItem à partir d'un Access
|
|
|
* @param access
|
|
|
*/
|
|
|
-const accessToItem = (access: Access): AccessListItem => {
|
|
|
+const accessToItem = (userSearchItem: UserSearchItem): UserListItem => {
|
|
|
return {
|
|
|
- id: access.id,
|
|
|
- title: access.person
|
|
|
- ? `${access.person.name} ${access.person.givenName}`
|
|
|
- : i18n.t('unknown'),
|
|
|
+ id: userSearchItem.id,
|
|
|
+ title: userSearchItem.fullName ? userSearchItem.fullName : `(${i18n.t('missing_name')})`,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const initialized: Ref<boolean> = ref(false)
|
|
|
-
|
|
|
/**
|
|
|
* Saisie de l'utilisateur utilisée pour filtrer la recherche
|
|
|
*/
|
|
|
const nameFilter: Ref<string | null> = ref(null)
|
|
|
|
|
|
-/**
|
|
|
- * Query transmise à l'API lors des changements de filtre de recherche
|
|
|
- */
|
|
|
-const query: ComputedRef<AnyJson> = computed(() => {
|
|
|
- let q: AnyJson = { 'groups[]': 'access_people_ref', 'order[name]': 'asc' }
|
|
|
-
|
|
|
- if (!initialized.value && props.modelValue) {
|
|
|
- if (Array.isArray(props.modelValue) && props.modelValue.length > 0) {
|
|
|
- q['id[in]'] = props.modelValue.join(',')
|
|
|
- } else {
|
|
|
- q['id[in]'] = props.modelValue
|
|
|
- }
|
|
|
- return q
|
|
|
+const activeIds = computed(() => {
|
|
|
+ if (Array.isArray(props.modelValue)) {
|
|
|
+ return props.modelValue
|
|
|
}
|
|
|
-
|
|
|
- if (nameFilter.value) {
|
|
|
- q['fullname'] = nameFilter.value
|
|
|
+ if (props.modelValue !== null && typeof props.modelValue === 'object') {
|
|
|
+ return [props.modelValue.id]
|
|
|
}
|
|
|
-
|
|
|
- return q
|
|
|
+ return []
|
|
|
})
|
|
|
|
|
|
+const queryActive = new Query(
|
|
|
+ new OrderBy('name', ORDER_BY_DIRECTION.ASC),
|
|
|
+ new PageFilter(ref(1), ref(10)),
|
|
|
+ new InArrayFilter('id', activeIds)
|
|
|
+)
|
|
|
+
|
|
|
+const {
|
|
|
+ data: collectionActive,
|
|
|
+ pending: pendingActive,
|
|
|
+} = fetchCollection(UserSearchItem, null, queryActive)
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Query transmise à l'API lors des changements de filtre de recherche
|
|
|
+ */
|
|
|
+const querySearch = new Query(
|
|
|
+ new OrderBy('name', ORDER_BY_DIRECTION.ASC),
|
|
|
+ new PageFilter(ref(1), ref(100)),
|
|
|
+ new SearchFilter('fullName', nameFilter, SEARCH_STRATEGY.IPARTIAL)
|
|
|
+)
|
|
|
+
|
|
|
/**
|
|
|
* On commence par fetcher les accesses déjà actifs, pour affichage des noms
|
|
|
*/
|
|
|
const {
|
|
|
- data: collection,
|
|
|
- pending,
|
|
|
- refresh,
|
|
|
-} = await fetchCollection(Access, null, query)
|
|
|
-initialized.value = true
|
|
|
+ data: collectionSearch,
|
|
|
+ pending: pendingSearch,
|
|
|
+ refresh: refreshSearch,
|
|
|
+} = fetchCollection(UserSearchItem, null, querySearch)
|
|
|
+
|
|
|
+const pending = computed(() => pendingSearch.value || pendingActive.value)
|
|
|
|
|
|
-// On a déjà récupéré les access actifs, on relance une requête pour récupérer la première page
|
|
|
-// des accesses suivants
|
|
|
-refresh()
|
|
|
|
|
|
/**
|
|
|
* Contenu de la liste autocomplete
|
|
|
*/
|
|
|
-const items: ComputedRef<Array<AccessListItem>> = computed(() => {
|
|
|
- if (pending.value || !collection.value) {
|
|
|
+const items: ComputedRef<Array<UserListItem>> = computed(() => {
|
|
|
+ if (pending.value || !(collectionActive.value && collectionSearch.value)) {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
- if (!collection.value) {
|
|
|
- return []
|
|
|
- }
|
|
|
-
|
|
|
- //@ts-ignore
|
|
|
- const fetchedItems = collection.value.items.map(accessToItem)
|
|
|
-
|
|
|
- // move the active items to the top and sort by name
|
|
|
- fetchedItems.sort((a, b) => {
|
|
|
- if (props.modelValue.includes(a.id) && !props.modelValue.includes(b.id)) {
|
|
|
- return -1
|
|
|
- } else if (
|
|
|
- !props.modelValue.includes(a.id) &&
|
|
|
- props.modelValue.includes(b.id)
|
|
|
- ) {
|
|
|
- return 1
|
|
|
- } else {
|
|
|
- return a.title.localeCompare(b.title)
|
|
|
- }
|
|
|
- })
|
|
|
+ const activeItems: UserListItem[] = collectionActive.value.items.map(accessToItem)
|
|
|
+ const searchedItems: UserListItem[] = collectionSearch.value.items
|
|
|
+ .map(accessToItem)
|
|
|
+ .filter(item => !collectionActive.value.items.find(other => other.id === item.id))
|
|
|
|
|
|
- return fetchedItems
|
|
|
+ return activeItems.concat(searchedItems)
|
|
|
})
|
|
|
|
|
|
/**
|
|
|
@@ -233,7 +225,7 @@ const inputDelay = 600
|
|
|
* @see https://docs-lodash.com/v4/debounce/
|
|
|
*/
|
|
|
const refreshDebounced: _.DebouncedFunc<() => void> = _.debounce(async () => {
|
|
|
- await refresh()
|
|
|
+ await refreshSearch()
|
|
|
}, inputDelay)
|
|
|
|
|
|
// ### Events
|