incr/resources/js/components/Display/StatsBox.tsx
2025-07-13 01:13:36 +02:00

222 lines
11 KiB
TypeScript

import { cn } from '@/lib/utils';
import { Plus, ChevronRight } from 'lucide-react';
import { useState } from 'react';
interface Milestone {
target: number;
description: string;
created_at: string;
}
interface StatsBoxProps {
stats: {
totalShares: number;
totalInvestment: number;
averageCostPerShare: number;
currentPrice?: number;
currentValue?: number;
profitLoss?: number;
profitLossPercentage?: number;
};
milestones?: Milestone[];
selectedMilestoneIndex?: number;
onMilestoneSelect?: (index: number) => void;
className?: string;
onAddPurchase?: () => void;
onAddMilestone?: () => void;
onUpdatePrice?: () => void;
}
export default function StatsBox({
stats,
milestones = [],
selectedMilestoneIndex = 0,
onMilestoneSelect,
className,
onAddPurchase,
onAddMilestone,
onUpdatePrice
}: StatsBoxProps) {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const handleCycleMilestone = () => {
if (milestones.length === 0 || !onMilestoneSelect) return;
const nextIndex = (selectedMilestoneIndex + 1) % milestones.length;
onMilestoneSelect(nextIndex);
};
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 (
<div
className={cn(
"bg-black p-8",
"transition-all duration-300",
className
)}
>
<div className="w-full border-4 border-red-500 p-2 bg-black space-y-4">
{/* STATS Title and Current Price */}
<div className="flex justify-between items-center mb-6 relative">
<h2 className="text-red-500 text-lg font-mono font-bold tracking-wider">
STATS
</h2>
<div className="flex items-center space-x-2 relative">
{stats.currentPrice && (
<div className="text-red-500 text-sm font-mono tracking-wider">
VWCE: {formatCurrencyDetailed(stats.currentPrice)}
</div>
)}
{/* Action Dropdown */}
<div className="relative">
<button
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
className="flex items-center justify-center px-2 py-1 rounded bg-red-600/20 border border-red-500/50 text-red-400 hover:bg-red-600/40 hover:text-red-300 transition-colors text-sm"
aria-label="Add actions"
>
<Plus className="w-4 h-4" />
</button>
{/* 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);
}}
className="w-full text-left px-4 py-2 text-blue-400 hover:bg-blue-600/20 hover:text-blue-300 transition-colors text-sm font-mono border-b border-red-500/20 last:border-b-0"
>
ADD MILESTONE
</button>
)}
{onUpdatePrice && (
<button
onClick={() => {
onUpdatePrice();
setIsDropdownOpen(false);
}}
className="w-full text-left px-4 py-2 text-green-400 hover:bg-green-600/20 hover:text-green-300 transition-colors text-sm font-mono border-b border-red-500/20 last:border-b-0"
>
UPDATE PRICE
</button>
)}
</div>
)}
</div>
{/* Milestone Cycle Button */}
{milestones.length > 1 && (
<button
onClick={handleCycleMilestone}
className="flex items-center justify-center px-2 py-1 rounded bg-blue-600/20 border border-blue-500/50 text-blue-400 hover:bg-blue-600/40 hover:text-blue-300 transition-colors text-sm"
aria-label="Cycle milestone"
>
<ChevronRight className="w-4 h-4" />
</button>
)}
</div>
</div>
{/* Milestone Table */}
<div className="border-t border-red-500/30 pt-4">
<div className="text-red-400/70 text-xs mb-3">MILESTONES</div>
<div className="overflow-x-auto">
<table className="w-full text-sm font-mono">
<thead>
<tr className="border-b border-red-500/20">
<th className="text-left text-red-400/60 text-xs py-2">DESCRIPTION</th>
<th className="text-right text-red-400/60 text-xs py-2">SHARES</th>
<th className="text-right text-red-400/60 text-xs py-2">SWR 3%</th>
<th className="text-right text-red-400/60 text-xs py-2">SWR 4%</th>
</tr>
</thead>
<tbody>
{/* Create combined array with current position and milestones, sorted by target */}
{[
...milestones.map(m => ({ ...m, isCurrent: false })),
{
target: stats.totalShares,
description: 'CURRENT',
created_at: '',
isCurrent: true
}
]
.sort((a, b) => a.target - b.target)
.map((item, index) => {
const swr3 = stats.currentPrice ? item.target * stats.currentPrice * 0.03 : 0;
const swr4 = stats.currentPrice ? item.target * stats.currentPrice * 0.04 : 0;
// Check if this milestone is the selected one for progress bar
const isSelectedMilestone = !item.isCurrent && milestones.findIndex(m =>
m.target === item.target && m.description === item.description
) === selectedMilestoneIndex;
return (
<tr
key={index}
className={cn(
"border-b border-red-500/10",
item.isCurrent
? "bg-red-500/10 text-red-300"
: isSelectedMilestone
? "bg-blue-500/10 text-blue-300 border-blue-500/30"
: stats.totalShares >= item.target
? "text-green-400/80"
: "text-red-400/70"
)}
>
<td className="py-2 pr-4">
{item.isCurrent ? (
<span className="font-bold">{item.description}</span>
) : (
item.description
)}
</td>
<td className="text-right py-2 pr-4">
{Math.floor(item.target).toLocaleString()}
</td>
<td className="text-right py-2 pr-4">
{stats.currentPrice ? formatCurrency(swr3) : 'N/A'}
</td>
<td className="text-right py-2">
{stats.currentPrice ? formatCurrency(swr4) : 'N/A'}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}