'use client'; import { useEffect, useMemo, useState } from 'react'; import { Search, RefreshCw, Download, Eye, FileText, FileSpreadsheet, Printer, X, } from 'lucide-react'; import { format } from 'date-fns'; import { toast } from 'sonner'; import { apiFetch } from '@/services/api/client'; import { SlideOver } from '@/components/ui/SlideOver'; import { TablePagination, useClientPagination } from '@/components/ui/TablePagination'; interface AdminInvoiceResponse { invoice_id: string; invoice_no: string; order_id: string | null; customer_id: string; customer_email: string | null; subtotal: number; discount_amount: number; cgst: number; sgst: number; igst: number; total_amount: number; status: string; pdf_url: string | null; created_at: string; } function statusBadgeClass(status?: string) { const s = (status || '').toUpperCase(); if (s.includes('CANCEL') || s.includes('VOID') || s.includes('UNPAID')) return 'bg-destructive text-white'; if (s.includes('PENDING') || s.includes('DRAFT')) return 'bg-warning text-white'; return 'bg-success text-white'; } export default function InvoiceIntelligencePage() { const [invoices, setInvoices] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [selectedInvoiceId, setSelectedInvoiceId] = useState(null); const [detailData, setDetailData] = useState(null); const [previewModal, setPreviewModal] = useState<{ open: boolean; invoice: AdminInvoiceResponse | null; format: 'standard' | 'thermal'; }>({ open: false, invoice: null, format: 'standard' }); const [previewPdfUrl, setPreviewPdfUrl] = useState(null); const [previewLoading, setPreviewLoading] = useState(false); const backendUrl = process.env.NEXT_PUBLIC_API_URL || ''; const fetchInvoices = async () => { setLoading(true); try { const data = await apiFetch('/api/v1/admin/invoices'); setInvoices(data); } catch (err: any) { toast.error(err.message || 'Failed to fetch invoices'); } finally { setLoading(false); } }; useEffect(() => { fetchInvoices(); }, []); useEffect(() => { return () => { if (previewPdfUrl) URL.revokeObjectURL(previewPdfUrl); }; }, [previewPdfUrl]); const handleInspectInvoice = (invoice: AdminInvoiceResponse) => { setSelectedInvoiceId(invoice.invoice_id); setDetailData(invoice); }; const closeDetails = () => { setSelectedInvoiceId(null); setDetailData(null); }; const handleExport = (type: 'excel' | 'zip') => { window.open(`${backendUrl}/api/v1/admin/invoices/export/${type}`, '_blank'); toast.success(type === 'excel' ? 'Invoice Excel download started' : 'Invoice ZIP download started'); }; const closePreview = () => { if (previewPdfUrl) { URL.revokeObjectURL(previewPdfUrl); setPreviewPdfUrl(null); } setPreviewModal({ open: false, invoice: null, format: 'standard' }); }; const handleOpenPreview = async (inv: AdminInvoiceResponse, formatType: 'standard' | 'thermal') => { setPreviewModal({ open: true, invoice: inv, format: formatType }); setPreviewLoading(true); if (previewPdfUrl) { URL.revokeObjectURL(previewPdfUrl); setPreviewPdfUrl(null); } try { const ep = formatType === 'thermal' ? 'thermal-download' : 'download'; const res = await fetch(`${backendUrl}/api/v1/admin/invoices/${inv.invoice_id}/${ep}`); if (!res.ok) throw new Error('PDF compilation failed'); const blob = await res.blob(); setPreviewPdfUrl(URL.createObjectURL(blob)); } catch { toast.error('Could not load the invoice preview'); } finally { setPreviewLoading(false); } }; const statusCounts = useMemo(() => { const map = new Map(); invoices.forEach((inv) => { const key = inv.status || 'Unknown'; map.set(key, (map.get(key) || 0) + 1); }); return Array.from(map.entries()); }, [invoices]); const filteredInvoices = useMemo(() => { const q = searchQuery.trim().toLowerCase(); return invoices.filter((inv) => { const statusMatch = statusFilter === 'all' || (inv.status || 'Unknown') === statusFilter; const searchMatch = !q || inv.invoice_no.toLowerCase().includes(q) || (inv.customer_email || '').toLowerCase().includes(q) || (inv.order_id || '').toLowerCase().includes(q); return statusMatch && searchMatch; }); }, [invoices, searchQuery, statusFilter]); const pager = useClientPagination(filteredInvoices); const dataCardShell = 'crm-radius-section border border-border bg-card shadow-[0_1px_3px_rgba(16,24,40,0.06)] overflow-hidden min-w-0'; const primaryButton = 'inline-flex items-center justify-center gap-2 h-9 px-4 crm-radius-control bg-primary hover:bg-primary/90 text-white text-[13px] font-semibold cursor-pointer disabled:opacity-50'; const secondaryButton = 'inline-flex items-center justify-center gap-2 h-9 px-4 crm-radius-control border border-border bg-card text-foreground text-[13px] font-medium hover:bg-muted cursor-pointer disabled:opacity-50'; const renderStatusBadge = (status: string) => ( {status || 'Issued'} ); return (

Sales Invoices

{invoices.length}
setSearchQuery(e.target.value)} className="w-full h-9 pl-9 pr-3 crm-radius-control border border-border bg-card text-[13px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors" />
{statusCounts.length > 0 && (
{statusCounts.map(([status, count]) => ( ))}
)}
{loading ? (
Loading invoices...
) : (
{filteredInvoices.length === 0 ? ( ) : ( pager.items.map((invoice) => ( handleInspectInvoice(invoice)} > )) )}
Invoice no. Customer Date Amount Status Action
{searchQuery || statusFilter !== 'all' ? 'No invoices match your search.' : 'No invoices found.'}
{invoice.invoice_no} {invoice.customer_email || 'Walk-in guest'} {format(new Date(invoice.created_at), 'dd-MMM-yyyy')} ₹{invoice.total_amount.toFixed(2)} {renderStatusBadge(invoice.status)}
)}
} > {detailData && (

{format(new Date(detailData.created_at), 'dd-MMM-yyyy HH:mm')}

{renderStatusBadge(detailData.status)}

Related order

{detailData.order_id || '—'}

Billed to

{detailData.customer_email || 'Walk-in guest'}

ID: {detailData.customer_id}

Taxable value ₹{detailData.subtotal.toFixed(2)}
{detailData.discount_amount > 0 && (
Discount -₹{detailData.discount_amount.toFixed(2)}
)}
CGST ₹{detailData.cgst.toFixed(2)}
SGST ₹{detailData.sgst.toFixed(2)}
IGST ₹{detailData.igst.toFixed(2)}
Grand total ₹{detailData.total_amount.toFixed(2)}
)}
{previewModal.open && previewModal.invoice && (
{previewLoading ? (
Loading preview...
) : previewPdfUrl ? (