feat: opções de visualização de transações (semanal, mensal, todas)
- Adicionado seletor de viewMode: 'week', 'month', 'all' - Backend ajustado para trazer todos os dados quando viewMode é month/all - Agrupamento por mês no frontend - Lista flat para visualização 'todas' - Traduções i18n para pt-BR, en, es
This commit is contained in:
parent
44bc999840
commit
27d7e91896
@ -397,8 +397,13 @@ public function byWeek(Request $request): JsonResponse
|
|||||||
// Verificar se há filtros ativos (além de date_field e currency)
|
// 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']);
|
$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
|
// Verificar modo de visualização
|
||||||
$perPage = $hasActiveFilters ? 100 : $request->get('per_page', 10); // Mais semanas quando filtrado
|
$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);
|
$page = $request->get('page', 1);
|
||||||
$currency = $request->get('currency'); // Filtro de divisa opcional
|
$currency = $request->get('currency'); // Filtro de divisa opcional
|
||||||
$dateField = $request->get('date_field', 'planned_date');
|
$dateField = $request->get('date_field', 'planned_date');
|
||||||
|
|||||||
@ -569,6 +569,13 @@
|
|||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"leaveEmptyForPlanned": "Leave empty to use planned amount",
|
"leaveEmptyForPlanned": "Leave empty to use planned amount",
|
||||||
"week": "Week",
|
"week": "Week",
|
||||||
|
"weekly": "Weekly",
|
||||||
|
"monthly": "Monthly",
|
||||||
|
"all": "All",
|
||||||
|
"viewWeekly": "Weekly view",
|
||||||
|
"viewMonthly": "Monthly view",
|
||||||
|
"viewAll": "View all",
|
||||||
|
"allTransactions": "All Transactions",
|
||||||
"type": {
|
"type": {
|
||||||
"label": "Type",
|
"label": "Type",
|
||||||
"credit": "Credit",
|
"credit": "Credit",
|
||||||
|
|||||||
@ -577,6 +577,13 @@
|
|||||||
"amount": "Valor",
|
"amount": "Valor",
|
||||||
"leaveEmptyForPlanned": "Dejar vacío para usar el valor previsto",
|
"leaveEmptyForPlanned": "Dejar vacío para usar el valor previsto",
|
||||||
"week": "Semana",
|
"week": "Semana",
|
||||||
|
"weekly": "Semanal",
|
||||||
|
"monthly": "Mensual",
|
||||||
|
"all": "Todas",
|
||||||
|
"viewWeekly": "Vista semanal",
|
||||||
|
"viewMonthly": "Vista mensual",
|
||||||
|
"viewAll": "Ver todas",
|
||||||
|
"allTransactions": "Todas las Transacciones",
|
||||||
"type": {
|
"type": {
|
||||||
"label": "Tipo",
|
"label": "Tipo",
|
||||||
"credit": "Crédito",
|
"credit": "Crédito",
|
||||||
|
|||||||
@ -579,6 +579,13 @@
|
|||||||
"amount": "Valor",
|
"amount": "Valor",
|
||||||
"leaveEmptyForPlanned": "Deixe vazio para usar o valor previsto",
|
"leaveEmptyForPlanned": "Deixe vazio para usar o valor previsto",
|
||||||
"week": "Semana",
|
"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": {
|
"type": {
|
||||||
"label": "Tipo",
|
"label": "Tipo",
|
||||||
"credit": "Crédito",
|
"credit": "Crédito",
|
||||||
|
|||||||
@ -41,6 +41,9 @@ export default function Transactions() {
|
|||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [perPage] = useState(5); // Semanas por página
|
const [perPage] = useState(5); // Semanas por página
|
||||||
|
|
||||||
|
// Estado de visualização: 'week', 'month', 'all'
|
||||||
|
const [viewMode, setViewMode] = useState('week');
|
||||||
|
|
||||||
// Estados de filtro
|
// Estados de filtro
|
||||||
const [filters, setFilters] = useState({
|
const [filters, setFilters] = useState({
|
||||||
account_id: '',
|
account_id: '',
|
||||||
@ -212,6 +215,7 @@ export default function Transactions() {
|
|||||||
page,
|
page,
|
||||||
per_page: perPage,
|
per_page: perPage,
|
||||||
currency: selectedCurrency,
|
currency: selectedCurrency,
|
||||||
|
view_mode: viewMode, // Enviar modo de visualização para o backend
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remover params vazios
|
// Remover params vazios
|
||||||
@ -253,7 +257,7 @@ export default function Transactions() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadWeeklyData();
|
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
|
// Mobile resize detection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -963,6 +967,12 @@ export default function Transactions() {
|
|||||||
return `${startDay} ${startMonth} - ${endDay} ${endMonth}`;
|
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
|
// Funções de seleção de transações
|
||||||
const getAllVisibleTransactionIds = () => {
|
const getAllVisibleTransactionIds = () => {
|
||||||
const ids = [];
|
const ids = [];
|
||||||
@ -1060,9 +1070,57 @@ export default function Transactions() {
|
|||||||
const pagination = currentCurrencyData?.pagination;
|
const pagination = currentCurrencyData?.pagination;
|
||||||
const weeks = currentCurrencyData?.weeks || [];
|
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 || []);
|
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
|
// Calcular totais gerais
|
||||||
const totalStats = weeks.reduce((acc, week) => ({
|
const totalStats = weeks.reduce((acc, week) => ({
|
||||||
credits: acc.credits + (week.summary?.credits?.total || 0),
|
credits: acc.credits + (week.summary?.credits?.total || 0),
|
||||||
@ -1119,6 +1177,43 @@ export default function Transactions() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* View Mode Selector + Stats */}
|
||||||
|
<div className="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
|
||||||
|
{/* View Mode Buttons */}
|
||||||
|
{!hasActiveFilters && (
|
||||||
|
<div className="btn-group" role="group">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm ${viewMode === 'week' ? 'btn-primary' : 'btn-outline-secondary'}`}
|
||||||
|
onClick={() => setViewMode('week')}
|
||||||
|
title={t('transactions.viewWeekly')}
|
||||||
|
>
|
||||||
|
<i className="bi bi-calendar-week me-1"></i>
|
||||||
|
<span className="d-none d-sm-inline">{t('transactions.weekly')}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm ${viewMode === 'month' ? 'btn-primary' : 'btn-outline-secondary'}`}
|
||||||
|
onClick={() => setViewMode('month')}
|
||||||
|
title={t('transactions.viewMonthly')}
|
||||||
|
>
|
||||||
|
<i className="bi bi-calendar-month me-1"></i>
|
||||||
|
<span className="d-none d-sm-inline">{t('transactions.monthly')}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm ${viewMode === 'all' ? 'btn-primary' : 'btn-outline-secondary'}`}
|
||||||
|
onClick={() => setViewMode('all')}
|
||||||
|
title={t('transactions.viewAll')}
|
||||||
|
>
|
||||||
|
<i className="bi bi-list-ul me-1"></i>
|
||||||
|
<span className="d-none d-sm-inline">{t('transactions.all')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{hasActiveFilters && <div></div>}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="txn-stats">
|
<div className="txn-stats">
|
||||||
<div className="txn-stat-card">
|
<div className="txn-stat-card">
|
||||||
@ -1554,8 +1649,8 @@ export default function Transactions() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Weeks List (grouped view when NO filters are active) */}
|
{/* Weeks List (grouped view when NO filters are active and viewMode is 'week') */}
|
||||||
{!loading && !hasActiveFilters && weeks.length > 0 && (
|
{!loading && !hasActiveFilters && viewMode === 'week' && weeks.length > 0 && (
|
||||||
<div className="txn-weeks-container">
|
<div className="txn-weeks-container">
|
||||||
{weeks.map((week) => (
|
{weeks.map((week) => (
|
||||||
<div key={week.year_week} className={`txn-week ${expandedWeeks[week.year_week] ? 'expanded' : ''}`}>
|
<div key={week.year_week} className={`txn-week ${expandedWeeks[week.year_week] ? 'expanded' : ''}`}>
|
||||||
@ -2201,6 +2296,309 @@ export default function Transactions() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Monthly List (grouped view when NO filters and viewMode is 'month') */}
|
||||||
|
{!loading && !hasActiveFilters && viewMode === 'month' && months.length > 0 && (
|
||||||
|
<div className="txn-weeks-container">
|
||||||
|
{months.map((month) => (
|
||||||
|
<div key={month.month_key} className={`txn-week ${expandedWeeks[month.month_key] ? 'expanded' : ''}`}>
|
||||||
|
{/* Month Header */}
|
||||||
|
<div className="txn-week-header" onClick={() => toggleWeekExpansion(month.month_key)}>
|
||||||
|
<div className="txn-week-left">
|
||||||
|
<div className="txn-week-chevron">
|
||||||
|
<i className="bi bi-chevron-right"></i>
|
||||||
|
</div>
|
||||||
|
<div className="txn-week-info">
|
||||||
|
<h3>
|
||||||
|
{formatMonthName(month)}
|
||||||
|
<div className="txn-week-badges">
|
||||||
|
<span className="txn-week-badge count">{month.summary.total_transactions}</span>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Month Summary */}
|
||||||
|
<div className="txn-week-summary">
|
||||||
|
<div className="txn-week-stat">
|
||||||
|
<div className="txn-week-stat-label">{t('transactions.credits')}</div>
|
||||||
|
<div className="txn-week-stat-value credit">+{formatCurrency(month.summary.credits.total, selectedCurrency)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="txn-week-stat">
|
||||||
|
<div className="txn-week-stat-label">{t('transactions.debits')}</div>
|
||||||
|
<div className="txn-week-stat-value debit">-{formatCurrency(month.summary.debits.total, selectedCurrency)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="txn-week-stat">
|
||||||
|
<div className="txn-week-stat-label">{t('transactions.balance')}</div>
|
||||||
|
<div className={`txn-week-stat-value ${month.summary.balance >= 0 ? 'balance-pos' : 'balance-neg'}`}>
|
||||||
|
{month.summary.balance >= 0 ? '+' : ''}{formatCurrency(month.summary.balance, selectedCurrency)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Month Transactions */}
|
||||||
|
{expandedWeeks[month.month_key] && (
|
||||||
|
<div className="txn-week-body">
|
||||||
|
{isMobile ? (
|
||||||
|
// Mobile: Cards Layout
|
||||||
|
<div className="d-flex flex-column gap-2 p-2">
|
||||||
|
{month.transactions.map(transaction => (
|
||||||
|
<div
|
||||||
|
key={transaction.id}
|
||||||
|
ref={transaction.id === highlightedTransactionId ? highlightedRef : null}
|
||||||
|
className={`card border-secondary ${transaction.is_overdue ? 'border-danger' : ''} ${transaction.id === highlightedTransactionId ? 'border-primary' : ''}`}
|
||||||
|
style={{ background: '#0f172a', cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
<div className="card-body p-3">
|
||||||
|
<div className="d-flex justify-content-between align-items-start mb-2">
|
||||||
|
<span className="text-slate-400" style={{ fontSize: '0.75rem' }}>
|
||||||
|
{formatDate(transaction.effective_date || transaction.planned_date)}
|
||||||
|
</span>
|
||||||
|
<div className="d-flex gap-1">
|
||||||
|
<span className={`badge ${transaction.type === 'credit' ? 'bg-success' : 'bg-danger'} bg-opacity-25 ${transaction.type === 'credit' ? 'text-success' : 'text-danger'}`} style={{ fontSize: '0.65rem' }}>
|
||||||
|
{transaction.type === 'credit' ? t('transactions.type.credit') : t('transactions.type.debit')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2" onClick={() => openDetailModal(transaction)}>
|
||||||
|
<span className="text-white fw-medium" style={{ fontSize: '0.85rem' }}>
|
||||||
|
{transaction.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex flex-wrap gap-2 mb-2">
|
||||||
|
<span className="badge bg-secondary" style={{ fontSize: '0.7rem' }}>
|
||||||
|
<i className="bi bi-wallet2 me-1"></i>
|
||||||
|
{transaction.account?.name}
|
||||||
|
</span>
|
||||||
|
{transaction.category && (
|
||||||
|
<span
|
||||||
|
className="badge"
|
||||||
|
style={{
|
||||||
|
backgroundColor: transaction.category.color + '20',
|
||||||
|
color: transaction.category.color,
|
||||||
|
fontSize: '0.7rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className={`bi ${transaction.category.icon} me-1`}></i>
|
||||||
|
{transaction.category.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="d-flex justify-content-between align-items-center pt-2" style={{ borderTop: '1px solid #334155' }}>
|
||||||
|
<div className={`fw-bold ${transaction.type === 'credit' ? 'text-success' : 'text-danger'}`} style={{ fontSize: '1rem' }}>
|
||||||
|
{transaction.type === 'credit' ? '+' : '-'}
|
||||||
|
{formatCurrency(transaction.amount || transaction.planned_amount, selectedCurrency)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// Desktop: Table Layout
|
||||||
|
<table className="txn-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width: '90px' }} className="col-date">{t('transactions.date')}</th>
|
||||||
|
<th className="col-description">{t('transactions.description')}</th>
|
||||||
|
<th style={{ width: '120px' }} className="col-account">{t('transactions.account')}</th>
|
||||||
|
<th style={{ width: '140px' }} className="col-category">{t('transactions.category')}</th>
|
||||||
|
<th style={{ width: '110px' }} className="text-end col-amount">{t('transactions.amount')}</th>
|
||||||
|
<th style={{ width: '70px' }} className="text-center col-type">{t('transactions.type.label')}</th>
|
||||||
|
<th style={{ width: '80px' }} className="text-center col-status">{t('transactions.status.label')}</th>
|
||||||
|
<th style={{ width: '40px' }} className="text-center col-actions"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{month.transactions.map(transaction => (
|
||||||
|
<tr
|
||||||
|
key={transaction.id}
|
||||||
|
ref={transaction.id === highlightedTransactionId ? highlightedRef : null}
|
||||||
|
className={`${transaction.is_overdue ? 'overdue' : ''} ${transaction.id === highlightedTransactionId ? 'highlighted-transaction' : ''}`}
|
||||||
|
>
|
||||||
|
<td className="col-date">
|
||||||
|
<span className="txn-date">
|
||||||
|
{formatDate(transaction.effective_date || transaction.planned_date)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="col-description">
|
||||||
|
<span className="txn-description" onClick={() => openDetailModal(transaction)}>
|
||||||
|
{transaction.description}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="col-account"><span className="txn-account">{transaction.account?.name}</span></td>
|
||||||
|
<td className="col-category">
|
||||||
|
{transaction.category && (
|
||||||
|
<span
|
||||||
|
className="txn-category-badge"
|
||||||
|
style={{ backgroundColor: transaction.category.color + '20', color: transaction.category.color }}
|
||||||
|
>
|
||||||
|
<i className={`bi ${transaction.category.icon}`}></i>
|
||||||
|
{transaction.category.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="col-amount">
|
||||||
|
<span className={`txn-amount ${transaction.type}`}>
|
||||||
|
{transaction.type === 'credit' ? '+' : '-'}
|
||||||
|
{formatCurrency(transaction.amount || transaction.planned_amount, selectedCurrency)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="text-center col-type">
|
||||||
|
<span className={`txn-type-badge ${transaction.type}`}>
|
||||||
|
{transaction.type === 'credit' ? t('transactions.type.credit') : t('transactions.type.debit')}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="text-center col-status">
|
||||||
|
<span className={`txn-status-badge ${transaction.status}`}>
|
||||||
|
{t(`transactions.status.${transaction.status}`)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="text-center col-actions">
|
||||||
|
<div className="dropdown">
|
||||||
|
<button className="txn-actions-btn" type="button" data-bs-toggle="dropdown">
|
||||||
|
<i className="bi bi-three-dots-vertical"></i>
|
||||||
|
</button>
|
||||||
|
<ul className="dropdown-menu dropdown-menu-end shadow-sm">
|
||||||
|
<li>
|
||||||
|
<button className="dropdown-item" onClick={() => openQuickCategorizeModal(transaction)}>
|
||||||
|
<i className="bi bi-tags text-success me-2"></i>
|
||||||
|
{t('transactions.quickCategorize')}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button className="dropdown-item" onClick={() => openEditModal(transaction)}>
|
||||||
|
<i className="bi bi-pencil text-primary me-2"></i>
|
||||||
|
{t('common.edit')}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* All Transactions List (flat view when NO filters and viewMode is 'all') */}
|
||||||
|
{!loading && !hasActiveFilters && viewMode === 'all' && allTransactions.length > 0 && (
|
||||||
|
<div className="txn-weeks-container">
|
||||||
|
<div className="txn-week expanded">
|
||||||
|
<div className="txn-week-header" style={{ cursor: 'default' }}>
|
||||||
|
<div className="txn-week-left">
|
||||||
|
<div className="txn-week-chevron">
|
||||||
|
<i className="bi bi-list-ul text-info"></i>
|
||||||
|
</div>
|
||||||
|
<div className="txn-week-info">
|
||||||
|
<h3>
|
||||||
|
{t('transactions.allTransactions') || 'Todas as Transações'}
|
||||||
|
<div className="txn-week-badges">
|
||||||
|
<span className="txn-week-badge count">{allTransactions.length}</span>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="txn-week-summary">
|
||||||
|
<div className="txn-week-stat">
|
||||||
|
<div className="txn-week-stat-label">{t('transactions.credits')}</div>
|
||||||
|
<div className="txn-week-stat-value credit">+{formatCurrency(totalStats.credits, selectedCurrency)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="txn-week-stat">
|
||||||
|
<div className="txn-week-stat-label">{t('transactions.debits')}</div>
|
||||||
|
<div className="txn-week-stat-value debit">-{formatCurrency(totalStats.debits, selectedCurrency)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="txn-week-stat">
|
||||||
|
<div className="txn-week-stat-label">{t('transactions.balance')}</div>
|
||||||
|
<div className={`txn-week-stat-value ${(totalStats.credits - totalStats.debits) >= 0 ? 'balance-pos' : 'balance-neg'}`}>
|
||||||
|
{(totalStats.credits - totalStats.debits) >= 0 ? '+' : ''}{formatCurrency(totalStats.credits - totalStats.debits, selectedCurrency)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="txn-week-body">
|
||||||
|
{isMobile ? (
|
||||||
|
<div className="d-flex flex-column gap-2 p-2">
|
||||||
|
{allTransactions.map(transaction => (
|
||||||
|
<div
|
||||||
|
key={transaction.id}
|
||||||
|
className={`card border-secondary ${transaction.is_overdue ? 'border-danger' : ''}`}
|
||||||
|
style={{ background: '#0f172a', cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
<div className="card-body p-3">
|
||||||
|
<div className="d-flex justify-content-between align-items-start mb-2">
|
||||||
|
<span className="text-slate-400" style={{ fontSize: '0.75rem' }}>
|
||||||
|
{formatDate(transaction.effective_date || transaction.planned_date)}
|
||||||
|
</span>
|
||||||
|
<span className={`badge ${transaction.type === 'credit' ? 'bg-success' : 'bg-danger'} bg-opacity-25`} style={{ fontSize: '0.65rem' }}>
|
||||||
|
{transaction.type === 'credit' ? t('transactions.type.credit') : t('transactions.type.debit')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2" onClick={() => openDetailModal(transaction)}>
|
||||||
|
<span className="text-white fw-medium">{transaction.description}</span>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex justify-content-between align-items-center pt-2" style={{ borderTop: '1px solid #334155' }}>
|
||||||
|
<div className={`fw-bold ${transaction.type === 'credit' ? 'text-success' : 'text-danger'}`}>
|
||||||
|
{transaction.type === 'credit' ? '+' : '-'}
|
||||||
|
{formatCurrency(transaction.amount || transaction.planned_amount, selectedCurrency)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<table className="txn-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{ width: '90px' }}>{t('transactions.date')}</th>
|
||||||
|
<th>{t('transactions.description')}</th>
|
||||||
|
<th style={{ width: '120px' }}>{t('transactions.account')}</th>
|
||||||
|
<th style={{ width: '140px' }}>{t('transactions.category')}</th>
|
||||||
|
<th style={{ width: '110px' }} className="text-end">{t('transactions.amount')}</th>
|
||||||
|
<th style={{ width: '70px' }} className="text-center">{t('transactions.type.label')}</th>
|
||||||
|
<th style={{ width: '80px' }} className="text-center">{t('transactions.status.label')}</th>
|
||||||
|
<th style={{ width: '40px' }} className="text-center"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{allTransactions.map(transaction => (
|
||||||
|
<tr key={transaction.id} className={transaction.is_overdue ? 'overdue' : ''}>
|
||||||
|
<td><span className="txn-date">{formatDate(transaction.effective_date || transaction.planned_date)}</span></td>
|
||||||
|
<td><span className="txn-description" onClick={() => openDetailModal(transaction)}>{transaction.description}</span></td>
|
||||||
|
<td><span className="txn-account">{transaction.account?.name}</span></td>
|
||||||
|
<td>
|
||||||
|
{transaction.category && (
|
||||||
|
<span className="txn-category-badge" style={{ backgroundColor: transaction.category.color + '20', color: transaction.category.color }}>
|
||||||
|
<i className={`bi ${transaction.category.icon}`}></i>
|
||||||
|
{transaction.category.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td><span className={`txn-amount ${transaction.type}`}>{transaction.type === 'credit' ? '+' : '-'}{formatCurrency(transaction.amount || transaction.planned_amount, selectedCurrency)}</span></td>
|
||||||
|
<td className="text-center"><span className={`txn-type-badge ${transaction.type}`}>{transaction.type === 'credit' ? t('transactions.type.credit') : t('transactions.type.debit')}</span></td>
|
||||||
|
<td className="text-center"><span className={`txn-status-badge ${transaction.status}`}>{t(`transactions.status.${transaction.status}`)}</span></td>
|
||||||
|
<td className="text-center">
|
||||||
|
<button className="txn-actions-btn" onClick={() => openQuickCategorizeModal(transaction)}><i className="bi bi-tags"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Modal de Criar/Editar */}
|
{/* Modal de Criar/Editar */}
|
||||||
<Modal
|
<Modal
|
||||||
show={showModal}
|
show={showModal}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user