- Redesigned category create/edit modal with elegant wizard-style UI - Redesigned batch categorization modal with visual cards and better preview - Added missing i18n translations (common.continue, creating, remove) - Added budgets.general and wizard translations for ES, PT-BR, EN - Fixed 3 demo user transactions that were missing categories
1755 lines
82 KiB
JavaScript
1755 lines
82 KiB
JavaScript
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 (
|
|
<div>
|
|
{/* Header */}
|
|
<div className="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h2 className={`text-white ${isMobile ? 'mb-0 fs-4' : 'mb-1'}`}>
|
|
<i className="bi bi-wallet2 me-2 text-primary"></i>
|
|
{t('nav.accounts')}
|
|
</h2>
|
|
{!isMobile && <p className="text-slate-400 mb-0">{t('accounts.title')}</p>}
|
|
</div>
|
|
<div className="d-flex gap-2">
|
|
{!isMobile && activeTab === 'accounts' && (
|
|
<button
|
|
className="btn btn-outline-secondary"
|
|
onClick={handleRecalculateBalances}
|
|
disabled={recalculating}
|
|
title={t('accounts.recalculateBalances')}
|
|
>
|
|
{recalculating ? (
|
|
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
|
|
) : (
|
|
<i className="bi bi-arrow-repeat me-2"></i>
|
|
)}
|
|
{t('accounts.recalculate')}
|
|
</button>
|
|
)}
|
|
{activeTab === 'accounts' && (
|
|
<button className={`btn btn-primary ${isMobile ? 'btn-sm' : ''}`} onClick={() => setShowAccountWizard(true)}>
|
|
<i className="bi bi-plus-lg me-2"></i>
|
|
{isMobile ? t('common.add') : t('accounts.newAccount')}
|
|
</button>
|
|
)}
|
|
{activeTab === 'liabilities' && (
|
|
<button className={`btn btn-warning ${isMobile ? 'btn-sm' : ''}`} onClick={() => navigate('/liabilities')}>
|
|
<i className="bi bi-gear me-2"></i>
|
|
{isMobile ? t('common.manage') : t('liabilities.manage')}
|
|
</button>
|
|
)}
|
|
{activeTab === 'assets' && (
|
|
<button className={`btn btn-success ${isMobile ? 'btn-sm' : ''}`} onClick={() => setShowAssetWizard(true)}>
|
|
<i className="bi bi-plus-lg me-2"></i>
|
|
{isMobile ? t('common.add') : 'Nuevo Activo'}
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs - Mobile Optimized */}
|
|
<ul className={`nav nav-tabs mb-4 ${isMobile ? 'nav-fill flex-nowrap overflow-auto' : ''}`} style={{ borderBottom: '1px solid #334155' }}>
|
|
<li className="nav-item">
|
|
<button
|
|
className={`nav-link ${activeTab === 'accounts' ? 'active bg-primary text-white' : 'text-slate-400'} ${isMobile ? 'px-2 py-2' : ''}`}
|
|
onClick={() => setActiveTab('accounts')}
|
|
style={{
|
|
border: 'none',
|
|
borderRadius: '0.5rem 0.5rem 0 0',
|
|
backgroundColor: activeTab === 'accounts' ? undefined : 'transparent',
|
|
fontSize: isMobile ? '0.75rem' : undefined,
|
|
whiteSpace: 'nowrap'
|
|
}}
|
|
>
|
|
<i className={`bi bi-wallet2 ${isMobile ? '' : 'me-2'}`}></i>
|
|
{isMobile ? '' : t('nav.accounts')} ({accounts.length})
|
|
</button>
|
|
</li>
|
|
<li className="nav-item">
|
|
<button
|
|
className={`nav-link ${activeTab === 'liabilities' ? 'active bg-warning text-dark' : 'text-slate-400'} ${isMobile ? 'px-2 py-2' : ''}`}
|
|
onClick={() => setActiveTab('liabilities')}
|
|
style={{
|
|
border: 'none',
|
|
borderRadius: '0.5rem 0.5rem 0 0',
|
|
backgroundColor: activeTab === 'liabilities' ? undefined : 'transparent',
|
|
fontSize: isMobile ? '0.75rem' : undefined,
|
|
whiteSpace: 'nowrap'
|
|
}}
|
|
>
|
|
<i className={`bi bi-bank ${isMobile ? '' : 'me-2'}`}></i>
|
|
{isMobile ? '' : t('nav.liabilities')} ({liabilityAccounts.length})
|
|
</button>
|
|
</li>
|
|
<li className="nav-item">
|
|
<button
|
|
className={`nav-link ${activeTab === 'assets' ? 'active bg-success text-white' : 'text-slate-400'} ${isMobile ? 'px-2 py-2' : ''}`}
|
|
onClick={() => setActiveTab('assets')}
|
|
style={{
|
|
border: 'none',
|
|
borderRadius: '0.5rem 0.5rem 0 0',
|
|
backgroundColor: activeTab === 'assets' ? undefined : 'transparent',
|
|
fontSize: isMobile ? '0.75rem' : undefined,
|
|
whiteSpace: 'nowrap'
|
|
}}
|
|
>
|
|
<i className={`bi bi-graph-up-arrow ${isMobile ? '' : 'me-2'}`}></i>
|
|
{isMobile ? '' : 'Activos'} ({assetAccounts.length})
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
{activeTab === 'accounts' && (
|
|
<>
|
|
{/* Summary Cards */}
|
|
<div className={`row ${isMobile ? 'g-2 mb-3' : 'mb-4'}`}>
|
|
{/* Total por Moeda */}
|
|
<div className="col-md-6">
|
|
<div className={`card border-0 ${!isMobile ? 'h-100' : ''}`} style={{ background: '#1e293b' }}>
|
|
<div className={`card-body ${isMobile ? 'p-3' : ''}`}>
|
|
<div className="d-flex align-items-start">
|
|
<div className={`rounded-circle bg-primary bg-opacity-25 ${isMobile ? 'p-2 me-2' : 'p-3 me-3'}`}>
|
|
<i className={`bi bi-wallet2 text-primary ${isMobile ? 'fs-5' : 'fs-4'}`}></i>
|
|
</div>
|
|
<div className="flex-grow-1">
|
|
<p className={`text-slate-400 mb-2 ${isMobile ? '' : 'small'}`} style={isMobile ? { fontSize: '0.75rem' } : undefined}>{t('dashboard.totalBalance')}</p>
|
|
{Object.keys(getTotalsByCurrency()).length > 0 ? (
|
|
<div className={`d-flex flex-wrap ${isMobile ? 'gap-2' : 'gap-3'}`}>
|
|
{Object.entries(getTotalsByCurrency()).map(([currency, total]) => (
|
|
<div key={currency} className="text-center">
|
|
<h4 className={`mb-0 ${total >= 0 ? 'text-success' : 'text-danger'} ${isMobile ? 'fs-6' : ''}`} style={isMobile ? { fontSize: '1rem' } : undefined}>
|
|
{formatCurrency(total, currency)}
|
|
</h4>
|
|
<small className="text-slate-500" style={isMobile ? { fontSize: '0.65rem' } : undefined}>{currency}</small>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<h4 className="mb-0 text-slate-500">-</h4>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Contas Ativas e Total */}
|
|
<div className="col-md-3 col-6">
|
|
<div className={`card border-0 ${!isMobile ? 'h-100' : ''}`} style={{ background: '#1e293b' }}>
|
|
<div className={`card-body ${isMobile ? 'p-3' : ''}`}>
|
|
<div className={`d-flex ${isMobile ? 'flex-column align-items-start' : 'align-items-center'}`}>
|
|
<div className={`rounded-circle bg-success bg-opacity-25 ${isMobile ? 'p-2 mb-2' : 'p-3 me-3'}`}>
|
|
<i className={`bi bi-check-circle text-success ${isMobile ? 'fs-6' : 'fs-4'}`}></i>
|
|
</div>
|
|
<div>
|
|
<p className={`text-slate-400 mb-0 ${isMobile ? '' : 'small'}`} style={isMobile ? { fontSize: '0.7rem' } : undefined}>{t('common.active')}</p>
|
|
<h3 className={`mb-0 text-white ${isMobile ? 'fs-5' : ''}`}>
|
|
{getTotalActiveAccounts()}
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="col-md-3 col-6">
|
|
<div className={`card border-0 ${!isMobile ? 'h-100' : ''}`} style={{ background: '#1e293b' }}>
|
|
<div className={`card-body ${isMobile ? 'p-3' : ''}`}>
|
|
<div className={`d-flex ${isMobile ? 'flex-column align-items-start' : 'align-items-center'}`}>
|
|
<div className={`rounded-circle bg-info bg-opacity-25 ${isMobile ? 'p-2 mb-2' : 'p-3 me-3'}`}>
|
|
<i className={`bi bi-credit-card text-info ${isMobile ? 'fs-6' : 'fs-4'}`}></i>
|
|
</div>
|
|
<div>
|
|
<p className={`text-slate-400 mb-0 ${isMobile ? '' : 'small'}`} style={isMobile ? { fontSize: '0.7rem' } : undefined}>{t('common.total')}</p>
|
|
<h3 className={`mb-0 text-white ${isMobile ? 'fs-5' : ''}`}>{getTotalAccounts()}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<div className="card border-0 mb-4" style={{ background: '#1e293b' }}>
|
|
<div className="card-body">
|
|
<div className="row g-3">
|
|
<div className="col-md-4">
|
|
<label className="form-label text-slate-400 small">{t('accounts.filterByType')}</label>
|
|
<select
|
|
className="form-select bg-dark text-white border-secondary"
|
|
value={filter.type}
|
|
onChange={(e) => setFilter(prev => ({ ...prev, type: e.target.value }))}
|
|
>
|
|
<option value="">{t('common.all')}</option>
|
|
{Object.entries(accountTypes).map(([key, label]) => (
|
|
<option key={key} value={key}>{label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="col-md-4">
|
|
<label className="form-label text-slate-400 small">{t('common.status')}</label>
|
|
<select
|
|
className="form-select bg-dark text-white border-secondary"
|
|
value={filter.is_active}
|
|
onChange={(e) => setFilter(prev => ({ ...prev, is_active: e.target.value }))}
|
|
>
|
|
<option value="">{t('common.all')}</option>
|
|
<option value="1">{t('common.active')}</option>
|
|
<option value="0">{t('common.inactive')}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Accounts List */}
|
|
<div className="card border-0" style={{ background: '#1e293b' }}>
|
|
<div className={`card-body ${isMobile ? 'p-2' : 'p-0'}`}>
|
|
{loading ? (
|
|
<div className="text-center py-5">
|
|
<div className="spinner-border text-primary" role="status">
|
|
<span className="visually-hidden">{t('common.loading')}</span>
|
|
</div>
|
|
</div>
|
|
) : accounts.length === 0 ? (
|
|
<div className="text-center py-5">
|
|
<i className="bi bi-wallet2 display-1 text-slate-600"></i>
|
|
<p className="text-slate-400 mt-3">{t('accounts.noAccounts')}</p>
|
|
<button className="btn btn-primary" onClick={() => setShowAccountWizard(true)}>
|
|
<i className="bi bi-plus-lg me-2"></i>
|
|
{t('accounts.newAccount')}
|
|
</button>
|
|
</div>
|
|
) : isMobile ? (
|
|
/* Mobile: Cards Layout */
|
|
<div className="d-flex flex-column gap-2">
|
|
{accounts.map((account) => (
|
|
<div
|
|
key={account.id}
|
|
className="p-3 rounded"
|
|
style={{
|
|
background: '#0f172a',
|
|
border: '1px solid #334155'
|
|
}}
|
|
>
|
|
<div className="d-flex justify-content-between align-items-start mb-2">
|
|
<div className="d-flex align-items-center flex-grow-1">
|
|
<div
|
|
className="rounded-circle d-flex align-items-center justify-content-center me-2"
|
|
style={{
|
|
width: '36px',
|
|
height: '36px',
|
|
backgroundColor: account.color + '25',
|
|
}}
|
|
>
|
|
<i className={`bi ${account.icon}`} style={{ color: account.color, fontSize: '1rem' }}></i>
|
|
</div>
|
|
<div className="flex-grow-1">
|
|
<div className="text-white fw-medium" style={{ fontSize: '0.9rem' }}>{account.name}</div>
|
|
{account.account_number && (
|
|
<small className="text-slate-400" style={{ fontSize: '0.7rem' }}>Nº {account.account_number}</small>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="d-flex gap-1">
|
|
<button
|
|
className="btn btn-link text-success p-1"
|
|
onClick={() => openAdjustModal(account)}
|
|
style={{ fontSize: '1rem' }}
|
|
>
|
|
<i className="bi bi-sliders"></i>
|
|
</button>
|
|
<button
|
|
className="btn btn-link text-info p-1"
|
|
onClick={() => {
|
|
setEditingAccount(account);
|
|
setShowAccountWizard(true);
|
|
}}
|
|
style={{ fontSize: '1rem' }}
|
|
>
|
|
<i className="bi bi-pencil"></i>
|
|
</button>
|
|
<button
|
|
className="btn btn-link text-danger p-1"
|
|
onClick={() => handleDeleteClick(account)}
|
|
style={{ fontSize: '1rem' }}
|
|
>
|
|
<i className="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="d-flex justify-content-between align-items-center">
|
|
<div className="d-flex gap-2 align-items-center">
|
|
<span className="badge" style={{ backgroundColor: '#334155', fontSize: '0.65rem' }}>
|
|
{accountTypes[account.type] || account.type}
|
|
</span>
|
|
{account.is_active ? (
|
|
<span className="badge bg-success" style={{ fontSize: '0.65rem' }}>{t('common.active')}</span>
|
|
) : (
|
|
<span className="badge bg-secondary" style={{ fontSize: '0.65rem' }}>{t('common.inactive')}</span>
|
|
)}
|
|
</div>
|
|
<div className={`fw-bold ${parseFloat(account.current_balance) >= 0 ? 'text-success' : 'text-danger'}`} style={{ fontSize: '0.95rem' }}>
|
|
{formatCurrency(account.current_balance, account.currency)}
|
|
</div>
|
|
</div>
|
|
{account.bank_name && (
|
|
<div className="mt-2 pt-2" style={{ borderTop: '1px solid #334155' }}>
|
|
<small className="text-slate-400" style={{ fontSize: '0.7rem' }}>
|
|
<i className="bi bi-bank me-1"></i>{account.bank_name}
|
|
</small>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
/* Desktop: Table Layout */
|
|
<div className="table-responsive">
|
|
<table className="table table-hover mb-0" style={{ '--bs-table-bg': 'transparent', backgroundColor: 'transparent' }}>
|
|
<thead style={{ backgroundColor: 'transparent' }}>
|
|
<tr style={{ borderBottom: '1px solid #334155', backgroundColor: 'transparent' }}>
|
|
<th className="text-slate-400 fw-normal py-3 ps-4" style={{ backgroundColor: 'transparent' }}>{t('accounts.accountName')}</th>
|
|
<th className="text-slate-400 fw-normal py-3" style={{ backgroundColor: 'transparent' }}>{t('common.type')}</th>
|
|
<th className="text-slate-400 fw-normal py-3" style={{ backgroundColor: 'transparent' }}>{t('accounts.bankName')}</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-end" style={{ backgroundColor: 'transparent' }}>{t('accounts.currentBalance')}</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-center" style={{ backgroundColor: 'transparent' }}>{t('common.status')}</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-end pe-4" style={{ backgroundColor: 'transparent' }}>{t('common.actions')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody style={{ backgroundColor: 'transparent' }}>
|
|
{accounts.map((account) => (
|
|
<tr key={account.id} style={{ borderBottom: '1px solid #334155', backgroundColor: 'transparent' }}>
|
|
<td className="py-3 ps-4" style={{ backgroundColor: 'transparent' }}>
|
|
<div className="d-flex align-items-center">
|
|
<div
|
|
className="rounded-circle d-flex align-items-center justify-content-center me-3"
|
|
style={{
|
|
width: '40px',
|
|
height: '40px',
|
|
backgroundColor: account.color + '25',
|
|
}}
|
|
>
|
|
<i className={`bi ${account.icon}`} style={{ color: account.color }}></i>
|
|
</div>
|
|
<div>
|
|
<div className="text-white fw-medium">{account.name}</div>
|
|
{account.account_number && (
|
|
<small className="text-slate-400">Nº {account.account_number}</small>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="py-3" style={{ backgroundColor: 'transparent' }}>
|
|
<span className="badge" style={{ backgroundColor: '#334155' }}>
|
|
{accountTypes[account.type] || account.type}
|
|
</span>
|
|
</td>
|
|
<td className="py-3 text-slate-300" style={{ backgroundColor: 'transparent' }}>{account.bank_name || '-'}</td>
|
|
<td className={`py-3 text-end fw-medium ${parseFloat(account.current_balance) >= 0 ? 'text-success' : 'text-danger'}`} style={{ backgroundColor: 'transparent' }}>
|
|
{formatCurrency(account.current_balance, account.currency)}
|
|
</td>
|
|
<td className="py-3 text-center" style={{ backgroundColor: 'transparent' }}>
|
|
{account.is_active ? (
|
|
<span className="badge bg-success">{t('common.active')}</span>
|
|
) : (
|
|
<span className="badge bg-secondary">{t('common.inactive')}</span>
|
|
)}
|
|
</td>
|
|
<td className="py-3 text-end pe-4" style={{ backgroundColor: 'transparent' }}>
|
|
<button
|
|
className="btn btn-link text-success p-1 me-1"
|
|
onClick={() => openAdjustModal(account)}
|
|
title={t('accounts.adjustBalance')}
|
|
>
|
|
<i className="bi bi-sliders"></i>
|
|
</button>
|
|
<button
|
|
className="btn btn-link text-info p-1 me-1"
|
|
onClick={() => {
|
|
setEditingAccount(account);
|
|
setShowAccountWizard(true);
|
|
}}
|
|
title={t('common.edit')}
|
|
>
|
|
<i className="bi bi-pencil"></i>
|
|
</button>
|
|
<button
|
|
className="btn btn-link text-danger p-1"
|
|
onClick={() => handleDeleteClick(account)}
|
|
title={t('common.delete')}
|
|
>
|
|
<i className="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Tab de Passivos */}
|
|
{activeTab === 'liabilities' && (
|
|
<>
|
|
{/* Liability Accounts Section */}
|
|
{liabilityAccounts.length > 0 ? (
|
|
<div className="card border-0" style={{ background: '#1e293b' }}>
|
|
<div className={`card-body ${isMobile ? 'p-2' : 'p-0'}`}>
|
|
{isMobile ? (
|
|
// Mobile: Cards Layout
|
|
<div className="d-flex flex-column gap-2">
|
|
{liabilityAccounts.map((liability) => (
|
|
<div
|
|
key={`liability-${liability.id}`}
|
|
className="card border-secondary"
|
|
style={{ cursor: 'pointer', background: '#0f172a' }}
|
|
onClick={() => navigate('/liabilities')}
|
|
>
|
|
<div className="card-body p-3">
|
|
{/* Header with Icon and Name */}
|
|
<div className="d-flex align-items-start gap-2 mb-2">
|
|
<div
|
|
className="rounded-circle d-flex align-items-center justify-content-center flex-shrink-0"
|
|
style={{
|
|
width: '35px',
|
|
height: '35px',
|
|
backgroundColor: (liability.color || '#DC2626') + '25',
|
|
}}
|
|
>
|
|
<i className={`bi ${liability.icon || 'bi-file-earmark-text'} fs-6`} style={{ color: liability.color || '#DC2626' }}></i>
|
|
</div>
|
|
<div className="flex-grow-1" style={{ minWidth: 0 }}>
|
|
<div className="text-white fw-medium mb-1" style={{ fontSize: '0.85rem' }}>
|
|
{liability.name}
|
|
</div>
|
|
{liability.contract_number && (
|
|
<div className="text-slate-400 mb-1" style={{ fontSize: '0.65rem' }}>
|
|
Nº {liability.contract_number}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div>
|
|
{liability.status === 'active' ? (
|
|
<span className="badge bg-primary" style={{ fontSize: '0.65rem' }}>
|
|
{t('common.active')}
|
|
</span>
|
|
) : liability.status === 'paid_off' ? (
|
|
<span className="badge bg-success" style={{ fontSize: '0.65rem' }}>
|
|
{t('liabilities.paid')}
|
|
</span>
|
|
) : (
|
|
<span className="badge bg-secondary" style={{ fontSize: '0.65rem' }}>
|
|
{liability.status}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Creditor */}
|
|
{liability.creditor && (
|
|
<div className="text-slate-400 mb-2" style={{ fontSize: '0.7rem' }}>
|
|
<i className="bi bi-building me-1"></i>
|
|
{liability.creditor}
|
|
</div>
|
|
)}
|
|
|
|
{/* Amounts */}
|
|
<div className="d-flex justify-content-between align-items-center mb-2 pt-2" style={{ borderTop: '1px solid #334155' }}>
|
|
<div>
|
|
<div className="text-slate-400" style={{ fontSize: '0.65rem' }}>Principal</div>
|
|
<div className="text-slate-300 fw-medium" style={{ fontSize: '0.75rem' }}>
|
|
{formatCurrency(liability.principal_amount, liability.currency)}
|
|
</div>
|
|
</div>
|
|
<div className="text-end">
|
|
<div className="text-slate-400" style={{ fontSize: '0.65rem' }}>Saldo Devedor</div>
|
|
<div className="text-danger fw-medium" style={{ fontSize: '0.75rem' }}>
|
|
-{formatCurrency(liability.remaining_balance, liability.currency)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div>
|
|
<div className="d-flex justify-content-between align-items-center mb-1">
|
|
<span className="text-slate-400" style={{ fontSize: '0.65rem' }}>Progresso</span>
|
|
<span className="text-slate-400 fw-medium" style={{ fontSize: '0.7rem' }}>
|
|
{liability.progress_percentage || 0}%
|
|
</span>
|
|
</div>
|
|
<div className="progress" style={{ height: '6px', backgroundColor: '#334155' }}>
|
|
<div
|
|
className="progress-bar bg-success"
|
|
style={{ width: `${liability.progress_percentage || 0}%` }}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
// Desktop: Table Layout
|
|
<div className="table-responsive">
|
|
<table className="table table-hover mb-0" style={{ '--bs-table-bg': 'transparent', backgroundColor: 'transparent' }}>
|
|
<thead style={{ backgroundColor: 'transparent' }}>
|
|
<tr style={{ borderBottom: '1px solid #334155', backgroundColor: 'transparent' }}>
|
|
<th className="text-slate-400 fw-normal py-3 ps-4" style={{ backgroundColor: 'transparent' }}>{t('liabilities.contractName')}</th>
|
|
<th className="text-slate-400 fw-normal py-3" style={{ backgroundColor: 'transparent' }}>{t('liabilities.creditor')}</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-end" style={{ backgroundColor: 'transparent' }}>{t('liabilities.principal')}</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-end" style={{ backgroundColor: 'transparent' }}>{t('liabilities.remaining')}</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-center" style={{ backgroundColor: 'transparent' }}>{t('liabilities.progress')}</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-center" style={{ backgroundColor: 'transparent' }}>{t('common.status')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody style={{ backgroundColor: 'transparent' }}>
|
|
{liabilityAccounts.map((liability) => (
|
|
<tr
|
|
key={`liability-${liability.id}`}
|
|
style={{ borderBottom: '1px solid #334155', backgroundColor: 'transparent', cursor: 'pointer' }}
|
|
onClick={() => navigate('/liabilities')}
|
|
>
|
|
<td className="py-3 ps-4" style={{ backgroundColor: 'transparent' }}>
|
|
<div className="d-flex align-items-center">
|
|
<div
|
|
className="rounded-circle d-flex align-items-center justify-content-center me-3"
|
|
style={{
|
|
width: '40px',
|
|
height: '40px',
|
|
backgroundColor: (liability.color || '#DC2626') + '25',
|
|
}}
|
|
>
|
|
<i className={`bi ${liability.icon || 'bi-file-earmark-text'}`} style={{ color: liability.color || '#DC2626' }}></i>
|
|
</div>
|
|
<div>
|
|
<div className="text-white fw-medium">{liability.name}</div>
|
|
{liability.contract_number && (
|
|
<small className="text-slate-400">Nº {liability.contract_number}</small>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="py-3 text-slate-300" style={{ backgroundColor: 'transparent' }}>{liability.creditor || '-'}</td>
|
|
<td className="py-3 text-end text-slate-300" style={{ backgroundColor: 'transparent' }}>
|
|
{formatCurrency(liability.principal_amount, liability.currency)}
|
|
</td>
|
|
<td className="py-3 text-end fw-medium text-danger" style={{ backgroundColor: 'transparent' }}>
|
|
-{formatCurrency(liability.remaining_balance, liability.currency)}
|
|
</td>
|
|
<td className="py-3 text-center" style={{ backgroundColor: 'transparent' }}>
|
|
<div className="d-flex align-items-center justify-content-center gap-2">
|
|
<div className="progress" style={{ width: '80px', height: '6px', backgroundColor: '#334155' }}>
|
|
<div
|
|
className="progress-bar bg-success"
|
|
style={{ width: `${liability.progress_percentage || 0}%` }}
|
|
></div>
|
|
</div>
|
|
<small className="text-slate-400">{liability.progress_percentage || 0}%</small>
|
|
</div>
|
|
</td>
|
|
<td className="py-3 text-center" style={{ backgroundColor: 'transparent' }}>
|
|
{liability.status === 'active' ? (
|
|
<span className="badge bg-primary">{t('common.active')}</span>
|
|
) : liability.status === 'paid_off' ? (
|
|
<span className="badge bg-success">{t('liabilities.paid')}</span>
|
|
) : (
|
|
<span className="badge bg-secondary">{liability.status}</span>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="card border-0" style={{ background: '#1e293b' }}>
|
|
<div className="card-body text-center py-5">
|
|
<i className="bi bi-bank display-1 text-slate-600"></i>
|
|
<p className="text-slate-400 mt-3">{t('liabilities.noLiabilities')}</p>
|
|
<button className="btn btn-primary" onClick={() => navigate('/liabilities')}>
|
|
<i className="bi bi-plus-lg me-2"></i>
|
|
{t('liabilities.import')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{/* Tab de Ativos */}
|
|
{activeTab === 'assets' && (
|
|
<>
|
|
{/* Summary Cards de Ativos */}
|
|
<div className={`row ${isMobile ? 'g-2 mb-3' : 'mb-4'}`}>
|
|
{Object.entries(getAssetTotalsByCurrency()).map(([currency, data]) => (
|
|
<div className="col-md-4" key={currency}>
|
|
<div className={`card border-0 ${!isMobile ? 'h-100' : ''}`} style={{ background: '#1e293b' }}>
|
|
<div className={`card-body ${isMobile ? 'p-3' : ''}`}>
|
|
<div className="d-flex justify-content-between align-items-start">
|
|
<div>
|
|
<p className={`text-slate-400 mb-1 ${isMobile ? '' : 'small'}`} style={isMobile ? { fontSize: '0.7rem' } : undefined}>
|
|
Activos ({currency}) - {data.count} items
|
|
</p>
|
|
<h4 className={`mb-0 text-success ${isMobile ? 'fs-5' : ''}`}>
|
|
{formatCurrency(data.current, currency)}
|
|
</h4>
|
|
{data.current !== data.acquisition && (
|
|
<small className={`${data.current > data.acquisition ? 'text-success' : 'text-danger'}`}>
|
|
<i className={`bi ${data.current > data.acquisition ? 'bi-arrow-up' : 'bi-arrow-down'} me-1`}></i>
|
|
{((data.current - data.acquisition) / data.acquisition * 100).toFixed(1)}% desde compra
|
|
</small>
|
|
)}
|
|
</div>
|
|
<div className={`rounded-circle bg-success bg-opacity-25 ${isMobile ? 'p-2' : 'p-3'}`}>
|
|
<i className={`bi bi-graph-up-arrow text-success ${isMobile ? 'fs-5' : 'fs-4'}`}></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
{Object.keys(getAssetTotalsByCurrency()).length === 0 && (
|
|
<div className="col-12">
|
|
<div className="card border-0" style={{ background: '#1e293b' }}>
|
|
<div className={`card-body ${isMobile ? 'p-3' : ''} text-center`}>
|
|
<i className="bi bi-graph-up-arrow text-slate-600 fs-1 mb-2"></i>
|
|
<p className="text-slate-400 mb-0">No hay activos registrados</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Lista de Ativos */}
|
|
<div className="card border-0" style={{ background: '#1e293b' }}>
|
|
<div className={`card-body ${isMobile ? 'p-2' : 'p-0'}`}>
|
|
{assetAccounts.length === 0 ? (
|
|
<div className="text-center py-5">
|
|
<i className="bi bi-graph-up-arrow display-1 text-slate-600"></i>
|
|
<p className="text-slate-400 mt-3">No hay activos registrados</p>
|
|
<button className="btn btn-success" onClick={() => setShowAssetWizard(true)}>
|
|
<i className="bi bi-plus-lg me-2"></i>
|
|
Crear Activo
|
|
</button>
|
|
</div>
|
|
) : isMobile ? (
|
|
// Mobile: Cards Layout para Ativos
|
|
<div className="d-flex flex-column gap-2">
|
|
{assetAccounts.map((asset) => (
|
|
<div
|
|
key={`asset-${asset.id}`}
|
|
className="card border-secondary"
|
|
style={{ cursor: 'pointer', background: '#0f172a' }}
|
|
>
|
|
<div className="card-body p-3">
|
|
{/* Header with Icon and Name */}
|
|
<div className="d-flex align-items-start gap-2 mb-2">
|
|
<div
|
|
className="rounded-circle d-flex align-items-center justify-content-center flex-shrink-0"
|
|
style={{
|
|
width: '35px',
|
|
height: '35px',
|
|
backgroundColor: (asset.color || '#10B981') + '25',
|
|
}}
|
|
>
|
|
<i className={`bi bi-${asset.asset_type === 'real_estate' ? 'house' : asset.asset_type === 'vehicle' ? 'truck' : asset.asset_type === 'investment' ? 'graph-up' : 'box'} fs-6`} style={{ color: asset.color || '#10B981' }}></i>
|
|
</div>
|
|
<div className="flex-grow-1" style={{ minWidth: 0 }}>
|
|
<div className="text-white fw-medium mb-1" style={{ fontSize: '0.85rem' }}>
|
|
{asset.name}
|
|
</div>
|
|
<div className="text-slate-400" style={{ fontSize: '0.65rem' }}>
|
|
{assetAccountService.statuses[asset.asset_type] || asset.asset_type}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<span className={`badge ${asset.status === 'active' ? 'bg-success' : 'bg-secondary'}`} style={{ fontSize: '0.65rem' }}>
|
|
{asset.status === 'active' ? 'Activo' : asset.status}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Values */}
|
|
<div className="d-flex justify-content-between align-items-center pt-2" style={{ borderTop: '1px solid #334155' }}>
|
|
<div>
|
|
<div className="text-slate-400" style={{ fontSize: '0.65rem' }}>Valor Actual</div>
|
|
<div className="text-success fw-bold" style={{ fontSize: '0.9rem' }}>
|
|
{formatCurrency(asset.current_value, asset.currency)}
|
|
</div>
|
|
</div>
|
|
<div className="text-end">
|
|
<div className="text-slate-400" style={{ fontSize: '0.65rem' }}>Rentabilidad</div>
|
|
<div className={`fw-medium ${parseFloat(asset.current_value) >= parseFloat(asset.acquisition_value) ? 'text-success' : 'text-danger'}`} style={{ fontSize: '0.8rem' }}>
|
|
{asset.acquisition_value > 0 ? (
|
|
<>
|
|
<i className={`bi ${parseFloat(asset.current_value) >= parseFloat(asset.acquisition_value) ? 'bi-arrow-up' : 'bi-arrow-down'} me-1`}></i>
|
|
{(((parseFloat(asset.current_value) - parseFloat(asset.acquisition_value)) / parseFloat(asset.acquisition_value)) * 100).toFixed(1)}%
|
|
</>
|
|
) : '-'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
// Desktop: Table Layout para Ativos
|
|
<div className="table-responsive">
|
|
<table className="table table-hover mb-0" style={{ '--bs-table-bg': 'transparent', backgroundColor: 'transparent' }}>
|
|
<thead style={{ backgroundColor: 'transparent' }}>
|
|
<tr style={{ borderBottom: '1px solid #334155', backgroundColor: 'transparent' }}>
|
|
<th className="text-slate-400 fw-normal py-3 ps-4" style={{ backgroundColor: 'transparent' }}>Nombre</th>
|
|
<th className="text-slate-400 fw-normal py-3" style={{ backgroundColor: 'transparent' }}>Tipo</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-end" style={{ backgroundColor: 'transparent' }}>Adquisición</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-end" style={{ backgroundColor: 'transparent' }}>Valor Actual</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-center" style={{ backgroundColor: 'transparent' }}>Rentabilidad</th>
|
|
<th className="text-slate-400 fw-normal py-3 text-center" style={{ backgroundColor: 'transparent' }}>{t('common.status')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody style={{ backgroundColor: 'transparent' }}>
|
|
{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 (
|
|
<tr
|
|
key={`asset-${asset.id}`}
|
|
style={{ borderBottom: '1px solid #334155', backgroundColor: 'transparent', cursor: 'pointer' }}
|
|
onClick={() => handleOpenAssetDetail(asset)}
|
|
className="asset-row-hover"
|
|
>
|
|
<td className="py-3 ps-4" style={{ backgroundColor: 'transparent' }}>
|
|
<div className="d-flex align-items-center">
|
|
<div
|
|
className="rounded-circle d-flex align-items-center justify-content-center me-3"
|
|
style={{
|
|
width: '40px',
|
|
height: '40px',
|
|
backgroundColor: (asset.color || '#10B981') + '25',
|
|
}}
|
|
>
|
|
<i className={`bi bi-${asset.asset_type === 'real_estate' ? 'house' : asset.asset_type === 'vehicle' ? 'truck' : asset.asset_type === 'investment' ? 'graph-up' : 'box'}`} style={{ color: asset.color || '#10B981' }}></i>
|
|
</div>
|
|
<div>
|
|
<div className="text-white fw-medium">{asset.name}</div>
|
|
{asset.description && (
|
|
<small className="text-slate-400">{asset.description.substring(0, 30)}</small>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td className="py-3" style={{ backgroundColor: 'transparent' }}>
|
|
<span className="badge" style={{ backgroundColor: '#334155' }}>
|
|
{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}
|
|
</span>
|
|
</td>
|
|
<td className="py-3 text-end text-slate-300" style={{ backgroundColor: 'transparent' }}>
|
|
{formatCurrency(asset.acquisition_value, asset.currency)}
|
|
</td>
|
|
<td className="py-3 text-end fw-medium text-success" style={{ backgroundColor: 'transparent' }}>
|
|
{formatCurrency(asset.current_value, asset.currency)}
|
|
</td>
|
|
<td className="py-3 text-center" style={{ backgroundColor: 'transparent' }}>
|
|
<span className={`fw-medium ${gainLoss >= 0 ? 'text-success' : 'text-danger'}`}>
|
|
<i className={`bi ${gainLoss >= 0 ? 'bi-arrow-up' : 'bi-arrow-down'} me-1`}></i>
|
|
{gainLossPercent.toFixed(1)}%
|
|
</span>
|
|
</td>
|
|
<td className="py-3 text-center" style={{ backgroundColor: 'transparent' }}>
|
|
<span className={`badge ${asset.status === 'active' ? 'bg-success' : 'bg-secondary'}`}>
|
|
{asset.status === 'active' ? 'Activo' : asset.status === 'disposed' ? 'Vendido' : asset.status}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Asset Wizard Modal */}
|
|
<AssetWizard
|
|
isOpen={showAssetWizard}
|
|
onClose={() => {
|
|
setShowAssetWizard(false);
|
|
setEditingAsset(null);
|
|
}}
|
|
onSuccess={handleAssetSuccess}
|
|
asset={editingAsset}
|
|
/>
|
|
|
|
{/* Account Wizard Modal */}
|
|
<AccountWizard
|
|
isOpen={showAccountWizard}
|
|
onClose={() => {
|
|
setShowAccountWizard(false);
|
|
setEditingAccount(null);
|
|
}}
|
|
onSuccess={handleAccountWizardSuccess}
|
|
account={editingAccount}
|
|
/>
|
|
|
|
{/* Modal de Criar/Editar */}
|
|
{showModal && (
|
|
<div className="modal show d-block" style={{ backgroundColor: 'rgba(0,0,0,0.7)' }}>
|
|
<div className="modal-dialog modal-lg modal-dialog-centered">
|
|
<div className="modal-content" style={{ background: '#1e293b' }}>
|
|
<div className="modal-header border-bottom" style={{ borderColor: '#334155 !important' }}>
|
|
<h5 className="modal-title text-white">
|
|
<i className={`bi ${selectedAccount ? 'bi-pencil' : 'bi-plus-circle'} me-2`}></i>
|
|
{selectedAccount ? t('accounts.editAccount') : t('accounts.newAccount')}
|
|
</h5>
|
|
<button type="button" className="btn-close btn-close-white" onClick={handleCloseModal}></button>
|
|
</div>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="modal-body">
|
|
<div className="row g-3">
|
|
{/* Nome */}
|
|
<div className="col-md-8">
|
|
<label className="form-label text-slate-300">{t('accounts.accountName')} *</label>
|
|
<input
|
|
type="text"
|
|
className="form-control bg-dark text-white border-secondary"
|
|
name="name"
|
|
value={formData.name}
|
|
onChange={handleChange}
|
|
placeholder="Ex: Conta Principal, Cartão Nubank..."
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{/* Tipo */}
|
|
<div className="col-md-4">
|
|
<label className="form-label text-slate-300">{t('common.type')} *</label>
|
|
<select
|
|
className="form-select bg-dark text-white border-secondary"
|
|
name="type"
|
|
value={formData.type}
|
|
onChange={handleChange}
|
|
required
|
|
>
|
|
{Object.entries(accountTypes).map(([key, label]) => (
|
|
<option key={key} value={key}>{label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
{/* Banco */}
|
|
<div className="col-md-6">
|
|
<label className="form-label text-slate-300">{t('accounts.bankName')}</label>
|
|
<input
|
|
type="text"
|
|
className="form-control bg-dark text-white border-secondary"
|
|
name="bank_name"
|
|
value={formData.bank_name}
|
|
onChange={handleChange}
|
|
placeholder="Ex: Banco do Brasil, Nubank..."
|
|
/>
|
|
</div>
|
|
|
|
{/* Número da Conta */}
|
|
<div className="col-md-6">
|
|
<label className="form-label text-slate-300">{t('accounts.accountNumber')}</label>
|
|
<input
|
|
type="text"
|
|
className="form-control bg-dark text-white border-secondary"
|
|
name="account_number"
|
|
value={formData.account_number}
|
|
onChange={handleChange}
|
|
placeholder="Ex: 12345-6"
|
|
/>
|
|
</div>
|
|
|
|
{/* Saldo Inicial */}
|
|
<div className="col-md-4">
|
|
<label className="form-label text-slate-300">{t('accounts.initialBalance')}</label>
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
className="form-control bg-dark text-white border-secondary"
|
|
name="initial_balance"
|
|
value={formData.initial_balance}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
|
|
{/* Limite de Crédito (para cartões) */}
|
|
{(formData.type === 'credit_card' || formData.type === 'liability') && (
|
|
<div className="col-md-4">
|
|
<label className="form-label text-slate-300">{t('accounts.creditLimit')}</label>
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
className="form-control bg-dark text-white border-secondary"
|
|
name="credit_limit"
|
|
value={formData.credit_limit}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Moeda */}
|
|
<div className="col-md-4">
|
|
<label className="form-label text-slate-300">{t('accounts.currency')}</label>
|
|
<CurrencySelector
|
|
value={formData.currency}
|
|
onChange={(currency) => setFormData(prev => ({ ...prev, currency }))}
|
|
/>
|
|
</div>
|
|
|
|
{/* Cor */}
|
|
<div className="col-md-3">
|
|
<label className="form-label text-slate-300">{t('common.color')}</label>
|
|
<input
|
|
type="color"
|
|
className="form-control form-control-color bg-dark border-secondary w-100"
|
|
name="color"
|
|
value={formData.color}
|
|
onChange={handleChange}
|
|
/>
|
|
</div>
|
|
|
|
{/* Ícone */}
|
|
<div className="col-md-5">
|
|
<label className="form-label text-slate-300">{t('common.icon')}</label>
|
|
<IconSelector
|
|
value={formData.icon}
|
|
onChange={(icon) => setFormData(prev => ({ ...prev, icon }))}
|
|
type="account"
|
|
/>
|
|
</div>
|
|
|
|
{/* Descrição */}
|
|
<div className="col-12">
|
|
<label className="form-label text-slate-300">{t('common.description')}</label>
|
|
<textarea
|
|
className="form-control bg-dark text-white border-secondary"
|
|
name="description"
|
|
value={formData.description}
|
|
onChange={handleChange}
|
|
rows="2"
|
|
placeholder={t('accounts.descriptionPlaceholder')}
|
|
></textarea>
|
|
</div>
|
|
|
|
{/* Checkboxes */}
|
|
<div className="col-md-6">
|
|
<div className="form-check">
|
|
<input
|
|
type="checkbox"
|
|
className="form-check-input"
|
|
id="is_active"
|
|
name="is_active"
|
|
checked={formData.is_active}
|
|
onChange={handleChange}
|
|
/>
|
|
<label className="form-check-label text-slate-300" htmlFor="is_active">
|
|
{t('common.active')}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div className="col-md-6">
|
|
<div className="form-check">
|
|
<input
|
|
type="checkbox"
|
|
className="form-check-input"
|
|
id="include_in_total"
|
|
name="include_in_total"
|
|
checked={formData.include_in_total}
|
|
onChange={handleChange}
|
|
/>
|
|
<label className="form-check-label text-slate-300" htmlFor="include_in_total">
|
|
{t('accounts.includeInTotal')}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="modal-footer border-top" style={{ borderColor: '#334155 !important' }}>
|
|
<button type="button" className="btn btn-outline-light" onClick={handleCloseModal}>
|
|
{t('common.cancel')}
|
|
</button>
|
|
<button type="submit" className="btn btn-primary" disabled={saving}>
|
|
{saving ? (
|
|
<>
|
|
<span className="spinner-border spinner-border-sm me-2"></span>
|
|
{t('common.loading')}
|
|
</>
|
|
) : (
|
|
<>
|
|
<i className="bi bi-check-lg me-2"></i>
|
|
{selectedAccount ? t('common.save') : t('common.create')}
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Modal de Ajuste de Saldo */}
|
|
{showAdjustModal && adjustAccount && (
|
|
<div className="modal show d-block" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
|
|
<div className="modal-dialog modal-dialog-centered">
|
|
<div className="modal-content" style={{ background: '#1e293b', border: '1px solid #334155' }}>
|
|
<div className="modal-header border-0">
|
|
<h5 className="modal-title text-white">
|
|
<i className="bi bi-sliders me-2 text-success"></i>
|
|
{t('accounts.adjustBalance')}
|
|
</h5>
|
|
<button
|
|
type="button"
|
|
className="btn-close btn-close-white"
|
|
onClick={() => setShowAdjustModal(false)}
|
|
></button>
|
|
</div>
|
|
<div className="modal-body">
|
|
<div className="alert alert-info bg-info bg-opacity-10 border-info">
|
|
<i className="bi bi-info-circle me-2"></i>
|
|
{t('accounts.adjustInfo')}
|
|
</div>
|
|
<div className="mb-3">
|
|
<label className="form-label text-white">{t('accounts.accountName')}</label>
|
|
<input
|
|
type="text"
|
|
className="form-control bg-dark text-white border-secondary"
|
|
value={adjustAccount.name}
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label className="form-label text-white">{t('accounts.currentBalance')}</label>
|
|
<input
|
|
type="text"
|
|
className="form-control bg-dark text-white border-secondary"
|
|
value={formatCurrency(adjustAccount.current_balance, adjustAccount.currency)}
|
|
disabled
|
|
/>
|
|
</div>
|
|
<div className="mb-3">
|
|
<label className="form-label text-white">
|
|
{t('accounts.targetBalance')} <span className="text-danger">*</span>
|
|
</label>
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
className="form-control bg-dark text-white border-secondary"
|
|
value={targetBalance}
|
|
onChange={(e) => setTargetBalance(e.target.value)}
|
|
placeholder={t('accounts.targetBalancePlaceholder')}
|
|
/>
|
|
<small className="text-muted">
|
|
{t('accounts.targetBalanceHelp')}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
<div className="modal-footer border-0">
|
|
<button
|
|
type="button"
|
|
className="btn btn-secondary"
|
|
onClick={() => setShowAdjustModal(false)}
|
|
>
|
|
{t('common.cancel')}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn btn-success"
|
|
onClick={handleAdjustBalance}
|
|
disabled={adjusting || targetBalance === ''}
|
|
>
|
|
{adjusting ? (
|
|
<>
|
|
<span className="spinner-border spinner-border-sm me-2"></span>
|
|
{t('common.loading')}
|
|
</>
|
|
) : (
|
|
<>
|
|
<i className="bi bi-check-lg me-2"></i>
|
|
{t('accounts.adjust')}
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Modal de Detalhes do Ativo */}
|
|
{showAssetDetail && selectedAsset && (
|
|
<div className="modal fade show d-block" style={{ backgroundColor: 'rgba(0,0,0,0.8)' }}>
|
|
<div className="modal-dialog modal-lg modal-dialog-centered">
|
|
<div className="modal-content" style={{ backgroundColor: '#1e293b', border: '1px solid #334155' }}>
|
|
<div className="modal-header border-0">
|
|
<div className="d-flex align-items-center">
|
|
<div
|
|
className="rounded-circle d-flex align-items-center justify-content-center me-3"
|
|
style={{
|
|
width: '48px',
|
|
height: '48px',
|
|
backgroundColor: (selectedAsset.color || '#10B981') + '25',
|
|
}}
|
|
>
|
|
<i className={`bi bi-${selectedAsset.asset_type === 'real_estate' ? 'house' : selectedAsset.asset_type === 'vehicle' ? 'truck' : selectedAsset.asset_type === 'investment' ? 'graph-up' : 'box'} fs-4`} style={{ color: selectedAsset.color || '#10B981' }}></i>
|
|
</div>
|
|
<div>
|
|
<h5 className="modal-title text-white mb-0">{selectedAsset.name}</h5>
|
|
{selectedAsset.description && (
|
|
<small className="text-slate-400">{selectedAsset.description}</small>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<button type="button" className="btn-close btn-close-white" onClick={handleCloseAssetDetail}></button>
|
|
</div>
|
|
<div className="modal-body">
|
|
{/* Valores principais */}
|
|
<div className="row g-3 mb-4">
|
|
<div className="col-md-4">
|
|
<div className="p-3 rounded" style={{ backgroundColor: '#0f172a' }}>
|
|
<div className="text-slate-400 small mb-1">Valor de Adquisición</div>
|
|
<div className="text-white fs-5 fw-bold">
|
|
{formatCurrency(selectedAsset.acquisition_value, selectedAsset.currency)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="col-md-4">
|
|
<div className="p-3 rounded" style={{ backgroundColor: '#0f172a' }}>
|
|
<div className="text-slate-400 small mb-1">Valor Actual</div>
|
|
<div className="text-success fs-5 fw-bold">
|
|
{formatCurrency(selectedAsset.current_value, selectedAsset.currency)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="col-md-4">
|
|
<div className="p-3 rounded" style={{ backgroundColor: '#0f172a' }}>
|
|
<div className="text-slate-400 small mb-1">Rentabilidad</div>
|
|
{(() => {
|
|
const gainLoss = parseFloat(selectedAsset.current_value) - parseFloat(selectedAsset.acquisition_value);
|
|
const gainLossPercent = selectedAsset.acquisition_value > 0 ? (gainLoss / parseFloat(selectedAsset.acquisition_value)) * 100 : 0;
|
|
return (
|
|
<div className={`fs-5 fw-bold ${gainLoss >= 0 ? 'text-success' : 'text-danger'}`}>
|
|
<i className={`bi ${gainLoss >= 0 ? 'bi-arrow-up' : 'bi-arrow-down'} me-1`}></i>
|
|
{formatCurrency(Math.abs(gainLoss), selectedAsset.currency)} ({gainLossPercent.toFixed(1)}%)
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Informações detalhadas */}
|
|
<div className="row g-3">
|
|
<div className="col-12">
|
|
<h6 className="text-white mb-3">
|
|
<i className="bi bi-info-circle me-2"></i>
|
|
Información del Activo
|
|
</h6>
|
|
</div>
|
|
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Tipo de Activo</span>
|
|
<div className="text-white">
|
|
{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}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Estado</span>
|
|
<div>
|
|
<span className={`badge ${selectedAsset.status === 'active' ? 'bg-success' : 'bg-secondary'}`}>
|
|
{selectedAsset.status === 'active' ? 'Activo' : selectedAsset.status === 'disposed' ? 'Vendido' : selectedAsset.status}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{selectedAsset.acquisition_date && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Fecha de Adquisición</span>
|
|
<div className="text-white">
|
|
{new Date(selectedAsset.acquisition_date).toLocaleDateString('es-ES', { day: '2-digit', month: 'long', year: 'numeric' })}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Campos específicos por tipo */}
|
|
{selectedAsset.asset_type === 'real_estate' && (
|
|
<>
|
|
{selectedAsset.address && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Dirección</span>
|
|
<div className="text-white">{selectedAsset.address}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.city && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Ciudad</span>
|
|
<div className="text-white">{selectedAsset.city}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.property_area_m2 && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Área</span>
|
|
<div className="text-white">{selectedAsset.property_area_m2} m²</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{selectedAsset.asset_type === 'vehicle' && (
|
|
<>
|
|
{selectedAsset.vehicle_brand && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Marca/Modelo</span>
|
|
<div className="text-white">{selectedAsset.vehicle_brand} {selectedAsset.vehicle_model}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.vehicle_year && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Año</span>
|
|
<div className="text-white">{selectedAsset.vehicle_year}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.vehicle_plate && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Matrícula</span>
|
|
<div className="text-white">{selectedAsset.vehicle_plate}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.vehicle_mileage && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Kilometraje</span>
|
|
<div className="text-white">{selectedAsset.vehicle_mileage.toLocaleString()} km</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{selectedAsset.asset_type === 'investment' && (
|
|
<>
|
|
{selectedAsset.investment_type && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Tipo de Inversión</span>
|
|
<div className="text-white">
|
|
{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}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.institution && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Institución</span>
|
|
<div className="text-white">{selectedAsset.institution}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.ticker && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Ticker/Símbolo</span>
|
|
<div className="text-white">{selectedAsset.ticker}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.quantity && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Cantidad</span>
|
|
<div className="text-white">{selectedAsset.quantity}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.interest_rate && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Tasa de Interés</span>
|
|
<div className="text-white">{selectedAsset.interest_rate}%</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{selectedAsset.maturity_date && (
|
|
<div className="col-md-6">
|
|
<div className="mb-3">
|
|
<span className="text-slate-400 small">Fecha de Vencimiento</span>
|
|
<div className="text-white">
|
|
{new Date(selectedAsset.maturity_date).toLocaleDateString('es-ES', { day: '2-digit', month: 'long', year: 'numeric' })}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="modal-footer border-0">
|
|
<button type="button" className="btn btn-primary me-2" onClick={handleEditAsset}>
|
|
<i className="bi bi-pencil me-2"></i>
|
|
Editar
|
|
</button>
|
|
<button type="button" className="btn btn-secondary" onClick={handleCloseAssetDetail}>
|
|
Cerrar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Modal de Confirmação de Exclusão */}
|
|
<ConfirmModal
|
|
show={showDeleteModal}
|
|
onHide={() => setShowDeleteModal(false)}
|
|
onConfirm={handleDeleteConfirm}
|
|
title={t('accounts.deleteAccount')}
|
|
message={t('accounts.deleteConfirm')}
|
|
confirmText={t('common.delete')}
|
|
loading={saving}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Accounts;
|