import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { reportService, categoryService } from '../services/api'; import useFormatters from '../hooks/useFormatters'; import BalanceProjectionChart from '../components/dashboard/BalanceProjectionChart'; import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, LineElement, PointElement, ArcElement, Title, Tooltip, Legend, Filler, } from 'chart.js'; import { Bar, Line, Doughnut, Pie } from 'react-chartjs-2'; ChartJS.register( CategoryScale, LinearScale, BarElement, LineElement, PointElement, ArcElement, Title, Tooltip, Legend, Filler ); const Reports = () => { const { t } = useTranslation(); const { currency, formatDate } = useFormatters(); const [activeTab, setActiveTab] = useState('summary'); const [loading, setLoading] = useState(true); const [year, setYear] = useState(new Date().getFullYear()); const [months, setMonths] = useState(12); // Data states const [summary, setSummary] = useState(null); const [categoryData, setCategoryData] = useState(null); const [evolutionData, setEvolutionData] = useState(null); const [dayOfWeekData, setDayOfWeekData] = useState(null); const [topExpenses, setTopExpenses] = useState(null); const [projection, setProjection] = useState(null); const [comparison, setComparison] = useState(null); const [costCenterData, setCostCenterData] = useState(null); const [recurringData, setRecurringData] = useState(null); const [liabilitiesData, setLiabilitiesData] = useState(null); const [futureData, setFutureData] = useState(null); const [overdueData, setOverdueData] = useState(null); // Load data based on active tab const loadData = useCallback(async () => { setLoading(true); try { switch (activeTab) { case 'summary': const summaryRes = await reportService.getSummary({ year }); setSummary(summaryRes); break; case 'category': const catRes = await reportService.getByCategory({ type: 'debit' }); setCategoryData(catRes); break; case 'evolution': const evoRes = await reportService.getMonthlyEvolution({ months }); setEvolutionData(evoRes); break; case 'dayOfWeek': const dowRes = await reportService.getByDayOfWeek({ months: 6 }); setDayOfWeekData(dowRes); break; case 'topExpenses': const topRes = await reportService.getTopExpenses({ limit: 20 }); setTopExpenses(topRes); break; case 'projection': const projRes = await reportService.getProjection(); setProjection(projRes); break; case 'comparison': const compRes = await reportService.comparePeriods(); setComparison(compRes); break; case 'costCenter': const ccRes = await reportService.getByCostCenter(); setCostCenterData(ccRes); break; case 'recurring': const recRes = await reportService.getRecurringReport(); setRecurringData(recRes); break; case 'liabilities': const liabRes = await reportService.getLiabilities(); setLiabilitiesData(liabRes); break; case 'future': const futRes = await reportService.getFutureTransactions({ days: 30 }); setFutureData(futRes); break; case 'overdue': const overdueRes = await reportService.getOverdue(); setOverdueData(overdueRes); break; } } catch (error) { console.error('Error loading report data:', error); } finally { setLoading(false); } }, [activeTab, year, months]); useEffect(() => { loadData(); }, [loadData]); const tabs = [ { id: 'summary', label: t('reports.summary'), icon: 'bi-clipboard-data' }, { id: 'category', label: t('reports.byCategory'), icon: 'bi-pie-chart' }, { id: 'costCenter', label: t('reports.byCostCenter'), icon: 'bi-diagram-3' }, { id: 'evolution', label: t('reports.monthlyEvolution'), icon: 'bi-graph-up' }, { id: 'comparison', label: t('reports.comparison'), icon: 'bi-arrow-left-right' }, { id: 'topExpenses', label: t('reports.topExpenses'), icon: 'bi-sort-down' }, { id: 'projection', label: t('reports.projection'), icon: 'bi-lightning' }, { id: 'recurring', label: t('reports.recurring'), icon: 'bi-arrow-repeat' }, { id: 'liabilities', label: t('reports.liabilities'), icon: 'bi-credit-card' }, { id: 'future', label: t('reports.futureTransactions'), icon: 'bi-calendar-plus' }, { id: 'overdue', label: t('reports.overdue'), icon: 'bi-exclamation-triangle' }, ]; // Chart options const chartOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: '#94a3b8' } } }, scales: { x: { ticks: { color: '#94a3b8' }, grid: { color: 'rgba(148, 163, 184, 0.1)' } }, y: { ticks: { color: '#94a3b8' }, grid: { color: 'rgba(148, 163, 184, 0.1)' } } } }; const doughnutOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: '#94a3b8', padding: 15, font: { size: 11 } } } } }; // Render Summary Tab const renderSummary = () => { if (!summary) return null; const savingsRate = summary.current.income > 0 ? ((summary.current.balance / summary.current.income) * 100).toFixed(1) : 0; const monthlyAvgIncome = (summary.current.income / 12).toFixed(2); const monthlyAvgExpense = (summary.current.expense / 12).toFixed(2); const monthlyAvgBalance = (summary.current.balance / 12).toFixed(2); return (
{/* Year Selector */}
{t('reports.annualSummary')}
{[2024, 2025].map(y => ( ))}
{/* Main KPI Cards */}
{t('reports.income')}

{currency(summary.current.income, summary.currency)}

{summary.variation.income !== 0 && !isNaN(summary.variation.income) && isFinite(summary.variation.income) && (
= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}> = 0 ? 'up' : 'down'} me-1`}> {Math.abs(summary.variation.income).toFixed(1)}% {t('reports.vsLastYear')}
)}
{t('reports.monthlyAverage')}: {currency(monthlyAvgIncome, summary.currency)}
{t('reports.expenses')}

{currency(summary.current.expense, summary.currency)}

{summary.variation.expense !== 0 && !isNaN(summary.variation.expense) && isFinite(summary.variation.expense) && (
= 0 ? 'up' : 'down'} me-1`}> {Math.abs(summary.variation.expense).toFixed(1)}% {t('reports.vsLastYear')}
)}
{t('reports.monthlyAverage')}: {currency(monthlyAvgExpense, summary.currency)}
{t('reports.balance')}

