2025-07-12 19:59:22 +02:00
|
|
|
import { cn } from '@/lib/utils';
|
2025-07-13 01:13:36 +02:00
|
|
|
import { Plus, ChevronRight } from 'lucide-react';
|
2025-07-13 01:07:16 +02:00
|
|
|
import { useState } from 'react';
|
2025-07-13 02:10:52 +02:00
|
|
|
import ComponentTitle from '@/components/ui/ComponentTitle';
|
2025-07-12 19:59:22 +02:00
|
|
|
|
2025-07-13 00:18:45 +02:00
|
|
|
interface Milestone {
|
|
|
|
|
target: number;
|
|
|
|
|
description: string;
|
|
|
|
|
created_at: string;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 19:59:22 +02:00
|
|
|
interface StatsBoxProps {
|
|
|
|
|
stats: {
|
|
|
|
|
totalShares: number;
|
|
|
|
|
totalInvestment: number;
|
|
|
|
|
averageCostPerShare: number;
|
|
|
|
|
currentPrice?: number;
|
|
|
|
|
currentValue?: number;
|
|
|
|
|
profitLoss?: number;
|
|
|
|
|
profitLossPercentage?: number;
|
|
|
|
|
};
|
2025-07-13 00:18:45 +02:00
|
|
|
milestones?: Milestone[];
|
2025-07-13 01:13:36 +02:00
|
|
|
selectedMilestoneIndex?: number;
|
|
|
|
|
onMilestoneSelect?: (index: number) => void;
|
2025-07-12 19:59:22 +02:00
|
|
|
className?: string;
|
|
|
|
|
onAddPurchase?: () => void;
|
|
|
|
|
onAddMilestone?: () => void;
|
2025-07-13 01:07:16 +02:00
|
|
|
onUpdatePrice?: () => void;
|
2025-07-12 19:59:22 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-13 01:13:36 +02:00
|
|
|
export default function StatsBox({
|
|
|
|
|
stats,
|
2025-07-13 00:18:45 +02:00
|
|
|
milestones = [],
|
2025-07-13 01:13:36 +02:00
|
|
|
selectedMilestoneIndex = 0,
|
|
|
|
|
onMilestoneSelect,
|
2025-07-12 19:59:22 +02:00
|
|
|
className,
|
|
|
|
|
onAddPurchase,
|
2025-07-13 01:07:16 +02:00
|
|
|
onAddMilestone,
|
|
|
|
|
onUpdatePrice
|
2025-07-12 19:59:22 +02:00
|
|
|
}: StatsBoxProps) {
|
2025-07-13 01:07:16 +02:00
|
|
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
2025-07-13 01:13:36 +02:00
|
|
|
|
|
|
|
|
const handleCycleMilestone = () => {
|
|
|
|
|
if (milestones.length === 0 || !onMilestoneSelect) return;
|
|
|
|
|
const nextIndex = (selectedMilestoneIndex + 1) % milestones.length;
|
|
|
|
|
onMilestoneSelect(nextIndex);
|
|
|
|
|
};
|
2025-07-12 19:59:22 +02:00
|
|
|
const formatCurrency = (amount: number) => {
|
|
|
|
|
return new Intl.NumberFormat('de-DE', {
|
|
|
|
|
style: 'currency',
|
|
|
|
|
currency: 'EUR',
|
|
|
|
|
minimumFractionDigits: 2,
|
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
|
}).format(amount);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatCurrencyDetailed = (amount: number) => {
|
|
|
|
|
return new Intl.NumberFormat('de-DE', {
|
|
|
|
|
style: 'currency',
|
|
|
|
|
currency: 'EUR',
|
|
|
|
|
minimumFractionDigits: 4,
|
|
|
|
|
}).format(amount);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2025-07-13 01:13:36 +02:00
|
|
|
<div
|
2025-07-12 19:59:22 +02:00
|
|
|
className={cn(
|
2025-07-13 01:07:16 +02:00
|
|
|
"bg-black p-8",
|
|
|
|
|
"transition-all duration-300",
|
2025-07-12 19:59:22 +02:00
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
>
|
2025-07-13 04:06:37 +02:00
|
|
|
<div className="w-full border-4 border-red-500 p-2 bg-black space-y-4 glow-red">
|
2025-07-13 01:07:16 +02:00
|
|
|
{/* STATS Title and Current Price */}
|
|
|
|
|
<div className="flex justify-between items-center mb-6 relative">
|
2025-07-13 02:10:52 +02:00
|
|
|
<ComponentTitle>Stats</ComponentTitle>
|
|
|
|
|
|
2025-07-13 01:13:36 +02:00
|
|
|
<div className="flex items-center space-x-2 relative">
|
2025-07-13 01:07:16 +02:00
|
|
|
{stats.currentPrice && (
|
|
|
|
|
<div className="text-red-500 text-sm font-mono tracking-wider">
|
|
|
|
|
VWCE: {formatCurrencyDetailed(stats.currentPrice)}
|
2025-07-12 19:59:22 +02:00
|
|
|
</div>
|
2025-07-13 01:07:16 +02:00
|
|
|
)}
|
2025-07-13 01:13:36 +02:00
|
|
|
|
2025-07-13 01:07:16 +02:00
|
|
|
{/* Action Dropdown */}
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
2025-07-13 01:27:02 +02:00
|
|
|
className="flex items-center justify-center px-2 py-1 rounded border border-red-500/50 text-red-500 hover:bg-red-800/40 hover:text-red-300 transition-colors text-sm"
|
2025-07-13 01:07:16 +02:00
|
|
|
aria-label="Add actions"
|
|
|
|
|
>
|
|
|
|
|
<Plus className="w-4 h-4" />
|
|
|
|
|
</button>
|
2025-07-13 01:13:36 +02:00
|
|
|
|
2025-07-13 01:07:16 +02:00
|
|
|
{/* Dropdown Menu */}
|
|
|
|
|
{isDropdownOpen && (
|
|
|
|
|
<div className="absolute top-full right-0 mt-2 bg-black border-2 border-red-500/50 rounded shadow-lg min-w-40 z-10">
|
|
|
|
|
{onAddPurchase && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => {
|
|
|
|
|
onAddPurchase();
|
|
|
|
|
setIsDropdownOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
className="w-full text-left px-4 py-2 text-red-400 hover:bg-red-600/20 hover:text-red-300 transition-colors text-sm font-mono border-b border-red-500/20 last:border-b-0"
|
|
|
|
|
>
|
|
|
|
|
ADD PURCHASE
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{onAddMilestone && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => {
|
|
|
|
|
onAddMilestone();
|
|
|
|
|
setIsDropdownOpen(false);
|
|
|
|
|
}}
|
2025-07-13 01:27:02 +02:00
|
|
|
className="w-full text-left px-4 py-2 text-red-400 hover:bg-red-600/20 hover:text-red-300 transition-colors transition-colors text-sm font-mono border-b border-red-500/20 last:border-b-0"
|
2025-07-13 01:07:16 +02:00
|
|
|
>
|
|
|
|
|
ADD MILESTONE
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{onUpdatePrice && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => {
|
|
|
|
|
onUpdatePrice();
|
|
|
|
|
setIsDropdownOpen(false);
|
|
|
|
|
}}
|
2025-07-13 01:27:02 +02:00
|
|
|
className="w-full text-left px-4 py-2 text-red-400 hover:bg-red-600/20 hover:text-red-300 transition-colors transition-colors text-sm font-mono border-b border-red-500/20 last:border-b-0"
|
2025-07-13 01:07:16 +02:00
|
|
|
>
|
|
|
|
|
UPDATE PRICE
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
2025-07-13 00:18:45 +02:00
|
|
|
</div>
|
2025-07-13 01:07:16 +02:00
|
|
|
)}
|
|
|
|
|
</div>
|
2025-07-13 01:13:36 +02:00
|
|
|
|
|
|
|
|
{/* Milestone Cycle Button */}
|
|
|
|
|
{milestones.length > 1 && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleCycleMilestone}
|
2025-07-13 01:27:02 +02:00
|
|
|
className="flex items-center justify-center px-2 py-1 rounded border border-red-500/50 text-red-500 hover:bg-red-800/40 hover:text-red-300 transition-colors text-sm"
|
2025-07-13 01:13:36 +02:00
|
|
|
aria-label="Cycle milestone"
|
|
|
|
|
>
|
|
|
|
|
<ChevronRight className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
2025-07-13 00:18:45 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-07-13 01:07:16 +02:00
|
|
|
{/* Milestone Table */}
|
2025-07-13 01:27:02 +02:00
|
|
|
<div className="pt-4">
|
|
|
|
|
<div className="text-red-500 underline font-bold mb-3 font-mono">MILESTONES</div>
|
2025-07-13 01:07:16 +02:00
|
|
|
<div className="overflow-x-auto">
|
|
|
|
|
<table className="w-full text-sm font-mono">
|
|
|
|
|
<thead>
|
2025-07-13 01:27:02 +02:00
|
|
|
<tr>
|
|
|
|
|
<th className="text-left text-red-500 text-xs py-2">DESCRIPTION</th>
|
|
|
|
|
<th className="text-right text-red-500 text-xs py-2">SHARES</th>
|
|
|
|
|
<th className="text-right text-red-500 text-xs py-2 pr-4">SWR 3%</th>
|
|
|
|
|
<th className="text-right text-red-500 text-xs py-2">SWR 4%</th>
|
2025-07-13 01:07:16 +02:00
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
2025-07-13 01:27:02 +02:00
|
|
|
{/* Current position row */}
|
|
|
|
|
<tr className="text-red-500 font-bold">
|
|
|
|
|
<td className="py-1 pr-4">CURRENT</td>
|
|
|
|
|
<td className="text-right py-1 pr-4">
|
|
|
|
|
{Math.floor(stats.totalShares).toLocaleString()}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="text-right py-1 pr-4">
|
|
|
|
|
{stats.currentPrice ? formatCurrency(stats.totalShares * stats.currentPrice * 0.03) : 'N/A'}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="text-right py-1">
|
|
|
|
|
{stats.currentPrice ? formatCurrency(stats.totalShares * stats.currentPrice * 0.04) : 'N/A'}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
|
|
{/* Render milestones after current */}
|
|
|
|
|
{milestones.map((milestone, index) => {
|
|
|
|
|
const swr3 = stats.currentPrice ? milestone.target * stats.currentPrice * 0.03 : 0;
|
|
|
|
|
const swr4 = stats.currentPrice ? milestone.target * stats.currentPrice * 0.04 : 0;
|
2025-07-13 01:13:36 +02:00
|
|
|
|
2025-07-13 01:27:02 +02:00
|
|
|
const isSelectedMilestone = index === selectedMilestoneIndex;
|
2025-07-13 01:13:36 +02:00
|
|
|
|
2025-07-13 01:07:16 +02:00
|
|
|
return (
|
2025-07-13 01:13:36 +02:00
|
|
|
<tr
|
|
|
|
|
key={index}
|
2025-07-13 01:07:16 +02:00
|
|
|
className={cn(
|
2025-07-13 01:27:02 +02:00
|
|
|
isSelectedMilestone
|
2025-07-13 02:10:52 +02:00
|
|
|
? "bg-red-500 text-black"
|
|
|
|
|
: "text-red-500 font-bold"
|
2025-07-13 01:07:16 +02:00
|
|
|
)}
|
|
|
|
|
>
|
2025-07-13 01:27:02 +02:00
|
|
|
<td className="py-1 pr-4">
|
|
|
|
|
{milestone.description}
|
2025-07-13 01:07:16 +02:00
|
|
|
</td>
|
2025-07-13 01:27:02 +02:00
|
|
|
<td className="text-right py-1 pr-4">
|
|
|
|
|
{Math.floor(milestone.target).toLocaleString()}
|
2025-07-13 01:07:16 +02:00
|
|
|
</td>
|
2025-07-13 01:27:02 +02:00
|
|
|
<td className="text-right py-1 pr-4">
|
2025-07-13 01:07:16 +02:00
|
|
|
{stats.currentPrice ? formatCurrency(swr3) : 'N/A'}
|
|
|
|
|
</td>
|
2025-07-13 01:27:02 +02:00
|
|
|
<td className="text-right py-1">
|
2025-07-13 01:07:16 +02:00
|
|
|
{stats.currentPrice ? formatCurrency(swr4) : 'N/A'}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2025-07-12 19:59:22 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-07-13 01:07:16 +02:00
|
|
|
</div>
|
2025-07-12 19:59:22 +02:00
|
|
|
</div>
|
|
|
|
|
);
|
2025-07-13 01:13:36 +02:00
|
|
|
}
|