v1.43.2 - Mobile: Contas Passivo otimizadas

This commit is contained in:
marcoitaloesp-ai 2025-12-16 11:43:59 +00:00 committed by GitHub
parent a244632e0a
commit be7bed5c99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 315 additions and 115 deletions

View File

@ -5,6 +5,20 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/).
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
## [1.43.2] - 2025-12-16
### Improved
- **Página Accounts - Contas Passivo Mobile** - Layout em cards para mobile
- Seção "Passivos" convertida para cards em dispositivos mobile
- Cards mostram: ícone + nome + status + número contrato + credor
- Valores principal e saldo devedor lado a lado
- Barra de progresso com percentagem visual
- Fontes otimizadas: 0.65-0.85rem para legibilidade mobile
- Padding compacto: p-3, background escuro (#0f172a)
- Mantém onClick para navegar para /liabilities
- Desktop mantém layout de tabela completo
## [1.43.1] - 2025-12-16
### Fixed

View File

@ -1 +1 @@
1.43.1
1.43.2

View File

@ -26,6 +26,7 @@ const Accounts = () => {
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 [formData, setFormData] = useState({
name: '',
@ -45,6 +46,12 @@ const Accounts = () => {
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]);
@ -293,53 +300,55 @@ const Accounts = () => {
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 className="text-white mb-1">
<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>
<p className="text-slate-400 mb-0">{t('accounts.title')}</p>
{!isMobile && <p className="text-slate-400 mb-0">{t('accounts.title')}</p>}
</div>
<div className="d-flex gap-2">
<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>
<button className="btn btn-primary" onClick={() => handleOpenModal()}>
{!isMobile && (
<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>
)}
<button className={`btn btn-primary ${isMobile ? 'btn-sm' : ''}`} onClick={() => handleOpenModal()}>
<i className="bi bi-plus-lg me-2"></i>
{t('accounts.newAccount')}
{isMobile ? t('common.add') : t('accounts.newAccount')}
</button>
</div>
</div>
{/* Summary Cards */}
<div className="row mb-4">
<div className={`row ${isMobile ? 'g-2 mb-3' : 'mb-4'}`}>
{/* Total por Moeda */}
<div className="col-md-6">
<div className="card border-0 h-100" style={{ background: '#1e293b' }}>
<div className="card-body">
<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 p-3 me-3">
<i className="bi bi-wallet2 text-primary fs-4"></i>
<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 small">{t('dashboard.totalBalance')}</p>
<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 gap-3">
<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'}`}>
<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">{currency}</small>
<small className="text-slate-500" style={isMobile ? { fontSize: '0.65rem' } : undefined}>{currency}</small>
</div>
))}
</div>
@ -353,16 +362,16 @@ const Accounts = () => {
</div>
{/* Contas Ativas e Total */}
<div className="col-md-3">
<div className="card border-0 h-100" style={{ background: '#1e293b' }}>
<div className="card-body">
<div className="d-flex align-items-center">
<div className="rounded-circle bg-success bg-opacity-25 p-3 me-3">
<i className="bi bi-check-circle text-success fs-4"></i>
<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 small">{t('common.active')}</p>
<h3 className="mb-0 text-white">
<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>
@ -370,16 +379,16 @@ const Accounts = () => {
</div>
</div>
</div>
<div className="col-md-3">
<div className="card border-0 h-100" style={{ background: '#1e293b' }}>
<div className="card-body">
<div className="d-flex align-items-center">
<div className="rounded-circle bg-info bg-opacity-25 p-3 me-3">
<i className="bi bi-credit-card text-info fs-4"></i>
<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 small">{t('common.total')}</p>
<h3 className="mb-0 text-white">{getTotalAccounts()}</h3>
<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>
@ -422,7 +431,7 @@ const Accounts = () => {
{/* Accounts List */}
<div className="card border-0" style={{ background: '#1e293b' }}>
<div className="card-body p-0">
<div className={`card-body ${isMobile ? 'p-2' : 'p-0'}`}>
{loading ? (
<div className="text-center py-5">
<div className="spinner-border text-primary" role="status">
@ -438,7 +447,88 @@ const Accounts = () => {
{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' }}> {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={() => handleOpenModal(account)}
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 bg-opacity-25 text-success" style={{ fontSize: '0.65rem' }}>{t('common.active')}</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-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' }}>
@ -525,8 +615,8 @@ const Accounts = () => {
{/* Liability Accounts Section */}
{liabilityAccounts.length > 0 && (filter.type === '' || filter.type === 'liability') && (
<div className="card border-0 mt-4" style={{ background: '#1e293b' }}>
<div className="card-header border-0 d-flex justify-content-between align-items-center" style={{ background: '#1e293b', borderBottom: '1px solid #334155' }}>
<h5 className="mb-0 text-white">
<div className={`card-header border-0 d-flex justify-content-between align-items-center ${isMobile ? 'py-2 px-3' : ''}`} style={{ background: '#1e293b', borderBottom: '1px solid #334155' }}>
<h5 className={`mb-0 text-white ${isMobile ? 'fs-6' : ''}`}>
<i className="bi bi-file-earmark-text me-2 text-danger"></i>
{t('liabilities.title')}
</h5>
@ -538,78 +628,174 @@ const Accounts = () => {
{t('common.details')}
</button>
</div>
<div className="card-body p-0">
<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={`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' }}>
{liability.contract_number}
</div>
)}
</div>
<div>
{liability.status === 'active' ? (
<span className="badge bg-primary bg-opacity-25 text-primary" style={{ fontSize: '0.65rem' }}>
{t('common.active')}
</span>
) : liability.status === 'paid_off' ? (
<span className="badge bg-success bg-opacity-25 text-success" style={{ fontSize: '0.65rem' }}>
{t('liabilities.paid')}
</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-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="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"> {liability.contract_number}</small>
)}
</div>
className="progress-bar bg-success"
style={{ width: `${liability.progress_percentage || 0}%` }}
></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 bg-opacity-25 text-primary">{t('common.active')}</span>
) : liability.status === 'paid_off' ? (
<span className="badge bg-success bg-opacity-25 text-success">{t('liabilities.paid')}</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-secondary">{liability.status}</span>
)}
</td>
</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>
))}
</tbody>
</table>
</div>
</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"> {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 bg-opacity-25 text-primary">{t('common.active')}</span>
) : liability.status === 'paid_off' ? (
<span className="badge bg-success bg-opacity-25 text-success">{t('liabilities.paid')}</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-secondary">{liability.status}</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
)}