"use client"; import { useState, useEffect } from "react"; import Link from "next/link"; import { useTranslations } from "next-intl"; import { Card } from "@/components/ui/card"; import type { Invoice, InvoiceStatus } from "@/types"; interface Props { initialInvoices: Invoice[]; } const STATUS_FILTERS: (InvoiceStatus | "all")[] = [ "all", "open", "overdue", "paid", "void", ]; /** * Filterable invoice list. Filters live in URL-less local state * (simpler than syncing to query string for a v1 admin tool); a * page refresh resets. * * Re-fetching strategy: when filters change, hit the API directly * rather than router.refresh() so we don't bounce the user through * a full page render. */ export function InvoicesTable({ initialInvoices }: Props) { const t = useTranslations("adminBilling"); const [statusFilter, setStatusFilter] = useState("all"); const [monthFilter, setMonthFilter] = useState(""); const [invoices, setInvoices] = useState(initialInvoices); const [busy, setBusy] = useState(false); useEffect(() => { // Effect runs after initial render too; skip refetch on mount // when filters are at their defaults — the server already // gave us the right initial set. if (statusFilter === "all" && monthFilter === "") return; let cancelled = false; setBusy(true); const params = new URLSearchParams(); if (statusFilter !== "all") params.set("status", statusFilter); if (monthFilter) params.set("month", monthFilter); fetch(`/api/admin/billing/invoices?${params}`) .then((r) => r.json()) .then((data) => { if (!cancelled) setInvoices(data); }) .catch((e) => console.error("Failed to load invoices:", e)) .finally(() => { if (!cancelled) setBusy(false); }); return () => { cancelled = true; }; }, [statusFilter, monthFilter]); return (
{monthFilter && ( )} {busy && ( {t("loading")} )} {/* Phase 8: shortcuts to the custom-invoice flow. The Drafts link is muted because most of the time it's empty; New invoice is the prominent CTA. */}
{t("draftsLink")} + {t("newInvoiceBtn")}
{invoices.length === 0 ? (

{t("noInvoicesFound")}

) : (
{invoices.map((inv) => ( ))}
{t("invoiceNumberCol")} {t("orgCol")} {t("periodCol")} {t("statusCol")} {t("totalCol")} {t("dueCol")}
{inv.invoiceNumber}
{inv.billingSnapshot.companyName || ( {inv.zitadelOrgId} )}
{inv.periodStart ? inv.periodStart.slice(0, 7) : inv.source === "custom" ? "—" : ""} CHF {inv.totalChf.toFixed(2)} {inv.dueAt}
)}
); } function StatusPill({ status }: { status: InvoiceStatus }) { const t = useTranslations("adminBilling"); const color = status === "paid" ? "bg-success/15 text-success" : status === "overdue" ? "bg-error/15 text-error" : status === "void" || status === "uncollectible" ? "bg-text-muted/15 text-text-muted" : "bg-accent/15 text-accent"; return ( {t(`status_${status}`)} ); }