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,7 +65,6 @@ 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>
@ -144,7 +142,6 @@ const CalendarioPage = () => {
</Card>
</div>
</div>
</Layout>
);
};

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,7 +106,6 @@ const CartoesCreditoPage = () => {
}, []);
return (
<Layout>
<div className="space-y-6">
<CartaoActions
isDetalhes={detalheCartaoAberto}
@ -132,7 +130,6 @@ const CartoesCreditoPage = () => {
/>
)}
</div>
</Layout>
);
};

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,7 +98,8 @@ 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">
<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>
@ -106,11 +107,13 @@ const CategoriasPage = () => {
<div className="h-4 bg-muted rounded w-1/4 mb-3"></div>
<div className="h-2 bg-muted rounded"></div>
</div>
</CardSpotlight>
</CardContent>
</Card>
))}
</div>
) : categories.length === 0 ? (
<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">
@ -121,14 +124,16 @@ const CategoriasPage = () => {
: 'Verifique se existem transações cadastradas'}
</p>
</div>
</CardSpotlight>
</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">
<Card key={category.categoria}>
<CardContent className="p-6">
<div className="pb-2">
<div className="text-base flex items-center font-semibold relative z-20">
<div className="text-base flex items-center font-semibold">
<span
className="w-3 h-3 rounded-full mr-2"
style={{ backgroundColor: category.color }}
@ -136,7 +141,7 @@ const CategoriasPage = () => {
{category.categoria}
</div>
</div>
<div className="relative z-20">
<div>
<div className="text-2xl font-bold mb-2">
{formatCurrency(category.valor)}
</div>
@ -154,15 +159,17 @@ const CategoriasPage = () => {
</div>
</div>
</div>
</CardSpotlight>
</CardContent>
</Card>
))}
</div>
<CardSpotlight className="mt-6">
<Card className="mt-6">
<CardContent className="p-6">
<div>
<h3 className="text-xl font-bold mb-4 relative z-20">Resumo de Categorias {tipoFiltro !== 'all' ? (tipoFiltro === 'despesa' ? '(Despesas)' : '(Receitas)') : ''}</h3>
<h3 className="text-xl font-bold mb-4">Resumo de Categorias {tipoFiltro !== 'all' ? (tipoFiltro === 'despesa' ? '(Despesas)' : '(Receitas)') : ''}</h3>
</div>
<div className="relative z-20">
<div>
<Table>
<TableHeader>
<TableRow>
@ -193,7 +200,8 @@ const CategoriasPage = () => {
</TableBody>
</Table>
</div>
</CardSpotlight>
</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,7 +64,6 @@ 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>
@ -89,7 +87,6 @@ const GruposWhatsApp = () => {
onDeleteGroup={handleDeleteGroup}
/>
</div>
</Layout>
);
};

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() {
const storedUserId = localStorage.getItem('userId');
const finDashUser = localStorage.getItem('finDashUser');
if (!storedUserId && finDashUser) {
localStorage.setItem('userId', finDashUser);
} else if (!storedUserId && !finDashUser) {
const defaultId = '9f267008-9128-4a2f-b730-de0a0b5602a9';
localStorage.setItem('userId', defaultId);
}
}, []);
const loadResumo = async () => {
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"
});
}
console.log("Carregando resumo financeiro...");
const data = await getResumoFinanceiro();
console.log("Resumo carregado:", data);
setResumo(data);
} catch (error) {
console.error("💥 [Dashboard] Erro ao carregar dados:", error);
console.error("Erro ao carregar resumo:", error);
toast({
title: "Erro ao carregar dados",
description: "Verifique a conexão com o Supabase",
title: "Erro ao carregar resumo",
description: "Não foi possível obter os dados do Supabase",
variant: "destructive"
});
} finally {
setIsLoading(false);
}
};
useEffect(() => {
loadResumo();
}, [toast]);
if (isLoading) {
return (
<div className="space-y-6">
<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>
);
}
loadData();
}, [selectedMonth, toast]);
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(value);
};
// Calculate total of all expenses (regular + credit cards)
const totalDespesasGeral = totals.despesas + totals.cartoes;
// 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>
<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 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>
{/* 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 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>
{/* ---- 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="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>
<div className="space-y-2">
<h2 className="text-xl font-semibold">Transações Recentes</h2>
<TransactionsTable transactions={transactions} isLoading={isLoading} />
</div>
</div>
{/* Tour de Onboarding */}
<OnboardingTour
isOpen={tourIsOpen}
currentStep={tourCurrentStep}
onNext={tourNextStep}
onSkip={skipTour}
onClose={closeTour}
<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={() => {}}
/>
</Layout>
</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);
}
};
const handleDeletarMeta = (mes: number, ano: number, id: string) => {
setMetaParaDeletar({ mes, ano, id });
setIsDeleteDialogOpen(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);
useEffect(() => {
loadMetas();
}, []);
const handleMetaSuccess = () => {
loadMetas();
setShowForm(false);
setEditingMeta(null);
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.`,
title: 'Sucesso',
description: editingMeta ? 'Meta atualizada com sucesso' : 'Meta criada com sucesso',
});
} catch (error) {
console.error('Erro ao deletar meta:', error);
};
const handleEditMeta = (meta: MetaFinanceira) => {
setEditingMeta(meta);
setShowForm(true);
};
const handleDeleteSuccess = () => {
loadMetas();
toast({
title: 'Erro ao deletar meta',
description: 'Não foi possível excluir a meta',
variant: 'destructive',
title: 'Sucesso',
description: 'Meta excluída com sucesso',
});
} 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 (
<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>
<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>
<Button
className="flex items-center gap-2 bg-finance-blue hover:bg-finance-blue/90"
onClick={handleNovaMeta}
onClick={() => setShowForm(true)}
className="bg-blue-600 hover:bg-blue-700 text-white"
>
<PlusCircle className="h-4 w-4" />
<Plus className="h-4 w-4 mr-2" />
Nova Meta
</Button>
</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);
}
});
}
{showForm && (
<MetaForm
meta={editingMeta}
onSuccess={handleMetaSuccess}
onCancel={() => {
setShowForm(false);
setEditingMeta(null);
}}
/>
))}
</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}
{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}
/>
</DialogContent>
</Dialog>
<AlertDialog
open={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
>
<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>
))}
</div>
)}
</div>
</Layout>
);
};

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,7 +53,6 @@ const TransacoesPage = () => {
};
return (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<div className="space-y-1">
@ -129,7 +127,6 @@ const TransacoesPage = () => {
onCloseCartaoCreditoDialog={handleCloseCartaoCreditoDialog}
/>
</div>
</Layout>
);
};

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,14 +84,11 @@ const WhatsApp = () => {
if (checkingExistingInstance || isLoading) {
return (
<Layout>
<LoadingState message="Verificando suas instâncias..." />
</Layout>
);
}
return (
<Layout>
<div className="space-y-6">
<WhatsAppHeader
userEmail={userEmail}
@ -119,7 +115,6 @@ const WhatsApp = () => {
setQrDialogOpen={setQrDialogOpen}
/>
</div>
</Layout>
);
};