From 8917e4890ecf3f92e4a00eff26b22dfe3b79050c Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 2 May 2025 15:21:11 +0000 Subject: [PATCH] Updated EditScenarioModal UI --- src/components/ScenarioEditModal.js | 824 ++++++++++++++++------------ user_profile.db | Bin 106496 -> 106496 bytes 2 files changed, 465 insertions(+), 359 deletions(-) diff --git a/src/components/ScenarioEditModal.js b/src/components/ScenarioEditModal.js index 074d87a..004e188 100644 --- a/src/components/ScenarioEditModal.js +++ b/src/components/ScenarioEditModal.js @@ -1,9 +1,9 @@ -// src/components/ScenarioEditModal.js import React, { useState, useEffect, useRef } from 'react'; import authFetch from '../utils/authFetch.js'; -import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js'; // Or wherever your simulator is +import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js'; +import { Button } from './ui/button.js'; -// JSON/CSV data paths +// Data paths const CIP_URL = '/cip_institution_mapping_new.json'; const IPEDS_URL = '/ic2023_ay.csv'; const CAREER_CLUSTERS_URL = '/career_clusters.json'; @@ -155,8 +155,7 @@ export default function ScenarioEditModal({ is_in_state: !!c.is_in_state, is_in_district: !!c.is_in_district, - is_online: !!c.is_online, - // This is the college row's enrollment status + is_online: !!c.is_in_online, college_enrollment_status_db: c.college_enrollment_status || 'not_enrolled', annual_financial_aid: c.annual_financial_aid ?? '', @@ -174,23 +173,22 @@ export default function ScenarioEditModal({ expected_salary: c.expected_salary ?? '' }); + // Manual / auto tuition if (c.tuition != null && c.tuition !== 0) { setManualTuition(String(c.tuition)); - setAutoTuition(''); // So user sees the DB value, not auto + setAutoTuition(''); } else { - // Else we do our auto-calc, or just set 0 if you prefer - const autoCalc = 12000; // or your IPEDS-based logic + const autoCalc = 12000; setAutoTuition(String(autoCalc)); setManualTuition(''); } + // Manual / auto program length if (c.program_length != null && c.program_length !== 0) { - // DB has real program length setManualProgLength(String(c.program_length)); - setAutoProgLength(''); // so we know user is seeing DB + setAutoProgLength(''); } else { - // No real DB value => show auto - const autoLen = 2.0; // or your own logic + const autoLen = 2.0; setAutoProgLength(String(autoLen)); setManualProgLength(''); } @@ -199,10 +197,11 @@ export default function ScenarioEditModal({ }, [show, scenario, collegeProfile]); /********************************************************* - * 8) Auto-calc tuition + program length => placeholders + * 8) Auto-calc placeholders (stubbed out) *********************************************************/ useEffect(() => { if (!show) return; + // IPEDS-based logic or other auto-calculation }, [ show, formData.selected_school, @@ -216,6 +215,7 @@ export default function ScenarioEditModal({ useEffect(() => { if (!show) return; + // Possibly recalc program length }, [ show, formData.program_type, @@ -242,8 +242,6 @@ export default function ScenarioEditModal({ /********************************************************* * 9.5) Program Type from CIP - * => Populate availableProgramTypes by matching CIP rows for - * (selected_school, selected_program) => (CREDDESC). *********************************************************/ useEffect(() => { if (!show) return; @@ -282,6 +280,7 @@ export default function ScenarioEditModal({ setFormData((prev) => ({ ...prev, career_name: val })); } } + function handleSelectCareer(title) { setCareerSearchInput(title); setFormData((prev) => ({ ...prev, career_name: title })); @@ -355,28 +354,37 @@ export default function ScenarioEditModal({ function handleManualTuitionChange(e) { setManualTuition(e.target.value); } + function handleManualProgLengthChange(e) { setManualProgLength(e.target.value); } /********************************************************* - * 11) After saving, we want to re-fetch scenario & college - * and then pass inCollege to the simulator + * 11) Simulation aggregator *********************************************************/ const [projectionData, setProjectionData] = useState([]); const [loanPayoffMonth, setLoanPayoffMonth] = useState(null); - // aggregator function buildMergedUserProfile(scenarioRow, collegeRow, financialData) { - // Make sure we read the final updated scenario row's enrollment const enrollment = scenarioRow.college_enrollment_status; - const inCollege = (enrollment === 'currently_enrolled' || enrollment === 'prospective_student'); + const inCollege = + enrollment === 'currently_enrolled' || + enrollment === 'prospective_student'; return { currentSalary: financialData.current_salary || 0, - monthlyExpenses: scenarioRow.planned_monthly_expenses ?? financialData.monthly_expenses ?? 0, - monthlyDebtPayments: scenarioRow.planned_monthly_debt_payments ?? financialData.monthly_debt_payments ?? 0, - partTimeIncome: scenarioRow.planned_additional_income ?? financialData.additional_income ?? 0, + monthlyExpenses: + scenarioRow.planned_monthly_expenses ?? + financialData.monthly_expenses ?? + 0, + monthlyDebtPayments: + scenarioRow.planned_monthly_debt_payments ?? + financialData.monthly_debt_payments ?? + 0, + partTimeIncome: + scenarioRow.planned_additional_income ?? + financialData.additional_income ?? + 0, emergencySavings: financialData.emergency_fund ?? 0, retirementSavings: financialData.retirement_savings ?? 0, @@ -413,7 +421,8 @@ export default function ScenarioEditModal({ hoursCompleted: collegeRow.hours_completed || 0, creditHoursPerYear: collegeRow.credit_hours_per_year || 0, programLength: collegeRow.program_length || 0, - expectedSalary: collegeRow.expected_salary || financialData.current_salary || 0, + expectedSalary: + collegeRow.expected_salary || financialData.current_salary || 0, startDate: scenarioRow.start_date || new Date().toISOString(), simulationYears: 20, @@ -421,41 +430,31 @@ export default function ScenarioEditModal({ }; } - /********************************************************* - * 12) handleSave => upsert scenario & college - * => Then re-fetch scenario, college, financial => aggregator => simulate + * 12) handleSave => upsert scenario & college => re-fetch => simulate *********************************************************/ async function handleSave() { try { - // --- Helper functions for partial update: --- function parseNumberIfGiven(val) { - if (val == null) return undefined; // skip if null/undefined - - // Convert to string before trimming + if (val == null) return undefined; const valStr = String(val).trim(); - if (valStr === '') { - return undefined; // skip if empty - } - + if (valStr === '') return undefined; const num = Number(valStr); return isNaN(num) ? undefined : num; } - + function parseStringIfGiven(val) { if (val == null) return undefined; const trimmed = String(val).trim(); return trimmed === '' ? undefined : trimmed; } - - // Let’s handle your manualTuition / manualProgLength logic too + const chosenTuitionVal = manualTuition.trim() !== '' ? Number(manualTuition) : undefined; const chosenProgLengthVal = manualProgLength.trim() !== '' ? Number(manualProgLength) : undefined; - - // The user sets scenario.college_enrollment_status => "currently_enrolled" - // We'll explicitly set the college row's status to match + + // Sync scenario's enrollment status with college row let finalCollegeStatus = formData.college_enrollment_status_db; if ( formData.college_enrollment_status === 'currently_enrolled' || @@ -465,59 +464,65 @@ export default function ScenarioEditModal({ } else { finalCollegeStatus = 'not_enrolled'; } - - // --- Build scenarioPayload with partial updates --- + + // Build scenario payload const scenarioPayload = {}; - - // (A) Some fields you always want to set: scenarioPayload.college_enrollment_status = finalCollegeStatus; scenarioPayload.currently_working = formData.currently_working || 'no'; - - // (B) scenario_title, career_name, status => only if typed + const scenarioTitle = parseStringIfGiven(formData.scenario_title); - if (scenarioTitle !== undefined) { - scenarioPayload.scenario_title = scenarioTitle; - } + if (scenarioTitle !== undefined) scenarioPayload.scenario_title = scenarioTitle; + const careerName = parseStringIfGiven(formData.career_name); - if (careerName !== undefined) { - scenarioPayload.career_name = careerName; - } + if (careerName !== undefined) scenarioPayload.career_name = careerName; + const scenarioStatus = parseStringIfGiven(formData.status); - if (scenarioStatus !== undefined) { - scenarioPayload.status = scenarioStatus; - } - - // (C) Dates + if (scenarioStatus !== undefined) scenarioPayload.status = scenarioStatus; + if (formData.start_date && formData.start_date.trim() !== '') { scenarioPayload.start_date = formData.start_date.trim(); } - if (formData.projected_end_date && formData.projected_end_date.trim() !== '') { - scenarioPayload.projected_end_date = formData.projected_end_date.trim(); + if ( + formData.projected_end_date && + formData.projected_end_date.trim() !== '' + ) { + scenarioPayload.projected_end_date = + formData.projected_end_date.trim(); } - - // (D) Numeric overrides + const pme = parseNumberIfGiven(formData.planned_monthly_expenses); if (pme !== undefined) scenarioPayload.planned_monthly_expenses = pme; - + const pmdp = parseNumberIfGiven(formData.planned_monthly_debt_payments); - if (pmdp !== undefined) scenarioPayload.planned_monthly_debt_payments = pmdp; - - const pmrc = parseNumberIfGiven(formData.planned_monthly_retirement_contribution); - if (pmrc !== undefined) scenarioPayload.planned_monthly_retirement_contribution = pmrc; - - const pmec = parseNumberIfGiven(formData.planned_monthly_emergency_contribution); - if (pmec !== undefined) scenarioPayload.planned_monthly_emergency_contribution = pmec; - + if (pmdp !== undefined) + scenarioPayload.planned_monthly_debt_payments = pmdp; + + const pmrc = parseNumberIfGiven( + formData.planned_monthly_retirement_contribution + ); + if (pmrc !== undefined) { + scenarioPayload.planned_monthly_retirement_contribution = pmrc; + } + + const pmec = parseNumberIfGiven( + formData.planned_monthly_emergency_contribution + ); + if (pmec !== undefined) { + scenarioPayload.planned_monthly_emergency_contribution = pmec; + } + const psep = parseNumberIfGiven(formData.planned_surplus_emergency_pct); - if (psep !== undefined) scenarioPayload.planned_surplus_emergency_pct = psep; - + if (psep !== undefined) + scenarioPayload.planned_surplus_emergency_pct = psep; + const psrp = parseNumberIfGiven(formData.planned_surplus_retirement_pct); - if (psrp !== undefined) scenarioPayload.planned_surplus_retirement_pct = psrp; - + if (psrp !== undefined) + scenarioPayload.planned_surplus_retirement_pct = psrp; + const pai = parseNumberIfGiven(formData.planned_additional_income); if (pai !== undefined) scenarioPayload.planned_additional_income = pai; - - // 1) Upsert scenario row + + // 1) Upsert scenario const scenRes = await authFetch('/api/premium/career-profile', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -529,81 +534,79 @@ export default function ScenarioEditModal({ } const scenData = await scenRes.json(); const updatedScenarioId = scenData.career_path_id; - - // --- Build collegePayload with partial updates --- + + // 2) Build college payload const collegePayload = { career_path_id: updatedScenarioId, - // We always sync these booleans or statuses college_enrollment_status: finalCollegeStatus, is_in_state: formData.is_in_state ? 1 : 0, is_in_district: formData.is_in_district ? 1 : 0, - is_in_online: formData.is_in_online ? 1 : 0 + is_in_online: formData.is_online ? 1 : 0 }; - - // Strings + const selSchool = parseStringIfGiven(formData.selected_school); if (selSchool !== undefined) collegePayload.selected_school = selSchool; - + const selProg = parseStringIfGiven(formData.selected_program); if (selProg !== undefined) collegePayload.selected_program = selProg; - + const progType = parseStringIfGiven(formData.program_type); if (progType !== undefined) collegePayload.program_type = progType; - + const acCal = parseStringIfGiven(formData.academic_calendar); if (acCal !== undefined) collegePayload.academic_calendar = acCal; - - // If user typed a date for expected_graduation - 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(); } - - // Numeric fields + const afa = parseNumberIfGiven(formData.annual_financial_aid); if (afa !== undefined) collegePayload.annual_financial_aid = afa; - + const ecd = parseNumberIfGiven(formData.existing_college_debt); if (ecd !== undefined) collegePayload.existing_college_debt = ecd; - + const tp = parseNumberIfGiven(formData.tuition_paid); if (tp !== undefined) collegePayload.tuition_paid = tp; - - // Chosen tuition if user typed manualTuition + if (chosenTuitionVal !== undefined && !isNaN(chosenTuitionVal)) { collegePayload.tuition = chosenTuitionVal; } - // chosenProgLength if user typed manualProgLength + if (chosenProgLengthVal !== undefined && !isNaN(chosenProgLengthVal)) { collegePayload.program_length = chosenProgLengthVal; } - + const ltg = parseNumberIfGiven(formData.loan_term); if (ltg !== undefined) collegePayload.loan_term = ltg; - + const ir = parseNumberIfGiven(formData.interest_rate); if (ir !== undefined) collegePayload.interest_rate = ir; - + const ep = parseNumberIfGiven(formData.extra_payment); if (ep !== undefined) collegePayload.extra_payment = ep; - + const chpy = parseNumberIfGiven(formData.credit_hours_per_year); if (chpy !== undefined) collegePayload.credit_hours_per_year = chpy; - + const hc = parseNumberIfGiven(formData.hours_completed); if (hc !== undefined) collegePayload.hours_completed = hc; - + const chr = parseNumberIfGiven(formData.credit_hours_required); if (chr !== undefined) collegePayload.credit_hours_required = chr; - + const esal = parseNumberIfGiven(formData.expected_salary); if (esal !== undefined) collegePayload.expected_salary = esal; - - // Defer Loan + if (formData.loan_deferral_until_graduation) { collegePayload.loan_deferral_until_graduation = 1; } - - // 2) Upsert the college row + + // 3) Upsert college const colRes = await authFetch('/api/premium/college-profile', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -613,11 +616,13 @@ export default function ScenarioEditModal({ const msg2 = await colRes.text(); throw new Error(`College upsert failed: ${msg2}`); } - - // 3) Re-fetch scenario, college, & financial => aggregator => simulate + + // 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?careerPathId=${updatedScenarioId}`), + authFetch( + `/api/premium/college-profile?careerPathId=${updatedScenarioId}` + ), authFetch(`/api/premium/financial-profile`) ]); if (!scenResp2.ok || !colResp2.ok || !finResp.ok) { @@ -626,21 +631,18 @@ export default function ScenarioEditModal({ collegeStatus: colResp2.status, financialStatus: finResp.status }); - onClose(); // or show an error + onClose(); 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; - - // 4) Build the aggregator and run the simulation + + // 5) Simulate const userProfile = buildMergedUserProfile( finalScenarioRow, finalCollegeRow, @@ -649,8 +651,8 @@ export default function ScenarioEditModal({ const results = simulateFinancialProjection(userProfile); setProjectionData(results.projectionData); setLoanPayoffMonth(results.loanPaidOffMonth); - - // 5) Now close the modal automatically + + // 6) Close or reload onClose(); window.location.reload(); } catch (err) { @@ -658,8 +660,6 @@ export default function ScenarioEditModal({ alert(err.message || 'Failed to save scenario data.'); } } - - /********************************************************* * 13) Render @@ -673,283 +673,317 @@ export default function ScenarioEditModal({ return (
-

