From b7fd8c99e5eddc6c4cb07831e59c9124f4d88f36 Mon Sep 17 00:00:00 2001 From: Padreug Date: Thu, 4 Jun 2026 23:41:57 +0200 Subject: [PATCH] feat(activities): fuzzy search on the Tickets roster Adds a search box above the roster list that fuzzy-matches the holder name and ticket id via the shared useFuzzySearch (Fuse.js) composable. Empty query keeps the unregistered-first sort intact; typing reorders by relevance. The empty-state message now distinguishes "no tickets sold yet" from "no rows matched the current query" so a busy roster + a typo doesn't look like backend trouble. --- src/modules/events/views/ScanTicketsPage.vue | 46 ++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/modules/events/views/ScanTicketsPage.vue b/src/modules/events/views/ScanTicketsPage.vue index 9d4aa2ce..8e81993 100644 --- a/src/modules/events/views/ScanTicketsPage.vue +++ b/src/modules/events/views/ScanTicketsPage.vue @@ -11,16 +11,19 @@ import { ScanLine, RefreshCw, UserCheck, + Search, } from 'lucide-vue-next' import { format } from 'date-fns' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' +import { Input } from '@/components/ui/input' import { ScrollArea } from '@/components/ui/scroll-area' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import QRScanner from '@/components/ui/qr-scanner.vue' import { useTicketScanner } from '../composables/useTicketScanner' import type { EventTicket } from '../composables/useTicketScanner' import { useEventDetail } from '../composables/useEventDetail' +import { useFuzzySearch } from '@/composables/useFuzzySearch' const route = useRoute() const router = useRouter() @@ -93,6 +96,24 @@ const unregisteredCount = computed( () => allTickets.value.filter(t => !t.registered).length, ) +// Fuzzy match on holder name + ticket id. When the search box is +// empty, Fuse returns the list in its incoming order so our +// unregistered-first sort is preserved. +const { searchQuery, filteredItems: searchedTickets } = useFuzzySearch( + allTickets, + { + fuseOptions: { + keys: [ + { name: 'name', weight: 0.7 }, + { name: 'id', weight: 0.3 }, + ], + threshold: 0.3, + ignoreLocation: true, + }, + matchAllWhenSearchEmpty: true, + }, +) + async function handleManualRegister(ticket: EventTicket) { pendingRegister.value.add(ticket.id) const res = await registerManually(ticket.id) @@ -239,14 +260,27 @@ function fmtTime(iso: string) { + +
+ + +
+ - +
  • @@ -289,9 +323,15 @@ function fmtTime(iso: string) {
-

+

No tickets sold yet.

+

+ No tickets match “{{ searchQuery }}”. +