import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { accountService, liabilityAccountService, assetAccountService } from '../services/api'; import { useToast } from '../components/Toast'; import { ConfirmModal } from '../components/Modal'; import IconSelector from '../components/IconSelector'; import CurrencySelector from '../components/CurrencySelector'; import { useFormatters } from '../hooks'; import AssetWizard from '../components/AssetWizard'; import AccountWizard from '../components/AccountWizard'; const Accounts = () => { const { t } = useTranslation(); const toast = useToast(); const navigate = useNavigate(); const { currency: formatCurrencyHook } = useFormatters(); const [accounts, setAccounts] = useState([]); const [liabilityAccounts, setLiabilityAccounts] = useState([]); const [assetAccounts, setAssetAccounts] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showAdjustModal, setShowAdjustModal] = useState(false); const [showAssetWizard, setShowAssetWizard] = useState(false); const [showAccountWizard, setShowAccountWizard] = useState(false); const [editingAccount, setEditingAccount] = useState(null); const [showAssetDetail, setShowAssetDetail] = useState(false); const [selectedAsset, setSelectedAsset] = useState(null); const [editingAsset, setEditingAsset] = useState(null); const [adjustAccount, setAdjustAccount] = useState(null); const [targetBalance, setTargetBalance] = useState(''); const [adjusting, setAdjusting] = useState(false); const [selectedAccount, setSelectedAccount] = useState(null); const [saving, setSaving] = useState(false); const [recalculating, setRecalculating] = useState(false); const [filter, setFilter] = useState({ type: '', is_active: '' }); const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const [activeTab, setActiveTab] = useState('accounts'); // 'accounts', 'liabilities' ou 'assets' const [formData, setFormData] = useState({ name: '', type: 'checking', bank_name: '', account_number: '', initial_balance: '0', credit_limit: '', currency: 'BRL', color: '#1E40AF', icon: 'bi-bank', description: '', is_active: true, include_in_total: true, }); const accountTypes = accountService.types; const accountIcons = accountService.icons; useEffect(() => { const handleResize = () => setIsMobile(window.innerWidth < 768); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); useEffect(() => { loadAccounts(); }, [filter]); const loadAccounts = async () => { try { setLoading(true); // Recalcular saldos automaticamente antes de carregar await accountService.recalculateAllBalances(); const params = {}; if (filter.type) params.type = filter.type; if (filter.is_active !== '') params.is_active = filter.is_active; // Carregar contas normais e passivas em paralelo const [accountsResponse, liabilityResponse, assetResponse] = await Promise.all([ accountService.getAll(params), liabilityAccountService.getAll({ is_active: filter.is_active || undefined }), assetAccountService.getAll({ status: filter.is_active === '1' ? 'active' : undefined }) ]); if (accountsResponse.success) { setAccounts(accountsResponse.data); } if (liabilityResponse.success) { setLiabilityAccounts(liabilityResponse.data); } if (assetResponse.success) { setAssetAccounts(assetResponse.data); } } catch (error) { toast.error(t('accounts.loadError')); } finally { setLoading(false); } }; const handleOpenModal = (account = null) => { if (account) { setSelectedAccount(account); setFormData({ name: account.name || '', type: account.type || 'checking', bank_name: account.bank_name || '', account_number: account.account_number || '', initial_balance: account.initial_balance?.toString() || '0', credit_limit: account.credit_limit?.toString() || '', currency: account.currency || 'BRL', color: account.color || '#1E40AF', icon: account.icon || 'bi-bank', description: account.description || '', is_active: account.is_active ?? true, include_in_total: account.include_in_total ?? true, }); } else { setSelectedAccount(null); setFormData({ name: '', type: 'checking', bank_name: '', account_number: '', initial_balance: '0', credit_limit: '', currency: 'BRL', color: '#1E40AF', icon: 'bi-bank', description: '', is_active: true, include_in_total: true, }); } setShowModal(true); }; const handleCloseModal = () => { setShowModal(false); setSelectedAccount(null); }; const handleChange = (e) => { const { name, value, type, checked } = e.target; setFormData(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value, })); // Auto-selecionar ícone baseado no tipo if (name === 'type' && accountIcons[value]) { setFormData(prev => ({ ...prev, icon: accountIcons[value], })); } }; const handleSubmit = async (e) => { e.preventDefault(); if (!formData.name.trim()) { toast.error(t('validation.required')); return; } setSaving(true); try { const data = { ...formData, initial_balance: parseFloat(formData.initial_balance) || 0, credit_limit: formData.credit_limit ? parseFloat(formData.credit_limit) : null, }; let response; if (selectedAccount) { response = await accountService.update(selectedAccount.id, data); } else { response = await accountService.create(data); } if (response.success) { toast.success(selectedAccount ? t('accounts.updateSuccess') : t('accounts.createSuccess')); handleCloseModal(); loadAccounts(); } } catch (error) { toast.error(error.response?.data?.message || t('accounts.createError')); } finally { setSaving(false); } }; const handleDeleteClick = (account) => { setSelectedAccount(account); setShowDeleteModal(true); }; const handleDeleteConfirm = async () => { if (!selectedAccount) return; setSaving(true); try { const response = await accountService.delete(selectedAccount.id); if (response.success) { toast.success(t('accounts.deleteSuccess')); setShowDeleteModal(false); setSelectedAccount(null); loadAccounts(); } } catch (error) { toast.error(error.response?.data?.message || t('accounts.deleteError')); } finally { setSaving(false); } }; const formatCurrency = (value, currency = 'BRL') => { return formatCurrencyHook(value, currency); }; const handleRecalculateBalances = async () => { setRecalculating(true); try { const response = await accountService.recalculateAllBalances(); if (response.success) { const updated = response.data.filter(acc => acc.difference !== 0); if (updated.length > 0) { toast.success(t('accounts.recalculateSuccess', { count: updated.length })); } else { toast.info(t('accounts.balancesUpToDate')); } loadAccounts(); } } catch (error) { toast.error(error.response?.data?.message || t('accounts.recalculateError')); } finally { setRecalculating(false); } }; // Abrir modal de ajuste de saldo const openAdjustModal = (account) => { setAdjustAccount(account); setTargetBalance(account.current_balance?.toString() || '0'); setShowAdjustModal(true); }; // Ajustar saldo da conta const handleAdjustBalance = async () => { if (!adjustAccount || targetBalance === '') return; setAdjusting(true); try { const response = await accountService.adjustBalance(adjustAccount.id, parseFloat(targetBalance)); if (response.success) { toast.success(t('accounts.adjustSuccess')); setShowAdjustModal(false); loadAccounts(); } } catch (error) { toast.error(error.response?.data?.message || t('accounts.adjustError')); } finally { setAdjusting(false); } }; // Abrir modal de detalhes do ativo const handleOpenAssetDetail = async (asset) => { try { const response = await assetAccountService.getById(asset.id); if (response.success) { setSelectedAsset(response.data); setShowAssetDetail(true); } } catch (error) { toast.error('Erro ao carregar detalhes do ativo'); } }; // Fechar modal de detalhes do ativo const handleCloseAssetDetail = () => { setShowAssetDetail(false); setSelectedAsset(null); }; // Editar ativo const handleEditAsset = () => { setEditingAsset(selectedAsset); setShowAssetDetail(false); setShowAssetWizard(true); }; // Callback após salvar/criar ativo const handleAssetSuccess = (assetData) => { loadAccounts(); setEditingAsset(null); toast.success(editingAsset ? 'Activo actualizado con éxito' : 'Activo creado con éxito'); }; // Callback após salvar/criar conta via wizard const handleAccountWizardSuccess = (data, destinationType) => { loadAccounts(); setEditingAccount(null); const isEditing = !!editingAccount; if (destinationType === 'asset') { toast.success(isEditing ? 'Cuenta de ahorro actualizada' : 'Cuenta de ahorro creada como activo'); } else if (destinationType === 'liability') { toast.success(isEditing ? 'Tarjeta de crédito actualizada' : 'Tarjeta de crédito creada como pasivo'); } else { toast.success(isEditing ? t('accounts.updateSuccess') : t('accounts.createSuccess')); } }; // Calcula totais agrupados por moeda (incluindo passivos como valor negativo e ativos como positivo) const getTotalsByCurrency = () => { const totals = {}; // Contas normais accounts .filter(acc => acc.is_active && acc.include_in_total) .forEach(acc => { const currency = acc.currency || 'BRL'; if (!totals[currency]) { totals[currency] = 0; } totals[currency] += parseFloat(acc.current_balance || 0); }); // Passivos (como valor negativo - saldo devedor) liabilityAccounts .filter(acc => acc.is_active && acc.status === 'active') .forEach(acc => { const currency = acc.currency || 'EUR'; if (!totals[currency]) { totals[currency] = 0; } // Subtrair o saldo devedor (remaining_balance) totals[currency] -= parseFloat(acc.remaining_balance || 0); }); // Ativos (como valor positivo - current_value) assetAccounts .filter(acc => acc.status === 'active') .forEach(acc => { const currency = acc.currency || 'EUR'; if (!totals[currency]) { totals[currency] = 0; } // Somar o valor atual do ativo totals[currency] += parseFloat(acc.current_value || 0); }); return totals; }; // Total de contas ativas (normais + passivas + ativos) const getTotalActiveAccounts = () => { const normalActive = accounts.filter(a => a.is_active).length; const liabilityActive = liabilityAccounts.filter(a => a.is_active).length; const assetActive = assetAccounts.filter(a => a.status === 'active').length; return normalActive + liabilityActive + assetActive; }; // Total de todas as contas const getTotalAccounts = () => { return accounts.length + liabilityAccounts.length + assetAccounts.length; }; // Total de ativos por moeda const getAssetTotalsByCurrency = () => { const totals = {}; assetAccounts .filter(acc => acc.status === 'active') .forEach(acc => { const currency = acc.currency || 'EUR'; if (!totals[currency]) { totals[currency] = { current: 0, acquisition: 0, count: 0 }; } totals[currency].current += parseFloat(acc.current_value || 0); totals[currency].acquisition += parseFloat(acc.acquisition_value || 0); totals[currency].count++; }); return totals; }; return (
{/* Header */}

