'use client'; import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { FileText, Search, User, Wallet, CreditCard, XCircle } from 'lucide-react'; import { toast } from 'react-toastify'; import api from '@/lib/api'; import type { Invoice, InvoiceStatus, PaymentMethod } from '@/types'; const statusLabels: Record = { draft: 'Draft', issued: 'Unpaid', partially_paid: 'Partially paid', paid: 'Paid', void: 'Void', failed: 'Failed', }; const statusClasses: Record = { draft: 'badge-gray', issued: 'badge-yellow', partially_paid: 'badge-blue', paid: 'badge-green', void: 'badge-gray', failed: 'badge-red', }; export default function AdminInvoicesPage() { const queryClient = useQueryClient(); const [status, setStatus] = useState<'all' | InvoiceStatus>('all'); const [paymentMethod, setPaymentMethod] = useState<'all' | PaymentMethod>('all'); const [search, setSearch] = useState(''); const [selectedId, setSelectedId] = useState(null); const [statusReason, setStatusReason] = useState(''); const { data: invoices = [], isLoading } = useQuery({ queryKey: ['admin-invoices', status, paymentMethod, search], queryFn: () => { const params: Record = { limit: '200' }; if (status !== 'all') params.status = status; if (paymentMethod !== 'all') params.paymentMethod = paymentMethod; if (search.trim()) params.search = search.trim(); return api.get('/billing/admin/invoices', { params }).then((r) => r.data); }, }); const { data: selectedInvoice } = useQuery({ queryKey: ['admin-invoice', selectedId], queryFn: () => api.get(`/billing/admin/invoices/${selectedId}`).then((r) => r.data), enabled: !!selectedId, }); const updateStatusMutation = useMutation({ mutationFn: ({ invoiceId, nextStatus, reason }: { invoiceId: string; nextStatus: InvoiceStatus; reason: string }) => api.patch(`/billing/admin/invoices/${invoiceId}/status`, { status: nextStatus, reason }).then((r) => r.data), onSuccess: () => { toast.success('Invoice status updated'); setStatusReason(''); queryClient.invalidateQueries({ queryKey: ['admin-invoices'] }); queryClient.invalidateQueries({ queryKey: ['admin-invoice', selectedId] }); }, onError: (err: any) => toast.error(err.response?.data?.message || 'Failed to update invoice status'), }); const formatPrice = (amount: number) => Number(amount || 0).toLocaleString('en-US'); const formatDate = (value?: string) => value ? new Date(value).toLocaleString('en-US') : '-'; const handleStatusUpdate = (nextStatus: InvoiceStatus) => { if (!selectedInvoice) return; if (!statusReason.trim()) { toast.error('Reason is required for manual status changes'); return; } updateStatusMutation.mutate({ invoiceId: selectedInvoice.id, nextStatus, reason: statusReason.trim(), }); }; return (

Invoice Management

Track all user invoices, payments, gateway refs, and wallet transactions.

setSearch(e.target.value)} placeholder="Search invoice, email, or application" className="input-field pl-9 w-full" />
{isLoading ? ( ) : invoices.length === 0 ? ( ) : invoices.map((invoice) => ( setSelectedId(invoice.id)} className={`cursor-pointer hover:bg-gray-50 ${selectedId === invoice.id ? 'bg-primary-50' : ''}`} > ))}
Invoice User Status Method Due
Loading...
No invoices found

{invoice.invoiceNumber}

{invoice.application?.name || invoice.reason} · {formatDate(invoice.createdAt)}

{invoice.user?.email || invoice.userId}

{statusLabels[invoice.status]} {invoice.paymentMethod || '-'}

{formatPrice(invoice.dueAmount)} T

Total {formatPrice(invoice.total)} T

{!selectedInvoice ? (
Select an invoice
) : (

{selectedInvoice.invoiceNumber}

{selectedInvoice.application?.name || selectedInvoice.reason}

{statusLabels[selectedInvoice.status]}

Total

{formatPrice(selectedInvoice.total)} T

Due

{formatPrice(selectedInvoice.dueAmount)} T

{selectedInvoice.user?.email || selectedInvoice.userId}
Tracking: {selectedInvoice.gatewayTrackingCode || '-'}
Method: {selectedInvoice.paymentMethod || '-'}

Line items

{(selectedInvoice.lines || []).map((line) => (

{line.label}

{line.description &&

{line.description}

}
{formatPrice(line.amount)} T
))}

Transactions

{(selectedInvoice.transactions || []).length === 0 ? (

No linked transactions yet

) : (
{(selectedInvoice.transactions || []).map((tx) => (
{tx.description || tx.type} {formatPrice(tx.amount)} T

{tx.type} · {formatDate(tx.createdAt)}

))}
)}

Manual status change