first commit
This commit is contained in:
207
frontend/src/app/(public)/dashboard/components/PaymentsTab.tsx
Normal file
207
frontend/src/app/(public)/dashboard/components/PaymentsTab.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Card from '@/components/ui/Card';
|
||||
import Button from '@/components/ui/Button';
|
||||
import { UserPayment } from '@/lib/api';
|
||||
|
||||
interface PaymentsTabProps {
|
||||
payments: UserPayment[];
|
||||
language: string;
|
||||
}
|
||||
|
||||
export default function PaymentsTab({ payments, language }: PaymentsTabProps) {
|
||||
const [filter, setFilter] = useState<'all' | 'paid' | 'pending'>('all');
|
||||
|
||||
const filteredPayments = payments.filter((payment) => {
|
||||
if (filter === 'all') return true;
|
||||
if (filter === 'paid') return payment.status === 'paid';
|
||||
if (filter === 'pending') return payment.status !== 'paid' && payment.status !== 'refunded';
|
||||
return true;
|
||||
});
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
return new Date(dateStr).toLocaleDateString(language === 'es' ? 'es-ES' : 'en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number, currency: string = 'PYG') => {
|
||||
if (currency === 'PYG') {
|
||||
return `${amount.toLocaleString('es-PY')} PYG`;
|
||||
}
|
||||
return `$${amount.toFixed(2)} ${currency}`;
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const styles: Record<string, string> = {
|
||||
paid: 'bg-green-100 text-green-800',
|
||||
pending: 'bg-yellow-100 text-yellow-800',
|
||||
pending_approval: 'bg-orange-100 text-orange-800',
|
||||
refunded: 'bg-purple-100 text-purple-800',
|
||||
failed: 'bg-red-100 text-red-800',
|
||||
};
|
||||
const labels: Record<string, Record<string, string>> = {
|
||||
en: {
|
||||
paid: 'Paid',
|
||||
pending: 'Pending',
|
||||
pending_approval: 'Awaiting Approval',
|
||||
refunded: 'Refunded',
|
||||
failed: 'Failed',
|
||||
},
|
||||
es: {
|
||||
paid: 'Pagado',
|
||||
pending: 'Pendiente',
|
||||
pending_approval: 'Esperando Aprobación',
|
||||
refunded: 'Reembolsado',
|
||||
failed: 'Fallido',
|
||||
},
|
||||
};
|
||||
return (
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${styles[status] || 'bg-gray-100 text-gray-800'}`}>
|
||||
{labels[language]?.[status] || status}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const getProviderLabel = (provider: string) => {
|
||||
const labels: Record<string, Record<string, string>> = {
|
||||
en: {
|
||||
lightning: 'Lightning (Bitcoin)',
|
||||
cash: 'Cash',
|
||||
bank_transfer: 'Bank Transfer',
|
||||
tpago: 'TPago',
|
||||
bancard: 'Card',
|
||||
},
|
||||
es: {
|
||||
lightning: 'Lightning (Bitcoin)',
|
||||
cash: 'Efectivo',
|
||||
bank_transfer: 'Transferencia Bancaria',
|
||||
tpago: 'TPago',
|
||||
bancard: 'Tarjeta',
|
||||
},
|
||||
};
|
||||
return labels[language]?.[provider] || provider;
|
||||
};
|
||||
|
||||
// Summary calculations
|
||||
const totalPaid = payments
|
||||
.filter((p) => p.status === 'paid')
|
||||
.reduce((sum, p) => sum + Number(p.amount), 0);
|
||||
const totalPending = payments
|
||||
.filter((p) => p.status !== 'paid' && p.status !== 'refunded')
|
||||
.reduce((sum, p) => sum + Number(p.amount), 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Summary Cards */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Card className="p-4">
|
||||
<p className="text-sm text-gray-600 mb-1">
|
||||
{language === 'es' ? 'Total Pagado' : 'Total Paid'}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-green-600">
|
||||
{formatCurrency(totalPaid)}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-4">
|
||||
<p className="text-sm text-gray-600 mb-1">
|
||||
{language === 'es' ? 'Pendiente' : 'Pending'}
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-yellow-600">
|
||||
{formatCurrency(totalPending)}
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filter Buttons */}
|
||||
<div className="flex gap-2">
|
||||
{(['all', 'paid', 'pending'] as const).map((f) => (
|
||||
<button
|
||||
key={f}
|
||||
onClick={() => setFilter(f)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
||||
filter === f
|
||||
? 'bg-secondary-blue text-white'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
{f === 'all' && (language === 'es' ? 'Todos' : 'All')}
|
||||
{f === 'paid' && (language === 'es' ? 'Pagados' : 'Paid')}
|
||||
{f === 'pending' && (language === 'es' ? 'Pendientes' : 'Pending')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Payments List */}
|
||||
{filteredPayments.length === 0 ? (
|
||||
<Card className="p-8 text-center">
|
||||
<p className="text-gray-600">
|
||||
{language === 'es' ? 'No hay pagos que mostrar' : 'No payments to display'}
|
||||
</p>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{filteredPayments.map((payment) => (
|
||||
<Card key={payment.id} className="p-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="font-semibold">
|
||||
{formatCurrency(Number(payment.amount), payment.currency)}
|
||||
</span>
|
||||
{getStatusBadge(payment.status)}
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-600 space-y-1">
|
||||
{payment.event && (
|
||||
<p>
|
||||
{language === 'es' && payment.event.titleEs
|
||||
? payment.event.titleEs
|
||||
: payment.event.title}
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<span className="font-medium">
|
||||
{language === 'es' ? 'Método:' : 'Method:'}
|
||||
</span>{' '}
|
||||
{getProviderLabel(payment.provider)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-medium">
|
||||
{language === 'es' ? 'Fecha:' : 'Date:'}
|
||||
</span>{' '}
|
||||
{formatDate(payment.createdAt)}
|
||||
</p>
|
||||
{payment.reference && (
|
||||
<p>
|
||||
<span className="font-medium">
|
||||
{language === 'es' ? 'Referencia:' : 'Reference:'}
|
||||
</span>{' '}
|
||||
{payment.reference}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{payment.invoice && (
|
||||
<a
|
||||
href={payment.invoice.pdfUrl || '#'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Button variant="outline" size="sm">
|
||||
{language === 'es' ? 'Descargar Factura' : 'Download Invoice'}
|
||||
</Button>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user