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:
parent
9800f987df
commit
5f3bf18b99
16
CHANGELOG.md
16
CHANGELOG.md
@ -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
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user