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 (
{t('accounts.title')}
}{t('dashboard.totalBalance')}
{Object.keys(getTotalsByCurrency()).length > 0 ? ({t('common.active')}
{t('common.total')}
{t('accounts.noAccounts')}
| {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')} )} |
| {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.status === 'active' ? ( {t('common.active')} ) : liability.status === 'paid_off' ? ( {t('liabilities.paid')} ) : ( {liability.status} )} |
{t('liabilities.noLiabilities')}
Activos ({currency}) - {data.count} items
No hay activos registrados
No hay activos registrados
| 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} |