import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import Layout from "@/components/Layout"; import { Button } from "@/components/ui/button"; import { useAuth } from "@/contexts/AuthContext"; import { aethexToast } from "@/lib/aethex-toast"; import { supabase } from "@/lib/supabase"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Input } from "@/components/ui/input"; import LoadingScreen from "@/components/LoadingScreen"; import { Receipt, ArrowLeft, Search, Download, Eye, Calendar, DollarSign, CheckCircle, Clock, AlertCircle, CreditCard, FileText, ArrowUpRight, Filter, } from "lucide-react"; const API_BASE = import.meta.env.VITE_API_BASE || ""; interface Invoice { id: string; invoice_number: string; description: string; status: "pending" | "paid" | "overdue" | "cancelled"; amount: number; tax: number; total: number; issued_date: string; due_date: string; paid_date?: string; line_items: { description: string; quantity: number; unit_price: number; total: number }[]; payment_method?: string; contract_id?: string; created_at: string; } export default function ClientInvoices() { const navigate = useNavigate(); const { user, loading: authLoading } = useAuth(); const [invoices, setInvoices] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [selectedInvoice, setSelectedInvoice] = useState(null); useEffect(() => { if (!authLoading && user) { loadInvoices(); } }, [user, authLoading]); const loadInvoices = async () => { try { setLoading(true); const { data: { session } } = await supabase.auth.getSession(); const token = session?.access_token; if (!token) throw new Error("No auth token"); const res = await fetch(`${API_BASE}/api/corp/invoices`, { headers: { Authorization: `Bearer ${token}` }, }); if (res.ok) { const data = await res.json(); setInvoices(Array.isArray(data) ? data : data.invoices || []); } } catch (error) { console.error("Failed to load invoices", error); aethexToast({ message: "Failed to load invoices", type: "error" }); } finally { setLoading(false); } }; const handlePayNow = async (invoice: Invoice) => { try { const { data: { session } } = await supabase.auth.getSession(); const token = session?.access_token; if (!token) throw new Error("No auth token"); const res = await fetch(`${API_BASE}/api/corp/invoices/${invoice.id}/pay`, { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); if (res.ok) { const data = await res.json(); if (data.checkout_url) { window.location.href = data.checkout_url; } else { aethexToast({ message: "Payment initiated", type: "success" }); loadInvoices(); } } else { throw new Error("Payment failed"); } } catch (error) { console.error("Payment error", error); aethexToast({ message: "Failed to process payment", type: "error" }); } }; if (authLoading || loading) { return ; } const filteredInvoices = invoices.filter((inv) => { const matchesSearch = inv.invoice_number.toLowerCase().includes(searchQuery.toLowerCase()) || inv.description?.toLowerCase().includes(searchQuery.toLowerCase()); const matchesStatus = statusFilter === "all" || inv.status === statusFilter; return matchesSearch && matchesStatus; }); const getStatusColor = (status: string) => { switch (status) { case "paid": return "bg-green-500/20 border-green-500/30 text-green-300"; case "pending": return "bg-yellow-500/20 border-yellow-500/30 text-yellow-300"; case "overdue": return "bg-red-500/20 border-red-500/30 text-red-300"; case "cancelled": return "bg-gray-500/20 border-gray-500/30 text-gray-300"; default: return ""; } }; const getStatusIcon = (status: string) => { switch (status) { case "paid": return ; case "pending": return ; case "overdue": return ; case "cancelled": return ; default: return null; } }; const stats = { total: invoices.reduce((acc, i) => acc + (i.total || i.amount || 0), 0), paid: invoices.filter(i => i.status === "paid").reduce((acc, i) => acc + (i.total || i.amount || 0), 0), pending: invoices.filter(i => i.status === "pending").reduce((acc, i) => acc + (i.total || i.amount || 0), 0), overdue: invoices.filter(i => i.status === "overdue").reduce((acc, i) => acc + (i.total || i.amount || 0), 0), }; return (

Invoices & Billing

Manage payments and billing history

{/* Filters */}
setSearchQuery(e.target.value)} className="pl-10 bg-slate-800/50 border-slate-700" />
All Pending Paid Overdue
{/* Invoice Detail or List */} {selectedInvoice ? (
Invoice {selectedInvoice.invoice_number} {selectedInvoice.description}
{/* Invoice Overview */}

Status

{getStatusIcon(selectedInvoice.status)} {selectedInvoice.status}

Total Amount

${(selectedInvoice.total || selectedInvoice.amount)?.toLocaleString()}

Issue Date

{new Date(selectedInvoice.issued_date).toLocaleDateString()}

Due Date

{new Date(selectedInvoice.due_date).toLocaleDateString()}

{/* Line Items */} {selectedInvoice.line_items?.length > 0 && (

Line Items

{selectedInvoice.line_items.map((item, idx) => ( ))} {selectedInvoice.tax > 0 && ( )}
Description Qty Unit Price Total
{item.description} {item.quantity} ${item.unit_price?.toLocaleString()} ${item.total?.toLocaleString()}
Subtotal ${selectedInvoice.amount?.toLocaleString()}
Tax ${selectedInvoice.tax?.toLocaleString()}
Total ${(selectedInvoice.total || selectedInvoice.amount)?.toLocaleString()}
)} {/* Payment Info */} {selectedInvoice.status === "paid" && selectedInvoice.paid_date && (

Payment Received

Paid on {new Date(selectedInvoice.paid_date).toLocaleDateString()} {selectedInvoice.payment_method && ` via ${selectedInvoice.payment_method}`}

)} {/* Actions */}
{(selectedInvoice.status === "pending" || selectedInvoice.status === "overdue") && ( )}
) : (
{filteredInvoices.length === 0 ? (

{searchQuery || statusFilter !== "all" ? "No invoices match your filters" : "No invoices yet"}

) : ( filteredInvoices.map((invoice) => ( setSelectedInvoice(invoice)} >

{invoice.invoice_number}

{getStatusIcon(invoice.status)} {invoice.status}

{invoice.description}

Issued: {new Date(invoice.issued_date).toLocaleDateString()} Due: {new Date(invoice.due_date).toLocaleDateString()}

${(invoice.total || invoice.amount)?.toLocaleString()}

{(invoice.status === "pending" || invoice.status === "overdue") && ( )}
)) )}
)}
); }