Fix: Garantir herança de cor em subcategorias
- Usar displayColor ao passar cor para filhos (não category.color) - Fallback para cor padrão se categoria não tiver cor - Garante que todas as subcategorias usem a mesma cor da categoria pai
This commit is contained in:
parent
1637e5da0c
commit
b0724d7b2c
@ -250,8 +250,8 @@ const Categories = () => {
|
||||
const renderCategory = (category, level = 0, parentColor = null) => {
|
||||
const hasChildren = category.children && category.children.length > 0;
|
||||
const isExpanded = expandedCategories[category.id];
|
||||
// Subcategorias herdam a cor da categoria pai
|
||||
const displayColor = level > 0 && parentColor ? parentColor : category.color;
|
||||
// Subcategorias herdam a cor da categoria pai - garantir que sempre use a cor do pai se disponível
|
||||
const displayColor = level > 0 && parentColor ? parentColor : (category.color || '#94a3b8');
|
||||
|
||||
return (
|
||||
<div key={category.id}>
|
||||
@ -343,7 +343,7 @@ const Categories = () => {
|
||||
{/* Children */}
|
||||
{hasChildren && isExpanded && (
|
||||
<div>
|
||||
{category.children.map(child => renderCategory(child, level + 1, category.color))}
|
||||
{category.children.map(child => renderCategory(child, level + 1, displayColor))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -35,6 +35,15 @@ const Reports = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currency, date: formatDate } = 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);
|
||||
}, []);
|
||||
|
||||
const [activeTab, setActiveTab] = useState('summary');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [year, setYear] = useState(new Date().getFullYear());
|
||||
@ -178,11 +187,11 @@ const Reports = () => {
|
||||
const monthlyAvgBalance = (summary.current.balance / 12).toFixed(2);
|
||||
|
||||
return (
|
||||
<div className="row g-4">
|
||||
<div className="row g-3">
|
||||
{/* Year Selector */}
|
||||
<div className="col-12">
|
||||
<div className="d-flex align-items-center justify-content-between mb-3">
|
||||
<h5 className="text-white mb-0">
|
||||
<h5 className="text-white mb-0" style={{ fontSize: isMobile ? '0.95rem' : '1.25rem' }}>
|
||||
<i className="bi bi-calendar3 me-2"></i>
|
||||
{t('reports.annualSummary')}
|
||||
</h5>
|
||||
@ -190,8 +199,12 @@ const Reports = () => {
|
||||
{[2024, 2025].map(y => (
|
||||
<button
|
||||
key={y}
|
||||
className={`btn btn-sm ${year === y ? 'btn-primary' : 'btn-outline-secondary'}`}
|
||||
className={`btn ${year === y ? 'btn-primary' : 'btn-outline-secondary'}`}
|
||||
onClick={() => setYear(y)}
|
||||
style={{
|
||||
fontSize: isMobile ? '0.75rem' : '0.875rem',
|
||||
padding: isMobile ? '0.25rem 0.75rem' : '0.375rem 1rem'
|
||||
}}
|
||||
>
|
||||
{y}
|
||||
</button>
|
||||
@ -201,29 +214,38 @@ const Reports = () => {
|
||||
</div>
|
||||
|
||||
{/* Main KPI Cards */}
|
||||
<div className="col-lg-3 col-md-6">
|
||||
<div className={isMobile ? "col-12" : "col-lg-3 col-md-6"}>
|
||||
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #059669 0%, #047857 100%)' }}>
|
||||
<div className="card-body text-white">
|
||||
<div className="d-flex align-items-center justify-content-between mb-3">
|
||||
<div>
|
||||
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.income')}</div>
|
||||
<h3 className="mb-0 fw-bold">{currency(summary.current.income, summary.currency)}</h3>
|
||||
</div>
|
||||
<div className="fs-1 opacity-50">
|
||||
<i className="bi bi-arrow-up-circle"></i>
|
||||
<div className="card-body text-white" style={{ padding: isMobile ? '0.75rem' : '1rem' }}>
|
||||
<div className="d-flex align-items-center justify-content-between mb-2">
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<div className="opacity-75 text-uppercase fw-semibold mb-1" style={{ fontSize: isMobile ? '0.65rem' : '0.75rem' }}>
|
||||
{t('reports.income')}
|
||||
</div>
|
||||
<h3 className="mb-0 fw-bold" style={{ fontSize: isMobile ? '1.1rem' : '1.5rem' }}>
|
||||
{currency(summary.current.income, summary.currency)}
|
||||
</h3>
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<div className="fs-1 opacity-50">
|
||||
<i className="bi bi-arrow-up-circle"></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{summary.variation.income !== 0 && !isNaN(summary.variation.income) && isFinite(summary.variation.income) && (
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className={`badge ${summary.variation.income >= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
|
||||
<span className={`badge ${summary.variation.income >= 0 ? 'bg-white text-success' : 'bg-white text-danger'}`}
|
||||
style={{ fontSize: isMobile ? '0.65rem' : '0.75rem', padding: isMobile ? '0.25rem 0.5rem' : '0.375rem 0.75rem' }}>
|
||||
<i className={`bi bi-arrow-${summary.variation.income >= 0 ? 'up' : 'down'} me-1`}></i>
|
||||
{Math.abs(summary.variation.income).toFixed(1)}%
|
||||
</span>
|
||||
<small className="opacity-75">{t('reports.vsLastYear')}</small>
|
||||
<small className="opacity-75" style={{ fontSize: isMobile ? '0.65rem' : '0.75rem' }}>
|
||||
{t('reports.vsLastYear')}
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
<hr className="my-2 opacity-25" />
|
||||
<div className="small d-flex justify-content-between">
|
||||
<div className="d-flex justify-content-between" style={{ fontSize: isMobile ? '0.7rem' : '0.875rem' }}>
|
||||
<span className="opacity-75">{t('reports.monthlyAverage')}:</span>
|
||||
<span className="fw-semibold">{currency(monthlyAvgIncome, summary.currency)}</span>
|
||||
</div>
|
||||
@ -231,29 +253,38 @@ const Reports = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-lg-3 col-md-6">
|
||||
<div className={isMobile ? "col-12" : "col-lg-3 col-md-6"}>
|
||||
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #dc2626 0%, #b91c1c 100%)' }}>
|
||||
<div className="card-body text-white">
|
||||
<div className="d-flex align-items-center justify-content-between mb-3">
|
||||
<div>
|
||||
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.expenses')}</div>
|
||||
<h3 className="mb-0 fw-bold">{currency(summary.current.expense, summary.currency)}</h3>
|
||||
</div>
|
||||
<div className="fs-1 opacity-50">
|
||||
<i className="bi bi-arrow-down-circle"></i>
|
||||
<div className="card-body text-white" style={{ padding: isMobile ? '0.75rem' : '1rem' }}>
|
||||
<div className="d-flex align-items-center justify-content-between mb-2">
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<div className="opacity-75 text-uppercase fw-semibold mb-1" style={{ fontSize: isMobile ? '0.65rem' : '0.75rem' }}>
|
||||
{t('reports.expenses')}
|
||||
</div>
|
||||
<h3 className="mb-0 fw-bold" style={{ fontSize: isMobile ? '1.1rem' : '1.5rem' }}>
|
||||
{currency(summary.current.expense, summary.currency)}
|
||||
</h3>
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<div className="fs-1 opacity-50">
|
||||
<i className="bi bi-arrow-down-circle"></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{summary.variation.expense !== 0 && !isNaN(summary.variation.expense) && isFinite(summary.variation.expense) && (
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className={`badge ${summary.variation.expense <= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
|
||||
<span className={`badge ${summary.variation.expense <= 0 ? 'bg-white text-success' : 'bg-white text-danger'}`}
|
||||
style={{ fontSize: isMobile ? '0.65rem' : '0.75rem', padding: isMobile ? '0.25rem 0.5rem' : '0.375rem 0.75rem' }}>
|
||||
<i className={`bi bi-arrow-${summary.variation.expense >= 0 ? 'up' : 'down'} me-1`}></i>
|
||||
{Math.abs(summary.variation.expense).toFixed(1)}%
|
||||
</span>
|
||||
<small className="opacity-75">{t('reports.vsLastYear')}</small>
|
||||
<small className="opacity-75" style={{ fontSize: isMobile ? '0.65rem' : '0.75rem' }}>
|
||||
{t('reports.vsLastYear')}
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
<hr className="my-2 opacity-25" />
|
||||
<div className="small d-flex justify-content-between">
|
||||
<div className="d-flex justify-content-between" style={{ fontSize: isMobile ? '0.7rem' : '0.875rem' }}>
|
||||
<span className="opacity-75">{t('reports.monthlyAverage')}:</span>
|
||||
<span className="fw-semibold">{currency(monthlyAvgExpense, summary.currency)}</span>
|
||||
</div>
|
||||
@ -261,29 +292,38 @@ const Reports = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-lg-3 col-md-6">
|
||||
<div className={isMobile ? "col-12" : "col-lg-3 col-md-6"}>
|
||||
<div className="card border-0 h-100" style={{ background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)' }}>
|
||||
<div className="card-body text-white">
|
||||
<div className="d-flex align-items-center justify-content-between mb-3">
|
||||
<div>
|
||||
<div className="opacity-75 small text-uppercase fw-semibold mb-1">{t('reports.balance')}</div>
|
||||
<h3 className="mb-0 fw-bold">{currency(summary.current.balance, summary.currency)}</h3>
|
||||
</div>
|
||||
<div className="fs-1 opacity-50">
|
||||
<i className="bi bi-wallet2"></i>
|
||||
<div className="card-body text-white" style={{ padding: isMobile ? '0.75rem' : '1rem' }}>
|
||||
<div className="d-flex align-items-center justify-content-between mb-2">
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<div className="opacity-75 text-uppercase fw-semibold mb-1" style={{ fontSize: isMobile ? '0.65rem' : '0.75rem' }}>
|
||||
{t('reports.balance')}
|
||||
</div>
|
||||
<h3 className="mb-0 fw-bold" style={{ fontSize: isMobile ? '1.1rem' : '1.5rem' }}>
|
||||
{currency(summary.current.balance, summary.currency)}
|
||||
</h3>
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<div className="fs-1 opacity-50">
|
||||
<i className="bi bi-wallet2"></i>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{summary.variation.balance !== 0 && !isNaN(summary.variation.balance) && isFinite(summary.variation.balance) && (
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span className={`badge ${summary.variation.balance >= 0 ? 'bg-white text-success' : 'bg-white text-danger'} px-2 py-1`}>
|
||||
<span className={`badge ${summary.variation.balance >= 0 ? 'bg-white text-success' : 'bg-white text-danger'}`}
|
||||
style={{ fontSize: isMobile ? '0.65rem' : '0.75rem', padding: isMobile ? '0.25rem 0.5rem' : '0.375rem 0.75rem' }}>
|
||||
<i className={`bi bi-arrow-${summary.variation.balance >= 0 ? 'up' : 'down'} me-1`}></i>
|
||||
{Math.abs(summary.variation.balance).toFixed(1)}%
|
||||
</span>
|
||||
<small className="opacity-75">{t('reports.vsLastYear')}</small>
|
||||
<small className="opacity-75" style={{ fontSize: isMobile ? '0.65rem' : '0.75rem' }}>
|
||||
{t('reports.vsLastYear')}
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
<hr className="my-2 opacity-25" />
|
||||
<div className="small d-flex justify-content-between">
|
||||
<div className="d-flex justify-content-between" style={{ fontSize: isMobile ? '0.7rem' : '0.875rem' }}>
|
||||
<span className="opacity-75">{t('reports.monthlyAverage')}:</span>
|
||||
<span className="fw-semibold">{currency(monthlyAvgBalance, summary.currency)}</span>
|
||||
</div>
|
||||
@ -1761,28 +1801,34 @@ const Reports = () => {
|
||||
return (
|
||||
<div className="reports-container">
|
||||
{/* Header */}
|
||||
<div className="d-flex justify-content-between align-items-center mb-4">
|
||||
<div className={`d-flex ${isMobile ? 'flex-column gap-2' : 'justify-content-between align-items-center'} mb-4`}>
|
||||
<div>
|
||||
<h4 className="text-white mb-1 fw-bold">
|
||||
<h4 className="text-white mb-1 fw-bold" style={{ fontSize: isMobile ? '1.1rem' : '1.5rem' }}>
|
||||
<i className="bi bi-bar-chart-line me-2"></i>
|
||||
{t('reports.title')}
|
||||
</h4>
|
||||
<p className="text-slate-400 mb-0 small">{t('reports.subtitle')}</p>
|
||||
<p className="text-slate-400 mb-0" style={{ fontSize: isMobile ? '0.75rem' : '0.875rem' }}>
|
||||
{t('reports.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="card border-0 mb-4" style={{ background: '#0f172a' }}>
|
||||
<div className="card-body p-2">
|
||||
<div className="d-flex flex-wrap gap-2">
|
||||
<div className="card-body" style={{ padding: isMobile ? '0.5rem' : '0.75rem' }}>
|
||||
<div className="d-flex flex-wrap gap-1">
|
||||
{tabs.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
className={`btn btn-sm ${activeTab === tab.id ? 'btn-primary' : 'btn-outline-secondary'}`}
|
||||
className={`btn ${activeTab === tab.id ? 'btn-primary' : 'btn-outline-secondary'}`}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
style={{
|
||||
fontSize: isMobile ? '0.7rem' : '0.875rem',
|
||||
padding: isMobile ? '0.25rem 0.5rem' : '0.375rem 0.75rem'
|
||||
}}
|
||||
>
|
||||
<i className={`bi ${tab.icon} me-1`}></i>
|
||||
{tab.label}
|
||||
<i className={`bi ${tab.icon} ${isMobile ? '' : 'me-1'}`}></i>
|
||||
{isMobile ? '' : tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user