2025-07-10 18:04:58 +02:00
|
|
|
import LedCounter from '@/components/Display/LedCounter';
|
2025-07-12 18:09:11 +02:00
|
|
|
import MilestoneModal from '@/components/Display/MilestoneModal';
|
2025-07-10 18:04:58 +02:00
|
|
|
import PurchaseModal from '@/components/Display/PurchaseModal';
|
|
|
|
|
import StatsPanel from '@/components/Display/StatsPanel';
|
2025-07-10 15:24:15 +02:00
|
|
|
import { Head } from '@inertiajs/react';
|
2025-07-10 18:04:58 +02:00
|
|
|
import { useEffect, useState } from 'react';
|
2025-07-10 15:24:15 +02:00
|
|
|
|
2025-07-10 18:04:58 +02:00
|
|
|
interface PurchaseSummary {
|
|
|
|
|
total_shares: number;
|
|
|
|
|
total_investment: number;
|
|
|
|
|
average_cost_per_share: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface CurrentPrice {
|
|
|
|
|
current_price: number | null;
|
|
|
|
|
}
|
2025-07-10 15:24:15 +02:00
|
|
|
|
|
|
|
|
export default function Dashboard() {
|
2025-07-10 18:04:58 +02:00
|
|
|
const [purchaseData, setPurchaseData] = useState<PurchaseSummary>({
|
|
|
|
|
total_shares: 0,
|
|
|
|
|
total_investment: 0,
|
|
|
|
|
average_cost_per_share: 0,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const [priceData, setPriceData] = useState<CurrentPrice>({
|
|
|
|
|
current_price: null,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const [showStats, setShowStats] = useState(false);
|
|
|
|
|
const [showPurchaseModal, setShowPurchaseModal] = useState(false);
|
2025-07-12 18:09:11 +02:00
|
|
|
const [showMilestoneModal, setShowMilestoneModal] = useState(false);
|
2025-07-10 18:04:58 +02:00
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
|
|
|
|
// Fetch purchase summary and current price
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const [purchaseResponse, priceResponse] = await Promise.all([
|
|
|
|
|
fetch('/purchases/summary'),
|
|
|
|
|
fetch('/pricing/current'),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (purchaseResponse.ok) {
|
|
|
|
|
const purchases = await purchaseResponse.json();
|
|
|
|
|
setPurchaseData(purchases);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (priceResponse.ok) {
|
|
|
|
|
const price = await priceResponse.json();
|
|
|
|
|
setPriceData(price);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to fetch data:', error);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fetchData();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Refresh data after successful purchase
|
|
|
|
|
const handlePurchaseSuccess = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const purchaseResponse = await fetch('/purchases/summary');
|
|
|
|
|
if (purchaseResponse.ok) {
|
|
|
|
|
const purchases = await purchaseResponse.json();
|
|
|
|
|
setPurchaseData(purchases);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Failed to refresh purchase data:', error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate portfolio stats
|
|
|
|
|
const currentValue = priceData.current_price
|
|
|
|
|
? purchaseData.total_shares * priceData.current_price
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const profitLoss = currentValue
|
|
|
|
|
? currentValue - purchaseData.total_investment
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const profitLossPercentage = profitLoss && purchaseData.total_investment > 0
|
|
|
|
|
? (profitLoss / purchaseData.total_investment) * 100
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const statsData = {
|
|
|
|
|
totalShares: purchaseData.total_shares,
|
|
|
|
|
totalInvestment: purchaseData.total_investment,
|
|
|
|
|
averageCostPerShare: purchaseData.average_cost_per_share,
|
|
|
|
|
currentPrice: priceData.current_price || undefined,
|
|
|
|
|
currentValue,
|
|
|
|
|
profitLoss,
|
|
|
|
|
profitLossPercentage,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Head title="Dashboard" />
|
|
|
|
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
|
|
|
|
<div className="text-red-500 font-mono text-lg animate-pulse">
|
|
|
|
|
LOADING...
|
2025-07-10 15:24:15 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-07-10 18:04:58 +02:00
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Head title="VWCE Tracker" />
|
|
|
|
|
|
|
|
|
|
{/* Main LED Display */}
|
|
|
|
|
<div className="min-h-screen bg-black flex items-center justify-center relative">
|
|
|
|
|
<LedCounter
|
|
|
|
|
value={purchaseData.total_shares}
|
|
|
|
|
className="max-w-4xl w-full mx-4"
|
|
|
|
|
currentPrice={priceData.current_price || undefined}
|
|
|
|
|
onStatsToggle={() => setShowStats(!showStats)}
|
|
|
|
|
showStats={showStats}
|
|
|
|
|
onAddPurchase={() => setShowPurchaseModal(true)}
|
2025-07-12 18:09:11 +02:00
|
|
|
onAddMilestone={() => setShowMilestoneModal(true)}
|
2025-07-10 18:04:58 +02:00
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Stats Panel */}
|
|
|
|
|
<StatsPanel
|
|
|
|
|
stats={statsData}
|
|
|
|
|
isVisible={showStats}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Purchase Modal */}
|
|
|
|
|
<PurchaseModal
|
|
|
|
|
isOpen={showPurchaseModal}
|
|
|
|
|
onClose={() => setShowPurchaseModal(false)}
|
|
|
|
|
onSuccess={handlePurchaseSuccess}
|
|
|
|
|
/>
|
2025-07-12 18:09:11 +02:00
|
|
|
|
|
|
|
|
{/* Milestone Modal */}
|
|
|
|
|
<MilestoneModal
|
|
|
|
|
isOpen={showMilestoneModal}
|
|
|
|
|
onClose={() => setShowMilestoneModal(false)}
|
|
|
|
|
onSuccess={() => {
|
|
|
|
|
// Could refresh milestone data here if needed
|
|
|
|
|
console.log('Milestone added successfully');
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2025-07-10 15:24:15 +02:00
|
|
|
</div>
|
2025-07-10 18:04:58 +02:00
|
|
|
</>
|
2025-07-10 15:24:15 +02:00
|
|
|
);
|
|
|
|
|
}
|