193 lines
9.6 KiB
TypeScript
193 lines
9.6 KiB
TypeScript
|
|
import { cn } from '@/lib/utils';
|
||
|
|
import { useState } from 'react';
|
||
|
|
|
||
|
|
interface StatsData {
|
||
|
|
totalShares: number;
|
||
|
|
totalInvestment: number;
|
||
|
|
averageCostPerShare: number;
|
||
|
|
currentPrice?: number;
|
||
|
|
currentValue?: number;
|
||
|
|
profitLoss?: number;
|
||
|
|
profitLossPercentage?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface StatsPanelProps {
|
||
|
|
stats: StatsData;
|
||
|
|
isVisible: boolean;
|
||
|
|
className?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function StatsPanel({ stats, isVisible, className }: StatsPanelProps) {
|
||
|
|
const [withdrawalRate, setWithdrawalRate] = useState(0.03); // 3% default
|
||
|
|
|
||
|
|
const calculateWithdrawal = (rate: number) => {
|
||
|
|
if (!stats.currentValue) return 0;
|
||
|
|
return stats.currentValue * rate;
|
||
|
|
};
|
||
|
|
|
||
|
|
const formatCurrency = (amount: number) => {
|
||
|
|
return new Intl.NumberFormat('de-DE', {
|
||
|
|
style: 'currency',
|
||
|
|
currency: 'EUR',
|
||
|
|
minimumFractionDigits: 2,
|
||
|
|
}).format(amount);
|
||
|
|
};
|
||
|
|
|
||
|
|
const formatPercentage = (percentage: number) => {
|
||
|
|
return `${percentage >= 0 ? '+' : ''}${percentage.toFixed(2)}%`;
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className={cn(
|
||
|
|
"fixed bottom-12 left-0 right-0",
|
||
|
|
"bg-black/95 backdrop-blur-sm",
|
||
|
|
"border-t border-red-500/30",
|
||
|
|
"transition-all duration-300 ease-in-out",
|
||
|
|
"transform",
|
||
|
|
isVisible ? "translate-y-0 opacity-100" : "translate-y-full opacity-0",
|
||
|
|
className
|
||
|
|
)}>
|
||
|
|
<div className="container mx-auto px-4 py-6">
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||
|
|
|
||
|
|
{/* Portfolio Overview */}
|
||
|
|
<div className="space-y-3">
|
||
|
|
<h3 className="text-red-400 font-bold text-sm uppercase tracking-wide">
|
||
|
|
Portfolio
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-2 font-mono text-sm">
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">Shares:</span>
|
||
|
|
<span className="text-red-400">{stats.totalShares.toFixed(6)}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">Invested:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(stats.totalInvestment)}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">Avg Cost:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(stats.averageCostPerShare)}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Current Value */}
|
||
|
|
{stats.currentPrice && (
|
||
|
|
<div className="space-y-3">
|
||
|
|
<h3 className="text-red-400 font-bold text-sm uppercase tracking-wide">
|
||
|
|
Current Value
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-2 font-mono text-sm">
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">Price:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(stats.currentPrice)}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">Value:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(stats.currentValue || 0)}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{stats.profitLoss !== undefined && (
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">P&L:</span>
|
||
|
|
<span className={cn(
|
||
|
|
"font-bold",
|
||
|
|
stats.profitLoss >= 0 ? "text-green-400" : "text-red-500"
|
||
|
|
)}>
|
||
|
|
{formatCurrency(stats.profitLoss)}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{stats.profitLossPercentage !== undefined && (
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">Return:</span>
|
||
|
|
<span className={cn(
|
||
|
|
"font-bold",
|
||
|
|
stats.profitLossPercentage >= 0 ? "text-green-400" : "text-red-500"
|
||
|
|
)}>
|
||
|
|
{formatPercentage(stats.profitLossPercentage)}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Withdrawal Estimates */}
|
||
|
|
{stats.currentValue && (
|
||
|
|
<div className="space-y-3">
|
||
|
|
<h3 className="text-red-400 font-bold text-sm uppercase tracking-wide">
|
||
|
|
Annual Withdrawal
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-2 font-mono text-sm">
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">3%:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(calculateWithdrawal(0.03))}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">4%:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(calculateWithdrawal(0.04))}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">Custom:</span>
|
||
|
|
<div className="flex items-center space-x-1">
|
||
|
|
<input
|
||
|
|
type="number"
|
||
|
|
value={withdrawalRate * 100}
|
||
|
|
onChange={(e) => setWithdrawalRate(Number(e.target.value) / 100)}
|
||
|
|
className="w-12 bg-transparent text-red-400 text-right text-xs border-b border-red-500/30 focus:border-red-400 outline-none"
|
||
|
|
min="0"
|
||
|
|
max="10"
|
||
|
|
step="0.1"
|
||
|
|
/>
|
||
|
|
<span className="text-red-300/70 text-xs">%</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70"></span>
|
||
|
|
<span className="text-red-400">{formatCurrency(calculateWithdrawal(withdrawalRate))}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Monthly Breakdown */}
|
||
|
|
{stats.currentValue && (
|
||
|
|
<div className="space-y-3">
|
||
|
|
<h3 className="text-red-400 font-bold text-sm uppercase tracking-wide">
|
||
|
|
Monthly Income
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-2 font-mono text-sm">
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">3%:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(calculateWithdrawal(0.03) / 12)}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">4%:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(calculateWithdrawal(0.04) / 12)}</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<span className="text-red-300/70">Custom:</span>
|
||
|
|
<span className="text-red-400">{formatCurrency(calculateWithdrawal(withdrawalRate) / 12)}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|