Refactor: Split CartoesCredito.tsx into components

Refactors the CartoesCreditoPage into smaller, reusable components to improve code organization and maintainability.
This commit is contained in:
gpt-engineer-app[bot] 2025-05-21 00:54:15 +00:00
parent 8253e603a6
commit df6ee93e01
7 changed files with 213 additions and 155 deletions

View File

@ -0,0 +1,107 @@
import { Button } from "@/components/ui/button";
import { PlusCircle, ArrowLeft } from "lucide-react";
import { useState } from "react";
import { CartaoCredito } from "@/types/cartaoTypes";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { CartaoCreditoForm } from "@/components/credito/CartaoCreditoForm";
import { DespesaCartaoForm } from "@/components/credito/DespesaCartaoForm";
interface CartaoActionsProps {
isDetalhes: boolean;
cartaoSelecionado: CartaoCredito | null;
onVoltarParaLista: () => void;
onCartaoSuccess: () => void;
onDespesaSuccess: () => void;
}
export function CartaoActions({
isDetalhes,
cartaoSelecionado,
onVoltarParaLista,
onCartaoSuccess,
onDespesaSuccess
}: CartaoActionsProps) {
const [isDialogNovoCartaoOpen, setIsDialogNovoCartaoOpen] = useState(false);
const [isDialogNovaDespesaOpen, setIsDialogNovaDespesaOpen] = useState(false);
return (
<>
<div className="flex justify-between items-center">
{isDetalhes ? (
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={onVoltarParaLista}
>
<ArrowLeft className="h-4 w-4" />
</Button>
<h1 className="text-2xl font-bold tracking-tight">
{cartaoSelecionado?.nome}
</h1>
</div>
) : (
<h1 className="text-2xl font-bold tracking-tight">Cartões de Crédito</h1>
)}
<Button
className="flex items-center gap-2"
onClick={() => isDetalhes
? setIsDialogNovaDespesaOpen(true)
: setIsDialogNovoCartaoOpen(true)}
>
<PlusCircle className="h-4 w-4" />
{isDetalhes ? 'Nova Despesa' : 'Novo Cartão'}
</Button>
</div>
{/* Dialog para adicionar novo cartão */}
<Dialog open={isDialogNovoCartaoOpen} onOpenChange={setIsDialogNovoCartaoOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Novo Cartão de Crédito</DialogTitle>
<DialogDescription>
Preencha as informações para cadastrar um novo cartão de crédito.
</DialogDescription>
</DialogHeader>
<CartaoCreditoForm
onSuccess={() => {
setIsDialogNovoCartaoOpen(false);
onCartaoSuccess();
}}
onCancel={() => setIsDialogNovoCartaoOpen(false)}
/>
</DialogContent>
</Dialog>
{/* Dialog para adicionar nova despesa */}
<Dialog open={isDialogNovaDespesaOpen} onOpenChange={setIsDialogNovaDespesaOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Nova Despesa do Cartão</DialogTitle>
<DialogDescription>
Preencha as informações para registrar uma nova despesa.
</DialogDescription>
</DialogHeader>
{cartaoSelecionado && (
<DespesaCartaoForm
cartao={cartaoSelecionado}
onSuccess={() => {
setIsDialogNovaDespesaOpen(false);
onDespesaSuccess();
}}
onCancel={() => setIsDialogNovaDespesaOpen(false)}
/>
)}
</DialogContent>
</Dialog>
</>
);
}

View File

@ -0,0 +1,23 @@
import { CartaoCredito, DespesaCartao } from "@/types/cartaoTypes";
import { CartaoSummaryCard } from "./CartaoSummaryCard";
import { DespesasList } from "./DespesasList";
interface CartaoDetalhesProps {
cartao: CartaoCredito;
despesas: DespesaCartao[];
isLoading: boolean;
}
export function CartaoDetalhes({ cartao, despesas, isLoading }: CartaoDetalhesProps) {
return (
<div className="space-y-6">
<CartaoSummaryCard totalDespesas={cartao.total_despesas} />
<div className="space-y-4">
<h2 className="text-xl font-semibold">Despesas do Cartão</h2>
<DespesasList despesas={despesas} isLoading={isLoading} />
</div>
</div>
);
}

View File

@ -0,0 +1,19 @@
import { CartaoCredito } from "@/types/cartaoTypes";
import { CartaoCreditoList } from "./CartaoCreditoList";
interface CartaoListViewProps {
cartoes: CartaoCredito[];
isLoading: boolean;
onCartaoClick: (cartao: CartaoCredito) => void;
}
export function CartaoListView({ cartoes, isLoading, onCartaoClick }: CartaoListViewProps) {
return (
<CartaoCreditoList
cartoes={cartoes}
isLoading={isLoading}
onCartaoClick={onCartaoClick}
/>
);
}

View File

@ -0,0 +1,29 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
interface CartaoSummaryCardProps {
totalDespesas: number;
}
export function CartaoSummaryCard({ totalDespesas }: CartaoSummaryCardProps) {
const formatCurrency = (value: number = 0) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(value);
};
return (
<Card>
<CardHeader className="pb-2">
<CardTitle>Resumo</CardTitle>
<CardDescription>Total de despesas do cartão</CardDescription>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">
{formatCurrency(totalDespesas)}
</p>
</CardContent>
</Card>
);
}

View File

@ -1,37 +1,18 @@
import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout';
import { CartaoCreditoForm } from '@/components/credito/CartaoCreditoForm';
import { DespesaCartaoForm } from '@/components/credito/DespesaCartaoForm';
import { CartaoCreditoList } from '@/components/credito/CartaoCreditoList';
import { DespesasList } from '@/components/credito/DespesasList';
import { CartaoCredito, DespesaCartao } from '@/types/cartaoTypes';
import { getCartoes, getDespesasCartao } from '@/services/cartaoCreditoService';
import { Button } from '@/components/ui/button';
import { useToast } from "@/components/ui/use-toast";
import { PlusCircle, ArrowLeft } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { CartaoActions } from '@/components/credito/CartaoActions';
import { CartaoDetalhes } from '@/components/credito/CartaoDetalhes';
import { CartaoListView } from '@/components/credito/CartaoListView';
const CartoesCreditoPage = () => {
const [cartoes, setCartoes] = useState<CartaoCredito[]>([]);
const [cartaoSelecionado, setCartaoSelecionado] = useState<CartaoCredito | null>(null);
const [despesas, setDespesas] = useState<DespesaCartao[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isDialogNovoCartaoOpen, setIsDialogNovoCartaoOpen] = useState(false);
const [isDialogNovaDespesaOpen, setIsDialogNovaDespesaOpen] = useState(false);
const [detalheCartaoAberto, setDetalheCartaoAberto] = useState(false);
const { toast } = useToast();
@ -74,7 +55,6 @@ const CartoesCreditoPage = () => {
};
const handleCartaoSuccess = () => {
setIsDialogNovoCartaoOpen(false);
loadCartoes();
toast({
title: "Cartão adicionado",
@ -83,7 +63,6 @@ const CartoesCreditoPage = () => {
};
const handleDespesaSuccess = () => {
setIsDialogNovaDespesaOpen(false);
// Recarregar detalhes do cartão após adicionar despesa
if (cartaoSelecionado) {
loadDespesasCartao(cartaoSelecionado.id);
@ -108,114 +87,30 @@ const CartoesCreditoPage = () => {
setDespesas([]);
};
const formatCurrency = (value: number = 0) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(value);
};
return (
<Layout>
<div className="space-y-6">
<CartaoActions
isDetalhes={detalheCartaoAberto}
cartaoSelecionado={cartaoSelecionado}
onVoltarParaLista={handleVoltarParaLista}
onCartaoSuccess={handleCartaoSuccess}
onDespesaSuccess={handleDespesaSuccess}
/>
{!detalheCartaoAberto ? (
// Tela de listagem de cartões
<>
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Cartões de Crédito</h1>
<Button
className="flex items-center gap-2"
onClick={() => setIsDialogNovoCartaoOpen(true)}
>
<PlusCircle className="h-4 w-4" />
Novo Cartão
</Button>
</div>
<CartaoCreditoList
cartoes={cartoes}
isLoading={isLoading}
onCartaoClick={handleCartaoClick}
/>
</>
) : (
// Tela de detalhes de um cartão específico
<>
<div className="flex justify-between items-center">
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={handleVoltarParaLista}
>
<ArrowLeft className="h-4 w-4" />
</Button>
<h1 className="text-2xl font-bold tracking-tight">
{cartaoSelecionado?.nome}
</h1>
</div>
<Button
className="flex items-center gap-2"
onClick={() => setIsDialogNovaDespesaOpen(true)}
>
<PlusCircle className="h-4 w-4" />
Nova Despesa
</Button>
</div>
<Card>
<CardHeader className="pb-2">
<CardTitle>Resumo</CardTitle>
<CardDescription>Total de despesas do cartão</CardDescription>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">
{formatCurrency(cartaoSelecionado?.total_despesas)}
</p>
</CardContent>
</Card>
<div className="space-y-4">
<h2 className="text-xl font-semibold">Despesas do Cartão</h2>
<DespesasList despesas={despesas} isLoading={isLoading} />
</div>
</>
<CartaoListView
cartoes={cartoes}
isLoading={isLoading}
onCartaoClick={handleCartaoClick}
/>
) : cartaoSelecionado && (
<CartaoDetalhes
cartao={cartaoSelecionado}
despesas={despesas}
isLoading={isLoading}
/>
)}
{/* Dialog para adicionar novo cartão */}
<Dialog open={isDialogNovoCartaoOpen} onOpenChange={setIsDialogNovoCartaoOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Novo Cartão de Crédito</DialogTitle>
<DialogDescription>
Preencha as informações para cadastrar um novo cartão de crédito.
</DialogDescription>
</DialogHeader>
<CartaoCreditoForm
onSuccess={handleCartaoSuccess}
onCancel={() => setIsDialogNovoCartaoOpen(false)}
/>
</DialogContent>
</Dialog>
{/* Dialog para adicionar nova despesa */}
<Dialog open={isDialogNovaDespesaOpen} onOpenChange={setIsDialogNovaDespesaOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Nova Despesa do Cartão</DialogTitle>
<DialogDescription>
Preencha as informações para registrar uma nova despesa.
</DialogDescription>
</DialogHeader>
{cartaoSelecionado && (
<DespesaCartaoForm
cartao={cartaoSelecionado}
onSuccess={handleDespesaSuccess}
onCancel={() => setIsDialogNovaDespesaOpen(false)}
/>
)}
</DialogContent>
</Dialog>
</div>
</Layout>
);

View File

@ -1,4 +1,3 @@
import { supabase } from "@/integrations/supabase/client";
import { CartaoCredito } from "@/types/cartaoTypes";
import { gerarCartaoCodigo } from "./cartaoCodigoUtils";
@ -33,28 +32,19 @@ export async function getCartoes(): Promise<CartaoCredito[]> {
// Garantir que todos os dados têm os campos necessários da interface CartaoCredito
const cartoesCompletos = data.map(cartao => {
const cartaoCompleto = { ...cartao } as CartaoCredito;
// Se não tiver cartao_codigo, gerar um código temporário baseado no id
if (!cartaoCompleto.cartao_codigo) {
cartaoCompleto.cartao_codigo = `cartao_${cartaoCompleto.id.substring(0, 8)}`;
}
// Se não tiver bandeira ou banco, adicionar valores padrão
if (!cartaoCompleto.bandeira) {
cartaoCompleto.bandeira = 'Não especificada';
}
if (!cartaoCompleto.banco) {
cartaoCompleto.banco = 'Não especificado';
}
const cartaoCompleto = {
...cartao,
bandeira: cartao.bandeira || 'Não especificada',
banco: cartao.banco || 'Não especificado',
cartao_codigo: cartao.cartao_codigo || `cartao_${cartao.id.substring(0, 8)}`
} as CartaoCredito;
return cartaoCompleto;
});
// Buscar o total de despesas para cada cartão
// Buscar o total de despesas para cada cartão - Promise.all já tipa corretamente o retorno
const cartoesComTotal = await Promise.all(
cartoesCompletos.map(async (cartao: CartaoCredito) => {
cartoesCompletos.map(async (cartao) => {
const total = await getTotalDespesasCartao(cartao.cartao_codigo);
return { ...cartao, total_despesas: total };
})

View File

@ -1,4 +1,3 @@
import { supabase } from "@/integrations/supabase/client";
import { DespesaCartao } from "@/types/cartaoTypes";
import { getCartao } from "./cartoesService";
@ -41,13 +40,10 @@ export async function getDespesasCartao(cartaoId: string): Promise<DespesaCartao
}
// Garantir que todas as despesas têm cartao_codigo
const despesasCompletas = data.map(despesa => {
const despesaCompleta = { ...despesa } as DespesaCartao;
if (!despesaCompleta.cartao_codigo) {
despesaCompleta.cartao_codigo = cartao.cartao_codigo;
}
return despesaCompleta;
});
const despesasCompletas = data.map(despesa => ({
...despesa,
cartao_codigo: despesa.cartao_codigo || cartao.cartao_codigo
})) as DespesaCartao[];
return despesasCompletas;
} catch (error) {
@ -132,8 +128,7 @@ export async function getTotalDespesasCartao(cartaoCodigo: string): Promise<numb
const { data, error } = await supabase
.from('despesas_cartao')
.select('valor')
.eq('cartao_codigo', cartaoCodigo)
.eq('login', normalizedEmail);
.eq('cartao_codigo', cartaoCodigo);
if (error) {
console.error('Erro ao calcular total de despesas:', error);