From ca67d36f33e2ce493a323d08980580468de76ccb Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Sun, 15 Mar 2026 23:23:35 +0000 Subject: [PATCH] style: redesenha cards-resumo de conta e fatura --- .../components/account-statement-card.tsx | 238 ++++++------- .../components/invoice-summary-card.tsx | 315 +++++++++--------- 2 files changed, 258 insertions(+), 295 deletions(-) diff --git a/src/features/accounts/components/account-statement-card.tsx b/src/features/accounts/components/account-statement-card.tsx index 3b878b0..411e080 100644 --- a/src/features/accounts/components/account-statement-card.tsx +++ b/src/features/accounts/components/account-statement-card.tsx @@ -1,15 +1,11 @@ "use client"; + import { RiInformationLine } from "@remixicon/react"; import Image from "next/image"; import type { ReactNode } from "react"; import MoneyValues from "@/shared/components/money-values"; import { Badge } from "@/shared/components/ui/badge"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@/shared/components/ui/card"; +import { Card, CardContent } from "@/shared/components/ui/card"; import { Tooltip, TooltipContent, @@ -19,8 +15,6 @@ import { resolveLogoSrc } from "@/shared/lib/logo"; import { formatCurrency } from "@/shared/utils/currency"; import { cn } from "@/shared/utils/ui"; -type DetailValue = string | number | ReactNode; - type AccountStatementCardProps = { accountName: string; accountType: string; @@ -37,11 +31,7 @@ type AccountStatementCardProps = { const getAccountStatusBadgeVariant = ( status: string, ): "success" | "outline" => { - const normalizedStatus = status.toLowerCase(); - if (normalizedStatus === "ativa") { - return "success"; - } - return "outline"; + return status.toLowerCase() === "ativa" ? "success" : "outline"; }; export function AccountStatementCard({ @@ -57,149 +47,133 @@ export function AccountStatementCard({ actions, }: AccountStatementCardProps) { const logoPath = resolveLogoSrc(logo); + const resultado = totalIncomes - totalExpenses; return ( - - -
- {logoPath ? ( -
- {`Logo -
- ) : null} - -
-
- - {accountName} - -

- Extrato de {periodLabel} -

+ + +
+ {/* Linha 1 — identidade */} +
+
+ {logoPath ? ( +
+ {`Logo +
+ ) : null} +
+

+ {accountName} +

+

+ Extrato de {periodLabel} +

+
{actions ?
{actions}
: null}
-
- - - {/* Composição do Saldo */} -
- } - tooltip="Saldo inicial cadastrado na conta somado aos lançamentos pagos anteriores a este mês." - /> - -
- - {formatCurrency(totalIncomes)} - - } - tooltip="Total de receitas deste mês classificadas como pagas para esta conta." - /> - - {formatCurrency(totalExpenses)} - - } - tooltip="Total de despesas pagas neste mês (considerando divisão entre pagadores)." - /> - - = 0 - ? "text-success" - : "text-destructive", - )} - /> - } - tooltip="Diferença entre entradas e saídas do mês; positivo indica saldo crescente." + {/* Linha 2 — saldo final (hero) */} +
+

+ Saldo ao final do período +

