"use client"; import { CircleDollarSign, FileText, Plus, Search, Wallet } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { AiBadge } from "@/components/ai-badge"; import { InvoiceDetailSheet } from "@/components/invoices/invoice-detail-sheet"; import { InvoiceFormDialog } from "@/components/invoices/invoice-form-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { ListPagination } from "@/components/ui/list-pagination"; import { formatInvoiceDate, formatMoney, type Invoice, type InvoiceStatus, invoiceTotal, listInvoices, } from "@/lib/invoices"; const statusVariant: Record< InvoiceStatus, "default" | "secondary" | "destructive" | "outline" > = { draft: "secondary", sent: "default", paid: "outline", void: "destructive", }; // Invoices shown per page before paginating. const PAGE_SIZE = 10; export function InvoicesView() { const { t } = useTranslation(); const [list, setList] = useState([]); const [query, setQuery] = useState(""); const [page, setPage] = useState(1); const [loadError, setLoadError] = useState(null); const [selected, setSelected] = useState(null); const [sheetOpen, setSheetOpen] = useState(false); const [formOpen, setFormOpen] = useState(false); const [formMode, setFormMode] = useState<"create" | "edit">("create"); const [editing, setEditing] = useState(null); useEffect(() => { let active = true; listInvoices() .then((data) => active && setList(data)) .catch((err) => { if (active) { setLoadError( err instanceof Error ? err.message : t("invoices.loadError"), ); } }); return () => { active = false; }; }, [t]); const search = query.trim().toLowerCase(); const filtered = useMemo(() => { if (!search) return list; return list.filter( (inv) => inv.name.toLowerCase().includes(search) || inv.number.toLowerCase().includes(search) || inv.fileNumber.includes(search) || inv.status.toLowerCase().includes(search), ); }, [list, search]); // Client-side pagination over the filtered list (10/page); clamp at render so a // shrinking list never leaves us past the last page. const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); const safePage = Math.min(page, totalPages); const pageRows = filtered.slice( (safePage - 1) * PAGE_SIZE, safePage * PAGE_SIZE, ); const kpis = useMemo(() => { const unpaid = list .filter((i) => i.status === "draft" || i.status === "sent") .reduce((sum, i) => sum + invoiceTotal(i), 0); const paid = list .filter((i) => i.status === "paid") .reduce((sum, i) => sum + invoiceTotal(i), 0); const drafts = list.filter((i) => i.status === "draft").length; return [ { label: t("invoices.kpi.outstanding"), value: formatMoney(unpaid), icon: CircleDollarSign, }, { label: t("invoices.kpi.paid"), value: formatMoney(paid), icon: Wallet }, { label: t("invoices.kpi.drafts"), value: String(drafts), icon: FileText, }, ]; }, [list, t]); const openInvoice = (inv: Invoice) => { setSelected(inv); setSheetOpen(true); }; const upsert = (saved: Invoice) => { setList((prev) => { const exists = prev.some((i) => i.id === saved.id); return exists ? prev.map((i) => (i.id === saved.id ? saved : i)) : [saved, ...prev]; }); setSelected((cur) => (cur?.id === saved.id ? saved : cur)); }; return (

{t("invoices.title")}

{t("invoices.subtitle")}

{ setQuery(event.target.value); setPage(1); }} placeholder={t("invoices.searchPlaceholder")} value={query} />
{kpis.map((k) => (
{k.label} {k.value}
))}
{pageRows.map((inv) => ( ))} {filtered.length === 0 && (

{loadError ? loadError : search ? t("invoices.noMatches") : t("invoices.empty")}

)}
setList((prev) => prev.filter((i) => i.id !== id))} onEdit={(inv) => { setSheetOpen(false); setFormMode("edit"); setEditing(inv); setFormOpen(true); }} onOpenChange={setSheetOpen} open={sheetOpen} />
); }