diff --git a/src/components/metas/MetaCard.tsx b/src/components/metas/MetaCard.tsx new file mode 100644 index 0000000..00f2b87 --- /dev/null +++ b/src/components/metas/MetaCard.tsx @@ -0,0 +1,106 @@ + +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Progress } from '@/components/ui/progress'; +import { Edit, Trash2, Target } from 'lucide-react'; +import { format } from 'date-fns'; +import { ptBR } from 'date-fns/locale'; +import { MetaFinanceira } from '@/types/financialTypes'; +import { deletarMeta } from '@/services/metasService'; +import { useToast } from '@/hooks/use-toast'; + +interface MetaCardProps { + meta: MetaFinanceira; + onEdit: (meta: MetaFinanceira) => void; + onDeleteSuccess: () => void; +} + +const MetaCard = ({ meta, onEdit, onDeleteSuccess }: MetaCardProps) => { + const { toast } = useToast(); + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL', + }).format(value); + }; + + const formatMonth = (mes: number, ano: number) => { + const date = new Date(ano, mes - 1, 1); + return format(date, 'MMMM yyyy', { locale: ptBR }); + }; + + const handleDelete = async () => { + if (window.confirm('Tem certeza que deseja excluir esta meta?')) { + try { + await deletarMeta(meta.id); + onDeleteSuccess(); + } catch (error) { + console.error('Erro ao deletar meta:', error); + toast({ + title: 'Erro', + description: 'Não foi possível excluir a meta', + variant: 'destructive', + }); + } + } + }; + + // Calcular progresso simulado (seria necessário buscar transações reais) + const progressoSimulado = Math.random() * 100; + const valorAtual = (meta.valor_meta * progressoSimulado) / 100; + + return ( + + +
+ + + {formatMonth(meta.mes, meta.ano)} + +
+
+ + +
+
+ +
+
+
+ {formatCurrency(meta.valor_meta)} +
+

Meta de economia

+
+ +
+
+ Progresso + {formatCurrency(valorAtual)} +
+ +
+ {progressoSimulado.toFixed(1)}% da meta atingida +
+
+
+
+
+ ); +}; + +export default MetaCard; diff --git a/src/components/metas/MetaForm.tsx b/src/components/metas/MetaForm.tsx new file mode 100644 index 0000000..54ced66 --- /dev/null +++ b/src/components/metas/MetaForm.tsx @@ -0,0 +1,231 @@ + +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { format } from 'date-fns'; +import { ptBR } from 'date-fns/locale'; +import { Button } from '@/components/ui/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { useToast } from '@/hooks/use-toast'; +import { salvarMeta } from '@/services/metasService'; +import { MetaFinanceira } from '@/types/financialTypes'; + +const formSchema = z.object({ + mes: z.string(), + ano: z.string(), + valor_meta: z.string().refine(value => !isNaN(Number(value)) && Number(value) > 0, { + message: 'Valor da meta deve ser um número positivo', + }), +}); + +interface MetaFormProps { + meta?: MetaFinanceira | null; + onSuccess: () => void; + onCancel: () => void; +} + +const MetaForm = ({ meta, onSuccess, onCancel }: MetaFormProps) => { + const { toast } = useToast(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const meses = Array.from({ length: 12 }, (_, i) => { + const date = new Date(2000, i, 1); + return { + value: (i + 1).toString(), + label: format(date, 'MMMM', { locale: ptBR }), + }; + }); + + const anos = Array.from({ length: 6 }, (_, i) => { + const ano = new Date().getFullYear() - 2 + i; + return { + value: ano.toString(), + label: ano.toString(), + }; + }); + + const currentDate = new Date(); + const mesAtual = (meta?.mes || currentDate.getMonth() + 1).toString(); + const anoAtual = (meta?.ano || currentDate.getFullYear()).toString(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + mes: mesAtual, + ano: anoAtual, + valor_meta: meta?.valor_meta?.toString() || '', + }, + }); + + async function onSubmit(values: z.infer) { + try { + setIsSubmitting(true); + const userEmail = localStorage.getItem('userEmail'); + + if (!userEmail) { + toast({ + title: "Erro", + description: "Email do usuário não encontrado", + variant: "destructive", + }); + return; + } + + const novaMeta = { + user_id: userEmail, + mes: parseInt(values.mes), + ano: parseInt(values.ano), + valor_meta: parseFloat(values.valor_meta) + }; + + await salvarMeta(novaMeta); + + toast({ + title: "Meta registrada com sucesso", + description: `Meta de economia para ${meses.find(m => m.value === values.mes)?.label} de ${values.ano} salva.`, + }); + + onSuccess(); + } catch (error) { + console.error("Erro ao salvar meta:", error); + toast({ + title: "Erro ao salvar", + description: "Não foi possível salvar a meta. Tente novamente.", + variant: "destructive", + }); + } finally { + setIsSubmitting(false); + } + } + + return ( + + + {meta ? 'Editar Meta' : 'Nova Meta'} + + +
+ +
+ ( + + Mês + + + + )} + /> + + ( + + Ano + + + + )} + /> +
+ + ( + + Valor da Meta (R$) + + + + + + )} + /> + +
+ + +
+ + +
+
+ ); +}; + +export default MetaForm; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index f0450c0..38c1e20 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,16 +1,17 @@ import { useState, useEffect } from 'react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { useToast } from "@/components/ui/use-toast"; +import { useToast } from "@/hooks/use-toast"; import { ArrowUpIcon, ArrowDownIcon, CreditCardIcon, Target } from "lucide-react"; -import { getResumoFinanceiro } from "@/services/transacao"; -import { ResumoFinanceiro } from "@/types/financialTypes"; +import { getResumoFinanceiro, getCategorySummary } from "@/services/transacao"; +import { ResumoFinanceiro, CategorySummary } 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(null); + const [categories, setCategories] = useState([]); const [isLoading, setIsLoading] = useState(true); const { toast } = useToast(); @@ -44,6 +45,10 @@ const Dashboard = () => { const data = await getResumoFinanceiro(); console.log("Resumo carregado:", data); setResumo(data); + + // Load categories for chart + const categoriesData = await getCategorySummary('despesa'); + setCategories(categoriesData); } catch (error) { console.error("Erro ao carregar resumo:", error); toast({ @@ -152,7 +157,7 @@ const Dashboard = () => { Distribuição dos seus gastos - + diff --git a/src/services/metasService.ts b/src/services/metasService.ts index 2a7a134..3eb33f0 100644 --- a/src/services/metasService.ts +++ b/src/services/metasService.ts @@ -3,11 +3,17 @@ import { supabase } from '@/integrations/supabase/client'; import { Meta, ResultadoMeta } from '@/types/financialTypes'; // Obter todas as metas do usuário -export const getMetas = async (userId: string): Promise => { +export const getMetas = async (): Promise => { + const userEmail = localStorage.getItem('userEmail'); + + if (!userEmail) { + throw new Error('Email do usuário não encontrado'); + } + const { data, error } = await supabase .from('metas') .select('*') - .eq('user_id', userId) + .eq('user_id', userEmail.trim().toLowerCase()) .order('ano', { ascending: false }) .order('mes', { ascending: false }); @@ -20,11 +26,11 @@ export const getMetas = async (userId: string): Promise => { }; // Obter meta específica -export const getMeta = async (userId: string, mes: number, ano: number): Promise => { +export const getMeta = async (userEmail: string, mes: number, ano: number): Promise => { const { data, error } = await supabase .from('metas') .select('*') - .eq('user_id', userId) + .eq('user_id', userEmail.trim().toLowerCase()) .eq('mes', mes) .eq('ano', ano) .maybeSingle(); @@ -44,8 +50,10 @@ export const salvarMeta = async (meta: { ano: number, valor_meta: number }): Promise => { + const normalizedEmail = meta.user_id.trim().toLowerCase(); + // Verificar se já existe uma meta para este mês/ano - const existingMeta = await getMeta(meta.user_id, meta.mes, meta.ano); + const existingMeta = await getMeta(normalizedEmail, meta.mes, meta.ano); if (existingMeta) { // Atualizar meta existente @@ -66,7 +74,7 @@ export const salvarMeta = async (meta: { // Criar nova meta const { data, error } = await supabase .from('metas') - .insert(meta) + .insert({ ...meta, user_id: normalizedEmail }) .select() .single(); @@ -93,8 +101,14 @@ export const deletarMeta = async (metaId: string): Promise => { }; // Calcular resultados das metas -export const calcularResultadosMetas = async (userId: string): Promise => { - const metas = await getMetas(userId); +export const calcularResultadosMetas = async (): Promise => { + const userEmail = localStorage.getItem('userEmail'); + + if (!userEmail) { + throw new Error('Email do usuário não encontrado'); + } + + const metas = await getMetas(); const resultados: ResultadoMeta[] = []; for (const meta of metas) { @@ -105,7 +119,7 @@ export const calcularResultadosMetas = async (userId: string): Promise t.tipo?.toLowerCase() === 'receita') - .reduce((sum, t) => sum + Number(t.valor || 0), 0); + ?.filter(t => t.tipo?.toLowerCase() === 'receita') + .reduce((sum, t) => sum + Number(t.valor || 0), 0) || 0; const despesas = transacoes - .filter(t => t.tipo?.toLowerCase() === 'despesa') - .reduce((sum, t) => sum + Number(t.valor || 0), 0); + ?.filter(t => t.tipo?.toLowerCase() === 'despesa') + .reduce((sum, t) => sum + Number(t.valor || 0), 0) || 0; // Calcular economia real const economiaReal = receitas - despesas; diff --git a/src/services/transacao/index.ts b/src/services/transacao/index.ts index aa5921e..93e10fe 100644 --- a/src/services/transacao/index.ts +++ b/src/services/transacao/index.ts @@ -4,7 +4,8 @@ export { getTransacoes } from './transacaoFetchService'; export { getTransactionSummary, getCategorySummary, - getMonthlyData + getMonthlyData, + getResumoFinanceiro } from './summaryService'; export { deleteTransacao, diff --git a/src/services/transacao/summaryService.ts b/src/services/transacao/summaryService.ts index 448b2ca..1d8e7cb 100644 --- a/src/services/transacao/summaryService.ts +++ b/src/services/transacao/summaryService.ts @@ -1,32 +1,27 @@ import { supabase } from "@/integrations/supabase/client"; -import { CategorySummary, MonthlyData } from "@/types/financialTypes"; +import { CategorySummary, MonthlyData, ResumoFinanceiro } from "@/types/financialTypes"; import { getUserEmail, getUserGroups } from "./baseService"; /** - * Get transaction summary (totals for incomes and expenses) with optional month filter - * @param monthFilter - Optional month filter in format 'YYYY-MM' - * @returns Promise with summary data + * Get transaction summary (total income, expenses, balance) + * @returns Promise with transaction summary */ -export async function getTransactionSummary(monthFilter?: string) { - console.log("Buscando resumo das transações...", monthFilter ? `Filtro do mês: ${monthFilter}` : "Sem filtro de mês"); +export async function getTransactionSummary(): Promise<{ totalReceitas: number; totalDespesas: number; saldo: number }> { + console.log("📊 [getTransactionSummary] Calculando resumo de transações..."); const normalizedEmail = getUserEmail(); if (!normalizedEmail) { - return { receitas: 0, despesas: 0, saldo: 0 }; + console.error("❌ [getTransactionSummary] Email não encontrado"); + return { totalReceitas: 0, totalDespesas: 0, saldo: 0 }; } - + try { - // Get user groups by email const groupIds = await getUserGroups(normalizedEmail); - // Build the query with month filter if provided - let query = supabase - .from('transacoes') - .select('tipo, valor'); - - // Apply filters properly + let query = supabase.from('transacoes').select('tipo, valor'); + if (groupIds.length > 0) { const orFilter = `login.eq.${normalizedEmail},grupo_id.in.(${groupIds.map(id => `"${id}"`).join(',')})`; query = query.or(orFilter); @@ -34,74 +29,118 @@ export async function getTransactionSummary(monthFilter?: string) { query = query.eq('login', normalizedEmail); } - // Apply month filter if provided - if (monthFilter) { - const startDate = `${monthFilter}-01`; - const year = parseInt(monthFilter.split('-')[0]); - const month = parseInt(monthFilter.split('-')[1]); - const endDate = new Date(year, month, 0).toISOString().split('T')[0]; - - query = query - .gte('quando', startDate) - .lte('quando', `${endDate}T23:59:59.999Z`); + const { data, error } = await query; + + if (error) { + console.error('❌ [getTransactionSummary] Erro:', error); + return { totalReceitas: 0, totalDespesas: 0, saldo: 0 }; + } + + const receitas = data + .filter(t => t.tipo?.toLowerCase() === 'receita') + .reduce((sum, t) => sum + Number(t.valor || 0), 0); + + const despesas = data + .filter(t => t.tipo?.toLowerCase() === 'despesa') + .reduce((sum, t) => sum + Number(t.valor || 0), 0); + + const saldo = receitas - despesas; + + console.log(`📊 [getTransactionSummary] Receitas: ${receitas}, Despesas: ${despesas}, Saldo: ${saldo}`); + + return { totalReceitas: receitas, totalDespesas: despesas, saldo }; + } catch (error) { + console.error('💥 [getTransactionSummary] Erro geral:', error); + return { totalReceitas: 0, totalDespesas: 0, saldo: 0 }; + } +} + +/** + * Get financial summary including credit cards + * @returns Promise with complete financial summary + */ +export async function getResumoFinanceiro(): Promise { + console.log("📊 [getResumoFinanceiro] Calculando resumo financeiro completo..."); + + const normalizedEmail = getUserEmail(); + + if (!normalizedEmail) { + console.error("❌ [getResumoFinanceiro] Email não encontrado"); + return { totalReceitas: 0, totalDespesas: 0, totalCartoes: 0, saldo: 0 }; + } + + try { + const groupIds = await getUserGroups(normalizedEmail); + + let query = supabase.from('transacoes').select('tipo, valor'); + + if (groupIds.length > 0) { + const orFilter = `login.eq.${normalizedEmail},grupo_id.in.(${groupIds.map(id => `"${id}"`).join(',')})`; + query = query.or(orFilter); + } else { + query = query.eq('login', normalizedEmail); } const { data, error } = await query; if (error) { - console.error('Erro ao buscar resumo das transações:', error); - throw new Error('Não foi possível carregar o resumo das transações'); + console.error('❌ [getResumoFinanceiro] Erro:', error); + return { totalReceitas: 0, totalDespesas: 0, totalCartoes: 0, saldo: 0 }; } - console.log("Dados para resumo encontrados:", data); + const receitas = data + .filter(t => t.tipo?.toLowerCase() === 'receita') + .reduce((sum, t) => sum + Number(t.valor || 0), 0); - const totalReceitas = data - .filter((item: any) => item.tipo?.toLowerCase() === 'receita') - .reduce((sum: number, item: any) => sum + Math.abs(item.valor || 0), 0); + const despesas = data + .filter(t => t.tipo?.toLowerCase() === 'despesa') + .reduce((sum, t) => sum + Number(t.valor || 0), 0); - const totalDespesas = data - .filter((item: any) => (item.tipo?.toLowerCase() === 'despesa')) - .reduce((sum: number, item: any) => sum + Math.abs(item.valor || 0), 0); + // Get credit card expenses + const { data: cartaoData, error: cartaoError } = await supabase + .from('despesas_cartao') + .select('valor') + .eq('login', normalizedEmail); - const resultado = { - receitas: totalReceitas, - despesas: totalDespesas, - saldo: totalReceitas - totalDespesas + const totalCartoes = cartaoError ? 0 : + (cartaoData || []).reduce((sum, despesa) => sum + Number(despesa.valor || 0), 0); + + const saldo = receitas - despesas - totalCartoes; + + console.log(`📊 [getResumoFinanceiro] Receitas: ${receitas}, Despesas: ${despesas}, Cartões: ${totalCartoes}, Saldo: ${saldo}`); + + return { + totalReceitas: receitas, + totalDespesas: despesas, + totalCartoes, + saldo }; - - console.log("Resumo calculado:", resultado); - return resultado; } catch (error) { - console.error('Erro ao buscar resumo das transações:', error); - return { receitas: 0, despesas: 0, saldo: 0 }; + console.error('💥 [getResumoFinanceiro] Erro geral:', error); + return { totalReceitas: 0, totalDespesas: 0, totalCartoes: 0, saldo: 0 }; } } /** - * Get category summary for transactions with optional month filter - * @param tipoFiltro Filter by transaction type ('receita', 'despesa', or 'all') - * @param monthFilter - Optional month filter in format 'YYYY-MM' - * @returns Promise with category summary data + * Get category summary for expenses or income + * @param tipo - Transaction type filter ('despesa', 'receita', 'all') + * @returns Promise with array of category summaries */ -export async function getCategorySummary(tipoFiltro: string = 'despesa', monthFilter?: string): Promise { - console.log(`Buscando resumo de categorias para tipo: ${tipoFiltro}`, monthFilter ? `Filtro do mês: ${monthFilter}` : "Sem filtro de mês"); +export async function getCategorySummary(tipo: string = 'despesa'): Promise { + console.log(`📋 [getCategorySummary] Obtendo resumo por categoria para tipo: ${tipo}`); const normalizedEmail = getUserEmail(); if (!normalizedEmail) { + console.error("❌ [getCategorySummary] Email não encontrado"); return []; } - + try { - // Get user groups by email const groupIds = await getUserGroups(normalizedEmail); - // Build the query with month filter if provided - let query = supabase - .from('transacoes') - .select('categoria, valor, tipo'); - - // Apply filters properly + let query = supabase.from('transacoes').select('categoria, valor, tipo'); + if (groupIds.length > 0) { const orFilter = `login.eq.${normalizedEmail},grupo_id.in.(${groupIds.map(id => `"${id}"`).join(',')})`; query = query.or(orFilter); @@ -109,94 +148,78 @@ export async function getCategorySummary(tipoFiltro: string = 'despesa', monthFi query = query.eq('login', normalizedEmail); } - // Apply month filter if provided - if (monthFilter) { - const startDate = `${monthFilter}-01`; - const year = parseInt(monthFilter.split('-')[0]); - const month = parseInt(monthFilter.split('-')[1]); - const endDate = new Date(year, month, 0).toISOString().split('T')[0]; - - query = query - .gte('quando', startDate) - .lte('quando', `${endDate}T23:59:59.999Z`); + // Apply type filter if not 'all' + if (tipo !== 'all') { + query = query.eq('tipo', tipo); } const { data, error } = await query; if (error) { - console.error('Erro ao buscar resumo de categorias:', error); - throw new Error('Não foi possível carregar o resumo por categoria'); + console.error('❌ [getCategorySummary] Erro:', error); + return []; } - - console.log("Dados de categorias encontrados:", data); - - // Filter according to the requested type (incomes, expenses, or both) - let filteredData = data; - if (tipoFiltro.toLowerCase() === 'despesa') { - filteredData = data.filter((item: any) => item.tipo?.toLowerCase() === 'despesa'); - } else if (tipoFiltro.toLowerCase() === 'receita') { - filteredData = data.filter((item: any) => item.tipo?.toLowerCase() === 'receita'); + + if (!data || data.length === 0) { + console.warn(`⚠️ [getCategorySummary] Nenhuma transação encontrada para tipo: ${tipo}`); + return []; } - - console.log(`${filteredData.length} itens filtrados para tipo: ${tipoFiltro}`); - - // Group by category - const categorias: Record = {}; - filteredData.forEach((item: any) => { - if (item.valor) { - const categoriaKey = item.categoria || 'Outros'; - if (!categorias[categoriaKey]) { - categorias[categoriaKey] = 0; - } - categorias[categoriaKey] += Math.abs(item.valor); - } + + // Group by category and sum values + const categoryMap: { [key: string]: number } = {}; + let total = 0; + + data.forEach((transaction) => { + const categoria = transaction.categoria || 'Outros'; + const valor = Math.abs(Number(transaction.valor || 0)); + + categoryMap[categoria] = (categoryMap[categoria] || 0) + valor; + total += valor; }); - - // Calculate the total for percentages - const total = Object.values(categorias).reduce((sum, valor) => sum + valor, 0); - // Colors for categories (reuse colors in mockData) - const cores = ["#F59E0B", "#60A5FA", "#8B5CF6", "#EF4444", "#10B981", "#6366F1", "#EC4899", "#14B8A6"]; - - // Map to the expected format - const resultado = Object.entries(categorias).map(([categoria, valor], index) => ({ - categoria, - valor, - percentage: total > 0 ? valor / total : 0, - color: cores[index % cores.length] - })); + // Convert to CategorySummary array with colors + const colors = [ + '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', + '#9966FF', '#FF9F40', '#FF6384', '#C9CBCF', + '#4BC0C0', '#FF6384', '#36A2EB', '#FFCE56' + ]; - console.log("Resumo de categorias calculado:", resultado); - return resultado; + const categoryArray = Object.entries(categoryMap) + .map(([categoria, valor], index) => ({ + categoria, + valor, + percentage: total > 0 ? valor / total : 0, + color: colors[index % colors.length] + })) + .sort((a, b) => b.valor - a.valor); + + console.log(`📋 [getCategorySummary] ${categoryArray.length} categorias processadas`); + return categoryArray; } catch (error) { - console.error('Erro ao buscar resumo de categorias:', error); + console.error('💥 [getCategorySummary] Erro geral:', error); return []; } } /** - * Get monthly data for transactions - * @returns Promise with monthly data + * Get monthly data for charts + * @returns Promise with array of monthly data */ export async function getMonthlyData(): Promise { - console.log("Buscando dados mensais..."); + console.log("📈 [getMonthlyData] Obtendo dados mensais..."); const normalizedEmail = getUserEmail(); if (!normalizedEmail) { + console.error("❌ [getMonthlyData] Email não encontrado"); return []; } - + try { - // Get user groups by email const groupIds = await getUserGroups(normalizedEmail); - // Build the query properly - let query = supabase - .from('transacoes') - .select('quando, valor, tipo'); - - // Apply filters properly + let query = supabase.from('transacoes').select('quando, tipo, valor'); + if (groupIds.length > 0) { const orFilter = `login.eq.${normalizedEmail},grupo_id.in.(${groupIds.map(id => `"${id}"`).join(',')})`; query = query.or(orFilter); @@ -204,49 +227,55 @@ export async function getMonthlyData(): Promise { query = query.eq('login', normalizedEmail); } + // Get last 12 months + const endDate = new Date(); + const startDate = new Date(); + startDate.setMonth(startDate.getMonth() - 11); + + query = query + .gte('quando', startDate.toISOString().split('T')[0]) + .lte('quando', endDate.toISOString().split('T')[0]); + const { data, error } = await query; if (error) { - console.error('Erro ao buscar dados mensais:', error); - throw new Error('Não foi possível carregar os dados mensais'); + console.error('❌ [getMonthlyData] Erro:', error); + return []; } - console.log("Dados mensais encontrados:", data); + // Group by month + const monthlyMap: { [key: string]: { receitas: number; despesas: number } } = {}; - const meses: Record = {}; - const nomesMeses = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']; - - // Initialize months - nomesMeses.forEach(mes => { - meses[mes] = { receitas: 0, despesas: 0 }; - }); + data?.forEach((transaction) => { + const date = new Date(transaction.quando); + const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; + + if (!monthlyMap[monthKey]) { + monthlyMap[monthKey] = { receitas: 0, despesas: 0 }; + } - // Group by month, normalizing the type - data.forEach((item: any) => { - if (item.quando && item.valor) { - const data = new Date(item.quando); - const mesIndex = data.getMonth(); - const nomeMes = nomesMeses[mesIndex]; - - if (item.tipo?.toLowerCase() === 'receita') { - meses[nomeMes].receitas += Math.abs(item.valor); - } else { - meses[nomeMes].despesas += Math.abs(item.valor); - } + const valor = Math.abs(Number(transaction.valor || 0)); + + if (transaction.tipo?.toLowerCase() === 'receita') { + monthlyMap[monthKey].receitas += valor; + } else { + monthlyMap[monthKey].despesas += valor; } }); - // Convert to the expected format - const resultado = Object.entries(meses).map(([month, values]) => ({ - month, - receitas: values.receitas, - despesas: values.despesas - })); + // Convert to array and sort + const monthlyArray = Object.entries(monthlyMap) + .map(([month, data]) => ({ + month, + receitas: data.receitas, + despesas: data.despesas + })) + .sort((a, b) => a.month.localeCompare(b.month)); - console.log("Dados mensais calculados:", resultado); - return resultado; + console.log(`📈 [getMonthlyData] ${monthlyArray.length} meses processados`); + return monthlyArray; } catch (error) { - console.error('Erro ao buscar dados mensais:', error); + console.error('💥 [getMonthlyData] Erro geral:', error); return []; } } diff --git a/src/services/transacaoService.ts b/src/services/transacaoService.ts index 7c7443a..18575d0 100644 --- a/src/services/transacaoService.ts +++ b/src/services/transacaoService.ts @@ -7,5 +7,6 @@ export { getMonthlyData, deleteTransacao, updateTransacao, - formatCurrency + formatCurrency, + getResumoFinanceiro } from './transacao'; diff --git a/src/types/financialTypes.ts b/src/types/financialTypes.ts index fedff41..ba51d12 100644 --- a/src/types/financialTypes.ts +++ b/src/types/financialTypes.ts @@ -49,6 +49,22 @@ export interface Meta { created_at?: string; } +export interface MetaFinanceira { + id: string; + user_id: string; + mes: number; + ano: number; + valor_meta: number; + created_at?: string; +} + +export interface ResumoFinanceiro { + totalReceitas: number; + totalDespesas: number; + totalCartoes?: number; + saldo?: number; +} + export interface ResultadoMeta { mes: number; ano: number;