+ +
+ + {status} + + + {accountType} + +
- {/* Saldo Atual - Destaque Principal */} - } - tooltip="Saldo inicial do período + entradas - saídas realizadas neste mês." - /> -
+ {/* Linha 3 — breakdown financeiro */} +
+ + + {formatCurrency(openingBalance)} + + - {/* Informações da FinancialAccount */} -
- - - - {status} - -
- } - tooltip="Indica se a conta está ativa para lançamentos ou foi desativada." - /> + + + {formatCurrency(totalIncomes)} + + + + + + {formatCurrency(totalExpenses)} + + + + + = 0 ? "text-success" : "text-destructive", + )} + > + {resultado >= 0 ? "+" : ""} + {formatCurrency(resultado)} + + +
); } -function DetailItem({ +function MetaItem({ label, - value, - className, tooltip, + children, }: { label: string; - value: DetailValue; - className?: string; - tooltip?: string; + tooltip: string; + children: ReactNode; }) { return ( -
- +
+ {label} - {tooltip ? ( - - - - - - {tooltip} - - - ) : null} + + + + + + {tooltip} + + -
{value}
+
{children}
); } diff --git a/src/features/invoices/components/invoice-summary-card.tsx b/src/features/invoices/components/invoice-summary-card.tsx index 6265723..85dec51 100644 --- a/src/features/invoices/components/invoice-summary-card.tsx +++ b/src/features/invoices/components/invoice-summary-card.tsx @@ -3,6 +3,7 @@ import { RiEditLine } from "@remixicon/react"; import Image from "next/image"; import { useRouter } from "next/navigation"; +import type { ReactNode } from "react"; import { useEffect, useState, useTransition } from "react"; import { toast } from "sonner"; import { @@ -13,12 +14,7 @@ import MoneyValues from "@/shared/components/money-values"; import StatusDot from "@/shared/components/status-dot"; import { Badge } from "@/shared/components/ui/badge"; import { Button } from "@/shared/components/ui/button"; -import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "@/shared/components/ui/card"; +import { Card, CardContent } from "@/shared/components/ui/card"; import { resolveCardBrandAsset } from "@/shared/lib/cards/brand-assets"; import { INVOICE_PAYMENT_STATUS, @@ -29,6 +25,7 @@ import { } from "@/shared/lib/invoices"; import { resolveLogoSrc } from "@/shared/lib/logo"; import { formatCurrency } from "@/shared/utils/currency"; +import { formatDateOnly } from "@/shared/utils/date"; import { cn } from "@/shared/utils/ui"; import { EditPaymentDateDialog } from "./edit-payment-date-dialog"; @@ -66,13 +63,17 @@ const formatDay = (value: string) => value.padStart(2, "0"); const getCardStatusDotColor = (status: string | null) => { if (!status) return "bg-gray-400"; - const normalizedStatus = status.toLowerCase(); - if (normalizedStatus === "ativo" || normalizedStatus === "active") { - return "bg-success"; - } - return "bg-gray-400"; + const s = status.toLowerCase(); + return s === "ativo" || s === "active" ? "bg-success" : "bg-gray-400"; }; +const formatPaymentDate = (value: Date | null) => + formatDateOnly(value, { + day: "2-digit", + month: "short", + year: "numeric", + }) ?? "data não informada"; + export function InvoiceSummaryCard({ cardId, period, @@ -95,20 +96,21 @@ export function InvoiceSummaryCard({ initialPaymentDate ?? new Date(), ); - // Atualizar estado quando initialPaymentDate mudar useEffect(() => { setPaymentDate(initialPaymentDate ?? new Date()); }, [initialPaymentDate]); const logoPath = resolveLogoSrc(logo); const brandAsset = resolveCardBrandAsset(cardBrand); - const limitLabel = - typeof limitAmount === "number" ? formatCurrency(limitAmount) : "—"; + const isPaid = invoiceStatus === INVOICE_PAYMENT_STATUS.PAID; + const paymentDateLabel = isPaid ? formatPaymentDate(paymentDate) : null; + const actionDescription = isPaid + ? `Pagamento registrado em ${paymentDateLabel}.` + : INVOICE_STATUS_DESCRIPTION[invoiceStatus]; - const targetStatus = - invoiceStatus === INVOICE_PAYMENT_STATUS.PAID - ? INVOICE_PAYMENT_STATUS.PENDING - : INVOICE_PAYMENT_STATUS.PAID; + const targetStatus = isPaid + ? INVOICE_PAYMENT_STATUS.PENDING + : INVOICE_PAYMENT_STATUS.PAID; const handleAction = () => { startTransition(async () => { @@ -152,150 +154,145 @@ export function InvoiceSummaryCard({ }; return ( - - -
- {logoPath ? ( -
- {`Logo -
- ) : cardBrand ? ( - - {cardBrand} - - ) : null} - -
-
- - {cardName} - -

- Invoice de {periodLabel} -

+ + +
+ {/* Linha 1 — identidade */} +
+
+ {logoPath ? ( +
+ {`Logo +
+ ) : cardBrand ? ( + + {cardBrand.slice(0, 2).toUpperCase()} + + ) : null} +
+

+ {cardName} +

+

+ Fatura de {periodLabel} +

+
{actions ?
{actions}
: null}
-
- - - {/* Destaque Principal */} -
- - } - /> - +

+ Valor da fatura +

+ +
{INVOICE_STATUS_LABEL[invoiceStatus]} - } - /> -
- - {/* Informações Gerais */} -
- Dia {formatDay(closingDay)} - } - /> - Dia {formatDay(dueDay)}} - /> - - {`Bandeira - {cardBrand} -
- ) : cardBrand ? ( - {cardBrand} - ) : ( - - ) - } - /> - + {cardStatus ? ( +
- {cardStatus} + {cardStatus}
- ) : ( - - ) - } - /> -
+ ) : null} +
+
- + {/* Linha 3 — metadados do cartão */} +
+ + + Dia {formatDay(dueDay)} + + - {/* Ações */} -
-

- {INVOICE_STATUS_DESCRIPTION[invoiceStatus]} -

-
- - {invoiceStatus === INVOICE_PAYMENT_STATUS.PAID && ( - - - - } - currentDate={paymentDate} - onDateChange={handleDateChange} - /> - )} + + + Dia {formatDay(closingDay)} + + + + {typeof limitAmount === "number" ? ( + + + {formatCurrency(limitAmount)} + + + ) : null} + + {cardBrand ? ( + +
+ {brandAsset ? ( + {cardBrand} + ) : null} + + {cardBrand} + +
+
+ ) : null} +
+ + {/* Linha 4 — ação */} +
+
+

+ {actionDescription} +

+
+
+ + {isPaid ? ( + + + + } + currentDate={paymentDate} + onDateChange={handleDateChange} + /> + ) : null} +
@@ -303,21 +300,13 @@ export function InvoiceSummaryCard({ ); } -type DetailItemProps = { - label?: string; - value: React.ReactNode; - className?: string; -}; - -function DetailItem({ label, value, className }: DetailItemProps) { +function MetaItem({ label, children }: { label: string; children: ReactNode }) { return ( -
- {label && ( - - {label} - - )} -
{value}
+
+ + {label} + +
{children}
); }