diff --git a/frontend/src/components/dashboard/ContributionGrowthChart.tsx b/frontend/src/components/dashboard/ContributionGrowthChart.tsx index 37422f7..7927a5f 100644 --- a/frontend/src/components/dashboard/ContributionGrowthChart.tsx +++ b/frontend/src/components/dashboard/ContributionGrowthChart.tsx @@ -17,16 +17,20 @@ interface ContributionGrowthChartProps { isDarkMode: boolean projectionData: { age: number; Total: number }[] annualContribution: number + showRealValues: boolean + inflationRate: number } export function ContributionGrowthChart({ isDarkMode, projectionData, annualContribution, + showRealValues, + inflationRate, }: ContributionGrowthChartProps) { const data = useMemo( - () => buildContributionGrowth({ projectionData, annualContribution }), - [projectionData, annualContribution] + () => buildContributionGrowth({ projectionData, annualContribution, showRealValues, inflationRate }), + [projectionData, annualContribution, showRealValues, inflationRate] ) if (data.length === 0) return null diff --git a/frontend/src/components/dashboard/GrowthChart.tsx b/frontend/src/components/dashboard/GrowthChart.tsx index 6be3af7..46c7e53 100644 --- a/frontend/src/components/dashboard/GrowthChart.tsx +++ b/frontend/src/components/dashboard/GrowthChart.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react' import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' import type { ProjectionDataPoint } from '@/types/household' import { formatCompactMoney, formatMoney } from '@/lib/formatting' @@ -6,18 +7,73 @@ import { TrendingUp } from 'lucide-react' interface GrowthChartProps { isDarkMode: boolean currentProjectionData: ProjectionDataPoint[] + conservativeProjectionData: ProjectionDataPoint[] + optimisticProjectionData: ProjectionDataPoint[] yearsToRetirement: number + expectedReturn: number } -export function GrowthChart({ isDarkMode, currentProjectionData, yearsToRetirement }: GrowthChartProps) { +export function GrowthChart({ + isDarkMode, + currentProjectionData, + conservativeProjectionData, + optimisticProjectionData, + yearsToRetirement, + expectedReturn, +}: GrowthChartProps) { + const [activeScenario, setActiveScenario] = useState<'conservative' | 'realistic' | 'optimistic'>('realistic') + if (yearsToRetirement <= 0 || currentProjectionData.length === 0) return null + const chartData = activeScenario === 'conservative' + ? conservativeProjectionData + : activeScenario === 'optimistic' + ? optimisticProjectionData + : currentProjectionData + return (
-

- - Growth Projection -

+
+

+ + Growth Projection +

+ + {/* Scenario Toggle */} +
+ + + +
+
+
@@ -34,7 +90,7 @@ export function GrowthChart({ isDarkMode, currentProjectionData, yearsToRetireme
- + ('growth') const projectionData = projection.currentProjectionData const startAge = projectionData.length > 0 ? projectionData[0].age : 0 @@ -30,36 +31,91 @@ export function ProjectionsTab() { return (
- }> - - + {/* 1. Accumulation Phase (Saving) Section */} +
+
+
+

1. Saving Phase (Accumulation)

+

Projecting your growth path up to retirement age

+
+ +
+ + + +
+
- }> - 0 ? projectionData[0].Total : 0} - annualContribution={projection.totalAnnualContributions} - yearsToRetirement={projection.yearsToRetirement} - expectedReturn={assumptions.expectedReturn} - inflationRate={assumptions.inflationRate} - startAge={startAge} - showRealValues={assumptions.showRealValues} - /> - + }> + {accumulationView === 'growth' && ( + + )} + {accumulationView === 'contributions' && ( + + )} + {accumulationView === 'volatility' && ( + 0 ? projectionData[0].Total : 0} + annualContribution={projection.totalAnnualContributions} + yearsToRetirement={projection.yearsToRetirement} + expectedReturn={assumptions.expectedReturn} + inflationRate={assumptions.inflationRate} + startAge={startAge} + showRealValues={assumptions.showRealValues} + /> + )} + +
- }> - - + {/* 2. Decumulation Phase (Spending) Section */} +
+
+

2. Spending Phase (Decumulation)

+

Projecting how long your savings will last in retirement

+
- }> + }> - + +
+ {/* Educational Footer Info */}

diff --git a/frontend/src/contexts/ProjectionContext.tsx b/frontend/src/contexts/ProjectionContext.tsx index c2c8d39..2639eb4 100644 --- a/frontend/src/contexts/ProjectionContext.tsx +++ b/frontend/src/contexts/ProjectionContext.tsx @@ -9,6 +9,8 @@ interface ProjectionContextValueTyped { wasmLoaded: boolean wasmError: string | null projectionData: ProjectionDataPoint[] + conservativeProjectionData: ProjectionDataPoint[] + optimisticProjectionData: ProjectionDataPoint[] realProjectionData: ProjectionDataPoint[] currentProjectionData: ProjectionDataPoint[] householdRetirementAge: number @@ -109,6 +111,14 @@ export function ProjectionProvider({ children }: { children: ReactNode }) { () => buildCombinedProjection(people, calculateProjection, expectedReturn, inflationRate, showRealValues, householdRetirementAge), [people, calculateProjection, expectedReturn, inflationRate, showRealValues, householdRetirementAge] ) + const conservativeProjectionData = useMemo( + () => buildCombinedProjection(people, calculateProjection, Math.max(0, expectedReturn - 2), inflationRate, showRealValues, householdRetirementAge), + [people, calculateProjection, expectedReturn, inflationRate, showRealValues, householdRetirementAge] + ) + const optimisticProjectionData = useMemo( + () => buildCombinedProjection(people, calculateProjection, expectedReturn + 2, inflationRate, showRealValues, householdRetirementAge), + [people, calculateProjection, expectedReturn, inflationRate, showRealValues, householdRetirementAge] + ) const realProjectionData = useMemo( () => buildCombinedProjection(people, calculateProjection, expectedReturn, inflationRate, true, householdRetirementAge), [people, calculateProjection, expectedReturn, inflationRate, householdRetirementAge] @@ -117,7 +127,8 @@ export function ProjectionProvider({ children }: { children: ReactNode }) { return (