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 { interface ProgressBarProps {
currentShares: number; currentShares: number;
milestones: Milestone[]; milestones: Milestone[];
selectedMilestoneIndex?: number;
className?: string; className?: string;
onClick?: () => void; onClick?: () => void;
} }
@ -16,15 +17,18 @@ interface ProgressBarProps {
export default function ProgressBar({ export default function ProgressBar({
currentShares, currentShares,
milestones, milestones,
selectedMilestoneIndex = 0,
className, className,
onClick onClick
}: ProgressBarProps) { }: ProgressBarProps) {
// Get the first milestone (lowest target) for progress calculation // Get the selected milestone for progress calculation
const firstMilestone = milestones.length > 0 ? milestones[0] : null; const selectedMilestone = milestones.length > 0 && selectedMilestoneIndex < milestones.length
? milestones[selectedMilestoneIndex]
: null;
// Calculate progress percentage // Calculate progress percentage
const progressPercentage = firstMilestone const progressPercentage = selectedMilestone
? Math.min((currentShares / firstMilestone.target) * 100, 100) ? Math.min((currentShares / selectedMilestone.target) * 100, 100)
: 0; : 0;
return ( return (
<div <div
@ -49,7 +53,7 @@ export default function ProgressBar({
/> />
{/* Text overlay */} {/* Text overlay */}
{firstMilestone && ( {selectedMilestone && (
<div className="relative h-full flex items-center justify-center"> <div className="relative h-full flex items-center justify-center">
{/* Base text (red on black background) */} {/* Base text (red on black background) */}
<div className="text-red-500 font-mono text-sm font-bold mix-blend-difference relative z-10"> <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 { cn } from '@/lib/utils';
import { Plus } from 'lucide-react'; import { Plus, ChevronRight } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
interface Milestone { interface Milestone {
@ -19,6 +19,8 @@ interface StatsBoxProps {
profitLossPercentage?: number; profitLossPercentage?: number;
}; };
milestones?: Milestone[]; milestones?: Milestone[];
selectedMilestoneIndex?: number;
onMilestoneSelect?: (index: number) => void;
className?: string; className?: string;
onAddPurchase?: () => void; onAddPurchase?: () => void;
onAddMilestone?: () => void; onAddMilestone?: () => void;
@ -28,12 +30,20 @@ interface StatsBoxProps {
export default function StatsBox({ export default function StatsBox({
stats, stats,
milestones = [], milestones = [],
selectedMilestoneIndex = 0,
onMilestoneSelect,
className, className,
onAddPurchase, onAddPurchase,
onAddMilestone, onAddMilestone,
onUpdatePrice onUpdatePrice
}: StatsBoxProps) { }: StatsBoxProps) {
const [isDropdownOpen, setIsDropdownOpen] = useState(false); 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) => { const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('de-DE', { return new Intl.NumberFormat('de-DE', {
style: 'currency', style: 'currency',
@ -65,7 +75,7 @@ export default function StatsBox({
<h2 className="text-red-500 text-lg font-mono font-bold tracking-wider"> <h2 className="text-red-500 text-lg font-mono font-bold tracking-wider">
STATS STATS
</h2> </h2>
<div className="flex items-center space-x-4 relative"> <div className="flex items-center space-x-2 relative">
{stats.currentPrice && ( {stats.currentPrice && (
<div className="text-red-500 text-sm font-mono tracking-wider"> <div className="text-red-500 text-sm font-mono tracking-wider">
VWCE: {formatCurrencyDetailed(stats.currentPrice)} VWCE: {formatCurrencyDetailed(stats.currentPrice)}
@ -121,6 +131,17 @@ export default function StatsBox({
</div> </div>
)} )}
</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>
</div> </div>
@ -153,6 +174,11 @@ export default function StatsBox({
const swr3 = stats.currentPrice ? item.target * stats.currentPrice * 0.03 : 0; const swr3 = stats.currentPrice ? item.target * stats.currentPrice * 0.03 : 0;
const swr4 = stats.currentPrice ? item.target * stats.currentPrice * 0.04 : 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 ( return (
<tr <tr
key={index} key={index}
@ -160,6 +186,8 @@ export default function StatsBox({
"border-b border-red-500/10", "border-b border-red-500/10",
item.isCurrent item.isCurrent
? "bg-red-500/10 text-red-300" ? "bg-red-500/10 text-red-300"
: isSelectedMilestone
? "bg-blue-500/10 text-blue-300 border-blue-500/30"
: stats.totalShares >= item.target : stats.totalShares >= item.target
? "text-green-400/80" ? "text-green-400/80"
: "text-red-400/70" : "text-red-400/70"

View file

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