+ );
+}
diff --git a/src/components/PremiumOnboarding/CollegeOnboarding.js b/src/components/PremiumOnboarding/CollegeOnboarding.js
index 6a569c0..c1a015a 100644
--- a/src/components/PremiumOnboarding/CollegeOnboarding.js
+++ b/src/components/PremiumOnboarding/CollegeOnboarding.js
@@ -266,7 +266,7 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId })
}
setAutoTuition(Math.round(estimate));
- // We do NOT auto-update parent's data. We'll do that in handleSubmit or if you prefer, you can store it in parent's data anyway.
+
}, [
icTuitionData, selected_school, program_type,
credit_hours_per_year, is_in_state, is_in_district, schoolData
diff --git a/src/components/ScenarioContainer.js b/src/components/ScenarioContainer.js
new file mode 100644
index 0000000..960bf71
--- /dev/null
+++ b/src/components/ScenarioContainer.js
@@ -0,0 +1,231 @@
+// src/components/ScenarioContainer.js
+import React, { useState, useEffect } from 'react';
+import { Line } from 'react-chartjs-2';
+import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js';
+
+// Reuse your existing:
+import ScenarioEditModal from './ScenarioEditModal.js';
+import MilestoneTimeline from './MilestoneTimeline.js';
+import AISuggestedMilestones from './AISuggestedMilestones.js';
+import authFetch from '../utils/authFetch.js';
+
+export default function ScenarioContainer({
+ scenario, // from career_paths row
+ financialProfile, // single row, shared across user
+ onClone,
+ onRemove,
+ onScenarioUpdated // callback to parent to store updated scenario data
+}) {
+ const [collegeProfile, setCollegeProfile] = useState(null);
+ const [milestones, setMilestones] = useState([]);
+ const [universalMilestones, setUniversalMilestones] = useState([]);
+
+ const [projectionData, setProjectionData] = useState([]);
+ const [loanPaidOffMonth, setLoanPaidOffMonth] = useState(null);
+
+ const [editOpen, setEditOpen] = useState(false);
+
+ // 1) Fetch the college profile for this scenario
+ useEffect(() => {
+ if (!scenario?.id) return;
+ async function loadCollegeProfile() {
+ try {
+ const res = await authFetch(`/api/premium/college-profile?careerPathId=${scenario.id}`);
+ if (res.ok) {
+ const data = await res.json();
+ setCollegeProfile(data);
+ } else {
+ console.warn('No college profile found or error:', res.status);
+ setCollegeProfile({});
+ }
+ } catch (err) {
+ console.error('Failed fetching college profile:', err);
+ }
+ }
+ loadCollegeProfile();
+ }, [scenario]);
+
+ // 2) Fetch scenario’s milestones (where is_universal=0) + universal (is_universal=1)
+ useEffect(() => {
+ if (!scenario?.id) return;
+ async function loadMilestones() {
+ try {
+ const [scenRes, uniRes] = await Promise.all([
+ authFetch(`/api/premium/milestones?careerPathId=${scenario.id}`),
+ // for universal: we do an extra call with no careerPathId.
+ // But your current code always requires a careerPathId. So you might
+ // create a new endpoint /api/premium/milestones?is_universal=1 or something.
+ // We'll assume you have it:
+ authFetch(`/api/premium/milestones?careerPathId=universal`)
+ ]);
+
+ let scenarioData = scenRes.ok ? (await scenRes.json()) : { milestones: [] };
+ let universalData = uniRes.ok ? (await uniRes.json()) : { milestones: [] };
+
+ setMilestones(scenarioData.milestones || []);
+ setUniversalMilestones(universalData.milestones || []);
+ } catch (err) {
+ console.error('Failed to load milestones:', err);
+ }
+ }
+ loadMilestones();
+ }, [scenario]);
+
+ // 3) Whenever we have financialProfile + collegeProfile + milestones, run the simulation
+ useEffect(() => {
+ if (!financialProfile || !collegeProfile) return;
+
+ // Merge them into the userProfile object for the simulator:
+ const mergedProfile = {
+ // Financial fields
+ currentSalary: financialProfile.current_salary || 0,
+ monthlyExpenses: financialProfile.monthly_expenses || 0,
+ monthlyDebtPayments: financialProfile.monthly_debt_payments || 0,
+ retirementSavings: financialProfile.retirement_savings || 0,
+ emergencySavings: financialProfile.emergency_fund || 0,
+ monthlyRetirementContribution: financialProfile.retirement_contribution || 0,
+ monthlyEmergencyContribution: financialProfile.emergency_contribution || 0,
+ surplusEmergencyAllocation: financialProfile.extra_cash_emergency_pct || 50,
+ surplusRetirementAllocation: financialProfile.extra_cash_retirement_pct || 50,
+
+ // College fields (scenario-based)
+ studentLoanAmount: collegeProfile.existing_college_debt || 0,
+ interestRate: collegeProfile.interest_rate || 5,
+ loanTerm: collegeProfile.loan_term || 10,
+ loanDeferralUntilGraduation: !!collegeProfile.loan_deferral_until_graduation,
+ academicCalendar: collegeProfile.academic_calendar || 'semester',
+ annualFinancialAid: collegeProfile.annual_financial_aid || 0,
+ calculatedTuition: collegeProfile.tuition || 0,
+ extraPayment: collegeProfile.extra_payment || 0,
+ gradDate: collegeProfile.expected_graduation || null,
+ programType: collegeProfile.program_type || '',
+ creditHoursPerYear: collegeProfile.credit_hours_per_year || 0,
+ hoursCompleted: collegeProfile.hours_completed || 0,
+ programLength: collegeProfile.program_length || 0,
+
+ // We assume user’s baseline “inCollege” from the DB:
+ inCollege:
+ collegeProfile.college_enrollment_status === 'currently_enrolled' ||
+ collegeProfile.college_enrollment_status === 'prospective_student',
+
+ // If you store expected_salary in collegeProfile
+ expectedSalary: collegeProfile.expected_salary || financialProfile.current_salary,
+
+ // Flatten the scenario + universal milestones’ impacts
+ milestoneImpacts: buildAllImpacts([...milestones, ...universalMilestones])
+ };
+
+ const { projectionData, loanPaidOffMonth } = simulateFinancialProjection(mergedProfile);
+ setProjectionData(projectionData);
+ setLoanPaidOffMonth(loanPaidOffMonth);
+ }, [financialProfile, collegeProfile, milestones, universalMilestones]);
+
+ // Helper: Flatten all milestone impacts into one array for the simulator
+ function buildAllImpacts(allMilestones) {
+ let impacts = [];
+ for (let m of allMilestones) {
+ // Possibly fetch m.impacts if you store them directly on the milestone
+ // or if you fetch them separately.
+ // If your code stores them as `m.impacts = [ { direction, amount, ... } ]`
+ if (m.impacts) {
+ impacts.push(...m.impacts);
+ }
+ // If you also want a milestone that sets a new salary, handle that logic too
+ // E.g., { impact_type: 'SALARY_CHANGE', start_date: m.date, newSalary: m.new_salary }
+ }
+ return impacts;
+ }
+
+ // 4) We’ll display a single line chart with Net Savings (or cumulativeNetSavings)
+ const labels = projectionData.map((p) => p.month);
+ const netSavingsData = projectionData.map((p) => p.cumulativeNetSavings || 0);
+
+ // Grab final row for some KPIs
+ const finalRow = projectionData[projectionData.length - 1] || {};
+ const finalRet = finalRow.retirementSavings?.toFixed(0) || '0';
+ const finalEmerg = finalRow.emergencySavings?.toFixed(0) || '0';
+
+ // 5) Handle “Edit” scenario -> open your existing `ScenarioEditModal.js`
+ // But that modal currently references setFinancialProfile, setCollegeProfile directly,
+ // so you may want a specialized version that changes only this scenario’s row.
+ // For simplicity, we’ll just show how to open it:
+
+ return (
+
+
{scenario.career_name || 'Untitled Scenario'}
+
Status: {scenario.status}
+
+
+
+
+ Loan Paid Off: {loanPaidOffMonth || 'N/A'}
+ Final Retirement: ${finalRet}
+ Final Emergency: ${finalEmerg}
+
+
+ {/* The timeline for this scenario. We pass careerPathId */}
+ {}}
+ onMilestoneUpdated={() => {
+ // re-fetch or something
+ // We'll just force a re-fetch of scenario’s milestones
+ // or re-run the entire load effect
+ }}
+ />
+
+ {/* Show AI suggestions if you like */}
+
+
+
+
+
+
+
+
+ {/* Reuse your existing ScenarioEditModal that expects
+ setFinancialProfile, setCollegeProfile, etc.
+ However, you might want a specialized "ScenarioEditModal" that updates
+ the DB fields for *this* scenario. For now, we just show how to open. */}
+ setEditOpen(false)}
+ financialProfile={financialProfile}
+ setFinancialProfile={() => {
+ // If you truly want scenario-specific financial data,
+ // you’d do a more advanced approach.
+ // For now, do nothing or re-fetch from server.
+ }}
+ collegeProfile={collegeProfile}
+ setCollegeProfile={(updated) => {
+ setCollegeProfile((prev) => ({ ...prev, ...updated }));
+ }}
+ apiURL="/api"
+ authFetch={authFetch}
+ />
+
+ );
+}
diff --git a/src/utils/FinancialProjectionService.js b/src/utils/FinancialProjectionService.js
index d619a0f..9f5347a 100644
--- a/src/utils/FinancialProjectionService.js
+++ b/src/utils/FinancialProjectionService.js
@@ -135,7 +135,11 @@ export function simulateFinancialProjection(userProfile) {
stateCode = 'GA',
// Financial milestone impacts
- milestoneImpacts = []
+ milestoneImpacts = [],
+
+ // Simulation duration
+ simulationYears = 20
+
} = userProfile;
/***************************************************
@@ -204,7 +208,7 @@ export function simulateFinancialProjection(userProfile) {
/***************************************************
* 6) SETUP FOR THE SIMULATION LOOP
***************************************************/
- const maxMonths = 240; // 20 years
+ const maxMonths = simulationYears*12; // 20 years
let loanBalance = Math.max(studentLoanAmount, 0);
let loanPaidOffMonth = null;
diff --git a/user_profile.db b/user_profile.db
index b441354..b4507c2 100644
Binary files a/user_profile.db and b/user_profile.db differ