Cycle milestones

This commit is contained in:
myrmidex 2025-07-13 01:13:36 +02:00
parent 6c1f4a14c3
commit 0f720d7c91
3 changed files with 69 additions and 26 deletions

View file

@ -9,6 +9,7 @@ interface Milestone {
interface ProgressBarProps {
currentShares: number;
milestones: Milestone[];
selectedMilestoneIndex?: number;
className?: string;
onClick?: () => void;
}
@ -16,15 +17,18 @@ interface ProgressBarProps {
export default function ProgressBar({
currentShares,
milestones,
selectedMilestoneIndex = 0,
className,
onClick
}: ProgressBarProps) {
// Get the first milestone (lowest target) for progress calculation
const firstMilestone = milestones.length > 0 ? milestones[0] : null;
// Get the selected milestone for progress calculation
const selectedMilestone = milestones.length > 0 && selectedMilestoneIndex < milestones.length
? milestones[selectedMilestoneIndex]
: null;
// Calculate progress percentage
const progressPercentage = firstMilestone
? Math.min((currentShares / firstMilestone.target) * 100, 100)
const progressPercentage = selectedMilestone
? Math.min((currentShares / selectedMilestone.target) * 100, 100)
: 0;
return (
<div
@ -49,7 +53,7 @@ export default function ProgressBar({
/>
{/* Text overlay */}
{firstMilestone && (
{selectedMilestone && (
<div className="relative h-full flex items-center justify-center">
{/* Base text (red on black background) */}
<div className="text-red-500 font-mono text-sm font-bold mix-blend-difference relative z-10">

View file

@ -1,5 +1,5 @@
import { cn } from '@/lib/utils';
import { Plus } from 'lucide-react';
import { Plus, ChevronRight } from 'lucide-react';
import { useState } from 'react';
interface Milestone {
@ -19,21 +19,31 @@ interface StatsBoxProps {
profitLossPercentage?: number;
};
milestones?: Milestone[];
selectedMilestoneIndex?: number;
onMilestoneSelect?: (index: number) => void;
className?: string;
onAddPurchase?: () => void;
onAddMilestone?: () => void;
onUpdatePrice?: () => void;
}
export default function StatsBox({
stats,
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',
@ -52,7 +62,7 @@ export default function StatsBox({
};
return (
<div
<div
className={cn(
"bg-black p-8",
"transition-all duration-300",
@ -65,13 +75,13 @@ export default function StatsBox({
<h2 className="text-red-500 text-lg font-mono font-bold tracking-wider">
STATS
</h2>
<div className="flex items-center space-x-4 relative">
<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
@ -81,7 +91,7 @@ export default function StatsBox({
>
<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">
@ -121,6 +131,17 @@ export default function StatsBox({
</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>
@ -141,28 +162,35 @@ export default function StatsBox({
{/* 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
{
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}
<tr
key={index}
className={cn(
"border-b border-red-500/10",
item.isCurrent
? "bg-red-500/10 text-red-300"
: stats.totalShares >= item.target
? "text-green-400/80"
: "text-red-400/70"
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">
@ -191,4 +219,4 @@ export default function StatsBox({
</div>
</div>
);
}
}

View file

@ -33,6 +33,7 @@ export default function Dashboard() {
});
const [milestones, setMilestones] = useState<Milestone[]>([]);
const [selectedMilestoneIndex, setSelectedMilestoneIndex] = useState(0);
const [showProgressBar, setShowProgressBar] = useState(false);
const [showStatsBox, setShowStatsBox] = useState(false);
const [activeForm, setActiveForm] = useState<'purchase' | 'milestone' | 'price' | null>(null);
@ -92,12 +93,19 @@ export default function Dashboard() {
if (milestonesResponse.ok) {
const milestonesData = await milestonesResponse.json();
setMilestones(milestonesData);
// Reset to first milestone when milestones change
setSelectedMilestoneIndex(0);
}
} catch (error) {
console.error('Failed to refresh milestone data:', error);
}
};
// Handle milestone selection
const handleMilestoneSelect = (index: number) => {
setSelectedMilestoneIndex(index);
};
// Refresh price data after successful update
const handlePriceSuccess = async () => {
try {
@ -182,6 +190,7 @@ export default function Dashboard() {
<ProgressBar
currentShares={purchaseData.total_shares}
milestones={milestones}
selectedMilestoneIndex={selectedMilestoneIndex}
onClick={handleProgressClick}
/>
</div>
@ -191,6 +200,8 @@ export default function Dashboard() {
<StatsBox
stats={statsData}
milestones={milestones}
selectedMilestoneIndex={selectedMilestoneIndex}
onMilestoneSelect={handleMilestoneSelect}
onAddPurchase={() => setActiveForm('purchase')}
onAddMilestone={() => setActiveForm('milestone')}
onUpdatePrice={() => setActiveForm('price')}