budget-view-finance/src/services/transacao/summaryService.ts
gpt-engineer-app[bot] 4e8972cb55 Enhance dashboard cards and charts
- Implemented year-long view for "Receitas vs Despesas" chart.
- Improved dashboard card design and functionality, including navigation to filtered transactions on click.
- Verified the accuracy of the "Despesa por Categoria" chart.
2025-06-24 18:38:44 +00:00

285 lines
9.2 KiB
TypeScript

import { supabase } from "@/integrations/supabase/client";
import { CategorySummary, MonthlyData, ResumoFinanceiro } from "@/types/financialTypes";
import { getUserEmail, getUserGroups } from "./baseService";
/**
* Get transaction summary (total income, expenses, balance)
* @returns Promise with transaction summary
*/
export async function getTransactionSummary(): Promise<{ totalReceitas: number; totalDespesas: number; saldo: number }> {
console.log("📊 [getTransactionSummary] Calculando resumo de transações...");
const normalizedEmail = getUserEmail();
if (!normalizedEmail) {
console.error("❌ [getTransactionSummary] Email não encontrado");
return { totalReceitas: 0, totalDespesas: 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('❌ [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<ResumoFinanceiro> {
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('❌ [getResumoFinanceiro] Erro:', error);
return { totalReceitas: 0, totalDespesas: 0, totalCartoes: 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);
// Get credit card expenses
const { data: cartaoData, error: cartaoError } = await supabase
.from('despesas_cartao')
.select('valor')
.eq('login', normalizedEmail);
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
};
} catch (error) {
console.error('💥 [getResumoFinanceiro] Erro geral:', error);
return { totalReceitas: 0, totalDespesas: 0, totalCartoes: 0, saldo: 0 };
}
}
/**
* 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(tipo: string = 'despesa'): Promise<CategorySummary[]> {
console.log(`📋 [getCategorySummary] Obtendo resumo por categoria para tipo: ${tipo}`);
const normalizedEmail = getUserEmail();
if (!normalizedEmail) {
console.error("❌ [getCategorySummary] Email não encontrado");
return [];
}
try {
const groupIds = await getUserGroups(normalizedEmail);
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);
} else {
query = query.eq('login', normalizedEmail);
}
// Apply type filter if not 'all'
if (tipo !== 'all') {
query = query.eq('tipo', tipo);
}
const { data, error } = await query;
if (error) {
console.error('❌ [getCategorySummary] Erro:', error);
return [];
}
if (!data || data.length === 0) {
console.warn(`⚠️ [getCategorySummary] Nenhuma transação encontrada para tipo: ${tipo}`);
return [];
}
// 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;
});
// Enhanced color palette with more distinct colors - avoiding white/light colors
const colors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9',
'#F8C471', '#82E0AA', '#F1948A', '#85C1E9', '#D7BDE2',
'#A3E4D7', '#FAD7A0', '#D5A6BD', '#AED6F1', '#A9DFBF',
'#FF8A80', '#80CBC4', '#81C784', '#FFB74D', '#F48FB1',
'#CE93D8', '#90CAF9', '#A5D6A7', '#FFCC02', '#FFAB40'
];
// Convert to CategorySummary array with colors
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('💥 [getCategorySummary] Erro geral:', error);
return [];
}
}
/**
* Get monthly data for charts - full year
* @returns Promise with array of monthly data
*/
export async function getMonthlyData(): Promise<MonthlyData[]> {
console.log("📈 [getMonthlyData] Obtendo dados mensais do ano completo...");
const normalizedEmail = getUserEmail();
if (!normalizedEmail) {
console.error("❌ [getMonthlyData] Email não encontrado");
return [];
}
try {
const groupIds = await getUserGroups(normalizedEmail);
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);
} else {
query = query.eq('login', normalizedEmail);
}
// Get full current year
const currentYear = new Date().getFullYear();
const startDate = new Date(currentYear, 0, 1); // January 1st
const endDate = new Date(currentYear, 11, 31); // December 31st
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('❌ [getMonthlyData] Erro:', error);
return [];
}
// Group by month
const monthlyMap: { [key: string]: { receitas: number; despesas: number } } = {};
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 };
}
const valor = Math.abs(Number(transaction.valor || 0));
if (transaction.tipo?.toLowerCase() === 'receita') {
monthlyMap[monthKey].receitas += valor;
} else {
monthlyMap[monthKey].despesas += valor;
}
});
// 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(`📈 [getMonthlyData] ${monthlyArray.length} meses processados`);
return monthlyArray;
} catch (error) {
console.error('💥 [getMonthlyData] Erro geral:', error);
return [];
}
}