mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
style: redesenha cards-resumo de conta e fatura
This commit is contained in:
@@ -1,15 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiInformationLine } from "@remixicon/react";
|
import { RiInformationLine } from "@remixicon/react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import MoneyValues from "@/shared/components/money-values";
|
import MoneyValues from "@/shared/components/money-values";
|
||||||
import { Badge } from "@/shared/components/ui/badge";
|
import { Badge } from "@/shared/components/ui/badge";
|
||||||
import {
|
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/shared/components/ui/card";
|
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -19,8 +15,6 @@ import { resolveLogoSrc } from "@/shared/lib/logo";
|
|||||||
import { formatCurrency } from "@/shared/utils/currency";
|
import { formatCurrency } from "@/shared/utils/currency";
|
||||||
import { cn } from "@/shared/utils/ui";
|
import { cn } from "@/shared/utils/ui";
|
||||||
|
|
||||||
type DetailValue = string | number | ReactNode;
|
|
||||||
|
|
||||||
type AccountStatementCardProps = {
|
type AccountStatementCardProps = {
|
||||||
accountName: string;
|
accountName: string;
|
||||||
accountType: string;
|
accountType: string;
|
||||||
@@ -37,11 +31,7 @@ type AccountStatementCardProps = {
|
|||||||
const getAccountStatusBadgeVariant = (
|
const getAccountStatusBadgeVariant = (
|
||||||
status: string,
|
status: string,
|
||||||
): "success" | "outline" => {
|
): "success" | "outline" => {
|
||||||
const normalizedStatus = status.toLowerCase();
|
return status.toLowerCase() === "ativa" ? "success" : "outline";
|
||||||
if (normalizedStatus === "ativa") {
|
|
||||||
return "success";
|
|
||||||
}
|
|
||||||
return "outline";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AccountStatementCard({
|
export function AccountStatementCard({
|
||||||
@@ -57,149 +47,133 @@ export function AccountStatementCard({
|
|||||||
actions,
|
actions,
|
||||||
}: AccountStatementCardProps) {
|
}: AccountStatementCardProps) {
|
||||||
const logoPath = resolveLogoSrc(logo);
|
const logoPath = resolveLogoSrc(logo);
|
||||||
|
const resultado = totalIncomes - totalExpenses;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border">
|
<Card className="gap-0 py-0">
|
||||||
<CardHeader className="flex flex-col gap-3">
|
<CardContent className="px-4 py-4 sm:px-5 sm:py-5">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex flex-col gap-4">
|
||||||
{logoPath ? (
|
{/* Linha 1 — identidade */}
|
||||||
<div className="flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-full border border-border/60 bg-background">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<Image
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
src={logoPath}
|
{logoPath ? (
|
||||||
alt={`Logo da conta ${accountName}`}
|
<div className="flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-full">
|
||||||
width={48}
|
<Image
|
||||||
height={48}
|
src={logoPath}
|
||||||
className="h-full w-full object-contain"
|
alt={`Logo ${accountName}`}
|
||||||
/>
|
width={42}
|
||||||
</div>
|
height={42}
|
||||||
) : null}
|
className="h-full w-full object-contain"
|
||||||
|
/>
|
||||||
<div className="flex w-full items-start justify-between gap-3">
|
</div>
|
||||||
<div className="space-y-1">
|
) : null}
|
||||||
<CardTitle className="text-xl font-semibold text-foreground">
|
<div className="min-w-0">
|
||||||
{accountName}
|
<h2 className="truncate text-sm font-semibold text-foreground">
|
||||||
</CardTitle>
|
{accountName}
|
||||||
<p className="text-sm text-muted-foreground">
|
</h2>
|
||||||
Extrato de {periodLabel}
|
<p className="text-xs text-muted-foreground">
|
||||||
</p>
|
Extrato de {periodLabel}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{actions ? <div className="shrink-0">{actions}</div> : null}
|
{actions ? <div className="shrink-0">{actions}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardContent className="space-y-4 border-t border-border/60 border-dashed pt-4">
|
{/* Linha 2 — saldo final (hero) */}
|
||||||
{/* Composição do Saldo */}
|
<div className="space-y-4">
|
||||||
<div className="space-y-3">
|
<p className="text-sm font-medium text-muted-foreground ">
|
||||||
<DetailItem
|
Saldo ao final do período
|
||||||
label="Saldo no início do período"
|
</p>
|
||||||
value={<MoneyValues amount={openingBalance} className="text-2xl" />}
|
<MoneyValues
|
||||||
tooltip="Saldo inicial cadastrado na conta somado aos lançamentos pagos anteriores a este mês."
|
amount={currentBalance}
|
||||||
/>
|
className="text-3xl leading-none font-semibold tracking-tight sm:text-[2rem]"
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
|
||||||
<DetailItem
|
|
||||||
label="Entradas"
|
|
||||||
value={
|
|
||||||
<span className="font-medium text-success">
|
|
||||||
{formatCurrency(totalIncomes)}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
tooltip="Total de receitas deste mês classificadas como pagas para esta conta."
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Saídas"
|
|
||||||
value={
|
|
||||||
<span className="font-medium text-destructive">
|
|
||||||
{formatCurrency(totalExpenses)}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
tooltip="Total de despesas pagas neste mês (considerando divisão entre pagadores)."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DetailItem
|
|
||||||
label="Resultado do período"
|
|
||||||
value={
|
|
||||||
<MoneyValues
|
|
||||||
amount={totalIncomes - totalExpenses}
|
|
||||||
className={cn(
|
|
||||||
"font-semibold text-xl",
|
|
||||||
totalIncomes - totalExpenses >= 0
|
|
||||||
? "text-success"
|
|
||||||
: "text-destructive",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
tooltip="Diferença entre entradas e saídas do mês; positivo indica saldo crescente."
|
|
||||||
/>
|
/>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge
|
||||||
|
variant={getAccountStatusBadgeVariant(status)}
|
||||||
|
className="text-[11px]"
|
||||||
|
>
|
||||||
|
{status}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{accountType}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Saldo Atual - Destaque Principal */}
|
{/* Linha 3 — breakdown financeiro */}
|
||||||
<DetailItem
|
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
||||||
label="Saldo ao final do período"
|
<MetaItem
|
||||||
value={<MoneyValues amount={currentBalance} className="text-2xl" />}
|
label="Saldo inicial"
|
||||||
tooltip="Saldo inicial do período + entradas - saídas realizadas neste mês."
|
tooltip="Saldo inicial cadastrado na conta somado aos lançamentos pagos anteriores a este mês."
|
||||||
/>
|
>
|
||||||
</div>
|
<span className="text-sm font-semibold text-foreground">
|
||||||
|
{formatCurrency(openingBalance)}
|
||||||
|
</span>
|
||||||
|
</MetaItem>
|
||||||
|
|
||||||
{/* Informações da FinancialAccount */}
|
<MetaItem
|
||||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 pt-2 border-t border-border/60 border-dashed">
|
label="Entradas"
|
||||||
<DetailItem
|
tooltip="Total de receitas deste mês classificadas como pagas para esta conta."
|
||||||
label="Tipo da conta"
|
>
|
||||||
value={accountType}
|
<span className="text-sm font-semibold text-success">
|
||||||
tooltip="Classificação definida na criação da conta (corrente, poupança, etc.)."
|
{formatCurrency(totalIncomes)}
|
||||||
/>
|
</span>
|
||||||
<DetailItem
|
</MetaItem>
|
||||||
label="Status da conta"
|
|
||||||
value={
|
<MetaItem
|
||||||
<div className="flex items-center">
|
label="Saídas"
|
||||||
<Badge
|
tooltip="Total de despesas pagas neste mês (considerando divisão entre pagadores)."
|
||||||
variant={getAccountStatusBadgeVariant(status)}
|
>
|
||||||
className="text-xs"
|
<span className="text-sm font-semibold text-destructive">
|
||||||
>
|
{formatCurrency(totalExpenses)}
|
||||||
{status}
|
</span>
|
||||||
</Badge>
|
</MetaItem>
|
||||||
</div>
|
|
||||||
}
|
<MetaItem
|
||||||
tooltip="Indica se a conta está ativa para lançamentos ou foi desativada."
|
label="Resultado"
|
||||||
/>
|
tooltip="Diferença entre entradas e saídas do mês; positivo indica saldo crescente."
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm font-semibold",
|
||||||
|
resultado >= 0 ? "text-success" : "text-destructive",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{resultado >= 0 ? "+" : ""}
|
||||||
|
{formatCurrency(resultado)}
|
||||||
|
</span>
|
||||||
|
</MetaItem>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DetailItem({
|
function MetaItem({
|
||||||
label,
|
label,
|
||||||
value,
|
|
||||||
className,
|
|
||||||
tooltip,
|
tooltip,
|
||||||
|
children,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
value: DetailValue;
|
tooltip: string;
|
||||||
className?: string;
|
children: ReactNode;
|
||||||
tooltip?: string;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cn("space-y-1", className)}>
|
<div className="rounded-md border border-border/60 px-3 py-2">
|
||||||
<span className="flex items-center gap-1 text-xs font-medium uppercase text-muted-foreground/80">
|
<span className="flex items-center gap-1 text-sm font-medium text-muted-foreground ">
|
||||||
{label}
|
{label}
|
||||||
{tooltip ? (
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<RiInformationLine className="size-3 shrink-0 cursor-help text-muted-foreground/50" />
|
||||||
<RiInformationLine className="size-3.5 cursor-help text-muted-foreground/60" />
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent side="top" align="start" className="max-w-xs text-xs">
|
||||||
<TooltipContent
|
{tooltip}
|
||||||
side="top"
|
</TooltipContent>
|
||||||
align="start"
|
</Tooltip>
|
||||||
className="max-w-xs text-xs"
|
|
||||||
>
|
|
||||||
{tooltip}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</span>
|
</span>
|
||||||
<div className="text-base text-foreground">{value}</div>
|
<div className="mt-1">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { RiEditLine } from "@remixicon/react";
|
import { RiEditLine } from "@remixicon/react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
import { useEffect, useState, useTransition } from "react";
|
import { useEffect, useState, useTransition } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
@@ -13,12 +14,7 @@ import MoneyValues from "@/shared/components/money-values";
|
|||||||
import StatusDot from "@/shared/components/status-dot";
|
import StatusDot from "@/shared/components/status-dot";
|
||||||
import { Badge } from "@/shared/components/ui/badge";
|
import { Badge } from "@/shared/components/ui/badge";
|
||||||
import { Button } from "@/shared/components/ui/button";
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import {
|
import { Card, CardContent } from "@/shared/components/ui/card";
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/shared/components/ui/card";
|
|
||||||
import { resolveCardBrandAsset } from "@/shared/lib/cards/brand-assets";
|
import { resolveCardBrandAsset } from "@/shared/lib/cards/brand-assets";
|
||||||
import {
|
import {
|
||||||
INVOICE_PAYMENT_STATUS,
|
INVOICE_PAYMENT_STATUS,
|
||||||
@@ -29,6 +25,7 @@ import {
|
|||||||
} from "@/shared/lib/invoices";
|
} from "@/shared/lib/invoices";
|
||||||
import { resolveLogoSrc } from "@/shared/lib/logo";
|
import { resolveLogoSrc } from "@/shared/lib/logo";
|
||||||
import { formatCurrency } from "@/shared/utils/currency";
|
import { formatCurrency } from "@/shared/utils/currency";
|
||||||
|
import { formatDateOnly } from "@/shared/utils/date";
|
||||||
import { cn } from "@/shared/utils/ui";
|
import { cn } from "@/shared/utils/ui";
|
||||||
import { EditPaymentDateDialog } from "./edit-payment-date-dialog";
|
import { EditPaymentDateDialog } from "./edit-payment-date-dialog";
|
||||||
|
|
||||||
@@ -66,13 +63,17 @@ const formatDay = (value: string) => value.padStart(2, "0");
|
|||||||
|
|
||||||
const getCardStatusDotColor = (status: string | null) => {
|
const getCardStatusDotColor = (status: string | null) => {
|
||||||
if (!status) return "bg-gray-400";
|
if (!status) return "bg-gray-400";
|
||||||
const normalizedStatus = status.toLowerCase();
|
const s = status.toLowerCase();
|
||||||
if (normalizedStatus === "ativo" || normalizedStatus === "active") {
|
return s === "ativo" || s === "active" ? "bg-success" : "bg-gray-400";
|
||||||
return "bg-success";
|
|
||||||
}
|
|
||||||
return "bg-gray-400";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatPaymentDate = (value: Date | null) =>
|
||||||
|
formatDateOnly(value, {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "short",
|
||||||
|
year: "numeric",
|
||||||
|
}) ?? "data não informada";
|
||||||
|
|
||||||
export function InvoiceSummaryCard({
|
export function InvoiceSummaryCard({
|
||||||
cardId,
|
cardId,
|
||||||
period,
|
period,
|
||||||
@@ -95,20 +96,21 @@ export function InvoiceSummaryCard({
|
|||||||
initialPaymentDate ?? new Date(),
|
initialPaymentDate ?? new Date(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Atualizar estado quando initialPaymentDate mudar
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPaymentDate(initialPaymentDate ?? new Date());
|
setPaymentDate(initialPaymentDate ?? new Date());
|
||||||
}, [initialPaymentDate]);
|
}, [initialPaymentDate]);
|
||||||
|
|
||||||
const logoPath = resolveLogoSrc(logo);
|
const logoPath = resolveLogoSrc(logo);
|
||||||
const brandAsset = resolveCardBrandAsset(cardBrand);
|
const brandAsset = resolveCardBrandAsset(cardBrand);
|
||||||
const limitLabel =
|
const isPaid = invoiceStatus === INVOICE_PAYMENT_STATUS.PAID;
|
||||||
typeof limitAmount === "number" ? formatCurrency(limitAmount) : "—";
|
const paymentDateLabel = isPaid ? formatPaymentDate(paymentDate) : null;
|
||||||
|
const actionDescription = isPaid
|
||||||
|
? `Pagamento registrado em ${paymentDateLabel}.`
|
||||||
|
: INVOICE_STATUS_DESCRIPTION[invoiceStatus];
|
||||||
|
|
||||||
const targetStatus =
|
const targetStatus = isPaid
|
||||||
invoiceStatus === INVOICE_PAYMENT_STATUS.PAID
|
? INVOICE_PAYMENT_STATUS.PENDING
|
||||||
? INVOICE_PAYMENT_STATUS.PENDING
|
: INVOICE_PAYMENT_STATUS.PAID;
|
||||||
: INVOICE_PAYMENT_STATUS.PAID;
|
|
||||||
|
|
||||||
const handleAction = () => {
|
const handleAction = () => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
@@ -152,150 +154,145 @@ export function InvoiceSummaryCard({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border">
|
<Card className="gap-0 py-0">
|
||||||
<CardHeader className="flex flex-col gap-3">
|
<CardContent className="px-4 py-4 sm:px-5 sm:py-5">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex flex-col gap-4">
|
||||||
{logoPath ? (
|
{/* Linha 1 — identidade */}
|
||||||
<div className="flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-full border border-border/60 bg-background">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<Image
|
<div className="flex min-w-0 items-center gap-3">
|
||||||
src={logoPath}
|
{logoPath ? (
|
||||||
alt={`Logo do cartão ${cardName}`}
|
<div className="flex size-12 shrink-0 items-center justify-center overflow-hidden rounded-full">
|
||||||
width={48}
|
<Image
|
||||||
height={48}
|
src={logoPath}
|
||||||
className="h-full w-full object-contain"
|
alt={`Logo ${cardName}`}
|
||||||
/>
|
width={42}
|
||||||
</div>
|
height={42}
|
||||||
) : cardBrand ? (
|
className="h-full w-full object-contain"
|
||||||
<span className="flex size-12 shrink-0 items-center justify-center rounded-full border border-border/60 bg-background text-sm font-semibold text-muted-foreground">
|
/>
|
||||||
{cardBrand}
|
</div>
|
||||||
</span>
|
) : cardBrand ? (
|
||||||
) : null}
|
<span className="flex size-10 shrink-0 items-center justify-center rounded-full border bg-background text-xs font-semibold text-muted-foreground">
|
||||||
|
{cardBrand.slice(0, 2).toUpperCase()}
|
||||||
<div className="flex w-full items-start justify-between gap-3">
|
</span>
|
||||||
<div className="space-y-1">
|
) : null}
|
||||||
<CardTitle className="text-xl font-semibold text-foreground">
|
<div className="min-w-0">
|
||||||
{cardName}
|
<h2 className="truncate text-sm font-semibold text-foreground">
|
||||||
</CardTitle>
|
{cardName}
|
||||||
<p className="text-sm text-muted-foreground">
|
</h2>
|
||||||
Invoice de {periodLabel}
|
<p className="text-xs text-muted-foreground">
|
||||||
</p>
|
Fatura de {periodLabel}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{actions ? <div className="shrink-0">{actions}</div> : null}
|
{actions ? <div className="shrink-0">{actions}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardContent className="space-y-4 border-t border-border/60 border-dashed pt-4">
|
{/* Linha 2 — valor da fatura (hero) */}
|
||||||
{/* Destaque Principal */}
|
<div className="space-y-4">
|
||||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
<p className="text-sm font-medium text-muted-foreground">
|
||||||
<DetailItem
|
Valor da fatura
|
||||||
label="Valor total"
|
</p>
|
||||||
value={
|
<MoneyValues
|
||||||
<MoneyValues
|
amount={totalAmount}
|
||||||
amount={totalAmount}
|
className={cn(
|
||||||
className="text-2xl text-foreground"
|
"text-3xl leading-none font-semibold tracking-tight sm:text-[2rem]",
|
||||||
/>
|
isPaid ? "text-success" : "text-foreground",
|
||||||
}
|
)}
|
||||||
/>
|
/>
|
||||||
<DetailItem
|
<div className="flex items-center gap-2">
|
||||||
label="Status da fatura"
|
|
||||||
value={
|
|
||||||
<Badge
|
<Badge
|
||||||
variant={INVOICE_STATUS_BADGE_VARIANT[invoiceStatus]}
|
variant={INVOICE_STATUS_BADGE_VARIANT[invoiceStatus]}
|
||||||
className="text-xs"
|
className="text-[11px]"
|
||||||
>
|
>
|
||||||
{INVOICE_STATUS_LABEL[invoiceStatus]}
|
{INVOICE_STATUS_LABEL[invoiceStatus]}
|
||||||
</Badge>
|
</Badge>
|
||||||
}
|
{cardStatus ? (
|
||||||
/>
|
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Informações Gerais */}
|
|
||||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
|
||||||
<DetailItem
|
|
||||||
label="Fechamento"
|
|
||||||
value={
|
|
||||||
<span className="font-medium">Dia {formatDay(closingDay)}</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Vencimento"
|
|
||||||
value={<span className="font-medium">Dia {formatDay(dueDay)}</span>}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Bandeira"
|
|
||||||
value={
|
|
||||||
brandAsset ? (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Image
|
|
||||||
src={brandAsset}
|
|
||||||
alt={`Bandeira ${cardBrand}`}
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
className="h-5 w-auto rounded"
|
|
||||||
/>
|
|
||||||
<span className="truncate">{cardBrand}</span>
|
|
||||||
</div>
|
|
||||||
) : cardBrand ? (
|
|
||||||
<span className="truncate">{cardBrand}</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-muted-foreground">—</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<DetailItem
|
|
||||||
label="Status cartão"
|
|
||||||
value={
|
|
||||||
cardStatus ? (
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<StatusDot color={getCardStatusDotColor(cardStatus)} />
|
<StatusDot color={getCardStatusDotColor(cardStatus)} />
|
||||||
<span className="truncate">{cardStatus}</span>
|
<span>{cardStatus}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : null}
|
||||||
<span className="text-muted-foreground">—</span>
|
</div>
|
||||||
)
|
</div>
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DetailItem
|
{/* Linha 3 — metadados do cartão */}
|
||||||
label="Limite do cartão"
|
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
|
||||||
value={limitLabel}
|
<MetaItem label="Vencimento">
|
||||||
className="sm:w-1/2"
|
<span className="text-sm font-semibold text-foreground">
|
||||||
/>
|
Dia {formatDay(dueDay)}
|
||||||
|
</span>
|
||||||
|
</MetaItem>
|
||||||
|
|
||||||
{/* Ações */}
|
<MetaItem label="Fechamento">
|
||||||
<div className="flex flex-col gap-2 pt-2 sm:flex-row sm:items-center sm:justify-between">
|
<span className="text-sm font-semibold text-foreground">
|
||||||
<p className="text-xs text-muted-foreground">
|
Dia {formatDay(closingDay)}
|
||||||
{INVOICE_STATUS_DESCRIPTION[invoiceStatus]}
|
</span>
|
||||||
</p>
|
</MetaItem>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Button
|
{typeof limitAmount === "number" ? (
|
||||||
type="button"
|
<MetaItem label="Limite">
|
||||||
variant={actionVariantByStatus[invoiceStatus]}
|
<span className="text-sm font-semibold text-foreground">
|
||||||
disabled={isPending}
|
{formatCurrency(limitAmount)}
|
||||||
onClick={handleAction}
|
</span>
|
||||||
className="w-full shrink-0 sm:w-auto"
|
</MetaItem>
|
||||||
>
|
) : null}
|
||||||
{isPending ? "Salvando..." : actionLabelByStatus[invoiceStatus]}
|
|
||||||
</Button>
|
{cardBrand ? (
|
||||||
{invoiceStatus === INVOICE_PAYMENT_STATUS.PAID && (
|
<MetaItem label="Bandeira">
|
||||||
<EditPaymentDateDialog
|
<div className="flex items-center gap-1.5">
|
||||||
trigger={
|
{brandAsset ? (
|
||||||
<Button
|
<Image
|
||||||
type="button"
|
src={brandAsset}
|
||||||
variant="ghost"
|
alt={cardBrand}
|
||||||
size="icon"
|
width={24}
|
||||||
className="shrink-0"
|
height={24}
|
||||||
aria-label="Editar data de pagamento"
|
className="h-4 w-auto shrink-0"
|
||||||
>
|
/>
|
||||||
<RiEditLine className="size-4" />
|
) : null}
|
||||||
</Button>
|
<span className="text-sm font-semibold text-foreground truncate">
|
||||||
}
|
{cardBrand}
|
||||||
currentDate={paymentDate}
|
</span>
|
||||||
onDateChange={handleDateChange}
|
</div>
|
||||||
/>
|
</MetaItem>
|
||||||
)}
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Linha 4 — ação */}
|
||||||
|
<div className="flex flex-col gap-3 rounded-md border border-dashed bg-muted/30 px-3 py-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{actionDescription}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex shrink-0 items-center gap-1.5">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
variant={actionVariantByStatus[invoiceStatus]}
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={handleAction}
|
||||||
|
className="min-w-32"
|
||||||
|
>
|
||||||
|
{isPending ? "Salvando..." : actionLabelByStatus[invoiceStatus]}
|
||||||
|
</Button>
|
||||||
|
{isPaid ? (
|
||||||
|
<EditPaymentDateDialog
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-sm"
|
||||||
|
className="text-muted-foreground hover:text-foreground"
|
||||||
|
aria-label="Editar data de pagamento"
|
||||||
|
>
|
||||||
|
<RiEditLine className="size-4" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
currentDate={paymentDate}
|
||||||
|
onDateChange={handleDateChange}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -303,21 +300,13 @@ export function InvoiceSummaryCard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type DetailItemProps = {
|
function MetaItem({ label, children }: { label: string; children: ReactNode }) {
|
||||||
label?: string;
|
|
||||||
value: React.ReactNode;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function DetailItem({ label, value, className }: DetailItemProps) {
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("space-y-1", className)}>
|
<div className="rounded-md border border-border/60 px-3 py-2">
|
||||||
{label && (
|
<span className="block text-sm font-medium text-muted-foreground">
|
||||||
<span className="block text-xs font-medium uppercase text-muted-foreground/80">
|
{label}
|
||||||
{label}
|
</span>
|
||||||
</span>
|
<div className="mt-1">{children}</div>
|
||||||
)}
|
|
||||||
<div className="text-base text-foreground">{value}</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user