diff --git a/package.json b/package.json index c35ea0b..47f9812 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "investment-portfolio-tracker", "private": true, - "version": "1.1.0", + "version": "1.1.1", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/InvestmentForm.tsx b/src/components/InvestmentForm.tsx index d3c503a..74bf48f 100644 --- a/src/components/InvestmentForm.tsx +++ b/src/components/InvestmentForm.tsx @@ -54,7 +54,7 @@ export default function InvestmentFormWrapper() { selectedAsset && (
- setSelectedAsset(null)} /> +
) @@ -63,7 +63,7 @@ export default function InvestmentFormWrapper() { ); } -const InvestmentForm = ({ assetId, clearSelectedAsset }: { assetId: string, clearSelectedAsset: () => void }) => { +const InvestmentForm = ({ assetId }: { assetId: string }) => { const [type, setType] = useState<'single' | 'periodic'>('single'); const [amount, setAmount] = useState(''); const [date, setDate] = useState(''); @@ -80,7 +80,7 @@ const InvestmentForm = ({ assetId, clearSelectedAsset }: { assetId: string, clea addInvestment: state.addInvestment, })); - const handleSubmit = async (e: React.FormEvent) => { + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); e.stopPropagation(); @@ -90,7 +90,7 @@ const InvestmentForm = ({ assetId, clearSelectedAsset }: { assetId: string, clea setIsSubmitting(true); - setTimeout(async () => { + setTimeout(() => { console.log("timeout") try { if (type === "single") { @@ -136,7 +136,6 @@ const InvestmentForm = ({ assetId, clearSelectedAsset }: { assetId: string, clea console.timeEnd('generatePeriodicInvestments'); setIsSubmitting(false); setAmount(''); - clearSelectedAsset(); } }, 10); }; diff --git a/src/components/Modals/FutureProjectionModal.tsx b/src/components/Modals/FutureProjectionModal.tsx index caca238..7a50bd8 100644 --- a/src/components/Modals/FutureProjectionModal.tsx +++ b/src/components/Modals/FutureProjectionModal.tsx @@ -282,20 +282,47 @@ export const FutureProjectionModal = ({ const renderScenarioDescription = () => { if (!scenarios.best.projection.length) return null; + const getLastValue = (projection: ProjectionData[]) => { + const lastPoint = projection[projection.length - 1]; + return { + value: lastPoint.value, + invested: lastPoint.invested, + returnPercentage: ((lastPoint.value - lastPoint.invested) / lastPoint.invested) * 100 + }; + }; + + const baseCase = getLastValue(projectionData); + const bestCase = getLastValue(scenarios.best.projection); + const worstCase = getLastValue(scenarios.worst.projection); + return (

Scenario Calculations

diff --git a/src/components/PortfolioTable.tsx b/src/components/PortfolioTable.tsx index 9d01de6..3afa24a 100644 --- a/src/components/PortfolioTable.tsx +++ b/src/components/PortfolioTable.tsx @@ -14,6 +14,16 @@ import { EditSavingsPlanModal } from "./Modals/EditSavingsPlanModal"; import { FutureProjectionModal } from "./Modals/FutureProjectionModal"; import { Tooltip } from "./utils/ToolTip"; +interface SavingsPlanPerformance { + assetName: string; + amount: number; + totalInvested: number; + currentValue: number; + performancePercentage: number; + performancePerAnnoPerformance: number; + allocation?: number; +} + export default function PortfolioTable() { const { assets, removeInvestment, clearInvestments } = usePortfolioSelector((state) => ({ assets: state.assets, @@ -110,9 +120,17 @@ export default function PortfolioTable() { const savingsPlansPerformance = useMemo(() => { if(isSavingsPlanOverviewDisabled) return []; - const performance = []; + const performance: SavingsPlanPerformance[] = []; + const totalSavingsPlansAmount = assets + .map(v => v.investments) + .flat() + .filter(inv => inv.type === 'periodic') + .reduce((sum, inv) => sum + inv.amount, 0); + + // Second pass to calculate individual performances with allocation for (const asset of assets) { const savingsPlans = asset.investments.filter(inv => inv.type === 'periodic'); + const amount = savingsPlans.reduce((sum, inv) => sum + inv.amount, 0); if (savingsPlans.length > 0) { const assetPerformance = calculateInvestmentPerformance([{ ...asset, @@ -121,13 +139,35 @@ export default function PortfolioTable() { performance.push({ assetName: asset.name, amount: savingsPlans[0].amount, - ...assetPerformance.summary + ...assetPerformance.summary, + allocation: amount / totalSavingsPlansAmount * 100 }); } } return performance; }, [assets, isSavingsPlanOverviewDisabled]); + const savingsPlansSummary = useMemo(() => { + if (savingsPlansPerformance.length === 0) return null; + + const totalCurrentValue = savingsPlansPerformance.reduce((sum, plan) => sum + plan.currentValue, 0); + const totalInvested = savingsPlansPerformance.reduce((sum, plan) => sum + plan.totalInvested, 0); + const weightedPerformance = savingsPlansPerformance.reduce((sum, plan) => { + return sum + (plan.performancePercentage * (plan.currentValue / totalCurrentValue)); + }, 0); + const weightedPerformancePA = savingsPlansPerformance.reduce((sum, plan) => { + return sum + (plan.performancePerAnnoPerformance * (plan.currentValue / totalCurrentValue)); + }, 0); + + return { + totalAmount: savingsPlansPerformance.reduce((sum, plan) => sum + plan.amount, 0), + totalInvested, + totalCurrentValue, + weightedPerformance, + weightedPerformancePA, + }; + }, [savingsPlansPerformance]); + const handleGeneratePDF = async () => { setIsGeneratingPDF(true); try { @@ -224,6 +264,7 @@ export default function PortfolioTable() { Asset Interval Amount + Allocation Total Invested Current Value Performance (%) @@ -232,7 +273,19 @@ export default function PortfolioTable() { - {savingsPlansPerformance.map((plan) => { + {savingsPlansSummary && ( + + Total + €{savingsPlansSummary.totalAmount.toFixed(2)} + 100% + €{savingsPlansSummary.totalInvested.toFixed(2)} + €{savingsPlansSummary.totalCurrentValue.toFixed(2)} + {savingsPlansSummary.weightedPerformance.toFixed(2)}% + {savingsPlansSummary.weightedPerformancePA.toFixed(2)}% + + + )} + {savingsPlansPerformance.sort((a, b) => Number(b.allocation || 0) - Number(a.allocation || 0)).map((plan) => { const asset = assets.find(a => a.name === plan.assetName)!; const firstInvestment = asset.investments.find(inv => inv.type === 'periodic')!; const groupId = firstInvestment.periodicGroupId!; @@ -240,7 +293,8 @@ export default function PortfolioTable() { return ( {plan.assetName} - {plan.amount} + €{plan.amount.toFixed(2)} + {plan.allocation?.toFixed(2)}% €{plan.totalInvested.toFixed(2)} €{plan.currentValue.toFixed(2)} {plan.performancePercentage.toFixed(2)}%