diff --git a/resources/js/components/Display/InlineForm.tsx b/resources/js/components/Display/InlineForm.tsx
new file mode 100644
index 0000000..949de4d
--- /dev/null
+++ b/resources/js/components/Display/InlineForm.tsx
@@ -0,0 +1,68 @@
+import AddMilestoneForm from '@/components/Milestones/AddMilestoneForm';
+import AddPurchaseForm from '@/components/Transactions/AddPurchaseForm';
+import { cn } from '@/lib/utils';
+import { X } from 'lucide-react';
+
+interface InlineFormProps {
+ type: 'purchase' | 'milestone' | null;
+ onClose: () => void;
+ onPurchaseSuccess?: () => void;
+ onMilestoneSuccess?: () => void;
+ className?: string;
+}
+
+export default function InlineForm({
+ type,
+ onClose,
+ onPurchaseSuccess,
+ onMilestoneSuccess,
+ className
+}: InlineFormProps) {
+ if (!type) return null;
+
+ const title = type === 'purchase' ? 'ADD PURCHASE' : 'ADD MILESTONE';
+
+ return (
+
+ {/* Header */}
+
+
+ {title}
+
+
+
+
+ {/* Form Content */}
+
+ {type === 'purchase' ? (
+
{
+ if (onPurchaseSuccess) onPurchaseSuccess();
+ onClose();
+ }}
+ />
+ ) : (
+ {
+ if (onMilestoneSuccess) onMilestoneSuccess();
+ onClose();
+ }}
+ />
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/resources/js/components/Display/LedDisplay.tsx b/resources/js/components/Display/LedDisplay.tsx
new file mode 100644
index 0000000..c12f0c6
--- /dev/null
+++ b/resources/js/components/Display/LedDisplay.tsx
@@ -0,0 +1,104 @@
+import { cn } from '@/lib/utils';
+import { useEffect, useState } from 'react';
+
+interface LedDisplayProps {
+ value: number;
+ className?: string;
+ animate?: boolean;
+ onClick?: () => void;
+}
+
+export default function LedDisplay({
+ value,
+ className,
+ animate = true,
+ onClick
+}: LedDisplayProps) {
+ const [displayValue, setDisplayValue] = useState(0);
+
+ // Animate number changes
+ useEffect(() => {
+ if (!animate) {
+ setDisplayValue(value);
+ return;
+ }
+
+ const duration = 1000; // 1 second animation
+ const steps = 60; // 60fps
+ const stepValue = (value - displayValue) / steps;
+
+ if (Math.abs(stepValue) < 0.01) {
+ setDisplayValue(value);
+ return;
+ }
+
+ const timer = setInterval(() => {
+ setDisplayValue(prev => {
+ const next = prev + stepValue;
+ if (Math.abs(next - value) < Math.abs(stepValue)) {
+ clearInterval(timer);
+ return value;
+ }
+ return next;
+ });
+ }, duration / steps);
+
+ return () => clearInterval(timer);
+ }, [value, displayValue, animate]);
+
+ // Format number appropriately for shares
+ const formatValue = (value: number) => {
+ // If it's a whole number, show it as integer
+ if (value % 1 === 0) {
+ return value.toString();
+ }
+ // Otherwise show up to 6 decimal places, removing trailing zeros
+ return value.toFixed(6).replace(/\.?0+$/, '');
+ };
+
+ const formattedValue = formatValue(displayValue);
+
+ return (
+
+
+ {/* Background glow effect */}
+
+ {formattedValue}
+
+
+ {/* Main LED text */}
+
+ {formattedValue}
+
+
+ {/* Subtle scan line effect */}
+
+
+
+ {/* Label */}
+
+ total shares
+
+
+ );
+}
\ No newline at end of file
diff --git a/resources/js/components/Display/ProgressBar.tsx b/resources/js/components/Display/ProgressBar.tsx
new file mode 100644
index 0000000..a777d3a
--- /dev/null
+++ b/resources/js/components/Display/ProgressBar.tsx
@@ -0,0 +1,60 @@
+import { cn } from '@/lib/utils';
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { useState } from 'react';
+
+interface ProgressBarProps {
+ value: number;
+ className?: string;
+ onClick?: () => void;
+}
+
+export default function ProgressBar({
+ value,
+ className,
+ onClick
+}: ProgressBarProps) {
+ const [currentMilestoneIndex, setCurrentMilestoneIndex] = useState(0);
+
+ // Milestone definitions
+ const milestones = [
+ { target: 1500, label: '1.5K', color: 'bg-blue-500' },
+ { target: 3000, label: '3K', color: 'bg-green-500' },
+ { target: 4500, label: '4.5K', color: 'bg-yellow-500' },
+ { target: 6000, label: '6K', color: 'bg-red-500' },
+ ];
+
+ const currentMilestone = milestones[currentMilestoneIndex];
+ const progress = Math.min((value / currentMilestone.target) * 100, 100);
+ const isCompleted = value >= currentMilestone.target;
+
+ // Milestone navigation
+ const nextMilestone = () => {
+ setCurrentMilestoneIndex((prev) =>
+ prev < milestones.length - 1 ? prev + 1 : 0
+ );
+ };
+
+ const prevMilestone = () => {
+ setCurrentMilestoneIndex((prev) =>
+ prev > 0 ? prev - 1 : milestones.length - 1
+ );
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/resources/js/components/Display/StatsBox.tsx b/resources/js/components/Display/StatsBox.tsx
new file mode 100644
index 0000000..c0e426e
--- /dev/null
+++ b/resources/js/components/Display/StatsBox.tsx
@@ -0,0 +1,156 @@
+import { cn } from '@/lib/utils';
+import { Plus } from 'lucide-react';
+
+interface StatsBoxProps {
+ stats: {
+ totalShares: number;
+ totalInvestment: number;
+ averageCostPerShare: number;
+ currentPrice?: number;
+ currentValue?: number;
+ profitLoss?: number;
+ profitLossPercentage?: number;
+ };
+ className?: string;
+ onAddPurchase?: () => void;
+ onAddMilestone?: () => void;
+}
+
+export default function StatsBox({
+ stats,
+ className,
+ onAddPurchase,
+ onAddMilestone
+}: StatsBoxProps) {
+ 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 (
+
+ {/* Current Price */}
+ {stats.currentPrice && (
+
+
+ current price
+
+
+ {formatCurrencyDetailed(stats.currentPrice)}
+
+
+ )}
+
+ {/* Portfolio Stats Grid */}
+
+ {/* Total Investment */}
+
+
Total Investment
+
+ {formatCurrency(stats.totalInvestment)}
+
+
+
+ {/* Current Value */}
+ {stats.currentValue && (
+
+
Current Value
+
+ {formatCurrency(stats.currentValue)}
+
+
+ )}
+
+ {/* Average Cost */}
+
+
Avg Cost/Share
+
+ {formatCurrencyDetailed(stats.averageCostPerShare)}
+
+
+
+ {/* Profit/Loss */}
+ {stats.profitLoss !== undefined && (
+
+
P&L
+
= 0 ? "text-green-400" : "text-red-400"
+ )}>
+ {stats.profitLoss >= 0 ? '+' : ''}{formatCurrency(stats.profitLoss)}
+
+
+ )}
+
+
+ {/* Withdrawal Estimates */}
+ {stats.currentValue && (
+
+
Annual Withdrawal (Safe)
+
+
+
3% Rule
+
+ {formatCurrency(stats.currentValue * 0.03)}
+
+
+
+
4% Rule
+
+ {formatCurrency(stats.currentValue * 0.04)}
+
+
+
+
+ )}
+
+ {/* Action Buttons */}
+
+
+ {/* Add Purchase Button */}
+ {onAddPurchase && (
+
+ )}
+
+ {/* Add Milestone Button */}
+ {onAddMilestone && (
+
+ )}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx
index e9a97e6..c9c3c1d 100644
--- a/resources/js/pages/dashboard.tsx
+++ b/resources/js/pages/dashboard.tsx
@@ -1,7 +1,7 @@
-import LedCounter from '@/components/Display/LedCounter';
-import MilestoneModal from '@/components/Display/MilestoneModal';
-import PurchaseModal from '@/components/Display/PurchaseModal';
-import StatsPanel from '@/components/Display/StatsPanel';
+import LedDisplay from '@/components/Display/LedDisplay';
+import InlineForm from '@/components/Display/InlineForm';
+import ProgressBar from '@/components/Display/ProgressBar';
+import StatsBox from '@/components/Display/StatsBox';
import { Head } from '@inertiajs/react';
import { useEffect, useState } from 'react';
@@ -26,9 +26,9 @@ export default function Dashboard() {
current_price: null,
});
- const [showStats, setShowStats] = useState(false);
- const [showPurchaseModal, setShowPurchaseModal] = useState(false);
- const [showMilestoneModal, setShowMilestoneModal] = useState(false);
+ const [showProgressBar, setShowProgressBar] = useState(false);
+ const [showStatsBox, setShowStatsBox] = useState(false);
+ const [activeForm, setActiveForm] = useState<'purchase' | 'milestone' | null>(null);
const [loading, setLoading] = useState(true);
// Fetch purchase summary and current price
@@ -109,44 +109,62 @@ export default function Dashboard() {
);
}
+ // Toggle handlers with cascading behavior
+ const handleLedClick = () => {
+ const newShowProgressBar = !showProgressBar;
+ setShowProgressBar(newShowProgressBar);
+ if (!newShowProgressBar) {
+ // If hiding progress bar, also hide stats box
+ setShowStatsBox(false);
+ }
+ };
+
+ const handleProgressClick = () => {
+ setShowStatsBox(!showStatsBox);
+ };
+
return (
<>