{currency(summary.current.balance, summary.currency)}

{summary.variation.balance !== 0 && !isNaN(summary.variation.balance) && isFinite(summary.variation.balance) && (
= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}> = 0 ? 'up' : 'down'} me-1`}> {Math.abs(summary.variation.balance).toFixed(1)}% {t('reports.vsLastYear')}
)}
{t('reports.monthlyAverage')}: {currency(monthlyAvgBalance, summary.currency)}
{t('reports.savingsRate')}

{savingsRate}%

{savingsRate >= 20 ? '🎯 ' + t('reports.excellentSavings') : savingsRate >= 10 ? '👍 ' + t('reports.goodSavings') : '💡 ' + t('reports.canImprove')}
{/* Comparison Chart */}
{t('reports.yearComparison')} - {year-1} vs {year}
{t('reports.total')}: {currency(summary.current.balance, summary.currency)}
); }; // Render Category Tab const renderCategory = () => { if (!categoryData) return null; const colors = categoryData.data.map((_, i) => `hsl(${(i * 360) / categoryData.data.length}, 70%, 50%)` ); return (
{t('reports.expenseDistribution')}
c.category_name), datasets: [{ data: categoryData.data.map(c => c.total), backgroundColor: colors, borderWidth: 0, }], }} options={doughnutOptions} />
{t('reports.categoryDetail')}
{currency(categoryData.total, categoryData.currency)}
{categoryData.data.map((cat, i) => ( ))}
{t('reports.category')} {t('common.total')} %
{cat.category_name} {currency(cat.total, categoryData.currency)} {cat.percentage}%
); }; // Render Evolution Tab const renderEvolution = () => { if (!evolutionData) return null; return (
{[6, 12, 24].map(m => ( ))}
{/* Averages Cards */}
{t('reports.avgIncome')}
{currency(evolutionData.averages.income, evolutionData.currency)}
{t('reports.avgExpense')}
{currency(evolutionData.averages.expense, evolutionData.currency)}
{t('reports.balance')}
= 0 ? 'text-success' : 'text-danger'}`}> {currency(evolutionData.averages.balance, evolutionData.currency)}
{t('reports.savingsRate')}
{evolutionData.averages.savings_rate}%
{/* Evolution Chart */}
{t('reports.monthlyEvolution')}
d.month_label), datasets: [ { label: t('reports.income'), data: evolutionData.data.map(d => d.income), borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', fill: true, tension: 0.3, }, { label: t('reports.expenses'), data: evolutionData.data.map(d => d.expense), borderColor: '#ef4444', backgroundColor: 'rgba(239, 68, 68, 0.1)', fill: true, tension: 0.3, }, { label: t('reports.balance'), data: evolutionData.data.map(d => d.balance), borderColor: '#3b82f6', backgroundColor: 'transparent', borderDash: [5, 5], tension: 0.3, }, ], }} options={chartOptions} />
{/* Savings Rate Chart */}
{t('reports.savingsRate')} por mes
d.month_label), datasets: [{ label: t('reports.savingsRate'), data: evolutionData.data.map(d => d.savings_rate), backgroundColor: evolutionData.data.map(d => d.savings_rate >= 20 ? 'rgba(16, 185, 129, 0.7)' : d.savings_rate >= 10 ? 'rgba(245, 158, 11, 0.7)' : d.savings_rate >= 0 ? 'rgba(239, 68, 68, 0.5)' : 'rgba(239, 68, 68, 0.8)' ), borderRadius: 4, }], }} options={{ ...chartOptions, plugins: { ...chartOptions.plugins, legend: { display: false } } }} />
); }; // Render Comparison Tab const renderComparison = () => { if (!comparison) return null; return (
{/* Header com seletor de períodos */}
{t('reports.periodComparison')}
{/* Cards KPI Comparativos */}
{t('reports.income')}

