Fix: Remove duplicate menu and category page effect

Removed the duplicate menu from the Dashboard, Transações, Cartões de Credito, Metas, Calendário, Conectar WhatsApp, and Grupos pages. Also removed the background effect from the Categorias page.
This commit is contained in:
gpt-engineer-app[bot] 2025-06-24 18:04:25 +00:00
parent 6121502853
commit 583136ca75
8 changed files with 568 additions and 774 deletions

View File

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Calendar } from "@/components/ui/calendar"; import { Calendar } from "@/components/ui/calendar";
import { getTransacoes } from '@/services/transacaoService'; import { getTransacoes } from '@/services/transacaoService';
@ -66,85 +65,83 @@ const CalendarioPage = () => {
}; };
return ( return (
<Layout> <div className="space-y-6">
<div className="space-y-6"> <div className="flex justify-between items-center">
<div className="flex justify-between items-center"> <h1 className="text-2xl font-bold tracking-tight">Calendário de Transações</h1>
<h1 className="text-2xl font-bold tracking-tight">Calendário de Transações</h1>
</div>
<div className="grid md:grid-cols-5 gap-6">
<Card className="md:col-span-2">
<CardContent className="pt-6">
<Calendar
mode="single"
selected={selectedDate}
onSelect={setSelectedDate}
locale={ptBR}
className="max-w-full pointer-events-auto"
modifiers={{
hasTransaction: (date) => hasDayTransaction(date),
}}
modifiersStyles={{
hasTransaction: {
fontWeight: "bold",
textDecoration: "underline",
backgroundColor: "rgba(16, 185, 129, 0.1)"
}
}}
/>
<div className="mt-4 text-sm text-muted-foreground text-center">
Dias com transações estão destacados
</div>
</CardContent>
</Card>
<Card className="md:col-span-3">
<CardContent className="p-6">
<h2 className="text-lg font-semibold mb-4">
{selectedDate ? format(selectedDate, "dd 'de' MMMM 'de' yyyy", { locale: ptBR }) : "Selecione uma data"}
</h2>
{isLoading ? (
<div className="space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="p-4 border rounded-md animate-pulse">
<div className="h-4 bg-muted rounded w-1/4 mb-2"></div>
<div className="h-6 bg-muted rounded w-1/2"></div>
</div>
))}
</div>
) : filteredTransactions.length === 0 ? (
<div className="text-center py-10 text-muted-foreground">
Nenhuma transação nesta data
</div>
) : (
<div className="space-y-3">
{filteredTransactions.map((transaction) => (
<div key={transaction.id} className="p-4 border rounded-md hover:bg-muted/50 transition-colors">
<div className="flex justify-between items-center mb-1">
<div className="font-medium">{transaction.estabelecimento}</div>
<Badge variant={transaction.tipo.toLowerCase() === 'receita' || transaction.tipo.toLowerCase() === 'entrada' ? "success" : "destructive"}>
{transaction.tipo.toLowerCase() === 'receita' || transaction.tipo.toLowerCase() === 'entrada' ? 'Receita' : 'Despesa'}
</Badge>
</div>
<div className="text-sm text-muted-foreground mb-2">{transaction.detalhes}</div>
<div className="flex justify-between">
<Badge variant="outline">{transaction.categoria}</Badge>
<span className={transaction.tipo.toLowerCase() === 'receita' || transaction.tipo.toLowerCase() === 'entrada' ? "text-finance-green font-medium" : "text-finance-red font-medium"}>
{transaction.tipo.toLowerCase() === 'receita' || transaction.tipo.toLowerCase() === 'entrada'
? formatCurrency(Math.abs(transaction.valor))
: `-${formatCurrency(Math.abs(transaction.valor))}`}
</span>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div> </div>
</Layout>
<div className="grid md:grid-cols-5 gap-6">
<Card className="md:col-span-2">
<CardContent className="pt-6">
<Calendar
mode="single"
selected={selectedDate}
onSelect={setSelectedDate}
locale={ptBR}
className="max-w-full pointer-events-auto"
modifiers={{
hasTransaction: (date) => hasDayTransaction(date),
}}
modifiersStyles={{
hasTransaction: {
fontWeight: "bold",
textDecoration: "underline",
backgroundColor: "rgba(16, 185, 129, 0.1)"
}
}}
/>
<div className="mt-4 text-sm text-muted-foreground text-center">
Dias com transações estão destacados
</div>
</CardContent>
</Card>
<Card className="md:col-span-3">
<CardContent className="p-6">
<h2 className="text-lg font-semibold mb-4">
{selectedDate ? format(selectedDate, "dd 'de' MMMM 'de' yyyy", { locale: ptBR }) : "Selecione uma data"}
</h2>
{isLoading ? (
<div className="space-y-3">
{[1, 2, 3].map((i) => (
<div key={i} className="p-4 border rounded-md animate-pulse">
<div className="h-4 bg-muted rounded w-1/4 mb-2"></div>
<div className="h-6 bg-muted rounded w-1/2"></div>
</div>
))}
</div>
) : filteredTransactions.length === 0 ? (
<div className="text-center py-10 text-muted-foreground">
Nenhuma transação nesta data
</div>
) : (
<div className="space-y-3">
{filteredTransactions.map((transaction) => (
<div key={transaction.id} className="p-4 border rounded-md hover:bg-muted/50 transition-colors">
<div className="flex justify-between items-center mb-1">
<div className="font-medium">{transaction.estabelecimento}</div>
<Badge variant={transaction.tipo.toLowerCase() === 'receita' || transaction.tipo.toLowerCase() === 'entrada' ? "success" : "destructive"}>
{transaction.tipo.toLowerCase() === 'receita' || transaction.tipo.toLowerCase() === 'entrada' ? 'Receita' : 'Despesa'}
</Badge>
</div>
<div className="text-sm text-muted-foreground mb-2">{transaction.detalhes}</div>
<div className="flex justify-between">
<Badge variant="outline">{transaction.categoria}</Badge>
<span className={transaction.tipo.toLowerCase() === 'receita' || transaction.tipo.toLowerCase() === 'entrada' ? "text-finance-green font-medium" : "text-finance-red font-medium"}>
{transaction.tipo.toLowerCase() === 'receita' || transaction.tipo.toLowerCase() === 'entrada'
? formatCurrency(Math.abs(transaction.valor))
: `-${formatCurrency(Math.abs(transaction.valor))}`}
</span>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
); );
}; };

View File

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout';
import { CartaoCredito, DespesaCartao } from '@/types/cartaoTypes'; import { CartaoCredito, DespesaCartao } from '@/types/cartaoTypes';
import { getCartoes, getDespesasCartao, getTotalDespesasCartao } from '@/services/cartaoCreditoService'; import { getCartoes, getDespesasCartao, getTotalDespesasCartao } from '@/services/cartaoCreditoService';
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
@ -107,32 +106,30 @@ const CartoesCreditoPage = () => {
}, []); }, []);
return ( return (
<Layout> <div className="space-y-6">
<div className="space-y-6"> <CartaoActions
<CartaoActions isDetalhes={detalheCartaoAberto}
isDetalhes={detalheCartaoAberto} cartaoSelecionado={cartaoSelecionado}
cartaoSelecionado={cartaoSelecionado} onVoltarParaLista={handleVoltarParaLista}
onVoltarParaLista={handleVoltarParaLista} onCartaoSuccess={handleCartaoSuccess}
onCartaoSuccess={handleCartaoSuccess} onDespesaSuccess={handleDespesaSuccess}
/>
{!detalheCartaoAberto ? (
<CartaoListView
cartoes={cartoes}
isLoading={isLoading}
onCartaoClick={handleCartaoClick}
/>
) : cartaoSelecionado && (
<CartaoDetalhesAvancado
cartao={cartaoSelecionado}
despesas={despesas}
isLoading={isLoading}
onDespesaSuccess={handleDespesaSuccess} onDespesaSuccess={handleDespesaSuccess}
/> />
)}
{!detalheCartaoAberto ? ( </div>
<CartaoListView
cartoes={cartoes}
isLoading={isLoading}
onCartaoClick={handleCartaoClick}
/>
) : cartaoSelecionado && (
<CartaoDetalhesAvancado
cartao={cartaoSelecionado}
despesas={despesas}
isLoading={isLoading}
onDespesaSuccess={handleDespesaSuccess}
/>
)}
</div>
</Layout>
); );
}; };

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout'; import { Card, CardContent } from '@/components/ui/card';
import { CardSpotlight } from '@/components/ui/card-spotlight';
import { getCategorySummary } from '@/services/transacao'; import { getCategorySummary } from '@/services/transacao';
import { CategorySummary } from '@/types/financialTypes'; import { CategorySummary } from '@/types/financialTypes';
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
@ -98,102 +98,110 @@ const CategoriasPage = () => {
{isLoading ? ( {isLoading ? (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{[1, 2, 3, 4, 5, 6].map((i) => ( {[1, 2, 3, 4, 5, 6].map((i) => (
<CardSpotlight key={i} className="animate-pulse"> <Card key={i} className="animate-pulse">
<div className="pb-2"> <CardContent className="p-6">
<div className="h-5 bg-muted rounded w-1/2"></div> <div className="pb-2">
</div> <div className="h-5 bg-muted rounded w-1/2"></div>
<div> </div>
<div className="h-4 bg-muted rounded w-1/4 mb-3"></div> <div>
<div className="h-2 bg-muted rounded"></div> <div className="h-4 bg-muted rounded w-1/4 mb-3"></div>
</div> <div className="h-2 bg-muted rounded"></div>
</CardSpotlight> </div>
</CardContent>
</Card>
))} ))}
</div> </div>
) : categories.length === 0 ? ( ) : categories.length === 0 ? (
<CardSpotlight> <Card>
<div className="py-10 text-center"> <CardContent className="p-6">
<p className="text-muted-foreground">Nenhuma categoria encontrada</p> <div className="py-10 text-center">
<p className="mt-2 text-sm"> <p className="text-muted-foreground">Nenhuma categoria encontrada</p>
{tipoFiltro === 'despesa' <p className="mt-2 text-sm">
? 'Verifique se existem transações do tipo "despesa" cadastradas' {tipoFiltro === 'despesa'
: tipoFiltro === 'receita' ? 'Verifique se existem transações do tipo "despesa" cadastradas'
? 'Verifique se existem transações do tipo "receita" cadastradas' : tipoFiltro === 'receita'
: 'Verifique se existem transações cadastradas'} ? 'Verifique se existem transações do tipo "receita" cadastradas'
</p> : 'Verifique se existem transações cadastradas'}
</div> </p>
</CardSpotlight> </div>
</CardContent>
</Card>
) : ( ) : (
<> <>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{categories.map((category) => ( {categories.map((category) => (
<CardSpotlight key={category.categoria} className="relative z-10"> <Card key={category.categoria}>
<div className="pb-2"> <CardContent className="p-6">
<div className="text-base flex items-center font-semibold relative z-20"> <div className="pb-2">
<span <div className="text-base flex items-center font-semibold">
className="w-3 h-3 rounded-full mr-2" <span
style={{ backgroundColor: category.color }} className="w-3 h-3 rounded-full mr-2"
/> style={{ backgroundColor: category.color }}
{category.categoria} />
</div> {category.categoria}
</div>
<div className="relative z-20">
<div className="text-2xl font-bold mb-2">
{formatCurrency(category.valor)}
</div>
<div className="space-y-2">
<Progress
value={category.percentage * 100}
className="h-2"
style={{
backgroundColor: 'rgba(0,0,0,0.1)',
'--progress-background': category.color
} as React.CSSProperties}
/>
<div className="text-xs text-muted-foreground">
{(category.percentage * 100).toFixed(1)}% do total
</div> </div>
</div> </div>
</div> <div>
</CardSpotlight> <div className="text-2xl font-bold mb-2">
{formatCurrency(category.valor)}
</div>
<div className="space-y-2">
<Progress
value={category.percentage * 100}
className="h-2"
style={{
backgroundColor: 'rgba(0,0,0,0.1)',
'--progress-background': category.color
} as React.CSSProperties}
/>
<div className="text-xs text-muted-foreground">
{(category.percentage * 100).toFixed(1)}% do total
</div>
</div>
</div>
</CardContent>
</Card>
))} ))}
</div> </div>
<CardSpotlight className="mt-6"> <Card className="mt-6">
<div> <CardContent className="p-6">
<h3 className="text-xl font-bold mb-4 relative z-20">Resumo de Categorias {tipoFiltro !== 'all' ? (tipoFiltro === 'despesa' ? '(Despesas)' : '(Receitas)') : ''}</h3> <div>
</div> <h3 className="text-xl font-bold mb-4">Resumo de Categorias {tipoFiltro !== 'all' ? (tipoFiltro === 'despesa' ? '(Despesas)' : '(Receitas)') : ''}</h3>
<div className="relative z-20"> </div>
<Table> <div>
<TableHeader> <Table>
<TableRow> <TableHeader>
<TableHead>Categoria</TableHead> <TableRow>
<TableHead>Valor</TableHead> <TableHead>Categoria</TableHead>
<TableHead>Percentual</TableHead> <TableHead>Valor</TableHead>
</TableRow> <TableHead>Percentual</TableHead>
</TableHeader>
<TableBody>
{categories.map((category) => (
<TableRow key={category.categoria}>
<TableCell className="font-medium flex items-center">
<span
className="w-3 h-3 rounded-full mr-2"
style={{ backgroundColor: category.color }}
/>
{category.categoria}
</TableCell>
<TableCell>{formatCurrency(category.valor)}</TableCell>
<TableCell>{(category.percentage * 100).toFixed(1)}%</TableCell>
</TableRow> </TableRow>
))} </TableHeader>
<TableRow className="bg-muted/50"> <TableBody>
<TableCell className="font-bold">Total</TableCell> {categories.map((category) => (
<TableCell className="font-bold">{formatCurrency(totalGastos)}</TableCell> <TableRow key={category.categoria}>
<TableCell>100%</TableCell> <TableCell className="font-medium flex items-center">
</TableRow> <span
</TableBody> className="w-3 h-3 rounded-full mr-2"
</Table> style={{ backgroundColor: category.color }}
</div> />
</CardSpotlight> {category.categoria}
</TableCell>
<TableCell>{formatCurrency(category.valor)}</TableCell>
<TableCell>{(category.percentage * 100).toFixed(1)}%</TableCell>
</TableRow>
))}
<TableRow className="bg-muted/50">
<TableCell className="font-bold">Total</TableCell>
<TableCell className="font-bold">{formatCurrency(totalGastos)}</TableCell>
<TableCell>100%</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</> </>
)} )}
</div> </div>

View File

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-react'; import { RefreshCw } from 'lucide-react';
import { listarGruposWhatsApp } from '@/services/gruposWhatsAppService'; import { listarGruposWhatsApp } from '@/services/gruposWhatsAppService';
@ -65,31 +64,29 @@ const GruposWhatsApp = () => {
}, []); }, []);
return ( return (
<Layout> <div className="space-y-6">
<div className="space-y-6"> <div className="flex justify-between items-center">
<div className="flex justify-between items-center"> <h1 className="text-2xl font-bold tracking-tight">Grupos do WhatsApp</h1>
<h1 className="text-2xl font-bold tracking-tight">Grupos do WhatsApp</h1> <div className="flex gap-2">
<div className="flex gap-2"> <Button
<Button variant="outline"
variant="outline" size="sm"
size="sm" onClick={buscarGrupos}
onClick={buscarGrupos} disabled={carregando}
disabled={carregando} >
> <RefreshCw className={`h-4 w-4 mr-2 ${carregando ? 'animate-spin' : ''}`} />
<RefreshCw className={`h-4 w-4 mr-2 ${carregando ? 'animate-spin' : ''}`} /> {carregando ? 'Atualizando...' : 'Atualizar'}
{carregando ? 'Atualizando...' : 'Atualizar'} </Button>
</Button>
</div>
</div> </div>
<CreateGroupForm userEmail={userEmail} onSuccess={buscarGrupos} />
<GroupsList
grupos={grupos}
carregando={carregando}
onDeleteGroup={handleDeleteGroup}
/>
</div> </div>
</Layout>
<CreateGroupForm userEmail={userEmail} onSuccess={buscarGrupos} />
<GroupsList
grupos={grupos}
carregando={carregando}
onDeleteGroup={handleDeleteGroup}
/>
</div>
); );
}; };

