import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { liabilityAccountService } from '../services/api'; import { useToast } from '../components/Toast'; import { ConfirmModal } from '../components/Modal'; import { useFormatters } from '../hooks'; const LiabilityAccounts = () => { const { t } = useTranslation(); const toast = useToast(); const { currency: formatCurrency } = useFormatters(); // Mobile detection const [isMobile, setIsMobile] = useState(window.innerWidth < 768); useEffect(() => { const handleResize = () => setIsMobile(window.innerWidth < 768); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // States const [accounts, setAccounts] = useState([]); const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); const [showImportModal, setShowImportModal] = useState(false); const [showDetailModal, setShowDetailModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showReconcileModal, setShowReconcileModal] = useState(false); const [selectedAccount, setSelectedAccount] = useState(null); const [selectedInstallment, setSelectedInstallment] = useState(null); const [eligibleTransactions, setEligibleTransactions] = useState([]); const [loadingTransactions, setLoadingTransactions] = useState(false); const [reconcileSearch, setReconcileSearch] = useState(''); const [markAsPaidOnReconcile, setMarkAsPaidOnReconcile] = useState(true); const [saving, setSaving] = useState(false); const [filter, setFilter] = useState({ status: '', is_active: '' }); const [showPriceAnalysisModal, setShowPriceAnalysisModal] = useState(false); // Import form state const [importForm, setImportForm] = useState({ file: null, name: '', creditor: '', contract_number: '', currency: 'EUR', description: '', }); useEffect(() => { loadAccounts(); }, [filter]); const loadAccounts = async () => { try { setLoading(true); const params = {}; if (filter.status) params.status = filter.status; if (filter.is_active !== '') params.is_active = filter.is_active; const response = await liabilityAccountService.getAll(params); if (response.success) { setAccounts(response.data); setSummary(response.summary); } } catch (error) { toast.error(t('liabilities.loadError')); } finally { setLoading(false); } }; const handleOpenImportModal = () => { setImportForm({ file: null, name: '', creditor: '', contract_number: '', currency: 'EUR', description: '', }); setShowImportModal(true); }; const handleCloseImportModal = () => { setShowImportModal(false); }; const handleImportChange = (e) => { const { name, value, files } = e.target; if (name === 'file') { setImportForm(prev => ({ ...prev, file: files[0] })); } else { setImportForm(prev => ({ ...prev, [name]: value })); } }; const handleImportSubmit = async (e) => { e.preventDefault(); if (!importForm.file) { toast.error(t('liabilities.selectFile')); return; } if (!importForm.name.trim()) { toast.error(t('validation.required')); return; } setSaving(true); try { const formData = new FormData(); formData.append('file', importForm.file); formData.append('name', importForm.name); formData.append('creditor', importForm.creditor); formData.append('contract_number', importForm.contract_number); formData.append('currency', importForm.currency); formData.append('description', importForm.description); const response = await liabilityAccountService.import(formData); if (response.success) { toast.success(t('liabilities.importSuccess', { count: response.imported_installments })); handleCloseImportModal(); loadAccounts(); // Abrir detalhes do contrato importado setSelectedAccount(response.data); setShowDetailModal(true); } } catch (error) { toast.error(error.response?.data?.message || t('liabilities.importError')); } finally { setSaving(false); } }; const handleOpenDetail = async (account) => { try { const response = await liabilityAccountService.getById(account.id); if (response.success) { setSelectedAccount(response.data); setShowDetailModal(true); } } catch (error) { toast.error(t('liabilities.loadError')); } }; const handleCloseDetail = () => { setShowDetailModal(false); setSelectedAccount(null); }; const handleDelete = async () => { if (!selectedAccount) return; setSaving(true); try { const response = await liabilityAccountService.delete(selectedAccount.id); if (response.success) { toast.success(t('liabilities.deleteSuccess')); setShowDeleteModal(false); setSelectedAccount(null); loadAccounts(); } } catch (error) { toast.error(t('liabilities.deleteError')); } finally { setSaving(false); } }; const handleMarkInstallmentPaid = async (installment) => { try { const response = await liabilityAccountService.updateInstallment( selectedAccount.id, installment.id, { status: 'paid' } ); if (response.success) { toast.success(t('liabilities.installmentPaid')); // Recarregar conta atualizada const accountResponse = await liabilityAccountService.getById(selectedAccount.id); if (accountResponse.success) { setSelectedAccount(accountResponse.data); } loadAccounts(); } } catch (error) { toast.error(t('liabilities.updateError')); } }; // ============================================ // Conciliação de Parcelas // ============================================ const handleOpenReconcileModal = async (installment) => { setSelectedInstallment(installment); setReconcileSearch(''); setMarkAsPaidOnReconcile(true); setShowReconcileModal(true); await loadEligibleTransactions(installment); }; const handleCloseReconcileModal = () => { setShowReconcileModal(false); setSelectedInstallment(null); setEligibleTransactions([]); }; const loadEligibleTransactions = async (installment, search = '') => { setLoadingTransactions(true); try { const params = {}; if (search) params.search = search; const response = await liabilityAccountService.getEligibleTransactions( selectedAccount.id, installment.id, params ); if (response.success) { setEligibleTransactions(response.data); } } catch (error) { toast.error(t('liabilities.loadError')); } finally { setLoadingTransactions(false); } }; const handleReconcileSearch = async (e) => { const value = e.target.value; setReconcileSearch(value); // Debounce search if (value.length >= 2 || value.length === 0) { await loadEligibleTransactions(selectedInstallment, value); } }; const handleReconcile = async (transaction) => { setSaving(true); try { const response = await liabilityAccountService.reconcile( selectedAccount.id, selectedInstallment.id, transaction.id, markAsPaidOnReconcile ); if (response.success) { toast.success(t('liabilities.reconcileSuccess')); handleCloseReconcileModal(); // Recarregar conta atualizada const accountResponse = await liabilityAccountService.getById(selectedAccount.id); if (accountResponse.success) { setSelectedAccount(accountResponse.data); } loadAccounts(); } } catch (error) { toast.error(error.response?.data?.message || t('liabilities.reconcileError')); } finally { setSaving(false); } }; const handleUnreconcile = async (installment) => { if (!confirm(t('liabilities.unreconcile') + '?')) return; setSaving(true); try { const response = await liabilityAccountService.unreconcile( selectedAccount.id, installment.id ); if (response.success) { toast.success(t('liabilities.unreconcileSuccess')); // Recarregar conta atualizada const accountResponse = await liabilityAccountService.getById(selectedAccount.id); if (accountResponse.success) { setSelectedAccount(accountResponse.data); } loadAccounts(); } } catch (error) { toast.error(t('liabilities.unreconcileError')); } finally { setSaving(false); } }; const formatPercent = (value) => { if (value === null || value === undefined) return '-'; return `${parseFloat(value).toFixed(2)}%`; }; const formatDate = (dateString) => { if (!dateString) return '-'; return new Date(dateString).toLocaleDateString(); }; const getStatusBadge = (status) => { const badges = { active: 'bg-primary', paid_off: 'bg-success', defaulted: 'bg-danger', renegotiated: 'bg-warning text-dark', pending: 'bg-secondary', paid: 'bg-success', partial: 'bg-info', overdue: 'bg-danger', cancelled: 'bg-dark', }; return badges[status] || 'bg-secondary'; }; const getStatusLabel = (status, isInstallment = false) => { const labels = isInstallment ? liabilityAccountService.installmentStatuses : liabilityAccountService.statuses; return labels[status] || status; }; // Calcular totais por moeda const getTotalsByCurrency = () => { const totals = {}; accounts.forEach(account => { const currency = account.currency || 'EUR'; if (!totals[currency]) { totals[currency] = { principal: 0, paid: 0, pending: 0, interest: 0, }; } totals[currency].principal += parseFloat(account.principal_amount) || 0; totals[currency].paid += parseFloat(account.total_paid) || 0; totals[currency].pending += parseFloat(account.total_pending) || 0; totals[currency].interest += parseFloat(account.total_interest) || 0; }); return totals; }; const totalsByCurrency = getTotalsByCurrency(); return (
{/* Header */}

{t('liabilities.title')}

{t('liabilities.subtitle')}
{/* Summary Cards */} {Object.keys(totalsByCurrency).length > 0 && (
{Object.entries(totalsByCurrency).map(([currency, totals]) => (
{t('liabilities.totalDebt')} ({currency})
{formatCurrency(totals.principal, currency)}
{t('liabilities.totalPaid')} ({currency})
{formatCurrency(totals.paid, currency)}
{t('liabilities.totalPending')} ({currency})
{formatCurrency(totals.pending, currency)}
{t('liabilities.totalInterest')} ({currency})
{formatCurrency(totals.interest, currency)}
))}
)} {/* Filters */}
{!isMobile && (
)}
{/* Contracts List */} {loading ? (
{t('common.loading')}
) : accounts.length === 0 ? (
{t('liabilities.noContracts')}

{t('liabilities.importHint')}

) : (
{accounts.map(account => (
{account.name}
{getStatusLabel(account.status)}
{account.creditor && (
{account.creditor}
)} {/* Progress Bar */}
{t('liabilities.progress')} {account.progress_percentage || 0}%
{/* Values */}
{t('liabilities.principal')}
{formatCurrency(account.principal_amount, account.currency)}
{t('liabilities.remaining')}
{formatCurrency(account.remaining_balance, account.currency)}
{t('liabilities.installments')}
{account.paid_installments}/{account.total_installments}
{t('liabilities.monthlyRate')}
{formatPercent(account.monthly_interest_rate)}
))}
)} {/* Import Modal */} {showImportModal && (
{t('liabilities.importContract')}
{t('liabilities.importInfo')}
{t('liabilities.fileFormatHint')}
)} {/* Detail Modal */} {showDetailModal && selectedAccount && (
{selectedAccount.name}
{/* Contract Type / Description */} {selectedAccount.description && (
{t('liabilities.contractType')}: {selectedAccount.description}
)} {/* Summary Cards */}
{t('liabilities.principal')}
{formatCurrency(selectedAccount.principal_amount, selectedAccount.currency)}
{t('liabilities.totalInterest')}
{formatCurrency(selectedAccount.total_interest, selectedAccount.currency)}
{t('liabilities.totalFees')}
{formatCurrency(selectedAccount.total_fees, selectedAccount.currency)}
{!isMobile &&
{t('liabilities.extraCharges')}
}
{t('liabilities.totalContract')}
{formatCurrency(selectedAccount.total_contract_value, selectedAccount.currency)}
{/* Interest Rates */}
{isMobile ? t('liabilities.monthly') : t('liabilities.monthlyRate')}
{formatPercent(selectedAccount.monthly_interest_rate)}
{isMobile ? t('liabilities.annual') : t('liabilities.annualRate')}
{formatPercent(selectedAccount.annual_interest_rate)}
{isMobile ? t('liabilities.total') : t('liabilities.totalRate')}
{formatPercent(selectedAccount.total_interest_rate)}
{/* Progress */}
{t('liabilities.paymentProgress')} {selectedAccount.paid_installments}/{selectedAccount.total_installments} {isMobile ? '' : t('liabilities.installments').toLowerCase()}
{selectedAccount.progress_percentage}%
{t('liabilities.paid')}: {formatCurrency(selectedAccount.principal_paid, selectedAccount.currency)} {t('liabilities.remaining')}: {formatCurrency(selectedAccount.remaining_balance, selectedAccount.currency)}
{/* Installments Table */}
{t('liabilities.installmentsList')}
{isMobile ? ( /* Mobile: Cards layout */
{selectedAccount.installments?.map(inst => (
{/* Header */}
{inst.installment_number} - {formatDate(inst.due_date)}
{getStatusLabel(inst.status, true)}
{/* Values */}
{t('liabilities.installmentAmount')}
{formatCurrency(inst.installment_amount, selectedAccount.currency)}
{inst.paid_amount > 0 && (
{t('liabilities.paidAmount')}
inst.installment_amount ? 'text-warning' : 'text-success'}`}> {formatCurrency(inst.paid_amount, selectedAccount.currency)} {inst.paid_amount > inst.installment_amount && }
)}
{t('liabilities.capital')}
{formatCurrency(inst.principal_amount, selectedAccount.currency)}
{t('liabilities.interest')}
{formatCurrency(inst.interest_amount, selectedAccount.currency)}
{inst.fee_amount > 0 && (
{t('liabilities.fees')}
{formatCurrency(inst.fee_amount, selectedAccount.currency)}
)}
{/* Reconciliation */} {inst.reconciled_transaction_id ? (
{t('liabilities.reconciled')}
) : (
{inst.status !== 'paid' && ( )}
)}
))}
) : ( /* Desktop: Table layout */
{selectedAccount.installments?.map(inst => ( ))}
# {t('liabilities.dueDate')} {t('liabilities.installmentAmount')} {t('liabilities.paidAmount')} {t('liabilities.capital')} {t('liabilities.interest')} {t('liabilities.fees')} {t('common.status')} {t('liabilities.reconciliation')}
{inst.installment_number} {formatDate(inst.due_date)} {formatCurrency(inst.installment_amount, selectedAccount.currency)} {inst.paid_amount > 0 ? ( inst.installment_amount ? 'text-warning' : ''}> {formatCurrency(inst.paid_amount, selectedAccount.currency)} {inst.paid_amount > inst.installment_amount && ( )} ) : '-'} {formatCurrency(inst.principal_amount, selectedAccount.currency)} {formatCurrency(inst.interest_amount, selectedAccount.currency)} {inst.fee_amount > 0 ? formatCurrency(inst.fee_amount, selectedAccount.currency) : '-'} {getStatusLabel(inst.status, true)} {inst.reconciled_transaction_id ? ( {t('liabilities.reconciled')} ) : ( {t('liabilities.notReconciled')} )}
{inst.status !== 'paid' && !inst.reconciled_transaction_id && ( )} {!inst.reconciled_transaction_id ? ( ) : ( )}
{t('common.total')} {formatCurrency(selectedAccount.total_contract_value, selectedAccount.currency)} {formatCurrency(selectedAccount.principal_amount, selectedAccount.currency)} {formatCurrency(selectedAccount.total_interest, selectedAccount.currency)} {formatCurrency(selectedAccount.total_fees, selectedAccount.currency)}
)}
)} {/* Reconcile Modal */} {showReconcileModal && selectedInstallment && (
{t('liabilities.reconcileInstallment')}
{/* Installment Info */}
{t('liabilities.installments')}: #{selectedInstallment.installment_number}
{t('liabilities.dueDate')}: {formatDate(selectedInstallment.due_date)}
{t('liabilities.installmentAmount')}: {formatCurrency(selectedInstallment.installment_amount, selectedAccount?.currency)}
{/* Options */}
setMarkAsPaidOnReconcile(e.target.checked)} />
{/* Search */}
{/* Transactions List */}
{t('liabilities.eligibleTransactions')}
{loadingTransactions ? (
{t('common.loading')}
) : eligibleTransactions.length === 0 ? (
{t('liabilities.noEligibleTransactions')}
) : (
{eligibleTransactions.map(tx => ( ))}
{t('transactions.date')} {t('transactions.description')} {t('transactions.account')} {t('transactions.amount')}
{formatDate(tx.effective_date || tx.planned_date)}
{tx.description || tx.original_description}
{tx.account?.name || '-'} {formatCurrency(Math.abs(tx.amount), tx.account?.currency)}
)}
)} {/* Delete Confirmation Modal */} setShowDeleteModal(false)} onConfirm={handleDelete} title={t('liabilities.deleteTitle')} message={t('liabilities.deleteConfirm', { name: selectedAccount?.name })} confirmText={t('common.delete')} cancelText={t('common.cancel')} variant="danger" loading={saving} /> {/* PRICE System Analysis Modal */} {showPriceAnalysisModal && selectedAccount && (
{t('liabilities.priceAnalysisTitle')} - {selectedAccount.name}
{/* Contract Summary */}
{t('liabilities.principal')}
{formatCurrency(selectedAccount.principal_amount, selectedAccount.currency)}
{t('liabilities.totalInterest')}
{formatCurrency(selectedAccount.total_interest, selectedAccount.currency)}
{t('liabilities.installments')}
{selectedAccount.total_installments}
{t('liabilities.totalContract')}
{formatCurrency(selectedAccount.total_contract_value, selectedAccount.currency)}
{/* Overview */}
{t('liabilities.priceOverview')}

{t('liabilities.priceOverviewText')}

{/* What is PRICE */}
{t('liabilities.whatIsPrice')}

{t('liabilities.whatIsPriceText')}

  • {t('liabilities.priceFeature1')}
  • {t('liabilities.priceFeature2')}
  • {t('liabilities.priceFeature3')}
{/* Mathematical Formula */}
{t('liabilities.priceMathFormula')}
PMT = PV × [i × (1 + i)ⁿ] / [(1 + i)ⁿ - 1]

{t('liabilities.priceWhere')}:

  • PMT = {t('liabilities.pricePMT')}
  • PV = {t('liabilities.pricePV')}
  • i = {t('liabilities.priceI')}
  • n = {t('liabilities.priceN')}
{t('liabilities.thisContract')}:
PV = {formatCurrency(selectedAccount.principal_amount, selectedAccount.currency)}
i = {formatPercent(selectedAccount.monthly_interest_rate)} {t('liabilities.perMonth')}
n = {selectedAccount.total_installments} {t('liabilities.installments').toLowerCase()}

PMT ≈ {formatCurrency(selectedAccount.installments?.[0]?.installment_amount || 0, selectedAccount.currency)}/{t('liabilities.perMonth')}
{/* Amortization Behavior */}
{t('liabilities.amortizationBehavior')}
{t('liabilities.earlyInstallments')}
  • {t('liabilities.earlyInstallmentsText1')}
  • {t('liabilities.earlyInstallmentsText2')}
{t('liabilities.lateInstallments')}
  • {t('liabilities.lateInstallmentsText1')}
  • {t('liabilities.lateInstallmentsText2')}
{selectedAccount.installments?.length >= 3 && (
{t('liabilities.visualExample')}:
{/* First installment */} {/* Middle installment */} {(() => { const midIndex = Math.floor(selectedAccount.installments.length / 2); const midInst = selectedAccount.installments[midIndex]; return ( ); })()} {/* Last installment */}
# {t('liabilities.installmentValue')} {t('liabilities.interest')} {t('liabilities.amortization')}
{selectedAccount.installments[0].installment_number} {formatCurrency(selectedAccount.installments[0].installment_amount, selectedAccount.currency)} {formatCurrency(selectedAccount.installments[0].interest_amount, selectedAccount.currency)} {formatCurrency(selectedAccount.installments[0].principal_amount, selectedAccount.currency)}
{midInst.installment_number} {formatCurrency(midInst.installment_amount, selectedAccount.currency)} {formatCurrency(midInst.interest_amount, selectedAccount.currency)} {formatCurrency(midInst.principal_amount, selectedAccount.currency)}
{selectedAccount.installments[selectedAccount.installments.length - 1].installment_number} {formatCurrency(selectedAccount.installments[selectedAccount.installments.length - 1].installment_amount, selectedAccount.currency)} {formatCurrency(selectedAccount.installments[selectedAccount.installments.length - 1].interest_amount, selectedAccount.currency)} {formatCurrency(selectedAccount.installments[selectedAccount.installments.length - 1].principal_amount, selectedAccount.currency)}
)}
{/* Interest Rates */}
{t('liabilities.interestRates')}
{t('liabilities.monthlyRate')}
{formatPercent(selectedAccount.monthly_interest_rate)}
{t('liabilities.perMonth')}
{t('liabilities.annualRate')}
{formatPercent(selectedAccount.annual_interest_rate)}
{t('liabilities.perYear')}
{t('liabilities.totalRate')}
{formatPercent(selectedAccount.total_interest_rate)}
{t('liabilities.interestOverPrincipal')}
{/* Contract Financial Summary */}
{t('liabilities.contractCost')}

{t('liabilities.contractCostText')}

{t('liabilities.principal')}
{formatCurrency(selectedAccount.principal_amount, selectedAccount.currency)}
{t('liabilities.totalInterest')}
+{formatCurrency(selectedAccount.total_interest, selectedAccount.currency)}
{t('liabilities.totalFees')}
+{formatCurrency(selectedAccount.total_fees, selectedAccount.currency)}
{t('liabilities.totalContract')}
{formatCurrency(selectedAccount.total_contract_value, selectedAccount.currency)}
{/* Financial Analysis Summary */}
{t('liabilities.financialSummary')}
  • {t('liabilities.summaryPointDynamic1', { ratio: selectedAccount.principal_amount > 0 ? (selectedAccount.total_interest / selectedAccount.principal_amount * 100).toFixed(0) : 0 })}
  • {t('liabilities.summaryPoint2')}
  • {t('liabilities.summaryPoint3')}
  • {t('liabilities.summaryPoint4')}
)}
); }; export default LiabilityAccounts;