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

214 lines
10 KiB
TypeScript

import { cn } from '@/lib/utils';
import { Plus, ChevronRight } from 'lucide-react';
import { useState } from 'react';
import ComponentTitle from '@/components/ui/ComponentTitle';
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">
<ComponentTitle>Stats</ComponentTitle>
<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 border border-red-500/50 text-red-500 hover:bg-red-800/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-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"
>
ADD MILESTONE
</button>
)}
{onUpdatePrice && (
<button
onClick={() => {
onUpdatePrice();
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 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 border border-red-500/50 text-red-500 hover:bg-red-800/40 hover:text-red-300 transition-colors text-sm"
aria-label="Cycle milestone"
>
<ChevronRight className="w-4 h-4" />
</button>
)}
</div>
</div>
{/* Milestone Table */}
<div className="pt-4">
<div className="text-red-500 underline font-bold mb-3 font-mono">MILESTONES</div>
<div className="overflow-x-auto">
<table className="w-full text-sm font-mono">
<thead>
<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>
</tr>
</thead>
<tbody>
{/* 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;
const isSelectedMilestone = index === selectedMilestoneIndex;
return (
<tr
key={index}
className={cn(
isSelectedMilestone
? "bg-red-500 text-black"
: "text-red-500 font-bold"
)}
>
<td className="py-1 pr-4">
{milestone.description}
</td>
<td className="text-right py-1 pr-4">
{Math.floor(milestone.target).toLocaleString()}
</td>
<td className="text-right py-1 pr-4">
{stats.currentPrice ? formatCurrency(swr3) : 'N/A'}
</td>
<td className="text-right py-1">
{stats.currentPrice ? formatCurrency(swr4) : 'N/A'}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}