'use client'; import { useState, useEffect } from 'react'; import { useLanguage } from '@/context/LanguageContext'; import { ticketsApi, eventsApi, Ticket, Event } from '@/lib/api'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { BottomSheet, MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents'; import { TicketIcon, CheckCircleIcon, XCircleIcon, CurrencyDollarIcon, UserIcon, EnvelopeIcon, PhoneIcon, FunnelIcon, MagnifyingGlassIcon, } from '@heroicons/react/24/outline'; import toast from 'react-hot-toast'; import clsx from 'clsx'; interface TicketWithDetails extends Omit { bookingId?: string; event?: Event; payment?: { id: string; ticketId?: string; provider: string; amount: number; currency: string; status: string; reference?: string; createdAt?: string; updatedAt?: string; }; } export default function AdminBookingsPage() { const { locale } = useLanguage(); const [tickets, setTickets] = useState([]); const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [processing, setProcessing] = useState(null); const [selectedEvent, setSelectedEvent] = useState(''); const [selectedStatus, setSelectedStatus] = useState(''); const [selectedPaymentStatus, setSelectedPaymentStatus] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [mobileFilterOpen, setMobileFilterOpen] = useState(false); useEffect(() => { loadData(); }, []); const loadData = async () => { try { const [ticketsRes, eventsRes] = await Promise.all([ ticketsApi.getAll(), eventsApi.getAll(), ]); const ticketsWithDetails = await Promise.all( ticketsRes.tickets.map(async (ticket) => { try { const { ticket: fullTicket } = await ticketsApi.getById(ticket.id); return fullTicket; } catch { return ticket; } }) ); setTickets(ticketsWithDetails); setEvents(eventsRes.events); } catch (error) { toast.error('Failed to load bookings'); } finally { setLoading(false); } }; const handleMarkPaid = async (ticketId: string) => { setProcessing(ticketId); try { await ticketsApi.markPaid(ticketId); toast.success('Payment marked as received'); loadData(); } catch (error: any) { toast.error(error.message || 'Failed to mark payment'); } finally { setProcessing(null); } }; const handleCheckin = async (ticketId: string) => { setProcessing(ticketId); try { await ticketsApi.checkin(ticketId); toast.success('Check-in successful'); loadData(); } catch (error: any) { toast.error(error.message || 'Failed to check in'); } finally { setProcessing(null); } }; const handleCancel = async (ticketId: string) => { if (!confirm('Are you sure you want to cancel this booking?')) return; setProcessing(ticketId); try { await ticketsApi.cancel(ticketId); toast.success('Booking cancelled'); loadData(); } catch (error: any) { toast.error(error.message || 'Failed to cancel'); } finally { setProcessing(null); } }; const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: 'America/Asuncion', }); }; const getStatusColor = (status: string) => { switch (status) { case 'confirmed': return 'bg-green-100 text-green-800'; case 'pending': return 'bg-yellow-100 text-yellow-800'; case 'cancelled': return 'bg-red-100 text-red-800'; case 'checked_in': return 'bg-blue-100 text-blue-800'; default: return 'bg-gray-100 text-gray-800'; } }; const getPaymentStatusColor = (status: string) => { switch (status) { case 'paid': return 'bg-green-100 text-green-800'; case 'pending': return 'bg-yellow-100 text-yellow-800'; case 'failed': case 'cancelled': return 'bg-red-100 text-red-800'; case 'refunded': return 'bg-purple-100 text-purple-800'; default: return 'bg-gray-100 text-gray-800'; } }; const getPaymentMethodLabel = (provider: string) => { switch (provider) { case 'bancard': return 'TPago / Card'; case 'lightning': return 'Bitcoin Lightning'; case 'cash': return 'Cash at Event'; default: return provider; } }; const filteredTickets = tickets.filter((ticket) => { if (selectedEvent && ticket.eventId !== selectedEvent) return false; if (selectedStatus && ticket.status !== selectedStatus) return false; if (selectedPaymentStatus && ticket.payment?.status !== selectedPaymentStatus) return false; if (searchQuery) { const q = searchQuery.toLowerCase(); const name = `${ticket.attendeeFirstName} ${ticket.attendeeLastName || ''}`.toLowerCase(); return name.includes(q) || (ticket.attendeeEmail?.toLowerCase().includes(q) || false); } return true; }); const sortedTickets = [...filteredTickets].sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ); const stats = { total: tickets.length, pending: tickets.filter(t => t.status === 'pending').length, confirmed: tickets.filter(t => t.status === 'confirmed').length, checkedIn: tickets.filter(t => t.status === 'checked_in').length, cancelled: tickets.filter(t => t.status === 'cancelled').length, pendingPayment: tickets.filter(t => t.payment?.status === 'pending').length, }; const getBookingInfo = (ticket: TicketWithDetails) => { if (!ticket.bookingId) { return { ticketCount: 1, bookingTotal: Number(ticket.payment?.amount || 0) }; } const bookingTickets = tickets.filter(t => t.bookingId === ticket.bookingId); return { ticketCount: bookingTickets.length, bookingTotal: bookingTickets.reduce((sum, t) => sum + Number(t.payment?.amount || 0), 0), }; }; const hasActiveFilters = selectedEvent || selectedStatus || selectedPaymentStatus || searchQuery; const clearFilters = () => { setSelectedEvent(''); setSelectedStatus(''); setSelectedPaymentStatus(''); setSearchQuery(''); }; const getPrimaryAction = (ticket: TicketWithDetails) => { if (ticket.status === 'pending' && ticket.payment?.status === 'pending') { return { label: 'Mark Paid', onClick: () => handleMarkPaid(ticket.id), color: 'text-green-600' }; } if (ticket.status === 'confirmed') { return { label: 'Check In', onClick: () => handleCheckin(ticket.id), color: 'text-blue-600' }; } return null; }; if (loading) { return (
); } return (

Manage Bookings

{/* Stats Cards */}

{stats.total}

Total

{stats.pending}

Pending

{stats.confirmed}

Confirmed

{stats.checkedIn}

Checked In

{stats.cancelled}

Cancelled

{stats.pendingPayment}

Pending Pay

{/* Desktop Filters */}
Filters
setSearchQuery(e.target.value)} className="w-full pl-9 pr-3 py-2 rounded-btn border border-secondary-light-gray text-sm focus:outline-none focus:ring-2 focus:ring-primary-yellow" />
{hasActiveFilters && (
Showing {sortedTickets.length} of {tickets.length}
)}
{/* Mobile Toolbar */}
setSearchQuery(e.target.value)} className="w-full pl-9 pr-3 py-2.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow" />
{hasActiveFilters && ( )}
{/* Desktop: Table */}
{sortedTickets.length === 0 ? ( ) : ( sortedTickets.map((ticket) => { const bookingInfo = getBookingInfo(ticket); return ( ); }) )}
Attendee Event Payment Status Booked Actions
No bookings found.

