v1.34.1: UI/UX melhorada do resumo de relatórios

Improved:
- Cards KPI redesenhados com ícones e melhor hierarquia
- Badges de variação com contraste aprimorado
- Métricas de média mensal adicionadas
- Card de Taxa de Poupança com barra de progresso
- Feedback inteligente (Excelente/Boa/Pode melhorar)
- Gráfico comparativo com tooltips e bordas arredondadas
- Tradução completa (pt-BR, en, es)
This commit is contained in:
marcoitaloesp-ai 2025-12-15 12:18:34 +00:00 committed by GitHub
parent f9571656d5
commit 82e1d7a884
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 205 additions and 45 deletions

View File

@ -5,6 +5,22 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/).
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
## [1.34.1] - 2025-12-15
### Improved
- **UI/UX do Resumo de Relatórios** - Interface redesenhada com visual mais profissional
- Cards KPI com ícones grandes e hierarquia visual clara
- Badges de variação com fundo branco para melhor contraste
- Adicionadas métricas de média mensal em todos os cards
- Novo card dedicado para Taxa de Poupança com barra de progresso
- Feedback visual inteligente baseado na taxa de poupança:
* 🎯 ≥20%: "Excelente poupança!"
* 👍 ≥10%: "Boa poupança"
* 💡 <10%: "Pode melhorar"
- Gráfico comparativo melhorado com bordas arredondadas e tooltips aprimorados
- Header do resumo com ícone e seletor de ano redesenhado
- Tradução completa: pt-BR, en, es
## [1.34.0] - 2025-12-14
### Fixed

View File

@ -1 +1 @@
1.34.0
1.34.1

View File

@ -1834,7 +1834,14 @@
"topExpenses": "Top Expenses",
"vsAverage": "vs Average",
"vsLastPeriod": "vs Last Period",
"vsLastYear": "vs last year",
"yearComparison": "Year Comparison",
"annualSummary": "Annual Summary",
"monthlyAverage": "Monthly Average",
"excellentSavings": "Excellent savings!",
"goodSavings": "Good savings",
"canImprove": "Can improve",
"total": "Total",
"expenseDistribution": "Expense Distribution",
"categoryDetail": "Category Detail",
"category": "Category",

View File

@ -1801,6 +1801,14 @@
"avgIncome": "Ingreso promedio",
"avgExpense": "Gasto promedio",
"vsLastPeriod": "vs período anterior",
"vsLastYear": "vs año anterior",
"yearComparison": "Comparación Anual",
"annualSummary": "Resumen Anual",
"monthlyAverage": "Promedio Mensual",
"excellentSavings": "¡Excelente ahorro!",
"goodSavings": "Buen ahorro",
"canImprove": "Puede mejorar",
"total": "Total",
"dayOfWeek": {
"sunday": "Domingo",
"monday": "Lunes",

View File

@ -1836,7 +1836,14 @@
"topExpenses": "Maiores Despesas",
"vsAverage": "vs Média",
"vsLastPeriod": "vs Período Anterior",
"vsLastYear": "vs ano anterior",
"yearComparison": "Comparativo Anual",
"annualSummary": "Resumo Anual",
"monthlyAverage": "Média Mensal",
"excellentSavings": "Excelente poupança!",
"goodSavings": "Boa poupança",
"canImprove": "Pode melhorar",
"total": "Total",
"expenseDistribution": "Distribuição de Despesas",
"categoryDetail": "Detalhes por Categoria",
"category": "Categoria",

View File

@ -169,64 +169,152 @@ const Reports = () => {
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 gap-2 mb-3">
{[2024, 2025].map(y => (
<button
key={y}
className={`btn btn-sm ${year === y ? 'btn-primary' : 'btn-outline-secondary'}`}
onClick={() => setYear(y)}
>
{y}
</button>
))}
<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>
{/* Summary Cards */}
<div className="col-md-4">
{/* 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">
<h6 className="opacity-75">{t('reports.income')} {year}</h6>
<h3 className="mb-2">{currency(summary.current.income, summary.currency)}</h3>
<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 && (
<span className={`badge ${summary.variation.income >= 0 ? 'bg-success' : 'bg-danger'}`}>
<i className={`bi bi-arrow-${summary.variation.income >= 0 ? 'up' : 'down'} me-1`}></i>
{Math.abs(summary.variation.income)}% {t('reports.vsLastPeriod')}
</span>
<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-md-4">
<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">
<h6 className="opacity-75">{t('reports.expenses')} {year}</h6>
<h3 className="mb-2">{currency(summary.current.expense, summary.currency)}</h3>
<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 && (
<span className={`badge ${summary.variation.expense <= 0 ? 'bg-success' : 'bg-danger'}`}>
<i className={`bi bi-arrow-${summary.variation.expense >= 0 ? 'up' : 'down'} me-1`}></i>
{Math.abs(summary.variation.expense)}% {t('reports.vsLastPeriod')}
</span>
<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-md-4">
<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">
<h6 className="opacity-75">{t('reports.balance')} {year}</h6>
<h3 className="mb-2">{currency(summary.current.balance, summary.currency)}</h3>
<span className="small opacity-75">
{t('reports.savingsRate')}: {summary.current.income > 0
? ((summary.current.balance / summary.current.income) * 100).toFixed(1)
: 0}%
</span>
<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 && (
<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>
@ -235,12 +323,17 @@ const Reports = () => {
<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.yearComparison')}
</h6>
<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: '300px' }}>
<div className="card-body" style={{ height: '320px' }}>
<Bar
data={{
labels: [t('common.incomes'), t('common.expenses'), t('common.balance')],
@ -248,20 +341,49 @@ const Reports = () => {
{
label: String(year - 1),
data: [summary.previous.income, summary.previous.expense, summary.previous.balance],
backgroundColor: 'rgba(148, 163, 184, 0.5)',
borderColor: '#94a3b8',
borderWidth: 1,
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.7)', 'rgba(239, 68, 68, 0.7)', 'rgba(59, 130, 246, 0.7)'],
backgroundColor: ['rgba(16, 185, 129, 0.8)', 'rgba(239, 68, 68, 0.8)', 'rgba(59, 130, 246, 0.8)'],
borderColor: ['#10b981', '#ef4444', '#3b82f6'],
borderWidth: 1,
borderWidth: 2,
borderRadius: 6,
},
],
}}
options={chartOptions}
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>