'use client'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import { CalendarIcon, MapPinIcon, ArrowDownTrayIcon, TicketIcon, } from '@heroicons/react/24/outline'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { UserTicket } from '@/lib/api'; import { formatDateLong, parseDate } from '@/lib/utils'; import { StatusPill, deriveTicketStatus } from './_shared/status'; import { groupByBooking, isUnpaid, isOnHold, ticketAmount, ticketPdfUrl, pyg, HOLD_THRESHOLD_HOURS, type BookingGroup, } from './_shared/helpers'; import PayActions from './_shared/PayActions'; import QrTicketModal from './_shared/QrTicketModal'; interface TicketsTabProps { tickets: UserTicket[]; language: string; onChange: () => void; } type Filter = 'all' | 'upcoming' | 'past'; export default function TicketsTab({ tickets, language: locale, onChange }: TicketsTabProps) { const [filter, setFilter] = useState('all'); const [qrTickets, setQrTickets] = useState(null); // Group multi-ticket bookings so both QRs sit together, then filter by the // group's event date. const groups = useMemo(() => { const now = Date.now(); return groupByBooking(tickets).filter((group) => { if (filter === 'all') return true; const start = group.tickets[0].event?.startDatetime; if (!start) return false; // undated tickets only show under "All" const isUpcoming = parseDate(start).getTime() > now; return filter === 'upcoming' ? isUpcoming : !isUpcoming; }); }, [tickets, filter]); const chips: { id: Filter; label: { en: string; es: string } }[] = [ { id: 'all', label: { en: 'All', es: 'Todas' } }, { id: 'upcoming', label: { en: 'Upcoming', es: 'Próximas' } }, { id: 'past', label: { en: 'Past', es: 'Pasadas' } }, ]; return (
{/* Filter chips */}
{chips.map((chip) => ( ))}
{groups.length === 0 ? (

{locale === 'es' ? 'No tienes entradas' : 'You have no tickets'}

) : (
{groups.map((group) => ( setQrTickets(group.tickets)} /> ))}
)} {qrTickets && ( setQrTickets(null)} /> )}
); } function BookingCard({ group, locale, onChange, onShowQr, }: { group: BookingGroup; locale: string; onChange: () => void; onShowQr: () => void; }) { const ticket = group.tickets[0]; const event = ticket.event; const title = (locale === 'es' && event?.titleEs ? event.titleEs : event?.title) || 'Event'; const status = deriveTicketStatus(ticket.status, ticket.payment?.status); const { amount, currency } = ticketAmount(ticket); const multi = group.tickets.length > 1; const ready = status === 'confirmed' || status === 'attended'; return (
{/* Image */}
{event?.bannerUrl ? ( {title} ) : (
)}
{/* Info */}

{title} {multi && ( {locale === 'es' ? `${group.tickets.length} entradas` : `${group.tickets.length} tickets`} )}

{event?.startDatetime && (

{formatDateLong(event.startDatetime, locale as 'en' | 'es')}

)} {event?.location && (

{event.location}

)}

{pyg(amount, currency)}

{isOnHold(ticket) && (

{locale === 'es' ? `Tu lugar fue liberado porque el pago no se confirmó dentro de ${HOLD_THRESHOLD_HOURS} horas.` : `Your spot has been released because payment was not confirmed within ${HOLD_THRESHOLD_HOURS} hours.`}

)}
{/* Actions */}
{isOnHold(ticket) ? ( ) : isUnpaid(ticket) ? ( ) : ( <> {ready && ( )} {ready && ( )} {ticket.invoice && ( )} )}
); }