浏览代码

add the UiInputAutocompleteAccesses

Olivier Massot 2 年之前
父节点
当前提交
dd8fb1deaf

+ 13 - 4
components/Layout/Parameters/Website.vue

@@ -54,12 +54,13 @@
           </div>
         </v-col>
 
-
         <v-col cols="6">
           <!-- les publicationDirectors sont des entités Access -->
-          <UiInputAutocomplete
+          <UiInputAutocompleteAccesses
+              v-model="parameters.publicationDirectors"
               field="publicationDirectors"
-              itemTitle="person.name"
+              :multiple="true"
+              chips
           />
 
           <UiInputCheckbox
@@ -84,8 +85,9 @@ import {useEntityFetch} from "~/composables/data/useEntityFetch";
 import {AsyncData} from "#app";
 import Subdomain from "~/models/Organization/Subdomain";
 import ApiResource from "~/models/ApiResource";
+import Access from "~/models/Access/Access";
 
-const router = useRouter()
+const i18n = useI18n()
 
 const { fetch, fetchCollection } = useEntityFetch()
 
@@ -116,6 +118,13 @@ const onAddSubdomainClick = () => {
 
 }
 
+const publicationDirectorsMapping = (access: Access): { id: number | string, title: string } => {
+  return {
+    id: access.id,
+    title: access.person ? `${access.person.givenName} ${access.person.name}` : i18n.t('unknown')
+  }
+}
+
 </script>
 
 <style scoped lang="scss">

+ 4 - 2
components/Ui/Input/Autocomplete.vue

@@ -27,6 +27,7 @@ Liste déroulante avec autocompletion, à placer dans un composant `UiForm`
         :rules="rules"
         :chips="chips"
         @update:model-value="onUpdate"
+        @update:search="emit('update:search', $event)"
     >
       <template v-if="slotText" #item="data">
 <!--        <v-list-item-content v-text="data.item.slotTextDisplay"></v-list-item-content>-->
@@ -40,6 +41,7 @@ import {computed, ComputedRef, Ref} from "@vue/reactivity";
 import {useFieldViolation} from "~/composables/form/useFieldViolation";
 import ObjectUtils from "~/services/utils/objectUtils";
 import {AnyJson} from "~/types/data";
