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 Layout from '@/components/layout/Layout';
import { Card, CardContent } from '@/components/ui/card';
import { Calendar } from "@/components/ui/calendar";
import { getTransacoes } from '@/services/transacaoService';
@ -66,85 +65,83 @@ const CalendarioPage = () => {
};
return (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<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 className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Calendário de Transações</h1>
</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 Layout from '@/components/layout/Layout';
import { CartaoCredito, DespesaCartao } from '@/types/cartaoTypes';
import { getCartoes, getDespesasCartao, getTotalDespesasCartao } from '@/services/cartaoCreditoService';
import { useToast } from "@/components/ui/use-toast";
@ -107,32 +106,30 @@ const CartoesCreditoPage = () => {
}, []);
return (
<Layout>
<div className="space-y-6">
<CartaoActions
isDetalhes={detalheCartaoAberto}
cartaoSelecionado={cartaoSelecionado}
onVoltarParaLista={handleVoltarParaLista}
onCartaoSuccess={handleCartaoSuccess}
<div className="space-y-6">
<CartaoActions
isDetalhes={detalheCartaoAberto}
cartaoSelecionado={cartaoSelecionado}
onVoltarParaLista={handleVoltarParaLista}
onCartaoSuccess={handleCartaoSuccess}
onDespesaSuccess={handleDespesaSuccess}
/>
{!detalheCartaoAberto ? (
<CartaoListView
cartoes={cartoes}
isLoading={isLoading}
onCartaoClick={handleCartaoClick}
/>
) : cartaoSelecionado && (
<CartaoDetalhesAvancado
cartao={cartaoSelecionado}
despesas={despesas}
isLoading={isLoading}
onDespesaSuccess={handleDespesaSuccess}
/>
{!detalheCartaoAberto ? (
<CartaoListView
cartoes={cartoes}
isLoading={isLoading}
onCartaoClick={handleCartaoClick}
/>
) : cartaoSelecionado && (
<CartaoDetalhesAvancado
cartao={cartaoSelecionado}
despesas={despesas}
isLoading={isLoading}
onDespesaSuccess={handleDespesaSuccess}
/>
)}
</div>
</Layout>
)}
</div>
);
};

View File

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

View File

