webmoney/frontend/src/pages/Reports.jsx
marcoitaloesp-ai d03565d4ab
v1.35.0: Redesign completo da seção de Comparação de Períodos
Improved:
- Cards KPI com gradientes e ícones para cada métrica
- Tabela comparativa detalhada lado a lado
- Gráfico de barras agrupadas profissional
- Layout responsivo otimizado
- Badges de variação com validação NaN
- Traduções completas (pt-BR, en, es)
2025-12-15 16:06:16 +00:00

1607 lines
68 KiB
JavaScript

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 (
<div className="row g-4">
{/* Year Selector */}
<div className="col-12">
<div className="d-flex align-items-center justify-content-between mb-3">
<h5 className="text-white mb-0">
<i className="bi bi-calendar3 me-2"></i>
{t('reports.annualSummary')}
</h5>
<div className="btn-group" role="group">
{[2024, 2025].map(y => (
<button
key={y}
className={`btn btn-sm ${year === y ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => setYear(y)}
>
{y}
</button>
))}
</div>
</div>
</div>
{/* Main KPI Cards */}
<div className="col-lg-3 col-md-6">
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #059669 0%, #047857 100%)' }}>
<div className="card-body text-white">
<div className="d-flex align-items-center justify-content-between mb-3">
<div>
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.income')}</div>
<h3 className="mb-0 fw-bold">{currency(summary.current.income, summary.currency)}</h3>
</div>
<div className="fs-1 opacity-50">
<i className="bi bi-arrow-up-circle"></i>
</div>
</div>
{summary.variation.income !== 0 && !isNaN(summary.variation.income) && isFinite(summary.variation.income) && (
<div className="d-flex align-items-center gap-2">
<span className={`badge ${summary.variation.income >= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
<i className={`bi bi-arrow-${summary.variation.income >= 0 ? 'up' : 'down'} me-1`}></i>
{Math.abs(summary.variation.income).toFixed(1)}%
</span>
<small className="opacity-75">{t('reports.vsLastYear')}</small>
</div>
)}
<hr className="my-2 opacity-25" />
<div className="small d-flex justify-content-between">
<span className="opacity-75">{t('reports.monthlyAverage')}:</span>
<span className="fw-semibold">{currency(monthlyAvgIncome, summary.currency)}</span>
</div>
</div>
</div>
</div>
<div className="col-lg-3 col-md-6">
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #dc2626 0%, #b91c1c 100%)' }}>
<div className="card-body text-white">
<div className="d-flex align-items-center justify-content-between mb-3">
<div>
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.expenses')}</div>
<h3 className="mb-0 fw-bold">{currency(summary.current.expense, summary.currency)}</h3>
</div>
<div className="fs-1 opacity-50">
<i className="bi bi-arrow-down-circle"></i>
</div>
</div>
{summary.variation.expense !== 0 && !isNaN(summary.variation.expense) && isFinite(summary.variation.expense) && (
<div className="d-flex align-items-center gap-2">
<span className={`badge ${summary.variation.expense <= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
<i className={`bi bi-arrow-${summary.variation.expense >= 0 ? 'up' : 'down'} me-1`}></i>
{Math.abs(summary.variation.expense).toFixed(1)}%
</span>
<small className="opacity-75">{t('reports.vsLastYear')}</small>
</div>
)}
<hr className="my-2 opacity-25" />
<div className="small d-flex justify-content-between">
<span className="opacity-75">{t('reports.monthlyAverage')}:</span>
<span className="fw-semibold">{currency(monthlyAvgExpense, summary.currency)}</span>
</div>
</div>
</div>
</div>
<div className="col-lg-3 col-md-6">
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)' }}>
<div className="card-body text-white">
<div className="d-flex align-items-center justify-content-between mb-3">
<div>
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.balance')}</div>
<h3 className="mb-0 fw-bold">{currency(summary.current.balance, summary.currency)}</h3>
</div>
<div className="fs-1 opacity-50">
<i className="bi bi-wallet2"></i>
</div>
</div>
{summary.variation.balance !== 0 && !isNaN(summary.variation.balance) && isFinite(summary.variation.balance) && (
<div className="d-flex align-items-center gap-2">
<span className={`badge ${summary.variation.balance >= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
<i className={`bi bi-arrow-${summary.variation.balance >= 0 ? 'up' : 'down'} me-1`}></i>
{Math.abs(summary.variation.balance).toFixed(1)}%
</span>
<small className="opacity-75">{t('reports.vsLastYear')}</small>
</div>
)}
<hr className="my-2 opacity-25" />
<div className="small d-flex justify-content-between">
<span className="opacity-75">{t('reports.monthlyAverage')}:</span>
<span className="fw-semibold">{currency(monthlyAvgBalance, summary.currency)}</span>
</div>
</div>
</div>
</div>
<div className="col-lg-3 col-md-6">
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)' }}>
<div className="card-body text-white">
<div className="d-flex align-items-center justify-content-between mb-3">
<div>
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.savingsRate')}</div>
<h3 className="mb-0 fw-bold">{savingsRate}%</h3>
</div>
<div className="fs-1 opacity-50">
<i className="bi bi-piggy-bank"></i>
</div>
</div>
<div className="progress mb-2" style={{ height: '8px', background: 'rgba(255,255,255,0.2)' }}>
<div
className="progress-bar bg-white"
role="progressbar"
style={{ width: `${Math.min(savingsRate, 100)}%` }}
></div>
</div>
<small className="opacity-75">
{savingsRate >= 20 ? '🎯 ' + t('reports.excellentSavings') :
savingsRate >= 10 ? '👍 ' + t('reports.goodSavings') :
'💡 ' + t('reports.canImprove')}
</small>
</div>
</div>
</div>
{/* Comparison Chart */}
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<div className="d-flex align-items-center justify-content-between">
<h6 className="text-white mb-0">
<i className="bi bi-bar-chart-line me-2"></i>
{t('reports.yearComparison')} - {year-1} vs {year}
</h6>
<span className="badge bg-primary">
{t('reports.total')}: {currency(summary.current.balance, summary.currency)}
</span>
</div>
</div>
<div className="card-body" style={{ height: '320px' }}>
<Bar
data={{
labels: [t('common.incomes'), t('common.expenses'), t('common.balance')],
datasets: [
{
label: String(year - 1),
data: [summary.previous.income, summary.previous.expense, summary.previous.balance],
backgroundColor: 'rgba(100, 116, 139, 0.6)',
borderColor: '#64748b',
borderWidth: 2,
borderRadius: 6,
},
{
label: String(year),
data: [summary.current.income, summary.current.expense, summary.current.balance],
backgroundColor: ['rgba(16, 185, 129, 0.8)', 'rgba(239, 68, 68, 0.8)', 'rgba(59, 130, 246, 0.8)'],
borderColor: ['#10b981', '#ef4444', '#3b82f6'],
borderWidth: 2,
borderRadius: 6,
},
],
}}
options={{
...chartOptions,
plugins: {
...chartOptions.plugins,
legend: {
position: 'top',
labels: {
color: '#94a3b8',
padding: 15,
font: { size: 12, weight: 'bold' }
}
},
tooltip: {
backgroundColor: 'rgba(15, 23, 42, 0.95)',
titleColor: '#fff',
bodyColor: '#94a3b8',
borderColor: '#334155',
borderWidth: 1,
padding: 12,
displayColors: true,
callbacks: {
label: function(context) {
return `${context.dataset.label}: ${currency(context.parsed.y, summary.currency)}`;
}
}
}
}
}}
/>
</div>
</div>
</div>
</div>
);
};
// Render Category Tab
const renderCategory = () => {
if (!categoryData) return null;
const colors = categoryData.data.map((_, i) =>
`hsl(${(i * 360) / categoryData.data.length}, 70%, 50%)`
);
return (
<div className="row g-4">
<div className="col-lg-6">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-pie-chart me-2"></i>
{t('reports.expenseDistribution')}
</h6>
</div>
<div className="card-body" style={{ height: '400px' }}>
<Doughnut
data={{
labels: categoryData.data.map(c => c.category_name),
datasets: [{
data: categoryData.data.map(c => c.total),
backgroundColor: colors,
borderWidth: 0,
}],
}}
options={doughnutOptions}
/>
</div>
</div>
</div>
<div className="col-lg-6">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent d-flex justify-content-between">
<h6 className="text-white mb-0">
<i className="bi bi-list-ol me-2"></i>
{t('reports.categoryDetail')}
</h6>
<span className="text-success fw-bold">{currency(categoryData.total, categoryData.currency)}</span>
</div>
<div className="card-body p-0" style={{ maxHeight: '400px', overflowY: 'auto' }}>
<table className="table table-dark table-hover mb-0">
<thead className="sticky-top" style={{ background: '#1e293b' }}>
<tr>
<th>{t('reports.category')}</th>
<th className="text-end">{t('common.total')}</th>
<th className="text-end">%</th>
</tr>
</thead>
<tbody>
{categoryData.data.map((cat, i) => (
<tr key={cat.category_id}>
<td>
<i className={`bi ${cat.icon} me-2`} style={{ color: colors[i] }}></i>
{cat.category_name}
</td>
<td className="text-end">{currency(cat.total, categoryData.currency)}</td>
<td className="text-end">
<span className="badge bg-secondary">{cat.percentage}%</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
};
// Render Evolution Tab
const renderEvolution = () => {
if (!evolutionData) return null;
return (
<div className="row g-4">
<div className="col-12">
<div className="d-flex gap-2 mb-3">
{[6, 12, 24].map(m => (
<button
key={m}
className={`btn btn-sm ${months === m ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => setMonths(m)}
>
{m} {t('common.months')}
</button>
))}
</div>
</div>
{/* Averages Cards */}
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.avgIncome')}</small>
<h5 className="text-success mb-0">{currency(evolutionData.averages.income, evolutionData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.avgExpense')}</small>
<h5 className="text-danger mb-0">{currency(evolutionData.averages.expense, evolutionData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.balance')}</small>
<h5 className={`mb-0 ${evolutionData.averages.balance >= 0 ? 'text-success' : 'text-danger'}`}>
{currency(evolutionData.averages.balance, evolutionData.currency)}
</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.savingsRate')}</small>
<h5 className="text-primary mb-0">{evolutionData.averages.savings_rate}%</h5>
</div>
</div>
</div>
{/* Evolution Chart */}
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-graph-up me-2"></i>
{t('reports.monthlyEvolution')}
</h6>
</div>
<div className="card-body" style={{ height: '350px' }}>
<Line
data={{
labels: evolutionData.data.map(d => 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}
/>
</div>
</div>
</div>
{/* Savings Rate Chart */}
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-percent me-2"></i>
{t('reports.savingsRate')} por mes
</h6>
</div>
<div className="card-body" style={{ height: '250px' }}>
<Bar
data={{
labels: evolutionData.data.map(d => 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 }
}
}}
/>
</div>
</div>
</div>
</div>
);
};
// Render Comparison Tab
const renderComparison = () => {
if (!comparison) return null;
return (
<div className="row g-4">
{/* Header com seletor de períodos */}
<div className="col-12">
<div className="d-flex align-items-center justify-content-between mb-3">
<h5 className="text-white mb-0">
<i className="bi bi-arrow-left-right me-2"></i>
{t('reports.periodComparison')}
</h5>
</div>
</div>
{/* Cards KPI Comparativos */}
<div className="col-lg-4 col-md-6">
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #059669 0%, #047857 100%)' }}>
<div className="card-body text-white">
<div className="d-flex align-items-center justify-content-between mb-3">
<div>
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.income')}</div>
<h3 className="mb-0 fw-bold">{currency(comparison.period1.income, comparison.currency)}</h3>
</div>
<div className="fs-1 opacity-50">
<i className="bi bi-arrow-up-circle"></i>
</div>
</div>
<div className="d-flex align-items-center gap-2 mb-2">
{comparison.variation.income !== 0 && !isNaN(comparison.variation.income) && isFinite(comparison.variation.income) && (
<>
<span className={`badge ${comparison.variation.income >= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
<i className={`bi bi-arrow-${comparison.variation.income >= 0 ? 'up' : 'down'} me-1`}></i>
{comparison.variation.income > 0 ? '+' : ''}{Math.abs(comparison.variation.income).toFixed(1)}%
</span>
<small className="opacity-75">{t('reports.vsPreviousPeriod')}</small>
</>
)}
</div>
<hr className="my-2 opacity-25" />
<div className="small">
<div className="d-flex justify-content-between opacity-75">
<span>{comparison.period2.label}:</span>
<span className="fw-semibold">{currency(comparison.period2.income, comparison.currency)}</span>
</div>
</div>
</div>
</div>
</div>
<div className="col-lg-4 col-md-6">
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #dc2626 0%, #b91c1c 100%)' }}>
<div className="card-body text-white">
<div className="d-flex align-items-center justify-content-between mb-3">
<div>
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.expenses')}</div>
<h3 className="mb-0 fw-bold">{currency(comparison.period1.expense, comparison.currency)}</h3>
</div>
<div className="fs-1 opacity-50">
<i className="bi bi-arrow-down-circle"></i>
</div>
</div>
<div className="d-flex align-items-center gap-2 mb-2">
{comparison.variation.expense !== 0 && !isNaN(comparison.variation.expense) && isFinite(comparison.variation.expense) && (
<>
<span className={`badge ${comparison.variation.expense <= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
<i className={`bi bi-arrow-${comparison.variation.expense >= 0 ? 'up' : 'down'} me-1`}></i>
{comparison.variation.expense > 0 ? '+' : ''}{Math.abs(comparison.variation.expense).toFixed(1)}%
</span>
<small className="opacity-75">{t('reports.vsPreviousPeriod')}</small>
</>
)}
</div>
<hr className="my-2 opacity-25" />
<div className="small">
<div className="d-flex justify-content-between opacity-75">
<span>{comparison.period2.label}:</span>
<span className="fw-semibold">{currency(comparison.period2.expense, comparison.currency)}</span>
</div>
</div>
</div>
</div>
</div>
<div className="col-lg-4 col-md-12">
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)' }}>
<div className="card-body text-white">
<div className="d-flex align-items-center justify-content-between mb-3">
<div>
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.balance')}</div>
<h3 className="mb-0 fw-bold">{currency(comparison.period1.balance, comparison.currency)}</h3>
</div>
<div className="fs-1 opacity-50">
<i className="bi bi-wallet2"></i>
</div>
</div>
<div className="d-flex align-items-center gap-2 mb-2">
{comparison.variation.balance !== 0 && !isNaN(comparison.variation.balance) && isFinite(comparison.variation.balance) && (
<>
<span className={`badge ${comparison.variation.balance >= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
<i className={`bi bi-arrow-${comparison.variation.balance >= 0 ? 'up' : 'down'} me-1`}></i>
{comparison.variation.balance > 0 ? '+' : ''}{Math.abs(comparison.variation.balance).toFixed(1)}%
</span>
<small className="opacity-75">{t('reports.vsPreviousPeriod')}</small>
</>
)}
</div>
<hr className="my-2 opacity-25" />
<div className="small">
<div className="d-flex justify-content-between opacity-75">
<span>{comparison.period2.label}:</span>
<span className="fw-semibold">{currency(comparison.period2.balance, comparison.currency)}</span>
</div>
</div>
</div>
</div>
</div>
{/* Tabela Comparativa Detalhada */}
<div className="col-lg-5">
<div className="card border-0 h-100" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-table me-2"></i>
{t('reports.detailedComparison')}
</h6>
</div>
<div className="card-body">
<table className="table table-dark table-borderless mb-0">
<thead className="border-bottom border-secondary">
<tr>
<th className="text-slate-400 fw-normal small">{t('reports.metric')}</th>
<th className="text-end text-slate-400 fw-normal small">{comparison.period2.label}</th>
<th className="text-end text-slate-400 fw-normal small">{comparison.period1.label}</th>
<th className="text-end text-slate-400 fw-normal small">{t('reports.variation')}</th>
</tr>
</thead>
<tbody>
<tr>
<td className="text-white">{t('reports.income')}</td>
<td className="text-end text-success">{currency(comparison.period2.income, comparison.currency)}</td>
<td className="text-end text-success fw-bold">{currency(comparison.period1.income, comparison.currency)}</td>
<td className="text-end">
{comparison.variation.income !== 0 && !isNaN(comparison.variation.income) && isFinite(comparison.variation.income) && (
<span className={`badge ${comparison.variation.income >= 0 ? 'bg-success' : 'bg-danger'}`}>
{comparison.variation.income > 0 ? '+' : ''}{comparison.variation.income.toFixed(1)}%
</span>
)}
</td>
</tr>
<tr>
<td className="text-white">{t('reports.expenses')}</td>
<td className="text-end text-danger">{currency(comparison.period2.expense, comparison.currency)}</td>
<td className="text-end text-danger fw-bold">{currency(comparison.period1.expense, comparison.currency)}</td>
<td className="text-end">
{comparison.variation.expense !== 0 && !isNaN(comparison.variation.expense) && isFinite(comparison.variation.expense) && (
<span className={`badge ${comparison.variation.expense <= 0 ? 'bg-success' : 'bg-danger'}`}>
{comparison.variation.expense > 0 ? '+' : ''}{comparison.variation.expense.toFixed(1)}%
</span>
)}
</td>
</tr>
<tr className="border-top border-secondary">
<td className="text-white fw-bold">{t('reports.balance')}</td>
<td className={`text-end fw-bold ${comparison.period2.balance >= 0 ? 'text-success' : 'text-danger'}`}>
{currency(comparison.period2.balance, comparison.currency)}
</td>
<td className={`text-end fw-bold ${comparison.period1.balance >= 0 ? 'text-success' : 'text-danger'}`}>
{currency(comparison.period1.balance, comparison.currency)}
</td>
<td className="text-end">
{comparison.variation.balance !== 0 && !isNaN(comparison.variation.balance) && isFinite(comparison.variation.balance) && (
<span className={`badge ${comparison.variation.balance >= 0 ? 'bg-success' : 'bg-danger'}`}>
{comparison.variation.balance > 0 ? '+' : ''}{comparison.variation.balance.toFixed(1)}%
</span>
)}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{/* Gráfico de Barras Agrupadas */}
<div className="col-lg-7">
<div className="card border-0 h-100" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-bar-chart-line me-2"></i>
{t('reports.visualComparison')}
</h6>
</div>
<div className="card-body" style={{ height: '320px' }}>
<Bar
data={{
labels: [t('reports.income'), t('reports.expenses'), t('reports.balance')],
datasets: [
{
label: comparison.period2.label,
data: [comparison.period2.income, comparison.period2.expense, comparison.period2.balance],
backgroundColor: 'rgba(100, 116, 139, 0.6)',
borderColor: '#64748b',
borderWidth: 2,
borderRadius: 6,
},
{
label: comparison.period1.label,
data: [comparison.period1.income, comparison.period1.expense, comparison.period1.balance],
backgroundColor: ['rgba(16, 185, 129, 0.8)', 'rgba(239, 68, 68, 0.8)', 'rgba(59, 130, 246, 0.8)'],
borderColor: ['#10b981', '#ef4444', '#3b82f6'],
borderWidth: 2,
borderRadius: 6,
},
],
}}
options={{
...chartOptions,
plugins: {
...chartOptions.plugins,
legend: {
position: 'top',
labels: {
color: '#94a3b8',
padding: 15,
font: { size: 12, weight: 'bold' }
}
},
tooltip: {
backgroundColor: 'rgba(15, 23, 42, 0.95)',
titleColor: '#fff',
bodyColor: '#94a3b8',
borderColor: '#334155',
borderWidth: 1,
padding: 12,
displayColors: true,
callbacks: {
label: function(context) {
return `${context.dataset.label}: ${currency(context.parsed.y, comparison.currency)}`;
}
}
}
}
}}
/>
</div>
</div>
</div>
</div>
);
};
// Render Top Expenses Tab
const renderTopExpenses = () => {
if (!topExpenses) return null;
return (
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent d-flex justify-content-between">
<h6 className="text-white mb-0">
<i className="bi bi-sort-down me-2"></i>
{t('reports.top20Expenses')}
</h6>
<span className="text-danger fw-bold">{currency(topExpenses.total, topExpenses.currency)}</span>
</div>
<div className="card-body p-0">
<div className="table-responsive">
<table className="table table-dark table-hover mb-0">
<thead>
<tr>
<th>#</th>
<th>{t('reports.description')}</th>
<th>{t('reports.category')}</th>
<th>{t('reports.date')}</th>
<th className="text-end">{t('reports.amount')}</th>
</tr>
</thead>
<tbody>
{topExpenses.data.map((item, i) => (
<tr key={item.id}>
<td><span className="badge bg-secondary">{i + 1}</span></td>
<td className="text-truncate" style={{ maxWidth: '200px' }}>{item.description}</td>
<td><span className="badge bg-primary">{item.category || '-'}</span></td>
<td className="text-slate-400">{item.date}</td>
<td className="text-end text-danger fw-bold">{currency(item.amount, item.currency || topExpenses.currency)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
// Render Projection Tab
const renderProjection = () => {
return (
<div className="row g-4">
{/* Balance Projection Chart - Full Width First */}
<div className="col-12">
<BalanceProjectionChart />
</div>
{projection && (
<>
<div className="col-md-6">
<div className="card border-0" style={{ background: '#1e293b' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-calendar3 me-2"></i>
{t('reports.currentMonth')}
</h6>
</div>
<div className="card-body">
<div className="d-flex justify-content-between mb-3">
<span className="text-slate-400">{t('reports.income')}</span>
<span className="text-success">{currency(projection.current_month.income, projection.currency)}</span>
</div>
<div className="d-flex justify-content-between mb-3">
<span className="text-slate-400">{t('reports.expenses')}</span>
<span className="text-danger">{currency(projection.current_month.expense, projection.currency)}</span>
</div>
<hr className="border-secondary" />
<div className="d-flex justify-content-between">
<span className="text-slate-400">{t('reports.daysRemaining')}</span>
<span className="text-white">{projection.current_month.days_remaining} {t('common.days')}</span>
</div>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card border-0" style={{ background: 'linear-gradient(135deg, #1e40af 0%, #3b82f6 100%)' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-lightning me-2"></i>
{t('reports.projectionTitle')}
</h6>
</div>
<div className="card-body text-white">
<div className="d-flex justify-content-between mb-3">
<span className="opacity-75">{t('reports.projectedIncome')}</span>
<span className="fw-bold">{currency(projection.projection.income, projection.currency)}</span>
</div>
<div className="d-flex justify-content-between mb-3">
<span className="opacity-75">{t('reports.projectedExpense')}</span>
<span className="fw-bold">{currency(projection.projection.expense, projection.currency)}</span>
</div>
<hr className="border-white opacity-25" />
<div className="d-flex justify-content-between">
<span className="opacity-75">{t('reports.balance')}</span>
<span className={`fw-bold ${projection.projection.balance >= 0 ? '' : 'text-warning'}`}>
{currency(projection.projection.balance, projection.currency)}
</span>
</div>
</div>
</div>
</div>
{/* vs Average */}
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-bar-chart me-2"></i>
{t('reports.vsAverage')} ({t('reports.last3Months')})
</h6>
</div>
<div className="card-body" style={{ height: '250px' }}>
<Bar
data={{
labels: [t('reports.income'), t('reports.expenses')],
datasets: [
{
label: t('reports.historicalAverage'),
data: [projection.historical_average.income, projection.historical_average.expense],
backgroundColor: 'rgba(148, 163, 184, 0.5)',
borderRadius: 4,
},
{
label: t('reports.monthProjection'),
data: [projection.projection.income, projection.projection.expense],
backgroundColor: ['rgba(16, 185, 129, 0.7)', 'rgba(239, 68, 68, 0.7)'],
borderRadius: 4,
},
],
}}
options={chartOptions}
/>
</div>
</div>
</div>
</>
)}
</div>
);
};
// Render Day of Week Tab
const renderDayOfWeek = () => {
if (!dayOfWeekData || !dayOfWeekData.data) return null;
const data = dayOfWeekData.data;
return (
<div className="row g-4">
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-calendar-week me-2"></i>
{t('reports.expensesByDayOfWeek')}
</h6>
</div>
<div className="card-body" style={{ height: '300px' }}>
<Bar
data={{
labels: data.map(d => 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 } }
}}
/>
</div>
</div>
</div>
<div className="col-12">
<div className="card border-0" style={{ background: '#1e293b' }}>
<div className="card-body p-0">
<table className="table table-dark mb-0">
<thead>
<tr>
<th>{t('reports.dayOfWeek.day')}</th>
<th className="text-center">{t('transactions.title')}</th>
<th className="text-end">{t('common.total')}</th>
<th className="text-end">{t('reports.avgExpense')}</th>
</tr>
</thead>
<tbody>
{dayOfWeekData.data.map(d => (
<tr key={d.day_num}>
<td>
<i className={`bi bi-calendar3 me-2 ${d.day_num === 1 || d.day_num === 7 ? 'text-warning' : 'text-primary'}`}></i>
{t(`reports.dayOfWeek.${d.day_key}`)}
</td>
<td className="text-center"><span className="badge bg-secondary">{d.count}</span></td>
<td className="text-end text-danger">{currency(d.total, dayOfWeekData.currency)}</td>
<td className="text-end text-slate-400">{currency(d.average, dayOfWeekData.currency)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
};
// Render Cost Center Tab
const renderCostCenter = () => {
if (!costCenterData) return null;
const data = costCenterData.data || [];
return (
<div className="row g-4">
<div className="col-md-4">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalIncome')}</small>
<h5 className="text-success mb-0">{currency(costCenterData.total_income || 0, costCenterData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalExpense')}</small>
<h5 className="text-danger mb-0">{currency(costCenterData.total_expense || 0, costCenterData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.balance')}</small>
<h5 className={`mb-0 ${(costCenterData.total_income - costCenterData.total_expense) >= 0 ? 'text-success' : 'text-danger'}`}>
{currency((costCenterData.total_income || 0) - (costCenterData.total_expense || 0), costCenterData.currency)}
</h5>
</div>
</div>
</div>
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-diagram-3 me-2"></i>
{t('reports.byCostCenter')}
</h6>
</div>
<div className="card-body p-0">
<div className="table-responsive">
<table className="table table-dark table-hover mb-0">
<thead>
<tr>
<th>{t('costCenters.name')}</th>
<th className="text-end">{t('reports.income')}</th>
<th className="text-end">{t('reports.expenses')}</th>
<th className="text-end">{t('reports.balance')}</th>
</tr>
</thead>
<tbody>
{data.map(cc => (
<tr key={cc.id}>
<td>
<span className="me-2" style={{ color: cc.color || '#6b7280' }}></span>
{cc.name}
</td>
<td className="text-end text-success">{currency(cc.income, costCenterData.currency)}</td>
<td className="text-end text-danger">{currency(cc.expense, costCenterData.currency)}</td>
<td className={`text-end ${cc.balance >= 0 ? 'text-success' : 'text-danger'}`}>
{currency(cc.balance, costCenterData.currency)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
};
// Render Recurring Tab
const renderRecurring = () => {
if (!recurringData) return null;
const templates = recurringData.templates || [];
const summary = recurringData.summary || {};
return (
<div className="row g-4">
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalRecurring')}</small>
<h4 className="text-white mb-0">{summary.total_recurring || 0}</h4>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.monthlyIncome')}</small>
<h5 className="text-success mb-0">{currency(summary.monthly_income || 0, recurringData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.monthlyExpense')}</small>
<h5 className="text-danger mb-0">{currency(summary.monthly_expense || 0, recurringData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.netRecurring')}</small>
<h5 className={`mb-0 ${(summary.net_recurring || 0) >= 0 ? 'text-success' : 'text-danger'}`}>
{currency(summary.net_recurring || 0, recurringData.currency)}
</h5>
</div>
</div>
</div>
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-arrow-repeat me-2"></i>
{t('reports.recurringList')}
</h6>
</div>
<div className="card-body p-0">
<div className="table-responsive">
<table className="table table-dark table-hover mb-0">
<thead>
<tr>
<th>{t('common.description')}</th>
<th>{t('reports.category')}</th>
<th>{t('recurring.frequency')}</th>
<th>{t('reports.nextDate')}</th>
<th className="text-end">{t('reports.amount')}</th>
</tr>
</thead>
<tbody>
{templates.map(t => (
<tr key={t.id}>
<td>{t.description}</td>
<td>
{t.category && (
<span className="badge" style={{ background: t.category_color || '#6b7280' }}>
<i className={`bi ${t.category_icon || 'bi-tag'} me-1`}></i>
{t.category}
</span>
)}
</td>
<td><span className="badge bg-secondary">{t.frequency}</span></td>
<td className="text-slate-400">{t.next_date}</td>
<td className={`text-end fw-bold ${t.type === 'credit' ? 'text-success' : 'text-danger'}`}>
{t.type === 'credit' ? '+' : '-'}{currency(t.amount, t.currency)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
};
// Render Liabilities Tab
const renderLiabilities = () => {
if (!liabilitiesData) return null;
const data = liabilitiesData.data || [];
const summary = liabilitiesData.summary || {};
return (
<div className="row g-4">
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalLiabilities')}</small>
<h4 className="text-white mb-0">{summary.total_liabilities || 0}</h4>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalDebt')}</small>
<h5 className="text-danger mb-0">{currency(summary.total_debt || 0, liabilitiesData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalPaid')}</small>
<h5 className="text-success mb-0">{currency(summary.total_paid || 0, liabilitiesData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalPending')}</small>
<h5 className="text-warning mb-0">{currency(summary.total_pending || 0, liabilitiesData.currency)}</h5>
</div>
</div>
</div>
{data.map(liability => (
<div key={liability.id} className="col-md-6">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-body">
<div className="d-flex justify-content-between align-items-start mb-3">
<div>
<h6 className="text-white mb-1">{liability.name}</h6>
<span className="badge bg-secondary">{liability.type}</span>
</div>
{liability.overdue_installments > 0 && (
<span className="badge bg-danger">
{liability.overdue_installments} {t('reports.overdueInstallments')}
</span>
)}
</div>
<div className="progress mb-3" style={{ height: '8px' }}>
<div
className="progress-bar bg-success"
style={{ width: `${liability.progress || 0}%` }}
></div>
</div>
<div className="d-flex justify-content-between text-sm">
<span className="text-slate-400">
{liability.paid_installments}/{liability.total_installments} {t('reports.installments')}
</span>
<span className="text-success">{liability.progress?.toFixed(1)}%</span>
</div>
<hr className="border-secondary my-3" />
<div className="row text-center">
<div className="col-4">
<small className="text-slate-400 d-block">{t('common.total')}</small>
<span className="text-white">{currency(liability.total_amount, liability.currency)}</span>
</div>
<div className="col-4">
<small className="text-slate-400 d-block">{t('reports.paid')}</small>
<span className="text-success">{currency(liability.paid_amount, liability.currency)}</span>
</div>
<div className="col-4">
<small className="text-slate-400 d-block">{t('reports.pending')}</small>
<span className="text-warning">{currency(liability.pending_amount, liability.currency)}</span>
</div>
</div>
{liability.next_installment && (
<div className={`mt-3 p-2 rounded ${liability.next_installment.is_overdue ? 'bg-danger bg-opacity-25' : 'bg-primary bg-opacity-25'}`}>
<small className="text-slate-400">{t('reports.nextInstallment')}:</small>
<div className="d-flex justify-content-between">
<span className="text-white">{formatDate(liability.next_installment.due_date)}</span>
<span className={liability.next_installment.is_overdue ? 'text-danger' : 'text-primary'}>
{currency(liability.next_installment.amount, liability.currency)}
</span>
</div>
</div>
)}
</div>
</div>
</div>
))}
</div>
);
};
// Render Future Transactions Tab
const renderFuture = () => {
if (!futureData) return null;
const data = futureData.data || [];
const summary = futureData.summary || {};
return (
<div className="row g-4">
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalTransactions')}</small>
<h4 className="text-white mb-0">{summary.total_transactions || 0}</h4>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.futureIncome')}</small>
<h5 className="text-success mb-0">{currency(summary.total_income || 0, futureData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.futureExpense')}</small>
<h5 className="text-danger mb-0">{currency(summary.total_expense || 0, futureData.currency)}</h5>
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.netImpact')}</small>
<h5 className={`mb-0 ${(summary.net_impact || 0) >= 0 ? 'text-success' : 'text-danger'}`}>
{currency(summary.net_impact || 0, futureData.currency)}
</h5>
</div>
</div>
</div>
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-calendar-plus me-2"></i>
{t('reports.next30Days')}
</h6>
</div>
<div className="card-body p-0">
<div className="table-responsive">
<table className="table table-dark table-hover mb-0">
<thead>
<tr>
<th>{t('reports.date')}</th>
<th>{t('common.description')}</th>
<th>{t('reports.category')}</th>
<th>{t('reports.account')}</th>
<th className="text-end">{t('reports.amount')}</th>
</tr>
</thead>
<tbody>
{data.map(tx => (
<tr key={tx.id}>
<td>
<span className="badge bg-primary">{tx.days_until}d</span>
<span className="text-slate-400 ms-2">{tx.date}</span>
</td>
<td>{tx.description}</td>
<td>
{tx.category && (
<span className="badge bg-secondary">
<i className={`bi ${tx.category_icon || 'bi-tag'} me-1`}></i>
{tx.category}
</span>
)}
</td>
<td className="text-slate-400">{tx.account}</td>
<td className={`text-end fw-bold ${tx.type === 'credit' ? 'text-success' : 'text-danger'}`}>
{tx.type === 'credit' ? '+' : '-'}{currency(tx.amount, tx.currency)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
};
// Render Overdue Tab
const renderOverdue = () => {
if (!overdueData) return null;
const data = overdueData.data || [];
const summary = overdueData.summary || {};
return (
<div className="row g-4">
<div className="col-md-6">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.totalOverdue')}</small>
<h4 className="text-danger mb-0">{summary.total_overdue || 0}</h4>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card border-0 text-center" style={{ background: '#1e293b' }}>
<div className="card-body">
<small className="text-slate-400">{t('reports.overdueAmount')}</small>
<h5 className="text-danger mb-0">{currency(summary.total_amount || 0, overdueData.currency)}</h5>
</div>
</div>
</div>
{data.length === 0 ? (
<div className="col-12">
<div className="card border-0 text-center py-5" style={{ background: '#0f172a' }}>
<div className="card-body">
<i className="bi bi-check-circle text-success" style={{ fontSize: '4rem' }}></i>
<h5 className="text-white mt-3">{t('reports.noOverdue')}</h5>
<p className="text-slate-400">{t('reports.noOverdueDescription')}</p>
</div>
</div>
</div>
) : (
<div className="col-12">
<div className="card border-0" style={{ background: '#0f172a' }}>
<div className="card-header border-0 bg-transparent">
<h6 className="text-white mb-0">
<i className="bi bi-exclamation-triangle text-danger me-2"></i>
{t('reports.overdueList')}
</h6>
</div>
<div className="card-body p-0">
<div className="table-responsive">
<table className="table table-dark table-hover mb-0">
<thead>
<tr>
<th>{t('common.description')}</th>
<th>{t('reports.dueDate')}</th>
<th>{t('reports.daysOverdue')}</th>
<th className="text-end">{t('reports.amount')}</th>
</tr>
</thead>
<tbody>
{data.map(item => (
<tr key={item.id}>
<td>{item.description}</td>
<td className="text-slate-400">{item.due_date}</td>
<td>
<span className="badge bg-danger">{item.days_overdue} {t('common.days')}</span>
</td>
<td className="text-end text-danger fw-bold">
{currency(item.amount, item.currency)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
)}
</div>
);
};
const renderContent = () => {
if (loading) {
return (
<div className="text-center py-5">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">{t('common.loading')}</span>
</div>
</div>
);
}
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 (
<div className="reports-container">
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 className="text-white mb-1 fw-bold">
<i className="bi bi-bar-chart-line me-2"></i>
{t('reports.title')}
</h4>
<p className="text-slate-400 mb-0 small">{t('reports.subtitle')}</p>
</div>
</div>
{/* Tabs */}
<div className="card border-0 mb-4" style={{ background: '#0f172a' }}>
<div className="card-body p-2">
<div className="d-flex flex-wrap gap-2">
{tabs.map(tab => (
<button
key={tab.id}
className={`btn btn-sm ${activeTab === tab.id ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => setActiveTab(tab.id)}
>
<i className={`bi ${tab.icon} me-1`}></i>
{tab.label}
</button>
))}
</div>
</div>
</div>
{/* Content */}
{renderContent()}
</div>
);
};
export default Reports;