+

Edit Scenario: {scenario?.scenario_title || scenario?.career_name || '(untitled)'}

- {/* -- SCENARIO FIELDS -- */} -

Scenario & Career

- - + {/* SECTION: Scenario & Career */} +

Scenario & Career

- - - {careerMatches.length > 0 && ( -
    + + +
+ + {/* Career Search */} +
+ + + {careerMatches.length > 0 && ( +
    + {careerMatches.map((c, idx) => ( +
  • handleSelectCareer(c)} + > + {c} +
  • + ))} +
+ )} +

+ Current Career: {formData.career_name || '(none)'} +

+
+ + {/* Status */} +
+ + +
- - - - - - - - - - - - - - - - {/* -- SCENARIO FINANCIAL OVERRIDES -- */} -

Scenario Financial Overwrites

-
+ {/* Dates */} +
- + + +
+
+ + +
+
+ + {/* College Enrollment Status */} +
+ + +
+ + {/* Currently Working */} +
+ + +
+ + {/* SECTION: Scenario Financial Overrides */} +

Scenario Financial Overrides

+
+
+
- +
- +
- +
- +
- +
- +
- {/* -- COLLEGE PROFILE FIELDS -- */} -

College Profile

- {(formData.college_enrollment_status === 'currently_enrolled' - || formData.college_enrollment_status === 'prospective_student' - ) ? ( + {/* SECTION: College Profile */} +

College Profile

+ {(formData.college_enrollment_status === 'currently_enrolled' || + formData.college_enrollment_status === 'prospective_student') ? ( <> -
-