diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ddfb34d..fbbeb9b 100755 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,7 +4,7 @@ **IMPORTANTE:** Estamos trabalhando diretamente no servidor de produção. -- **Hostname:** mail.cnxifly.com +- **Hostname:** cnxifly.com - **IP:** 213.165.93.60 - **Sistema:** Ubuntu 24.04.3 LTS - **Workspace:** `/root/webmoney` (symlink: `/var/www/webmoney`) @@ -55,7 +55,7 @@ cd /root/webmoney/backend && php artisan config:clear && php artisan cache:clear cd /root/webmoney git add -A && git commit -m "descrição" && git push origin main -# 6. Atualizar VERSION e CHANGELOG.md se necessário +# 6. Atualizar VERSION, README, APRENDIZEDOS_TECNICOS e CHANGELOG.md se necessário ``` ### ❌ Proibições Git diff --git a/backend/app/Http/Controllers/Api/TransactionController.php b/backend/app/Http/Controllers/Api/TransactionController.php index 59671ea..0c51153 100755 --- a/backend/app/Http/Controllers/Api/TransactionController.php +++ b/backend/app/Http/Controllers/Api/TransactionController.php @@ -393,7 +393,12 @@ public function duplicate(Request $request, Transaction $transaction): JsonRespo public function byWeek(Request $request): JsonResponse { $userId = $request->user()->id; - $perPage = $request->get('per_page', 10); // Semanas por página + + // 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 $page = $request->get('page', 1); $currency = $request->get('currency'); // Filtro de divisa opcional $dateField = $request->get('date_field', 'planned_date'); @@ -432,11 +437,41 @@ public function byWeek(Request $request): JsonResponse $query->where('status', $request->status); } - // Filtro por período - if ($request->has('start_date') && $request->has('end_date')) { - $query->inPeriod($request->start_date, $request->end_date, $dateField); - } else { - // Sem filtro de data: não mostrar transações futuras + // Filtro por período - aceita filtros parciais + $hasDateFilter = $request->has('start_date') || $request->has('end_date'); + + // Para effective_date, usar COALESCE com planned_date como fallback + $dateColumn = $dateField === 'effective_date' + ? DB::raw('COALESCE(effective_date, planned_date)') + : $dateField; + + if ($hasDateFilter) { + if ($request->has('start_date') && $request->has('end_date')) { + // Ambas as datas especificadas + if ($dateField === 'effective_date') { + $query->whereRaw('COALESCE(effective_date, planned_date) >= ?', [$request->start_date]) + ->whereRaw('COALESCE(effective_date, planned_date) <= ?', [$request->end_date]); + } else { + $query->inPeriod($request->start_date, $request->end_date, $dateField); + } + } elseif ($request->has('start_date')) { + // Apenas data inicial - mostrar a partir desta data + if ($dateField === 'effective_date') { + $query->whereRaw('COALESCE(effective_date, planned_date) >= ?', [$request->start_date]); + } else { + $query->where($dateField, '>=', $request->start_date); + } + } elseif ($request->has('end_date')) { + // Apenas data final - mostrar até esta data (incluindo futuras) + if ($dateField === 'effective_date') { + $query->whereRaw('COALESCE(effective_date, planned_date) <= ?', [$request->end_date]); + } else { + $query->where($dateField, '<=', $request->end_date); + } + } + } elseif (!$hasActiveFilters) { + // Sem filtro de data E sem filtros ativos: não mostrar transações futuras + // Quando há filtros ativos, mostrar todas as transações (incluindo futuras) $query->where('planned_date', '<=', now()->toDateString()); } diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 36b5ac3..6183fb0 100755 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -680,7 +680,10 @@ "quickCostCenter": { "codePlaceholder": "E.g.: CC001", "codeHelp": "Optional code to identify the cost center" - } + }, + "filteredResults": "Filtered Results", + "filterActive": "Filters active", + "uncategorized": "Uncategorized" }, "currencies": { "BRL": "Brazilian Real", diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json index c7ecde2..2436752 100755 --- a/frontend/src/i18n/locales/es.json +++ b/frontend/src/i18n/locales/es.json @@ -688,7 +688,10 @@ "quickCostCenter": { "codePlaceholder": "Ej: CC001", "codeHelp": "Código opcional para identificar el centro de costo" - } + }, + "filteredResults": "Resultados Filtrados", + "filterActive": "Filtros activos", + "uncategorized": "Sin Categoría" }, "currencies": { "BRL": "Real Brasileño", diff --git a/frontend/src/i18n/locales/pt-BR.json b/frontend/src/i18n/locales/pt-BR.json index 30f4a04..aa79885 100755 --- a/frontend/src/i18n/locales/pt-BR.json +++ b/frontend/src/i18n/locales/pt-BR.json @@ -690,7 +690,10 @@ "quickCostCenter": { "codePlaceholder": "Ex: CC001", "codeHelp": "Código opcional para identificar o centro de custo" - } + }, + "filteredResults": "Resultados Filtrados", + "filterActive": "Filtros ativos", + "uncategorized": "Sem Categoria" }, "currencies": { "BRL": "Real Brasileiro", diff --git a/frontend/src/pages/TransactionsByWeek.jsx b/frontend/src/pages/TransactionsByWeek.jsx index 91851c2..2d3fbf4 100755 --- a/frontend/src/pages/TransactionsByWeek.jsx +++ b/frontend/src/pages/TransactionsByWeek.jsx @@ -1059,6 +1059,9 @@ export default function Transactions() { const currentCurrencyData = weeklyData?.data?.[selectedCurrency]; const pagination = currentCurrencyData?.pagination; const weeks = currentCurrencyData?.weeks || []; + + // Criar lista flat de todas as transações para modo filtrado + const allTransactions = weeks.flatMap(week => week.transactions || []); // Calcular totais gerais const totalStats = weeks.reduce((acc, week) => ({ @@ -1301,8 +1304,258 @@ export default function Transactions() { )} - {/* Weeks List */} - {!loading && weeks.length > 0 && ( + {/* Filtered Transactions List (flat view when filters are active) */} + {!loading && hasActiveFilters && allTransactions.length > 0 && ( +
+
+ {/* Header mostrando info do filtro */} +
+
+
+ +
+
+

