Refactor: Split CartoesCredito.tsx into components
Refactors the CartoesCreditoPage into smaller, reusable components to improve code organization and maintainability.
This commit is contained in:
parent
8253e603a6
commit
df6ee93e01
107
src/components/credito/CartaoActions.tsx
Normal file
107
src/components/credito/CartaoActions.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
23
src/components/credito/CartaoDetalhes.tsx
Normal file
23
src/components/credito/CartaoDetalhes.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
19
src/components/credito/CartaoListView.tsx
Normal file
19
src/components/credito/CartaoListView.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
29
src/components/credito/CartaoSummaryCard.tsx
Normal file
29
src/components/credito/CartaoSummaryCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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 };
|
||||
})
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user