diff --git a/backend/server3.js b/backend/server3.js index 25fe759..0beb851 100644 --- a/backend/server3.js +++ b/backend/server3.js @@ -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.' }); diff --git a/src/components/CareerRoadmap.js b/src/components/CareerRoadmap.js index 35aca43..e32d53b 100644 --- a/src/components/CareerRoadmap.js +++ b/src/components/CareerRoadmap.js @@ -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); diff --git a/src/components/PremiumOnboarding/CollegeOnboarding.js b/src/components/PremiumOnboarding/CollegeOnboarding.js index 97f2b59..820adf4 100644 --- a/src/components/PremiumOnboarding/CollegeOnboarding.js +++ b/src/components/PremiumOnboarding/CollegeOnboarding.js @@ -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 /> + {college_enrollment_status === 'prospective_student' && ( +
+ + +
+ )} + {/* If "currently_enrolled" show Hours Completed + Program Length */} {college_enrollment_status === 'currently_enrolled' && ( <>
- + { localStorage.removeItem('premiumOnboardingState'); // 5) Navigate away - navigate('/milestone-tracker'); + navigate('/career-roadmap'); } catch (err) { console.error(err); // Optionally show error to user diff --git a/src/components/PremiumOnboarding/ReviewPage.js b/src/components/PremiumOnboarding/ReviewPage.js index c39ad7d..5696934 100644 --- a/src/components/PremiumOnboarding/ReviewPage.js +++ b/src/components/PremiumOnboarding/ReviewPage.js @@ -39,7 +39,7 @@ function ReviewPage({

Career Info

Career Name: {careerData.career_name || 'N/A'}
Currently Working: {careerData.currently_working || 'N/A'}
-
Enrollment Status: {careerData.college_enrollment_status || 'N/A'}
+
College enrollment Status: {careerData.college_enrollment_status || 'N/A'}
Status: {careerData.status || 'N/A'}
Start Date: {careerData.start_date || 'N/A'}
Projected End Date: {careerData.projected_end_date || 'N/A'}
@@ -90,21 +90,21 @@ function ReviewPage({

College Info

-
College Name
-
Major
-
Program Type
-
Tuition (calculated)
-
Program Length (years)
-
Credit Hours Per Year
-
Credit Hours Required
-
Hours Completed
-
Is In State?
-
Loan Deferral Until Graduation?
-
Annual Financial Aid
-
Existing College Debt
-
Extra Monthly Payment
-
Expected Graduation
-
Expected Salary
+
College Name: {formatNum(collegeData.selected_school)}
+
Major {formatNum(collegeData.selected_program)}
+
Program Type {formatNum(collegeData.program_type)}
+
Yearly Tuition {formatNum(collegeData.tuition)}
+
Program Length (years) {formatNum(collegeData.program_length)}
+
Credit Hours Per Year {formatNum(collegeData.credit_hours_per_year)}
+
Credit Hours Required {formatNum(collegeData.credit_hours_required)}
+
Hours Completed {formatNum(collegeData.hours_completed)}
+
Is In State? {formatNum(collegeData.is_in_state)}
+
Loan Deferral Until Graduation? {formatNum(collegeData.loan_deferral_until_graduation)}
+
Annual Financial Aid {formatNum(collegeData.annual_financial_aid)}
+
Existing College Debt {formatNum(collegeData.existing_college_debt)}
+
Extra Monthly Payment {formatNum(collegeData.extra_payment)}
+
Expected Graduation {formatNum(collegeData.expected_graduation)}
+
Expected Salary {formatNum(collegeData.expected_salary)}
)} diff --git a/src/components/ScenarioEditModal.js b/src/components/ScenarioEditModal.js index 6d2f478..ced0d01 100644 --- a/src/components/ScenarioEditModal.js +++ b/src/components/ScenarioEditModal.js @@ -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,30 +620,31 @@ export default function ScenarioEditModal({ } // 3) Upsert or skip - 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), - }); - 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'); - // Optionally: if you want to delete an existing college profile: - // await authFetch(`/api/premium/college-profile/delete/${updatedScenarioId}`, { method: 'DELETE' }); + 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) + }); + 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' + ); + // 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 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 */}
+
+ {/* Expected Graduation */}