diff --git a/CHANGELOG.md b/CHANGELOG.md index 8166d4a..b02b061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,52 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/). Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/). +## [1.43.0] - 2025-12-16 + +### Added +- **Widget Projeção de Saldo Mobile** - Comportamento colapsável implementado + - Inicia colapsado em mobile, expande automaticamente se houver dados + - Botão toggle chevron up/down no header + - Botões de período (1, 2, 3, 6, 12 meses) otimizados: + - Desktop: btn-group horizontal + - Mobile: layout flex-wrap em 3 colunas (33.333% cada) + - Altura do gráfico ajustada: 300px (mobile) vs 350px (desktop) + - Cards de estatísticas otimizados para mobile: + - Padding reduzido: p-2 (mobile) vs p-3 (desktop) + - Gap menor: g-2 vs g-3 + - Fontes menores: labels 0.7rem, valores 0.9rem + - Percentual de variação oculto em mobile + +- **Widget Análise de Sobrepagamentos Mobile** - UX completamente redesenhada + - Comportamento colapsável com auto-expansão quando há dados + - Header otimizado: texto menor, badge compacto, "(12 meses)" oculto + - Gráfico responsivo: 180px (mobile) vs 220px (desktop) + - Cards de resumo compactos: padding p-1, fontes 0.65-0.85rem + - **Layout de transações revolucionário**: + - Mobile: Cards verticais ao invés de tabela horizontal + - Cada card mostra: descrição, valor (+), categoria, data, percentual + - Fontes otimizadas: 0.6-0.75rem + - Border amarelo sutil para destaque + - Altura máxima: 200px com scroll + - Desktop: Tabela completa mantida com todas as colunas + +### Fixed +- **React Hooks Error #310** - Corrigida violação das regras de hooks + - Problema: `useEffect` sendo chamado após returns condicionais + - Solução: Movido cálculo de `monthsData` para antes dos returns + - Adicionado `useEffect` na importação: `import { useEffect }` + - Todos os hooks agora executam na ordem correta + - Dependências corretas em useEffect: `[isMobile, monthsData.length, isExpanded]` + +### Improved +- **Consistência Mobile Global** - Todos os widgets principais otimizados: + - ✅ Calendário: navegação entre semanas + colapso + - ✅ Próximos 7 Dias: colapso + sync altura desktop + - ✅ Transações em Atraso: colapso + auto-expansão + - ✅ Projeção de Saldo: colapso + botões otimizados + cards compactos + - ✅ Análise Sobrepagamentos: colapso + layout cards mobile + - UX mobile profissional e consistente em todo Dashboard + ## [1.42.0] - 2025-12-16 ### Added diff --git a/VERSION b/VERSION index a50908c..b978278 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.42.0 +1.43.0 diff --git a/frontend/src/components/dashboard/BalanceProjectionChart.jsx b/frontend/src/components/dashboard/BalanceProjectionChart.jsx index 44be99c..31854e7 100644 --- a/frontend/src/components/dashboard/BalanceProjectionChart.jsx +++ b/frontend/src/components/dashboard/BalanceProjectionChart.jsx @@ -35,6 +35,8 @@ const BalanceProjectionChart = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [isMobile, setIsMobile] = useState(window.innerWidth < 768); + const [isExpanded, setIsExpanded] = useState(false); const loadData = useCallback(async () => { setLoading(true); @@ -42,18 +44,36 @@ const BalanceProjectionChart = () => { try { const response = await reportService.getProjectionChart({ months }); setData(response); + + // Auto-expand if we have data points in mobile + if (isMobile && response?.data?.length > 0) { + setIsExpanded(true); + } } catch (err) { console.error('Error loading projection chart:', err); setError(t('common.error')); } finally { setLoading(false); } - }, [months, t]); + }, [months, t, isMobile]); useEffect(() => { loadData(); }, [loadData]); + useEffect(() => { + const handleResize = () => { + const mobile = window.innerWidth < 768; + setIsMobile(mobile); + if (!mobile) { + setIsExpanded(false); + } + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + const periodOptions = [ { value: 1, label: t('reports.projectionChart.1month') || '1 mês' }, { value: 2, label: t('reports.projectionChart.2months') || '2 meses' }, @@ -202,30 +222,44 @@ const BalanceProjectionChart = () => { const changeIcon = summary?.change >= 0 ? 'bi-arrow-up' : 'bi-arrow-down'; return ( -
-
-
-
- - {t('reports.projectionChart.title') || 'Projeção de Saldo'} -
- - {t('reports.projectionChart.subtitle') || 'Evolução prevista do seu saldo'} - -
-
- {periodOptions.map(opt => ( +
+
+
+
+
+ + {t('reports.projectionChart.title') || 'Projeção de Saldo'} +
+ + {t('reports.projectionChart.subtitle') || 'Evolução prevista do seu saldo'} + +
+ {isMobile && ( - ))} + )}
+ {(!isMobile || isExpanded) && ( +
+ {periodOptions.map(opt => ( + + ))} +
+ )}
-
+
{/* Summary Stats */} {summary && ( <> @@ -245,36 +279,46 @@ const BalanceProjectionChart = () => {
)} -
+
-
- {t('reports.projectionChart.currentBalance') || 'Saldo Atual'} - {currency(summary.current_balance, data?.currency)} +
+ + {t('reports.projectionChart.currentBalance') || 'Saldo Atual'} + + + {currency(summary.current_balance, data?.currency)} +
-
- {t('reports.projectionChart.finalBalance') || 'Saldo Final'} - = 0 ? 'text-success' : 'text-danger'}`}> +
+ + {t('reports.projectionChart.finalBalance') || 'Saldo Final'} + + = 0 ? 'text-success' : 'text-danger'} d-block`} style={isMobile ? { fontSize: '0.9rem' } : undefined}> {currency(summary.final_balance, data?.currency)}
-
- {t('reports.projectionChart.minBalance') || 'Saldo Mínimo'} - = 0 ? 'text-warning' : 'text-danger'}`}> +
+ + {t('reports.projectionChart.minBalance') || 'Saldo Mínimo'} + + = 0 ? 'text-warning' : 'text-danger'} d-block`} style={isMobile ? { fontSize: '0.9rem' } : undefined}> {currency(summary.min_balance, data?.currency)}
-
- {t('reports.projectionChart.change') || 'Variação'} - +
+ + {t('reports.projectionChart.change') || 'Variação'} + + {currency(Math.abs(summary.change), data?.currency)} - ({summary.change_percent}%) + {!isMobile && ({summary.change_percent}%)}
@@ -295,7 +339,7 @@ const BalanceProjectionChart = () => { )} {/* Chart */} -
+
diff --git a/frontend/src/components/dashboard/OverpaymentsAnalysis.jsx b/frontend/src/components/dashboard/OverpaymentsAnalysis.jsx index 93742a0..87e1a85 100644 --- a/frontend/src/components/dashboard/OverpaymentsAnalysis.jsx +++ b/frontend/src/components/dashboard/OverpaymentsAnalysis.jsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { Chart as ChartJS, CategoryScale, @@ -29,10 +29,56 @@ const OverpaymentsAnalysis = ({ data, loading, onTransactionClick }) => { const { currency } = useFormatters(); const chartRef = useRef(null); const [selectedMonth, setSelectedMonth] = useState(null); + const [isMobile, setIsMobile] = useState(window.innerWidth < 768); + const [isExpanded, setIsExpanded] = useState(false); // Helper para locale dinâmico const getLocale = () => i18n.language === 'pt-BR' ? 'pt-BR' : i18n.language === 'es' ? 'es-ES' : 'en-US'; + // Agrupar transações por mês (apenas sobrepagamentos - variance > 0) + const overpaymentsByMonth = {}; + if (data?.transactions) { + data.transactions + .filter(t => t.variance > 0) + .forEach(t => { + const month = t.effective_date.substring(0, 7); + if (!overpaymentsByMonth[month]) { + overpaymentsByMonth[month] = { + month, + total: 0, + count: 0, + transactions: [] + }; + } + overpaymentsByMonth[month].total += t.variance; + overpaymentsByMonth[month].count += 1; + overpaymentsByMonth[month].transactions.push(t); + }); + } + + // Criar array de meses ordenados + const monthsData = Object.values(overpaymentsByMonth).sort((a, b) => a.month.localeCompare(b.month)); + + useEffect(() => { + const handleResize = () => { + const mobile = window.innerWidth < 768; + setIsMobile(mobile); + if (!mobile) { + setIsExpanded(false); + } + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + // Auto-expand if we have data in mobile + useEffect(() => { + if (isMobile && monthsData.length > 0 && !isExpanded) { + setIsExpanded(true); + } + }, [isMobile, monthsData.length, isExpanded]); + if (loading) { return (
@@ -65,28 +111,6 @@ const OverpaymentsAnalysis = ({ data, loading, onTransactionClick }) => { ); } - // Agrupar transações por mês (apenas sobrepagamentos - variance > 0) - const overpaymentsByMonth = {}; - data.transactions - .filter(t => t.variance > 0) - .forEach(t => { - const month = t.effective_date.substring(0, 7); - if (!overpaymentsByMonth[month]) { - overpaymentsByMonth[month] = { - month, - total: 0, - count: 0, - transactions: [] - }; - } - overpaymentsByMonth[month].total += t.variance; - overpaymentsByMonth[month].count += 1; - overpaymentsByMonth[month].transactions.push(t); - }); - - // Criar array de meses ordenados - const monthsData = Object.values(overpaymentsByMonth).sort((a, b) => a.month.localeCompare(b.month)); - // Calcular total geral const totalOverpayment = monthsData.reduce((sum, m) => sum + m.total, 0); @@ -180,29 +204,44 @@ const OverpaymentsAnalysis = ({ data, loading, onTransactionClick }) => { const selectedMonthData = selectedMonth !== null ? monthsData[selectedMonth] : null; return ( -
+
{/* Header */} -
-
- - {t('dashboard.overpaymentsAnalysis')} ({data.period?.months || 12} {t('common.months')}) -
- - - Total: {currency(totalOverpayment, 'BRL')} - +
+
+ + + {t('dashboard.overpaymentsAnalysis')} {!isMobile && `(${data.period?.months || 12} ${t('common.months')})`} + +
+ {isMobile && ( + + )} +
+ {(!isMobile || isExpanded) && ( + + + Total: {currency(totalOverpayment, 'BRL')} + + )}
-
+
{/* Instrução */} -

+

{t('dashboard.clickBarToSeeDetails')}

{/* Gráfico */} -
+
@@ -231,33 +270,33 @@ const OverpaymentsAnalysis = ({ data, loading, onTransactionClick }) => {
{/* Resumo do mês */} -
+
-
- +
+ {t('dashboard.totalOverpaid')} - + +{currency(selectedMonthData.total, 'BRL')}
-
- +
+ {t('dashboard.transactions')} - + {selectedMonthData.count}
-
- +
+ {t('dashboard.avgPerTransaction')} - + +{currency(selectedMonthData.total / selectedMonthData.count, 'BRL')}
@@ -265,87 +304,136 @@ const OverpaymentsAnalysis = ({ data, loading, onTransactionClick }) => {
{/* Tabela de transações */} -
- - - - - - - - - - - - - - - {selectedMonthData.transactions.map((tx) => ( - onTransactionClick?.(tx.id)} - style={{ cursor: 'pointer' }} - > - - - - - - - - + + + + ))} + + ) : ( + /* Layout Desktop - Tabela */ +
+
{t('common.date')}{t('common.description')}{t('dashboard.category')}{t('dashboard.plannedValue')}{t('dashboard.actualValue')}{t('dashboard.difference')}{t('dashboard.delay')}%
- {new Date(tx.effective_date).toLocaleDateString(getLocale())} - - - {tx.description.length > 30 ? tx.description.substring(0, 30) + '...' : tx.description} - - - {tx.category ? ( + {isMobile ? ( + /* Layout Mobile - Cards */ +
+ {selectedMonthData.transactions.map((tx) => ( +
onTransactionClick?.(tx.id)} + style={{ + cursor: 'pointer', + background: 'rgba(0,0,0,0.3)', + border: '1px solid rgba(251, 191, 36, 0.2)' + }} + > +
+ + {tx.description.length > 25 ? tx.description.substring(0, 25) + '...' : tx.description} + + + +{currency(tx.variance, 'BRL')} + +
+
+
+ {tx.category && ( {tx.category.name} - ) : ( - - )} -
- {currency(tx.planned_amount, 'BRL')} - - {currency(tx.actual_amount, 'BRL')} - - - - +{currency(tx.variance, 'BRL')} + + {new Date(tx.effective_date).toLocaleDateString(getLocale(), { day: '2-digit', month: '2-digit' })} - - {tx.delay_days !== null && tx.delay_days !== 0 ? ( - 0 ? 'rgba(239, 68, 68, 0.8)' : 'rgba(34, 197, 94, 0.8)' - }} - > - 0 ? '-history' : ''} me-1`} style={{ fontSize: '9px' }}> - {tx.delay_days > 0 ? `+${tx.delay_days}d` : `${tx.delay_days}d`} - - ) : ( - - - )} - + + +{tx.variance_percent}% -
+ + + + + + + + + + - ))} - -
{t('common.date')}{t('common.description')}{t('dashboard.category')}{t('dashboard.plannedValue')}{t('dashboard.actualValue')}{t('dashboard.difference')}{t('dashboard.delay')}%
-
+ + + {selectedMonthData.transactions.map((tx) => ( + onTransactionClick?.(tx.id)} + style={{ cursor: 'pointer' }} + > + + {new Date(tx.effective_date).toLocaleDateString(getLocale())} + + + + {tx.description.length > 30 ? tx.description.substring(0, 30) + '...' : tx.description} + + + + {tx.category ? ( + + {tx.category.name} + + ) : ( + - + )} + + + {currency(tx.planned_amount, 'BRL')} + + + {currency(tx.actual_amount, 'BRL')} + + + + + +{currency(tx.variance, 'BRL')} + + + + {tx.delay_days !== null && tx.delay_days !== 0 ? ( + 0 ? 'rgba(239, 68, 68, 0.8)' : 'rgba(34, 197, 94, 0.8)' + }} + > + 0 ? '-history' : ''} me-1`} style={{ fontSize: '9px' }}> + {tx.delay_days > 0 ? `+${tx.delay_days}d` : `${tx.delay_days}d`} + + ) : ( + - + )} + + + +{tx.variance_percent}% + + + ))} + + +
+ )}
)}