feat: Implement ticket grouping in MyTickets for improved organization

- Enhance useUserTickets composable to group tickets by event, providing counts for paid, pending, and registered tickets.
- Update MyTickets.vue to display tickets in organized groups, improving user experience with clear event headers and ticket summaries.
- Refactor ticket display logic to accommodate grouped ticket views, ensuring a cohesive layout across different ticket statuses.
This commit is contained in:
padreug 2025-08-01 22:08:10 +02:00
parent a6a65800a4
commit 67e4c0db87
2 changed files with 358 additions and 240 deletions

View file

@ -4,6 +4,14 @@ import type { Ticket } from '@/lib/types/event'
import { fetchUserTickets } from '@/lib/api/events' import { fetchUserTickets } from '@/lib/api/events'
import { useAuth } from './useAuth' import { useAuth } from './useAuth'
interface GroupedTickets {
eventId: string
tickets: Ticket[]
paidCount: number
pendingCount: number
registeredCount: number
}
export function useUserTickets() { export function useUserTickets() {
const { isAuthenticated, currentUser } = useAuth() const { isAuthenticated, currentUser } = useAuth()
@ -54,6 +62,43 @@ export function useUserTickets() {
return sortedTickets.value.filter(ticket => ticket.paid && !ticket.registered) return sortedTickets.value.filter(ticket => ticket.paid && !ticket.registered)
}) })
// Group tickets by event
const groupedTickets = computed(() => {
const groups = new Map<string, GroupedTickets>()
sortedTickets.value.forEach(ticket => {
if (!groups.has(ticket.event)) {
groups.set(ticket.event, {
eventId: ticket.event,
tickets: [],
paidCount: 0,
pendingCount: 0,
registeredCount: 0
})
}
const group = groups.get(ticket.event)!
group.tickets.push(ticket)
if (ticket.paid) {
group.paidCount++
} else {
group.pendingCount++
}
if (ticket.registered) {
group.registeredCount++
}
})
// Convert to array and sort by most recent ticket in each group
return Array.from(groups.values()).sort((a, b) => {
const aLatest = Math.max(...a.tickets.map(t => new Date(t.time).getTime()))
const bLatest = Math.max(...b.tickets.map(t => new Date(t.time).getTime()))
return bLatest - aLatest
})
})
// Load tickets when authenticated // Load tickets when authenticated
const loadTickets = async () => { const loadTickets = async () => {
if (isAuthenticated.value && currentUser.value) { if (isAuthenticated.value && currentUser.value) {
@ -68,6 +113,7 @@ export function useUserTickets() {
pendingTickets, pendingTickets,
registeredTickets, registeredTickets,
unregisteredTickets, unregisteredTickets,
groupedTickets,
isLoading, isLoading,
error, error,

View file

@ -18,6 +18,7 @@ const {
pendingTickets, pendingTickets,
registeredTickets, registeredTickets,
unregisteredTickets, unregisteredTickets,
groupedTickets,
isLoading, isLoading,
error, error,
refresh refresh
@ -131,292 +132,363 @@ onMounted(async () => {
<TabsContent value="all"> <TabsContent value="all">
<ScrollArea class="h-[600px] w-full pr-4"> <ScrollArea class="h-[600px] w-full pr-4">
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div class="space-y-4">
<Card v-for="ticket in tickets" :key="ticket.id" class="flex flex-col"> <div v-for="group in groupedTickets" :key="group.eventId" class="space-y-3">
<CardHeader> <!-- Event Header -->
<div class="bg-muted/50 rounded-lg p-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle> <div>
<div class="flex items-center gap-2"> <h3 class="font-semibold text-foreground">Event: {{ group.eventId }}</h3>
<Badge :variant="getTicketStatus(ticket).status === 'pending' ? 'secondary' : 'default'"> <p class="text-sm text-muted-foreground">
{{ getTicketStatus(ticket).label }} {{ group.tickets.length }} ticket{{ group.tickets.length !== 1 ? 's' : '' }}
</Badge> {{ group.paidCount }} paid
<Button {{ group.pendingCount }} pending
variant="ghost" {{ group.registeredCount }} registered
size="sm" </p>
@click="toggleQRCode(ticket.id)"
class="h-6 w-6 p-0"
>
<QrCode class="h-4 w-4" />
</Button>
</div> </div>
<Badge variant="outline" class="text-xs">
{{ group.tickets.length }} total
</Badge>
</div> </div>
<CardDescription> </div>
Event ID: {{ ticket.event }}
</CardDescription> <!-- Tickets Grid -->
</CardHeader> <div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<CardContent class="flex-grow"> <Card v-for="ticket in group.tickets" :key="ticket.id" class="flex flex-col">
<div class="space-y-2"> <CardHeader>
<div class="flex justify-between"> <div class="flex items-center justify-between">
<span class="text-muted-foreground">Status:</span> <CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
<div class="flex items-center gap-1"> <div class="flex items-center gap-2">
<component :is="getTicketStatus(ticket).icon" class="w-4 h-4" :class="getTicketStatus(ticket).color" /> <Badge :variant="getTicketStatus(ticket).status === 'pending' ? 'secondary' : 'default'">
<span class="text-sm">{{ getTicketStatus(ticket).label }}</span> {{ getTicketStatus(ticket).label }}
</div> </Badge>
</div> <Button
<div class="flex justify-between"> variant="ghost"
<span class="text-muted-foreground">Purchased:</span> size="sm"
<span class="text-sm">{{ formatDate(ticket.time) }}</span> @click="toggleQRCode(ticket.id)"
</div> class="h-6 w-6 p-0"
<div class="flex justify-between"> >
<span class="text-muted-foreground">Time:</span> <QrCode class="h-4 w-4" />
<span class="text-sm">{{ formatTime(ticket.time) }}</span> </Button>
</div>
<div v-if="ticket.reg_timestamp" class="flex justify-between">
<span class="text-muted-foreground">Registered:</span>
<span class="text-sm">{{ formatDate(ticket.reg_timestamp) }}</span>
</div>
<!-- QR Code Section -->
<div v-if="showQRCode[ticket.id]" class="mt-4 pt-4 border-t">
<div class="flex flex-col items-center space-y-2">
<img
v-if="qrCodes[ticket.id]"
:src="qrCodes[ticket.id]"
alt="Ticket QR Code"
class="w-32 h-32 border rounded-lg"
/>
<div v-else class="w-32 h-32 border rounded-lg flex items-center justify-center">
<span class="text-xs text-muted-foreground">Loading...</span>
</div> </div>
<div class="text-center"> </div>
<p class="text-xs text-muted-foreground">Ticket ID</p> </CardHeader>
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1"> <CardContent class="flex-grow">
<p class="text-xs font-mono break-all">{{ ticket.id }}</p> <div class="space-y-2">
<div class="flex justify-between">
<span class="text-muted-foreground">Status:</span>
<div class="flex items-center gap-1">
<component :is="getTicketStatus(ticket).icon" class="w-4 h-4" :class="getTicketStatus(ticket).color" />
<span class="text-sm">{{ getTicketStatus(ticket).label }}</span>
</div>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Purchased:</span>
<span class="text-sm">{{ formatDate(ticket.time) }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Time:</span>
<span class="text-sm">{{ formatTime(ticket.time) }}</span>
</div>
<div v-if="ticket.reg_timestamp" class="flex justify-between">
<span class="text-muted-foreground">Registered:</span>
<span class="text-sm">{{ formatDate(ticket.reg_timestamp) }}</span>
</div>
<!-- QR Code Section -->
<div v-if="showQRCode[ticket.id]" class="mt-4 pt-4 border-t">
<div class="flex flex-col items-center space-y-2">
<img
v-if="qrCodes[ticket.id]"
:src="qrCodes[ticket.id]"
alt="Ticket QR Code"
class="w-32 h-32 border rounded-lg"
/>
<div v-else class="w-32 h-32 border rounded-lg flex items-center justify-center">
<span class="text-xs text-muted-foreground">Loading...</span>
</div>
<div class="text-center">
<p class="text-xs text-muted-foreground">Ticket ID</p>
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1">
<p class="text-xs font-mono break-all">{{ ticket.id }}</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </CardContent>
</div> </Card>
</CardContent> </div>
</Card> </div>
</div> </div>
</ScrollArea> </ScrollArea>
</TabsContent> </TabsContent>
<TabsContent value="paid"> <TabsContent value="paid">
<ScrollArea class="h-[600px] w-full pr-4"> <ScrollArea class="h-[600px] w-full pr-4">
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div class="space-y-4">
<Card v-for="ticket in paidTickets" :key="ticket.id" class="flex flex-col"> <div v-for="group in groupedTickets.filter(g => g.paidCount > 0)" :key="group.eventId" class="space-y-3">
<CardHeader> <!-- Event Header -->
<div class="bg-muted/50 rounded-lg p-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle> <div>
<div class="flex items-center gap-2"> <h3 class="font-semibold text-foreground">Event: {{ group.eventId }}</h3>
<Badge variant="default"> <p class="text-sm text-muted-foreground">
{{ getTicketStatus(ticket).label }} {{ group.paidCount }} paid ticket{{ group.paidCount !== 1 ? 's' : '' }}
</Badge> </p>
<Button
variant="ghost"
size="sm"
@click="toggleQRCode(ticket.id)"
class="h-6 w-6 p-0"
>
<QrCode class="h-4 w-4" />
</Button>
</div> </div>
<Badge variant="default" class="text-xs">
{{ group.paidCount }} paid
</Badge>
</div> </div>
<CardDescription> </div>
Event ID: {{ ticket.event }}
</CardDescription> <!-- Paid Tickets Grid -->
</CardHeader> <div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<CardContent class="flex-grow"> <Card v-for="ticket in group.tickets.filter(t => t.paid)" :key="ticket.id" class="flex flex-col">
<div class="space-y-2"> <CardHeader>
<div class="flex justify-between"> <div class="flex items-center justify-between">
<span class="text-muted-foreground">Status:</span> <CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
<div class="flex items-center gap-1"> <div class="flex items-center gap-2">
<component :is="getTicketStatus(ticket).icon" class="w-4 h-4" :class="getTicketStatus(ticket).color" /> <Badge variant="default">
<span class="text-sm">{{ getTicketStatus(ticket).label }}</span> {{ getTicketStatus(ticket).label }}
</div> </Badge>
</div> <Button
<div class="flex justify-between"> variant="ghost"
<span class="text-muted-foreground">Purchased:</span> size="sm"
<span class="text-sm">{{ formatDate(ticket.time) }}</span> @click="toggleQRCode(ticket.id)"
</div> class="h-6 w-6 p-0"
<div class="flex justify-between"> >
<span class="text-muted-foreground">Time:</span> <QrCode class="h-4 w-4" />
<span class="text-sm">{{ formatTime(ticket.time) }}</span> </Button>
</div>
<div v-if="ticket.reg_timestamp" class="flex justify-between">
<span class="text-muted-foreground">Registered:</span>
<span class="text-sm">{{ formatDate(ticket.reg_timestamp) }}</span>
</div>
<!-- QR Code Section -->
<div v-if="showQRCode[ticket.id]" class="mt-4 pt-4 border-t">
<div class="flex flex-col items-center space-y-2">
<img
v-if="qrCodes[ticket.id]"
:src="qrCodes[ticket.id]"
alt="Ticket QR Code"
class="w-32 h-32 border rounded-lg"
/>
<div v-else class="w-32 h-32 border rounded-lg flex items-center justify-center">
<span class="text-xs text-muted-foreground">Loading...</span>
</div> </div>
<div class="text-center"> </div>
<p class="text-xs text-muted-foreground">Ticket ID</p> </CardHeader>
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1"> <CardContent class="flex-grow">
<p class="text-xs font-mono break-all">{{ ticket.id }}</p> <div class="space-y-2">
<div class="flex justify-between">
<span class="text-muted-foreground">Status:</span>
<div class="flex items-center gap-1">
<component :is="getTicketStatus(ticket).icon" class="w-4 h-4" :class="getTicketStatus(ticket).color" />
<span class="text-sm">{{ getTicketStatus(ticket).label }}</span>
</div>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Purchased:</span>
<span class="text-sm">{{ formatDate(ticket.time) }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Time:</span>
<span class="text-sm">{{ formatTime(ticket.time) }}</span>
</div>
<div v-if="ticket.reg_timestamp" class="flex justify-between">
<span class="text-muted-foreground">Registered:</span>
<span class="text-sm">{{ formatDate(ticket.reg_timestamp) }}</span>
</div>
<!-- QR Code Section -->
<div v-if="showQRCode[ticket.id]" class="mt-4 pt-4 border-t">
<div class="flex flex-col items-center space-y-2">
<img
v-if="qrCodes[ticket.id]"
:src="qrCodes[ticket.id]"
alt="Ticket QR Code"
class="w-32 h-32 border rounded-lg"
/>
<div v-else class="w-32 h-32 border rounded-lg flex items-center justify-center">
<span class="text-xs text-muted-foreground">Loading...</span>
</div>
<div class="text-center">
<p class="text-xs text-muted-foreground">Ticket ID</p>
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1">
<p class="text-xs font-mono break-all">{{ ticket.id }}</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </CardContent>
</div> </Card>
</CardContent> </div>
</Card> </div>
</div> </div>
</ScrollArea> </ScrollArea>
</TabsContent> </TabsContent>
<TabsContent value="pending"> <TabsContent value="pending">
<ScrollArea class="h-[600px] w-full pr-4"> <ScrollArea class="h-[600px] w-full pr-4">
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div class="space-y-4">
<Card v-for="ticket in pendingTickets" :key="ticket.id" class="flex flex-col opacity-75"> <div v-for="group in groupedTickets.filter(g => g.pendingCount > 0)" :key="group.eventId" class="space-y-3">
<CardHeader> <!-- Event Header -->
<div class="bg-muted/50 rounded-lg p-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle> <div>
<div class="flex items-center gap-2"> <h3 class="font-semibold text-foreground">Event: {{ group.eventId }}</h3>
<Badge variant="secondary"> <p class="text-sm text-muted-foreground">
{{ getTicketStatus(ticket).label }} {{ group.pendingCount }} pending ticket{{ group.pendingCount !== 1 ? 's' : '' }}
</Badge> </p>
<Button
variant="ghost"
size="sm"
@click="toggleQRCode(ticket.id)"
class="h-6 w-6 p-0"
>
<QrCode class="h-4 w-4" />
</Button>
</div> </div>
<Badge variant="secondary" class="text-xs">
{{ group.pendingCount }} pending
</Badge>
</div> </div>
<CardDescription> </div>
Event ID: {{ ticket.event }}
</CardDescription> <!-- Pending Tickets Grid -->
</CardHeader> <div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<CardContent class="flex-grow"> <Card v-for="ticket in group.tickets.filter(t => !t.paid)" :key="ticket.id" class="flex flex-col opacity-75">
<div class="space-y-2"> <CardHeader>
<div class="flex justify-between"> <div class="flex items-center justify-between">
<span class="text-muted-foreground">Status:</span> <CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
<div class="flex items-center gap-1"> <div class="flex items-center gap-2">
<component :is="getTicketStatus(ticket).icon" class="w-4 h-4" :class="getTicketStatus(ticket).color" /> <Badge variant="secondary">
<span class="text-sm">{{ getTicketStatus(ticket).label }}</span> {{ getTicketStatus(ticket).label }}
</div> </Badge>
</div> <Button
<div class="flex justify-between"> variant="ghost"
<span class="text-muted-foreground">Created:</span> size="sm"
<span class="text-sm">{{ formatDate(ticket.time) }}</span> @click="toggleQRCode(ticket.id)"
</div> class="h-6 w-6 p-0"
<div class="flex justify-between"> >
<span class="text-muted-foreground">Time:</span> <QrCode class="h-4 w-4" />
<span class="text-sm">{{ formatTime(ticket.time) }}</span> </Button>
</div>
<!-- QR Code Section -->
<div v-if="showQRCode[ticket.id]" class="mt-4 pt-4 border-t">
<div class="flex flex-col items-center space-y-2">
<img
v-if="qrCodes[ticket.id]"
:src="qrCodes[ticket.id]"
alt="Ticket QR Code"
class="w-32 h-32 border rounded-lg"
/>
<div v-else class="w-32 h-32 border rounded-lg flex items-center justify-center">
<span class="text-xs text-muted-foreground">Loading...</span>
</div> </div>
<div class="text-center"> </div>
<p class="text-xs text-muted-foreground">Ticket ID</p> </CardHeader>
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1"> <CardContent class="flex-grow">
<p class="text-xs font-mono break-all">{{ ticket.id }}</p> <div class="space-y-2">
<div class="flex justify-between">
<span class="text-muted-foreground">Status:</span>
<div class="flex items-center gap-1">
<component :is="getTicketStatus(ticket).icon" class="w-4 h-4" :class="getTicketStatus(ticket).color" />
<span class="text-sm">{{ getTicketStatus(ticket).label }}</span>
</div>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Created:</span>
<span class="text-sm">{{ formatDate(ticket.time) }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Time:</span>
<span class="text-sm">{{ formatTime(ticket.time) }}</span>
</div>
<!-- QR Code Section -->
<div v-if="showQRCode[ticket.id]" class="mt-4 pt-4 border-t">
<div class="flex flex-col items-center space-y-2">
<img
v-if="qrCodes[ticket.id]"
:src="qrCodes[ticket.id]"
alt="Ticket QR Code"
class="w-32 h-32 border rounded-lg"
/>
<div v-else class="w-32 h-32 border rounded-lg flex items-center justify-center">
<span class="text-xs text-muted-foreground">Loading...</span>
</div>
<div class="text-center">
<p class="text-xs text-muted-foreground">Ticket ID</p>
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1">
<p class="text-xs font-mono break-all">{{ ticket.id }}</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </CardContent>
</div> </Card>
</CardContent> </div>
</Card> </div>
</div> </div>
</ScrollArea> </ScrollArea>
</TabsContent> </TabsContent>
<TabsContent value="registered"> <TabsContent value="registered">
<ScrollArea class="h-[600px] w-full pr-4"> <ScrollArea class="h-[600px] w-full pr-4">
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div class="space-y-4">
<Card v-for="ticket in registeredTickets" :key="ticket.id" class="flex flex-col"> <div v-for="group in groupedTickets.filter(g => g.registeredCount > 0)" :key="group.eventId" class="space-y-3">
<CardHeader> <!-- Event Header -->
<div class="bg-muted/50 rounded-lg p-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle> <div>
<div class="flex items-center gap-2"> <h3 class="font-semibold text-foreground">Event: {{ group.eventId }}</h3>
<Badge variant="default"> <p class="text-sm text-muted-foreground">
{{ getTicketStatus(ticket).label }} {{ group.registeredCount }} registered ticket{{ group.registeredCount !== 1 ? 's' : '' }}
</Badge> </p>
<Button
variant="ghost"
size="sm"
@click="toggleQRCode(ticket.id)"
class="h-6 w-6 p-0"
>
<QrCode class="h-4 w-4" />
</Button>
</div> </div>
<Badge variant="default" class="text-xs">
{{ group.registeredCount }} registered
</Badge>
</div> </div>
<CardDescription> </div>
Event ID: {{ ticket.event }}
</CardDescription> <!-- Registered Tickets Grid -->
</CardHeader> <div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<CardContent class="flex-grow"> <Card v-for="ticket in group.tickets.filter(t => t.registered)" :key="ticket.id" class="flex flex-col">
<div class="space-y-2"> <CardHeader>
<div class="flex justify-between"> <div class="flex items-center justify-between">
<span class="text-muted-foreground">Status:</span> <CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
<div class="flex items-center gap-1"> <div class="flex items-center gap-2">
<component :is="getTicketStatus(ticket).icon" class="w-4 h-4" :class="getTicketStatus(ticket).color" /> <Badge variant="default">
<span class="text-sm">{{ getTicketStatus(ticket).label }}</span> {{ getTicketStatus(ticket).label }}
</div> </Badge>
</div> <Button
<div class="flex justify-between"> variant="ghost"
<span class="text-muted-foreground">Purchased:</span> size="sm"
<span class="text-sm">{{ formatDate(ticket.time) }}</span> @click="toggleQRCode(ticket.id)"
</div> class="h-6 w-6 p-0"
<div class="flex justify-between"> >
<span class="text-muted-foreground">Time:</span> <QrCode class="h-4 w-4" />
<span class="text-sm">{{ formatTime(ticket.time) }}</span> </Button>
</div>
<div v-if="ticket.reg_timestamp" class="flex justify-between">
<span class="text-muted-foreground">Registered:</span>
<span class="text-sm">{{ formatDate(ticket.reg_timestamp) }}</span>
</div>
<!-- QR Code Section -->
<div v-if="showQRCode[ticket.id]" class="mt-4 pt-4 border-t">
<div class="flex flex-col items-center space-y-2">
<img
v-if="qrCodes[ticket.id]"
:src="qrCodes[ticket.id]"
alt="Ticket QR Code"
class="w-32 h-32 border rounded-lg"
/>
<div v-else class="w-32 h-32 border rounded-lg flex items-center justify-center">
<span class="text-xs text-muted-foreground">Loading...</span>
</div> </div>
<div class="text-center"> </div>
<p class="text-xs text-muted-foreground">Ticket ID</p> </CardHeader>
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1"> <CardContent class="flex-grow">
<p class="text-xs font-mono break-all">{{ ticket.id }}</p> <div class="space-y-2">
<div class="flex justify-between">
<span class="text-muted-foreground">Status:</span>
<div class="flex items-center gap-1">
<component :is="getTicketStatus(ticket).icon" class="w-4 h-4" :class="getTicketStatus(ticket).color" />
<span class="text-sm">{{ getTicketStatus(ticket).label }}</span>
</div>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Purchased:</span>
<span class="text-sm">{{ formatDate(ticket.time) }}</span>
</div>
<div class="flex justify-between">
<span class="text-muted-foreground">Time:</span>
<span class="text-sm">{{ formatTime(ticket.time) }}</span>
</div>
<div v-if="ticket.reg_timestamp" class="flex justify-between">
<span class="text-muted-foreground">Registered:</span>
<span class="text-sm">{{ formatDate(ticket.reg_timestamp) }}</span>
</div>
<!-- QR Code Section -->
<div v-if="showQRCode[ticket.id]" class="mt-4 pt-4 border-t">
<div class="flex flex-col items-center space-y-2">
<img
v-if="qrCodes[ticket.id]"
:src="qrCodes[ticket.id]"
alt="Ticket QR Code"
class="w-32 h-32 border rounded-lg"
/>
<div v-else class="w-32 h-32 border rounded-lg flex items-center justify-center">
<span class="text-xs text-muted-foreground">Loading...</span>
</div>
<div class="text-center">
<p class="text-xs text-muted-foreground">Ticket ID</p>
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1">
<p class="text-xs font-mono break-all">{{ ticket.id }}</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </CardContent>
</div> </Card>
</CardContent> </div>
</Card> </div>
</div> </div>
</ScrollArea> </ScrollArea>
</TabsContent> </TabsContent>