diff --git a/backend/app/Http/Controllers/Api/TransactionController.php b/backend/app/Http/Controllers/Api/TransactionController.php index 0c51153..9d714b2 100755 --- a/backend/app/Http/Controllers/Api/TransactionController.php +++ b/backend/app/Http/Controllers/Api/TransactionController.php @@ -397,8 +397,13 @@ public function byWeek(Request $request): JsonResponse // Verificar se há filtros ativos (além de date_field e currency) $hasActiveFilters = $request->hasAny(['account_id', 'category_id', 'cost_center_id', 'type', 'status', 'search', 'start_date', 'end_date']); - // Se há filtros, trazer mais semanas para mostrar todos os resultados - $perPage = $hasActiveFilters ? 100 : $request->get('per_page', 10); // Mais semanas quando filtrado + // Verificar modo de visualização + $viewMode = $request->get('view_mode', 'week'); // 'week', 'month', 'all' + + // Determinar quantidade de semanas por página + // Se há filtros OU viewMode é 'month' ou 'all', trazer todas as semanas + $shouldFetchAll = $hasActiveFilters || in_array($viewMode, ['month', 'all']); + $perPage = $shouldFetchAll ? 1000 : $request->get('per_page', 10); $page = $request->get('page', 1); $currency = $request->get('currency'); // Filtro de divisa opcional $dateField = $request->get('date_field', 'planned_date'); diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 6183fb0..fd2890b 100755 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -569,6 +569,13 @@ "amount": "Amount", "leaveEmptyForPlanned": "Leave empty to use planned amount", "week": "Week", + "weekly": "Weekly", + "monthly": "Monthly", + "all": "All", + "viewWeekly": "Weekly view", + "viewMonthly": "Monthly view", + "viewAll": "View all", + "allTransactions": "All Transactions", "type": { "label": "Type", "credit": "Credit", diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json index 2436752..4947717 100755 --- a/frontend/src/i18n/locales/es.json +++ b/frontend/src/i18n/locales/es.json @@ -577,6 +577,13 @@ "amount": "Valor", "leaveEmptyForPlanned": "Dejar vacío para usar el valor previsto", "week": "Semana", + "weekly": "Semanal", + "monthly": "Mensual", + "all": "Todas", + "viewWeekly": "Vista semanal", + "viewMonthly": "Vista mensual", + "viewAll": "Ver todas", + "allTransactions": "Todas las Transacciones", "type": { "label": "Tipo", "credit": "Crédito", diff --git a/frontend/src/i18n/locales/pt-BR.json b/frontend/src/i18n/locales/pt-BR.json index aa79885..5b91f3b 100755 --- a/frontend/src/i18n/locales/pt-BR.json +++ b/frontend/src/i18n/locales/pt-BR.json @@ -579,6 +579,13 @@ "amount": "Valor", "leaveEmptyForPlanned": "Deixe vazio para usar o valor previsto", "week": "Semana", + "weekly": "Semanal", + "monthly": "Mensal", + "all": "Todas", + "viewWeekly": "Visualização semanal", + "viewMonthly": "Visualização mensal", + "viewAll": "Ver todas", + "allTransactions": "Todas as Transações", "type": { "label": "Tipo", "credit": "Crédito", diff --git a/frontend/src/pages/TransactionsByWeek.jsx b/frontend/src/pages/TransactionsByWeek.jsx index 2d3fbf4..0d446fd 100755 --- a/frontend/src/pages/TransactionsByWeek.jsx +++ b/frontend/src/pages/TransactionsByWeek.jsx @@ -40,6 +40,9 @@ export default function Transactions() { // Estados de paginação const [page, setPage] = useState(1); const [perPage] = useState(5); // Semanas por página + + // Estado de visualização: 'week', 'month', 'all' + const [viewMode, setViewMode] = useState('week'); // Estados de filtro const [filters, setFilters] = useState({ @@ -212,6 +215,7 @@ export default function Transactions() { page, per_page: perPage, currency: selectedCurrency, + view_mode: viewMode, // Enviar modo de visualização para o backend }; // Remover params vazios @@ -253,7 +257,7 @@ export default function Transactions() { useEffect(() => { loadWeeklyData(); - }, [filters, page, selectedCurrency]); // eslint-disable-line react-hooks/exhaustive-deps + }, [filters, page, selectedCurrency, viewMode]); // eslint-disable-line react-hooks/exhaustive-deps // Mobile resize detection useEffect(() => { @@ -962,6 +966,12 @@ export default function Transactions() { } return `${startDay} ${startMonth} - ${endDay} ${endMonth}`; }; + + // Formatar nome do mês + const formatMonthName = (month) => { + const date = new Date(month.year, month.month - 1, 1); + return date.toLocaleDateString(getLocale(), { month: 'long', year: 'numeric' }); + }; // Funções de seleção de transações const getAllVisibleTransactionIds = () => { @@ -1060,8 +1070,56 @@ export default function Transactions() { const pagination = currentCurrencyData?.pagination; const weeks = currentCurrencyData?.weeks || []; - // Criar lista flat de todas as transações para modo filtrado + // Criar lista flat de todas as transações para modo filtrado ou "todas" const allTransactions = weeks.flatMap(week => week.transactions || []); + + // Agrupar transações por mês para viewMode 'month' + const months = React.useMemo(() => { + if (viewMode !== 'month' || hasActiveFilters) return []; + + const monthMap = {}; + allTransactions.forEach(txn => { + const date = txn.effective_date || txn.planned_date; + const monthKey = date.substring(0, 7); // "YYYY-MM" + if (!monthMap[monthKey]) { + monthMap[monthKey] = { + month_key: monthKey, + year: parseInt(monthKey.split('-')[0]), + month: parseInt(monthKey.split('-')[1]), + transactions: [], + summary: { + credits: { total: 0, count: 0 }, + debits: { total: 0, count: 0 }, + pending: { total: 0, count: 0 }, + total_transactions: 0, + balance: 0, + } + }; + } + monthMap[monthKey].transactions.push(txn); + monthMap[monthKey].summary.total_transactions++; + + const amount = txn.amount || txn.planned_amount; + if (txn.type === 'credit') { + monthMap[monthKey].summary.credits.total += amount; + monthMap[monthKey].summary.credits.count++; + } else { + monthMap[monthKey].summary.debits.total += amount; + monthMap[monthKey].summary.debits.count++; + } + if (txn.status === 'pending') { + monthMap[monthKey].summary.pending.total += amount; + monthMap[monthKey].summary.pending.count++; + } + }); + + // Calcular balance e ordenar + Object.values(monthMap).forEach(m => { + m.summary.balance = m.summary.credits.total - m.summary.debits.total; + }); + + return Object.values(monthMap).sort((a, b) => b.month_key.localeCompare(a.month_key)); + }, [allTransactions, viewMode, hasActiveFilters]); // Calcular totais gerais const totalStats = weeks.reduce((acc, week) => ({ @@ -1119,6 +1177,43 @@ export default function Transactions() { + {/* View Mode Selector + Stats */} +
| {t('transactions.date')} | +{t('transactions.description')} | +{t('transactions.account')} | +{t('transactions.category')} | +{t('transactions.amount')} | +{t('transactions.type.label')} | +{t('transactions.status.label')} | ++ |
|---|---|---|---|---|---|---|---|
| + + {formatDate(transaction.effective_date || transaction.planned_date)} + + | ++ openDetailModal(transaction)}> + {transaction.description} + + | +{transaction.account?.name} | ++ {transaction.category && ( + + + {transaction.category.name} + + )} + | ++ + {transaction.type === 'credit' ? '+' : '-'} + {formatCurrency(transaction.amount || transaction.planned_amount, selectedCurrency)} + + | ++ + {transaction.type === 'credit' ? t('transactions.type.credit') : t('transactions.type.debit')} + + | ++ + {t(`transactions.status.${transaction.status}`)} + + | +
+
+
+
+
|
+
| {t('transactions.date')} | +{t('transactions.description')} | +{t('transactions.account')} | +{t('transactions.category')} | +{t('transactions.amount')} | +{t('transactions.type.label')} | +{t('transactions.status.label')} | ++ |
|---|---|---|---|---|---|---|---|
| {formatDate(transaction.effective_date || transaction.planned_date)} | +openDetailModal(transaction)}>{transaction.description} | +{transaction.account?.name} | ++ {transaction.category && ( + + + {transaction.category.name} + + )} + | +{transaction.type === 'credit' ? '+' : '-'}{formatCurrency(transaction.amount || transaction.planned_amount, selectedCurrency)} | +{transaction.type === 'credit' ? t('transactions.type.credit') : t('transactions.type.debit')} | +{t(`transactions.status.${transaction.status}`)} | ++ + | +