diff --git a/client/src/pages/Budget.tsx b/client/src/pages/Budget.tsx index 9033bea..f935af3 100644 --- a/client/src/pages/Budget.tsx +++ b/client/src/pages/Budget.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react'; import { api } from '../lib/api'; -import { Plus, TrendingUp, TrendingDown, DollarSign, Edit2, Trash2, AlertCircle, Users } from 'lucide-react'; +import { Plus, TrendingUp, TrendingDown, DollarSign, Edit2, Trash2, AlertCircle, Users, ChevronLeft, ChevronRight } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle, Button, Dialog, Input, Select, Textarea, Badge, Tabs } from '../components/ui'; -import { BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; +import { BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { format } from 'date-fns'; import { fr } from 'date-fns/locale'; import { CHART_COLOR_PRESETS } from '../design/colorPresets'; @@ -33,22 +33,23 @@ interface BudgetLimit { year: number; } -interface MemberStats { - assigned_to: string | null; - member_name: string; - member_color: string; - total_expenses: number; - total_income: number; -} - interface BudgetStats { totalExpenses: number; totalIncome: number; balance: number; byCategory: Array<{ category: string; category_total: number }>; - byMember: MemberStats[]; + byMember: Array<{ assigned_to: string; member_name: string; member_color: string; category: string; amount: number }>; } +interface MonthlyStats { + month: number; + totalExpenses: number; + totalIncome: number; + balance: number; +} + +const MONTH_SHORT = ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc']; + const CATEGORIES = [ { value: 'Alimentation', label: 'Alimentation' }, { value: 'Santé', label: 'Santé' }, @@ -74,6 +75,7 @@ const Budget: React.FC = () => { const [entries, setEntries] = useState([]); const [limits, setLimits] = useState([]); const [stats, setStats] = useState(null); + const [monthlyStats, setMonthlyStats] = useState([]); const [familyMembers, setFamilyMembers] = useState([]); const [loading, setLoading] = useState(true); const [dialogOpen, setDialogOpen] = useState(false); @@ -82,8 +84,8 @@ const Budget: React.FC = () => { const [formError, setFormError] = useState(''); const [limitError, setLimitError] = useState(''); const [filterMember, setFilterMember] = useState(''); - const [currentMonth] = useState(new Date().getMonth() + 1); - const [currentYear] = useState(new Date().getFullYear()); + const [currentMonth, setCurrentMonth] = useState(new Date().getMonth() + 1); + const [currentYear, setCurrentYear] = useState(new Date().getFullYear()); const [formData, setFormData] = useState({ category: 'Alimentation', @@ -99,12 +101,16 @@ const Budget: React.FC = () => { monthly_limit: '', }); + useEffect(() => { + loadFamilyMembers(); + }, []); + useEffect(() => { loadEntries(); loadLimits(); loadStats(); - loadFamilyMembers(); - }, []); + loadMonthlyStats(currentYear); + }, [currentMonth, currentYear]); const loadEntries = async () => { try { @@ -160,11 +166,8 @@ const Budget: React.FC = () => { category_total: toNumber(item.category_total), })), byMember: (response.data.byMember || []).map((item) => ({ - assigned_to: item.assigned_to, - member_name: item.member_name || 'Non assigné', - member_color: item.member_color || '#94a3b8', - total_expenses: toNumber(item.total_expenses), - total_income: toNumber(item.total_income), + ...item, + amount: toNumber(item.amount), })), }); } @@ -173,6 +176,24 @@ const Budget: React.FC = () => { } }; + const loadMonthlyStats = async (year = currentYear) => { + try { + const response = await api.get<{ success: boolean; data: MonthlyStats[] }>( + `/api/budget/statistics/monthly?year=${year}` + ); + if (response.success) { + setMonthlyStats(response.data.map((item) => ({ + ...item, + totalExpenses: toNumber(item.totalExpenses), + totalIncome: toNumber(item.totalIncome), + balance: toNumber(item.balance), + }))); + } + } catch (error) { + console.error('Failed to load monthly stats:', error); + } + }; + const loadFamilyMembers = async () => { try { const response = await api.get<{ success: boolean; data: FamilyMember[] }>('/api/family'); @@ -184,6 +205,15 @@ const Budget: React.FC = () => { } }; + const navigateMonth = (direction: -1 | 1) => { + let newMonth = currentMonth + direction; + let newYear = currentYear; + if (newMonth < 1) { newMonth = 12; newYear -= 1; } + if (newMonth > 12) { newMonth = 1; newYear += 1; } + setCurrentMonth(newMonth); + setCurrentYear(newYear); + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setFormError(''); @@ -210,7 +240,7 @@ const Budget: React.FC = () => { loadStats(); } catch (error) { console.error('Failed to save entry:', error); - setFormError(error instanceof Error ? error.message : 'Impossible d’enregistrer cette entrée.'); + setFormError(error instanceof Error ? error.message : "Impossible d'enregistrer cette entrée."); } }; @@ -322,11 +352,8 @@ const Budget: React.FC = () => { label: 'Entrées', content: (
-
-

- {format(new Date(currentYear, currentMonth - 1), 'MMMM yyyy', { locale: fr })} -

-
+
+
{familyMembers.length > 0 && (