972 lines
32 KiB
JavaScript
972 lines
32 KiB
JavaScript
// src/components/ScenarioEditModal.js
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import authFetch from '../utils/authFetch.js';
|
|
|
|
// Paths to your JSON/CSV data. Adjust if needed:
|
|
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, // onClose(updatedScenario, updatedCollege)
|
|
scenario,
|
|
collegeProfile
|
|
}) {
|
|
const [formData, setFormData] = useState({});
|
|
|
|
// CIP & IPEDS data
|
|
const [schoolData, setSchoolData] = useState([]);
|
|
const [icTuitionData, setIcTuitionData] = useState([]);
|
|
|
|
// suggestions
|
|
const [schoolSuggestions, setSchoolSuggestions] = useState([]);
|
|
const [programSuggestions, setProgramSuggestions] = useState([]);
|
|
const [availableProgramTypes, setAvailableProgramTypes] = useState([]);
|
|
|
|
// 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');
|
|
|
|
// career auto-suggest
|
|
const [allCareers, setAllCareers] = useState([]);
|
|
const [careerSearchInput, setCareerSearchInput] = useState('');
|
|
const [careerMatches, setCareerMatches] = useState([]);
|
|
const careerDropdownRef = useRef(null);
|
|
|
|
// ---------- Load CIP/iPEDS/career data once ----------
|
|
useEffect(() => {
|
|
async function loadCIP() {
|
|
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 to load CIP data:', err);
|
|
}
|
|
}
|
|
async function loadIPEDS() {
|
|
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 to load iPEDS data:', err);
|
|
}
|
|
}
|
|
async function loadCareers() {
|
|
try {
|
|
const res = await fetch(CAREER_CLUSTERS_URL);
|
|
const data = await res.json();
|
|
const titles = new Set();
|
|
for (const cluster of Object.keys(data)) {
|
|
for (const subdiv of Object.keys(data[cluster])) {
|
|
const arr = data[cluster][subdiv];
|
|
arr.forEach((obj) => {
|
|
if (obj.title) titles.add(obj.title);
|
|
});
|
|
}
|
|
}
|
|
setAllCareers([...titles]);
|
|
} catch (err) {
|
|
console.error('Failed to load career_clusters:', err);
|
|
}
|
|
}
|
|
|
|
loadCIP();
|
|
loadIPEDS();
|
|
loadCareers();
|
|
}, []);
|
|
|
|
// ---------- career auto-suggest logic ----------
|
|
useEffect(() => {
|
|
if (!careerSearchInput) {
|
|
setCareerMatches([]);
|
|
return;
|
|
}
|
|
const lower = careerSearchInput.toLowerCase();
|
|
const partials = allCareers
|
|
.filter((title) => title.toLowerCase().includes(lower))
|
|
.slice(0, 15);
|
|
setCareerMatches(partials);
|
|
}, [careerSearchInput, allCareers]);
|
|
|
|
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([]);
|
|
}
|
|
|
|
// ---------- Show => populate formData from scenario & college ----------
|
|
useEffect(() => {
|
|
if (!show) return;
|
|
if (!scenario) return;
|
|
|
|
const s = scenario || {};
|
|
const c = collegeProfile || {};
|
|
|
|
setFormData({
|
|
// Scenario
|
|
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
|
|
selected_school: c.selected_school || '',
|
|
selected_program: c.selected_program || '',
|
|
program_type: c.program_type || '',
|
|
academic_calendar: c.academic_calendar || 'semester',
|
|
is_in_state: !!c.is_in_state,
|
|
is_in_district: !!c.is_in_district,
|
|
is_online: !!c.is_online,
|
|
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: c.tuition ?? 0,
|
|
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 ?? ''
|
|
});
|
|
|
|
// set up manual vs auto
|
|
setManualTuition('');
|
|
setAutoTuition(0);
|
|
setManualProgLength('');
|
|
setAutoProgLength('0.00');
|
|
|
|
// career input
|
|
setCareerSearchInput(s.career_name || '');
|
|
}, [show, scenario, collegeProfile]);
|
|
|
|
// ---------- handle form changes ----------
|
|
function handleFormChange(e) {
|
|
const { name, type, checked, value } = e.target;
|
|
let val = value;
|
|
if (type === 'checkbox') {
|
|
val = checked;
|
|
}
|
|
setFormData((prev) => ({ ...prev, [name]: val }));
|
|
}
|
|
|
|
// ---------- school / program changes ----------
|
|
function handleSchoolChange(e) {
|
|
const val = e.target.value;
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
selected_school: val,
|
|
selected_program: '',
|
|
program_type: '',
|
|
credit_hours_required: ''
|
|
}));
|
|
if (!val) {
|
|
setSchoolSuggestions([]);
|
|
return;
|
|
}
|
|
const filtered = schoolData.filter((s) =>
|
|
s.INSTNM.toLowerCase().includes(val.toLowerCase())
|
|
);
|
|
const uniqueSchools = [...new Set(filtered.map((s) => s.INSTNM))];
|
|
setSchoolSuggestions(uniqueSchools.slice(0, 10));
|
|
setProgramSuggestions([]);
|
|
setAvailableProgramTypes([]);
|
|
}
|
|
function handleSchoolSelect(schoolName) {
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
selected_school: schoolName,
|
|
selected_program: '',
|
|
program_type: '',
|
|
credit_hours_required: ''
|
|
}));
|
|
setSchoolSuggestions([]);
|
|
setProgramSuggestions([]);
|
|
setAvailableProgramTypes([]);
|
|
}
|
|
|
|
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 uniquePrograms = [...new Set(filtered.map((r) => r.CIPDESC))];
|
|
setProgramSuggestions(uniquePrograms.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');
|
|
}
|
|
|
|
// ---------- manual tuition & program length ----------
|
|
function handleManualTuitionChange(e) {
|
|
setManualTuition(e.target.value);
|
|
}
|
|
function handleManualProgLengthChange(e) {
|
|
setManualProgLength(e.target.value);
|
|
}
|
|
|
|
// ---------- auto-calc tuition ----------
|
|
useEffect(() => {
|
|
const {
|
|
selected_school,
|
|
program_type,
|
|
credit_hours_per_year,
|
|
is_in_state,
|
|
is_in_district
|
|
} = formData;
|
|
if (!icTuitionData.length) return;
|
|
if (!selected_school || !program_type || !credit_hours_per_year) return;
|
|
|
|
// find
|
|
const found = schoolData.find(
|
|
(s) => s.INSTNM.toLowerCase() === selected_school.toLowerCase()
|
|
);
|
|
if (!found?.UNITID) return;
|
|
const match = icTuitionData.find((row) => row.UNITID === found.UNITID);
|
|
if (!match) return;
|
|
|
|
const isGradOrProf = [
|
|
"Master's Degree",
|
|
"Doctoral Degree",
|
|
"Graduate/Professional Certificate",
|
|
"First Professional Degree"
|
|
].includes(program_type);
|
|
|
|
let partTimeRate = 0;
|
|
let fullTimeTuition = 0;
|
|
if (isGradOrProf) {
|
|
if (is_in_district) {
|
|
partTimeRate = parseFloat(match.HRCHG5 || 0);
|
|
fullTimeTuition = parseFloat(match.TUITION5 || 0);
|
|
} else if (is_in_state) {
|
|
partTimeRate = parseFloat(match.HRCHG6 || 0);
|
|
fullTimeTuition = parseFloat(match.TUITION6 || 0);
|
|
} else {
|
|
partTimeRate = parseFloat(match.HRCHG7 || 0);
|
|
fullTimeTuition = parseFloat(match.TUITION7 || 0);
|
|
}
|
|
} else {
|
|
// undergrad
|
|
if (is_in_district) {
|
|
partTimeRate = parseFloat(match.HRCHG1 || 0);
|
|
fullTimeTuition = parseFloat(match.TUITION1 || 0);
|
|
} else if (is_in_state) {
|
|
partTimeRate = parseFloat(match.HRCHG2 || 0);
|
|
fullTimeTuition = parseFloat(match.TUITION2 || 0);
|
|
} else {
|
|
partTimeRate = parseFloat(match.HRCHG3 || 0);
|
|
fullTimeTuition = parseFloat(match.TUITION3 || 0);
|
|
}
|
|
}
|
|
const chpy = parseFloat(credit_hours_per_year) || 0;
|
|
let estimate = 0;
|
|
if (chpy < 24 && partTimeRate) {
|
|
estimate = partTimeRate * chpy;
|
|
} else {
|
|
estimate = fullTimeTuition;
|
|
}
|
|
setAutoTuition(Math.round(estimate));
|
|
}, [
|
|
icTuitionData,
|
|
formData.selected_school,
|
|
formData.program_type,
|
|
formData.credit_hours_per_year,
|
|
formData.is_in_district,
|
|
formData.is_in_state,
|
|
schoolData
|
|
]);
|
|
|
|
// ---------- auto-calc program length ----------
|
|
useEffect(() => {
|
|
const { program_type, hours_completed, credit_hours_per_year, credit_hours_required } =
|
|
formData;
|
|
if (!program_type) return;
|
|
if (!hours_completed || !credit_hours_per_year) return;
|
|
|
|
let required = 0;
|
|
switch (program_type) {
|
|
case "Associate's Degree":
|
|
required = 60;
|
|
break;
|
|
case "Bachelor's Degree":
|
|
required = 120;
|
|
break;
|
|
case "Master's Degree":
|
|
required = 60;
|
|
break;
|
|
case "Doctoral Degree":
|
|
required = 120;
|
|
break;
|
|
case "First Professional Degree":
|
|
required = 180;
|
|
break;
|
|
case "Graduate/Professional Certificate":
|
|
required = parseInt(credit_hours_required, 10) || 0;
|
|
break;
|
|
default:
|
|
required = parseInt(credit_hours_required, 10) || 0;
|
|
break;
|
|
}
|
|
const remain = required - (parseInt(hours_completed, 10) || 0);
|
|
const yrs = remain / (parseFloat(credit_hours_per_year) || 1);
|
|
setAutoProgLength(yrs.toFixed(2));
|
|
}, [
|
|
formData.program_type,
|
|
formData.hours_completed,
|
|
formData.credit_hours_per_year,
|
|
formData.credit_hours_required
|
|
]);
|
|
|
|
// ---------- handleSave => PUT scenario & college => onClose(...)
|
|
async function handleSave() {
|
|
try {
|
|
// chosen tuition
|
|
const chosenTuition =
|
|
manualTuition.trim() === '' ? autoTuition : parseFloat(manualTuition);
|
|
const chosenProgLen =
|
|
manualProgLength.trim() === '' ? autoProgLength : manualProgLength;
|
|
|
|
// scenario payload
|
|
const scenarioPayload = {
|
|
scenario_title: formData.scenario_title || '',
|
|
career_name: formData.career_name || '',
|
|
status: formData.status || 'planned',
|
|
start_date: formData.start_date || null,
|
|
projected_end_date: formData.projected_end_date || null,
|
|
college_enrollment_status: formData.college_enrollment_status || 'not_enrolled',
|
|
currently_working: formData.currently_working || 'no',
|
|
|
|
planned_monthly_expenses:
|
|
formData.planned_monthly_expenses === '' ? null : Number(formData.planned_monthly_expenses),
|
|
planned_monthly_debt_payments:
|
|
formData.planned_monthly_debt_payments === '' ? null : Number(formData.planned_monthly_debt_payments),
|
|
planned_monthly_retirement_contribution:
|
|
formData.planned_monthly_retirement_contribution === '' ? null : Number(formData.planned_monthly_retirement_contribution),
|
|
planned_monthly_emergency_contribution:
|
|
formData.planned_monthly_emergency_contribution === '' ? null : Number(formData.planned_monthly_emergency_contribution),
|
|
planned_surplus_emergency_pct:
|
|
formData.planned_surplus_emergency_pct === '' ? null : Number(formData.planned_surplus_emergency_pct),
|
|
planned_surplus_retirement_pct:
|
|
formData.planned_surplus_retirement_pct === '' ? null : Number(formData.planned_surplus_retirement_pct),
|
|
planned_additional_income:
|
|
formData.planned_additional_income === '' ? null : Number(formData.planned_additional_income)
|
|
};
|
|
|
|
// 1) Put scenario
|
|
const scenRes = await authFetch(`/api/premium/career-profile/${scenario.id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(scenarioPayload)
|
|
});
|
|
if (!scenRes.ok) {
|
|
const eText = await scenRes.text();
|
|
throw new Error('Scenario update failed: ' + eText);
|
|
}
|
|
const updatedScenario = await scenRes.json(); // updated scenario row
|
|
|
|
// 2) Put college
|
|
const colId = collegeProfile?.id; // or handle no ID
|
|
const collegePayload = {
|
|
selected_school: formData.selected_school || null,
|
|
selected_program: formData.selected_program || null,
|
|
program_type: formData.program_type || null,
|
|
academic_calendar: formData.academic_calendar || 'semester',
|
|
is_in_state: formData.is_in_state ? 1 : 0,
|
|
is_in_district: formData.is_in_district ? 1 : 0,
|
|
is_online: formData.is_online ? 1 : 0,
|
|
college_enrollment_status: formData.college_enrollment_status_db || 'not_enrolled',
|
|
annual_financial_aid:
|
|
formData.annual_financial_aid === '' ? 0 : Number(formData.annual_financial_aid),
|
|
existing_college_debt:
|
|
formData.existing_college_debt === '' ? 0 : Number(formData.existing_college_debt),
|
|
tuition: chosenTuition,
|
|
tuition_paid:
|
|
formData.tuition_paid === '' ? 0 : Number(formData.tuition_paid),
|
|
loan_deferral_until_graduation:
|
|
formData.loan_deferral_until_graduation ? 1 : 0,
|
|
loan_term:
|
|
formData.loan_term === '' ? 10 : Number(formData.loan_term),
|
|
interest_rate:
|
|
formData.interest_rate === '' ? 5 : Number(formData.interest_rate),
|
|
extra_payment:
|
|
formData.extra_payment === '' ? 0 : Number(formData.extra_payment),
|
|
credit_hours_per_year:
|
|
formData.credit_hours_per_year === '' ? 0 : Number(formData.credit_hours_per_year),
|
|
hours_completed:
|
|
formData.hours_completed === '' ? 0 : Number(formData.hours_completed),
|
|
program_length: chosenProgLen,
|
|
credit_hours_required:
|
|
formData.credit_hours_required === '' ? 0 : Number(formData.credit_hours_required),
|
|
expected_graduation: formData.expected_graduation || null,
|
|
expected_salary:
|
|
formData.expected_salary === '' ? 0 : Number(formData.expected_salary)
|
|
};
|
|
|
|
const colRes = await authFetch(`/api/premium/college-profile/${colId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(collegePayload)
|
|
});
|
|
if (!colRes.ok) {
|
|
const colText = await colRes.text();
|
|
throw new Error('College update failed: ' + colText);
|
|
}
|
|
const updatedCollege = await colRes.json();
|
|
|
|
onClose(updatedScenario, updatedCollege);
|
|
} catch (err) {
|
|
console.error('Error in handleSave:', err);
|
|
alert(err.message || 'Failed to save scenario changes');
|
|
}
|
|
}
|
|
|
|
if (!show) return null;
|
|
|
|
// displayed tuition/programLength
|
|
const displayedTuition =
|
|
manualTuition.trim() === '' ? autoTuition : manualTuition;
|
|
const displayedProgLen =
|
|
manualProgLength.trim() === '' ? autoProgLength : manualProgLength;
|
|
|
|
return (
|
|
<div
|
|
className="modal-backdrop"
|
|
style={{
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100vw',
|
|
height: '100vh',
|
|
background: 'rgba(0,0,0,0.6)',
|
|
zIndex: 9999
|
|
}}
|
|
>
|
|
<div
|
|
className="modal-container"
|
|
style={{
|
|
background: '#fff',
|
|
width: '90%',
|
|
maxWidth: '900px',
|
|
maxHeight: '85vh',
|
|
margin: '30px auto',
|
|
padding: '1rem',
|
|
borderRadius: '6px',
|
|
overflowY: 'auto', // allow scroll
|
|
position: 'relative'
|
|
}}
|
|
>
|
|
<h2>
|
|
Edit Scenario: {scenario?.scenario_title || scenario?.career_name}
|
|
</h2>
|
|
|
|
{/* ============ SCENARIO (CAREER) SECTION ============ */}
|
|
<h3 style={{ marginTop: '1rem' }}>Scenario (Career Paths)</h3>
|
|
<hr />
|
|
<div style={{ marginTop: '0.5rem' }}>
|
|
<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',
|
|
padding: '4px',
|
|
maxHeight: '150px',
|
|
overflowY: 'auto',
|
|
background: '#fff',
|
|
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 (scenario)</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>
|
|
</div>
|
|
|
|
{/* ============ SCENARIO (FINANCIAL) SECTION ============ */}
|
|
<h3 style={{ marginTop: '1rem' }}>Scenario Overwrites (financial)</h3>
|
|
<hr />
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: 'repeat(auto-fill,minmax(220px,1fr))',
|
|
gap: '1rem',
|
|
marginTop: '0.5rem'
|
|
}}
|
|
>
|
|
<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 Pmts</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 % Emer</label>
|
|
<input
|
|
type="number"
|
|
name="planned_surplus_emergency_pct"
|
|
value={formData.planned_surplus_emergency_pct}
|
|
onChange={handleFormChange}
|
|
style={{ width: '100%' }}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label>Surplus % Ret</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 SECTION ============ */}
|
|
<h3 style={{ marginTop: '1rem' }}>College Profile</h3>
|
|
<hr />
|
|
{(formData.college_enrollment_status === 'currently_enrolled' ||
|
|
formData.college_enrollment_status === 'prospective_student') ? (
|
|
<div style={{ marginTop: '0.5rem' }}>
|
|
<div>
|
|
<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>
|
|
|
|
<label>School</label>
|
|
<input
|
|
type="text"
|
|
value={formData.selected_school}
|
|
onChange={handleSchoolChange}
|
|
style={{ width: '100%' }}
|
|
/>
|
|
{schoolSuggestions.length > 0 && (
|
|
<ul
|
|
style={{
|
|
border: '1px solid #ccc',
|
|
padding: '4px',
|
|
maxHeight: '150px',
|
|
overflowY: 'auto',
|
|
background: '#fff',
|
|
position: 'absolute'
|
|
}}
|
|
>
|
|
{schoolSuggestions.map((sch, idx) => (
|
|
<li
|
|
key={idx}
|
|
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',
|
|
padding: '4px',
|
|
maxHeight: '150px',
|
|
overflowY: 'auto',
|
|
background: '#fff',
|
|
position: 'absolute'
|
|
}}
|
|
>
|
|
{programSuggestions.map((prog, i) => (
|
|
<li
|
|
key={i}
|
|
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>
|
|
|
|
{['Graduate/Professional Certificate','Doctoral Degree','First Professional Degree']
|
|
.includes(formData.program_type) && (
|
|
<>
|
|
<label>Credit Hours Required</label>
|
|
<input
|
|
type="number"
|
|
name="credit_hours_required"
|
|
value={formData.credit_hours_required}
|
|
onChange={handleFormChange}
|
|
style={{ width: '100%' }}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
<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/override)</label>
|
|
<input
|
|
type="number"
|
|
value={displayedTuition}
|
|
onChange={handleManualTuitionChange}
|
|
placeholder="blank => auto"
|
|
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/override)</label>
|
|
<input
|
|
type="number"
|
|
value={manualProgLength.trim() === '' ? autoProgLength : manualProgLength}
|
|
onChange={handleManualProgLengthChange}
|
|
placeholder="blank => auto"
|
|
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>
|
|
)}
|
|
|
|
<div style={{ marginTop: '1rem', textAlign: 'right' }}>
|
|
<button onClick={() => onClose(null, null)} style={{ marginRight: '0.5rem' }}>
|
|
Cancel
|
|
</button>
|
|
<button onClick={handleSave}>Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|