117 lines
4 KiB
Vue
117 lines
4 KiB
Vue
<script setup>
|
|
import { computed, ref } from 'vue'
|
|
|
|
const props = defineProps({
|
|
macros: { type: Array, default: () => [] },
|
|
collections: { type: Array, default: () => [] },
|
|
disabled: { type: Boolean, default: false },
|
|
})
|
|
|
|
const model = defineModel({ type: Number, default: null })
|
|
const search = ref('')
|
|
const isOpen = ref(false)
|
|
|
|
const filteredMacros = computed(() => {
|
|
const q = search.value.toLowerCase()
|
|
return props.macros.filter((m) => m.name.toLowerCase().includes(q))
|
|
})
|
|
|
|
const groupedMacros = computed(() => {
|
|
const groups = {}
|
|
props.collections.forEach((c) => {
|
|
groups[c.name] = []
|
|
})
|
|
groups['Ohne Sammlung'] = []
|
|
filteredMacros.value.forEach((m) => {
|
|
const coll = props.collections.find((c) => c.macros?.some((cm) => cm.id === m.id))
|
|
const key = coll?.name ?? 'Ohne Sammlung'
|
|
if (!groups[key]) groups[key] = []
|
|
groups[key].push(m)
|
|
})
|
|
return groups
|
|
})
|
|
|
|
const selectedMacro = computed(() => props.macros.find((m) => m.id === model.value))
|
|
|
|
function select(macro) {
|
|
model.value = macro.id
|
|
search.value = ''
|
|
isOpen.value = false
|
|
}
|
|
|
|
function open() {
|
|
if (!props.disabled) isOpen.value = true
|
|
}
|
|
|
|
function close() {
|
|
setTimeout(() => {
|
|
isOpen.value = false
|
|
}, 150)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative">
|
|
<div
|
|
class="flex cursor-pointer items-center gap-2 rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm"
|
|
:class="{ 'cursor-not-allowed opacity-50': disabled }"
|
|
@click="open"
|
|
data-testid="macro-picker-trigger"
|
|
>
|
|
<span
|
|
v-if="selectedMacro?.color"
|
|
class="h-4 w-4 shrink-0 rounded"
|
|
:style="{ backgroundColor: selectedMacro.color }"
|
|
/>
|
|
<span class="flex-1 truncate text-gray-700">
|
|
{{ selectedMacro ? selectedMacro.name : 'Makro auswählen...' }}
|
|
</span>
|
|
</div>
|
|
|
|
<div
|
|
v-if="isOpen"
|
|
class="absolute z-50 mt-1 w-full rounded-lg border border-gray-200 bg-white shadow-lg"
|
|
data-testid="macro-picker-dropdown"
|
|
>
|
|
<div class="border-b border-gray-100 p-2">
|
|
<input
|
|
v-model="search"
|
|
type="text"
|
|
placeholder="Makro suchen..."
|
|
class="w-full rounded border-gray-300 text-sm"
|
|
data-testid="macro-picker-search"
|
|
autofocus
|
|
@blur="close"
|
|
/>
|
|
</div>
|
|
<div class="max-h-64 overflow-y-auto">
|
|
<template v-for="(group, name) in groupedMacros" :key="name">
|
|
<div
|
|
v-if="group.length > 0"
|
|
class="bg-gray-50 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-gray-400"
|
|
>
|
|
{{ name }}
|
|
</div>
|
|
<button
|
|
v-for="macro in group"
|
|
:key="macro.id"
|
|
class="flex w-full items-center gap-2 px-3 py-2 text-sm transition-colors hover:bg-amber-50"
|
|
:class="macro.hidden_at ? 'text-gray-400' : 'text-gray-700'"
|
|
:data-testid="'macro-option-' + macro.id"
|
|
@click="select(macro)"
|
|
>
|
|
<span
|
|
class="h-3 w-3 shrink-0 rounded"
|
|
:style="macro.color ? { backgroundColor: macro.color } : { backgroundColor: '#ccc' }"
|
|
/>
|
|
<span class="truncate">{{ macro.name }}{{ macro.hidden_at ? ' (deaktiviert)' : '' }}</span>
|
|
</button>
|
|
</template>
|
|
<div v-if="filteredMacros.length === 0" class="px-3 py-4 text-center text-sm text-gray-400">
|
|
Kein Makro gefunden
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|