"use client"; import { updateInvoicePaymentStatusAction } from "@/app/(dashboard)/cartoes/[cartaoId]/fatura/actions"; import MoneyValues from "@/components/money-values"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { CardContent } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter as ModalFooter, } from "@/components/ui/dialog"; import type { DashboardInvoice } from "@/lib/dashboard/invoices"; import { INVOICE_PAYMENT_STATUS, INVOICE_STATUS_LABEL } from "@/lib/faturas"; import { getAvatarSrc } from "@/lib/pagadores/utils"; import { formatPeriodForUrl } from "@/lib/utils/period"; import { RiBillLine, RiCheckboxCircleFill, RiCheckboxCircleLine, RiExternalLinkLine, RiLoader4Line, } from "@remixicon/react"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState, useTransition } from "react"; import { toast } from "sonner"; import { Badge } from "../ui/badge"; import { HoverCard, HoverCardContent, HoverCardTrigger, } from "../ui/hover-card"; import { WidgetEmptyState } from "../widget-empty-state"; type InvoicesWidgetProps = { invoices: DashboardInvoice[]; }; type ModalState = "idle" | "processing" | "success"; const DUE_DATE_FORMATTER = new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "short", year: "numeric", timeZone: "UTC", }); const resolveLogoPath = (logo: string | null) => { if (!logo) { return null; } if (/^(https?:\/\/|data:)/.test(logo)) { return logo; } return logo.startsWith("/") ? logo : `/logos/${logo}`; }; const buildInitials = (value: string) => { const parts = value.trim().split(/\s+/).filter(Boolean); if (parts.length === 0) { return "CC"; } if (parts.length === 1) { const firstPart = parts[0]; return firstPart ? firstPart.slice(0, 2).toUpperCase() : "CC"; } const firstChar = parts[0]?.[0] ?? ""; const secondChar = parts[1]?.[0] ?? ""; return `${firstChar}${secondChar}`.toUpperCase() || "CC"; }; const parseDueDate = (period: string, dueDay: string) => { const [yearStr, monthStr] = period.split("-"); const dayNumber = Number.parseInt(dueDay, 10); const year = Number.parseInt(yearStr ?? "", 10); const month = Number.parseInt(monthStr ?? "", 10); if ( Number.isNaN(dayNumber) || Number.isNaN(year) || Number.isNaN(month) || period.length !== 7 ) { return { label: `Vence dia ${dueDay}`, }; } const date = new Date(Date.UTC(year, month - 1, dayNumber)); return { label: `Vence em ${DUE_DATE_FORMATTER.format(date)}`, }; }; const formatPaymentDate = (value: string | null) => { if (!value) { return null; } const [yearStr, monthStr, dayStr] = value.split("-"); const year = Number.parseInt(yearStr ?? "", 10); const month = Number.parseInt(monthStr ?? "", 10); const day = Number.parseInt(dayStr ?? "", 10); if ( Number.isNaN(year) || Number.isNaN(month) || Number.isNaN(day) || yearStr?.length !== 4 || monthStr?.length !== 2 || dayStr?.length !== 2 ) { return null; } const date = new Date(Date.UTC(year, month - 1, day)); return { label: `Pago em ${DUE_DATE_FORMATTER.format(date)}`, }; }; const getTodayDateString = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const day = String(now.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }; const formatSharePercentage = (value: number) => { if (!Number.isFinite(value) || value <= 0) { return "0%"; } const digits = value >= 10 ? 0 : value >= 1 ? 1 : 2; return ( value.toLocaleString("pt-BR", { minimumFractionDigits: digits, maximumFractionDigits: digits, }) + "%" ); }; const getShareLabel = (amount: number, total: number) => { if (total <= 0) { return "0% do total"; } const percentage = (amount / total) * 100; return `${formatSharePercentage(percentage)} do total`; }; export function InvoicesWidget({ invoices }: InvoicesWidgetProps) { const router = useRouter(); const [isPending, startTransition] = useTransition(); const [items, setItems] = useState(invoices); const [isModalOpen, setIsModalOpen] = useState(false); const [selectedId, setSelectedId] = useState(null); const [modalState, setModalState] = useState("idle"); useEffect(() => { setItems(invoices); }, [invoices]); const selectedInvoice = useMemo( () => items.find((invoice) => invoice.id === selectedId) ?? null, [items, selectedId] ); const selectedLogo = useMemo( () => (selectedInvoice ? resolveLogoPath(selectedInvoice.logo) : null), [selectedInvoice] ); const selectedPaymentInfo = useMemo( () => (selectedInvoice ? formatPaymentDate(selectedInvoice.paidAt) : null), [selectedInvoice] ); const handleOpenModal = (invoiceId: string) => { setSelectedId(invoiceId); setModalState("idle"); setIsModalOpen(true); }; const handleCloseModal = () => { setIsModalOpen(false); setModalState("idle"); setSelectedId(null); }; const handleConfirmPayment = () => { if (!selectedInvoice) { return; } setModalState("processing"); startTransition(async () => { const result = await updateInvoicePaymentStatusAction({ cartaoId: selectedInvoice.cardId, period: selectedInvoice.period, status: INVOICE_PAYMENT_STATUS.PAID, }); if (result.success) { toast.success(result.message); setItems((previous) => previous.map((invoice) => invoice.id === selectedInvoice.id ? { ...invoice, paymentStatus: INVOICE_PAYMENT_STATUS.PAID, paidAt: getTodayDateString(), } : invoice ) ); setModalState("success"); router.refresh(); return; } toast.error(result.error); setModalState("idle"); }); }; const getStatusBadgeVariant = (status: string): "success" | "info" => { const normalizedStatus = status.toLowerCase(); if (normalizedStatus === "em aberto") { return "info"; } return "success"; }; return ( <> {items.length === 0 ? ( } title="Nenhuma fatura para o período selecionado" description="Quando houver cartões com compras registradas, eles aparecerão aqui." /> ) : (
    {items.map((invoice) => { const logo = resolveLogoPath(invoice.logo); const initials = buildInitials(invoice.cardName); const dueInfo = parseDueDate(invoice.period, invoice.dueDay); const isPaid = invoice.paymentStatus === INVOICE_PAYMENT_STATUS.PAID; const paymentInfo = formatPaymentDate(invoice.paidAt); return (
  • {logo ? ( {`Logo ) : ( {initials} )}
    {(() => { const breakdown = invoice.pagadorBreakdown ?? []; const hasBreakdown = breakdown.length > 0; const linkNode = ( {invoice.cardName} ); if (!hasBreakdown) { return linkNode; } const totalForShare = Math.abs(invoice.totalAmount); return ( {linkNode}

    Distribuição por pagador

      {breakdown.map((share, index) => (
    • {buildInitials(share.pagadorName)}

      {share.pagadorName}

      {getShareLabel( share.amount, totalForShare )}

    • ))}
    ); })()}
    {!isPaid ? {dueInfo.label} : null} {isPaid && paymentInfo ? ( {paymentInfo.label} ) : null}
  • ); })}
)}
{ if (!open) { handleCloseModal(); return; } setIsModalOpen(true); }} > { if (modalState === "processing") { event.preventDefault(); return; } handleCloseModal(); }} onPointerDownOutside={(event) => { if (modalState === "processing") { event.preventDefault(); } }} > {modalState === "success" ? (
Pagamento confirmado! Atualizamos o status da fatura. O lançamento do pagamento aparecerá no extrato em instantes.
) : ( <> Confirmar pagamento Revise os dados antes de confirmar. Vamos registrar a fatura como paga. {selectedInvoice ? (
{selectedLogo ? ( {`Logo ) : ( {buildInitials(selectedInvoice.cardName)} )}

Cartão

{selectedInvoice.cardName}

{selectedInvoice.paymentStatus !== INVOICE_PAYMENT_STATUS.PAID ? (

{ parseDueDate( selectedInvoice.period, selectedInvoice.dueDay ).label }

) : null} {selectedInvoice.paymentStatus === INVOICE_PAYMENT_STATUS.PAID && selectedPaymentInfo ? (

{selectedPaymentInfo.label}

) : null}
Valor da fatura
Status atual {INVOICE_STATUS_LABEL[selectedInvoice.paymentStatus]}
) : null} )}
); }