import React, { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import { Line } from 'react-chartjs-2';
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js';
import toast, { Toaster } from 'react-hot-toast';
import { Link } from 'react-router-dom';
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
function Investments() {
const [investments, setInvestments] = useState([]);
const [amount, setAmount] = useState('');
const [price, setPrice] = useState('');
const [type, setType] = useState('');
const [bitcoinHistory, setBitcoinHistory] = useState([]);
const [bitcoinPrice, setBitcoinPrice] = useState(null);
const [isDarkMode, setIsDarkMode] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState('');
const [searchType, setSearchType] = useState('');
const [minAmount, setMinAmount] = useState('');
const [maxAmount, setMaxAmount] = useState('');
const [selectedDate, setSelectedDate] = useState('');
const token = localStorage.getItem('token');
const navigate = useNavigate();
const colors = {
primary: '#2171B5',
secondary: '#6BAED6',
tertiary: '#BDD7E7',
background: '#EFF3FF',
};
useEffect(() => {
const fetchData = async () => {
if (!token) {
navigate('/login');
return;
}
setIsLoading(true);
try {
const [investmentsRes, bitcoinHistoryRes, bitcoinPriceRes] = await Promise.all([
axios.get('https://backend-rockefeller-finance.onrender.com/api/investments', {
headers: { Authorization: `Bearer ${token}` },
}),
axios.get('https://backend-rockefeller-finance.onrender.com/api/bitcoin-history'),
axios.get('https://backend-rockefeller-finance.onrender.com/api/bitcoin-price'),
]);
setInvestments(investmentsRes.data);
setBitcoinHistory(bitcoinHistoryRes.data);
setBitcoinPrice(bitcoinPriceRes.data.price);
setError('');
} catch (err) {
console.error('API error:', err, err.response);
if (err.response?.status === 401) {
localStorage.removeItem('token');
navigate('/login');
setError('Phiên đăng nhập hết hạn. Vui lòng đăng nhập lại.');
} else if (err.response?.data?.errors) {
setError(err.response.data.errors.map(e => e.msg).join(', '));
} else {
setError(err.response?.data?.error || err.response?.data?.warning || 'Lỗi lấy dữ liệu');
}
} finally {
setIsLoading(false);
}
};
fetchData();
}, [token, navigate]);
const validateForm = () => {
const errors = {};
if (!amount || parseFloat(amount) <= 0) {
errors.amount = 'Số tiền phải lớn hơn 0';
}
if (!price || parseFloat(price) <= 0) {
errors.price = 'Giá phải lớn hơn 0';
}
if (!type) {
errors.type = 'Vui lòng chọn loại đầu tư';
}
setError(Object.values(errors).join(', ') || '');
return Object.keys(errors).length === 0;
};
const handleAddInvestment = async (e) => {
e.preventDefault();
if (!validateForm()) {
toast.error(error);
return;
}
setIsLoading(true);
try {
const response = await axios.post(
'https://backend-rockefeller-finance.onrender.com/api/investments',
{ amount: parseFloat(amount), price: parseFloat(price), type },
{ headers: { Authorization: `Bearer ${token}` } }
);
setInvestments(response.data);
setAmount('');
setPrice('');
setType('');
toast.success('Thêm đầu tư thành công!');
setError('');
} catch (err) {
console.error('API error:', err, err.response);
if (err.response?.data?.errors) {
toast.error(err.response.data.errors.map(e => e.msg).join(', '));
} else {
toast.error(err.response?.data?.error || err.response?.data?.warning || 'Lỗi thêm đầu tư');
}
} finally {
setIsLoading(false);
}
};
const handleDeleteInvestment = async (index) => {
if (!window.confirm('Bạn có chắc muốn xóa đầu tư này?')) {
return;
}
setIsLoading(true);
try {
const response = await axios.delete(
`https://backend-rockefeller-finance.onrender.com/api/investments/${index}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
setInvestments(response.data);
toast.success('Xóa đầu tư thành công!');
setError('');
} catch (err) {
console.error('API error:', err, err.response);
if (err.response?.data?.errors) {
toast.error(err.response.data.errors.map(e => e.msg).join(', '));
} else {
toast.error(err.response?.data?.error || 'Lỗi xóa đầu tư');
}
} finally {
setIsLoading(false);
}
};
const handleLogout = () => {
localStorage.removeItem('token');
navigate('/login');
toast.success('Đăng xuất thành công!');
};
const toggleDarkMode = () => {
setIsDarkMode(!isDarkMode);
};
const formatVND = (value) => {
const num = parseFloat(value) || 0;
return new Intl.NumberFormat('vi-VN', { style: 'currency', currency: 'VND' }).format(num);
};
const formatUSD = (value) => {
const num = parseFloat(value) || 0;
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(num);
};
const filteredInvestments = useMemo(() => {
return investments.filter((inv) => {
if (!inv) return false;
const amountMatch =
(!minAmount || parseFloat(inv.amount) >= parseFloat(minAmount)) &&
(!maxAmount || parseFloat(inv.amount) <= parseFloat(maxAmount));
const typeMatch = !searchType || inv.type.toLowerCase().includes(searchType.toLowerCase());
const dateMatch = !selectedDate || inv.date === selectedDate;
return amountMatch && typeMatch && dateMatch;
});
}, [investments, minAmount, maxAmount, searchType, selectedDate]);
const totalInvestment = filteredInvestments.reduce((sum, inv) => sum + parseFloat(inv.amount || 0), 0);
const currentValue = filteredInvestments.reduce((sum, inv) => {
if (inv.type.toLowerCase().includes('bitcoin') && bitcoinPrice) {
return sum + (parseFloat(inv.amount) / inv.price) * bitcoinPrice;
}
return sum + parseFloat(inv.amount || 0);
}, 0);
const chartData = {
labels: bitcoinHistory.map((data) => data.date),
datasets: [
{
label: 'Giá Bitcoin (USD)',
data: bitcoinHistory.map((data) => data.price),
fill: false,
borderColor: colors.primary,
backgroundColor: colors.secondary,
tension: 0.2,
},
],
};
const chartOptions = {
responsive: true,
plugins: {
legend: {
position: 'top',
labels: {
color: isDarkMode ? '#FFFFFF' : '#1F2937',
font: { size: 14 },
},
},
tooltip: {
callbacks: {
label: (context) => `Giá: ${formatUSD(context.raw)}`,
},
},
title: {
display: true,
text: 'Lịch sử giá Bitcoin (7 ngày)',
color: isDarkMode ? '#FFFFFF' : '#1F2937',
font: { size: 16 },
},
},
scales: {
x: {
ticks: { color: isDarkMode ? '#FFFFFF' : '#1F2937' },
grid: { color: isDarkMode ? '#4B5563' : '#E5E7EB' },
},
y: {
ticks: {
color: isDarkMode ? '#FFFFFF' : '#1F2937',
callback: (value) => formatUSD(value),
},
grid: { color: isDarkMode ? '#4B5563' : '#E5E7EB' },
},
},
};
const investmentTypes = ['Bitcoin ETF', 'Vàng', 'Chứng khoán'];
const availableDates = [...new Set(investments.map((inv) => inv.date))].sort();
return (
);
}
export default Investments;
{isLoading && (
)}
{error && (
{filteredInvestments.length > 0 ? (
>
)}
{error}
)}
{!isLoading && (
<>
Quản lý đầu tư
*32 Lá Thư*: "Đầu tư khôn ngoan là đa dạng hóa và không vượt quá 10% danh mục cho một tài sản."
Thống kê đầu tư
Tổng đầu tư
{formatVND(totalInvestment)}
Giá trị hiện tại
{formatVND(currentValue)}
Lịch sử đầu tư
setSearchType(e.target.value)}
className={`w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-[#2171B5] transition ${
isDarkMode ? 'bg-gray-700 text-white border-gray-600' : 'bg-white text-gray-800 border-gray-300'
}`}
placeholder="Tìm loại (ví dụ: Bitcoin ETF)"
/>
setMinAmount(e.target.value)}
className={`w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-[#2171B5] transition ${
isDarkMode ? 'bg-gray-700 text-white border-gray-600' : 'bg-white text-gray-800 border-gray-300'
}`}
placeholder="Số tiền tối thiểu"
/>
setMaxAmount(e.target.value)}
className={`w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-[#2171B5] transition ${
isDarkMode ? 'bg-gray-700 text-white border-gray-600' : 'bg-white text-gray-800 border-gray-300'
}`}
placeholder="Số tiền tối đa"
/>
{filteredInvestments.map((investment, index) => (
))}
) : (
Ngày: {investment.date}
Loại: {investment.type}
Số tiền: {formatVND(investment.amount)}
Giá mua: {formatUSD(investment.price)}
{investment.type.toLowerCase().includes('bitcoin') && bitcoinPrice && (Giá trị hiện tại: {formatVND((investment.amount / investment.price) * bitcoinPrice)}
)}
Không tìm thấy đầu tư phù hợp với bộ lọc.
)}