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