@ -1,6 +1,5 @@
import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout';
import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-react';
import { listarGruposWhatsApp } from '@/services/gruposWhatsAppService';
@ -65,31 +64,29 @@ const GruposWhatsApp = () => {
}, []);
return (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Grupos do WhatsApp</h1>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={buscarGrupos}
disabled={carregando}
>
<RefreshCw className={`h-4 w-4 mr-2 ${carregando ? 'animate-spin' : ''}`} />
{carregando ? 'Atualizando...' : 'Atualizar'}
</Button>
</div>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Grupos do WhatsApp</h1>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={buscarGrupos}
disabled={carregando}
>
<RefreshCw className={`h-4 w-4 mr-2 ${carregando ? 'animate-spin' : ''}`} />
{carregando ? 'Atualizando...' : 'Atualizar'}
</Button>
</div>
<CreateGroupForm userEmail={userEmail} onSuccess={buscarGrupos} />
<GroupsList
grupos={grupos}
carregando={carregando}
onDeleteGroup={handleDeleteGroup}
/>
</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 Layout from '@/components/layout/Layout';
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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { useToast } from "@/components/ui/use-toast";
import {
getTransacoes,
getTransactionSummary,
getCategorySummary,
getMonthlyData
} from '@/services/transacao';
import { getCartoes } from '@/services/cartaoCreditoService';
import { ArrowUpIcon, ArrowDownIcon, CreditCardIcon, Target } from "lucide-react";
import { getResumoFinanceiro } from "@/services/transacao";
import { ResumoFinanceiro } from "@/types/financialTypes";
import TransactionsTable from "@/components/dashboard/TransactionsTable";
import { useTransactions } from "@/hooks/useTransactions";
import CategoryChart from "@/components/dashboard/CategoryChart";
const Dashboard = () => {
const [resumo, setResumo] = useState<ResumoFinanceiro | null>(null);
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();
// 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 now = new Date();
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(() => {
async function loadData() {
try {
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);
}
}
const storedUserId = localStorage.getItem('userId');
const finDashUser = localStorage.getItem('finDashUser');
loadData();
}, [selectedMonth, toast]);
if (!storedUserId && finDashUser) {
localStorage.setItem('userId', finDashUser);
} else if (!storedUserId && !finDashUser) {
const defaultId = '9f267008-9128-4a2f-b730-de0a0b5602a9';
localStorage.setItem('userId', defaultId);
}
}, []);
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(value);
const loadResumo = async () => {
try {
setIsLoading(true);
console.log("Carregando resumo financeiro...");
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)
const totalDespesasGeral = totals.despesas + totals.cartoes;
useEffect(() => {
loadResumo();
}, [toast]);
// Função para formatar o mês para exibição
const formatMonthDisplay = (month: string) => {
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>
if (isLoading) {
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">Dashboard Financeiro</h1>
<p className="text-sm text-muted-foreground">
Dados de: {formatMonthDisplay(selectedMonth)}
</p>
</div>
<div className="flex items-center gap-4">
<MonthFilter
selectedMonth={selectedMonth}
onMonthChange={setSelectedMonth}
/>
</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 className="grid gap-4 md:grid-cols-4">
{[1, 2, 3, 4].map((i) => (
<Card key={i}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<div className="h-4 bg-muted rounded w-1/2 animate-pulse"></div>
<div className="h-4 w-4 bg-muted rounded animate-pulse"></div>
</CardHeader>
<CardContent>
<div className="h-8 bg-muted rounded w-3/4 mb-2 animate-pulse"></div>
<div className="h-3 bg-muted rounded w-1/2 animate-pulse"></div>
</CardContent>
</Card>
))}
</div>
</div>
);
}
{/* Tour de Onboarding */}
<OnboardingTour
isOpen={tourIsOpen}
currentStep={tourCurrentStep}
onNext={tourNextStep}
onSkip={skipTour}
onClose={closeTour}
/>
</Layout>
return (
<div className="space-y-6" data-tour="dashboard-content">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
</div>
<div className="grid gap-4 md:grid-cols-4">
<Card>
<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 { format } from 'date-fns';
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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
import { ResultadoMeta } from '@/types/financialTypes';
import { useToast } from '@/components/ui/use-toast';
import { calcularResultadosMetas, getMeta, deletarMeta } from '@/services/metasService';
import { PlusCircle } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Plus } from 'lucide-react';
import { MetaFinanceira } from '@/types/financialTypes';
import { getMetas } from '@/services/metasService';
import { useToast } from '@/hooks/use-toast';
import MetaForm from '@/components/metas/MetaForm';
import MetaCard from '@/components/metas/MetaCard';
const MetasPage = () => {
const { toast } = useToast();
const [userId, setUserId] = useState<string>('');
const [resultados, setResultados] = useState<ResultadoMeta[]>([]);
const [metas, setMetas] = useState<MetaFinanceira[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [metaParaEditar, setMetaParaEditar] = useState<{ mes: number, ano: number, id?: string } | null>(null);
const [metaParaDeletar, setMetaParaDeletar] = useState<{ mes: number, ano: number, id: string } | null>(null);
const [valorMetaAtual, setValorMetaAtual] = useState(0);
const [isDeletingMeta, setIsDeletingMeta] = useState(false);
const [showForm, setShowForm] = useState(false);
const [editingMeta, setEditingMeta] = useState<MetaFinanceira | null>(null);
const { toast } = useToast();
useEffect(() => {
// 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) => {
const loadMetas = async () => {
try {
setIsLoading(true);
const resultado = await calcularResultadosMetas(user);
setResultados(resultado);
const data = await getMetas();
setMetas(data);
} catch (error) {
console.error('Erro ao carregar metas:', error);
toast({
title: 'Erro ao carregar metas',
description: 'Não foi possível obter os dados de metas',
title: 'Erro',
description: 'Não foi possível carregar suas metas',
variant: 'destructive',
});
} finally {
@ -80,207 +33,107 @@ const MetasPage = () => {
}
};
const handleEditarMeta = async (mes: number, ano: number) => {
try {
const meta = await getMeta(userId, mes, ano);
if (meta) {
setValorMetaAtual(meta.valor_meta);
setMetaParaEditar({ mes, ano, id: meta.id });
setIsDialogOpen(true);
}
} catch (error) {
console.error('Erro ao buscar meta para edição:', error);
}
useEffect(() => {
loadMetas();
}, []);
const handleMetaSuccess = () => {
loadMetas();
setShowForm(false);
setEditingMeta(null);
toast({
title: 'Sucesso',
description: editingMeta ? 'Meta atualizada com sucesso' : 'Meta criada com sucesso',
});
};
const handleDeletarMeta = (mes: number, ano: number, id: string) => {
setMetaParaDeletar({ mes, ano, id });
setIsDeleteDialogOpen(true);
const handleEditMeta = (meta: MetaFinanceira) => {
setEditingMeta(meta);
setShowForm(true);
};
const confirmarDeletarMeta = async () => {
if (!metaParaDeletar?.id) return;
try {
setIsDeletingMeta(true);
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 handleDeleteSuccess = () => {
loadMetas();
toast({
title: 'Sucesso',
description: 'Meta excluída com sucesso',
});
};
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 (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Metas de Economia</h1>
<Button
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 className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">Metas Financeiras</h1>
<p className="text-gray-600 mt-2">Defina e acompanhe seus objetivos financeiros</p>
</div>
{isLoading ? (
<div className="flex justify-center py-10">
<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}
<Button
onClick={() => setShowForm(true)}
className="bg-blue-600 hover:bg-blue-700 text-white"
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
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>
<Plus className="h-4 w-4 mr-2" />
Nova Meta
</Button>
</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 Layout from '@/components/layout/Layout';
import TransactionsTable from '@/components/dashboard/TransactionsTable';
import { useTransactions } from '@/hooks/useTransactions';
import { TransactionHeader } from '@/components/transacoes/TransactionHeader';
@ -54,82 +53,80 @@ const TransacoesPage = () => {
};
return (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">Transações</h1>
<p className="text-sm text-muted-foreground">
Dados de: {formatMonthDisplay(selectedMonth)}
</p>
{/* Exibe mensagem para trial OU para admin-liberou */}
{!access.loading && access.podeAdicionarTransacao && (
access.adminLiberou ? (
<div className="mt-1 text-xs text-green-600 font-bold">
{access.motivo}
</div>
) : (access.diasRestantesTrial > 0 && (
<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.`}
</div>
))
)}
</div>
<MonthFilter
selectedMonth={selectedMonth}
onMonthChange={setSelectedMonth}
/>
<div className="space-y-6">
<div className="flex justify-between items-center">
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight">Transações</h1>
<p className="text-sm text-muted-foreground">
Dados de: {formatMonthDisplay(selectedMonth)}
</p>
{/* Exibe mensagem para trial OU para admin-liberou */}
{!access.loading && access.podeAdicionarTransacao && (
access.adminLiberou ? (
<div className="mt-1 text-xs text-green-600 font-bold">
{access.motivo}
</div>
) : (access.diasRestantesTrial > 0 && (
<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.`}
</div>
))
)}
</div>
{/* 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}
<MonthFilter
selectedMonth={selectedMonth}
onMonthChange={setSelectedMonth}
/>
</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 Layout from '@/components/layout/Layout';
import LoadingState from '@/components/whatsapp/LoadingState';
import WhatsAppHeader from '@/components/whatsapp/WhatsAppHeader';
import WhatsAppManager from '@/components/whatsapp/WhatsAppManager';
@ -85,41 +84,37 @@ const WhatsApp = () => {
if (checkingExistingInstance || isLoading) {
return (
<Layout>
<LoadingState message="Verificando suas instâncias..." />
</Layout>
<LoadingState message="Verificando suas instâncias..." />
);
}
return (
<Layout>
<div className="space-y-6">
<WhatsAppHeader
userEmail={userEmail}
onRefresh={recheckInstance}
isRefreshing={checkingExistingInstance}
/>
<WhatsAppManager
hasExistingInstance={hasExistingInstance}
existingInstanceData={existingInstanceData}
instances={instances}
isRefreshing={isRefreshing}
activeInstance={activeInstance}
qrDialogOpen={qrDialogOpen}
onInstanceCreated={handleInstanceCreated}
onViewQrCode={handleViewQrCode}
onDelete={handleDeleteInstanceWrapper}
onRestart={handleRestartInstance}
onLogout={handleLogoutInstance}
onDisconnect={handleDisconnectInstance}
onSetPresence={handleSetPresence}
onRefreshInstances={refreshInstances}
onStatusCheck={checkAllInstancesStatus}
setQrDialogOpen={setQrDialogOpen}
/>
</div>
</Layout>
<div className="space-y-6">
<WhatsAppHeader
userEmail={userEmail}
onRefresh={recheckInstance}
isRefreshing={checkingExistingInstance}
/>
<WhatsAppManager
hasExistingInstance={hasExistingInstance}
existingInstanceData={existingInstanceData}
instances={instances}
isRefreshing={isRefreshing}
activeInstance={activeInstance}
qrDialogOpen={qrDialogOpen}
onInstanceCreated={handleInstanceCreated}
onViewQrCode={handleViewQrCode}
onDelete={handleDeleteInstanceWrapper}
onRestart={handleRestartInstance}
onLogout={handleLogoutInstance}
onDisconnect={handleDisconnectInstance}
onSetPresence={handleSetPresence}
onRefreshInstances={refreshInstances}
onStatusCheck={checkAllInstancesStatus}
setQrDialogOpen={setQrDialogOpen}
/>
</div>
);
};