import { useCallback, useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { Alert, Badge, Button, Card, PageHeader } from "@eden/ui"; import { useAuth } from "../../lib/auth/AuthContext.js"; import { ApiError } from "../../lib/api/http.js"; import { consolidateBillingRun, getBillingRun } from "../../lib/api/billing.js"; import type { BillingRunWithInvoices } from "../../lib/api/billing.js"; import { formatCurrencyBRL, formatDateBR } from "../../lib/format.js"; import { Breadcrumb } from "../../components/Breadcrumb.js"; import { INVOICE_STATUS_LABEL, INVOICE_STATUS_VARIANT, RUN_STATUS_LABEL, RUN_STATUS_VARIANT, periodLabel } from "./billingStatus.js"; /** * Layout reference: DreamsERP theme `.tmp/theme-reference/recurringInvoiceDetails.tsx` * (breadcrumb + title with a status pill, a two-column summary block, an * item table, right-aligned total below it) — same shape as * `ContractDetailPage`'s financial summary + items sections. Rebuilt with * `@eden/ui` + plain Tailwind, no theme JSX copied. */ export function BillingRunDetailPage() { const { id } = useParams<{ id: string }>(); const { accessToken } = useAuth(); const [run, setRun] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const load = useCallback(() => { if (!id || !accessToken) return; setLoading(true); setError(null); getBillingRun(id, accessToken) .then(setRun) .catch((err) => setError(err instanceof ApiError ? err.message : "Não foi possível carregar o fechamento.")) .finally(() => setLoading(false)); }, [id, accessToken]); useEffect(() => { load(); }, [load]); if (loading) { return (

Carregando fechamento…

); } if (error || !run) { return (
{error ?? "Fechamento não encontrado."}
); } return (
{RUN_STATUS_LABEL[run.status]}} /> {run.status === "draft" ? ( Rodar o formulário de fechamento de novo com o MESMO ano, mês e dia de cobrança reaproveita este fechamento e regenera suas faturas — seguro para repetir. Consolidar é a única ação irreversível. ) : null}
{run.invoices.length === 0 ? ( ) : ( run.invoices.map((invoice) => ( )) )} {run.invoices.length > 0 ? ( ) : null}
Fatura Assinatura Vencimento Valor Status
Nenhuma fatura gerada — nenhuma assinatura ativa bate com este dia de cobrança.
{invoice.id.slice(0, 8)} {invoice.billing_subscription_id.slice(0, 8)} {formatDateBR(invoice.due_date)} {formatCurrencyBRL(invoice.amount)} {INVOICE_STATUS_LABEL[invoice.status]}
Total {formatCurrencyBRL(run.totalAmount)}
); } function Row({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function ConsolidateCard({ run, accessToken, onConsolidated, }: { run: BillingRunWithInvoices; accessToken: string | null; onConsolidated: () => void; }) { const [confirming, setConfirming] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); if (run.status !== "draft") { return (

Status {RUN_STATUS_LABEL[run.status]} é final — este fechamento não pode mais ser alterado.

); } async function handleConfirm() { if (!accessToken) return; setError(null); setSubmitting(true); try { await consolidateBillingRun(run.id, accessToken); setConfirming(false); onConsolidated(); } catch (err) { setError(err instanceof ApiError ? err.message : "Não foi possível consolidar o fechamento."); } finally { setSubmitting(false); } } return ( {error ? ( {error} ) : null} {confirming ? (
Consolidar trava o fechamento e todas as suas faturas — não será mais possível regenerá-las.
) : ( )}
); }