View File

@ -1,228 +1,178 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import SummaryCard from '@/components/dashboard/SummaryCard';
import TransactionsTable from '@/components/dashboard/TransactionsTable';
import CategoryChart from '@/components/dashboard/CategoryChart';
import MonthlyChart from '@/components/dashboard/MonthlyChart';
import { MonthFilter } from '@/components/filters/MonthFilter';
import { OnboardingTour, useOnboardingTour } from '@/components/onboarding';
import { Wallet, ArrowUp, ArrowDown, PiggyBank } from 'lucide-react';
import { Transaction, CategorySummary, MonthlyData } from '@/types/financialTypes';
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { import { ArrowUpIcon, ArrowDownIcon, CreditCardIcon, Target } from "lucide-react";
getTransacoes, import { getResumoFinanceiro } from "@/services/transacao";
getTransactionSummary, import { ResumoFinanceiro } from "@/types/financialTypes";
getCategorySummary, import TransactionsTable from "@/components/dashboard/TransactionsTable";
getMonthlyData import { useTransactions } from "@/hooks/useTransactions";
} from '@/services/transacao'; import CategoryChart from "@/components/dashboard/CategoryChart";
import { getCartoes } from '@/services/cartaoCreditoService';
const Dashboard = () => { const Dashboard = () => {
const [resumo, setResumo] = useState<ResumoFinanceiro | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [categories, setCategories] = useState<CategorySummary[]>([]);
const [monthlyData, setMonthlyData] = useState<MonthlyData[]>([]);
const [totals, setTotals] = useState({ receitas: 0, despesas: 0, saldo: 0, cartoes: 0 });
const { toast } = useToast(); const { toast } = useToast();
// Hook do tour de onboarding
const {
isOpen: tourIsOpen,
currentStep: tourCurrentStep,
nextStep: tourNextStep,
skipTour,
closeTour
} = useOnboardingTour();
// Função para obter o mês atual no formato YYYY-MM
const getCurrentMonth = () => { const getCurrentMonth = () => {
const now = new Date(); const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
}; };
const [selectedMonth, setSelectedMonth] = useState<string>(getCurrentMonth()); const {
transactions,
isLoading: transactionsLoading,
formatCurrency
} = useTransactions({ monthFilter: getCurrentMonth() });
// Load data when component mounts or month changes
useEffect(() => { useEffect(() => {
async function loadData() { const storedUserId = localStorage.getItem('userId');
try { const finDashUser = localStorage.getItem('finDashUser');
setIsLoading(true);
console.log("🚀 [Dashboard] Iniciando carregamento de dados para o mês:", selectedMonth);
// Verificar se existe email no localStorage
const userEmail = localStorage.getItem('userEmail');
console.log("📧 [Dashboard] Email no localStorage:", userEmail);
if (!userEmail) {
console.error("❌ [Dashboard] Email não encontrado no localStorage!");
toast({
title: "Erro de autenticação",
description: "Email do usuário não encontrado. Faça login novamente.",
variant: "destructive"
});
return;
}
// Buscar todos os dados necessários com filtro de mês
console.log("📊 [Dashboard] Buscando dados do Supabase...");
const [transacoesData, totalsData, categoriesData, monthlyDataResult, cartoesData] = await Promise.all([
getTransacoes(selectedMonth),
getTransactionSummary(selectedMonth),
getCategorySummary('despesa', selectedMonth),
getMonthlyData(), // Monthly data não precisa de filtro pois já mostra todos os meses
getCartoes()
]);
// Calcular total dos cartões
const totalCartoes = cartoesData.reduce((sum, cartao) => sum + (cartao.total_despesas || 0), 0);
console.log("✅ [Dashboard] Dados obtidos com sucesso:", {
transacoes: transacoesData.length,
totals: totalsData,
categories: categoriesData.length,
monthlyData: monthlyDataResult.length,
cartoes: cartoesData.length,
totalCartoes: totalCartoes,
mesEscolhido: selectedMonth
});
setTransactions(transacoesData);
setTotals({
...totalsData,
cartoes: totalCartoes,
// Atualiza o saldo incluindo despesas de cartão
saldo: totalsData.receitas - totalsData.despesas - totalCartoes
});
setCategories(categoriesData);
setMonthlyData(monthlyDataResult);
if (transacoesData.length > 0 || categoriesData.length > 0 || monthlyDataResult.length > 0) {
toast({
title: "Dados carregados com sucesso",
description: `${transacoesData.length} transações encontradas para ${selectedMonth}`
});
} else {
toast({
title: "Nenhum dado encontrado",
description: `Não foram encontradas transações para ${selectedMonth}`,
variant: "destructive"
});
}
} catch (error) {
console.error("💥 [Dashboard] Erro ao carregar dados:", error);
toast({
title: "Erro ao carregar dados",
description: "Verifique a conexão com o Supabase",
variant: "destructive"
});
} finally {
setIsLoading(false);
}
}
loadData(); if (!storedUserId && finDashUser) {
}, [selectedMonth, toast]); localStorage.setItem('userId', finDashUser);
} else if (!storedUserId && !finDashUser) {
const defaultId = '9f267008-9128-4a2f-b730-de0a0b5602a9';
localStorage.setItem('userId', defaultId);
}
}, []);
const formatCurrency = (value: number) => { const loadResumo = async () => {
return new Intl.NumberFormat('pt-BR', { try {
style: 'currency', setIsLoading(true);
currency: 'BRL', console.log("Carregando resumo financeiro...");
}).format(value); const data = await getResumoFinanceiro();
console.log("Resumo carregado:", data);
setResumo(data);
} catch (error) {
console.error("Erro ao carregar resumo:", error);
toast({
title: "Erro ao carregar resumo",
description: "Não foi possível obter os dados do Supabase",
variant: "destructive"
});
} finally {
setIsLoading(false);
}
}; };
// Calculate total of all expenses (regular + credit cards) useEffect(() => {
const totalDespesasGeral = totals.despesas + totals.cartoes; loadResumo();
}, [toast]);
// Função para formatar o mês para exibição if (isLoading) {
const formatMonthDisplay = (month: string) => { return (
const [year, monthNum] = month.split('-');
const months = [
'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'
];
return `${months[parseInt(monthNum) - 1]} ${year}`;
};
return (
<Layout>
<div className="space-y-6"> <div className="space-y-6">
<div className="flex justify-between items-center"> <div className="grid gap-4 md:grid-cols-4">
<div className="space-y-1"> {[1, 2, 3, 4].map((i) => (
<h1 className="text-2xl font-bold tracking-tight">Dashboard Financeiro</h1> <Card key={i}>
<p className="text-sm text-muted-foreground"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
Dados de: {formatMonthDisplay(selectedMonth)} <div className="h-4 bg-muted rounded w-1/2 animate-pulse"></div>
</p> <div className="h-4 w-4 bg-muted rounded animate-pulse"></div>
</div> </CardHeader>
<div className="flex items-center gap-4"> <CardContent>
<MonthFilter <div className="h-8 bg-muted rounded w-3/4 mb-2 animate-pulse"></div>
selectedMonth={selectedMonth} <div className="h-3 bg-muted rounded w-1/2 animate-pulse"></div>
onMonthChange={setSelectedMonth} </CardContent>
/> </Card>
</div> ))}
</div>
{/* Resumo em cards */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<SummaryCard
title="Receitas"
value={formatCurrency(totals.receitas)}
icon={<ArrowUp size={24} className="text-green-600" />}
trend={5}
iconClass="bg-green-100"
valueClass="text-green-700"
className="border-green-200 shadow-lg"
/>
<SummaryCard
title="Despesas"
value={formatCurrency(totalDespesasGeral)}
secondaryValue={`Cartões: ${formatCurrency(totals.cartoes)}`}
icon={<ArrowDown size={24} className="text-red-600" />}
trend={-2}
iconClass="bg-red-100"
valueClass="text-red-700"
className="border-red-200 shadow-lg"
/>
<SummaryCard
title="Saldo"
value={formatCurrency(totals.saldo)}
icon={<Wallet size={24} className="text-blue-600" />}
iconClass="bg-blue-100"
valueClass="text-blue-700"
className="border-blue-200 shadow-lg"
/>
<SummaryCard
title="Economia"
value={`${totals.receitas > 0 ? ((totals.saldo / totals.receitas) * 100).toFixed(1) : 0}%`}
secondaryValue={formatCurrency(totals.saldo)}
icon={<PiggyBank size={24} className="text-purple-600" />}
iconClass="bg-purple-100"
valueClass="text-purple-700"
className="border-purple-200 shadow-lg"
/>
</div>
{/* ---- GRÁFICOS EM COLUNA ---- */}
<div className="flex flex-col gap-6 w-full">
<MonthlyChart data={monthlyData} isLoading={isLoading} />
<CategoryChart categories={categories} isLoading={isLoading} />
</div>
<div className="space-y-2">
<h2 className="text-xl font-semibold">Transações Recentes</h2>
<TransactionsTable transactions={transactions} isLoading={isLoading} />
</div> </div>
</div> </div>
);
}
{/* Tour de Onboarding */} return (
<OnboardingTour <div className="space-y-6" data-tour="dashboard-content">
isOpen={tourIsOpen} <div className="flex items-center justify-between">
currentStep={tourCurrentStep} <h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
onNext={tourNextStep} </div>
onSkip={skipTour}
onClose={closeTour} <div className="grid gap-4 md:grid-cols-4">
/> <Card>
</Layout> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Receitas</CardTitle>
<ArrowUpIcon className="h-4 w-4 text-green-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">
{resumo ? formatCurrency(resumo.totalReceitas) : 'R$ 0,00'}
</div>
<p className="text-xs text-muted-foreground">Entradas do mês</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Despesas</CardTitle>
<ArrowDownIcon className="h-4 w-4 text-red-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-red-600">
{resumo ? formatCurrency(resumo.totalDespesas) : 'R$ 0,00'}
</div>
<p className="text-xs text-muted-foreground">Saídas do mês</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Cartões de Crédito</CardTitle>
<CreditCardIcon className="h-4 w-4 text-blue-600" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-blue-600">
{resumo ? formatCurrency(resumo.totalCartoes || 0) : 'R$ 0,00'}
</div>
<p className="text-xs text-muted-foreground">Gastos nos cartões</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Saldo</CardTitle>
<Target className="h-4 w-4 text-purple-600" />
</CardHeader>
<CardContent>
<div className={`text-2xl font-bold ${
resumo && (resumo.totalReceitas - resumo.totalDespesas - (resumo.totalCartoes || 0)) >= 0
? 'text-green-600'
: 'text-red-600'
}`}>
{resumo ? formatCurrency(resumo.totalReceitas - resumo.totalDespesas - (resumo.totalCartoes || 0)) : 'R$ 0,00'}
</div>
<p className="text-xs text-muted-foreground">Resultado do mês</p>
</CardContent>
</Card>
</div>
<div className="grid gap-6 md:grid-cols-2">
<Card className="md:col-span-1">
<CardHeader>
<CardTitle>Despesas por Categoria</CardTitle>
<CardDescription>Distribuição dos seus gastos</CardDescription>
</CardHeader>
<CardContent>
<CategoryChart />
</CardContent>
</Card>
<Card className="md:col-span-1">
<CardHeader>
<CardTitle>Últimas Transações</CardTitle>
<CardDescription>Suas transações mais recentes</CardDescription>
</CardHeader>
<CardContent>
<TransactionsTable
transactions={transactions.slice(0, 5)}
isLoading={transactionsLoading}
showPagination={false}
onEdit={() => {}}
onDelete={() => {}}
/>
</CardContent>
</Card>
</div>
</div>
); );
}; };

