expenese dinh cao cua nhan loai phai mot chut anh hung can trong

import React, { useState, useEffect, useRef } from 'react'; import axios from 'axios'; import numberToWords from '../utils/numberToWords'; // Category config to match Home.js const categories = [ { value: 'essentials', label: 'Tiêu dùng thiết yếu', color: '#22c55e', icon: ( ), }, { value: 'savings', label: 'Tiết kiệm bắt buộc', color: '#3b82f6', icon: ( ), }, { value: 'selfInvestment', label: 'Đầu tư bản thân', color: '#eab308', icon: ( ), }, { value: 'charity', label: 'Từ thiện', color: '#a21caf', icon: ( ), }, { value: 'emergency', label: 'Dự phòng linh hoạt', color: '#f97316', icon: ( ), }, ]; function Expenses() { // State const [amount, setAmount] = useState(''); const [category, setCategory] = useState(''); const [date, setDate] = useState(''); const [purpose, setPurpose] = useState(''); const [location, setLocation] = useState(''); const [initialBudget, setInitialBudget] = useState(null); const [newBudget, setNewBudget] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [allocations, setAllocations] = useState({ essentials: 0, savings: 0, selfInvestment: 0, charity: 0, emergency: 0, }); const [expenses, setExpenses] = useState([]); const [error, setError] = useState(''); const [isDarkMode, setIsDarkMode] = useState( () => window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ); const [showSnackbar, setShowSnackbar] = useState(false); const [snackbarMsg, setSnackbarMsg] = useState(''); const [showAllExpenses, setShowAllExpenses] = useState(false); // For expanding/collapsing expense history const token = localStorage.getItem('token'); const snackbarTimeout = useRef(null); // Theme sync (like Home.js) useEffect(() => { if (isDarkMode) { document.documentElement.classList.add('dark'); document.body.style.background = '#111827'; } else { document.documentElement.classList.remove('dark'); document.body.style.background = '#F3F4F6'; } }, [isDarkMode]); // Fetch data useEffect(() => { const fetchData = async () => { try { const [budgetRes, expensesRes, allocationsRes] = await Promise.all([ axios.get('https://backend-rockefeller-finance.onrender.com/api/initial-budget', { headers: { Authorization: `Bearer ${token}` }, }), axios.get('https://backend-rockefeller-finance.onrender.com/api/expenses', { headers: { Authorization: `Bearer ${token}` }, }), axios.get('https://backend-rockefeller-finance.onrender.com/api/allocations', { headers: { Authorization: `Bearer ${token}` }, }), ]); setInitialBudget(budgetRes.data.initialBudget); setExpenses(expensesRes.data); setAllocations(allocationsRes.data); } catch (err) { showError(err.response?.data?.error || 'Lỗi lấy dữ liệu'); } }; if (token) fetchData(); // eslint-disable-next-line }, [token]); // Snackbar const showError = (msg) => { setError(msg); setSnackbarMsg(msg); setShowSnackbar(true); if (snackbarTimeout.current) clearTimeout(snackbarTimeout.current); snackbarTimeout.current = setTimeout(() => setShowSnackbar(false), 3500); }; // Format currency const formatVND = (value) => { const num = parseFloat(value) || 0; return new Intl.NumberFormat('vi-VN', { style: 'currency', currency: 'VND' }).format(num); }; // Budget submit const handleBudgetSubmit = async (e) => { e.preventDefault(); const parsedBudget = parseFloat(newBudget); if (isNaN(parsedBudget) || parsedBudget <= 0) { showError('Vui lòng nhập số tiền hợp lệ!'); return; } setIsSubmitting(true); try { const response = await axios.post( 'https://backend-rockefeller-finance.onrender.com/api/initial-budget', { initialBudget: parsedBudget }, { headers: { Authorization: `Bearer ${token}` } } ); setInitialBudget(response.data.initialBudget); setAllocations(response.data.allocations); setNewBudget(''); setError(''); setSnackbarMsg('Ngân sách đã được cập nhật!'); setShowSnackbar(true); } catch (err) { showError(err.response?.data?.error || 'Lỗi lưu ngân sách'); } finally { setIsSubmitting(false); } }; // Expense submit const handleExpenseSubmit = async (e) => { e.preventDefault(); const parsedAmount = parseFloat(amount); if (isNaN(parsedAmount) || parsedAmount <= 0) { showError('Vui lòng nhập số tiền hợp lệ!'); return; } if (!purpose || !location) { showError('Vui lòng nhập mục đích và vị trí!'); return; } setIsSubmitting(true); try { const response = await axios.post( 'https://backend-rockefeller-finance.onrender.com/api/expenses', { amount: parsedAmount, category, purpose, location, date: date || new Date().toLocaleDateString('vi-VN'), }, { headers: { Authorization: `Bearer ${token}` } } ); setExpenses(response.data); // Update allocations and budget const [budgetRes, allocRes] = await Promise.all([ axios.get('https://backend-rockefeller-finance.onrender.com/api/initial-budget', { headers: { Authorization: `Bearer ${token}` }, }), axios.get('https://backend-rockefeller-finance.onrender.com/api/allocations', { headers: { Authorization: `Bearer ${token}` }, }), ]); setInitialBudget(budgetRes.data.initialBudget); setAllocations(allocRes.data); setAmount(''); setCategory(''); setPurpose(''); setLocation(''); setDate(''); setError(''); setSnackbarMsg('Chi tiêu đã được thêm!'); setShowSnackbar(true); } catch (err) { showError(err.response?.data?.error || 'Lỗi thêm chi tiêu'); } finally { setIsSubmitting(false); } }; // Delete expense const handleDeleteExpense = async (index) => { try { const response = await axios.delete( `https://backend-rockefeller-finance.onrender.com/api/expenses/${index}`, { headers: { Authorization: `Bearer ${token}` } } ); setExpenses(response.data); // Update budget and allocations const [budgetRes, allocRes] = await Promise.all([ axios.get('https://backend-rockefeller-finance.onrender.com/api/initial-budget', { headers: { Authorization: `Bearer ${token}` }, }), axios.get('https://backend-rockefeller-finance.onrender.com/api/allocations', { headers: { Authorization: `Bearer ${token}` }, }), ]); setInitialBudget(budgetRes.data.initialBudget); setAllocations(allocRes.data); setSnackbarMsg('Đã xóa chi tiêu!'); setShowSnackbar(true); } catch (err) { showError(err.response?.data?.error || 'Lỗi xóa chi tiêu'); } }; // Theme toggle const toggleDarkMode = () => setIsDarkMode((prev) => !prev); // Total allocations const totalAmount = parseFloat(allocations.essentials) + parseFloat(allocations.savings) + parseFloat(allocations.selfInvestment) + parseFloat(allocations.charity) + parseFloat(allocations.emergency); // Expense history logic for step1 & step2 // Always show newest first const sortedExpenses = [...expenses].reverse(); // How many to show when collapsed const COLLAPSED_COUNT = 1; // Responsive, modern, glassmorphism, and micro-interactions return (
{/* Snackbar */}
{snackbarMsg}
{/* Topbar - match Home.js */}

