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
-
- Avg. Base Case: Using historical average return of {performancePerAnno.toFixed(2)}%
+ Avg. Base Case: Using historical average return of{' '}
+ {performancePerAnno.toFixed(2)}%
+
+ After {years} years you'd have{' '}
+ {formatCurrency(baseCase.value)} from {formatCurrency(baseCase.invested)} invested,{' '}
+ that's a total return of {baseCase.returnPercentage.toFixed(2)}%
+
-
Best Case: Average of top 50% performing years ({scenarios.best.avaragedAmount} years) at {scenarios.best.percentage.toFixed(2)}%,
- averaged with base case to {scenarios.best.percentageAveraged.toFixed(2)}%
+ averaged with base case to {scenarios.best.percentageAveraged.toFixed(2)}%.{' '}
+
+ After {years} years you'd have {formatCurrency(bestCase.value)} from {formatCurrency(bestCase.invested)} invested,{' '}
+ that's a total return of {bestCase.returnPercentage.toFixed(2)}%
+
-
Worst Case: Average of bottom 50% performing years ({scenarios.worst.avaragedAmount} years) at {scenarios.worst.percentage.toFixed(2)}%,
- averaged with base case to {scenarios.worst.percentageAveraged.toFixed(2)}%
+ averaged with base case to {scenarios.worst.percentageAveraged.toFixed(2)}%.{' '}
+
+ After {years} years you'd have {formatCurrency(worstCase.value)} from {formatCurrency(worstCase.invested)} invested,{' '}
+ that's a total return of {worstCase.returnPercentage.toFixed(2)}%
+
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)}% |