dev1/src/components/ScenarioContainer.js

217 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/components/ScenarioContainer.js
import React, { useState, useEffect } from 'react';
import { Line } from 'react-chartjs-2';
import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js';
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
}) {
const [localScenario, setLocalScenario] = useState(scenario);
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);
// Re-sync if parent updates scenario
useEffect(() => {
setLocalScenario(scenario);
}, [scenario]);
// 1) Fetch the college profile for this scenario
useEffect(() => {
if (!localScenario?.id) return;
async function loadCollegeProfile() {
try {
const res = await authFetch(
`/api/premium/college-profile?careerPathId=${localScenario.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();
}, [localScenario]);
// 2) Fetch scenarios milestones (and universal)
useEffect(() => {
if (!localScenario?.id) return;
async function loadMilestones() {
try {
const [scenRes, uniRes] = await Promise.all([
authFetch(`/api/premium/milestones?careerPathId=${localScenario.id}`),
authFetch(`/api/premium/milestones?careerPathId=universal`) // if you have that route
]);
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();
}, [localScenario]);
// 3) Merge real snapshot + scenario overrides => run simulation
useEffect(() => {
if (!financialProfile || !collegeProfile) return;
// Merge the scenario's planned overrides if not null,
// else fallback to the real snapshot in financialProfile
const mergedProfile = {
currentSalary: financialProfile.current_salary || 0,
monthlyExpenses:
localScenario.planned_monthly_expenses ?? financialProfile.monthly_expenses ?? 0,
monthlyDebtPayments:
localScenario.planned_monthly_debt_payments ?? financialProfile.monthly_debt_payments ?? 0,
retirementSavings: financialProfile.retirement_savings ?? 0,
emergencySavings: financialProfile.emergency_fund ?? 0,
monthlyRetirementContribution:
localScenario.planned_monthly_retirement_contribution ??
financialProfile.retirement_contribution ??
0,
monthlyEmergencyContribution:
localScenario.planned_monthly_emergency_contribution ??
financialProfile.emergency_contribution ??
0,
surplusEmergencyAllocation:
localScenario.planned_surplus_emergency_pct ??
financialProfile.extra_cash_emergency_pct ??
50,
surplusRetirementAllocation:
localScenario.planned_surplus_retirement_pct ??
financialProfile.extra_cash_retirement_pct ??
50,
additionalIncome:
localScenario.planned_additional_income ?? financialProfile.additional_income ?? 0,
// College fields
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,
inCollege:
collegeProfile.college_enrollment_status === 'currently_enrolled' ||
collegeProfile.college_enrollment_status === 'prospective_student',
expectedSalary: collegeProfile.expected_salary || financialProfile.current_salary || 0,
// Flatten scenario + universal milestoneImpacts
milestoneImpacts: buildAllImpacts([...milestones, ...universalMilestones])
};
const { projectionData, loanPaidOffMonth } =
simulateFinancialProjection(mergedProfile);
setProjectionData(projectionData);
setLoanPaidOffMonth(loanPaidOffMonth);
}, [financialProfile, collegeProfile, localScenario, milestones, universalMilestones]);
function buildAllImpacts(allMilestones) {
let impacts = [];
for (let m of allMilestones) {
if (m.impacts) {
impacts.push(...m.impacts);
}
// If new_salary logic is relevant, handle it here
}
return impacts;
}
// Edit => open modal
return (
<div style={{ width: '420px', border: '1px solid #ccc', padding: '1rem' }}>
<h3>{localScenario.career_name || 'Untitled Scenario'}</h3>
<p>Status: {localScenario.status}</p>
<Line
data={{
labels: projectionData.map((p) => p.month),
datasets: [
{
label: 'Net Savings',
data: projectionData.map((p) => p.cumulativeNetSavings || 0),
borderColor: 'blue',
fill: false
}
]
}}
options={{ responsive: true }}
/>
<div style={{ marginTop: '0.5rem' }}>
<strong>Loan Paid Off:</strong> {loanPaidOffMonth || 'N/A'} <br />
<strong>Retirement (final):</strong> ${
projectionData[projectionData.length - 1]?.retirementSavings?.toFixed(0) || 0
}
</div>
<MilestoneTimeline
careerPathId={localScenario.id}
authFetch={authFetch}
activeView="Financial"
setActiveView={() => {}}
onMilestoneUpdated={() => {
// re-fetch or something
}}
/>
<AISuggestedMilestones
career={localScenario.career_name}
careerPathId={localScenario.id}
authFetch={authFetch}
activeView="Financial"
projectionData={projectionData}
/>
<div style={{ marginTop: '0.5rem' }}>
<button onClick={() => setEditOpen(true)}>Edit</button>
<button onClick={onClone} style={{ marginLeft: '0.5rem' }}>
Clone
</button>
<button onClick={onRemove} style={{ marginLeft: '0.5rem', color: 'red' }}>
Remove
</button>
</div>
{/* Updated ScenarioEditModal that references localScenario + setLocalScenario */}
<ScenarioEditModal
show={editOpen}
onClose={() => setEditOpen(false)}
scenario={localScenario}
setScenario={setLocalScenario}
apiURL="/api"
/>
</div>
);
}