{t('nav.accounts')}

{!isMobile &&

{t('accounts.title')}

}
{!isMobile && activeTab === 'accounts' && ( )} {activeTab === 'accounts' && ( )} {activeTab === 'liabilities' && ( )} {activeTab === 'assets' && ( )}
{/* Tabs - Mobile Optimized */} {activeTab === 'accounts' && ( <> {/* Summary Cards */}
{/* Total por Moeda */}

{t('dashboard.totalBalance')}

{Object.keys(getTotalsByCurrency()).length > 0 ? (
{Object.entries(getTotalsByCurrency()).map(([currency, total]) => (

= 0 ? 'text-success' : 'text-danger'} ${isMobile ? 'fs-6' : ''}`} style={isMobile ? { fontSize: '1rem' } : undefined}> {formatCurrency(total, currency)}

{currency}
))}
) : (

-

)}
{/* Contas Ativas e Total */}

{t('common.active')}

{getTotalActiveAccounts()}

{t('common.total')}

{getTotalAccounts()}

{/* Filters */}
{/* Accounts List */}
{loading ? (
{t('common.loading')}
) : accounts.length === 0 ? (

{t('accounts.noAccounts')}

) : isMobile ? ( /* Mobile: Cards Layout */
{accounts.map((account) => (
{account.name}
{account.account_number && ( Nº {account.account_number} )}
{accountTypes[account.type] || account.type} {account.is_active ? ( {t('common.active')} ) : ( {t('common.inactive')} )}
= 0 ? 'text-success' : 'text-danger'}`} style={{ fontSize: '0.95rem' }}> {formatCurrency(account.current_balance, account.currency)}
{account.bank_name && (
{account.bank_name}
)}
))}
) : ( /* Desktop: Table Layout */
{accounts.map((account) => ( ))}
{t('accounts.accountName')} {t('common.type')} {t('accounts.bankName')} {t('accounts.currentBalance')} {t('common.status')} {t('common.actions')}
{account.name}
{account.account_number && ( Nº {account.account_number} )}
{accountTypes[account.type] || account.type} {account.bank_name || '-'} = 0 ? 'text-success' : 'text-danger'}`} style={{ backgroundColor: 'transparent' }}> {formatCurrency(account.current_balance, account.currency)} {account.is_active ? ( {t('common.active')} ) : ( {t('common.inactive')} )}
)}
)} {/* Tab de Passivos */} {activeTab === 'liabilities' && ( <> {/* Liability Accounts Section */} {liabilityAccounts.length > 0 ? (
{isMobile ? ( // Mobile: Cards Layout
{liabilityAccounts.map((liability) => (
navigate('/liabilities')} >
{/* Header with Icon and Name */}
{liability.name}
{liability.contract_number && (
Nº {liability.contract_number}
)}
{liability.status === 'active' ? ( {t('common.active')} ) : liability.status === 'paid_off' ? ( {t('liabilities.paid')} ) : ( {liability.status} )}
{/* Creditor */} {liability.creditor && (
{liability.creditor}
)} {/* Amounts */}
Principal
{formatCurrency(liability.principal_amount, liability.currency)}
Saldo Devedor
-{formatCurrency(liability.remaining_balance, liability.currency)}
{/* Progress Bar */}
Progresso {liability.progress_percentage || 0}%
))}
) : ( // Desktop: Table Layout
{liabilityAccounts.map((liability) => ( navigate('/liabilities')} > ))}
{t('liabilities.contractName')} {t('liabilities.creditor')} {t('liabilities.principal')} {t('liabilities.remaining')} {t('liabilities.progress')} {t('common.status')}
{liability.name}
{liability.contract_number && ( Nº {liability.contract_number} )}
{liability.creditor || '-'} {formatCurrency(liability.principal_amount, liability.currency)} -{formatCurrency(liability.remaining_balance, liability.currency)}
{liability.progress_percentage || 0}%
{liability.status === 'active' ? ( {t('common.active')} ) : liability.status === 'paid_off' ? ( {t('liabilities.paid')} ) : ( {liability.status} )}
)}
) : (

{t('liabilities.noLiabilities')}

)} )} {/* Tab de Ativos */} {activeTab === 'assets' && ( <> {/* Summary Cards de Ativos */}
{Object.entries(getAssetTotalsByCurrency()).map(([currency, data]) => (

Activos ({currency}) - {data.count} items

{formatCurrency(data.current, currency)}

{data.current !== data.acquisition && ( data.acquisition ? 'text-success' : 'text-danger'}`}> data.acquisition ? 'bi-arrow-up' : 'bi-arrow-down'} me-1`}> {((data.current - data.acquisition) / data.acquisition * 100).toFixed(1)}% desde compra )}
))} {Object.keys(getAssetTotalsByCurrency()).length === 0 && (

No hay activos registrados

)}
{/* Lista de Ativos */}
{assetAccounts.length === 0 ? (

No hay activos registrados

) : isMobile ? ( // Mobile: Cards Layout para Ativos
{assetAccounts.map((asset) => (
{/* Header with Icon and Name */}
{asset.name}
{assetAccountService.statuses[asset.asset_type] || asset.asset_type}
{asset.status === 'active' ? 'Activo' : asset.status}
{/* Values */}
Valor Actual
{formatCurrency(asset.current_value, asset.currency)}
Rentabilidad
= parseFloat(asset.acquisition_value) ? 'text-success' : 'text-danger'}`} style={{ fontSize: '0.8rem' }}> {asset.acquisition_value > 0 ? ( <> = parseFloat(asset.acquisition_value) ? 'bi-arrow-up' : 'bi-arrow-down'} me-1`}> {(((parseFloat(asset.current_value) - parseFloat(asset.acquisition_value)) / parseFloat(asset.acquisition_value)) * 100).toFixed(1)}% ) : '-'}
))}
) : ( // Desktop: Table Layout para Ativos
{assetAccounts.map((asset) => { const gainLoss = parseFloat(asset.current_value) - parseFloat(asset.acquisition_value); const gainLossPercent = asset.acquisition_value > 0 ? (gainLoss / parseFloat(asset.acquisition_value)) * 100 : 0; return ( handleOpenAssetDetail(asset)} className="asset-row-hover" > ); })}
Nombre Tipo Adquisición Valor Actual Rentabilidad {t('common.status')}
{asset.name}
{asset.description && ( {asset.description.substring(0, 30)} )}
{asset.asset_type === 'real_estate' ? 'Inmueble' : asset.asset_type === 'vehicle' ? 'Vehículo' : asset.asset_type === 'investment' ? 'Inversión' : asset.asset_type === 'equipment' ? 'Equipamiento' : asset.asset_type === 'receivable' ? 'Crédito' : asset.asset_type === 'cash' ? 'Efectivo' : asset.asset_type} {formatCurrency(asset.acquisition_value, asset.currency)} {formatCurrency(asset.current_value, asset.currency)} = 0 ? 'text-success' : 'text-danger'}`}> = 0 ? 'bi-arrow-up' : 'bi-arrow-down'} me-1`}> {gainLossPercent.toFixed(1)}% {asset.status === 'active' ? 'Activo' : asset.status === 'disposed' ? 'Vendido' : asset.status}
)}
)} {/* Asset Wizard Modal */} { setShowAssetWizard(false); setEditingAsset(null); }} onSuccess={handleAssetSuccess} asset={editingAsset} /> {/* Account Wizard Modal */} { setShowAccountWizard(false); setEditingAccount(null); }} onSuccess={handleAccountWizardSuccess} account={editingAccount} /> {/* Modal de Criar/Editar */} {showModal && (
{selectedAccount ? t('accounts.editAccount') : t('accounts.newAccount')}
{/* Nome */}
{/* Tipo */}
{/* Banco */}
{/* Número da Conta */}
{/* Saldo Inicial */}
{/* Limite de Crédito (para cartões) */} {(formData.type === 'credit_card' || formData.type === 'liability') && (
)} {/* Moeda */}
setFormData(prev => ({ ...prev, currency }))} />
{/* Cor */}
{/* Ícone */}
setFormData(prev => ({ ...prev, icon }))} type="account" />
{/* Descrição */}
{/* Checkboxes */}
)} {/* Modal de Ajuste de Saldo */} {showAdjustModal && adjustAccount && (
{t('accounts.adjustBalance')}
{t('accounts.adjustInfo')}
setTargetBalance(e.target.value)} placeholder={t('accounts.targetBalancePlaceholder')} /> {t('accounts.targetBalanceHelp')}
)} {/* Modal de Detalhes do Ativo */} {showAssetDetail && selectedAsset && (
{selectedAsset.name}
{selectedAsset.description && ( {selectedAsset.description} )}
{/* Valores principais */}
Valor de Adquisición
{formatCurrency(selectedAsset.acquisition_value, selectedAsset.currency)}
Valor Actual
{formatCurrency(selectedAsset.current_value, selectedAsset.currency)}
Rentabilidad
{(() => { const gainLoss = parseFloat(selectedAsset.current_value) - parseFloat(selectedAsset.acquisition_value); const gainLossPercent = selectedAsset.acquisition_value > 0 ? (gainLoss / parseFloat(selectedAsset.acquisition_value)) * 100 : 0; return (
= 0 ? 'text-success' : 'text-danger'}`}> = 0 ? 'bi-arrow-up' : 'bi-arrow-down'} me-1`}> {formatCurrency(Math.abs(gainLoss), selectedAsset.currency)} ({gainLossPercent.toFixed(1)}%)
); })()}
{/* Informações detalhadas */}
Información del Activo
Tipo de Activo
{selectedAsset.asset_type === 'real_estate' ? 'Inmueble' : selectedAsset.asset_type === 'vehicle' ? 'Vehículo' : selectedAsset.asset_type === 'investment' ? 'Inversión' : selectedAsset.asset_type === 'equipment' ? 'Equipamiento' : selectedAsset.asset_type === 'receivable' ? 'Crédito por Cobrar' : selectedAsset.asset_type === 'cash' ? 'Efectivo' : selectedAsset.asset_type}
Estado
{selectedAsset.status === 'active' ? 'Activo' : selectedAsset.status === 'disposed' ? 'Vendido' : selectedAsset.status}
{selectedAsset.acquisition_date && (
Fecha de Adquisición
{new Date(selectedAsset.acquisition_date).toLocaleDateString('es-ES', { day: '2-digit', month: 'long', year: 'numeric' })}
)} {/* Campos específicos por tipo */} {selectedAsset.asset_type === 'real_estate' && ( <> {selectedAsset.address && (
Dirección
{selectedAsset.address}
)} {selectedAsset.city && (
Ciudad
{selectedAsset.city}
)} {selectedAsset.property_area_m2 && (
Área
{selectedAsset.property_area_m2} m²
)} )} {selectedAsset.asset_type === 'vehicle' && ( <> {selectedAsset.vehicle_brand && (
Marca/Modelo
{selectedAsset.vehicle_brand} {selectedAsset.vehicle_model}
)} {selectedAsset.vehicle_year && (
Año
{selectedAsset.vehicle_year}
)} {selectedAsset.vehicle_plate && (
Matrícula
{selectedAsset.vehicle_plate}
)} {selectedAsset.vehicle_mileage && (
Kilometraje
{selectedAsset.vehicle_mileage.toLocaleString()} km
)} )} {selectedAsset.asset_type === 'investment' && ( <> {selectedAsset.investment_type && (
Tipo de Inversión
{selectedAsset.investment_type === 'stocks' ? 'Acciones' : selectedAsset.investment_type === 'bonds' ? 'Bonos' : selectedAsset.investment_type === 'etf' ? 'ETF' : selectedAsset.investment_type === 'mutual_fund' ? 'Fondo Mutuo' : selectedAsset.investment_type === 'crypto' ? 'Criptomoneda' : selectedAsset.investment_type === 'fixed_deposit' ? 'Depósito a Plazo' : selectedAsset.investment_type}
)} {selectedAsset.institution && (
Institución
{selectedAsset.institution}
)} {selectedAsset.ticker && (
Ticker/Símbolo
{selectedAsset.ticker}
)} {selectedAsset.quantity && (
Cantidad
{selectedAsset.quantity}
)} {selectedAsset.interest_rate && (
Tasa de Interés
{selectedAsset.interest_rate}%
)} {selectedAsset.maturity_date && (
Fecha de Vencimiento
{new Date(selectedAsset.maturity_date).toLocaleDateString('es-ES', { day: '2-digit', month: 'long', year: 'numeric' })}
)} )}
)} {/* Modal de Confirmação de Exclusão */} setShowDeleteModal(false)} onConfirm={handleDeleteConfirm} title={t('accounts.deleteAccount')} message={t('accounts.deleteConfirm')} confirmText={t('common.delete')} loading={saving} />
); }; export default Accounts;