'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, Download } from 'lucide-react'; import { toast } from 'react-toastify'; import api from '@/lib/api'; import { useT, useLocale } from '@/i18n/I18nProvider'; import type { Invoice, InvoiceStatus } from '@/types'; 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 t = useT(); const inv = t.dashboard.invoices; const locale = useLocale(); const statusLabel = (s: InvoiceStatus) => (inv.status as Record)[s] ?? s; 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 verifyGatewayMutation = useMutation({ mutationFn: ({ invoiceId, trackingCode, amount }: { invoiceId: string; trackingCode: string; amount: number }) => api.post(`/billing/invoices/${invoiceId}/gateway/verify`, { trackingCode, amount }).then((r) => r.data), onSuccess: (data) => { toast.success(data.effect ? inv.paymentCompleteUpdated : inv.paymentComplete); refresh(); window.history.replaceState({}, '', window.location.pathname); }, onError: (err: any) => toast.error(err.response?.data?.message || inv.gatewayFailed), }); useEffect(() => { const invoiceId = searchParams.get('invoiceId'); const trackingCode = searchParams.get('trackingCode'); const amount = Number(searchParams.get('amount') || 0); const status = searchParams.get('status'); if (invoiceId && trackingCode && amount > 0 && status === 'success' && !verifyGatewayMutation.isPending) { setSelectedId(invoiceId); verifyGatewayMutation.mutate({ invoiceId, trackingCode, amount }); } }, [searchParams, verifyGatewayMutation]); const payMutation = useMutation({ mutationFn: async (invoiceId: string) => { const callbackUrl = `${window.location.origin}/dashboard/invoices`; const { data } = await api.post(`/billing/invoices/${invoiceId}/pay/mixed`, { callbackUrl }); if (data.gatewayUrl && data.gatewayAmount > 0) { window.location.href = data.gatewayUrl; return data; } return data; }, onSuccess: (data) => { if (data.gatewayUrl && data.gatewayAmount > 0) return; toast.success(data.effect ? inv.invoicePaidUpdated : inv.invoicePaid); refresh(); }, onError: (err: any) => toast.error(err.response?.data?.message || inv.paymentFailed), }); const downloadPdfMutation = useMutation({ mutationFn: async (invoice: Invoice) => { const { data } = await api.get(`/billing/invoices/${invoice.id}/pdf`, { responseType: 'blob' }); const url = window.URL.createObjectURL(new Blob([data], { type: 'application/pdf' })); const link = document.createElement('a'); link.href = url; link.download = `${invoice.invoiceNumber}.pdf`; document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); }, onError: () => toast.error(inv.downloadFailed), }); const formatPrice = (amount: number) => Number(amount || 0).toLocaleString('en-US'); const formatDate = (value?: string) => value ? new Date(value).toLocaleString(locale) : '-'; const dueAmount = Number(selectedInvoice?.dueAmount || 0); const walletBalance = Number(walletData?.balance || 0); const isPayable = selectedInvoice?.status === 'issued' || selectedInvoice?.status === 'partially_paid'; return (

{inv.title}

{inv.subtitle}

{inv.walletBalance}

{formatPrice(walletBalance)} {inv.toman}

{(['all', 'unpaid', 'paid', 'failed', 'void'] as const).map((status) => ( ))}
{isLoading ? (
{inv.loadingInvoices}
) : visibleInvoices.length === 0 ? (
{inv.noInvoices}
) : (
{visibleInvoices.map((invoice) => ( ))}
)}
{!selectedInvoice ? (
{inv.selectInvoice}
) : (

{selectedInvoice.invoiceNumber}

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

{statusLabel(selectedInvoice.status)}
{inv.total}{formatPrice(selectedInvoice.total)} {inv.toman}
{inv.paidLabel}{formatPrice(selectedInvoice.paidAmount)} {inv.toman}
{inv.due}{formatPrice(selectedInvoice.dueAmount)} {inv.toman}
{inv.method}{selectedInvoice.paymentMethod || '-'}

{inv.lineItems}

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

{line.label}

{line.description &&

{line.description}

}
{formatPrice(line.amount)} {t.common.currencyShort}
))}
{isPayable && (

{inv.payment}

{walletBalance < dueAmount && (

{inv.walletCoversNote .replace('{wallet}', formatPrice(walletBalance)) .replace('{rest}', formatPrice(dueAmount - walletBalance))}

)}
)} {selectedInvoice.status === 'paid' && (
{inv.paidOn.replace('{date}', formatDate(selectedInvoice.paidAt))}
)} {selectedInvoice.status === 'failed' && (
{selectedInvoice.statusReason || inv.paymentFailed}
)} {selectedInvoice.status === 'partially_paid' && (
{inv.waitingRemaining}
)}
)}
); }