'use client'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'next/navigation'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { CreditCard, FileText, Wallet, XCircle, CheckCircle, Clock, ExternalLink } from 'lucide-react'; import { toast } from 'react-toastify'; import api from '@/lib/api'; import type { Invoice, InvoiceStatus } 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 InvoicesPage() { const queryClient = useQueryClient(); const searchParams = useSearchParams(); const [statusFilter, setStatusFilter] = useState<'all' | 'unpaid' | InvoiceStatus>('all'); const [selectedId, setSelectedId] = useState(searchParams.get('invoice')); useEffect(() => { const invoiceId = searchParams.get('invoice'); if (invoiceId) setSelectedId(invoiceId); }, [searchParams]); const { data: walletData } = useQuery<{ balance: number }>({ queryKey: ['wallet-balance'], queryFn: () => api.get('/billing/wallet').then((r) => r.data), }); const { data: invoices = [], isLoading } = useQuery({ queryKey: ['invoices', statusFilter], queryFn: () => { const params: Record = {}; if (statusFilter !== 'all' && statusFilter !== 'unpaid') params.status = statusFilter; return api.get('/billing/invoices', { params }).then((r) => r.data); }, }); const visibleInvoices = invoices.filter((invoice) => { if (statusFilter === 'unpaid') return invoice.status === 'issued' || invoice.status === 'partially_paid'; return true; }); const { data: selectedInvoice } = useQuery({ queryKey: ['invoice', selectedId], queryFn: () => api.get(`/billing/invoices/${selectedId}`).then((r) => r.data), enabled: !!selectedId, }); const refresh = () => { queryClient.invalidateQueries({ queryKey: ['invoices'] }); queryClient.invalidateQueries({ queryKey: ['invoice', selectedId] }); queryClient.invalidateQueries({ queryKey: ['wallet-balance'] }); queryClient.invalidateQueries({ queryKey: ['wallet-transactions'] }); }; const walletPayMutation = useMutation({ mutationFn: (invoiceId: string) => api.post(`/billing/invoices/${invoiceId}/pay/wallet`).then((r) => r.data), onSuccess: (data) => { toast.success(data.effect ? 'Invoice paid and service updated' : 'Invoice paid'); refresh(); }, onError: (err: any) => toast.error(err.response?.data?.message || 'Wallet payment failed'), }); const gatewayPayMutation = useMutation({ mutationFn: async ({ invoiceId, mixed }: { invoiceId: string; mixed: boolean }) => { const callbackUrl = `${window.location.origin}/dashboard/invoices`; const { data } = await api.post( `/billing/invoices/${invoiceId}/pay/${mixed ? 'mixed' : 'gateway'}`, { callbackUrl }, ); if (data.gatewayAmount === 0) return data; const amount = data.gatewayAmount ?? data.amount; const trackingCode = data.trackingCode; if (!trackingCode || !amount) return data; const verified = await api.post(`/billing/invoices/${invoiceId}/gateway/verify`, { trackingCode, amount, }); return verified.data; }, onSuccess: (data) => { toast.success(data.effect ? 'Payment complete and service updated' : 'Payment complete'); refresh(); }, onError: (err: any) => toast.error(err.response?.data?.message || 'Gateway payment failed'), }); const formatPrice = (amount: number) => Number(amount || 0).toLocaleString('en-US'); const formatDate = (value?: string) => value ? new Date(value).toLocaleString('en-US') : '-'; const dueAmount = Number(selectedInvoice?.dueAmount || 0); const walletBalance = Number(walletData?.balance || 0); const isPayable = selectedInvoice?.status === 'issued' || selectedInvoice?.status === 'partially_paid'; return (

Invoices

Review what each payment was for and pay open invoices.

Wallet balance

{formatPrice(walletBalance)} Toman

{(['all', 'unpaid', 'paid', 'failed', 'void'] as const).map((status) => ( ))}
{isLoading ? (
Loading invoices...
) : visibleInvoices.length === 0 ? (
No invoices found
) : (
{visibleInvoices.map((invoice) => ( ))}
)}
{!selectedInvoice ? (
Select an invoice to view details
) : (

{selectedInvoice.invoiceNumber}

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

{statusLabels[selectedInvoice.status]}
Total{formatPrice(selectedInvoice.total)} Toman
Paid{formatPrice(selectedInvoice.paidAmount)} Toman
Due{formatPrice(selectedInvoice.dueAmount)} Toman
Method{selectedInvoice.paymentMethod || '-'}

Line items

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

{line.label}

{line.description &&

{line.description}

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

Payment options

{walletBalance < dueAmount && (

Wallet is short by {formatPrice(dueAmount - walletBalance)} Toman. Use mixed payment to cover the delta by gateway.

)}
)} {selectedInvoice.status === 'paid' && (
Paid on {formatDate(selectedInvoice.paidAt)}
)} {selectedInvoice.status === 'failed' && (
{selectedInvoice.statusReason || 'Payment failed'}
)} {selectedInvoice.status === 'partially_paid' && (
Waiting for the remaining payment.
)}
)}
); }