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 (
<>
{visibleInvoices.length} total
{((metrics.paid / metrics.all) * 100 || 0).toFixed(0)}% collected
• {getStatusCount("Disputed")} disputed (${metrics.disputed.toLocaleString()})
)} {metrics.overdue > 0 && (• {getStatusCount("Overdue")} overdue (${metrics.overdue.toLocaleString()})
)}${(metrics.all / 1000).toFixed(0)}K
Total
${(metrics.outstanding / 1000).toFixed(0)}K
Pending
{getStatusCount("Disputed")}
Disputed
${(metrics.paid / 1000).toFixed(0)}K
Paid
Showing {filteredInvoices.length} of {visibleInvoices.length} invoices