{currency(comparison.period1.income, comparison.currency)}

{comparison.variation.income !== 0 && !isNaN(comparison.variation.income) && isFinite(comparison.variation.income) && ( <> = 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}> = 0 ? 'up' : 'down'} me-1`}> {comparison.variation.income > 0 ? '+' : ''}{Math.abs(comparison.variation.income).toFixed(1)}% {t('reports.vsPreviousPeriod')} )}

{comparison.period2.label}: {currency(comparison.period2.income, comparison.currency)}
{t('reports.expenses')}

{currency(comparison.period1.expense, comparison.currency)}

{comparison.variation.expense !== 0 && !isNaN(comparison.variation.expense) && isFinite(comparison.variation.expense) && ( <> = 0 ? 'up' : 'down'} me-1`}> {comparison.variation.expense > 0 ? '+' : ''}{Math.abs(comparison.variation.expense).toFixed(1)}% {t('reports.vsPreviousPeriod')} )}

{comparison.period2.label}: {currency(comparison.period2.expense, comparison.currency)}
{t('reports.balance')}

{currency(comparison.period1.balance, comparison.currency)}

{comparison.variation.balance !== 0 && !isNaN(comparison.variation.balance) && isFinite(comparison.variation.balance) && ( <> = 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}> = 0 ? 'up' : 'down'} me-1`}> {comparison.variation.balance > 0 ? '+' : ''}{Math.abs(comparison.variation.balance).toFixed(1)}% {t('reports.vsPreviousPeriod')} )}

{comparison.period2.label}: {currency(comparison.period2.balance, comparison.currency)}
{/* Tabela Comparativa Detalhada */}
{t('reports.detailedComparison')}
{t('reports.metric')} {comparison.period2.label} {comparison.period1.label} {t('reports.variation')}
{t('reports.income')} {currency(comparison.period2.income, comparison.currency)} {currency(comparison.period1.income, comparison.currency)} {comparison.variation.income !== 0 && !isNaN(comparison.variation.income) && isFinite(comparison.variation.income) && ( = 0 ? 'bg-success' : 'bg-danger'}`}> {comparison.variation.income > 0 ? '+' : ''}{comparison.variation.income.toFixed(1)}% )}
{t('reports.expenses')} {currency(comparison.period2.expense, comparison.currency)} {currency(comparison.period1.expense, comparison.currency)} {comparison.variation.expense !== 0 && !isNaN(comparison.variation.expense) && isFinite(comparison.variation.expense) && ( {comparison.variation.expense > 0 ? '+' : ''}{comparison.variation.expense.toFixed(1)}% )}
{t('reports.balance')} = 0 ? 'text-success' : 'text-danger'}`}> {currency(comparison.period2.balance, comparison.currency)} = 0 ? 'text-success' : 'text-danger'}`}> {currency(comparison.period1.balance, comparison.currency)} {comparison.variation.balance !== 0 && !isNaN(comparison.variation.balance) && isFinite(comparison.variation.balance) && ( = 0 ? 'bg-success' : 'bg-danger'}`}> {comparison.variation.balance > 0 ? '+' : ''}{comparison.variation.balance.toFixed(1)}% )}
{/* Gráfico de Barras Agrupadas */}
{t('reports.visualComparison')}
); }; // Render Top Expenses Tab const renderTopExpenses = () => { if (!topExpenses) return null; return (
{t('reports.top20Expenses')}
{currency(topExpenses.total, topExpenses.currency)}
{topExpenses.data.map((item, i) => ( ))}
# {t('reports.description')} {t('reports.category')} {t('reports.date')} {t('reports.amount')}
{i + 1} {item.description} {item.category || '-'} {item.date} {currency(item.amount, item.currency || topExpenses.currency)}
); }; // Render Projection Tab const renderProjection = () => { return (
{/* Balance Projection Chart - Full Width First */}
{projection && ( <>
{t('reports.currentMonth')}
{t('reports.income')} {currency(projection.current_month.income, projection.currency)}
{t('reports.expenses')} {currency(projection.current_month.expense, projection.currency)}

{t('reports.daysRemaining')} {projection.current_month.days_remaining} {t('common.days')}
{t('reports.projectionTitle')}
{t('reports.projectedIncome')} {currency(projection.projection.income, projection.currency)}
{t('reports.projectedExpense')} {currency(projection.projection.expense, projection.currency)}

{t('reports.balance')} = 0 ? '' : 'text-warning'}`}> {currency(projection.projection.balance, projection.currency)}
{/* vs Average */}
{t('reports.vsAverage')} ({t('reports.last3Months')})
)}
); }; // Render Day of Week Tab const renderDayOfWeek = () => { if (!dayOfWeekData || !dayOfWeekData.data) return null; const data = dayOfWeekData.data; return (
{t('reports.expensesByDayOfWeek')}
t(`reports.dayOfWeek.${d.day_key}`)), datasets: [{ label: t('reports.totalSpent'), data: data.map(d => d.total), backgroundColor: data.map(d => d.day_num === 1 || d.day_num === 7 ? 'rgba(245, 158, 11, 0.7)' : 'rgba(59, 130, 246, 0.7)' ), borderRadius: 4, }], }} options={{ ...chartOptions, plugins: { ...chartOptions.plugins, legend: { display: false } } }} />
{dayOfWeekData.data.map(d => ( ))}
{t('reports.dayOfWeek.day')} {t('transactions.title')} {t('common.total')} {t('reports.avgExpense')}
{t(`reports.dayOfWeek.${d.day_key}`)} {d.count} {currency(d.total, dayOfWeekData.currency)} {currency(d.average, dayOfWeekData.currency)}
); }; // Render Cost Center Tab const renderCostCenter = () => { if (!costCenterData) return null; const data = costCenterData.data || []; return (
{t('reports.totalIncome')}
{currency(costCenterData.total_income || 0, costCenterData.currency)}
{t('reports.totalExpense')}
{currency(costCenterData.total_expense || 0, costCenterData.currency)}
{t('reports.balance')}
= 0 ? 'text-success' : 'text-danger'}`}> {currency((costCenterData.total_income || 0) - (costCenterData.total_expense || 0), costCenterData.currency)}
{t('reports.byCostCenter')}
{data.map(cc => ( ))}
{t('costCenters.name')} {t('reports.income')} {t('reports.expenses')} {t('reports.balance')}
{cc.name} {currency(cc.income, costCenterData.currency)} {currency(cc.expense, costCenterData.currency)} = 0 ? 'text-success' : 'text-danger'}`}> {currency(cc.balance, costCenterData.currency)}
); }; // Render Recurring Tab const renderRecurring = () => { if (!recurringData) return null; const templates = recurringData.templates || []; const summary = recurringData.summary || {}; return (
{t('reports.totalRecurring')}

