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:
parent
a6a65800a4
commit
67e4c0db87
2 changed files with 358 additions and 240 deletions
|
|
@ -18,6 +18,7 @@ const {
|
|||
pendingTickets,
|
||||
registeredTickets,
|
||||
unregisteredTickets,
|
||||
groupedTickets,
|
||||
isLoading,
|
||||
error,
|
||||
refresh
|
||||
|
|
@ -131,292 +132,363 @@ onMounted(async () => {
|
|||
|
||||
<TabsContent value="all">
|
||||
<ScrollArea class="h-[600px] w-full pr-4">
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="ticket in tickets" :key="ticket.id" class="flex flex-col">
|
||||
<CardHeader>
|
||||
<div class="space-y-4">
|
||||
<div v-for="group in groupedTickets" :key="group.eventId" class="space-y-3">
|
||||
<!-- Event Header -->
|
||||
<div class="bg-muted/50 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge :variant="getTicketStatus(ticket).status === 'pending' ? 'secondary' : 'default'">
|
||||
{{ getTicketStatus(ticket).label }}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="toggleQRCode(ticket.id)"
|
||||
class="h-6 w-6 p-0"
|
||||
>
|
||||
<QrCode class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h3 class="font-semibold text-foreground">Event: {{ group.eventId }}</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ group.tickets.length }} ticket{{ group.tickets.length !== 1 ? 's' : '' }} •
|
||||
{{ group.paidCount }} paid •
|
||||
{{ group.pendingCount }} pending •
|
||||
{{ group.registeredCount }} registered
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" class="text-xs">
|
||||
{{ group.tickets.length }} total
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Event ID: {{ ticket.event }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
<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>
|
||||
|
||||
<!-- Tickets Grid -->
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="ticket in group.tickets" :key="ticket.id" class="flex flex-col">
|
||||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge :variant="getTicketStatus(ticket).status === 'pending' ? 'secondary' : 'default'">
|
||||
{{ getTicketStatus(ticket).label }}
|
||||
</Badge>
|
||||
<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 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>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
<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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="paid">
|
||||
<ScrollArea class="h-[600px] w-full pr-4">
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="ticket in paidTickets" :key="ticket.id" class="flex flex-col">
|
||||
<CardHeader>
|
||||
<div class="space-y-4">
|
||||
<div v-for="group in groupedTickets.filter(g => g.paidCount > 0)" :key="group.eventId" class="space-y-3">
|
||||
<!-- Event Header -->
|
||||
<div class="bg-muted/50 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge variant="default">
|
||||
{{ getTicketStatus(ticket).label }}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="toggleQRCode(ticket.id)"
|
||||
class="h-6 w-6 p-0"
|
||||
>
|
||||
<QrCode class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h3 class="font-semibold text-foreground">Event: {{ group.eventId }}</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ group.paidCount }} paid ticket{{ group.paidCount !== 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="default" class="text-xs">
|
||||
{{ group.paidCount }} paid
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Event ID: {{ ticket.event }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
<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>
|
||||
|
||||
<!-- Paid Tickets Grid -->
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="ticket in group.tickets.filter(t => t.paid)" :key="ticket.id" class="flex flex-col">
|
||||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge variant="default">
|
||||
{{ getTicketStatus(ticket).label }}
|
||||
</Badge>
|
||||
<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 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>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
<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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="pending">
|
||||
<ScrollArea class="h-[600px] w-full pr-4">
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="ticket in pendingTickets" :key="ticket.id" class="flex flex-col opacity-75">
|
||||
<CardHeader>
|
||||
<div class="space-y-4">
|
||||
<div v-for="group in groupedTickets.filter(g => g.pendingCount > 0)" :key="group.eventId" class="space-y-3">
|
||||
<!-- Event Header -->
|
||||
<div class="bg-muted/50 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge variant="secondary">
|
||||
{{ getTicketStatus(ticket).label }}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="toggleQRCode(ticket.id)"
|
||||
class="h-6 w-6 p-0"
|
||||
>
|
||||
<QrCode class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h3 class="font-semibold text-foreground">Event: {{ group.eventId }}</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ group.pendingCount }} pending ticket{{ group.pendingCount !== 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="secondary" class="text-xs">
|
||||
{{ group.pendingCount }} pending
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Event ID: {{ ticket.event }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
<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>
|
||||
|
||||
<!-- Pending Tickets Grid -->
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="ticket in group.tickets.filter(t => !t.paid)" :key="ticket.id" class="flex flex-col opacity-75">
|
||||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge variant="secondary">
|
||||
{{ getTicketStatus(ticket).label }}
|
||||
</Badge>
|
||||
<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 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>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
<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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="registered">
|
||||
<ScrollArea class="h-[600px] w-full pr-4">
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="ticket in registeredTickets" :key="ticket.id" class="flex flex-col">
|
||||
<CardHeader>
|
||||
<div class="space-y-4">
|
||||
<div v-for="group in groupedTickets.filter(g => g.registeredCount > 0)" :key="group.eventId" class="space-y-3">
|
||||
<!-- Event Header -->
|
||||
<div class="bg-muted/50 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge variant="default">
|
||||
{{ getTicketStatus(ticket).label }}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="toggleQRCode(ticket.id)"
|
||||
class="h-6 w-6 p-0"
|
||||
>
|
||||
<QrCode class="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h3 class="font-semibold text-foreground">Event: {{ group.eventId }}</h3>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ group.registeredCount }} registered ticket{{ group.registeredCount !== 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="default" class="text-xs">
|
||||
{{ group.registeredCount }} registered
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Event ID: {{ ticket.event }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
<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>
|
||||
|
||||
<!-- Registered Tickets Grid -->
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="ticket in group.tickets.filter(t => t.registered)" :key="ticket.id" class="flex flex-col">
|
||||
<CardHeader>
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground text-sm">Ticket #{{ ticket.id.slice(0, 8) }}</CardTitle>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge variant="default">
|
||||
{{ getTicketStatus(ticket).label }}
|
||||
</Badge>
|
||||
<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 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>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
<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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue