'use client'; import { useState, useEffect } from 'react'; import { useParams, useRouter } from 'next/navigation'; import Link from 'next/link'; import { useLanguage } from '@/context/LanguageContext'; import { eventsApi, ticketsApi, emailsApi, paymentOptionsApi, Event, Ticket, EmailTemplate, PaymentOptionsConfig } from '@/lib/api'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { ArrowLeftIcon, CalendarIcon, MapPinIcon, CurrencyDollarIcon, UsersIcon, TicketIcon, CheckCircleIcon, ClockIcon, XCircleIcon, EnvelopeIcon, PencilIcon, EyeIcon, PaperAirplaneIcon, UserGroupIcon, MagnifyingGlassIcon, FunnelIcon, PlusIcon, ChatBubbleLeftIcon, ArrowUturnLeftIcon, XMarkIcon, CreditCardIcon, BanknotesIcon, BoltIcon, BuildingLibraryIcon, ArrowPathIcon, } from '@heroicons/react/24/outline'; import toast from 'react-hot-toast'; import clsx from 'clsx'; type TabType = 'overview' | 'attendees' | 'tickets' | 'email' | 'payments'; export default function AdminEventDetailPage() { const params = useParams(); const router = useRouter(); const eventId = params.id as string; const { t, locale } = useLanguage(); const [loading, setLoading] = useState(true); const [event, setEvent] = useState(null); const [tickets, setTickets] = useState([]); const [activeTab, setActiveTab] = useState('overview'); // Email state const [templates, setTemplates] = useState([]); const [selectedTemplate, setSelectedTemplate] = useState(''); const [recipientFilter, setRecipientFilter] = useState<'all' | 'confirmed' | 'pending' | 'checked_in'>('confirmed'); const [customMessage, setCustomMessage] = useState(''); const [sending, setSending] = useState(false); const [previewHtml, setPreviewHtml] = useState(null); // Attendees tab state const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState<'all' | 'pending' | 'confirmed' | 'checked_in' | 'cancelled'>('all'); const [showAddAtDoorModal, setShowAddAtDoorModal] = useState(false); const [showManualTicketModal, setShowManualTicketModal] = useState(false); const [showNoteModal, setShowNoteModal] = useState(false); const [selectedTicket, setSelectedTicket] = useState(null); const [noteText, setNoteText] = useState(''); const [addAtDoorForm, setAddAtDoorForm] = useState({ firstName: '', lastName: '', email: '', phone: '', autoCheckin: true, adminNote: '', }); const [manualTicketForm, setManualTicketForm] = useState({ firstName: '', lastName: '', email: '', phone: '', adminNote: '', }); const [submitting, setSubmitting] = useState(false); // Tickets tab state const [ticketSearchQuery, setTicketSearchQuery] = useState(''); const [ticketStatusFilter, setTicketStatusFilter] = useState<'all' | 'confirmed' | 'checked_in'>('all'); // Payment options state const [globalPaymentOptions, setGlobalPaymentOptions] = useState(null); const [paymentOverrides, setPaymentOverrides] = useState>({}); const [hasPaymentOverrides, setHasPaymentOverrides] = useState(false); const [savingPayments, setSavingPayments] = useState(false); const [loadingPayments, setLoadingPayments] = useState(false); useEffect(() => { loadEventData(); }, [eventId]); const loadEventData = async () => { try { const [eventRes, ticketsRes, templatesRes] = await Promise.all([ eventsApi.getById(eventId), ticketsApi.getAll({ eventId }), emailsApi.getTemplates(), ]); setEvent(eventRes.event); setTickets(ticketsRes.tickets); setTemplates(templatesRes.templates.filter(t => t.isActive)); } catch (error) { toast.error('Failed to load event data'); } finally { setLoading(false); } }; const loadPaymentOptions = async () => { if (globalPaymentOptions) return; // Already loaded setLoadingPayments(true); try { const [globalRes, overridesRes] = await Promise.all([ paymentOptionsApi.getGlobal(), paymentOptionsApi.getEventOverrides(eventId), ]); setGlobalPaymentOptions(globalRes.paymentOptions); if (overridesRes.overrides) { setPaymentOverrides(overridesRes.overrides); setHasPaymentOverrides(true); } } catch (error) { toast.error('Failed to load payment options'); } finally { setLoadingPayments(false); } }; // Load payment options when switching to payments tab useEffect(() => { if (activeTab === 'payments') { loadPaymentOptions(); } }, [activeTab]); const getEffectivePaymentOption = (key: K): PaymentOptionsConfig[K] => { if (paymentOverrides[key] !== undefined && paymentOverrides[key] !== null) { return paymentOverrides[key] as PaymentOptionsConfig[K]; } return globalPaymentOptions?.[key] as PaymentOptionsConfig[K]; }; const updatePaymentOverride = ( key: K, value: PaymentOptionsConfig[K] | null ) => { setPaymentOverrides((prev) => ({ ...prev, [key]: value })); setHasPaymentOverrides(true); }; const handleSavePaymentOptions = async () => { setSavingPayments(true); try { await paymentOptionsApi.updateEventOverrides(eventId, paymentOverrides); toast.success(locale === 'es' ? 'Opciones de pago guardadas' : 'Payment options saved'); } catch (error: any) { toast.error(error.message || 'Failed to save payment options'); } finally { setSavingPayments(false); } }; const handleResetToGlobal = async () => { if (!confirm(locale === 'es' ? '¿Resetear a la configuración global? Se eliminarán todas las personalizaciones de este evento.' : 'Reset to global settings? This will remove all customizations for this event.')) { return; } setSavingPayments(true); try { await paymentOptionsApi.deleteEventOverrides(eventId); setPaymentOverrides({}); setHasPaymentOverrides(false); toast.success(locale === 'es' ? 'Restablecido a configuración global' : 'Reset to global settings'); } catch (error: any) { toast.error(error.message || 'Failed to reset payment options'); } finally { setSavingPayments(false); } }; const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', }); }; const formatTime = (dateStr: string) => { return new Date(dateStr).toLocaleTimeString(locale === 'es' ? 'es-ES' : 'en-US', { hour: '2-digit', minute: '2-digit', }); }; const formatCurrency = (amount: number, currency: string) => { if (currency === 'PYG') { return `${amount.toLocaleString('es-PY')} PYG`; } return `$${amount.toFixed(2)} ${currency}`; }; const getTicketsByStatus = (status: string) => { return tickets.filter(t => t.status === status); }; const getFilteredRecipientCount = () => { if (recipientFilter === 'all') return tickets.length; return getTicketsByStatus(recipientFilter).length; }; const getStatusBadge = (status: string) => { const styles: Record = { pending: 'bg-yellow-100 text-yellow-800', confirmed: 'bg-green-100 text-green-800', cancelled: 'bg-red-100 text-red-800', checked_in: 'bg-blue-100 text-blue-800', }; return ( {status.replace('_', ' ')} ); }; const handleMarkPaid = async (ticketId: string) => { try { await ticketsApi.markPaid(ticketId); toast.success('Payment marked as received'); loadEventData(); } catch (error: any) { toast.error(error.message || 'Failed to mark payment'); } }; const handleCheckin = async (ticketId: string) => { try { await ticketsApi.checkin(ticketId); toast.success('Attendee checked in'); loadEventData(); } catch (error: any) { toast.error(error.message || 'Failed to check in'); } }; const handleRemoveCheckin = async (ticketId: string) => { if (!confirm('Are you sure you want to remove the check-in for this attendee?')) return; try { await ticketsApi.removeCheckin(ticketId); toast.success('Check-in removed'); loadEventData(); } catch (error: any) { toast.error(error.message || 'Failed to remove check-in'); } }; const handleOpenNoteModal = (ticket: Ticket) => { setSelectedTicket(ticket); setNoteText(ticket.adminNote || ''); setShowNoteModal(true); }; const handleSaveNote = async () => { if (!selectedTicket) return; setSubmitting(true); try { await ticketsApi.updateNote(selectedTicket.id, noteText); toast.success('Note saved'); setShowNoteModal(false); setSelectedTicket(null); setNoteText(''); loadEventData(); } catch (error: any) { toast.error(error.message || 'Failed to save note'); } finally { setSubmitting(false); } }; const handleAddAtDoor = async (e: React.FormEvent) => { e.preventDefault(); if (!event) return; setSubmitting(true); try { await ticketsApi.adminCreate({ eventId: event.id, firstName: addAtDoorForm.firstName, lastName: addAtDoorForm.lastName || undefined, email: addAtDoorForm.email, phone: addAtDoorForm.phone, autoCheckin: addAtDoorForm.autoCheckin, adminNote: addAtDoorForm.adminNote || undefined, }); toast.success(addAtDoorForm.autoCheckin ? 'Attendee added and checked in' : 'Attendee added'); setShowAddAtDoorModal(false); setAddAtDoorForm({ firstName: '', lastName: '', email: '', phone: '', autoCheckin: true, adminNote: '' }); loadEventData(); } catch (error: any) { toast.error(error.message || 'Failed to add attendee'); } finally { setSubmitting(false); } }; const handleManualTicket = async (e: React.FormEvent) => { e.preventDefault(); if (!event) return; setSubmitting(true); try { await ticketsApi.manualCreate({ eventId: event.id, firstName: manualTicketForm.firstName, lastName: manualTicketForm.lastName || undefined, email: manualTicketForm.email, phone: manualTicketForm.phone || undefined, adminNote: manualTicketForm.adminNote || undefined, }); toast.success('Manual ticket created — confirmation email sent'); setShowManualTicketModal(false); setManualTicketForm({ firstName: '', lastName: '', email: '', phone: '', adminNote: '' }); loadEventData(); } catch (error: any) { toast.error(error.message || 'Failed to create manual ticket'); } finally { setSubmitting(false); } }; // Filtered tickets for attendees tab const filteredTickets = tickets.filter((ticket) => { // Status filter if (statusFilter !== 'all' && ticket.status !== statusFilter) { return false; } // Search filter if (searchQuery) { const query = searchQuery.toLowerCase(); const fullName = `${ticket.attendeeFirstName} ${ticket.attendeeLastName || ''}`.trim().toLowerCase(); return ( fullName.includes(query) || (ticket.attendeeEmail?.toLowerCase().includes(query) || false) || (ticket.attendeePhone?.toLowerCase().includes(query) || false) || ticket.id.toLowerCase().includes(query) ); } return true; }); // Filtered tickets for the Tickets tab (only confirmed/checked_in) const confirmedTickets = tickets.filter(t => ['confirmed', 'checked_in'].includes(t.status)); const filteredConfirmedTickets = confirmedTickets.filter((ticket) => { // Status filter if (ticketStatusFilter !== 'all' && ticket.status !== ticketStatusFilter) { return false; } // Search filter if (ticketSearchQuery) { const query = ticketSearchQuery.toLowerCase(); const fullName = `${ticket.attendeeFirstName} ${ticket.attendeeLastName || ''}`.trim().toLowerCase(); return ( fullName.includes(query) || ticket.id.toLowerCase().includes(query) ); } return true; }); const handlePreviewEmail = async () => { if (!selectedTemplate) { toast.error('Please select a template'); return; } try { const res = await emailsApi.preview({ templateSlug: selectedTemplate, variables: { attendeeName: 'John Doe', attendeeEmail: 'john@example.com', ticketId: 'TKT-PREVIEW', eventTitle: event?.title || '', eventDate: event ? formatDate(event.startDatetime) : '', eventTime: event ? formatTime(event.startDatetime) : '', eventLocation: event?.location || '', eventLocationUrl: event?.locationUrl || '', eventPrice: event ? formatCurrency(event.price, event.currency) : '', customMessage: customMessage || 'Your custom message will appear here.', }, locale, }); setPreviewHtml(res.bodyHtml); } catch (error) { toast.error('Failed to preview email'); } }; const handleSendEmail = async () => { if (!selectedTemplate) { toast.error('Please select a template'); return; } const recipientCount = getFilteredRecipientCount(); if (recipientCount === 0) { toast.error('No recipients match the selected filter'); return; } if (!confirm(`Send email to ${recipientCount} ${recipientFilter === 'all' ? 'attendee(s)' : `${recipientFilter} attendee(s)`}?`)) { return; } setSending(true); try { const res = await emailsApi.sendToEvent(eventId, { templateSlug: selectedTemplate, recipientFilter, customVariables: customMessage ? { customMessage } : undefined, }); if (res.success) { toast.success(`Email sent to ${res.sentCount} recipients`); } else { toast.error(`Sent: ${res.sentCount}, Failed: ${res.failedCount}`); } } catch (error: any) { toast.error(error.message || 'Failed to send emails'); } finally { setSending(false); } }; if (loading) { return (
); } if (!event) { return (

Event not found

); } const confirmedCount = getTicketsByStatus('confirmed').length; const pendingCount = getTicketsByStatus('pending').length; const checkedInCount = getTicketsByStatus('checked_in').length; const cancelledCount = getTicketsByStatus('cancelled').length; return (
{/* Header */}

{event.title}

{formatDate(event.startDatetime)}

{/* Stats Cards */}

{event.capacity}

Capacity

{confirmedCount}

Confirmed

{pendingCount}

Pending

{checkedInCount}

Checked In

{formatCurrency(confirmedCount * event.price, event.currency)}

Revenue

{/* Tabs */}
{/* Overview Tab */} {activeTab === 'overview' && (

Event Information

Date & Time

{formatDate(event.startDatetime)}

{formatTime(event.startDatetime)}{event.endDatetime && ` - ${formatTime(event.endDatetime)}`}

Location

{event.location}

{event.locationUrl && ( View on Map )}

Price

{event.price === 0 ? 'Free' : formatCurrency(event.price, event.currency)}

Capacity

{confirmedCount + checkedInCount} / {event.capacity} spots filled

{Math.max(0, event.capacity - confirmedCount - checkedInCount)} spots remaining

Description

{event.description}

{event.descriptionEs && ( <>

Spanish:

{event.descriptionEs}

)}
{event.bannerUrl && (

Event Banner

{event.title}
)}
)} {/* Attendees Tab */} {activeTab === 'attendees' && (
{/* Filters & Actions Bar */}
{/* Search */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow" />
{/* Status Filter */}
{/* Action Buttons */}
{/* Filter Results Summary */} {(searchQuery || statusFilter !== 'all') && (
Showing {filteredTickets.length} of {tickets.length} attendees {(searchQuery || statusFilter !== 'all') && ( )}
)}
{/* Attendees Table */}
{filteredTickets.length === 0 ? ( ) : ( filteredTickets.map((ticket) => ( )) )}
Attendee Contact Status Note Booked Actions
{tickets.length === 0 ? 'No attendees yet' : 'No attendees match the current filters'}

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

ID: {ticket.id.slice(0, 8)}...

{ticket.bookingId && (

📦 {locale === 'es' ? 'Reserva grupal' : 'Group booking'}

)}

{ticket.attendeeEmail}

{ticket.attendeePhone}

{getStatusBadge(ticket.status)} {ticket.checkinAt && (

{new Date(ticket.checkinAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}

)}
{ticket.adminNote ? (

{ticket.adminNote}

) : ( - )}
{new Date(ticket.createdAt).toLocaleDateString()}
{/* Note button */} {ticket.status === 'pending' && ( )} {ticket.status === 'confirmed' && ( )} {ticket.status === 'checked_in' && ( )}
)} {/* Tickets Tab */} {activeTab === 'tickets' && (
{/* Search & Filter Bar */}
{/* Search */}
setTicketSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow" />
{/* Status Filter */}
{(ticketSearchQuery || ticketStatusFilter !== 'all') && (
Showing {filteredConfirmedTickets.length} of {confirmedTickets.length} tickets
)}
{/* Tickets Table */}
{filteredConfirmedTickets.length === 0 ? ( ) : ( filteredConfirmedTickets.map((ticket) => ( )) )}
Attendee Name Ticket ID Booking ID Status Check-in Time Actions
{confirmedTickets.length === 0 ? 'No confirmed tickets yet' : 'No tickets match the current filters'}

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

{ticket.id.slice(0, 8)}... {ticket.bookingId ? ( {ticket.bookingId.slice(0, 8)}... ) : ( )} {ticket.status === 'confirmed' ? ( Valid ) : ( Checked In )} {ticket.checkinAt ? ( new Date(ticket.checkinAt).toLocaleString(locale === 'es' ? 'es-ES' : 'en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }) ) : ( )}
{ticket.status === 'confirmed' && ( )} {ticket.status === 'checked_in' && ( )}
)} {/* Add at Door Modal */} {showAddAtDoorModal && (

Add Attendee at Door

setAddAtDoorForm({ ...addAtDoorForm, firstName: e.target.value })} className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow" placeholder="First name" />
setAddAtDoorForm({ ...addAtDoorForm, lastName: e.target.value })} className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow" placeholder="Last name" />
setAddAtDoorForm({ ...addAtDoorForm, email: e.target.value })} className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow" placeholder="email@example.com" />
setAddAtDoorForm({ ...addAtDoorForm, phone: e.target.value })} className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow" placeholder="+595 981 123456" />