feat: Enhance QR code generation and ticket navigation in MyTickets
- Implement functionality to always generate QR codes for all tickets, improving accessibility. - Refactor ticket navigation to automatically generate QR codes for the next and previous tickets when cycling through them. - Update layout for better visibility of ticket details and QR codes, ensuring a consistent user experience across different ticket statuses. - Introduce a watcher to generate QR codes when grouped tickets change, enhancing real-time updates.
This commit is contained in:
parent
774df2757d
commit
deabf9464a
1 changed files with 233 additions and 229 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- eslint-disable vue/multi-word-component-names -->
|
<!-- eslint-disable vue/multi-word-component-names -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
import { useUserTickets } from '@/composables/useUserTickets'
|
import { useUserTickets } from '@/composables/useUserTickets'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
import { useAuth } from '@/composables/useAuth'
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
|
@ -24,9 +24,8 @@ const {
|
||||||
refresh
|
refresh
|
||||||
} = useUserTickets()
|
} = useUserTickets()
|
||||||
|
|
||||||
// QR code state
|
// QR code state - now always generate QR codes for all tickets
|
||||||
const qrCodes = ref<Record<string, string>>({})
|
const qrCodes = ref<Record<string, string>>({})
|
||||||
const showQRCode = ref<Record<string, boolean>>({})
|
|
||||||
|
|
||||||
// Ticket cycling state
|
// Ticket cycling state
|
||||||
const currentTicketIndex = ref<Record<string, number>>({})
|
const currentTicketIndex = ref<Record<string, number>>({})
|
||||||
|
|
@ -52,7 +51,7 @@ async function generateQRCode(ticketId: string) {
|
||||||
const qrcode = await import('qrcode')
|
const qrcode = await import('qrcode')
|
||||||
const ticketUrl = `ticket://${ticketId}`
|
const ticketUrl = `ticket://${ticketId}`
|
||||||
const dataUrl = await qrcode.toDataURL(ticketUrl, {
|
const dataUrl = await qrcode.toDataURL(ticketUrl, {
|
||||||
width: 128,
|
width: 200, // Larger QR code for easier scanning
|
||||||
margin: 2,
|
margin: 2,
|
||||||
color: {
|
color: {
|
||||||
dark: '#000000',
|
dark: '#000000',
|
||||||
|
|
@ -67,10 +66,12 @@ async function generateQRCode(ticketId: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleQRCode(ticketId: string) {
|
// Generate QR codes for all tickets in a group
|
||||||
showQRCode.value[ticketId] = !showQRCode.value[ticketId]
|
async function generateAllQRCodes(tickets: any[]) {
|
||||||
if (showQRCode.value[ticketId] && !qrCodes.value[ticketId]) {
|
for (const ticket of tickets) {
|
||||||
generateQRCode(ticketId)
|
if (!qrCodes.value[ticket.id]) {
|
||||||
|
await generateQRCode(ticket.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,14 +84,34 @@ function setCurrentTicketIndex(eventId: string, index: number) {
|
||||||
currentTicketIndex.value[eventId] = index
|
currentTicketIndex.value[eventId] = index
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextTicket(eventId: string, totalTickets: number) {
|
async function nextTicket(eventId: string, totalTickets: number) {
|
||||||
const current = getCurrentTicketIndex(eventId)
|
const current = getCurrentTicketIndex(eventId)
|
||||||
setCurrentTicketIndex(eventId, (current + 1) % totalTickets)
|
const nextIndex = (current + 1) % totalTickets
|
||||||
|
setCurrentTicketIndex(eventId, nextIndex)
|
||||||
|
|
||||||
|
// Generate QR code for the new ticket if needed
|
||||||
|
const group = groupedTickets.value.find(g => g.eventId === eventId)
|
||||||
|
if (group) {
|
||||||
|
const newTicket = group.tickets[nextIndex]
|
||||||
|
if (newTicket && !qrCodes.value[newTicket.id]) {
|
||||||
|
await generateQRCode(newTicket.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevTicket(eventId: string, totalTickets: number) {
|
async function prevTicket(eventId: string, totalTickets: number) {
|
||||||
const current = getCurrentTicketIndex(eventId)
|
const current = getCurrentTicketIndex(eventId)
|
||||||
setCurrentTicketIndex(eventId, current === 0 ? totalTickets - 1 : current - 1)
|
const prevIndex = current === 0 ? totalTickets - 1 : current - 1
|
||||||
|
setCurrentTicketIndex(eventId, prevIndex)
|
||||||
|
|
||||||
|
// Generate QR code for the new ticket if needed
|
||||||
|
const group = groupedTickets.value.find(g => g.eventId === eventId)
|
||||||
|
if (group) {
|
||||||
|
const newTicket = group.tickets[prevIndex]
|
||||||
|
if (newTicket && !qrCodes.value[newTicket.id]) {
|
||||||
|
await generateQRCode(newTicket.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentTicket(tickets: any[], eventId: string) {
|
function getCurrentTicket(tickets: any[], eventId: string) {
|
||||||
|
|
@ -98,6 +119,17 @@ function getCurrentTicket(tickets: any[], eventId: string) {
|
||||||
return tickets[index] || tickets[0]
|
return tickets[index] || tickets[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch for changes in grouped tickets and generate QR codes
|
||||||
|
watch(groupedTickets, async (newGroups) => {
|
||||||
|
for (const group of newGroups) {
|
||||||
|
for (const ticket of group.tickets) {
|
||||||
|
if (!qrCodes.value[ticket.id]) {
|
||||||
|
await generateQRCode(ticket.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (isAuthenticated.value) {
|
if (isAuthenticated.value) {
|
||||||
await refresh()
|
await refresh()
|
||||||
|
|
@ -159,7 +191,7 @@ 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="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<Card v-for="group in groupedTickets" :key="group.eventId" class="flex flex-col">
|
<Card v-for="group in groupedTickets" :key="group.eventId" class="flex flex-col">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
|
@ -198,66 +230,59 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Current Ticket Display -->
|
<!-- Current Ticket Display -->
|
||||||
<div v-if="getCurrentTicket(group.tickets, group.eventId)" class="space-y-3">
|
<div v-if="getCurrentTicket(group.tickets, group.eventId)" class="space-y-4">
|
||||||
<div class="flex items-center justify-between">
|
<!-- QR Code - Always Visible -->
|
||||||
<span class="text-sm font-medium">
|
<div class="flex justify-center">
|
||||||
Ticket #{{ getCurrentTicket(group.tickets, group.eventId).id.slice(0, 8) }}
|
<div class="text-center space-y-2">
|
||||||
</span>
|
<img
|
||||||
<div class="flex items-center gap-2">
|
v-if="qrCodes[getCurrentTicket(group.tickets, group.eventId).id]"
|
||||||
<Badge :variant="getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).status === 'pending' ? 'secondary' : 'default'">
|
:src="qrCodes[getCurrentTicket(group.tickets, group.eventId).id]"
|
||||||
{{ getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).label }}
|
alt="Ticket QR Code"
|
||||||
</Badge>
|
class="w-48 h-48 border rounded-lg mx-auto"
|
||||||
<Button
|
/>
|
||||||
variant="ghost"
|
<div v-else class="w-48 h-48 border rounded-lg flex items-center justify-center mx-auto">
|
||||||
size="sm"
|
<span class="text-xs text-muted-foreground">Loading...</span>
|
||||||
@click="toggleQRCode(getCurrentTicket(group.tickets, group.eventId).id)"
|
</div>
|
||||||
class="h-6 w-6 p-0"
|
<div class="text-center">
|
||||||
>
|
<p class="text-xs text-muted-foreground">Ticket ID</p>
|
||||||
<QrCode class="h-4 w-4" />
|
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1">
|
||||||
</Button>
|
<p class="text-xs font-mono break-all">{{ getCurrentTicket(group.tickets, group.eventId).id }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Ticket Details -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-muted-foreground">Status:</span>
|
<span class="text-sm font-medium">
|
||||||
<div class="flex items-center gap-1">
|
Ticket #{{ getCurrentTicket(group.tickets, group.eventId).id.slice(0, 8) }}
|
||||||
<component :is="getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).icon" class="w-4 h-4" :class="getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).color" />
|
</span>
|
||||||
<span class="text-sm">{{ getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).label }}</span>
|
<Badge :variant="getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).status === 'pending' ? 'secondary' : 'default'">
|
||||||
</div>
|
{{ getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).label }}
|
||||||
</div>
|
</Badge>
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Purchased:</span>
|
|
||||||
<span class="text-sm">{{ formatDate(getCurrentTicket(group.tickets, group.eventId).time) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Time:</span>
|
|
||||||
<span class="text-sm">{{ formatTime(getCurrentTicket(group.tickets, group.eventId).time) }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="getCurrentTicket(group.tickets, group.eventId).reg_timestamp" class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Registered:</span>
|
|
||||||
<span class="text-sm">{{ formatDate(getCurrentTicket(group.tickets, group.eventId).reg_timestamp) }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- QR Code Section -->
|
<div class="space-y-1 text-sm">
|
||||||
<div v-if="showQRCode[getCurrentTicket(group.tickets, group.eventId).id]" class="mt-4 pt-4 border-t">
|
<div class="flex justify-between">
|
||||||
<div class="flex flex-col items-center space-y-2">
|
<span class="text-muted-foreground">Status:</span>
|
||||||
<img
|
<div class="flex items-center gap-1">
|
||||||
v-if="qrCodes[getCurrentTicket(group.tickets, group.eventId).id]"
|
<component :is="getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).icon" class="w-4 h-4" :class="getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).color" />
|
||||||
:src="qrCodes[getCurrentTicket(group.tickets, group.eventId).id]"
|
<span>{{ getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).label }}</span>
|
||||||
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">{{ getCurrentTicket(group.tickets, group.eventId).id }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Purchased:</span>
|
||||||
|
<span>{{ formatDate(getCurrentTicket(group.tickets, group.eventId).time) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Time:</span>
|
||||||
|
<span>{{ formatTime(getCurrentTicket(group.tickets, group.eventId).time) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="getCurrentTicket(group.tickets, group.eventId).reg_timestamp" class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Registered:</span>
|
||||||
|
<span>{{ formatDate(getCurrentTicket(group.tickets, group.eventId).reg_timestamp) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -270,7 +295,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
<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="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<Card v-for="group in groupedTickets.filter(g => g.paidCount > 0)" :key="group.eventId" class="flex flex-col">
|
<Card v-for="group in groupedTickets.filter(g => g.paidCount > 0)" :key="group.eventId" class="flex flex-col">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
|
@ -309,66 +334,59 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Current Ticket Display -->
|
<!-- Current Ticket Display -->
|
||||||
<div v-if="getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)" class="space-y-3">
|
<div v-if="getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)" class="space-y-4">
|
||||||
<div class="flex items-center justify-between">
|
<!-- QR Code - Always Visible -->
|
||||||
<span class="text-sm font-medium">
|
<div class="flex justify-center">
|
||||||
Ticket #{{ getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id.slice(0, 8) }}
|
<div class="text-center space-y-2">
|
||||||
</span>
|
<img
|
||||||
<div class="flex items-center gap-2">
|
v-if="qrCodes[getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id]"
|
||||||
<Badge variant="default">
|
:src="qrCodes[getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id]"
|
||||||
{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)).label }}
|
alt="Ticket QR Code"
|
||||||
</Badge>
|
class="w-48 h-48 border rounded-lg mx-auto"
|
||||||
<Button
|
/>
|
||||||
variant="ghost"
|
<div v-else class="w-48 h-48 border rounded-lg flex items-center justify-center mx-auto">
|
||||||
size="sm"
|
<span class="text-xs text-muted-foreground">Loading...</span>
|
||||||
@click="toggleQRCode(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id)"
|
</div>
|
||||||
class="h-6 w-6 p-0"
|
<div class="text-center">
|
||||||
>
|
<p class="text-xs text-muted-foreground">Ticket ID</p>
|
||||||
<QrCode class="h-4 w-4" />
|
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1">
|
||||||
</Button>
|
<p class="text-xs font-mono break-all">{{ getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Ticket Details -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-muted-foreground">Status:</span>
|
<span class="text-sm font-medium">
|
||||||
<div class="flex items-center gap-1">
|
Ticket #{{ getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id.slice(0, 8) }}
|
||||||
<component :is="getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)).icon" class="w-4 h-4" :class="getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)).color" />
|
</span>
|
||||||
<span class="text-sm">{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)).label }}</span>
|
<Badge variant="default">
|
||||||
</div>
|
{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)).label }}
|
||||||
</div>
|
</Badge>
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Purchased:</span>
|
|
||||||
<span class="text-sm">{{ formatDate(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).time) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Time:</span>
|
|
||||||
<span class="text-sm">{{ formatTime(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).time) }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).reg_timestamp" class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Registered:</span>
|
|
||||||
<span class="text-sm">{{ formatDate(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).reg_timestamp) }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- QR Code Section -->
|
<div class="space-y-1 text-sm">
|
||||||
<div v-if="showQRCode[getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id]" class="mt-4 pt-4 border-t">
|
<div class="flex justify-between">
|
||||||
<div class="flex flex-col items-center space-y-2">
|
<span class="text-muted-foreground">Status:</span>
|
||||||
<img
|
<div class="flex items-center gap-1">
|
||||||
v-if="qrCodes[getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id]"
|
<component :is="getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)).icon" class="w-4 h-4" :class="getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)).color" />
|
||||||
:src="qrCodes[getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id]"
|
<span>{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId)).label }}</span>
|
||||||
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">{{ getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).id }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Purchased:</span>
|
||||||
|
<span>{{ formatDate(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).time) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Time:</span>
|
||||||
|
<span>{{ formatTime(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).time) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).reg_timestamp" class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Registered:</span>
|
||||||
|
<span>{{ formatDate(getCurrentTicket(group.tickets.filter(t => t.paid), group.eventId).reg_timestamp) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -381,8 +399,8 @@ onMounted(async () => {
|
||||||
|
|
||||||
<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="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<Card v-for="group in groupedTickets.filter(g => g.pendingCount > 0)" :key="group.eventId" class="flex flex-col">
|
<Card v-for="group in groupedTickets.filter(g => g.pendingCount > 0)" :key="group.eventId" class="flex flex-col opacity-75">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<CardTitle class="text-foreground">Event: {{ group.eventId }}</CardTitle>
|
<CardTitle class="text-foreground">Event: {{ group.eventId }}</CardTitle>
|
||||||
|
|
@ -420,62 +438,55 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Current Ticket Display -->
|
<!-- Current Ticket Display -->
|
||||||
<div v-if="getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)" class="space-y-3">
|
<div v-if="getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)" class="space-y-4">
|
||||||
<div class="flex items-center justify-between">
|
<!-- QR Code - Always Visible -->
|
||||||
<span class="text-sm font-medium">
|
<div class="flex justify-center">
|
||||||
Ticket #{{ getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id.slice(0, 8) }}
|
<div class="text-center space-y-2">
|
||||||
</span>
|
<img
|
||||||
<div class="flex items-center gap-2">
|
v-if="qrCodes[getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id]"
|
||||||
<Badge variant="secondary">
|
:src="qrCodes[getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id]"
|
||||||
{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)).label }}
|
alt="Ticket QR Code"
|
||||||
</Badge>
|
class="w-48 h-48 border rounded-lg mx-auto"
|
||||||
<Button
|
/>
|
||||||
variant="ghost"
|
<div v-else class="w-48 h-48 border rounded-lg flex items-center justify-center mx-auto">
|
||||||
size="sm"
|
<span class="text-xs text-muted-foreground">Loading...</span>
|
||||||
@click="toggleQRCode(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id)"
|
</div>
|
||||||
class="h-6 w-6 p-0"
|
<div class="text-center">
|
||||||
>
|
<p class="text-xs text-muted-foreground">Ticket ID</p>
|
||||||
<QrCode class="h-4 w-4" />
|
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1">
|
||||||
</Button>
|
<p class="text-xs font-mono break-all">{{ getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Ticket Details -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-muted-foreground">Status:</span>
|
<span class="text-sm font-medium">
|
||||||
<div class="flex items-center gap-1">
|
Ticket #{{ getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id.slice(0, 8) }}
|
||||||
<component :is="getTicketStatus(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)).icon" class="w-4 h-4" :class="getTicketStatus(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)).color" />
|
</span>
|
||||||
<span class="text-sm">{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)).label }}</span>
|
<Badge variant="secondary">
|
||||||
</div>
|
{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)).label }}
|
||||||
</div>
|
</Badge>
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Created:</span>
|
|
||||||
<span class="text-sm">{{ formatDate(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).time) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Time:</span>
|
|
||||||
<span class="text-sm">{{ formatTime(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).time) }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- QR Code Section -->
|
<div class="space-y-1 text-sm">
|
||||||
<div v-if="showQRCode[getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id]" class="mt-4 pt-4 border-t">
|
<div class="flex justify-between">
|
||||||
<div class="flex flex-col items-center space-y-2">
|
<span class="text-muted-foreground">Status:</span>
|
||||||
<img
|
<div class="flex items-center gap-1">
|
||||||
v-if="qrCodes[getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id]"
|
<component :is="getTicketStatus(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)).icon" class="w-4 h-4" :class="getTicketStatus(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)).color" />
|
||||||
:src="qrCodes[getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id]"
|
<span>{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId)).label }}</span>
|
||||||
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">{{ getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).id }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Created:</span>
|
||||||
|
<span>{{ formatDate(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).time) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Time:</span>
|
||||||
|
<span>{{ formatTime(getCurrentTicket(group.tickets.filter(t => !t.paid), group.eventId).time) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -488,7 +499,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
<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="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<Card v-for="group in groupedTickets.filter(g => g.registeredCount > 0)" :key="group.eventId" class="flex flex-col">
|
<Card v-for="group in groupedTickets.filter(g => g.registeredCount > 0)" :key="group.eventId" class="flex flex-col">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
|
|
@ -527,66 +538,59 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Current Ticket Display -->
|
<!-- Current Ticket Display -->
|
||||||
<div v-if="getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)" class="space-y-3">
|
<div v-if="getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)" class="space-y-4">
|
||||||
<div class="flex items-center justify-between">
|
<!-- QR Code - Always Visible -->
|
||||||
<span class="text-sm font-medium">
|
<div class="flex justify-center">
|
||||||
Ticket #{{ getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id.slice(0, 8) }}
|
<div class="text-center space-y-2">
|
||||||
</span>
|
<img
|
||||||
<div class="flex items-center gap-2">
|
v-if="qrCodes[getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id]"
|
||||||
<Badge variant="default">
|
:src="qrCodes[getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id]"
|
||||||
{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)).label }}
|
alt="Ticket QR Code"
|
||||||
</Badge>
|
class="w-48 h-48 border rounded-lg mx-auto"
|
||||||
<Button
|
/>
|
||||||
variant="ghost"
|
<div v-else class="w-48 h-48 border rounded-lg flex items-center justify-center mx-auto">
|
||||||
size="sm"
|
<span class="text-xs text-muted-foreground">Loading...</span>
|
||||||
@click="toggleQRCode(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id)"
|
</div>
|
||||||
class="h-6 w-6 p-0"
|
<div class="text-center">
|
||||||
>
|
<p class="text-xs text-muted-foreground">Ticket ID</p>
|
||||||
<QrCode class="h-4 w-4" />
|
<div class="bg-background border rounded px-2 py-1 max-w-full mt-1">
|
||||||
</Button>
|
<p class="text-xs font-mono break-all">{{ getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Ticket Details -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-muted-foreground">Status:</span>
|
<span class="text-sm font-medium">
|
||||||
<div class="flex items-center gap-1">
|
Ticket #{{ getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id.slice(0, 8) }}
|
||||||
<component :is="getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)).icon" class="w-4 h-4" :class="getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)).color" />
|
</span>
|
||||||
<span class="text-sm">{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)).label }}</span>
|
<Badge variant="default">
|
||||||
</div>
|
{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)).label }}
|
||||||
</div>
|
</Badge>
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Purchased:</span>
|
|
||||||
<span class="text-sm">{{ formatDate(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).time) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Time:</span>
|
|
||||||
<span class="text-sm">{{ formatTime(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).time) }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).reg_timestamp" class="flex justify-between">
|
|
||||||
<span class="text-muted-foreground">Registered:</span>
|
|
||||||
<span class="text-sm">{{ formatDate(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).reg_timestamp) }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- QR Code Section -->
|
<div class="space-y-1 text-sm">
|
||||||
<div v-if="showQRCode[getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id]" class="mt-4 pt-4 border-t">
|
<div class="flex justify-between">
|
||||||
<div class="flex flex-col items-center space-y-2">
|
<span class="text-muted-foreground">Status:</span>
|
||||||
<img
|
<div class="flex items-center gap-1">
|
||||||
v-if="qrCodes[getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id]"
|
<component :is="getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)).icon" class="w-4 h-4" :class="getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)).color" />
|
||||||
:src="qrCodes[getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id]"
|
<span>{{ getTicketStatus(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId)).label }}</span>
|
||||||
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">{{ getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).id }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Purchased:</span>
|
||||||
|
<span>{{ formatDate(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).time) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Time:</span>
|
||||||
|
<span>{{ formatTime(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).time) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).reg_timestamp" class="flex justify-between">
|
||||||
|
<span class="text-muted-foreground">Registered:</span>
|
||||||
|
<span>{{ formatDate(getCurrentTicket(group.tickets.filter(t => t.registered), group.eventId).reg_timestamp) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue