v1.43.4 - Padronização de badges e botões em todo o sistema

- Badges: Estilo translúcido uniforme (bg-opacity-25 + text-color) via CSS global
- Afetados: RecurringTransactions, Accounts, Categories, TransactionsByWeek
- Widgets: UpcomingWidget, OverdueWidget, CalendarWidget, OverpaymentsAnalysis
- Botões: Estilo outline padronizado (btn-outline-*) em RecurringTransactions
- Simplificação: Remover classes redundantes dos JSX
This commit is contained in:
marcoitaloesp-ai 2025-12-16 12:48:08 +00:00 committed by GitHub
parent 9800f987df
commit 5f3bf18b99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 224 additions and 26 deletions

View File

@ -5,6 +5,22 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/).
Este projeto adota [Versionamento Semântico](https://semver.org/pt-BR/).
## [1.43.4] - 2025-12-16
### Improved
- **Badges Padronizados** - Estilo translúcido consistente em todo o sistema
- Padrão global via CSS: `bg-{color}` → estilo `bg-opacity-25 text-{color}` automático
- Aparência moderna e uniforme: background translúcido + texto colorido
- Afetados: RecurringTransactions, Accounts, Categories, TransactionsByWeek, FinancialHealth, CostCenters
- Widgets: UpcomingWidget, OverdueWidget, CalendarWidget, OverpaymentsAnalysis
- Simplificação: remover classes redundantes dos JSX, CSS aplica estilo
- **Botões de Ação Padronizados** - RecurringTransactions
- Estilo outline consistente: `btn-outline-{color}` em todas as abas
- Templates e Instâncias usam mesmo padrão visual
- Info (visualizar), Primary (executar), Success (editar), Warning (adiar), Danger (excluir)
## [1.43.3] - 2025-12-16
### Improved

View File

@ -1 +1 @@
1.43.3
1.43.4

View File

@ -606,7 +606,7 @@ const CalendarWidget = () => {
{item.description}
</span>
{item.type === 'recurring' && (
<span className="badge bg-warning text-dark" style={{ fontSize: '9px' }}>
<span className="badge bg-warning" style={{ fontSize: '9px' }}>
#{item.occurrence_number}
</span>
)}

View File

@ -226,7 +226,7 @@ const OverpaymentsAnalysis = ({ data, loading, onTransactionClick }) => {
)}
</div>
{(!isMobile || isExpanded) && (
<span className={`badge bg-warning text-dark ${isMobile ? 'px-2 py-1' : 'px-3 py-2'}`}>
<span className={`badge bg-warning ${isMobile ? 'px-2 py-1' : 'px-3 py-2'}`}>
<i className="bi bi-arrow-up-right me-1"></i>
Total: {currency(totalOverpayment, 'BRL')}
</span>
@ -322,7 +322,7 @@ const OverpaymentsAnalysis = ({ data, loading, onTransactionClick }) => {
<span className="text-white" style={{ fontSize: '0.75rem', fontWeight: '500' }}>
{tx.description.length > 25 ? tx.description.substring(0, 25) + '...' : tx.description}
</span>
<span className="badge bg-warning text-dark ms-2" style={{ fontSize: '0.65rem' }}>
<span className="badge bg-warning ms-2" style={{ fontSize: '0.65rem' }}>
+{currency(tx.variance, 'BRL')}
</span>
</div>
@ -404,7 +404,7 @@ const OverpaymentsAnalysis = ({ data, loading, onTransactionClick }) => {
{currency(tx.actual_amount, 'BRL')}
</td>
<td className="text-end border-0">
<span className="badge bg-warning text-dark small">
<span className="badge bg-warning small">
<i className="bi bi-arrow-up me-1" style={{ fontSize: '10px' }}></i>
+{currency(tx.variance, 'BRL')}
</span>

View File

@ -206,7 +206,7 @@ const UpcomingWidget = () => {
{item.account?.name || '-'}
</small>
{item.type === 'recurring' && (
<span className="badge bg-warning text-dark" style={{ fontSize: '8px', padding: '1px 4px' }}>
<span className="badge bg-warning" style={{ fontSize: '8px', padding: '1px 4px' }}>
#{item.occurrence_number}
</span>
)}

View File

@ -2878,3 +2878,11 @@ a,
.category-dropdown-scroll::-webkit-scrollbar-thumb:hover {
background: rgba(100, 116, 139, 0.7);
}
/* Badges padronizados - estilo outline */
.badge.bg-primary { background-color: rgba(59, 130, 246, 0.25) !important; color: #3b82f6 !important; }
.badge.bg-success { background-color: rgba(34, 197, 94, 0.25) !important; color: #22c55e !important; }
.badge.bg-danger { background-color: rgba(239, 68, 68, 0.25) !important; color: #ef4444 !important; }
.badge.bg-warning { background-color: rgba(234, 179, 8, 0.25) !important; color: #eab308 !important; }
.badge.bg-info { background-color: rgba(14, 165, 233, 0.25) !important; color: #0ea5e9 !important; }
.badge.bg-secondary { background-color: rgba(148, 163, 184, 0.25) !important; color: #94a3b8 !important; }

View File

@ -508,9 +508,9 @@ const Accounts = () => {
{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-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>
<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' }}>
@ -575,9 +575,9 @@ const Accounts = () => {
</td>
<td className="py-3 text-center" style={{ backgroundColor: 'transparent' }}>
{account.is_active ? (
<span className="badge bg-success bg-opacity-25 text-success">{t('common.active')}</span>
<span className="badge bg-success">{t('common.active')}</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-secondary">{t('common.inactive')}</span>
<span className="badge bg-secondary">{t('common.inactive')}</span>
)}
</td>
<td className="py-3 text-end pe-4" style={{ backgroundColor: 'transparent' }}>
@ -664,15 +664,15 @@ const Accounts = () => {
</div>
<div>
{liability.status === 'active' ? (
<span className="badge bg-primary bg-opacity-25 text-primary" style={{ fontSize: '0.65rem' }}>
<span className="badge bg-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' }}>
<span className="badge bg-success" style={{ fontSize: '0.65rem' }}>
{t('liabilities.paid')}
</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-secondary" style={{ fontSize: '0.65rem' }}>
<span className="badge bg-secondary" style={{ fontSize: '0.65rem' }}>
{liability.status}
</span>
)}
@ -783,11 +783,11 @@ const Accounts = () => {
</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>
<span className="badge bg-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-success">{t('liabilities.paid')}</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-secondary">{liability.status}</span>
<span className="badge bg-secondary">{liability.status}</span>
)}
</td>
</tr>

View File

@ -306,9 +306,9 @@ const Categories = () => {
{/* Status */}
<div className="me-3">
{category.is_active ? (
<span className="badge bg-success bg-opacity-25 text-success">{t('common.active')}</span>
<span className="badge bg-success">{t('common.active')}</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-secondary">{t('common.inactive')}</span>
<span className="badge bg-secondary">{t('common.inactive')}</span>
)}
</div>
@ -759,7 +759,7 @@ const Categories = () => {
{item.description}
</td>
<td>
<span className="badge bg-warning text-dark">{item.matched_keyword}</span>
<span className="badge bg-warning">{item.matched_keyword}</span>
</td>
<td className="text-info">{item.category_name}</td>
</tr>

View File

@ -345,9 +345,9 @@ const CostCenters = () => {
{/* Status */}
<div className="d-flex justify-content-between align-items-center">
{item.is_active ? (
<span className="badge bg-success bg-opacity-25 text-success">{t('common.active')}</span>
<span className="badge bg-success">{t('common.active')}</span>
) : (
<span className="badge bg-secondary bg-opacity-25 text-secondary">{t('common.inactive')}</span>
<span className="badge bg-secondary">{t('common.inactive')}</span>
)}
</div>
</div>

View File

@ -847,7 +847,7 @@ const FinancialHealth = () => {
<span className="text-white">{t(`financialHealth.trend.${data.trends?.income_trend?.direction}`)}</span>
</div>
</div>
<span className="badge bg-success bg-opacity-25 text-success">
<span className="badge bg-success">
{data.trends?.income_trend?.strength}%
</span>
</div>

View File

@ -7,6 +7,9 @@ import { recurringService, accountService, categoryService } from '../services/a
const RecurringTransactions = () => {
const { t } = useTranslation();
// Mobile detection
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
// State
const [templates, setTemplates] = useState([]);
const [pendingInstances, setPendingInstances] = useState([]);
@ -80,6 +83,13 @@ const RecurringTransactions = () => {
loadData();
}, [loadData]);
// Mobile resize detection
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth < 768);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// Toast helper
const showToast = (message, type = 'success') => {
setToast({ show: true, message, type });
@ -314,7 +324,7 @@ const RecurringTransactions = () => {
>
<i className="bi bi-clock me-2" />
{t('recurring.pendingInstances')}
<span className="badge bg-warning text-dark ms-2">{pendingInstances.length}</span>
<span className="badge bg-warning ms-2">{pendingInstances.length}</span>
</button>
</li>
</ul>
@ -379,7 +389,88 @@ const RecurringTransactions = () => {
<i className="bi bi-inbox display-1" />
<p className="mt-3">{t('recurring.noTemplates')}</p>
</div>
) : isMobile ? (
// Mobile: Cards Layout
<div className="d-flex flex-column gap-2 p-2">
{templates.map((template) => (
<div key={template.id} className="card border-secondary" style={{ background: '#0f172a' }}>
<div className="card-body p-3">
{/* Header: Nome + Status */}
<div className="d-flex justify-content-between align-items-start mb-2">
<div className="flex-grow-1" style={{ minWidth: 0 }}>
<div className="fw-bold text-white mb-1" style={{ fontSize: '0.9rem' }}>
{template.name}
</div>
{template.transaction_description && (
<div className="text-muted" style={{ fontSize: '0.7rem' }}>
{template.transaction_description}
</div>
)}
</div>
<span className={`badge ${template.is_active ? 'bg-success' : 'bg-secondary'} ms-2`} style={{ fontSize: '0.65rem' }}>
{template.is_active ? t('common.active') : t('common.inactive')}
</span>
</div>
{/* Frequência + Tipo */}
<div className="d-flex gap-2 mb-2 flex-wrap">
<span className="badge bg-primary" style={{ fontSize: '0.7rem' }}>
<i className="bi bi-clock me-1"></i>
{t(`recurring.frequencies.${template.frequency}`, template.frequency)}
{template.frequency_interval > 1 && ` (x${template.frequency_interval})`}
</span>
<span className={`badge ${template.type === 'expense' ? 'bg-danger' : 'bg-success'}`} style={{ fontSize: '0.7rem' }}>
{t(`transactions.${template.type}`)}
</span>
{(template.pending_instances_count || 0) > 0 && (
<span className="badge bg-warning" style={{ fontSize: '0.7rem' }}>
{template.pending_instances_count} {t('recurring.pendingInstances')}
</span>
)}
</div>
{/* Valor + Ações */}
<div className="d-flex justify-content-between align-items-center pt-2" style={{ borderTop: '1px solid #334155' }}>
<div className={`fw-bold ${template.type === 'expense' ? 'text-danger' : 'text-success'}`} style={{ fontSize: '1rem' }}>
{formatCurrency(template.planned_amount)}
</div>
<div className="btn-group btn-group-sm">
<button
className="btn btn-sm btn-outline-info"
onClick={() => handleViewInstances(template)}
title={t('recurring.actions.viewInstances')}
>
<i className="bi bi-eye" />
</button>
<button
className="btn btn-sm btn-outline-primary"
onClick={() => handleEditTemplate(template)}
title={t('common.edit')}
>
<i className="bi bi-pencil" />
</button>
<button
className={`btn btn-sm btn-outline-${template.is_active ? 'warning' : 'success'}`}
onClick={() => handlePauseResume(template)}
title={template.is_active ? t('recurring.actions.pause') : t('recurring.actions.resume')}
>
<i className={`bi bi-${template.is_active ? 'pause' : 'play'}`} />
</button>
<button
className="btn btn-sm btn-outline-danger"
onClick={() => handleDelete(template)}
title={t('common.delete')}
>
<i className="bi bi-trash" />
</button>
</div>
</div>
</div>
</div>
))}
</div>
) : (
// Desktop: Table Layout
<div className="table-responsive">
<table className="table table-dark table-hover">
<thead>
@ -417,7 +508,7 @@ const RecurringTransactions = () => {
{formatCurrency(template.planned_amount)}
</td>
<td className="text-center">
<span className="badge bg-warning text-dark">
<span className="badge bg-warning">
{template.pending_instances_count || 0}
</span>
</td>
@ -479,7 +570,90 @@ const RecurringTransactions = () => {
<i className="bi bi-check-circle display-1" />
<p className="mt-3">{t('recurring.noPendingInstances')}</p>
</div>
) : isMobile ? (
// Mobile: Cards Layout
<div className="d-flex flex-column gap-2 p-2">
{pendingInstances.map((instance) => {
const days = Math.ceil((new Date(instance.due_date) - new Date()) / (1000 * 60 * 60 * 24));
const isOverdue = days < 0;
const isDueToday = days === 0;
return (
<div key={instance.id} className={`card border-${isOverdue ? 'danger' : isDueToday ? 'warning' : 'secondary'}`} style={{ background: '#0f172a' }}>
<div className="card-body p-3">
{/* Header: Template + Status */}
<div className="d-flex justify-content-between align-items-start mb-2">
<div className="flex-grow-1" style={{ minWidth: 0 }}>
<div className="fw-bold text-white mb-1" style={{ fontSize: '0.9rem' }}>
{instance.template?.name}
</div>
<div className="text-muted" style={{ fontSize: '0.7rem' }}>
{instance.template?.account?.name} #{instance.occurrence_number}
</div>
</div>
<span className={`badge ${getStatusBadge(instance.status)} ms-2`} style={{ fontSize: '0.65rem' }}>
{t(`recurring.status.${instance.status}`)}
</span>
</div>
{/* Data de vencimento */}
<div className="mb-2">
<div className={`${isOverdue ? 'text-danger' : isDueToday ? 'text-warning' : 'text-slate-300'}`} style={{ fontSize: '0.75rem' }}>
<i className="bi bi-calendar me-1"></i>
{formatDate(instance.due_date)}
{instance.status === 'pending' && (
<span className="ms-2">
{isOverdue && `(${Math.abs(days)} ${t('recurring.daysOverdue')})`}
{isDueToday && `(${t('recurring.dueToday')})`}
{!isOverdue && !isDueToday && `(${days} ${t('recurring.daysUntilDue')})`}
</span>
)}
</div>
</div>
{/* Valor + Ações */}
<div className="d-flex justify-content-between align-items-center pt-2" style={{ borderTop: '1px solid #334155' }}>
<div className="fw-bold text-white" style={{ fontSize: '1rem' }}>
{formatCurrency(instance.planned_amount)}
</div>
<div className="btn-group btn-group-sm">
<button
className="btn btn-sm btn-outline-success"
onClick={() => handlePayInstance(instance)}
title={t('recurring.actions.pay')}
>
<i className="bi bi-check-lg" />
</button>
<button
className="btn btn-sm btn-outline-primary"
onClick={() => handleReconcileInstance(instance)}
title={t('recurring.actions.reconcile')}
>
<i className="bi bi-link-45deg" />
</button>
<button
className="btn btn-sm btn-outline-info"
onClick={() => handleEditInstance(instance)}
title={t('common.edit')}
>
<i className="bi bi-pencil" />
</button>
<button
className="btn btn-sm btn-outline-warning"
onClick={() => handleSkipInstance(instance)}
title={t('recurring.actions.skip')}
>
<i className="bi bi-skip-forward" />
</button>
</div>
</div>
</div>
</div>
);
})}
</div>
) : (
// Desktop: Table Layout
<div className="table-responsive">
<table className="table table-dark table-hover">
<thead>

View File

@ -1414,7 +1414,7 @@ export default function Transactions() {
{/* Account + Category */}
<div className="d-flex flex-wrap gap-2 mb-2">
<span className="badge bg-secondary bg-opacity-25 text-slate-300" style={{ fontSize: '0.7rem' }}>
<span className="badge bg-secondary" style={{ fontSize: '0.7rem' }}>
<i className="bi bi-wallet2 me-1"></i>
{transaction.account?.name}
</span>
@ -2674,7 +2674,7 @@ export default function Transactions() {
<label className="form-check-label text-slate-300" htmlFor="addKeywordCheck">
<i className="bi bi-key me-1 text-warning"></i>
{t('transactions.addAsKeyword')}
<span className="badge bg-warning text-dark ms-2">
<span className="badge bg-warning ms-2">
"{filters.search}"
</span>
</label>