dev1/src/components/ScenarioEditModal.js

1162 lines
40 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

// src/components/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
// JSON/CSV data paths
const CIP_URL = '/cip_institution_mapping_new.json';
const IPEDS_URL = '/ic2023_ay.csv';
const CAREER_CLUSTERS_URL = '/career_clusters.json';
export default function ScenarioEditModal({
show,
onClose,
scenario,
collegeProfile
}) {
/*********************************************************
* 1) CIP / IPEDS data states
*********************************************************/
const [schoolData, setSchoolData] = useState([]);
const [icTuitionData, setIcTuitionData] = useState([]);
/*********************************************************
* 2) Suggestions & program types
*********************************************************/
const [schoolSuggestions, setSchoolSuggestions] = useState([]);
const [programSuggestions, setProgramSuggestions] = useState([]);
const [availableProgramTypes, setAvailableProgramTypes] = useState([]);
/*********************************************************
* 3) Manual vs auto for tuition & program length
*********************************************************/
const [manualTuition, setManualTuition] = useState('');
const [autoTuition, setAutoTuition] = useState(0);
const [manualProgLength, setManualProgLength] = useState('');
const [autoProgLength, setAutoProgLength] = useState('0.00');
/*********************************************************
* 4) Career auto-suggest
*********************************************************/
const [allCareers, setAllCareers] = useState([]);
const [careerSearchInput, setCareerSearchInput] = useState('');
const [careerMatches, setCareerMatches] = useState([]);
const careerDropdownRef = useRef(null);
/*********************************************************
* 5) Combined formData => scenario + college
*********************************************************/
const [formData, setFormData] = useState({});
/*********************************************************
* 6) On show => load CIP, IPEDS, CAREERS
*********************************************************/
useEffect(() => {
if (!show) return;
const loadCIP = async () => {
try {
const res = await fetch(CIP_URL);
const text = await res.text();
const lines = text.split('\n');
const parsed = lines
.map((line) => {
try {
return JSON.parse(line);
} catch {
return null;
}
})
.filter(Boolean);
setSchoolData(parsed);
} catch (err) {
console.error('Failed loading CIP data:', err);
}
};
const loadIPEDS = async () => {
try {
const res = await fetch(IPEDS_URL);
const text = await res.text();
const rows = text.split('\n').map((line) => line.split(','));
const headers = rows[0];
const dataRows = rows.slice(1).map((row) =>
Object.fromEntries(row.map((val, idx) => [headers[idx], val]))
);
setIcTuitionData(dataRows);
} catch (err) {
console.error('Failed loading IPEDS data:', err);
}
};
const loadCareers = async () => {
try {
const resp = await fetch(CAREER_CLUSTERS_URL);
if (!resp.ok) {
throw new Error(`Failed career_clusters fetch: ${resp.status}`);
}
const data = await resp.json();
const titlesSet = new Set();
for (const cluster of Object.keys(data)) {
for (const sub of Object.keys(data[cluster])) {
const arr = data[cluster][sub];
if (Array.isArray(arr)) {
arr.forEach((cObj) => {
if (cObj?.title) titlesSet.add(cObj.title);
});
}
}
}
setAllCareers([...titlesSet]);
} catch (err) {
console.error('Failed loading career_clusters:', err);
}
};
loadCIP();
loadIPEDS();
loadCareers();
}, [show]);
/*********************************************************
* 7) If scenario + collegeProfile => fill form
*********************************************************/
useEffect(() => {
if (!show || !scenario) return;
const s = scenario || {};
const c = collegeProfile || {};
setFormData({
// scenario portion
scenario_title: s.scenario_title || '',
career_name: s.career_name || '',
status: s.status || 'planned',
start_date: s.start_date || '',
projected_end_date: s.projected_end_date || '',
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_retirement_contribution:
s.planned_monthly_retirement_contribution ?? '',
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 ?? '',
// college portion
selected_school: c.selected_school || '',
selected_program: c.selected_program || '',
program_type: c.program_type || '',
academic_calendar: c.academic_calendar || 'monthly',
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
college_enrollment_status_db: c.college_enrollment_status || 'not_enrolled',
annual_financial_aid: c.annual_financial_aid ?? '',
existing_college_debt: c.existing_college_debt ?? '',
tuition_paid: c.tuition_paid ?? 0,
loan_deferral_until_graduation: !!c.loan_deferral_until_graduation,
loan_term: c.loan_term ?? 10,
interest_rate: c.interest_rate ?? 5,
extra_payment: c.extra_payment ?? 0,
credit_hours_per_year: c.credit_hours_per_year ?? '',
hours_completed: c.hours_completed ?? '',
program_length: c.program_length ?? '',
credit_hours_required: c.credit_hours_required ?? '',
expected_graduation: c.expected_graduation || '',
expected_salary: c.expected_salary ?? ''
});
if (c.tuition != null && c.tuition !== 0) {
setManualTuition(String(c.tuition));
setAutoTuition(''); // So user sees the DB value, not auto
} else {
// Else we do our auto-calc, or just set 0 if you prefer
const autoCalc = 12000; // or your IPEDS-based logic
setAutoTuition(String(autoCalc));
setManualTuition('');
}
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
} else {
// No real DB value => show auto
const autoLen = 2.0; // or your own logic
setAutoProgLength(String(autoLen));
setManualProgLength('');
}
setCareerSearchInput(s.career_name || '');
}, [show, scenario, collegeProfile]);
/*********************************************************
* 8) Auto-calc tuition + program length => placeholders
*********************************************************/
useEffect(() => {
if (!show) return;
}, [
show,
formData.selected_school,
formData.program_type,
formData.credit_hours_per_year,
formData.is_in_district,
formData.is_in_state,
schoolData,
icTuitionData
]);
useEffect(() => {
if (!show) return;
}, [
show,
formData.program_type,
formData.hours_completed,
formData.credit_hours_per_year,
formData.credit_hours_required
]);
/*********************************************************
* 9) Career auto-suggest
*********************************************************/
useEffect(() => {
if (!show) return;
if (!careerSearchInput.trim()) {
setCareerMatches([]);
return;
}
const lower = careerSearchInput.toLowerCase();
const partials = allCareers
.filter((title) => title.toLowerCase().includes(lower))
.slice(0, 15);
setCareerMatches(partials);
}, [show, careerSearchInput, allCareers]);
/*********************************************************
* 9.5) Program Type from CIP
* => Populate availableProgramTypes by matching CIP rows for
* (selected_school, selected_program) => (CREDDESC).
*********************************************************/
useEffect(() => {
if (!show) return;
if (!formData.selected_school || !formData.selected_program) {
setAvailableProgramTypes([]);
return;
}
const filtered = schoolData.filter(
(row) =>
row.INSTNM.toLowerCase() === formData.selected_school.toLowerCase() &&
row.CIPDESC === formData.selected_program
);
const possibleTypes = [...new Set(filtered.map((r) => r.CREDDESC))];
setAvailableProgramTypes(possibleTypes);
}, [
show,
formData.selected_school,
formData.selected_program,
schoolData
]);
/*********************************************************
* 10) Handlers
*********************************************************/
function handleFormChange(e) {
const { name, type, checked, value } = e.target;
let val = value;
if (type === 'checkbox') val = checked;
setFormData((prev) => ({ ...prev, [name]: val }));
}
function handleCareerInputChange(e) {
const val = e.target.value;
setCareerSearchInput(val);
if (allCareers.includes(val)) {
setFormData((prev) => ({ ...prev, career_name: val }));
}
}
function handleSelectCareer(title) {
setCareerSearchInput(title);
setFormData((prev) => ({ ...prev, career_name: title }));
setCareerMatches([]);
}
function handleSchoolChange(e) {
const val = e.target.value;
setFormData((prev) => ({
...prev,
selected_school: val,
selected_program: '',
program_type: '',
credit_hours_required: ''
}));
if (!val) {
setSchoolSuggestions([]);
setProgramSuggestions([]);
setAvailableProgramTypes([]);
return;
}
const filtered = schoolData.filter((s) =>
s.INSTNM.toLowerCase().includes(val.toLowerCase())
);
const unique = [...new Set(filtered.map((s) => s.INSTNM))];
setSchoolSuggestions(unique.slice(0, 10));
}
function handleSchoolSelect(sch) {
setFormData((prev) => ({
...prev,
selected_school: sch,
selected_program: '',
program_type: '',
credit_hours_required: ''
}));
setSchoolSuggestions([]);
}
function handleProgramChange(e) {
const val = e.target.value;
setFormData((prev) => ({ ...prev, selected_program: val }));
if (!val) {
setProgramSuggestions([]);
return;
}
const filtered = schoolData.filter(
(row) =>
row.INSTNM.toLowerCase() === formData.selected_school.toLowerCase() &&
row.CIPDESC.toLowerCase().includes(val.toLowerCase())
);
const unique = [...new Set(filtered.map((r) => r.CIPDESC))];
setProgramSuggestions(unique.slice(0, 10));
}
function handleProgramSelect(prog) {
setFormData((prev) => ({ ...prev, selected_program: prog }));
setProgramSuggestions([]);
}
function handleProgramTypeSelect(e) {
setFormData((prev) => ({
...prev,
program_type: e.target.value,
credit_hours_required: ''
}));
setManualProgLength('');
setAutoProgLength('0.00');
}
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
*********************************************************/
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');
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,
emergencySavings: financialData.emergency_fund ?? 0,
retirementSavings: financialData.retirement_savings ?? 0,
monthlyRetirementContribution:
scenarioRow.planned_monthly_retirement_contribution ??
financialData.retirement_contribution ??
0,
monthlyEmergencyContribution:
scenarioRow.planned_monthly_emergency_contribution ??
financialData.emergency_contribution ??
0,
surplusEmergencyAllocation:
scenarioRow.planned_surplus_emergency_pct ??
financialData.extra_cash_emergency_pct ??
50,
surplusRetirementAllocation:
scenarioRow.planned_surplus_retirement_pct ??
financialData.extra_cash_retirement_pct ??
50,
// college
inCollege,
studentLoanAmount: collegeRow.existing_college_debt || 0,
interestRate: collegeRow.interest_rate || 5,
loanTerm: collegeRow.loan_term || 10,
loanDeferralUntilGraduation: !!collegeRow.loan_deferral_until_graduation,
academicCalendar: collegeRow.academic_calendar || 'monthly',
annualFinancialAid: collegeRow.annual_financial_aid || 0,
calculatedTuition: collegeRow.tuition || 0,
extraPayment: collegeRow.extra_payment || 0,
gradDate: collegeRow.expected_graduation || null,
programType: collegeRow.program_type || null,
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,
startDate: scenarioRow.start_date || new Date().toISOString(),
simulationYears: 20,
milestoneImpacts: []
};
}
/*********************************************************
* 12) handleSave => upsert scenario & college
* => Then re-fetch scenario, college, financial => aggregator => 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
const valStr = String(val).trim();
if (valStr === '') {
return undefined; // skip if empty
}
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;
}
// Lets 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
let finalCollegeStatus = formData.college_enrollment_status_db;
if (
formData.college_enrollment_status === 'currently_enrolled' ||
formData.college_enrollment_status === 'prospective_student'
) {
finalCollegeStatus = formData.college_enrollment_status;
} else {
finalCollegeStatus = 'not_enrolled';
}
// --- Build scenarioPayload with partial updates ---
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;
}
const careerName = parseStringIfGiven(formData.career_name);
if (careerName !== undefined) {
scenarioPayload.career_name = careerName;
}
const scenarioStatus = parseStringIfGiven(formData.status);
if (scenarioStatus !== undefined) {
scenarioPayload.status = scenarioStatus;
}
// (C) Dates
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();
}
// (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;
const psep = parseNumberIfGiven(formData.planned_surplus_emergency_pct);
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;
const pai = parseNumberIfGiven(formData.planned_additional_income);
if (pai !== undefined) scenarioPayload.planned_additional_income = pai;
// 1) Upsert scenario row
const scenRes = await authFetch('/api/premium/career-profile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(scenarioPayload)
});
if (!scenRes.ok) {
const msg = await scenRes.text();
throw new Error(`Scenario upsert failed: ${msg}`);
}
const scenData = await scenRes.json();
const updatedScenarioId = scenData.career_path_id;
// --- Build collegePayload with partial updates ---
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
};
// 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();
}
// 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
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}`);
}
// 3) 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/financial-profile`)
]);
if (!scenResp2.ok || !colResp2.ok || !finResp.ok) {
console.error('One re-fetch failed after upsert.', {
scenarioStatus: scenResp2.status,
collegeStatus: colResp2.status,
financialStatus: finResp.status
});
onClose(); // or show an error
return;
}
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
const userProfile = buildMergedUserProfile(
finalScenarioRow,
finalCollegeRow,
finalFinancial
);
const results = simulateFinancialProjection(userProfile);
setProjectionData(results.projectionData);
setLoanPayoffMonth(results.loanPaidOffMonth);
// 5) Now close the modal automatically
onClose();
window.location.reload();
} catch (err) {
console.error('Error saving scenario + college:', err);
alert(err.message || 'Failed to save scenario data.');
}
}
/*********************************************************
* 13) Render
*********************************************************/
if (!show) return null;
const displayedTuition =
manualTuition.trim() === '' ? autoTuition : manualTuition;
const displayedProgLength =
manualProgLength.trim() === '' ? autoProgLength : manualProgLength;
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
background: 'rgba(0,0,0,0.6)',
zIndex: 9999,
overflow: 'hidden'
}}
>
<div
style={{
position: 'absolute',
top: '5%',
left: '50%',
transform: 'translateX(-50%)',
width: '90%',
maxWidth: '900px',
maxHeight: '85vh',
background: '#fff',
borderRadius: '6px',
padding: '1rem',
overflowY: 'auto'
}}
>
<h2>
Edit Scenario: {scenario?.scenario_title || scenario?.career_name || '(untitled)'}
</h2>
{/* -- SCENARIO FIELDS -- */}
<h3>Scenario & Career</h3>
<label>Scenario Title</label>
<input
type="text"
name="scenario_title"
value={formData.scenario_title}
onChange={handleFormChange}
style={{ width: '100%', marginBottom: '0.5rem' }}
/>
<label>Career Search</label>
<input
type="text"
value={careerSearchInput}
onChange={handleCareerInputChange}
style={{ width: '100%' }}
/>
{careerMatches.length > 0 && (
<ul
ref={careerDropdownRef}
style={{
border: '1px solid #ccc',
background: '#fff',
maxHeight: '150px',
overflowY: 'auto',
position: 'absolute'
}}
>
{careerMatches.map((c, idx) => (
<li
key={idx}
style={{ cursor: 'pointer' }}
onClick={() => handleSelectCareer(c)}
>
{c}
</li>
))}
</ul>
)}
<p>
<em>Current Career:</em> {formData.career_name || '(none)'}
</p>
<label>Status</label>
<select
name="status"
value={formData.status}
onChange={handleFormChange}
style={{ width: '100%', marginBottom: '0.5rem' }}
>
<option value="planned">Planned</option>
<option value="current">Current</option>
<option value="completed">Completed</option>
<option value="exploring">Exploring</option>
</select>
<label>Start Date</label>
<input
type="date"
name="start_date"
value={formData.start_date || ''}
onChange={handleFormChange}
style={{ width: '100%', marginBottom: '0.5rem' }}
/>
<label>Projected End Date</label>
<input
type="date"
name="projected_end_date"
value={formData.projected_end_date || ''}
onChange={handleFormChange}
style={{ width: '100%', marginBottom: '0.5rem' }}
/>
<label>College Enrollment Status (scenario row)</label>
<select
name="college_enrollment_status"
value={formData.college_enrollment_status}
onChange={handleFormChange}
style={{ width: '100%', marginBottom: '0.5rem' }}
>
<option value="not_enrolled">Not Enrolled</option>
<option value="currently_enrolled">Currently Enrolled</option>
<option value="prospective_student">Prospective</option>
</select>
<label>Currently Working?</label>
<select
name="currently_working"
value={formData.currently_working}
onChange={handleFormChange}
style={{ width: '100%', marginBottom: '0.5rem' }}
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
{/* -- SCENARIO FINANCIAL OVERRIDES -- */}
<h3>Scenario Financial Overwrites</h3>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(220px,1fr))',
gap: '1rem'
}}
>
<div>
<label>Monthly Expenses</label>
<input
type="number"
name="planned_monthly_expenses"
value={formData.planned_monthly_expenses}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
</div>
<div>
<label>Monthly Debt Payments</label>
<input
type="number"
name="planned_monthly_debt_payments"
value={formData.planned_monthly_debt_payments}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
</div>
<div>
<label>Retirement Contrib</label>
<input
type="number"
name="planned_monthly_retirement_contribution"
value={formData.planned_monthly_retirement_contribution}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
</div>
<div>
<label>Emergency Contrib</label>
<input
type="number"
name="planned_monthly_emergency_contribution"
value={formData.planned_monthly_emergency_contribution}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
</div>
<div>
<label>Surplus % to Emergency</label>
<input
type="number"
name="planned_surplus_emergency_pct"
value={formData.planned_surplus_emergency_pct}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
</div>
<div>
<label>Surplus % to Retirement</label>
<input
type="number"
name="planned_surplus_retirement_pct"
value={formData.planned_surplus_retirement_pct}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
</div>
<div>
<label>Additional Income</label>
<input
type="number"
name="planned_additional_income"
value={formData.planned_additional_income}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
</div>
</div>
{/* -- COLLEGE PROFILE FIELDS -- */}
<h3 style={{ marginTop: '1rem' }}>College Profile</h3>
{(formData.college_enrollment_status === 'currently_enrolled'
|| formData.college_enrollment_status === 'prospective_student'
) ? (
<>
<div style={{ marginBottom: '1rem', marginTop: '0.5rem' }}>
<label style={{ marginRight: '1rem' }}>
<input
type="checkbox"
name="is_in_district"
checked={!!formData.is_in_district}
onChange={handleFormChange}
/>
In District
</label>
<label style={{ marginRight: '1rem' }}>
<input
type="checkbox"
name="is_in_state"
checked={!!formData.is_in_state}
onChange={handleFormChange}
/>
In State
</label>
<label>
<input
type="checkbox"
name="is_online"
checked={!!formData.is_online}
onChange={handleFormChange}
/>
Fully Online
</label>
</div>
<label>
<input
type="checkbox"
name="loan_deferral_until_graduation"
checked={!!formData.loan_deferral_until_graduation}
onChange={handleFormChange}
/>
{' '}Defer Loan Payments until Graduation?
</label>
<div style={{ marginTop: '0.5rem' }}>
<label>School</label>
<input
type="text"
value={formData.selected_school}
onChange={handleSchoolChange}
style={{ width: '100%' }}
/>
{schoolSuggestions.length > 0 && (
<ul
style={{
border: '1px solid #ccc',
background: '#fff',
maxHeight: '150px',
overflowY: 'auto',
position: 'absolute'
}}
>
{schoolSuggestions.map((sch, i) => (
<li
key={i}
style={{ cursor: 'pointer' }}
onClick={() => handleSchoolSelect(sch)}
>
{sch}
</li>
))}
</ul>
)}
<label>Program</label>
<input
type="text"
value={formData.selected_program}
onChange={handleProgramChange}
style={{ width: '100%' }}
/>
{programSuggestions.length > 0 && (
<ul
style={{
border: '1px solid #ccc',
background: '#fff',
maxHeight: '150px',
overflowY: 'auto',
position: 'absolute'
}}
>
{programSuggestions.map((prog, idx) => (
<li
key={idx}
style={{ cursor: 'pointer' }}
onClick={() => handleProgramSelect(prog)}
>
{prog}
</li>
))}
</ul>
)}
<label>Program Type</label>
<select
name="program_type"
value={formData.program_type}
onChange={handleProgramTypeSelect}
style={{ width: '100%' }}
>
<option value="">(none)</option>
{availableProgramTypes.map((pt, i) => (
<option key={i} value={pt}>{pt}</option>
))}
</select>
<label>Academic Calendar</label>
<select
name="academic_calendar"
value={formData.academic_calendar}
onChange={handleFormChange}
style={{ width: '100%', marginBottom: '0.5rem' }}
>
<option value="monthly">Monthly</option>
<option value="semester">Semester</option>
<option value="quarter">Quarter</option>
<option value="trimester">Trimester</option>
</select>
<label>Credit Hours per Year</label>
<input
type="number"
name="credit_hours_per_year"
value={formData.credit_hours_per_year}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
<label>Yearly Tuition (auto or override)</label>
<input
type="number"
value={displayedTuition}
onChange={handleManualTuitionChange}
style={{ width: '100%' }}
/>
<label>Annual Financial Aid</label>
<input
type="number"
name="annual_financial_aid"
value={formData.annual_financial_aid}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
<label>Existing College Debt</label>
<input
type="number"
name="existing_college_debt"
value={formData.existing_college_debt}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
{formData.college_enrollment_status === 'currently_enrolled' && (
<>
<label>Tuition Paid</label>
<input
type="number"
name="tuition_paid"
value={formData.tuition_paid}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
<label>Hours Completed</label>
<input
type="number"
name="hours_completed"
value={formData.hours_completed}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
<label>Program Length (auto or override)</label>
<input
type="number"
value={displayedProgLength}
onChange={handleManualProgLengthChange}
style={{ width: '100%' }}
/>
</>
)}
<label>Expected Graduation</label>
<input
type="date"
name="expected_graduation"
value={formData.expected_graduation}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
<label>Interest Rate (%)</label>
<input
type="number"
name="interest_rate"
value={formData.interest_rate}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
<label>Loan Term (years)</label>
<input
type="number"
name="loan_term"
value={formData.loan_term}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
<label>Extra Payment (monthly)</label>
<input
type="number"
name="extra_payment"
value={formData.extra_payment}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
<label>Expected Salary After Graduation</label>
<input
type="number"
name="expected_salary"
value={formData.expected_salary}
onChange={handleFormChange}
style={{ width: '100%' }}
/>
</div>
</>
) : (
<p style={{ marginTop: '0.5rem' }}>
Not currently enrolled or prospective. Minimal college fields only.
</p>
)}
{/* final actions */}
<div style={{ marginTop: '1rem', textAlign: 'right' }}>
<button
onClick={() => onClose(null, null)}
style={{ marginRight: '0.5rem' }}
>
Cancel
</button>
<button onClick={handleSave}>
Save
</button>
</div>
{/* Show a preview if we have simulation data */}
{projectionData.length > 0 && (
<div style={{ marginTop: '1rem', border:'1px solid #ccc', padding:'0.5rem' }}>
<h4>Simulation Preview (first 5 months):</h4>
<pre style={{ maxHeight:'200px', overflow:'auto', background:'#f9f9f9' }}>
{JSON.stringify(projectionData.slice(0,5), null, 2)}
</pre>
{loanPayoffMonth && (
<p>Loan Payoff Month: {loanPayoffMonth}</p>
)}
</div>
)}
</div>
</div>
);
}