{summary.total_recurring || 0}

{t('reports.monthlyIncome')}
{currency(summary.monthly_income || 0, recurringData.currency)}
{t('reports.monthlyExpense')}
{currency(summary.monthly_expense || 0, recurringData.currency)}
{t('reports.netRecurring')}
= 0 ? 'text-success' : 'text-danger'}`}> {currency(summary.net_recurring || 0, recurringData.currency)}
{t('reports.recurringList')}
{templates.map(t => ( ))}
{t('common.description')} {t('reports.category')} {t('recurring.frequency')} {t('reports.nextDate')} {t('reports.amount')}
{t.description} {t.category && ( {t.category} )} {t.frequency} {t.next_date} {t.type === 'credit' ? '+' : '-'}{currency(t.amount, t.currency)}
); }; // Render Liabilities Tab const renderLiabilities = () => { if (!liabilitiesData) return null; const data = liabilitiesData.data || []; const summary = liabilitiesData.summary || {}; return (
{t('reports.totalLiabilities')}

{summary.total_liabilities || 0}

{t('reports.totalDebt')}
{currency(summary.total_debt || 0, liabilitiesData.currency)}
{t('reports.totalPaid')}
{currency(summary.total_paid || 0, liabilitiesData.currency)}
{t('reports.totalPending')}
{currency(summary.total_pending || 0, liabilitiesData.currency)}
{data.map(liability => (
{liability.name}
{liability.type}
{liability.overdue_installments > 0 && ( {liability.overdue_installments} {t('reports.overdueInstallments')} )}
{liability.paid_installments}/{liability.total_installments} {t('reports.installments')} {liability.progress?.toFixed(1)}%

{t('common.total')} {currency(liability.total_amount, liability.currency)}
{t('reports.paid')} {currency(liability.paid_amount, liability.currency)}
{t('reports.pending')} {currency(liability.pending_amount, liability.currency)}
{liability.next_installment && (
{t('reports.nextInstallment')}:
{formatDate(liability.next_installment.due_date)} {currency(liability.next_installment.amount, liability.currency)}
)}
))}
); }; // Render Future Transactions Tab const renderFuture = () => { if (!futureData) return null; const data = futureData.data || []; const summary = futureData.summary || {}; return (
{t('reports.totalTransactions')}

{summary.total_transactions || 0}

{t('reports.futureIncome')}
{currency(summary.total_income || 0, futureData.currency)}
{t('reports.futureExpense')}
{currency(summary.total_expense || 0, futureData.currency)}
{t('reports.netImpact')}
= 0 ? 'text-success' : 'text-danger'}`}> {currency(summary.net_impact || 0, futureData.currency)}
{t('reports.next30Days')}
{data.map(tx => ( ))}
{t('reports.date')} {t('common.description')} {t('reports.category')} {t('reports.account')} {t('reports.amount')}
{tx.days_until}d {tx.date} {tx.description} {tx.category && ( {tx.category} )} {tx.account} {tx.type === 'credit' ? '+' : '-'}{currency(tx.amount, tx.currency)}
); }; // Render Overdue Tab const renderOverdue = () => { if (!overdueData) return null; const data = overdueData.data || []; const summary = overdueData.summary || {}; return (
{t('reports.totalOverdue')}

