webmoney/frontend/src/components/dashboard/UpcomingWidget.jsx

183 lines
7.1 KiB
JavaScript

import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { dashboardService } from '../../services/api';
import useFormatters from '../../hooks/useFormatters';
const UpcomingWidget = () => {
const { t } = useTranslation();
const { currency } = useFormatters();
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const loadData = useCallback(async () => {
setLoading(true);
try {
const result = await dashboardService.getUpcoming(7);
setData(result);
} catch (error) {
console.error('Error loading upcoming:', error);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
loadData();
}, [loadData]);
const getDaysLabel = (daysUntil) => {
if (daysUntil === 0) return t('dashboard.today');
if (daysUntil === 1) return t('dashboard.tomorrow');
return `${daysUntil} ${t('dashboard.daysAhead')}`;
};
const getTypeIcon = (item) => {
if (item.type === 'recurring') {
return 'bi-arrow-repeat';
}
if (item.is_transfer) {
return 'bi-arrow-left-right';
}
return item.transaction_type === 'credit' ? 'bi-arrow-down-circle' : 'bi-arrow-up-circle';
};
const getTypeColor = (item) => {
if (item.type === 'recurring') return '#f59e0b';
if (item.is_transfer) return '#8b5cf6';
return item.transaction_type === 'credit' ? '#10b981' : '#ef4444';
};
return (
<div className="card border-0 h-100" style={{ background: '#0f172a' }}>
<div className="card-header border-0 d-flex justify-content-between align-items-center py-3"
style={{ background: 'transparent', borderBottom: '1px solid rgba(59, 130, 246, 0.2)' }}>
<h6 className="text-white mb-0 d-flex align-items-center">
<i className="bi bi-clock-history me-2 text-primary"></i>
{t('dashboard.upcomingTransactions')}
</h6>
<button
className="btn btn-sm btn-outline-secondary border-0"
onClick={loadData}
disabled={loading}
>
<i className={`bi bi-arrow-clockwise ${loading ? 'spin' : ''}`}></i>
</button>
</div>
<div className="card-body py-2" style={{ maxHeight: '400px', overflowY: 'auto' }}>
{loading ? (
<div className="text-center py-4">
<div className="spinner-border spinner-border-sm text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
) : !data?.by_date?.length ? (
<div className="text-center text-slate-400 py-4">
<i className="bi bi-calendar-check fs-1 mb-2 d-block"></i>
<p className="small mb-0">{t('dashboard.noUpcomingTransactions')}</p>
</div>
) : (
<>
{data.by_date.map((day) => (
<div key={day.date} className="mb-3">
{/* Header do dia */}
<div className="d-flex align-items-center justify-content-between mb-2">
<div className="d-flex align-items-center gap-2">
<span
className={`badge ${day.is_today ? 'bg-primary' : 'bg-secondary'}`}
style={{ fontSize: '10px' }}
>
{getDaysLabel(day.days_until)}
</span>
<small className="text-slate-400">{day.date_formatted}</small>
</div>
<small className="text-slate-500">
{day.items.length} {day.items.length === 1 ? t('common.item') : t('common.items')}
</small>
</div>
{/* Items do dia */}
{day.items.map((item) => (
<div
key={`${item.type}-${item.id}`}
className="d-flex align-items-center gap-2 py-2 px-2 rounded mb-1"
style={{
background: 'rgba(30, 41, 59, 0.5)',
borderLeft: `3px solid ${getTypeColor(item)}`,
}}
>
{/* Ícone */}
<div
className="rounded-circle d-flex align-items-center justify-content-center flex-shrink-0"
style={{
width: '28px',
height: '28px',
background: `${getTypeColor(item)}20`,
color: getTypeColor(item),
}}
>
<i className={`bi ${getTypeIcon(item)}`} style={{ fontSize: '12px' }}></i>
</div>
{/* Info */}
<div className="flex-grow-1 min-width-0">
<div className="text-white small text-truncate" title={item.description}>
{item.description}
</div>
<div className="d-flex align-items-center gap-2">
<small className="text-slate-500" style={{ fontSize: '10px' }}>
{item.account?.name || '-'}
</small>
{item.type === 'recurring' && (
<span className="badge bg-warning text-dark" style={{ fontSize: '8px', padding: '1px 4px' }}>
#{item.occurrence_number}
</span>
)}
</div>
</div>
{/* Valor */}
<div className={`fw-bold small text-end ${
item.transaction_type === 'credit' ? 'text-success' : 'text-danger'
}`}>
{item.transaction_type === 'credit' ? '+' : '-'}
{currency(item.amount, item.account?.currency || 'EUR')}
</div>
</div>
))}
</div>
))}
</>
)}
</div>
{/* Footer com resumo */}
{data?.summary && !loading && data.by_date?.length > 0 && (
<div className="card-footer border-0 py-2" style={{ background: 'rgba(30, 41, 59, 0.5)' }}>
<div className="row g-2 text-center">
<div className="col-6">
<small className="text-slate-500 d-block" style={{ fontSize: '10px' }}>
{t('dashboard.income')}
</small>
<span className="text-success fw-bold small">
+{currency(data.summary.total_credit || 0, 'EUR')}
</span>
</div>
<div className="col-6">
<small className="text-slate-500 d-block" style={{ fontSize: '10px' }}>
{t('dashboard.expenses')}
</small>
<span className="text-danger fw-bold small">
-{currency(data.summary.total_debit || 0, 'EUR')}
</span>
</div>
</div>
</div>
)}
</div>
);
};
export default UpcomingWidget;