import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { budgetService, categoryService, costCenterService } from '../services/api'; import useFormatters from '../hooks/useFormatters'; import { getCurrencyByCode } from '../config/currencies'; import ConfirmModal from '../components/ConfirmModal'; import BudgetWizard from '../components/BudgetWizard'; const Budgets = () => { const { t } = useTranslation(); const { currency } = useFormatters(); const [loading, setLoading] = useState(true); const [budgets, setBudgets] = useState([]); const [categories, setCategories] = useState([]); const [availableCategories, setAvailableCategories] = useState([]); const [costCenters, setCostCenters] = useState([]); const [year, setYear] = useState(new Date().getFullYear()); const [month, setMonth] = useState(new Date().getMonth() + 1); const [showModal, setShowModal] = useState(false); const [editingBudget, setEditingBudget] = useState(null); const [deleteBudget, setDeleteBudget] = useState(null); const [yearSummary, setYearSummary] = useState(null); const [primaryCurrency, setPrimaryCurrency] = useState('EUR'); const [showWizard, setShowWizard] = useState(false); // Meses con i18n const getMonths = () => [ { value: 1, label: t('months.january') }, { value: 2, label: t('months.february') }, { value: 3, label: t('months.march') }, { value: 4, label: t('months.april') }, { value: 5, label: t('months.may') }, { value: 6, label: t('months.june') }, { value: 7, label: t('months.july') }, { value: 8, label: t('months.august') }, { value: 9, label: t('months.september') }, { value: 10, label: t('months.october') }, { value: 11, label: t('months.november') }, { value: 12, label: t('months.december') }, ]; const months = getMonths(); useEffect(() => { loadData(); }, [year, month]); const loadData = async () => { setLoading(true); try { const [budgetsData, categoriesData, availableData, summaryData, costCentersData] = await Promise.all([ budgetService.getAll({ year, month }), categoryService.getAll(), budgetService.getAvailableCategories({ year, month }), budgetService.getYearSummary({ year }), costCenterService.getAll(), ]); // Extraer datos del response si viene en formato { data, ... } const budgetsList = budgetsData?.data || budgetsData; setBudgets(Array.isArray(budgetsList) ? budgetsList : []); // Detectar moneda primaria de los presupuestos o usar EUR if (budgetsList?.length > 0 && budgetsList[0].currency) { setPrimaryCurrency(budgetsList[0].currency); } const cats = categoriesData?.data || categoriesData; // Filtrar categorías de gastos: expense o both setCategories(Array.isArray(cats) ? cats.filter(c => c.type === 'expense' || c.type === 'both') : []); // Categorías disponibles (no usadas aún) const available = Array.isArray(availableData) ? availableData : []; setAvailableCategories(available); setYearSummary(Array.isArray(summaryData) ? summaryData : []); // Cost Centers const centers = costCentersData?.data || costCentersData; setCostCenters(Array.isArray(centers) ? centers : []); } catch (error) { console.error('Error loading budgets:', error); } finally { setLoading(false); } }; const handleDelete = async () => { if (!deleteBudget) return; try { await budgetService.delete(deleteBudget.id); setDeleteBudget(null); loadData(); } catch (error) { console.error('Error deleting budget:', error); } }; const handleEdit = (budget) => { setEditingBudget(budget); setShowModal(true); }; const handleCopyToNextMonth = async () => { try { await budgetService.copyToNextMonth(year, month); // Move to next month view if (month === 12) { setYear(year + 1); setMonth(1); } else { setMonth(month + 1); } } catch (error) { console.error('Error copying budgets:', error); } }; const openNewBudget = () => { setEditingBudget(null); setShowModal(true); }; const getProgressColor = (percentage) => { if (percentage >= 100) return '#ef4444'; if (percentage >= 80) return '#f59e0b'; if (percentage >= 60) return '#eab308'; return '#10b981'; }; // Calculate totals agrupados por moneda const safeBudgets = Array.isArray(budgets) ? budgets : []; // Agrupar por moneda const totalsByCurrency = safeBudgets.reduce((acc, b) => { const curr = b.currency || primaryCurrency; if (!acc[curr]) { acc[curr] = { budgeted: 0, spent: 0 }; } acc[curr].budgeted += parseFloat(b.amount || 0); acc[curr].spent += parseFloat(b.spent_amount || 0); return acc; }, {}); // Totales principales (para compatibilidad) const totals = { budgeted: safeBudgets.reduce((sum, b) => sum + parseFloat(b.amount || 0), 0), spent: safeBudgets.reduce((sum, b) => sum + parseFloat(b.spent_amount || 0), 0), }; totals.remaining = totals.budgeted - totals.spent; totals.percentage = totals.budgeted > 0 ? (totals.spent / totals.budgeted) * 100 : 0; // Formatear totales por moneda const formatTotalsByCurrency = (type) => { const entries = Object.entries(totalsByCurrency); if (entries.length === 0) return currency(0, primaryCurrency); if (entries.length === 1) { const [curr, vals] = entries[0]; const value = type === 'budgeted' ? vals.budgeted : type === 'spent' ? vals.spent : vals.budgeted - vals.spent; return currency(value, curr); } return entries.map(([curr, vals]) => { const value = type === 'budgeted' ? vals.budgeted : type === 'spent' ? vals.spent : vals.budgeted - vals.spent; return currency(value, curr); }).join(' + '); }; if (loading) { return (
{t('budgets.subtitle')}
{t('budgets.noBudgetsDescription')}
| {t('budgets.month')} | {t('budgets.budgeted')} | {t('budgets.spent')} | {t('budgets.remaining')} | {t('budgets.usage')} |
|---|---|---|---|---|
| {monthName} {isCurrentMonth && ( {t('budgets.currentMonth')} )} | {currency(item.budgeted, primaryCurrency)} | {currency(item.spent, primaryCurrency)} | = 0 ? 'text-success' : 'text-danger'}`}> {currency(item.remaining, primaryCurrency)} | {item.percentage.toFixed(1)}% |