import { Ticket } from '@/lib/api'; import { parseDate, EVENT_TIMEZONE } from '@/lib/utils'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { Dropdown, DropdownItem, MoreMenu } from '@/components/admin/MobileComponents'; import clsx from 'clsx'; import { MagnifyingGlassIcon, ChevronDownIcon, ArrowDownTrayIcon, PlusIcon, EnvelopeIcon, StarIcon, FunnelIcon, ChatBubbleLeftIcon, ArrowPathIcon, BanknotesIcon, CheckCircleIcon, } from '@heroicons/react/24/outline'; import { StatusBadge, PaymentBadge } from '../_components/StatusBadge'; import type { AttendeeStatusFilter, AddTicketType, PrimaryAction } from '../_types'; interface AttendeesTabProps { locale: string; tickets: Ticket[]; filteredTickets: Ticket[]; searchQuery: string; setSearchQuery: (value: string) => void; statusFilter: AttendeeStatusFilter; setStatusFilter: (value: AttendeeStatusFilter) => void; pendingCount: number; confirmedCount: number; checkedInCount: number; cancelledCount: number; onHoldCount: number; exporting: boolean; showExportDropdown: boolean; setShowExportDropdown: (value: boolean) => void; showAddTicketDropdown: boolean; setShowAddTicketDropdown: (value: boolean) => void; handleExportAttendees: (status: 'confirmed' | 'checked_in' | 'confirmed_pending' | 'all') => void; openAddTicket: (type: AddTicketType) => void; setMobileFilterOpen: (value: boolean) => void; setShowExportSheet: (value: boolean) => void; setShowAddTicketSheet: (value: boolean) => void; getPrimaryAction: (ticket: Ticket) => PrimaryAction | null; handleOpenNoteModal: (ticket: Ticket) => void; handleReactivate: (ticket: Ticket) => void; handleMarkPaid: (ticketId: string) => void; handleCheckin: (ticketId: string) => void; } export function AttendeesTab({ locale, tickets, filteredTickets, searchQuery, setSearchQuery, statusFilter, setStatusFilter, pendingCount, confirmedCount, checkedInCount, cancelledCount, onHoldCount, exporting, showExportDropdown, setShowExportDropdown, showAddTicketDropdown, setShowAddTicketDropdown, handleExportAttendees, openAddTicket, setMobileFilterOpen, setShowExportSheet, setShowAddTicketSheet, getPrimaryAction, handleOpenNoteModal, handleReactivate, handleMarkPaid, handleCheckin, }: AttendeesTabProps) { return (
{/* Desktop toolbar */}
{/* Left: Search + Status */}
setSearchQuery(e.target.value)} className="w-full pl-9 pr-3 py-1.5 text-sm rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow" />
{/* Right: Export + Add Ticket dropdown */} {exporting ? (
) : ( )} Export } > handleExportAttendees('all')}>Export All handleExportAttendees('confirmed')}>Export Confirmed handleExportAttendees('checked_in')}>Export Checked-in handleExportAttendees('confirmed_pending')}>Confirmed & Pending
Format: CSV
Add Ticket } > { openAddTicket('paid'); setShowAddTicketDropdown(false); }}> Paid Ticket { openAddTicket('unpaid'); setShowAddTicketDropdown(false); }}> Unpaid Ticket { openAddTicket('guest'); setShowAddTicketDropdown(false); }}> Invite Guest
{(searchQuery || statusFilter !== 'all') && (
Showing {filteredTickets.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" />
{(searchQuery || statusFilter !== 'all') && ( )}
{(searchQuery || statusFilter !== 'all') && (

Showing {filteredTickets.length} of {tickets.length}

)}
{/* Desktop: Dense table */}
{filteredTickets.length === 0 ? ( ) : ( filteredTickets.map((ticket) => { const primary = getPrimaryAction(ticket); return ( ); }) )}
Attendee Contact Status Booked Actions
{tickets.length === 0 ? 'No attendees yet' : 'No attendees match the current filters'}

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

{ticket.bookingId && ( Group booking )}

{ticket.attendeeEmail}

{ticket.attendeePhone &&

{ticket.attendeePhone}

} {ticket.attendeeRuc &&

RUC: {ticket.attendeeRuc}

}
{ticket.checkinAt && (

{parseDate(ticket.checkinAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', timeZone: EVENT_TIMEZONE })}

)}
{parseDate(ticket.createdAt).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { timeZone: EVENT_TIMEZONE })}
{primary && ( )} {ticket.status === 'on_hold' && ( handleReactivate(ticket)}> Reactivate )} {ticket.paymentStatus === 'unpaid' && ticket.status === 'checked_in' && ( handleMarkPaid(ticket.id)}> Mark as Paid )} {ticket.paymentStatus === 'unpaid' && ticket.status === 'confirmed' && ( handleCheckin(ticket.id)}> Check In (unpaid) )} handleOpenNoteModal(ticket)}> {ticket.adminNote ? 'Edit Note' : 'Add Note'} {ticket.adminNote && (
Note: {ticket.adminNote}
)}
ID: {ticket.id.slice(0, 8)}...
{/* Mobile: Card layout */}
{filteredTickets.length === 0 ? (
{tickets.length === 0 ? 'No attendees yet' : 'No attendees match the current filters'}
) : ( filteredTickets.map((ticket) => { const primary = getPrimaryAction(ticket); return (

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

{ticket.attendeeEmail}

{ticket.attendeePhone &&

{ticket.attendeePhone}

} {ticket.attendeeRuc &&

RUC: {ticket.attendeeRuc}

}

{parseDate(ticket.createdAt).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { timeZone: EVENT_TIMEZONE })} {ticket.checkinAt && ` ยท Checked in ${parseDate(ticket.checkinAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', timeZone: EVENT_TIMEZONE })}`}

{primary && ( )} {ticket.status === 'on_hold' && ( handleReactivate(ticket)}> Reactivate )} {ticket.paymentStatus === 'unpaid' && ticket.status === 'checked_in' && ( handleMarkPaid(ticket.id)}> Mark as Paid )} {ticket.paymentStatus === 'unpaid' && ticket.status === 'confirmed' && ( handleCheckin(ticket.id)}> Check In (unpaid) )} handleOpenNoteModal(ticket)}> {ticket.adminNote ? 'Edit Note' : 'Add Note'}
); }) )}
{/* Mobile FAB */}
); }