'use client'; import { useState, useEffect } from 'react'; import { useLanguage } from '@/context/LanguageContext'; import { paymentsApi, adminApi, eventsApi, PaymentWithDetails, Event, ExportedPayment, FinancialSummary } from '@/lib/api'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import Input from '@/components/ui/Input'; import { CheckCircleIcon, ArrowPathIcon, ArrowDownTrayIcon, DocumentArrowDownIcon, XCircleIcon, ClockIcon, ExclamationTriangleIcon, ChatBubbleLeftIcon, BoltIcon, BanknotesIcon, BuildingLibraryIcon, CreditCardIcon, EnvelopeIcon, } from '@heroicons/react/24/outline'; import toast from 'react-hot-toast'; type Tab = 'pending_approval' | 'all'; export default function AdminPaymentsPage() { const { t, locale } = useLanguage(); const [payments, setPayments] = useState([]); const [pendingApprovalPayments, setPendingApprovalPayments] = useState([]); const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('pending_approval'); const [statusFilter, setStatusFilter] = useState(''); const [providerFilter, setProviderFilter] = useState(''); // Modal state const [selectedPayment, setSelectedPayment] = useState(null); const [noteText, setNoteText] = useState(''); const [processing, setProcessing] = useState(false); const [sendEmail, setSendEmail] = useState(true); const [sendingReminder, setSendingReminder] = useState(false); // Export state const [showExportModal, setShowExportModal] = useState(false); const [exporting, setExporting] = useState(false); const [exportData, setExportData] = useState<{ payments: ExportedPayment[]; summary: FinancialSummary } | null>(null); const [exportFilters, setExportFilters] = useState({ startDate: '', endDate: '', eventId: '', }); useEffect(() => { loadData(); }, [statusFilter, providerFilter]); const loadData = async () => { try { setLoading(true); const [pendingRes, allRes, eventsRes] = await Promise.all([ paymentsApi.getPendingApproval(), paymentsApi.getAll({ status: statusFilter || undefined, provider: providerFilter || undefined }), eventsApi.getAll(), ]); setPendingApprovalPayments(pendingRes.payments); setPayments(allRes.payments); setEvents(eventsRes.events); } catch (error) { toast.error('Failed to load payments'); } finally { setLoading(false); } }; const handleApprove = async (payment: PaymentWithDetails) => { setProcessing(true); try { await paymentsApi.approve(payment.id, noteText, sendEmail); toast.success(locale === 'es' ? 'Pago aprobado' : 'Payment approved'); setSelectedPayment(null); setNoteText(''); setSendEmail(true); loadData(); } catch (error: any) { toast.error(error.message || 'Failed to approve payment'); } finally { setProcessing(false); } }; const handleReject = async (payment: PaymentWithDetails) => { setProcessing(true); try { await paymentsApi.reject(payment.id, noteText, sendEmail); toast.success(locale === 'es' ? 'Pago rechazado' : 'Payment rejected'); setSelectedPayment(null); setNoteText(''); setSendEmail(true); loadData(); } catch (error: any) { toast.error(error.message || 'Failed to reject payment'); } finally { setProcessing(false); } }; const handleSendReminder = async (payment: PaymentWithDetails) => { setSendingReminder(true); try { const result = await paymentsApi.sendReminder(payment.id); toast.success(locale === 'es' ? 'Recordatorio enviado' : 'Reminder sent'); // Update the selected payment with the new reminderSentAt timestamp if (result.reminderSentAt) { setSelectedPayment({ ...payment, reminderSentAt: result.reminderSentAt }); } // Also refresh the data to update the lists loadData(); } catch (error: any) { toast.error(error.message || 'Failed to send reminder'); } finally { setSendingReminder(false); } }; const handleConfirmPayment = async (id: string) => { try { await paymentsApi.approve(id); toast.success('Payment confirmed'); loadData(); } catch (error) { toast.error('Failed to confirm payment'); } }; const handleRefund = async (id: string) => { if (!confirm('Are you sure you want to process this refund?')) return; try { await paymentsApi.refund(id); toast.success('Refund processed'); loadData(); } catch (error: any) { toast.error(error.message || 'Failed to process refund'); } }; const handleExport = async () => { setExporting(true); try { const data = await adminApi.exportFinancial({ startDate: exportFilters.startDate || undefined, endDate: exportFilters.endDate || undefined, eventId: exportFilters.eventId || undefined, }); setExportData(data); } catch (error) { toast.error('Failed to generate export'); } finally { setExporting(false); } }; const downloadCSV = () => { if (!exportData) return; const headers = ['Payment ID', 'Amount', 'Currency', 'Provider', 'Status', 'Reference', 'Paid At', 'Created At', 'Attendee Name', 'Attendee Email', 'Event Title', 'Event Date']; const rows = exportData.payments.map(p => [ p.paymentId, p.amount, p.currency, p.provider, p.status, p.reference || '', p.paidAt || '', p.createdAt, `${p.attendeeFirstName} ${p.attendeeLastName || ''}`.trim(), p.attendeeEmail || '', p.eventTitle, p.eventDate, ]); const csvContent = [headers, ...rows].map(row => row.map(cell => `"${cell}"`).join(',')).join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `financial-export-${new Date().toISOString().split('T')[0]}.csv`; link.click(); toast.success('CSV downloaded'); }; const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', }); }; const formatCurrency = (amount: number, currency: string) => { return `${amount.toLocaleString()} ${currency}`; }; const getStatusBadge = (status: string) => { const styles: Record = { pending: 'bg-gray-100 text-gray-700', pending_approval: 'bg-yellow-100 text-yellow-700', paid: 'bg-green-100 text-green-700', refunded: 'bg-blue-100 text-blue-700', failed: 'bg-red-100 text-red-700', cancelled: 'bg-gray-100 text-gray-700', }; const labels: Record = { pending: locale === 'es' ? 'Pendiente' : 'Pending', pending_approval: locale === 'es' ? 'Esperando Aprobaci贸n' : 'Pending Approval', paid: locale === 'es' ? 'Pagado' : 'Paid', refunded: locale === 'es' ? 'Reembolsado' : 'Refunded', failed: locale === 'es' ? 'Fallido' : 'Failed', cancelled: locale === 'es' ? 'Cancelado' : 'Cancelled', }; return ( {labels[status] || status} ); }; const getProviderIcon = (provider: string) => { const icons: Record = { lightning: BoltIcon, cash: BanknotesIcon, bank_transfer: BuildingLibraryIcon, tpago: CreditCardIcon, bancard: CreditCardIcon, }; const Icon = icons[provider] || CreditCardIcon; return ; }; const getProviderLabel = (provider: string) => { const labels: Record = { cash: locale === 'es' ? 'Efectivo' : 'Cash', bank_transfer: locale === 'es' ? 'Transferencia Bancaria' : 'Bank Transfer', lightning: 'Lightning', tpago: 'TPago', bancard: 'Bancard', }; return labels[provider] || provider; }; // Helper to get booking info for a payment (ticket count and total) const getBookingInfo = (payment: PaymentWithDetails) => { if (!payment.ticket?.bookingId) { return { ticketCount: 1, bookingTotal: payment.amount }; } // Count all payments with the same bookingId const bookingPayments = payments.filter( p => p.ticket?.bookingId === payment.ticket?.bookingId ); return { ticketCount: bookingPayments.length, bookingTotal: bookingPayments.reduce((sum, p) => sum + Number(p.amount), 0), }; }; // Get booking info for pending approval payments const getPendingBookingInfo = (payment: PaymentWithDetails) => { if (!payment.ticket?.bookingId) { return { ticketCount: 1, bookingTotal: payment.amount }; } // Count all pending payments with the same bookingId const bookingPayments = pendingApprovalPayments.filter( p => p.ticket?.bookingId === payment.ticket?.bookingId ); return { ticketCount: bookingPayments.length, bookingTotal: bookingPayments.reduce((sum, p) => sum + Number(p.amount), 0), }; }; // Calculate totals (sum all individual payment amounts) const totalPending = payments .filter(p => p.status === 'pending' || p.status === 'pending_approval') .reduce((sum, p) => sum + Number(p.amount), 0); const totalPaid = payments .filter(p => p.status === 'paid') .reduce((sum, p) => sum + Number(p.amount), 0); // Get unique booking count (for summary display) const getUniqueBookingsCount = (paymentsList: PaymentWithDetails[]) => { const seen = new Set(); let count = 0; paymentsList.forEach(p => { const bookingKey = p.ticket?.bookingId || p.id; if (!seen.has(bookingKey)) { seen.add(bookingKey); count++; } }); return count; }; const pendingBookingsCount = getUniqueBookingsCount( payments.filter(p => p.status === 'pending' || p.status === 'pending_approval') ); const paidBookingsCount = getUniqueBookingsCount( payments.filter(p => p.status === 'paid') ); const pendingApprovalBookingsCount = getUniqueBookingsCount(pendingApprovalPayments); if (loading) { return (
); } return (

{t('admin.payments.title')}

{/* Approval Detail Modal */} {selectedPayment && (() => { const modalBookingInfo = getBookingInfo(selectedPayment); return (

{locale === 'es' ? 'Verificar Pago' : 'Verify Payment'}

{locale === 'es' ? 'Monto Total' : 'Total Amount'}

{formatCurrency(modalBookingInfo.bookingTotal, selectedPayment.currency)}

{modalBookingInfo.ticketCount > 1 && (

馃摝 {modalBookingInfo.ticketCount} tickets 脳 {formatCurrency(selectedPayment.amount, selectedPayment.currency)}

)}

{locale === 'es' ? 'M茅todo' : 'Method'}

{getProviderIcon(selectedPayment.provider)} {getProviderLabel(selectedPayment.provider)}

{selectedPayment.ticket && (

{locale === 'es' ? 'Asistente' : 'Attendee'}

{selectedPayment.ticket.attendeeFirstName} {selectedPayment.ticket.attendeeLastName}

{selectedPayment.ticket.attendeeEmail}

{selectedPayment.ticket.attendeePhone && (

{selectedPayment.ticket.attendeePhone}

)}
)} {selectedPayment.event && (

{locale === 'es' ? 'Evento' : 'Event'}

{selectedPayment.event.title}

{formatDate(selectedPayment.event.startDatetime)}

)} {selectedPayment.userMarkedPaidAt && (
{locale === 'es' ? 'Usuario marc贸 como pagado:' : 'User marked as paid:'} {formatDate(selectedPayment.userMarkedPaidAt)}
)} {selectedPayment.reminderSentAt && (
{locale === 'es' ? 'Recordatorio enviado:' : 'Reminder sent:'} {formatDate(selectedPayment.reminderSentAt)}
)} {selectedPayment.payerName && (

{locale === 'es' ? '鈿狅笍 Pagado por otra persona:' : '鈿狅笍 Paid by someone else:'}

{selectedPayment.payerName}

)}