{ticket.attendeeFirstName} {ticket.attendeeLastName || ''}

{ticket.attendeeEmail || 'N/A'}

{ticket.attendeePhone &&

{ticket.attendeePhone}

}
{ticket.event?.title || events.find(e => e.id === ticket.eventId)?.title || 'Unknown'} {ticket.payment?.status || 'pending'}

{getPaymentMethodLabel(ticket.payment?.provider || 'cash')}

{ticket.payment && (

{bookingInfo.bookingTotal.toLocaleString()} {ticket.payment.currency}

)}
{ticket.status.replace('_', ' ')} {ticket.bookingId && (

Group Booking

)}
{formatDate(ticket.createdAt)}
{ticket.status === 'pending' && ticket.payment?.status === 'pending' && ( )} {ticket.status === 'confirmed' && ( )} {(ticket.status === 'pending' || ticket.status === 'confirmed') && ( handleCancel(ticket.id)} className="text-red-600"> Cancel )} {ticket.status === 'checked_in' && ( Attended )} {ticket.status === 'cancelled' && ( Cancelled )}
{/* Mobile: Card List */}
{sortedTickets.length === 0 ? (
No bookings found.
) : ( sortedTickets.map((ticket) => { const bookingInfo = getBookingInfo(ticket); const primary = getPrimaryAction(ticket); const eventTitle = ticket.event?.title || events.find(e => e.id === ticket.eventId)?.title || 'Unknown'; return (

{ticket.attendeeFirstName} {ticket.attendeeLastName || ''}

{ticket.attendeeEmail || 'N/A'}

{ticket.status.replace('_', ' ')}
{eventTitle} | {ticket.payment?.status || 'pending'} {ticket.payment && ( <> | {bookingInfo.bookingTotal.toLocaleString()} {ticket.payment.currency} )}
{ticket.bookingId && (

{bookingInfo.ticketCount} tickets - Group Booking

)}

{formatDate(ticket.createdAt)}

{primary && ( )} {(ticket.status === 'pending' || ticket.status === 'confirmed') && ( {ticket.status === 'pending' && ticket.payment?.status === 'pending' && !primary && ( handleMarkPaid(ticket.id)}> Mark Paid )} handleCancel(ticket.id)} className="text-red-600"> Cancel Booking )} {ticket.status === 'checked_in' && ( Attended )} {ticket.status === 'cancelled' && ( Cancelled )}
); }) )}
{/* Mobile Filter BottomSheet */} setMobileFilterOpen(false)} title="Filters">
{[ { value: '', label: 'All Statuses' }, { value: 'pending', label: `Pending (${stats.pending})` }, { value: 'confirmed', label: `Confirmed (${stats.confirmed})` }, { value: 'checked_in', label: `Checked In (${stats.checkedIn})` }, { value: 'cancelled', label: `Cancelled (${stats.cancelled})` }, ].map((opt) => ( ))}
{[ { value: '', label: 'All Payments' }, { value: 'pending', label: 'Pending' }, { value: 'paid', label: 'Paid' }, { value: 'refunded', label: 'Refunded' }, { value: 'failed', label: 'Failed' }, ].map((opt) => ( ))}
); }