diff --git a/app/Http/Controllers/Pricing/PricingController.php b/app/Http/Controllers/Pricing/PricingController.php index cdd34a3..bb07156 100644 --- a/app/Http/Controllers/Pricing/PricingController.php +++ b/app/Http/Controllers/Pricing/PricingController.php @@ -18,7 +18,7 @@ public function current(): JsonResponse ]); } - public function update(Request $request): JsonResponse + public function update(Request $request) { $validated = $request->validate([ 'date' => 'required|date|before_or_equal:today', @@ -27,11 +27,7 @@ public function update(Request $request): JsonResponse $assetPrice = AssetPrice::updatePrice($validated['date'], $validated['price']); - return response()->json([ - 'success' => true, - 'message' => 'Asset price updated successfully!', - 'data' => $assetPrice, - ]); + return back()->with('success', 'Asset price updated successfully!'); } public function history(Request $request): JsonResponse diff --git a/resources/js/components/Display/InlineForm.tsx b/resources/js/components/Display/InlineForm.tsx index 949de4d..9d6b693 100644 --- a/resources/js/components/Display/InlineForm.tsx +++ b/resources/js/components/Display/InlineForm.tsx @@ -1,13 +1,15 @@ import AddMilestoneForm from '@/components/Milestones/AddMilestoneForm'; import AddPurchaseForm from '@/components/Transactions/AddPurchaseForm'; +import UpdatePriceForm from '@/components/Pricing/UpdatePriceForm'; import { cn } from '@/lib/utils'; import { X } from 'lucide-react'; interface InlineFormProps { - type: 'purchase' | 'milestone' | null; + type: 'purchase' | 'milestone' | 'price' | null; onClose: () => void; onPurchaseSuccess?: () => void; onMilestoneSuccess?: () => void; + onPriceSuccess?: () => void; className?: string; } @@ -16,11 +18,12 @@ export default function InlineForm({ onClose, onPurchaseSuccess, onMilestoneSuccess, + onPriceSuccess, className }: InlineFormProps) { if (!type) return null; - const title = type === 'purchase' ? 'ADD PURCHASE' : 'ADD MILESTONE'; + const title = type === 'purchase' ? 'ADD PURCHASE' : type === 'milestone' ? 'ADD MILESTONE' : 'UPDATE PRICE'; return (
- ) : ( + ) : type === 'milestone' ? ( { if (onMilestoneSuccess) onMilestoneSuccess(); onClose(); }} /> + ) : ( + { + if (onPriceSuccess) onPriceSuccess(); + onClose(); + }} + /> )}
diff --git a/resources/js/components/Display/StatsBox.tsx b/resources/js/components/Display/StatsBox.tsx index b88d0bf..4d11085 100644 --- a/resources/js/components/Display/StatsBox.tsx +++ b/resources/js/components/Display/StatsBox.tsx @@ -1,5 +1,6 @@ import { cn } from '@/lib/utils'; import { Plus } from 'lucide-react'; +import { useState } from 'react'; interface Milestone { target: number; @@ -21,6 +22,7 @@ interface StatsBoxProps { className?: string; onAddPurchase?: () => void; onAddMilestone?: () => void; + onUpdatePrice?: () => void; } export default function StatsBox({ @@ -28,8 +30,10 @@ export default function StatsBox({ milestones = [], className, onAddPurchase, - onAddMilestone + onAddMilestone, + onUpdatePrice }: StatsBoxProps) { + const [isDropdownOpen, setIsDropdownOpen] = useState(false); const formatCurrency = (amount: number) => { return new Intl.NumberFormat('de-DE', { style: 'currency', @@ -50,149 +54,141 @@ export default function StatsBox({ 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)} +
+ {/* STATS Title and Current Price */} +
+

+ STATS +

+
+ {stats.currentPrice && ( +
+ VWCE: {formatCurrencyDetailed(stats.currentPrice)}
-
-
-
4% Rule
-
- {formatCurrency(stats.currentValue * 0.04)} -
-
-
-
- )} - - {/* Milestones */} - {milestones.length > 0 && ( -
-
Milestones
-
- {milestones.map((milestone, index) => { - const isReached = stats.totalShares >= milestone.target; - return ( -
-
-
- - {milestone.target.toLocaleString()} - -
-
- {milestone.description} -
+ )} + + {/* Action Dropdown */} +
+ + + {/* Dropdown Menu */} + {isDropdownOpen && ( +
+ {onAddPurchase && ( + + )} + {onAddMilestone && ( + + )} + {onUpdatePrice && ( + + )}
- ); - })} + )} +
- )} - {/* Action Buttons */} + {/* Milestone Table */}
-
- {/* Add Purchase Button */} - {onAddPurchase && ( - - )} - - {/* Add Milestone Button */} - {onAddMilestone && ( - - )} +
MILESTONES
+
+ + + + + + + + + + + {/* 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; + + return ( + = item.target + ? "text-green-400/80" + : "text-red-400/70" + )} + > + + + + + + ); + })} + +
DESCRIPTIONSHARESSWR 3%SWR 4%
+ {item.isCurrent ? ( + {item.description} + ) : ( + item.description + )} + + {Math.floor(item.target).toLocaleString()} + + {stats.currentPrice ? formatCurrency(swr3) : 'N/A'} + + {stats.currentPrice ? formatCurrency(swr4) : 'N/A'} +
+
); } \ No newline at end of file diff --git a/resources/js/components/Pricing/UpdatePriceForm.tsx b/resources/js/components/Pricing/UpdatePriceForm.tsx index b4944e5..bd18cb9 100644 --- a/resources/js/components/Pricing/UpdatePriceForm.tsx +++ b/resources/js/components/Pricing/UpdatePriceForm.tsx @@ -16,9 +16,10 @@ interface PriceUpdateFormData { interface UpdatePriceFormProps { currentPrice?: number; className?: string; + onSuccess?: () => void; } -export default function UpdatePriceForm({ currentPrice, className }: UpdatePriceFormProps) { +export default function UpdatePriceForm({ currentPrice, className, onSuccess }: UpdatePriceFormProps) { const { data, setData, post, processing, errors } = useForm({ date: new Date().toISOString().split('T')[0], // Today's date in YYYY-MM-DD format price: currentPrice?.toString() || '', @@ -31,6 +32,7 @@ export default function UpdatePriceForm({ currentPrice, className }: UpdatePrice onSuccess: () => { // Keep the date, reset only price if needed // User might want to update same day multiple times + if (onSuccess) onSuccess(); }, }); }; diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index 4275e3b..1727698 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -35,7 +35,7 @@ export default function Dashboard() { const [milestones, setMilestones] = useState([]); const [showProgressBar, setShowProgressBar] = useState(false); const [showStatsBox, setShowStatsBox] = useState(false); - const [activeForm, setActiveForm] = useState<'purchase' | 'milestone' | null>(null); + const [activeForm, setActiveForm] = useState<'purchase' | 'milestone' | 'price' | null>(null); const [loading, setLoading] = useState(true); // Fetch purchase summary, current price, and milestones @@ -98,6 +98,19 @@ export default function Dashboard() { } }; + // Refresh price data after successful update + const handlePriceSuccess = async () => { + try { + const priceResponse = await fetch('/pricing/current'); + if (priceResponse.ok) { + const price = await priceResponse.json(); + setPriceData(price); + } + } catch (error) { + console.error('Failed to refresh price data:', error); + } + }; + // Calculate portfolio stats const currentValue = priceData.current_price @@ -180,6 +193,7 @@ export default function Dashboard() { milestones={milestones} onAddPurchase={() => setActiveForm('purchase')} onAddMilestone={() => setActiveForm('milestone')} + onUpdatePrice={() => setActiveForm('price')} />
@@ -190,6 +204,7 @@ export default function Dashboard() { onClose={() => setActiveForm(null)} onPurchaseSuccess={handlePurchaseSuccess} onMilestoneSuccess={handleMilestoneSuccess} + onPriceSuccess={handlePriceSuccess} />