+ {t('transactions.filteredResults') || 'Resultados Filtrados'} +
+ {allTransactions.length} +
+

+
+ + {t('transactions.filterActive') || 'Filtros ativos'} +
+
+
+ + {/* Summary totals */} +
+
+
{t('transactions.credits')}
+
+{formatCurrency(totalStats.credits, selectedCurrency)}
+
+
+
{t('transactions.debits')}
+
-{formatCurrency(totalStats.debits, selectedCurrency)}
+
+
+
{t('transactions.balance')}
+
= 0 ? 'balance-pos' : 'balance-neg'}`}> + {(totalStats.credits - totalStats.debits) >= 0 ? '+' : ''}{formatCurrency(totalStats.credits - totalStats.debits, selectedCurrency)} +
+
+
+
+ + {/* All transactions in flat list */} +
+ {isMobile ? ( + // Mobile: Cards Layout +
+ {allTransactions.map(transaction => ( +
+
+ {/* Header: Date + Type Badge + Status */} +
+
+ handleToggleTransaction(transaction.id)} + onClick={(e) => e.stopPropagation()} + /> + + {formatDate(transaction.effective_date || transaction.planned_date)} + {transaction.is_overdue && } + +
+
+ + {transaction.type === 'credit' ? t('transactions.type.credit') : t('transactions.type.debit')} + + + {t(`transactions.status.${transaction.status}`)} + +
+
+ + {/* Description */} +
openDetailModal(transaction)}> +
+ {transaction.is_transfer && } + {transaction.is_reconciled && } + + {transaction.description} + +
+
+ + {/* Account + Category */} +
+ + + {transaction.account?.name} + + {transaction.category && ( + + + {transaction.category.name} + + )} +
+ + {/* Amount + Actions */} +
+
+ {transaction.type === 'credit' ? '+' : '-'} + {formatCurrency(transaction.amount || transaction.planned_amount, selectedCurrency)} +
+ +
+
+
+ ))} +
+ ) : ( + // Desktop: Table Layout + + + + + + + + + + + + + + + + {allTransactions.map(transaction => ( + + + + + + + + + + + + ))} + +
+ 0 && allTransactions.every(t => selectedTransactionIds.has(t.id))} + onChange={() => { + const allIds = allTransactions.map(t => t.id); + const allSelected = allIds.every(id => selectedTransactionIds.has(id)); + setSelectedTransactionIds(prev => { + const next = new Set(prev); + allIds.forEach(id => allSelected ? next.delete(id) : next.add(id)); + return next; + }); + }} + /> + {t('transactions.date')}{t('transactions.description')}{t('transactions.account')}{t('transactions.category')}{t('transactions.amount')}{t('transactions.type.label')}{t('transactions.status.label')}
+ handleToggleTransaction(transaction.id)} + /> + + + {formatDate(transaction.effective_date || transaction.planned_date)} + {transaction.is_overdue && } + + +
+ {transaction.is_transfer && } + {transaction.is_reconciled && ( + + + + )} + 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}`)} + + + +
+ )} +
+
+
+ )} + + {/* Weeks List (grouped view when NO filters are active) */} + {!loading && !hasActiveFilters && weeks.length > 0 && (
{weeks.map((week) => (