<template> <div class="flex flex-col gap-2 h-full w-full overflow-hidden"> <Combobox v-model="presence" multiple> <div class="rounded-md shadow-sm relative block w-full border border-gray-300 focus:border-primary placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-0 focus:z-10 sm:text-sm resize-none" > <ComboboxInput class="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0" @input="query = $event.target.value" placeholder="suchen..." /> <ComboboxButton class="absolute inset-y-0 right-0 flex items-center pr-2"> <ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" /> </ComboboxButton> </div> <ComboboxOptions class="w-full h-full overflow-y-auto flex flex-col gap-1 rounded-md border border-gray-300 bg-white text-base shadow-md ring-1 ring-black/5 focus:outline-none sm:text-sm" :static="true" :open="true" > <div v-if="availableForces.length === 0" class="relative cursor-default select-none px-4 py-2 text-gray-700"> Keine Auswahl verfügbar </div> <div v-if="filtered.length === 0 && query !== ''" class="relative cursor-default select-none px-4 py-2 text-gray-700" > Keine Treffer </div> <ComboboxOption v-for="person in filtered" as="template" :key="person.id" :value="person.id" v-slot="{ selected, active }" > <li class="relative cursor-pointer select-none py-2 pl-10 pr-4" :class="{ '!bg-primary text-white': active, 'text-gray-900': !active, 'bg-red-200': selected, }" > <span class="block truncate" :class="{ 'font-medium': selected, 'font-normal': !selected }"> {{ person.firstname }} {{ person.lastname }} </span> <span v-if="selected" class="absolute inset-y-0 left-0 flex items-center pl-3" :class="{ 'text-white': active && !selected, 'text-primary': !active }" > <CheckIcon class="h-5 w-5" aria-hidden="true" /> </span> </li> </ComboboxOption> </ComboboxOptions> </Combobox> </div> </template> <script setup lang="ts"> import { defineComponent, type PropType } from "vue"; import { Combobox, ComboboxInput, ComboboxButton, ComboboxOptions, ComboboxOption } from "@headlessui/vue"; import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid"; import { useForceStore } from "@/stores/admin/configuration/force"; import { mapState } from "pinia"; import * as Y from "yjs"; </script> <script lang="ts"> export default defineComponent({ props: { document: { type: Object as PropType<Y.Doc>, required: true, }, }, data() { return { query: "" as string, }; }, computed: { ...mapState(useForceStore, ["availableForces"]), presence: { get() { return Array.from(this.document.getMap<boolean>("presence").keys()); }, set(val: Array<string>) { let added = val.filter((e) => !this.document.getMap<boolean>("presence").has(e)); let removed = Array.from(this.document.getMap<boolean>("presence").keys()).filter((e) => !val.includes(e)); added.forEach((a) => { this.document.getMap<boolean>("presence").set(a, true); }); removed.forEach((r) => { this.document.getMap<boolean>("presence").delete(r); }); }, }, filtered() { return this.query == "" ? this.availableForces : this.availableForces.filter((f) => { const arr = this.query.toLocaleLowerCase().split(" "); if (arr.length == 1) { return arr.some( (q) => f.firstname.toLocaleLowerCase().includes(q) || f.lastname.toLocaleLowerCase().includes(q) ); } else { return arr .flatMap((v, i) => arr.slice(i + 1).map((w) => [v, w])) .some( (q) => (f.firstname.toLocaleLowerCase().includes(q[0]) && f.lastname.toLocaleLowerCase().includes(q[1])) || (f.firstname.toLocaleLowerCase().includes(q[1]) && f.lastname.toLocaleLowerCase().includes(q[0])) ); } }); }, }, }); </script>