+import {PropType} from "@vue/runtime-core";
 
 const props = defineProps({
   /**
@@ -74,7 +76,7 @@ const props = defineProps({
    * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-items
    */
   items: {
-    type: Array<Object>,
+    type: Array as PropType<Array<Object>>,
     required: false,
     default: () => []
   },
@@ -199,7 +201,7 @@ const fieldLabel: string = props.label ?? props.field
 
 const {fieldViolations, updateViolationState} = useFieldViolation(props.field)
 
-const emit = defineEmits(['update:model-value'])
+const emit = defineEmits(['update:model-value', 'update:search'])
 
 const onUpdate = (event: string) => {
   updateViolationState(event)

+ 142 - 0
components/Ui/Input/Autocomplete/Accesses.vue

@@ -0,0 +1,142 @@
+<template>
+  <main>
+    <UiInputAutocomplete
+        :model-value="modelValue"
+        :field="field"
+        :label="label"
+        :items="items"
+        :isLoading="pending"
+        item-title="title"
+        item-value="id"
+        :multiple="multiple"
+        :chips="chips"
+        prependIcon="fas fa-magnifying-glass"
+        :return-object="false"
+        @update:model-value="onUpdateModelvalue"
+        @update:search="onUpdateSearch"
+    />
+  </main>
+</template>
+
+<script setup lang="ts">
+import {PropType} from "@vue/runtime-core";
+import {computed, ComputedRef, Ref} from "@vue/reactivity";
+import {AnyJson, AssociativeArray} from "~/types/data";
+import {useEntityFetch} from "~/composables/data/useEntityFetch";
+import Access from "~/models/Access/Access";
+
+const props = defineProps({
+  /**
+   * v-model
+   */
+  modelValue: {
+    type: [String, Number, Object, Array],
+    required: false,
+    default: null
+  },
+  /**
+   * Filtres à transmettre à la source de données
+   */
+  filters: {
+    type: Object as PropType<Ref<AssociativeArray>>,
+    required: false,
+    default: ref(null)
+  },
+  /**
+   * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
+   * - Utilisé par la validation
+   * - Laisser null si le champ ne s'applique pas à une entité
+   */
+  field: {
+    type: String,
+    required: false,
+    default: null
+  },
+  /**
+   * Label du champ
+   * Si non défini, c'est le nom de propriété qui est utilisé
+   */
+  label: {
+    type: String,
+    required: false,
+    default: null
+  },
+  /**
+   * Définit si le champ est en lecture seule
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-readonly
+   */
+  readonly: {
+    type: Boolean,
+    required: false
+  },
+  /**
+   * Autorise la sélection multiple
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-multiple
+   */
+  multiple: {
+    type: Boolean,
+    default: false
+  },
+  /**
+   * Rends les résultats sous forme de puces
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-chips
+   */
+  chips: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const { fetchCollection } = useEntityFetch()
+
+const i18n = useI18n()
+
+const nameFilter = ref(null)
+
+const query: ComputedRef<AnyJson> = computed(() => {
+
+  let q = props.filters.value ?? {}
+  if (nameFilter.value !== null) {
+    q['fullname'] = nameFilter.value
+  }
+
+  return { ...q, ...{ 'groups[]': 'access_people_ref' } }
+})
+
+const { data: collection, pending, refresh } = await fetchCollection(Access, null, query)
+
+const accessToItem = (access: Access): { id: number | string, title: string } => {
+  return {
+    id: access.id,
+    title: access.person ? `${access.person.givenName} ${access.person.name}` : i18n.t('unknown')
+  }
+}
+
+const items: ComputedRef<Array<{ id: number | string, title: string }>> = computed(() => {
+  if (!pending.value && collection.value && collection.value.items) {
+    // @ts-ignore
+    return collection.value.items.map(accessToItem)
+  }
+  return []
+})
+
+const emit = defineEmits(['update:model-value'])
+
+const onUpdateModelvalue = (event: Array<number>) => {
+  console.log(event)
+  emit('update:model-value', event)
+}
+
+const onUpdateSearch = (event: string) => {
+  if (event.length <= 2) {
+    return
+  }
+  refresh()
+}
+
+
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 15 - 15
components/Ui/Input/AutocompleteWithAPI.vue

@@ -7,21 +7,21 @@ d'une api)
 <template>
   <main>
     <UiInputAutocomplete
-      :field="field"
-      :label="label"
-      :data="remoteData ? remoteData : data"
-      :items="items"
-      :isLoading="isLoading"
-      :item-text="itemText"
-      :slotText="slotText"
-      :item-value="itemValue"
-      :multiple="multiple"
-      :chips="chips"
-      prependIcon="mdi-magnify"
-      :return-object="returnObject"
-      @research="search"
-      :no-filter="noFilter"
-      @update="$emit('update', $event, field)"
+        :field="field"
+        :label="label"
+        :data="remoteData ? remoteData : data"
+        :items="items"
+        :isLoading="isLoading"
+        :item-text="itemText"
+        :slotText="slotText"
+        :item-value="itemValue"
+        :multiple="multiple"
+        :chips="chips"
+        prependIcon="mdi-magnify"
+        :return-object="returnObject"
+        @research="search"
+        :no-filter="noFilter"
+        @update="$emit('update', $event, field)"
     />
   </main>
 </template>

+ 136 - 0
components/Ui/Input/AutocompleteWithAp2i.vue

@@ -0,0 +1,136 @@
+<!--
+Liste déroulante avec autocompletion issue de Ap2i
+
+@see https://vuetifyjs.com/en/components/autocompletes/#usage
+-->
+<template>
+  <main>
+    <UiInputAutocomplete
+      :v-model="modelValue"
+      :field="field"
+      :label="label"
+      :items="items"
+      :isLoading="pending"
+      item-title="title"
+      item-value="id"
+      :multiple="multiple"
+      :chips="chips"
+      prependIcon="fas fa-magnifying-glass"
+      :return-object="false"
+    />
+  </main>
+</template>
+
+<script setup lang="ts">
+
+import {computed, ComputedRef, Ref} from "@vue/reactivity";
+import {PropType} from "@vue/runtime-core";
+import {useEntityFetch} from "~/composables/data/useEntityFetch";
+import ApiResource from "~/models/ApiResource";
+import ApiModel from "~/models/ApiModel";
+import {AnyJson, AssociativeArray, Collection} from "~/types/data";
+
+const props = defineProps({
+  /**
+   * v-model
+   */
+  modelValue: {
+    type: [String, Number, Object, Array],
+    required: false,
+    default: null
+  },
+  /**
+   * Classe de l'ApiModel (ex: Organization, Notification, ...) qui sert de source à la liste
+   */
+  model: {
+    type: Function as any as () => typeof ApiModel,
+    required: true
+  },
+  /**
+   * Filtres à transmettre à la source de données
+   */
+  query: {
+    type: Object as PropType<Ref<AssociativeArray>>,
+    required: false,
+    default: ref(null)
+  },
+  /**
+   * Fonction qui sera exécutée sur chaque item, et qui doit renvoyer un objet contenant les
+   * propriétés 'id' et 'title'
+   *
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-item-title
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-item-value
+   */
+  transformation: {
+    type: Function as PropType<(item: ApiResource) => { id: number | string, title: string }>,
+    required: false,
+    default: (item: ApiResource) => item
+  },
+  /**
+   * Nom de la propriété d'une entité lorsque l'input concerne cette propriété
+   * - Utilisé par la validation
+   * - Laisser null si le champ ne s'applique pas à une entité
+   */
+  field: {
+    type: String,
+    required: false,
+    default: null
+  },
+  /**
+   * Label du champ
+   * Si non défini, c'est le nom de propriété qui est utilisé
+   */
+  label: {
+    type: String,
+    required: false,
+    default: null
+  },
+  /**
+   * Définit si le champ est en lecture seule
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-readonly
+   */
+  readonly: {
+    type: Boolean,
+    required: false
+  },
+  /**
+   * Autorise la sélection multiple
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-multiple
+   */
+  multiple: {
+    type: Boolean,
+    default: false
+  },
+  /**
+   * Rends les résultats sous forme de puces
+   * @see https://vuetifyjs.com/en/api/v-autocomplete/#props-chips
+   */
+  chips: {
+    type: Boolean,
+    default: false
+  },
+  // TODO: c'est quoi?
+  slotText: {
+    type: Array,
+    required: false,
+    default: null
+  },
+})
+
+const { fetchCollection } = useEntityFetch()
+
+const query: ComputedRef<AnyJson> = computed(() => {
+  return { ...(props.query.value ?? {}), ...{ 'groups[]': 'access_people_ref' } }
+})
+
+const { data: collection, pending } = await fetchCollection(props.model, null, query)
+
+const items: ComputedRef<Array<{ id: number | string, title: string }>> = computed(() => {
+  if (!pending.value && collection.value && collection.value.items) {
+    console.log(collection)
+
+    return collection.value.items.map(props.transformation)
+  }
+  return []
+})
+</script>

+ 2 - 4
composables/data/useEntityFetch.ts

@@ -22,11 +22,9 @@ export const useEntityFetch = (lazy: boolean = false): useEntityFetchReturnType
         { lazy }
     )
 
-    const fetchCollection = (model: typeof ApiResource, parent: ApiResource | null = null, query: Ref<AssociativeArray> = ref([])) => useAsyncData(
+    const fetchCollection = (model: typeof ApiResource, parent: ApiResource | null = null, query: Ref<AssociativeArray | null> = ref(null)) => useAsyncData(
         model.entity + '_many',
-        async () => {
-            return em.fetchCollection(model, parent, query.value)
-        },
+        () => em.fetchCollection(model, parent, query.value),
         { lazy }
     )
 

+ 6 - 0
models/Person/Person.ts

@@ -17,4 +17,10 @@ export default class Person extends ApiModel {
 
   @Str(null)
   declare username: string|null
+
+  @Str(null)
+  declare name: string|null
+
+  @Str(null)
+  declare givenName: string|null
 }

+ 1 - 1
services/data/entityManager.ts

@@ -175,7 +175,7 @@ class EntityManager {
      * @param query
      * @param parent
      */
-    public async fetchCollection(model: typeof ApiResource, parent: ApiResource | null, query: AssociativeArray = []): Promise<Collection> {
+    public async fetchCollection(model: typeof ApiResource, parent: ApiResource | null, query: AssociativeArray | null = null): Promise<Collection> {
         let url
 
         if (parent !== null) {