Fixed UUID issues between onboarding and simulator, new issues with college-profile saving from onboarding.
This commit is contained in:
parent
287737fa8b
commit
f9177fbf38
@ -165,7 +165,7 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
|
||||
}
|
||||
|
||||
try {
|
||||
const newId = uuidv4();
|
||||
const finalId = req.body.id || uuidv4();
|
||||
|
||||
// 1) Insert includes career_goals
|
||||
const sql = `
|
||||
@ -207,7 +207,7 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
|
||||
`;
|
||||
|
||||
await pool.query(sql, [
|
||||
newId,
|
||||
finalId,
|
||||
req.id,
|
||||
scenario_title || null,
|
||||
career_name,
|
||||
@ -231,12 +231,12 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
|
||||
`SELECT id
|
||||
FROM career_profiles
|
||||
WHERE id = ?`,
|
||||
[newId]
|
||||
[finalId]
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'Career profile upserted.',
|
||||
career_profile_id: rows[0]?.id || newId
|
||||
career_profile_id: finalId
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error upserting career profile:', error);
|
||||
@ -1383,6 +1383,7 @@ app.post('/api/premium/financial-profile', authenticatePremiumUser, async (req,
|
||||
|
||||
app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, res) => {
|
||||
const {
|
||||
id, // <-- Accept this in body
|
||||
career_profile_id,
|
||||
selected_school,
|
||||
selected_program,
|
||||
@ -1395,6 +1396,7 @@ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, re
|
||||
credit_hours_required,
|
||||
hours_completed,
|
||||
program_length,
|
||||
enrollment_date,
|
||||
expected_graduation,
|
||||
existing_college_debt,
|
||||
interest_rate,
|
||||
@ -1409,7 +1411,8 @@ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, re
|
||||
} = req.body;
|
||||
|
||||
try {
|
||||
const newId = uuidv4();
|
||||
// If the request includes an existing id, use it; otherwise generate a new one
|
||||
const finalId = id || uuidv4();
|
||||
|
||||
const sql = `
|
||||
INSERT INTO college_profiles (
|
||||
@ -1428,6 +1431,7 @@ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, re
|
||||
hours_completed,
|
||||
program_length,
|
||||
credit_hours_required,
|
||||
enrollment_date,
|
||||
expected_graduation,
|
||||
existing_college_debt,
|
||||
interest_rate,
|
||||
@ -1444,7 +1448,7 @@ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, re
|
||||
?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?
|
||||
?, ?, ?, ?, ?
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
is_in_state = VALUES(is_in_state),
|
||||
@ -1456,6 +1460,7 @@ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, re
|
||||
hours_completed = VALUES(hours_completed),
|
||||
program_length = VALUES(program_length),
|
||||
credit_hours_required = VALUES(credit_hours_required),
|
||||
enrollment_date = VALUES(enrollment_date),
|
||||
expected_graduation = VALUES(expected_graduation),
|
||||
existing_college_debt = VALUES(existing_college_debt),
|
||||
interest_rate = VALUES(interest_rate),
|
||||
@ -1470,8 +1475,8 @@ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, re
|
||||
`;
|
||||
|
||||
await pool.query(sql, [
|
||||
newId,
|
||||
req.id,
|
||||
finalId,
|
||||
req.id, // user_id
|
||||
career_profile_id,
|
||||
selected_school,
|
||||
selected_program,
|
||||
@ -1485,6 +1490,7 @@ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, re
|
||||
hours_completed || 0,
|
||||
program_length || 0,
|
||||
credit_hours_required || 0,
|
||||
enrollment_date || null,
|
||||
expected_graduation || null,
|
||||
existing_college_debt || 0,
|
||||
interest_rate || 0,
|
||||
@ -1497,7 +1503,7 @@ app.post('/api/premium/college-profile', authenticatePremiumUser, async (req, re
|
||||
tuition_paid || 0
|
||||
]);
|
||||
|
||||
res.status(201).json({ message: 'College profile upsert done.' });
|
||||
res.status(201).json({ message: 'College profile upsert done.', id: finalId });
|
||||
} catch (error) {
|
||||
console.error('Error saving college profile:', error);
|
||||
res.status(500).json({ error: 'Failed to save college profile.' });
|
||||
|
@ -20,8 +20,6 @@ import parseFloatOrZero from '../utils/ParseFloatorZero.js';
|
||||
import { getFullStateName } from '../utils/stateUtils.js';
|
||||
|
||||
import { Button } from './ui/button.js';
|
||||
import CareerSelectDropdown from './CareerSelectDropdown.js';
|
||||
import MilestoneTimeline from './MilestoneTimeline.js';
|
||||
import ScenarioEditModal from './ScenarioEditModal.js';
|
||||
|
||||
import './CareerRoadmap.css';
|
||||
@ -634,6 +632,7 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
|
||||
annualFinancialAid: collegeData.annualFinancialAid,
|
||||
calculatedTuition: collegeData.calculatedTuition,
|
||||
extraPayment: collegeData.extraPayment,
|
||||
enrollmentDate: collegeProfile.enrollmentDate || null,
|
||||
inCollege: collegeData.inCollege,
|
||||
gradDate: collegeData.gradDate,
|
||||
programType: collegeData.programType,
|
||||
@ -653,6 +652,8 @@ export default function CareerRoadmap({ selectedCareer: initialCareer }) {
|
||||
randomRangeMax
|
||||
};
|
||||
|
||||
console.log('Merged profile to simulate =>', mergedProfile);
|
||||
|
||||
const { projectionData: pData, loanPaidOffMonth } =
|
||||
simulateFinancialProjection(mergedProfile);
|
||||
|
||||
|
@ -23,6 +23,7 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerProfileId
|
||||
annual_financial_aid = '',
|
||||
is_online = false,
|
||||
existing_college_debt = '',
|
||||
enrollment_date = '',
|
||||
expected_graduation = '',
|
||||
interest_rate = 5.5,
|
||||
loan_term = 10,
|
||||
@ -494,11 +495,24 @@ function CollegeOnboarding({ nextStep, prevStep, data, setData, careerProfileId
|
||||
/>
|
||||
</div>
|
||||
|
||||
{college_enrollment_status === 'prospective_student' && (
|
||||
<div className="space-y-1">
|
||||
<label className="block font-medium">Intended Start Date</label>
|
||||
<input
|
||||
type="date"
|
||||
name="enrollment_date"
|
||||
value={enrollment_date || ''}
|
||||
onChange={handleParentFieldChange}
|
||||
className="w-full border rounded p-2"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* If "currently_enrolled" show Hours Completed + Program Length */}
|
||||
{college_enrollment_status === 'currently_enrolled' && (
|
||||
<>
|
||||
<div className="space-y-1">
|
||||
<label className="block font-medium">Hours Completed</label>
|
||||
<label className="block font-medium">Completed Credit Hours (that count towards program completion)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="hours_completed"
|
||||
|
@ -113,7 +113,7 @@ const OnboardingContainer = () => {
|
||||
localStorage.removeItem('premiumOnboardingState');
|
||||
|
||||
// 5) Navigate away
|
||||
navigate('/milestone-tracker');
|
||||
navigate('/career-roadmap');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// Optionally show error to user
|
||||
|
@ -39,7 +39,7 @@ function ReviewPage({
|
||||
<h3 className="text-xl font-semibold">Career Info</h3>
|
||||
<div><strong>Career Name:</strong> {careerData.career_name || 'N/A'}</div>
|
||||
<div><strong>Currently Working:</strong> {careerData.currently_working || 'N/A'}</div>
|
||||
<div><strong>Enrollment Status:</strong> {careerData.college_enrollment_status || 'N/A'}</div>
|
||||
<div><strong>College enrollment Status:</strong> {careerData.college_enrollment_status || 'N/A'}</div>
|
||||
<div><strong>Status:</strong> {careerData.status || 'N/A'}</div>
|
||||
<div><strong>Start Date:</strong> {careerData.start_date || 'N/A'}</div>
|
||||
<div><strong>Projected End Date:</strong> {careerData.projected_end_date || 'N/A'}</div>
|
||||
@ -90,21 +90,21 @@ function ReviewPage({
|
||||
<div className="p-4 border rounded-md space-y-2">
|
||||
<h3 className="text-xl font-semibold">College Info</h3>
|
||||
|
||||
<div><strong>College Name</strong></div>
|
||||
<div><strong>Major</strong></div>
|
||||
<div><strong>Program Type</strong></div>
|
||||
<div><strong>Tuition (calculated)</strong></div>
|
||||
<div><strong>Program Length (years)</strong></div>
|
||||
<div><strong>Credit Hours Per Year</strong></div>
|
||||
<div><strong>Credit Hours Required</strong></div>
|
||||
<div><strong>Hours Completed</strong></div>
|
||||
<div><strong>Is In State?</strong></div>
|
||||
<div><strong>Loan Deferral Until Graduation?</strong></div>
|
||||
<div><strong>Annual Financial Aid</strong></div>
|
||||
<div><strong>Existing College Debt</strong></div>
|
||||
<div><strong>Extra Monthly Payment</strong></div>
|
||||
<div><strong>Expected Graduation</strong></div>
|
||||
<div><strong>Expected Salary</strong></div>
|
||||
<div><strong>College Name:</strong> {formatNum(collegeData.selected_school)}</div>
|
||||
<div><strong>Major</strong> {formatNum(collegeData.selected_program)}</div>
|
||||
<div><strong>Program Type</strong> {formatNum(collegeData.program_type)}</div>
|
||||
<div><strong>Yearly Tuition</strong> {formatNum(collegeData.tuition)}</div>
|
||||
<div><strong>Program Length (years)</strong> {formatNum(collegeData.program_length)}</div>
|
||||
<div><strong>Credit Hours Per Year</strong> {formatNum(collegeData.credit_hours_per_year)}</div>
|
||||
<div><strong>Credit Hours Required</strong> {formatNum(collegeData.credit_hours_required)}</div>
|
||||
<div><strong>Hours Completed</strong> {formatNum(collegeData.hours_completed)}</div>
|
||||
<div><strong>Is In State?</strong> {formatNum(collegeData.is_in_state)}</div>
|
||||
<div><strong>Loan Deferral Until Graduation?</strong> {formatNum(collegeData.loan_deferral_until_graduation)}</div>
|
||||
<div><strong>Annual Financial Aid</strong> {formatNum(collegeData.annual_financial_aid)}</div>
|
||||
<div><strong>Existing College Debt</strong> {formatNum(collegeData.existing_college_debt)}</div>
|
||||
<div><strong>Extra Monthly Payment</strong> {formatNum(collegeData.extra_payment)}</div>
|
||||
<div><strong>Expected Graduation</strong> {formatNum(collegeData.expected_graduation)}</div>
|
||||
<div><strong>Expected Salary</strong> {formatNum(collegeData.expected_salary)}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
|
||||
import authFetch from '../utils/authFetch.js';
|
||||
import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js';
|
||||
import { Button } from './ui/button.js';
|
||||
import parseFloatOrZero from '../utils/ParseFloatorZero.js';
|
||||
|
||||
// Data paths
|
||||
const CIP_URL = '/cip_institution_mapping_new.json';
|
||||
@ -137,17 +138,18 @@ export default function ScenarioEditModal({
|
||||
college_enrollment_status: s.college_enrollment_status || 'not_enrolled',
|
||||
currently_working: s.currently_working || 'no',
|
||||
|
||||
planned_monthly_expenses: s.planned_monthly_expenses ?? '',
|
||||
planned_monthly_debt_payments: s.planned_monthly_debt_payments ?? '',
|
||||
planned_monthly_expenses: s.planned_monthly_expenses ?? null,
|
||||
planned_monthly_debt_payments: s.planned_monthly_debt_payments ?? null,
|
||||
planned_monthly_retirement_contribution:
|
||||
s.planned_monthly_retirement_contribution ?? '',
|
||||
s.planned_monthly_retirement_contribution ?? null,
|
||||
planned_monthly_emergency_contribution:
|
||||
s.planned_monthly_emergency_contribution ?? '',
|
||||
planned_surplus_emergency_pct: s.planned_surplus_emergency_pct ?? '',
|
||||
planned_surplus_retirement_pct: s.planned_surplus_retirement_pct ?? '',
|
||||
planned_additional_income: s.planned_additional_income ?? '',
|
||||
s.planned_monthly_emergency_contribution ?? null,
|
||||
planned_surplus_emergency_pct: s.planned_surplus_emergency_pct ?? null,
|
||||
planned_surplus_retirement_pct: s.planned_surplus_retirement_pct ?? null,
|
||||
planned_additional_income: s.planned_additional_income ?? null,
|
||||
|
||||
// college portion
|
||||
college_profile_id: c.id || null,
|
||||
selected_school: c.selected_school || '',
|
||||
selected_program: c.selected_program || '',
|
||||
program_type: c.program_type || '',
|
||||
@ -169,7 +171,8 @@ export default function ScenarioEditModal({
|
||||
hours_completed: c.hours_completed ?? '',
|
||||
program_length: c.program_length ?? '',
|
||||
credit_hours_required: c.credit_hours_required ?? '',
|
||||
expected_graduation: c.expected_graduation || '',
|
||||
enrollment_date: c.enrollment_date ? c.enrollment_date.substring(0, 10): '',
|
||||
expected_graduation: c.expected_graduation ? c.expected_graduation.substring(0, 10): '',
|
||||
expected_salary: c.expected_salary ?? ''
|
||||
});
|
||||
|
||||
@ -415,7 +418,7 @@ export default function ScenarioEditModal({
|
||||
annualFinancialAid: collegeRow.annual_financial_aid || 0,
|
||||
calculatedTuition: collegeRow.tuition || 0,
|
||||
extraPayment: collegeRow.extra_payment || 0,
|
||||
|
||||
enrollmentDate: collegeRow.enrollment_date || null,
|
||||
gradDate: collegeRow.expected_graduation || null,
|
||||
programType: collegeRow.program_type || null,
|
||||
hoursCompleted: collegeRow.hours_completed || 0,
|
||||
@ -467,6 +470,12 @@ export default function ScenarioEditModal({
|
||||
|
||||
// Build scenario payload
|
||||
const scenarioPayload = {};
|
||||
|
||||
// If scenario already has an id, include it:
|
||||
if (scenario?.id) {
|
||||
scenarioPayload.id = scenario.id;
|
||||
}
|
||||
|
||||
scenarioPayload.college_enrollment_status = finalCollegeStatus;
|
||||
scenarioPayload.currently_working = formData.currently_working || 'no';
|
||||
|
||||
@ -486,8 +495,7 @@ export default function ScenarioEditModal({
|
||||
formData.projected_end_date &&
|
||||
formData.projected_end_date.trim() !== ''
|
||||
) {
|
||||
scenarioPayload.projected_end_date =
|
||||
formData.projected_end_date.trim();
|
||||
scenarioPayload.projected_end_date = formData.projected_end_date.trim();
|
||||
}
|
||||
|
||||
const pme = parseNumberIfGiven(formData.planned_monthly_expenses);
|
||||
@ -537,6 +545,7 @@ export default function ScenarioEditModal({
|
||||
|
||||
// 2) Build college payload
|
||||
const collegePayload = {
|
||||
id: formData.college_profile_id || null,
|
||||
career_profile_id: updatedScenarioId,
|
||||
college_enrollment_status: finalCollegeStatus,
|
||||
is_in_state: formData.is_in_state ? 1 : 0,
|
||||
@ -556,12 +565,16 @@ export default function ScenarioEditModal({
|
||||
const acCal = parseStringIfGiven(formData.academic_calendar);
|
||||
if (acCal !== undefined) collegePayload.academic_calendar = acCal;
|
||||
|
||||
if (
|
||||
formData.expected_graduation &&
|
||||
formData.expected_graduation.trim() !== ''
|
||||
) {
|
||||
collegePayload.expected_graduation =
|
||||
formData.expected_graduation.trim();
|
||||
if (formData.expected_graduation && formData.expected_graduation.trim() !== '') {
|
||||
collegePayload.expected_graduation = formData.expected_graduation
|
||||
.trim()
|
||||
.substring(0, 10);
|
||||
}
|
||||
|
||||
if (formData.enrollment_date && formData.enrollment_date.trim() !== '') {
|
||||
collegePayload.enrollment_date = formData.enrollment_date
|
||||
.trim()
|
||||
.substring(0, 10);
|
||||
}
|
||||
|
||||
const afa = parseNumberIfGiven(formData.annual_financial_aid);
|
||||
@ -607,20 +620,23 @@ export default function ScenarioEditModal({
|
||||
}
|
||||
|
||||
// 3) Upsert or skip
|
||||
if (finalCollegeStatus === 'currently_enrolled' ||
|
||||
finalCollegeStatus === 'prospective_student')
|
||||
{
|
||||
if (
|
||||
finalCollegeStatus === 'currently_enrolled' ||
|
||||
finalCollegeStatus === 'prospective_student'
|
||||
) {
|
||||
const colRes = await authFetch('/api/premium/college-profile', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(collegePayload),
|
||||
body: JSON.stringify(collegePayload)
|
||||
});
|
||||
if (!colRes.ok) {
|
||||
const msg2 = await colRes.text();
|
||||
throw new Error(`College upsert failed: ${msg2}`);
|
||||
}
|
||||
} else {
|
||||
console.log('Skipping college-profile upsert in EditScenarioModal because user not enrolled');
|
||||
console.log(
|
||||
'Skipping college-profile upsert in EditScenarioModal because user not enrolled'
|
||||
);
|
||||
// Optionally: if you want to delete an existing college profile:
|
||||
// await authFetch(`/api/premium/college-profile/delete/${updatedScenarioId}`, { method: 'DELETE' });
|
||||
}
|
||||
@ -628,9 +644,7 @@ export default function ScenarioEditModal({
|
||||
// 4) Re-fetch scenario, college, financial => aggregator => simulate
|
||||
const [scenResp2, colResp2, finResp] = await Promise.all([
|
||||
authFetch(`/api/premium/career-profile/${updatedScenarioId}`),
|
||||
authFetch(
|
||||
`/api/premium/college-profile?careerProfileId=${updatedScenarioId}`
|
||||
),
|
||||
authFetch(`/api/premium/college-profile?careerProfileId=${updatedScenarioId}`),
|
||||
authFetch(`/api/premium/financial-profile`)
|
||||
]);
|
||||
if (!scenResp2.ok || !colResp2.ok || !finResp.ok) {
|
||||
@ -643,24 +657,139 @@ export default function ScenarioEditModal({
|
||||
return;
|
||||
}
|
||||
|
||||
const [finalScenarioRow, finalCollegeRaw, finalFinancial] =
|
||||
await Promise.all([scenResp2.json(), colResp2.json(), finResp.json()]);
|
||||
const [finalScenarioRow, finalCollegeRaw, finalFinancial] = await Promise.all([
|
||||
scenResp2.json(),
|
||||
colResp2.json(),
|
||||
finResp.json()
|
||||
]);
|
||||
|
||||
let finalCollegeRow = Array.isArray(finalCollegeRaw)
|
||||
? finalCollegeRaw[0] || {}
|
||||
: finalCollegeRaw;
|
||||
|
||||
// 5) Simulate
|
||||
// -------------------------------------------
|
||||
// 5) Before simulate: parse numeric fields
|
||||
// to avoid .toFixed errors
|
||||
// -------------------------------------------
|
||||
// scenario planned_ fields
|
||||
if (finalScenarioRow.planned_monthly_expenses != null) {
|
||||
finalScenarioRow.planned_monthly_expenses = parseFloatOrZero(
|
||||
finalScenarioRow.planned_monthly_expenses,
|
||||
null
|
||||
);
|
||||
}
|
||||
if (finalScenarioRow.planned_monthly_debt_payments != null) {
|
||||
finalScenarioRow.planned_monthly_debt_payments = parseFloatOrZero(
|
||||
finalScenarioRow.planned_monthly_debt_payments,
|
||||
null
|
||||
);
|
||||
}
|
||||
if (finalScenarioRow.planned_monthly_retirement_contribution != null) {
|
||||
finalScenarioRow.planned_monthly_retirement_contribution = parseFloatOrZero(
|
||||
finalScenarioRow.planned_monthly_retirement_contribution,
|
||||
null
|
||||
);
|
||||
}
|
||||
if (finalScenarioRow.planned_monthly_emergency_contribution != null) {
|
||||
finalScenarioRow.planned_monthly_emergency_contribution = parseFloatOrZero(
|
||||
finalScenarioRow.planned_monthly_emergency_contribution,
|
||||
null
|
||||
);
|
||||
}
|
||||
if (finalScenarioRow.planned_surplus_emergency_pct != null) {
|
||||
finalScenarioRow.planned_surplus_emergency_pct = parseFloatOrZero(
|
||||
finalScenarioRow.planned_surplus_emergency_pct,
|
||||
null
|
||||
);
|
||||
}
|
||||
if (finalScenarioRow.planned_surplus_retirement_pct != null) {
|
||||
finalScenarioRow.planned_surplus_retirement_pct = parseFloatOrZero(
|
||||
finalScenarioRow.planned_surplus_retirement_pct,
|
||||
null
|
||||
);
|
||||
}
|
||||
if (finalScenarioRow.planned_additional_income != null) {
|
||||
finalScenarioRow.planned_additional_income = parseFloatOrZero(
|
||||
finalScenarioRow.planned_additional_income,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
// college numeric fields (force all to numbers or 0)
|
||||
const numericFields = [
|
||||
'existing_college_debt',
|
||||
'extra_payment',
|
||||
'tuition',
|
||||
'tuition_paid',
|
||||
'interest_rate',
|
||||
'loan_term',
|
||||
'credit_hours_per_year',
|
||||
'hours_completed',
|
||||
'program_length',
|
||||
'expected_salary',
|
||||
'annual_financial_aid',
|
||||
'credit_hours_required'
|
||||
];
|
||||
for (const field of numericFields) {
|
||||
if (finalCollegeRow[field] != null) {
|
||||
finalCollegeRow[field] = parseFloatOrZero(finalCollegeRow[field], 0);
|
||||
} else {
|
||||
finalCollegeRow[field] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Also ensure all scenario/financial fields used in buildMergedUserProfile are numbers
|
||||
const scenarioNumericFields = [
|
||||
'planned_monthly_expenses',
|
||||
'planned_monthly_debt_payments',
|
||||
'planned_monthly_retirement_contribution',
|
||||
'planned_monthly_emergency_contribution',
|
||||
'planned_surplus_emergency_pct',
|
||||
'planned_surplus_retirement_pct',
|
||||
'planned_additional_income'
|
||||
];
|
||||
for (const field of scenarioNumericFields) {
|
||||
if (finalScenarioRow[field] != null) {
|
||||
finalScenarioRow[field] = parseFloatOrZero(finalScenarioRow[field], 0);
|
||||
} else {
|
||||
finalScenarioRow[field] = 0;
|
||||
}
|
||||
}
|
||||
if (finalFinancial) {
|
||||
const financialNumericFields = [
|
||||
'current_salary',
|
||||
'monthly_expenses',
|
||||
'monthly_debt_payments',
|
||||
'additional_income',
|
||||
'emergency_fund',
|
||||
'retirement_savings',
|
||||
'retirement_contribution',
|
||||
'emergency_contribution',
|
||||
'extra_cash_emergency_pct',
|
||||
'extra_cash_retirement_pct'
|
||||
];
|
||||
for (const field of financialNumericFields) {
|
||||
if (finalFinancial[field] != null) {
|
||||
finalFinancial[field] = parseFloatOrZero(finalFinancial[field], 0);
|
||||
} else {
|
||||
finalFinancial[field] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6) Now simulate
|
||||
const userProfile = buildMergedUserProfile(
|
||||
finalScenarioRow,
|
||||
finalCollegeRow,
|
||||
finalFinancial
|
||||
);
|
||||
console.log('UserProfile from Modal:', userProfile);
|
||||
|
||||
const results = simulateFinancialProjection(userProfile);
|
||||
setProjectionData(results.projectionData);
|
||||
setLoanPayoffMonth(results.loanPaidOffMonth);
|
||||
|
||||
// 6) Close or reload
|
||||
// 7) Close or reload
|
||||
onClose();
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
@ -797,7 +926,7 @@ export default function ScenarioEditModal({
|
||||
{/* College Enrollment Status */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
College Enrollment Status (scenario row)
|
||||
College Enrollment Status
|
||||
</label>
|
||||
<select
|
||||
name="college_enrollment_status"
|
||||
@ -947,7 +1076,7 @@ export default function ScenarioEditModal({
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_online"
|
||||
checked={!!formData.is_online}
|
||||
checked={!!formData.is_in_online}
|
||||
onChange={handleFormChange}
|
||||
className="mr-1"
|
||||
/>
|
||||
@ -1169,6 +1298,20 @@ export default function ScenarioEditModal({
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Enrollment Date */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Enrollment Date
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
name="enrollment_date"
|
||||
value={formData.enrollment_date || ''}
|
||||
onChange={handleFormChange}
|
||||
className="border border-gray-300 rounded p-2 w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Expected Graduation */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
|
@ -109,6 +109,7 @@ export function simulateFinancialProjection(userProfile) {
|
||||
hoursCompleted = 0,
|
||||
creditHoursPerYear,
|
||||
calculatedTuition,
|
||||
enrollmentDate,
|
||||
gradDate,
|
||||
startDate,
|
||||
academicCalendar = 'monthly',
|
||||
@ -174,6 +175,7 @@ function getMonthlyInterestRate() {
|
||||
/***************************************************
|
||||
* 2) CLAMP THE SCENARIO START TO MONTH-BEGIN
|
||||
***************************************************/
|
||||
|
||||
const scenarioStartClamped = moment(startDate || new Date()).startOf('month');
|
||||
|
||||
/***************************************************
|
||||
@ -267,24 +269,28 @@ function getMonthlyInterestRate() {
|
||||
// If there's a gradDate, let's see if we pass it:
|
||||
const graduationDateObj = gradDate ? moment(gradDate).startOf('month') : null;
|
||||
|
||||
const enrollmentDateObj = enrollmentDate
|
||||
? moment(enrollmentDate).startOf('month')
|
||||
: scenarioStartClamped.clone(); // fallback
|
||||
|
||||
|
||||
/***************************************************
|
||||
* 7) THE MONTHLY LOOP
|
||||
***************************************************/
|
||||
|
||||
for (let monthIndex = 0; monthIndex < maxMonths; monthIndex++) {
|
||||
const currentSimDate = scenarioStartClamped.clone().add(monthIndex, 'months');
|
||||
|
||||
// Check if loan is fully paid
|
||||
if (loanBalance <= 0 && !loanPaidOffMonth) {
|
||||
loanPaidOffMonth = currentSimDate.format('YYYY-MM');
|
||||
}
|
||||
|
||||
// Are we still in college for this month?
|
||||
// figure out if we are in the college window
|
||||
let stillInCollege = false;
|
||||
if (inCollege) {
|
||||
if (graduationDateObj) {
|
||||
stillInCollege = currentSimDate.isBefore(graduationDateObj, 'month');
|
||||
} else {
|
||||
stillInCollege = (monthIndex < totalAcademicMonths);
|
||||
if (inCollege && enrollmentDateObj && graduationDateObj) {
|
||||
stillInCollege = currentSimDate.isSameOrAfter(enrollmentDateObj)
|
||||
&& currentSimDate.isBefore(graduationDateObj);
|
||||
|
||||
if (inCollege && gradDate) {
|
||||
stillInCollege =
|
||||
currentSimDate.isSameOrAfter(enrollmentDateObj) &&
|
||||
currentSimDate.isBefore(graduationDateObj);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user