From 99bc049cf40db0140441bacbaf22beab1e70940f Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Sun, 31 May 2026 15:18:55 -0300 Subject: [PATCH] fix(boletos): diferencia pagamentos e recebimentos --- .../calendar/components/event-modal.tsx | 8 ++- src/features/dashboard/bills/bills-helpers.ts | 47 ++++++++++++- .../components/bills/bill-list-item.tsx | 68 +++++++++++-------- .../components/bills/bill-payment-dialog.tsx | 30 +++++--- .../dashboard/components/bills/bills-list.tsx | 2 +- .../components/bills/bills-widget-view.tsx | 4 +- .../details/payer-payment-method-cards.tsx | 2 +- .../transactions/actions/single-actions.ts | 14 ++-- src/shared/lib/payers/details.ts | 3 + src/shared/utils/financial-dates.ts | 10 +-- 10 files changed, 130 insertions(+), 58 deletions(-) diff --git a/src/features/calendar/components/event-modal.tsx b/src/features/calendar/components/event-modal.tsx index b42dfa9..ce69596 100644 --- a/src/features/calendar/components/event-modal.tsx +++ b/src/features/calendar/components/event-modal.tsx @@ -81,6 +81,8 @@ const renderLancamento = ( const renderBoleto = (event: Extract) => { const isPaid = Boolean(event.transaction.isSettled); + const isIncome = event.transaction.transactionType === "Receita"; + const settlementLabel = isIncome ? "Recebido" : "Pago"; const dueDateLabel = formatFinancialDateLabel( event.transaction.dueDate, "Vence em", @@ -89,7 +91,7 @@ const renderBoleto = (event: Extract) => { const paymentDateLabel = isPaid ? formatFinancialDateLabel( event.transaction.boletoPaymentDate, - "Pago em", + `${settlementLabel} em`, DATE_FORMAT, ) : null; @@ -109,7 +111,9 @@ const renderBoleto = (event: Extract) => { {paymentDateLabel} )} - {isPaid ? "Pago" : "Pendente"} + + {isPaid ? settlementLabel : "Pendente"} + ; +export const isIncomeBill = (bill: Pick) => { + return bill.transactionType === "Receita"; +}; + export const formatBillDateLabel = (value: string | null, prefix?: string) => { return formatFinancialDateLabel(value, prefix); }; @@ -22,10 +32,15 @@ export const buildBillStatusLabel = (bill: BillStatusDateItem) => { isSettled: bill.isSettled, dueDate: bill.dueDate, paidAt: bill.boletoPaymentDate, + paidPrefix: isIncomeBill(bill) ? "Recebido em" : "Pago em", }); }; export const buildBillWidgetStatusLabel = (bill: BillStatusDateItem) => { + if (bill.isSettled && isIncomeBill(bill)) { + return formatRelativeFinancialDateLabel(bill.boletoPaymentDate, "received"); + } + return buildRelativeFinancialStatusLabel({ isSettled: bill.isSettled, dueDate: bill.dueDate, @@ -43,6 +58,34 @@ export const isBillOverdue = (bill: DashboardBill) => { return isDateOnlyPast(bill.dueDate); }; +export const formatBillWidgetOverdueLabel = ( + bill: Pick, +): string | null => { + if (bill.isSettled) { + return null; + } + + const dueDateValue = toDateOnlyString(bill.dueDate); + const todayValue = getBusinessDateString(); + if (!dueDateValue || dueDateValue >= todayValue) { + return null; + } + + const dueDate = parseUtcDateString(dueDateValue); + const today = parseUtcDateString(todayValue); + if (!dueDate || !today) { + return null; + } + + const overdueDays = Math.round( + (today.getTime() - dueDate.getTime()) / (24 * 60 * 60 * 1000), + ); + const overdueLabel = isIncomeBill(bill) ? "Atrasada" : "Atrasado"; + return overdueDays === 1 + ? `${overdueLabel} · venceu ontem` + : `${overdueLabel} · venceu há ${overdueDays} dias`; +}; + export const getBillStatusBadgeVariant = ( statusLabel: string, ): "success" | "info" => { diff --git a/src/features/dashboard/components/bills/bill-list-item.tsx b/src/features/dashboard/components/bills/bill-list-item.tsx index ebb08c6..69ed460 100644 --- a/src/features/dashboard/components/bills/bill-list-item.tsx +++ b/src/features/dashboard/components/bills/bill-list-item.tsx @@ -1,9 +1,11 @@ -import { RiCheckboxCircleFill, RiExternalLinkLine } from "@remixicon/react"; +import { RiCheckboxCircleFill } from "@remixicon/react"; import Link from "next/link"; import { buildBillStatusLabel, buildBillWidgetStatusLabel, + formatBillWidgetOverdueLabel, isBillOverdue, + isIncomeBill, } from "@/features/dashboard/bills/bills-helpers"; import type { DashboardBill } from "@/features/dashboard/bills/bills-queries"; import { EstablishmentLogo } from "@/shared/components/entity-avatar"; @@ -36,8 +38,10 @@ export function BillListItem({ bill, period, onPay }: BillListItemProps) { const statusLabel = buildBillWidgetStatusLabel(bill); const absoluteStatusLabel = buildBillStatusLabel(bill); const overdue = isBillOverdue(bill); + const income = isIncomeBill(bill); + const overdueLabel = formatBillWidgetOverdueLabel(bill); const statusTooltipLabel = - statusLabel && statusLabel !== absoluteStatusLabel + overdueLabel || (statusLabel && statusLabel !== absoluteStatusLabel) ? absoluteStatusLabel : null; const href = buildTransactionsHref(bill.name, period); @@ -53,10 +57,6 @@ export function BillListItem({ bill, period, onPay }: BillListItemProps) { className="inline-flex max-w-full items-center gap-1 text-sm font-medium text-foreground underline-offset-2 hover:text-primary hover:underline" > {bill.name} -
{statusLabel ? ( @@ -67,9 +67,10 @@ export function BillListItem({ bill, period, onPay }: BillListItemProps) { className={cn( "cursor-help rounded-full py-0.5", bill.isSettled && "text-success font-semibold", + overdue && "text-destructive font-semibold", )} > - {statusLabel} + {overdueLabel ?? statusLabel} @@ -81,9 +82,10 @@ export function BillListItem({ bill, period, onPay }: BillListItemProps) { className={cn( "rounded-full py-0.5", bill.isSettled && "text-success font-semibold", + overdue && "text-destructive font-semibold", )} > - {statusLabel} + {overdueLabel ?? statusLabel} ) ) : null} @@ -93,29 +95,35 @@ export function BillListItem({ bill, period, onPay }: BillListItemProps) {
- + ) : income ? ( + "Receber" + ) : ( + "Pagar" + )} + + )}
); diff --git a/src/features/dashboard/components/bills/bill-payment-dialog.tsx b/src/features/dashboard/components/bills/bill-payment-dialog.tsx index 46878bf..66b99fd 100644 --- a/src/features/dashboard/components/bills/bill-payment-dialog.tsx +++ b/src/features/dashboard/components/bills/bill-payment-dialog.tsx @@ -7,6 +7,7 @@ import { type BillDialogState, formatBillDateLabel, getBillStatusBadgeVariant, + isIncomeBill, } from "@/features/dashboard/bills/bills-helpers"; import type { BillPaymentAccountOption, @@ -66,11 +67,13 @@ export function BillPaymentDialog({ onConfirm, }: BillPaymentDialogProps) { const isProcessing = modalState === "processing" || isPending; + const income = bill ? isIncomeBill(bill) : false; + const settlementLabel = income ? "Recebido" : "Pago"; const dueLabel = bill ? formatBillDateLabel(bill.dueDate, "Vencimento:") : null; const paidLabel = bill - ? formatBillDateLabel(bill.boletoPaymentDate, "Pago em:") + ? formatBillDateLabel(bill.boletoPaymentDate, `${settlementLabel} em:`) : null; const isBillPending = bill ? !bill.isSettled : false; const paymentDateValue = paymentDate.toISOString().split("T")[0] ?? ""; @@ -103,8 +106,8 @@ export function BillPaymentDialog({ > {modalState === "success" ? ( ) : ( @@ -112,10 +115,12 @@ export function BillPaymentDialog({
- Confirmar pagamento + + {income ? "Confirmar recebimento" : "Confirmar pagamento"} + {isBillPending - ? "Escolha a conta de origem e a data em que o boleto foi pago." + ? `Escolha a conta de ${income ? "destino" : "origem"} e a data em que o boleto foi ${income ? "recebido" : "pago"}.` : "Boleto"}
@@ -158,12 +163,15 @@ export function BillPaymentDialog({
- {bill.isSettled ? "Pago em" : "Vencimento"} + {bill.isSettled + ? `${settlementLabel} em` + : "Vencimento"}

{bill.isSettled - ? (paidLabel?.replace("Pago em: ", "") ?? "—") + ? (paidLabel?.replace(`${settlementLabel} em: `, "") ?? + "—") : (dueLabel?.replace("Vencimento: ", "") ?? "—")}

@@ -175,7 +183,7 @@ export function BillPaymentDialog({