{summary.total_overdue || 0}

{t('reports.overdueAmount')}
{currency(summary.total_amount || 0, overdueData.currency)}
{data.length === 0 ? (
{t('reports.noOverdue')}

{t('reports.noOverdueDescription')}

) : (
{t('reports.overdueList')}
{data.map(item => ( ))}
{t('common.description')} {t('reports.dueDate')} {t('reports.daysOverdue')} {t('reports.amount')}
{item.description} {item.due_date} {item.days_overdue} {t('common.days')} {currency(item.amount, item.currency)}
)}
); }; const renderContent = () => { if (loading) { return (
{t('common.loading')}
); } switch (activeTab) { case 'summary': return renderSummary(); case 'category': return renderCategory(); case 'costCenter': return renderCostCenter(); case 'evolution': return renderEvolution(); case 'comparison': return renderComparison(); case 'topExpenses': return renderTopExpenses(); case 'projection': return renderProjection(); case 'recurring': return renderRecurring(); case 'liabilities': return renderLiabilities(); case 'future': return renderFuture(); case 'overdue': return renderOverdue(); default: return null; } }; return (
{/* Header */}

{t('reports.title')}

{t('reports.subtitle')}

{/* Tabs */}
{tabs.map(tab => ( ))}
{/* Content */} {renderContent()}
); }; export default Reports;