feat(activities): ticket purchase + Nostr-driven inventory sync #71
2 changed files with 45 additions and 8 deletions
fix(activities): MyTickets tab pills + group header count seats not rows
Last commit fixed the dialog + ActivityDetailPage to read extra.quantity,
but missed three more row-count → seat-count surfaces in
MyTicketsPage:
- Tab pills (All / Paid / Pending / Registered) used
`paidTickets.length` etc. on the filtered row arrays — so a user
who bought 1+5+5+6+3+1+1+1 = 23 seats across 8 rows saw "All
(8)". Now reads from useUserTickets.{total,paid,pending,
registered}Seats which sum extra.quantity.
- Group header badge "{{ group.tickets.length }} tickets" → uses
group.paidCount + pendingCount (already seat-summed by the
previous fix to groupedTickets).
- Group description gains a "({N} purchases)" sub-line when seats
≠ rows so the buyer can see at a glance "you have 23 tickets
across 8 purchases".
- Per-row carousel card grows a `×N` chip next to the truncated
Ticket #ID when that row represents multi-seat — same chip
language as the ActivityDetailPage owned-tickets section.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
commit
ab171b4903
|
|
@ -66,6 +66,21 @@ export function useUserTickets() {
|
|||
return sortedTickets.value.filter(ticket => ticket.paid && !ticket.registered)
|
||||
})
|
||||
|
||||
// Seat counts (sum extra.quantity across rows) so the tab pills
|
||||
// surface "what the buyer bought" rather than "how many purchase
|
||||
// rows are in the database". A multi-ticket purchase is one row
|
||||
// with extra.quantity = N seats.
|
||||
function seatsOnRow(t: ActivityTicket): number {
|
||||
return Math.max(1, t.extra?.quantity ?? 1)
|
||||
}
|
||||
function sumSeats(arr: ActivityTicket[]): number {
|
||||
return arr.reduce((total, t) => total + seatsOnRow(t), 0)
|
||||
}
|
||||
const totalSeats = computed(() => sumSeats(sortedTickets.value))
|
||||
const paidSeats = computed(() => sumSeats(paidTickets.value))
|
||||
const pendingSeats = computed(() => sumSeats(pendingTickets.value))
|
||||
const registeredSeats = computed(() => sumSeats(registeredTickets.value))
|
||||
|
||||
const groupedTickets = computed(() => {
|
||||
const groups = new Map<string, GroupedTickets>()
|
||||
|
||||
|
|
@ -119,6 +134,11 @@ export function useUserTickets() {
|
|||
registeredTickets,
|
||||
unregisteredTickets,
|
||||
groupedTickets,
|
||||
totalSeats,
|
||||
paidSeats,
|
||||
pendingSeats,
|
||||
registeredSeats,
|
||||
seatsOnRow,
|
||||
isLoading,
|
||||
error,
|
||||
refresh: loadTickets,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ const {
|
|||
pendingTickets,
|
||||
registeredTickets,
|
||||
groupedTickets,
|
||||
totalSeats,
|
||||
paidSeats,
|
||||
pendingSeats,
|
||||
registeredSeats,
|
||||
seatsOnRow,
|
||||
isLoading,
|
||||
error,
|
||||
refresh
|
||||
|
|
@ -158,10 +163,10 @@ onMounted(async () => {
|
|||
<div v-else-if="tickets.length > 0">
|
||||
<Tabs default-value="all" class="w-full">
|
||||
<TabsList class="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="all">All ({{ tickets.length }})</TabsTrigger>
|
||||
<TabsTrigger value="paid">Paid ({{ paidTickets.length }})</TabsTrigger>
|
||||
<TabsTrigger value="pending">Pending ({{ pendingTickets.length }})</TabsTrigger>
|
||||
<TabsTrigger value="registered">Registered ({{ registeredTickets.length }})</TabsTrigger>
|
||||
<TabsTrigger value="all">All ({{ totalSeats }})</TabsTrigger>
|
||||
<TabsTrigger value="paid">Paid ({{ paidSeats }})</TabsTrigger>
|
||||
<TabsTrigger value="pending">Pending ({{ pendingSeats }})</TabsTrigger>
|
||||
<TabsTrigger value="registered">Registered ({{ registeredSeats }})</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<!-- All Tickets Tab -->
|
||||
|
|
@ -173,11 +178,14 @@ onMounted(async () => {
|
|||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-foreground">Event: {{ group.eventId.slice(0, 8) }}...</CardTitle>
|
||||
<Badge variant="outline">
|
||||
{{ group.tickets.length }} ticket{{ group.tickets.length !== 1 ? 's' : '' }}
|
||||
{{ group.paidCount + group.pendingCount }} ticket{{ (group.paidCount + group.pendingCount) !== 1 ? 's' : '' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
{{ group.paidCount }} paid · {{ group.pendingCount }} pending · {{ group.registeredCount }} registered
|
||||
<span v-if="group.tickets.length !== (group.paidCount + group.pendingCount)" class="block text-xs">
|
||||
({{ group.tickets.length }} purchase{{ group.tickets.length !== 1 ? 's' : '' }})
|
||||
</span>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="flex-grow">
|
||||
|
|
@ -217,9 +225,18 @@ onMounted(async () => {
|
|||
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium">
|
||||
Ticket #{{ getCurrentTicket(group.tickets, group.eventId).id.slice(0, 8) }}
|
||||
</span>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-sm font-medium">
|
||||
Ticket #{{ getCurrentTicket(group.tickets, group.eventId).id.slice(0, 8) }}
|
||||
</span>
|
||||
<Badge
|
||||
v-if="seatsOnRow(getCurrentTicket(group.tickets, group.eventId)) > 1"
|
||||
variant="secondary"
|
||||
class="text-[10px] font-mono px-1.5"
|
||||
>
|
||||
×{{ seatsOnRow(getCurrentTicket(group.tickets, group.eventId)) }}
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge :variant="getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).status === 'pending' ? 'secondary' : 'default'">
|
||||
{{ getTicketStatus(getCurrentTicket(group.tickets, group.eventId)).label }}
|
||||
</Badge>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue