import React, { useState } from "react"; import { base44 } from "@/api/base44Client"; import { useQuery } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { FileText, Plus, Search, Eye, AlertTriangle, CheckCircle, Clock, DollarSign, Edit, TrendingUp, TrendingDown, Calendar, ArrowUpRight, Sparkles, BarChart3, PieChart, MapPin, User } from "lucide-react"; import { format, parseISO, isPast } from "date-fns"; import PageHeader from "@/components/common/PageHeader"; import { useNavigate } from "react-router-dom"; import { createPageUrl } from "@/utils"; import AutoInvoiceGenerator from "@/components/invoices/AutoInvoiceGenerator"; const statusColors = { 'Draft': 'bg-slate-100 text-slate-600 font-medium', 'Open': 'bg-blue-100 text-blue-700 font-medium', 'Pending Review': 'bg-blue-100 text-blue-700 font-medium', 'Confirmed': 'bg-amber-100 text-amber-700 font-medium', 'Approved': 'bg-emerald-100 text-emerald-700 font-medium', 'Disputed': 'bg-red-100 text-red-700 font-medium', 'Under Review': 'bg-orange-100 text-orange-700 font-medium', 'Resolved': 'bg-cyan-100 text-cyan-700 font-medium', 'Overdue': 'bg-red-100 text-red-700 font-medium', 'Paid': 'bg-emerald-100 text-emerald-700 font-medium', 'Reconciled': 'bg-purple-100 text-purple-700 font-medium', 'Cancelled': 'bg-slate-100 text-slate-600 font-medium', }; export default function Invoices() { const navigate = useNavigate(); const [activeTab, setActiveTab] = useState("all"); const [searchTerm, setSearchTerm] = useState(""); const { data: user } = useQuery({ queryKey: ['current-user-invoices'], queryFn: () => base44.auth.me(), }); const { data: invoices = [], isLoading } = useQuery({ queryKey: ['invoices'], queryFn: () => base44.entities.Invoice.list('-issue_date'), initialData: [], }); const userRole = user?.user_role || user?.role; // Auto-mark overdue invoices React.useEffect(() => { invoices.forEach(async (invoice) => { if (invoice.status === "Approved" && isPast(parseISO(invoice.due_date))) { try { await base44.entities.Invoice.update(invoice.id, { status: "Overdue" }); } catch (error) { console.error('Failed to mark invoice as overdue:', error); } } }); }, [invoices]); // Filter invoices based on user role const visibleInvoices = React.useMemo(() => { if (userRole === "client") { return invoices.filter(inv => inv.business_name === user?.company_name || inv.manager_name === user?.full_name || inv.created_by === user?.email ); } if (userRole === "vendor") { return invoices.filter(inv => inv.vendor_name === user?.company_name || inv.vendor_id === user?.vendor_id ); } return invoices; }, [invoices, userRole, user]); const getFilteredInvoices = () => { let filtered = visibleInvoices; if (activeTab !== "all") { const statusMap = { 'pending': 'Pending Review', 'approved': 'Approved', 'disputed': 'Disputed', 'overdue': 'Overdue', 'paid': 'Paid', 'reconciled': 'Reconciled', }; filtered = filtered.filter(inv => inv.status === statusMap[activeTab]); } if (searchTerm) { filtered = filtered.filter(inv => inv.invoice_number?.toLowerCase().includes(searchTerm.toLowerCase()) || inv.business_name?.toLowerCase().includes(searchTerm.toLowerCase()) || inv.manager_name?.toLowerCase().includes(searchTerm.toLowerCase()) || inv.event_name?.toLowerCase().includes(searchTerm.toLowerCase()) ); } return filtered; }; const filteredInvoices = getFilteredInvoices(); const getStatusCount = (status) => { if (status === "all") return visibleInvoices.length; return visibleInvoices.filter(inv => inv.status === status).length; }; const getTotalAmount = (status) => { const filtered = status === "all" ? visibleInvoices : visibleInvoices.filter(inv => inv.status === status); return filtered.reduce((sum, inv) => sum + (inv.amount || 0), 0); }; const metrics = { all: getTotalAmount("all"), pending: getTotalAmount("Pending Review"), approved: getTotalAmount("Approved"), disputed: getTotalAmount("Disputed"), overdue: getTotalAmount("Overdue"), paid: getTotalAmount("Paid"), outstanding: getTotalAmount("Pending Review") + getTotalAmount("Approved") + getTotalAmount("Overdue"), }; // Smart Insights const insights = React.useMemo(() => { const currentMonth = visibleInvoices.filter(inv => { const issueDate = parseISO(inv.issue_date); const now = new Date(); return issueDate.getMonth() === now.getMonth() && issueDate.getFullYear() === now.getFullYear(); }); const lastMonth = visibleInvoices.filter(inv => { const issueDate = parseISO(inv.issue_date); const now = new Date(); const lastMonthDate = new Date(now.getFullYear(), now.getMonth() - 1); return issueDate.getMonth() === lastMonthDate.getMonth() && issueDate.getFullYear() === lastMonthDate.getFullYear(); }); const currentTotal = currentMonth.reduce((sum, inv) => sum + (inv.amount || 0), 0); const lastTotal = lastMonth.reduce((sum, inv) => sum + (inv.amount || 0), 0); const percentChange = lastTotal > 0 ? ((currentTotal - lastTotal) / lastTotal * 100).toFixed(1) : 0; const avgPaymentTime = visibleInvoices .filter(inv => inv.status === "Paid" && inv.paid_date && inv.issue_date) .map(inv => { const days = Math.floor((parseISO(inv.paid_date) - parseISO(inv.issue_date)) / (1000 * 60 * 60 * 24)); return days; }); const avgDays = avgPaymentTime.length > 0 ? Math.round(avgPaymentTime.reduce((a, b) => a + b, 0) / avgPaymentTime.length) : 0; const onTimePayments = visibleInvoices.filter(inv => inv.status === "Paid" && inv.paid_date && inv.due_date && parseISO(inv.paid_date) <= parseISO(inv.due_date) ).length; const totalPaid = visibleInvoices.filter(inv => inv.status === "Paid").length; const onTimeRate = totalPaid > 0 ? ((onTimePayments / totalPaid) * 100).toFixed(0) : 0; const topClient = Object.entries( visibleInvoices.reduce((acc, inv) => { const client = inv.business_name || "Unknown"; acc[client] = (acc[client] || 0) + (inv.amount || 0); return acc; }, {}) ).sort((a, b) => b[1] - a[1])[0]; // For clients: calculate best hub by reconciliation rate const bestHub = userRole === "client" ? (() => { const hubStats = visibleInvoices.reduce((acc, inv) => { const hub = inv.hub || "Unknown"; if (!acc[hub]) { acc[hub] = { total: 0, reconciled: 0, paid: 0 }; } acc[hub].total++; if (inv.status === "Reconciled") acc[hub].reconciled++; if (inv.status === "Paid" || inv.status === "Reconciled") acc[hub].paid++; return acc; }, {}); const sortedHubs = Object.entries(hubStats) .map(([hub, stats]) => ({ hub, rate: stats.total > 0 ? ((stats.paid / stats.total) * 100).toFixed(0) : 0, total: stats.total })) .sort((a, b) => b.rate - a.rate); return sortedHubs[0] || null; })() : null; return { percentChange, isGrowth: percentChange > 0, avgDays, onTimeRate, topClient: topClient ? { name: topClient[0], amount: topClient[1] } : null, bestHub, currentMonthCount: currentMonth.length, currentTotal, }; }, [visibleInvoices, userRole]); return ( <>
{/* Left Sidebar - Summary */}
{/* Logo Card */}

Invoices

{visibleInvoices.length} total

Total Value ${metrics.all.toLocaleString()}

{((metrics.paid / metrics.all) * 100 || 0).toFixed(0)}% collected

{userRole === "vendor" && ( )} {/* Status Breakdown */}

Status Breakdown

{[ { label: "Pending", status: "Pending Review", color: "bg-blue-500", value: getStatusCount("Pending Review"), amount: getTotalAmount("Pending Review") }, { label: "Approved", status: "Approved", color: "bg-emerald-500", value: getStatusCount("Approved"), amount: getTotalAmount("Approved") }, { label: "Disputed", status: "Disputed", color: "bg-red-500", value: getStatusCount("Disputed"), amount: getTotalAmount("Disputed") }, { label: "Overdue", status: "Overdue", color: "bg-amber-500", value: getStatusCount("Overdue"), amount: getTotalAmount("Overdue") }, { label: "Paid", status: "Paid", color: "bg-green-500", value: getStatusCount("Paid"), amount: getTotalAmount("Paid") }, { label: "Reconciled", status: "Reconciled", color: "bg-purple-500", value: getStatusCount("Reconciled"), amount: getTotalAmount("Reconciled") }, ].map(item => ( ))}
{/* Sales & Revenue - Vendor Focused */}

Sales & Revenue

Total Sales ${metrics.all.toLocaleString()}
Collected ${metrics.paid.toLocaleString()}
Pending Revenue ${metrics.outstanding.toLocaleString()}
Collection Rate {((metrics.paid / metrics.all) * 100 || 0).toFixed(0)}%
Avg. Invoice ${visibleInvoices.length > 0 ? Math.round(metrics.all / visibleInvoices.length).toLocaleString() : 0}
{/* Quick Insights */}

Performance

Avg. Payment {insights.avgDays} days
On-Time Rate {insights.onTimeRate}%
This Month ${insights.currentTotal.toLocaleString()}
Monthly Count {insights.currentMonthCount} invoices
MoM Change {insights.isGrowth ? '+' : ''}{insights.percentChange}%
{insights.topClient && (
Top Client {insights.topClient.name}
)}
{/* Alerts */} {(metrics.disputed > 0 || metrics.overdue > 0) && (

Requires Attention

{metrics.disputed > 0 && (

• {getStatusCount("Disputed")} disputed (${metrics.disputed.toLocaleString()})

)} {metrics.overdue > 0 && (

• {getStatusCount("Overdue")} overdue (${metrics.overdue.toLocaleString()})

)}
)}
{/* Main Content */}
{/* Top Bar */}
setSearchTerm(e.target.value)} className="pl-9 h-10 bg-slate-50 border-0" />
{userRole === "vendor" && ( )}
{/* Stats Row - Mobile */}

${(metrics.all / 1000).toFixed(0)}K

Total

${(metrics.outstanding / 1000).toFixed(0)}K

Pending

{getStatusCount("Disputed")}

Disputed

${(metrics.paid / 1000).toFixed(0)}K

Paid

{/* Invoice Table */}
Invoice Client Event / Hub Date Amount Status Due Actions {filteredInvoices.length === 0 ? (

No invoices found

Try adjusting your search or filters

) : ( filteredInvoices.map((invoice) => { const invoiceDate = invoice.issue_date ? parseISO(invoice.issue_date) : new Date(); const dateFormatted = format(invoiceDate, 'MMM d, yyyy'); const dueDate = invoice.due_date ? format(parseISO(invoice.due_date), 'MMM d') : '—'; const isOverdue = invoice.due_date && isPast(parseISO(invoice.due_date)) && invoice.status !== "Paid" && invoice.status !== "Reconciled"; return ( navigate(createPageUrl(`InvoiceDetail?id=${invoice.id}`))} >

{invoice.invoice_number}

{invoice.manager_name || '—'}

{invoice.business_name || '—'}

{invoice.event_name || 'Untitled'}

{invoice.hub || '—'}
{dateFormatted} ${invoice.amount?.toLocaleString() || 0} {invoice.status} {dueDate}
); }) )}
{/* Table Footer */} {filteredInvoices.length > 0 && (

Showing {filteredInvoices.length} of {visibleInvoices.length} invoices

Total: ${filteredInvoices.reduce((sum, inv) => sum + (inv.amount || 0), 0).toLocaleString()}
)}
); }