From 1f9098879e88352b7632d3decc6acdb03653b76f Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Sat, 11 Apr 2026 17:51:14 +0000 Subject: [PATCH] feat(parcelas): redesenho do card de grupo com dialog de detalhes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Card de grupo de parcelas ganhou um dialog ao clicar em "Ver detalhes", separando parcelas pagas e pendentes, com seleção parcial e logo do estabelecimento. Substituída lógica de expand inline pelo dialog. Co-Authored-By: Claude Sonnet 4.6 --- .../installment-group-card.tsx | 446 ++++++++++++------ 1 file changed, 292 insertions(+), 154 deletions(-) diff --git a/src/features/dashboard/components/installment-analysis/installment-group-card.tsx b/src/features/dashboard/components/installment-analysis/installment-group-card.tsx index 89325c8..90a297e 100644 --- a/src/features/dashboard/components/installment-analysis/installment-group-card.tsx +++ b/src/features/dashboard/components/installment-analysis/installment-group-card.tsx @@ -1,19 +1,35 @@ "use client"; import { - RiArrowDownSLine, - RiArrowRightSLine, + RiBankCard2Line, RiCheckboxCircleFill, + RiEyeLine, + RiTimeLine, } from "@remixicon/react"; import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; +import Image from "next/image"; import { useState } from "react"; import MoneyValues from "@/shared/components/money-values"; import { Badge } from "@/shared/components/ui/badge"; -import { Card, CardContent } from "@/shared/components/ui/card"; +import { Button } from "@/shared/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/shared/components/ui/card"; import { Checkbox } from "@/shared/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/shared/components/ui/dialog"; import { Progress } from "@/shared/components/ui/progress"; -import { cn } from "@/shared/utils/ui"; +import { cn } from "@/shared/utils"; import type { InstallmentGroup } from "./types"; type InstallmentGroupCardProps = { @@ -29,18 +45,22 @@ export function InstallmentGroupCard({ onToggleGroup, onToggleInstallment, }: InstallmentGroupCardProps) { - const [isExpanded, setIsExpanded] = useState(false); + const [isDetailsOpen, setIsDetailsOpen] = useState(false); const unpaidInstallments = group.pendingInstallments.filter( (i) => !i.isSettled, ); - + const paidInstallments = group.pendingInstallments.filter((i) => i.isSettled); const unpaidCount = unpaidInstallments.length; const isFullySelected = selectedInstallments.size === unpaidInstallments.length && unpaidInstallments.length > 0; + const isPartiallySelected = selectedInstallments.size > 0 && !isFullySelected; + + const hasSelection = selectedInstallments.size > 0; + const progress = group.totalInstallments > 0 ? (group.paidInstallments / group.totalInstallments) * 100 @@ -50,186 +70,304 @@ export function InstallmentGroupCard({ .filter((i) => selectedInstallments.has(i.id) && !i.isSettled) .reduce((sum, i) => sum + Number(i.amount), 0); - // Calcular valor total de todas as parcelas (pagas + pendentes) const totalAmount = group.pendingInstallments.reduce( (sum, i) => sum + i.amount, 0, ); - // Calcular valor pendente (apenas não pagas) const pendingAmount = unpaidInstallments.reduce( (sum, i) => sum + i.amount, 0, ); return ( - - - {/* Header do card */} -
- + <> + + {/* Header Section */} + +
+ {/* Checkbox de seleção do grupo */} +
+ +
-
-
-
- {group.cartaoLogo && ( - +
+ {group.cartaoLogo ? ( + {group.cartaoName + ) : ( +
+ +
)} - {group.name} - - | {group.cartaoName} - -
- -
-
- Total: - -
-
- - Pendente: - - +
+ + {group.name} + + + {group.cartaoName ?? "Compra parcelada"} +
- {/* Progress bar */} -
-
+ {/* Badge de status */} + + {progress === 100 ? "Quitado" : `${Math.round(progress)}% pago`} + +
+ + + + {/* Grid de valores */} +
+
+

+ Valor total +

+ +
+
+

+ Pendente +

+ 0 ? "text-amber-600" : "text-success-600", + )} + /> +
+
+ + {/* Barra de progresso */} +
+
+
+ - {group.paidInstallments} de {group.totalInstallments} pagas + {group.paidInstallments} de {group.totalInstallments} parcelas + pagas -
+
+ {unpaidCount > 0 && ( +
+ {unpaidCount} {unpaidCount === 1 ? "pendente" : "pendentes"} - {selectedInstallments.size > 0 && ( - - • Selecionado:{" "} - - - )}
-
- -
- - {/* Botão de expandir */} - +
+
-
- {/* Lista de parcelas expandida */} - {isExpanded && ( -
- {group.pendingInstallments.map((installment) => { - const isSelected = selectedInstallments.has(installment.id); - const isPaid = installment.isSettled; - const dueDate = installment.dueDate - ? format(installment.dueDate, "dd/MM/yyyy", { locale: ptBR }) - : format(installment.purchaseDate, "dd/MM/yyyy", { - locale: ptBR, - }); + {/* Valor selecionado */} + {hasSelection && ( +
+ + {selectedInstallments.size}{" "} + {selectedInstallments.size === 1 + ? "parcela selecionada" + : "parcelas selecionadas"} + + +
+ )} - return ( -
- - !isPaid && onToggleInstallment(installment.id) - } - aria-label={`Selecionar parcela ${installment.currentInstallment} de ${group.totalInstallments}`} - /> + {/* Botão para abrir detalhes */} + + + -
-
-

- Parcela {installment.currentInstallment}/ - {group.totalInstallments} - {isPaid && ( - - Pago - - )} -

-

- Vencimento: {dueDate} -

-
- - -
+ {/* Modal de detalhes */} + + + +
+ {group.cartaoLogo ? ( + {group.cartaoName + ) : ( +
+
- ); - })} + )} + {group.name} +
+ + Detalhes das parcelas do grupo {group.name} + +
+ +
+ {/* Parcelas pagas */} + {paidInstallments.length > 0 && ( +
+

+ Parcelas pagas +

+ {paidInstallments.map((installment) => { + const dueDate = installment.dueDate + ? format(installment.dueDate, "dd MMM yyyy", { + locale: ptBR, + }) + : format(installment.purchaseDate, "dd MMM yyyy", { + locale: ptBR, + }); + + return ( +
+
+ +
+ +
+

+ Parcela {installment.currentInstallment}/ + {group.totalInstallments} +

+

+ Vencimento: {dueDate} +

+
+ + +
+ ); + })} +
+ )} + + {/* Parcelas pendentes */} + {unpaidInstallments.length > 0 && ( +
+

+ Parcelas pendentes +

+ {unpaidInstallments.map((installment) => { + const isSelected = selectedInstallments.has(installment.id); + const dueDate = installment.dueDate + ? format(installment.dueDate, "dd MMM yyyy", { + locale: ptBR, + }) + : format(installment.purchaseDate, "dd MMM yyyy", { + locale: ptBR, + }); + + return ( + + ); + })} +
+ )}
- )} - - + + {/* Footer com resumo da seleção */} + {hasSelection && ( +
+ + {selectedInstallments.size}{" "} + {selectedInstallments.size === 1 + ? "parcela selecionada" + : "parcelas selecionadas"} + + +
+ )} +
+
+ ); }