151 lines
5.8 KiB
TypeScript
151 lines
5.8 KiB
TypeScript
/**
|
|
* Admin All Payments Page
|
|
* View and manage all payment transactions
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { Search, Filter, Loader2, AlertCircle } from 'lucide-react';
|
|
import { Card } from '../../components/ui/card';
|
|
import Badge from '../../components/ui/badge/Badge';
|
|
import { getAdminPayments, type Payment } from '../../services/billing.api';
|
|
|
|
type AdminPayment = Payment & { account_name?: string };
|
|
|
|
export default function AdminAllPaymentsPage() {
|
|
const [payments, setPayments] = useState<AdminPayment[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string>('');
|
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
|
|
useEffect(() => {
|
|
loadPayments();
|
|
}, []);
|
|
|
|
const loadPayments = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const data = await getAdminPayments();
|
|
setPayments(data.results || []);
|
|
} catch (err: any) {
|
|
setError(err.message || 'Failed to load payments');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const filteredPayments = payments.filter((payment) => {
|
|
return statusFilter === 'all' || payment.status === statusFilter;
|
|
});
|
|
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'succeeded':
|
|
case 'completed':
|
|
return 'success';
|
|
case 'processing':
|
|
case 'pending':
|
|
case 'pending_approval':
|
|
return 'warning';
|
|
case 'refunded':
|
|
return 'info';
|
|
default:
|
|
return 'error';
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<Loader2 className="w-8 h-8 animate-spin text-blue-600" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<div className="mb-6">
|
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">All Payments</h1>
|
|
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
|
View and manage all payment transactions
|
|
</p>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg flex items-center gap-3">
|
|
<AlertCircle className="w-5 h-5 text-red-600" />
|
|
<p className="text-red-800 dark:text-red-200">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mb-6 flex items-center gap-2">
|
|
<Filter className="w-5 h-5 text-gray-400" />
|
|
<select
|
|
value={statusFilter}
|
|
onChange={(e) => setStatusFilter(e.target.value)}
|
|
className="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800"
|
|
>
|
|
<option value="all">All Status</option>
|
|
<option value="pending_approval">Pending Approval</option>
|
|
<option value="processing">Processing</option>
|
|
<option value="succeeded">Succeeded</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="failed">Failed</option>
|
|
<option value="cancelled">Cancelled</option>
|
|
<option value="refunded">Refunded</option>
|
|
</select>
|
|
</div>
|
|
|
|
<Card className="overflow-hidden">
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full">
|
|
<thead className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
|
<tr>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Account</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Invoice</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Amount</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Method</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Date</th>
|
|
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
|
|
{filteredPayments.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={6} className="px-6 py-8 text-center text-gray-500">No payments found</td>
|
|
</tr>
|
|
) : (
|
|
filteredPayments.map((payment) => (
|
|
<tr key={payment.id} className="hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
<td className="px-6 py-4 font-medium">{payment.account_name}</td>
|
|
<td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">
|
|
{payment.invoice_number || payment.invoice_id || '—'}
|
|
</td>
|
|
<td className="px-6 py-4 font-semibold">{payment.currency} {payment.amount}</td>
|
|
<td className="px-6 py-4 text-sm capitalize">{payment.payment_method.replace('_', ' ')}</td>
|
|
<td className="px-6 py-4">
|
|
<Badge
|
|
variant="light"
|
|
color={getStatusColor(payment.status)}
|
|
>
|
|
{payment.status}
|
|
</Badge>
|
|
</td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">
|
|
{new Date(payment.created_at).toLocaleDateString()}
|
|
</td>
|
|
<td className="px-6 py-4 text-right">
|
|
<button className="text-blue-600 hover:text-blue-700 text-sm">View</button>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|