| 1 | <template> |
| 2 | <div v-if="event" class="flex flex-col gap-3"> |
| 3 | <div class="flex items-center justify-between gap-3"> |
| 4 | <p class="text-secondary text-[10px] font-medium tracking-widest uppercase">Event fields</p> |
| 5 | <span class="text-default font-mono text-xs tabular-nums">{{ sortedFields.length }}</span> |
| 6 | </div> |
| 7 | |
| 8 | <dl class="border-border divide-border divide-y rounded-lg border"> |
| 9 | <div |
| 10 | v-for="[key, value] in sortedFields" |
| 11 | :key |
| 12 | class="group hover:bg-hover-005 flex items-start gap-3 px-3 py-2.5 transition-colors" |
| 13 | > |
| 14 | <dt class="text-secondary max-w-44 min-w-36 shrink-0 truncate font-mono text-xs" :title="key"> |
| 15 | {{ key }} |
| 16 | </dt> |
| 17 | <dd class="flex min-w-0 flex-1 items-start justify-end gap-3"> |
| 18 | <span class="text-default min-w-0 flex-1 text-right font-mono text-xs break-all"> |
| 19 | {{ formatValue(value) }} |
| 20 | </span> |
| 21 | <div class="flex shrink-0 gap-0.5 opacity-0 transition-opacity group-hover:opacity-100"> |
| 22 | <n-tooltip placement="top" class="px-2! py-1! text-xs!"> |
| 23 | <template #trigger> |
| 24 | <n-button size="tiny" quaternary @click="emit('filter-add', key, String(value))"> |
| 25 | <template #icon> |
| 26 | <Icon :name="FilterAddIcon" :size="14" /> |
| 27 | </template> |
| 28 | </n-button> |
| 29 | </template> |
| 30 | Include in query |
| 31 | </n-tooltip> |
| 32 | <n-tooltip placement="top" class="px-2! py-1! text-xs!"> |
| 33 | <template #trigger> |
| 34 | <n-button size="tiny" quaternary @click="emit('filter-exclude', key, String(value))"> |
| 35 | <template #icon> |
| 36 | <Icon :name="FilterRemoveIcon" :size="14" /> |
| 37 | </template> |
| 38 | </n-button> |
| 39 | </template> |
| 40 | Exclude from query |
| 41 | </n-tooltip> |
| 42 | </div> |
| 43 | </dd> |
| 44 | </div> |
| 45 | </dl> |
| 46 | </div> |
| 47 | |
| 48 | <n-empty v-else description="No event selected" class="h-48 justify-center" /> |
| 49 | </template> |
| 50 | |
| 51 | <script setup lang="ts"> |
| 52 | import type { EventSearchResult } from "@/types/events.d" |
| 53 | import { NButton, NEmpty, NTooltip } from "naive-ui" |
| 54 | import { computed } from "vue" |
| 55 | import Icon from "@/components/common/Icon.vue" |
| 56 | |
| 57 | const props = defineProps<{ |
| 58 | event: EventSearchResult | null |
| 59 | }>() |
| 60 | |
| 61 | const emit = defineEmits<{ |
| 62 | "filter-add": [field: string, value: string] |
| 63 | "filter-exclude": [field: string, value: string] |
| 64 | }>() |
| 65 | |
| 66 | const FilterAddIcon = "carbon:filter" |
| 67 | const FilterRemoveIcon = "carbon:filter-remove" |
| 68 | |
| 69 | const sortedFields = computed(() => { |
| 70 | if (!props.event) return [] |
| 71 | return Object.entries(props.event) |
| 72 | .filter(([key]) => !key.startsWith("_")) |
| 73 | .sort(([a], [b]) => a.localeCompare(b)) |
| 74 | }) |
| 75 | |
| 76 | function formatValue(value: unknown): string { |
| 77 | if (value === null || value === undefined) return "—" |
| 78 | if (typeof value === "object") return JSON.stringify(value) |
| 79 | return String(value) |
| 80 | } |
| 81 | </script> |