Quản lý chi tiêu

{/* Error */} {error && (
{error}
)} {/* Budget input */} {initialBudget === null || initialBudget === 0 ? (

Nhập ngân sách ban đầu

setNewBudget(e.target.value)} className={`input-modern ${isDarkMode ? 'dark' : ''}`} placeholder="Nhập ngân sách ban đầu" required min="1" inputMode="numeric" />
) : ( <> {/* Add more budget */}

Nạp thêm ngân sách

setNewBudget(e.target.value)} className={`input-modern ${isDarkMode ? 'dark' : ''}`} placeholder="Nhập số tiền nạp thêm" min="1" inputMode="numeric" />
{/* Budget balance */}

Số dư ngân sách

{formatVND(initialBudget)}

💰
{/* Add expense */}

Thêm chi tiêu

setAmount(e.target.value)} className={`input-modern ${isDarkMode ? 'dark' : ''}`} placeholder="Nhập số tiền" required min="1" inputMode="numeric" />
{/* Mobile: show icon and color for selected category */}
{(() => { const cat = categories.find(c => c.label === category); if (!cat) return null; return ( {cat.icon} {cat.label} ); })()}
setPurpose(e.target.value)} className={`input-modern ${isDarkMode ? 'dark' : ''}`} placeholder="Nhập mục đích (ví dụ: Mua thực phẩm)" required />
setLocation(e.target.value)} className={`input-modern ${isDarkMode ? 'dark' : ''}`} placeholder="Nhập vị trí (ví dụ: Siêu thị VinMart)" required />
setDate(e.target.value)} className={`input-modern ${isDarkMode ? 'dark' : ''}`} />
{/* Expense history */}

Lịch sử chi tiêu {sortedExpenses.length > COLLAPSED_COUNT && ( )}

{sortedExpenses.length === 0 ? ( ) : ( (showAllExpenses ? sortedExpenses : sortedExpenses.slice(0, COLLAPSED_COUNT)).map((expense, index) => ( )) )}
Ngày Danh mục Số tiền (VND) Mục đích Vị trí Hành động
Không có chi tiêu nào.
{expense.date} {expense.category} {formatVND(expense.amount)} {expense.purpose} {expense.location}
{/* Budget allocations */}

Phân bổ ngân sách

{categories.map((cat) => (
{cat.icon} {cat.label}
{formatVND(allocations[cat.value])}
))}
{/* Total allocations */} {totalAmount > 0 && (

Tổng số tiền phân bổ

{formatVND(totalAmount)}

{numberToWords(totalAmount)}

)} )}
{/* Modern glassmorphism and utility classes */}
); } export default Expenses;
Huyền

Một Blog Anime chia sẻ những bộ anime hay download về để xem chất lượng cao nhất. neyuhv.blogspot.com does not host any files, it merely links to 3rd party services. Legal issues should be taken up with the file hosts and providers. neyuhv.blogspot.com is not responsible for any media files shown by the video providers.

Mới hơn Cũ hơn