View File

@ -1,78 +1,31 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { format } from 'date-fns'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { ptBR } from 'date-fns/locale';
import Layout from '@/components/layout/Layout';
import { MetasForm } from '@/components/dashboard/MetasForm';
import { MetaProgressCard } from '@/components/dashboard/MetaProgressCard';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card'; import { Plus } from 'lucide-react';
import { ResultadoMeta } from '@/types/financialTypes'; import { MetaFinanceira } from '@/types/financialTypes';
import { useToast } from '@/components/ui/use-toast'; import { getMetas } from '@/services/metasService';
import { calcularResultadosMetas, getMeta, deletarMeta } from '@/services/metasService'; import { useToast } from '@/hooks/use-toast';
import { PlusCircle } from 'lucide-react'; import MetaForm from '@/components/metas/MetaForm';
import { import MetaCard from '@/components/metas/MetaCard';
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
const MetasPage = () => { const MetasPage = () => {
const { toast } = useToast(); const [metas, setMetas] = useState<MetaFinanceira[]>([]);
const [userId, setUserId] = useState<string>('');
const [resultados, setResultados] = useState<ResultadoMeta[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isDialogOpen, setIsDialogOpen] = useState(false); const [showForm, setShowForm] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [editingMeta, setEditingMeta] = useState<MetaFinanceira | null>(null);
const [metaParaEditar, setMetaParaEditar] = useState<{ mes: number, ano: number, id?: string } | null>(null); const { toast } = useToast();
const [metaParaDeletar, setMetaParaDeletar] = useState<{ mes: number, ano: number, id: string } | null>(null);
const [valorMetaAtual, setValorMetaAtual] = useState(0);
const [isDeletingMeta, setIsDeletingMeta] = useState(false);
useEffect(() => { const loadMetas = async () => {
// Obter userId armazenado no localStorage
const storedUserId = localStorage.getItem('userId');
if (storedUserId) {
setUserId(storedUserId);
loadData(storedUserId);
} else {
// Tentar usar finDashUser como fallback
const finDashUser = localStorage.getItem('finDashUser');
if (finDashUser) {
setUserId(finDashUser);
loadData(finDashUser);
} else {
// Caso não encontre nenhum ID de usuário, usar um ID padrão temporário
const defaultId = '9f267008-9128-4a2f-b730-de0a0b5602a9';
localStorage.setItem('userId', defaultId);
setUserId(defaultId);
loadData(defaultId);
}
}
}, []);
const loadData = async (user: string) => {
try { try {
setIsLoading(true); setIsLoading(true);
const resultado = await calcularResultadosMetas(user); const data = await getMetas();
setResultados(resultado); setMetas(data);
} catch (error) { } catch (error) {
console.error('Erro ao carregar metas:', error); console.error('Erro ao carregar metas:', error);
toast({ toast({
title: 'Erro ao carregar metas', title: 'Erro',
description: 'Não foi possível obter os dados de metas', description: 'Não foi possível carregar suas metas',
variant: 'destructive', variant: 'destructive',
}); });
} finally { } finally {
@ -80,207 +33,107 @@ const MetasPage = () => {
} }
}; };
const handleEditarMeta = async (mes: number, ano: number) => { useEffect(() => {
try { loadMetas();
const meta = await getMeta(userId, mes, ano); }, []);
if (meta) {
setValorMetaAtual(meta.valor_meta); const handleMetaSuccess = () => {
setMetaParaEditar({ mes, ano, id: meta.id }); loadMetas();
setIsDialogOpen(true); setShowForm(false);
} setEditingMeta(null);
} catch (error) { toast({
console.error('Erro ao buscar meta para edição:', error); title: 'Sucesso',
} description: editingMeta ? 'Meta atualizada com sucesso' : 'Meta criada com sucesso',
});
}; };
const handleDeletarMeta = (mes: number, ano: number, id: string) => { const handleEditMeta = (meta: MetaFinanceira) => {
setMetaParaDeletar({ mes, ano, id }); setEditingMeta(meta);
setIsDeleteDialogOpen(true); setShowForm(true);
}; };
const confirmarDeletarMeta = async () => { const handleDeleteSuccess = () => {
if (!metaParaDeletar?.id) return; loadMetas();
toast({
try { title: 'Sucesso',
setIsDeletingMeta(true); description: 'Meta excluída com sucesso',
await deletarMeta(metaParaDeletar.id); });
// Atualizar lista de metas após deleção
await loadData(userId);
toast({
title: 'Meta excluída',
description: `A meta de ${format(new Date(metaParaDeletar.ano, metaParaDeletar.mes - 1, 1), 'MMMM', { locale: ptBR })} ${metaParaDeletar.ano} foi excluída com sucesso.`,
});
} catch (error) {
console.error('Erro ao deletar meta:', error);
toast({
title: 'Erro ao deletar meta',
description: 'Não foi possível excluir a meta',
variant: 'destructive',
});
} finally {
setIsDeletingMeta(false);
setIsDeleteDialogOpen(false);
setMetaParaDeletar(null);
}
}; };
const handleNovaMeta = () => {
setMetaParaEditar(null);
setValorMetaAtual(0);
setIsDialogOpen(true);
};
const handleSuccess = () => {
setIsDialogOpen(false);
loadData(userId);
};
const getMesAtual = () => {
const now = new Date();
return {
mes: now.getMonth() + 1,
ano: now.getFullYear(),
nome: format(now, 'MMMM', { locale: ptBR }),
};
};
const mesAtual = getMesAtual();
// Agrupar por ano (objeto com chave = ano e valor = array de metas daquele ano)
const resultadosPorAno = resultados.reduce((acc, meta) => {
if (!acc[meta.ano]) {
acc[meta.ano] = [];
}
acc[meta.ano].push(meta);
return acc;
}, {} as Record<number, ResultadoMeta[]>);
// Ordenar os anos de forma decrescente
const anosOrdenados = Object.keys(resultadosPorAno)
.map(Number)
.sort((a, b) => b - a);
return ( return (
<Layout> <div className="space-y-6">
<div className="space-y-6"> <div className="flex items-center justify-between">
<div className="flex justify-between items-center"> <div>
<h1 className="text-2xl font-bold tracking-tight">Metas de Economia</h1> <h1 className="text-3xl font-bold text-gray-900">Metas Financeiras</h1>
<Button <p className="text-gray-600 mt-2">Defina e acompanhe seus objetivos financeiros</p>
className="flex items-center gap-2 bg-finance-blue hover:bg-finance-blue/90"
onClick={handleNovaMeta}
>
<PlusCircle className="h-4 w-4" />
Nova Meta
</Button>
</div> </div>
<Button
{isLoading ? ( onClick={() => setShowForm(true)}
<div className="flex justify-center py-10"> className="bg-blue-600 hover:bg-blue-700 text-white"
<div className="text-center">
<div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full mx-auto"></div>
<p className="mt-4 text-muted-foreground">Carregando suas metas...</p>
</div>
</div>
) : resultados.length === 0 ? (
<Card className="flex flex-col items-center justify-center py-10 px-4 text-center">
<h3 className="text-xl font-semibold mb-2">Nenhuma meta definida</h3>
<p className="text-muted-foreground mb-6">
Defina sua primeira meta de economia para {mesAtual.nome} {mesAtual.ano}
</p>
<Button onClick={handleNovaMeta}>
<PlusCircle className="h-4 w-4 mr-2" />
Definir Meta
</Button>
</Card>
) : (
<>
{anosOrdenados.map(ano => (
<div key={ano} className="space-y-3">
<h2 className="text-xl font-semibold">{ano}</h2>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{resultadosPorAno[ano]
.sort((a, b) => b.mes - a.mes)
.map(meta => (
<MetaProgressCard
key={`${meta.ano}-${meta.mes}`}
meta={meta}
onEditClick={() => handleEditarMeta(meta.mes, meta.ano)}
onDeleteClick={() => {
// Aqui precisamos buscar o ID primeiro pois resultadoMeta não tem o ID
// Vamos usar o metaParaEditar.id que já foi carregado se o usuário clicou em editar antes
if (metaParaEditar && metaParaEditar.mes === meta.mes && metaParaEditar.ano === meta.ano && metaParaEditar.id) {
handleDeletarMeta(meta.mes, meta.ano, metaParaEditar.id);
} else {
// Buscar o ID antes de deletar
getMeta(userId, meta.mes, meta.ano).then(metaCompleta => {
if (metaCompleta && metaCompleta.id) {
handleDeletarMeta(meta.mes, meta.ano, metaCompleta.id);
}
});
}
}}
/>
))}
</div>
</div>
))}
</>
)}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>
{metaParaEditar ? 'Editar Meta' : 'Nova Meta'}
</DialogTitle>
<DialogDescription>
{metaParaEditar
? 'Atualize os valores da sua meta de economia.'
: 'Defina uma nova meta de economia mensal.'}
</DialogDescription>
</DialogHeader>
<MetasForm
onSuccess={handleSuccess}
onCancel={() => setIsDialogOpen(false)}
userId={userId}
valorInicial={metaParaEditar ? valorMetaAtual : 0}
mesInicial={metaParaEditar?.mes}
anoInicial={metaParaEditar?.ano}
/>
</DialogContent>
</Dialog>
<AlertDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
> >
<AlertDialogContent> <Plus className="h-4 w-4 mr-2" />
<AlertDialogHeader> Nova Meta
<AlertDialogTitle> </Button>
Confirmar exclusão
</AlertDialogTitle>
<AlertDialogDescription>
{metaParaDeletar && (
`Tem certeza que deseja excluir a meta de ${format(new Date(metaParaDeletar.ano, metaParaDeletar.mes - 1, 1), 'MMMM', { locale: ptBR })} de ${metaParaDeletar.ano}? Esta ação não pode ser desfeita.`
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeletingMeta}>Cancelar</AlertDialogCancel>
<AlertDialogAction
onClick={confirmarDeletarMeta}
disabled={isDeletingMeta}
className="bg-destructive hover:bg-destructive/90"
>
{isDeletingMeta ? 'Deletando...' : 'Deletar Meta'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div> </div>
</Layout>
{showForm && (
<MetaForm
meta={editingMeta}
onSuccess={handleMetaSuccess}
onCancel={() => {
setShowForm(false);
setEditingMeta(null);
}}
/>
)}
{isLoading ? (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{[1, 2, 3].map((i) => (
<Card key={i} className="animate-pulse">
<CardHeader>
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="h-3 bg-gray-200 rounded"></div>
<div className="h-3 bg-gray-200 rounded w-5/6"></div>
</div>
</CardContent>
</Card>
))}
</div>
) : metas.length === 0 ? (
<Card>
<CardContent className="py-10 text-center">
<div className="text-gray-400 mb-4">
<Plus className="h-16 w-16 mx-auto" />
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Nenhuma meta cadastrada</h3>
<p className="text-gray-600 mb-4">Comece criando sua primeira meta financeira</p>
<Button
onClick={() => setShowForm(true)}
className="bg-blue-600 hover:bg-blue-700 text-white"
>
<Plus className="h-4 w-4 mr-2" />
Criar primeira meta
</Button>
</CardContent>
</Card>
) : (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{metas.map((meta) => (
<MetaCard
key={meta.id}
meta={meta}
onEdit={handleEditMeta}
onDeleteSuccess={handleDeleteSuccess}
/>
))}
</div>
)}
</div>
); );
}; };

View File

@ -1,6 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import Layout from '@/components/layout/Layout';
import TransactionsTable from '@/components/dashboard/TransactionsTable'; import TransactionsTable from '@/components/dashboard/TransactionsTable';
import { useTransactions } from '@/hooks/useTransactions'; import { useTransactions } from '@/hooks/useTransactions';
import { TransactionHeader } from '@/components/transacoes/TransactionHeader'; import { TransactionHeader } from '@/components/transacoes/TransactionHeader';
@ -54,82 +53,80 @@ const TransacoesPage = () => {
}; };
return ( return (
<Layout> <div className="space-y-6">
<div className="space-y-6"> <div className="flex justify-between items-center">
<div className="flex justify-between items-center"> <div className="space-y-1">
<div className="space-y-1"> <h1 className="text-2xl font-bold tracking-tight">Transações</h1>
<h1 className="text-2xl font-bold tracking-tight">Transações</h1> <p className="text-sm text-muted-foreground">
<p className="text-sm text-muted-foreground"> Dados de: {formatMonthDisplay(selectedMonth)}
Dados de: {formatMonthDisplay(selectedMonth)} </p>
</p> {/* Exibe mensagem para trial OU para admin-liberou */}
{/* Exibe mensagem para trial OU para admin-liberou */} {!access.loading && access.podeAdicionarTransacao && (
{!access.loading && access.podeAdicionarTransacao && ( access.adminLiberou ? (
access.adminLiberou ? ( <div className="mt-1 text-xs text-green-600 font-bold">
<div className="mt-1 text-xs text-green-600 font-bold"> {access.motivo}
{access.motivo} </div>
</div> ) : (access.diasRestantesTrial > 0 && (
) : (access.diasRestantesTrial > 0 && ( <div className="mt-1 text-xs text-blue-600 font-medium">
<div className="mt-1 text-xs text-blue-600 font-medium"> {`Você está em período gratuito. ${access.diasRestantesTrial} dia(s) restante(s) de teste.`}
{`Você está em período gratuito. ${access.diasRestantesTrial} dia(s) restante(s) de teste.`} </div>
</div> ))
)) )}
)}
</div>
<MonthFilter
selectedMonth={selectedMonth}
onMonthChange={setSelectedMonth}
/>
</div> </div>
<MonthFilter
{/* Mensagem de bloqueio */} selectedMonth={selectedMonth}
{!access.loading && !access.podeAdicionarTransacao && ( onMonthChange={setSelectedMonth}
<div className="bg-yellow-100 border border-yellow-300 text-yellow-800 px-4 py-3 rounded relative mb-4">
<span className="font-medium">Atenção:</span> {access.motivo}
</div>
)}
{/* Só passa as funções de adicionar se permitido */}
<TransactionHeader
onOpenDialog={access.podeAdicionarTransacao ? handleOpenDialog : () => {}}
onOpenCartaoCreditoDialog={access.podeAdicionarTransacao ? handleOpenCartaoCreditoDialog : () => {}}
disableAdicionar={!access.podeAdicionarTransacao}
/>
{/* Resumo em Cards */}
<TransactionSummaryCards
totalReceitas={totalReceitas}
totalDespesas={totalDespesas}
totalCartoes={totalCartoes}
formatCurrency={formatCurrency}
/>
{/* Tabela completa de transações */}
<div className="space-y-4">
<h2 className="text-xl font-semibold">Todas as Transações</h2>
<TransactionsTable
transactions={transactions}
isLoading={isLoading}
showPagination={true}
onEdit={handleEditTransaction}
onDelete={loadTransactions}
/>
</div>
{/* Diálogos */}
<TransactionDialogs
isDialogOpen={isDialogOpen}
isCartaoCreditoDialogOpen={isCartaoCreditoDialogOpen}
tipoForm={tipoForm}
selectedTransaction={selectedTransaction}
isEditing={isEditing}
cartoes={cartoes}
onTransactionSuccess={handleTransactionSuccess}
onDespesaCartaoSuccess={handleDespesaCartaoSuccess}
onCloseDialog={handleCloseDialog}
onCloseCartaoCreditoDialog={handleCloseCartaoCreditoDialog}
/> />
</div> </div>
</Layout>
{/* Mensagem de bloqueio */}
{!access.loading && !access.podeAdicionarTransacao && (
<div className="bg-yellow-100 border border-yellow-300 text-yellow-800 px-4 py-3 rounded relative mb-4">
<span className="font-medium">Atenção:</span> {access.motivo}
</div>
)}
{/* Só passa as funções de adicionar se permitido */}
<TransactionHeader
onOpenDialog={access.podeAdicionarTransacao ? handleOpenDialog : () => {}}
onOpenCartaoCreditoDialog={access.podeAdicionarTransacao ? handleOpenCartaoCreditoDialog : () => {}}
disableAdicionar={!access.podeAdicionarTransacao}
/>
{/* Resumo em Cards */}
<TransactionSummaryCards
totalReceitas={totalReceitas}
totalDespesas={totalDespesas}
totalCartoes={totalCartoes}
formatCurrency={formatCurrency}
/>
{/* Tabela completa de transações */}
<div className="space-y-4">
<h2 className="text-xl font-semibold">Todas as Transações</h2>
<TransactionsTable
transactions={transactions}
isLoading={isLoading}
showPagination={true}
onEdit={handleEditTransaction}
onDelete={loadTransactions}
/>
</div>
{/* Diálogos */}
<TransactionDialogs
isDialogOpen={isDialogOpen}
isCartaoCreditoDialogOpen={isCartaoCreditoDialogOpen}
tipoForm={tipoForm}
selectedTransaction={selectedTransaction}
isEditing={isEditing}
cartoes={cartoes}
onTransactionSuccess={handleTransactionSuccess}
onDespesaCartaoSuccess={handleDespesaCartaoSuccess}
onCloseDialog={handleCloseDialog}
onCloseCartaoCreditoDialog={handleCloseCartaoCreditoDialog}
/>
</div>
); );
}; };

View File

@ -1,6 +1,5 @@
import { WhatsAppInstance } from '@/types/whatsAppTypes'; import { WhatsAppInstance } from '@/types/whatsAppTypes';
import Layout from '@/components/layout/Layout';
import LoadingState from '@/components/whatsapp/LoadingState'; import LoadingState from '@/components/whatsapp/LoadingState';
import WhatsAppHeader from '@/components/whatsapp/WhatsAppHeader'; import WhatsAppHeader from '@/components/whatsapp/WhatsAppHeader';
import WhatsAppManager from '@/components/whatsapp/WhatsAppManager'; import WhatsAppManager from '@/components/whatsapp/WhatsAppManager';
@ -85,41 +84,37 @@ const WhatsApp = () => {
if (checkingExistingInstance || isLoading) { if (checkingExistingInstance || isLoading) {
return ( return (
<Layout> <LoadingState message="Verificando suas instâncias..." />
<LoadingState message="Verificando suas instâncias..." />
</Layout>
); );
} }
return ( return (
<Layout> <div className="space-y-6">
<div className="space-y-6"> <WhatsAppHeader
<WhatsAppHeader userEmail={userEmail}
userEmail={userEmail} onRefresh={recheckInstance}
onRefresh={recheckInstance} isRefreshing={checkingExistingInstance}
isRefreshing={checkingExistingInstance} />
/>
<WhatsAppManager
<WhatsAppManager hasExistingInstance={hasExistingInstance}
hasExistingInstance={hasExistingInstance} existingInstanceData={existingInstanceData}
existingInstanceData={existingInstanceData} instances={instances}
instances={instances} isRefreshing={isRefreshing}
isRefreshing={isRefreshing} activeInstance={activeInstance}
activeInstance={activeInstance} qrDialogOpen={qrDialogOpen}
qrDialogOpen={qrDialogOpen} onInstanceCreated={handleInstanceCreated}
onInstanceCreated={handleInstanceCreated} onViewQrCode={handleViewQrCode}
onViewQrCode={handleViewQrCode} onDelete={handleDeleteInstanceWrapper}
onDelete={handleDeleteInstanceWrapper} onRestart={handleRestartInstance}
onRestart={handleRestartInstance} onLogout={handleLogoutInstance}
onLogout={handleLogoutInstance} onDisconnect={handleDisconnectInstance}
onDisconnect={handleDisconnectInstance} onSetPresence={handleSetPresence}
onSetPresence={handleSetPresence} onRefreshInstances={refreshInstances}
onRefreshInstances={refreshInstances} onStatusCheck={checkAllInstancesStatus}
onStatusCheck={checkAllInstancesStatus} setQrDialogOpen={setQrDialogOpen}
setQrDialogOpen={setQrDialogOpen} />
/> </div>
</div>
</Layout>
); );
}; };