dev1/src/components/ScenarioEditModal.js

1244 lines
45 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

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';
import InfoTooltip from "./ui/infoTooltip.js";
// 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,
financialProfile
}) {
/*********************************************************
* 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({});
const [showCollegeForm, setShowCollegeForm] = useState(false);
/*********************************************************
* Auto-expand the college section each time the modal opens.
* --------------------------------------------------------
* ❑ The effect runs exactly once per modalopen (`show` → true).
* ❑ If the saved scenario already says the user is
* currently_enrolled or prospective_student
* we open the section so they immediately see their data.
* ❑ Once open, the user can click Hide/Show; we *dont* re-run
* on every keystroke, so the effect wont fight the button.
*********************************************************/
useEffect(() => {
if (!show) return;
setShowCollegeForm(
['currently_enrolled', 'prospective_student']
.includes(formData.college_enrollment_status)
);
}, [show]);
/*********************************************************
* 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) Whenever the **modal is shown** *or* **scenario.id changes**
* → hydrate the form + careerSearch box.
*********************************************************/
useEffect(() => {
if (!show || !scenario) return;
const s = scenario || {};
const c = collegeProfile || {};
const safe = v =>
v === null || v === undefined ? '' : v;
setFormData({
// scenario portion
scenario_title : safe(s.scenario_title),
career_name : safe(s.career_name),
status : safe(s.status || 'planned'),
start_date : safe(s.start_date),
retirement_start_date: safe(s.retirement_start_date),
desired_retirement_income_monthly : safe(
s.desired_retirement_income_monthly
),
planned_monthly_expenses : safe(s.planned_monthly_expenses),
planned_monthly_debt_payments : safe(s.planned_monthly_debt_payments),
planned_monthly_retirement_contribution: safe(s.planned_monthly_retirement_contribution),
planned_monthly_emergency_contribution : safe(s.planned_monthly_emergency_contribution),
planned_surplus_emergency_pct : safe(s.planned_surplus_emergency_pct),
planned_surplus_retirement_pct : safe(s.planned_surplus_retirement_pct),
planned_additional_income : safe(s.planned_additional_income),
// college portion
college_profile_id: safe(c.id || null),
selected_school: safe(c.selected_school || ''),
selected_program: safe(c.selected_program || ''),
program_type: safe(c.program_type || ''),
academic_calendar: safe(c.academic_calendar || 'monthly'),
is_in_state: safe(!!c.is_in_state),
is_in_district: safe(!!c.is_in_district),
is_online: safe(!!c.is_online),
college_enrollment_status_db: safe(c.college_enrollment_status || 'not_enrolled'),
annual_financial_aid : safe(c.annual_financial_aid),
existing_college_debt : safe(c.existing_college_debt),
tuition_paid : safe(c.tuition_paid),
loan_term : safe(c.loan_term ?? 10),
interest_rate : safe(c.interest_rate ?? 5),
extra_payment : safe(c.extra_payment),
credit_hours_per_year: safe(c.credit_hours_per_year ?? ''),
hours_completed: safe(c.hours_completed ?? ''),
program_length: safe(c.program_length ?? ''),
credit_hours_required: safe(c.credit_hours_required ?? ''),
enrollment_date: safe(c.enrollment_date ? c.enrollment_date.substring(0, 10): ''),
expected_graduation: safe(c.expected_graduation ? c.expected_graduation.substring(0, 10): ''),
expected_salary: safe(c.expected_salary ?? '')
});
// Manual / auto tuition
if (c.tuition != null && c.tuition !== 0) {
setManualTuition(String(c.tuition));
setAutoTuition('');
} else {
const autoCalc = 12000;
setAutoTuition(String(autoCalc));
setManualTuition('');
}
// Manual / auto program length
if (c.program_length != null && c.program_length !== 0) {
setManualProgLength(String(c.program_length));
setAutoProgLength('');
} else {
const autoLen = 2.0;
setAutoProgLength(String(autoLen));
setManualProgLength('');
}
setCareerSearchInput(s.career_name || '');
}, [show, scenario?.id, collegeProfile]);
/*********************************************************
* 8) Auto-calc placeholders (stubbed out)
*********************************************************/
useEffect(() => {
if (!show) return;
// IPEDS-based logic or other auto-calculation
}, [
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;
// Possibly recalc program length
}, [
show,
formData.program_type,
formData.hours_completed,
formData.credit_hours_per_year,
formData.credit_hours_required
]);
/*********************************************************
* 9) Career auto-suggest
*********************************************************/
useEffect(() => {
if (!show) return;
// 1⃣ trim once, reuse everywhere
const typed = careerSearchInput.trim();
// Nothing typed → clear list
if (!typed) {
setCareerMatches([]);
return;
}
/* 2⃣ Exact match (case-insensitive) → suppress dropdown */
if (allCareers.some(t => t.toLowerCase() === typed.toLowerCase())) {
setCareerMatches([]);
return;
}
// 3⃣ Otherwise show up to 15 partial matches
const lower = typed.toLowerCase();
const partials = allCareers
.filter(title => title.toLowerCase().includes(lower))
.slice(0, 15);
setCareerMatches(partials);
}, [show, careerSearchInput, allCareers]);
/*********************************************************
* 9.5) Program Type from CIP
*********************************************************/
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) Simulation aggregator
*********************************************************/
const [projectionData, setProjectionData] = useState([]);
const [loanPayoffMonth, setLoanPayoffMonth] = useState(null);
function buildMergedUserProfile(scenarioRow, collegeRow, financialData) {
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,
enrollmentDate: collegeRow.enrollment_date || null,
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().slice(0, 10),
simulationYears: 20,
milestoneImpacts: []
};
}
/*********************************************************
* 12) handleSave => upsert scenario & college => re-fetch => simulate
*********************************************************/
async function handleSave() {
try {
/* ─── helpers ───────────────────────────────────────────── */
const n = v => (v === "" || v == null ? undefined : Number(v));
const s = v => {
if (v == null) return undefined;
const t = String(v).trim();
return t === "" ? undefined : t;
};
/* ─── 0) did the user change the title? ─────────────────── */
const originalName = scenario?.career_name?.trim() || "";
const editedName = (formData.career_name || "").trim();
const titleChanged = editedName && editedName !== originalName;
/* ─── 1) build scenario payload ─────────────────────────── */
const scenarioPayload = {
scenario_title : s(formData.scenario_title),
career_name : editedName, // always include
college_enrollment_status : formData.college_enrollment_status,
currently_working : formData.currently_working || "no",
status : s(formData.status),
start_date : s(formData.start_date),
retirement_start_date : s(formData.retirement_start_date),
desired_retirement_income_monthly : n(formData.desired_retirement_income_monthly),
planned_monthly_expenses : n(formData.planned_monthly_expenses),
planned_monthly_debt_payments : n(formData.planned_monthly_debt_payments),
planned_monthly_retirement_contribution: n(formData.planned_monthly_retirement_contribution),
planned_monthly_emergency_contribution : n(formData.planned_monthly_emergency_contribution),
planned_surplus_emergency_pct : n(formData.planned_surplus_emergency_pct),
planned_surplus_retirement_pct : n(formData.planned_surplus_retirement_pct),
planned_additional_income : n(formData.planned_additional_income)
};
/* If the title did NOT change, keep the id so the UPSERT
updates the existing row. Otherwise omit id → new row */
if (!titleChanged && scenario?.id) {
scenarioPayload.id = scenario.id;
}
/* ─── 2) POST (always) ─────────────────────────────────── */
const scenRes = await authFetch("/api/premium/career-profile", {
method : "POST",
headers: { "Content-Type": "application/json" },
body : JSON.stringify(scenarioPayload)
});
if (!scenRes.ok) throw new Error(await scenRes.text());
const { career_profile_id } = await scenRes.json();
// ─── AUTO-CREATE / UPDATE “Retirement” milestone ──────────────────
if (formData.retirement_start_date) {
const payload = {
title : 'Retirement',
description : 'User-defined retirement date (auto-generated)',
date : formData.retirement_start_date,
career_profile_id: career_profile_id,
progress : 0,
status : 'planned',
is_universal : 0
};
// Ask the backend if one already exists for this scenario
const check = await authFetch(
`/api/premium/milestones?careerProfileId=${career_profile_id}`
).then(r => r.json());
const existing = (check.milestones || []).find(m => m.title === 'Retirement');
await authFetch(
existing ? `/api/premium/milestones/${existing.id}` : '/api/premium/milestone',
{
method : existing ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' },
body : JSON.stringify(payload)
}
);
}
/* ─── 3) (optional) upsert college profile keep yours… ─ */
/* ─── 4) update localStorage so CareerRoadmap re-hydrates ─ */
localStorage.setItem(
"selectedCareer",
JSON.stringify({ title: editedName })
);
localStorage.setItem(
"lastSelectedCareerProfileId",
String(career_profile_id)
);
/* ─── 5) close modal + tell parent to refetch ───────────── */
onClose(true); // CareerRoadmaps onClose(true) triggers reload
} catch (err) {
console.error("handleSave", err);
alert(err.message || "Failed to save scenario");
}
}
/*********************************************************
* 13) Render
*********************************************************/
if (!show) return null;
const displayedTuition =
manualTuition.trim() === '' ? autoTuition : manualTuition;
const displayedProgLength =
manualProgLength.trim() === '' ? autoProgLength : manualProgLength;
return (
<div
className={`
fixed inset-0 z-50 flex items-start justify-center
bg-black bg-opacity-60 overflow-hidden pt-8
`}
>
<div
className={`
relative bg-white rounded-lg p-6 w-full max-w-4xl
max-h-[85vh] overflow-y-auto
`}
>
<h2 className="text-2xl font-semibold mb-4">
Edit Scenario: {scenario?.scenario_title || scenario?.career_name || '(untitled)'}
</h2>
{/* SECTION: Scenario & Career */}
<h3 className="text-xl font-medium mb-2">Scenario & Career</h3>
{/* Scenario Title */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Scenario Title
</label>
<input
type="text"
name="scenario_title"
value={formData.scenario_title}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full focus:outline-none focus:border-blue-500"
/>
</div>
{/* Career Search */}
<div className="mb-4 relative">
<label className="block text-sm font-medium text-gray-700 mb-1">
Career Search
</label>
<input
type="text"
value={careerSearchInput}
onChange={handleCareerInputChange}
className="border border-gray-300 rounded p-2 w-full focus:outline-none focus:border-blue-500"
/>
{careerMatches.length > 0 && (
<ul
ref={careerDropdownRef}
className="
absolute top-full left-0 w-full border border-gray-200 bg-white z-10
max-h-48 overflow-auto mt-1
"
>
{careerMatches.map((c, idx) => (
<li
key={idx}
className="p-2 cursor-pointer hover:bg-gray-100"
onClick={() => handleSelectCareer(c)}
>
{c}
</li>
))}
</ul>
)}
<p className="text-sm text-gray-600 mt-1">
<em>Current Career:</em> {formData.career_name || '(none)'}
</p>
</div>
{/* Status */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Status
</label>
<select
name="status"
value={formData.status}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
>
<option value="planned">Planned</option>
<option value="current">Current</option>
<option value="completed">Completed</option>
<option value="exploring">Exploring</option>
</select>
</div>
{/* Dates */}
<div className="mb-4 grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Start Date
</label>
<input
type="date"
name="start_date"
value={formData.start_date || ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
</div>
{/* Retirement date */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Planned Retirement Date
</label>
<input
type="date"
name="retirement_start_date"
value={formData.retirement_start_date}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Desired retirement income (monthly) */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Desired Retirement Income (monthly $)
</label>
<input
type="number"
name="desired_retirement_income_monthly"
value={formData.desired_retirement_income_monthly}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
{formData.desired_retirement_income_monthly && (
<p className="text-xs text-gray-500">
$
{(formData.desired_retirement_income_monthly*12)
.toLocaleString()} per year
</p>
)}
</div>
{/* College Enrollment Status */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
College Enrollment Status
</label>
<select
name="college_enrollment_status"
value={formData.college_enrollment_status}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
>
<option value="not_enrolled">Not Enrolled</option>
<option value="currently_enrolled">Currently Enrolled</option>
<option value="prospective_student">Prospective</option>
</select>
</div>
{/* Currently Working */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Currently Working?
</label>
<select
name="currently_working"
value={formData.currently_working}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
>
<option value="yes">Yes</option>
<option value="no">No</option>
</select>
</div>
{/* SECTION: Scenario Financial Overrides */}
<h3 className="text-xl font-medium mt-6 mb-3">Scenario Financial Overrides</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 mb-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Monthly Expenses
</label>
<input
type="number"
name="planned_monthly_expenses"
value={formData.planned_monthly_expenses}
placeholder={financialProfile?.monthly_expenses ?? ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Monthly Debt Payments
</label>
<input
type="number"
name="planned_monthly_debt_payments"
value={formData.planned_monthly_debt_payments}
placeholder={financialProfile?.monthly_debt_payments ?? ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Retirement Contrib
</label>
<input
type="number"
name="planned_monthly_retirement_contribution"
value={formData.planned_monthly_retirement_contribution}
placeholder={financialProfile?.retirement_contribution ?? ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Emergency Contrib
</label>
<input
type="number"
name="planned_monthly_emergency_contribution"
value={formData.planned_monthly_emergency_contribution}
placeholder={financialProfile?.emergency_contribution ?? ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Surplus % to Emergency
</label>
<input
type="number"
name="planned_surplus_emergency_pct"
value={formData.planned_surplus_emergency_pct}
placeholder={financialProfile?.extra_cash_emergency_pct ?? ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Surplus % to Retirement
</label>
<input
type="number"
name="planned_surplus_retirement_pct"
value={formData.planned_surplus_retirement_pct}
placeholder={financialProfile?.extra_cash_retirement_pct ?? ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Additional Income
</label>
<input
type="number"
name="planned_additional_income"
value={formData.planned_additional_income}
placeholder={financialProfile?.additional_income ?? ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
</div>
{/* ───────── COLLEGE PROFILE heading + Add plan ───────── */}
<div className="flex items-center justify-between mb-2">
{/* left cluster (title + info badge) */}
<div className="flex items-center space-x-2">
<h3 className="text-xl font-medium">College Profile</h3>
<InfoTooltip message="Get this info from the “Educational Programs” tab in Preparing & Upskilling for Your Career." />
</div>
{/* right cluster (toggle button) */}
<Button
variant="secondary"
size="sm"
onClick={() => {
if (
!showCollegeForm &&
formData.college_enrollment_status === 'not_enrolled'
) {
setFormData(prev => ({
...prev,
college_enrollment_status: 'prospective_student'
}));
}
setShowCollegeForm(prev => !prev);
}}
>
{showCollegeForm ? 'Hide' : 'Add plan'}
</Button>
</div>
{/* Collapse / expand the full set of inputs */}
{showCollegeForm ? (
<>
{/* District / State / Online check-boxes ------------------ */}
<div className="flex items-center space-x-4 mb-4">
{[
{ name: 'is_in_district', label: 'In District' },
{ name: 'is_in_state', label: 'In State' },
{ name: 'is_online', label: 'Fully Online' }
].map(({ name, label }) => (
<label key={name} className="inline-flex items-center">
<input
type="checkbox"
name={name}
checked={!!formData[name]}
onChange={handleFormChange}
className="mr-1"
/>
{label}
</label>
))}
</div>
{/* Loan Deferral */}
<div className="mb-4">
<label className="inline-flex items-center">
<input
type="checkbox"
name="loan_deferral_until_graduation"
checked={!!formData.loan_deferral_until_graduation}
onChange={handleFormChange}
className="mr-1"
/>
Defer Loan Payments until Graduation?
</label>
</div>
{/* School */}
<div className="mb-4 relative">
<label className="block text-sm font-medium text-gray-700 mb-1">
School
</label>
<input
type="text"
value={formData.selected_school}
onChange={handleSchoolChange}
className="border border-gray-300 rounded p-2 w-full focus:outline-none focus:border-blue-500"
/>
{schoolSuggestions.length > 0 && (
<ul
className="
absolute top-full left-0 w-full
border border-gray-200 bg-white z-10
max-h-48 overflow-auto mt-1
"
>
{schoolSuggestions.map((sch, i) => (
<li
key={i}
className="p-2 cursor-pointer hover:bg-gray-100"
onClick={() => handleSchoolSelect(sch)}
>
{sch}
</li>
))}
</ul>
)}
</div>
{/* Program */}
<div className="mb-4 relative">
<label className="block text-sm font-medium text-gray-700 mb-1">
Program
</label>
<input
type="text"
value={formData.selected_program}
onChange={handleProgramChange}
className="border border-gray-300 rounded p-2 w-full focus:outline-none focus:border-blue-500"
/>
{programSuggestions.length > 0 && (
<ul
className="
absolute top-full left-0 w-full
border border-gray-200 bg-white z-10
max-h-48 overflow-auto mt-1
"
>
{programSuggestions.map((prog, idx) => (
<li
key={idx}
className="p-2 cursor-pointer hover:bg-gray-100"
onClick={() => handleProgramSelect(prog)}
>
{prog}
</li>
))}
</ul>
)}
</div>
{/* Program Type */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Program Type
</label>
<select
name="program_type"
value={formData.program_type}
onChange={handleProgramTypeSelect}
className="border border-gray-300 rounded p-2 w-full"
>
<option value="">(none)</option>
{availableProgramTypes.map((pt, i) => (
<option key={i} value={pt}>
{pt}
</option>
))}
</select>
</div>
{/* Academic Calendar */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Academic Calendar
</label>
<select
name="academic_calendar"
value={formData.academic_calendar}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
>
<option value="monthly">Monthly</option>
<option value="semester">Semester</option>
<option value="quarter">Quarter</option>
<option value="trimester">Trimester</option>
</select>
</div>
{/* Credit Hours per Year */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Credit Hours per Year
</label>
<input
type="number"
name="credit_hours_per_year"
value={formData.credit_hours_per_year}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Yearly Tuition */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Yearly Tuition (auto or override)
</label>
<input
type="number"
value={displayedTuition}
onChange={handleManualTuitionChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Annual Financial Aid */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Annual Financial Aid
</label>
<input
type="number"
name="annual_financial_aid"
value={formData.annual_financial_aid}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Existing College Debt */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Existing College Debt
</label>
<input
type="number"
name="existing_college_debt"
value={formData.existing_college_debt}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Currently Enrolled Only Fields */}
{formData.college_enrollment_status === 'currently_enrolled' && (
<>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Tuition Paid
</label>
<input
type="number"
name="tuition_paid"
value={formData.tuition_paid}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Hours Completed
</label>
<input
type="number"
name="hours_completed"
value={formData.hours_completed}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Program Length (auto or override)
</label>
<input
type="number"
value={displayedProgLength}
onChange={handleManualProgLengthChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
</>
)}
{/* Enrollment Date */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Enrollment Date
</label>
<input
type="date"
name="enrollment_date"
value={formData.enrollment_date || ''}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Expected Graduation */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Expected Graduation
</label>
<input
type="date"
name="expected_graduation"
value={formData.expected_graduation}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Interest Rate */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Interest Rate (%)
</label>
<input
type="number"
name="interest_rate"
value={formData.interest_rate}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Loan Term */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Loan Term (years)
</label>
<input
type="number"
name="loan_term"
value={formData.loan_term}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Extra Payment */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Extra Payment (monthly)
</label>
<input
type="number"
name="extra_payment"
value={formData.extra_payment}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
{/* Expected Salary */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-1">
Expected Salary After Graduation
</label>
<input
type="number"
name="expected_salary"
value={formData.expected_salary}
onChange={handleFormChange}
className="border border-gray-300 rounded p-2 w-full"
/>
</div>
</>
) : (
<p className="text-sm text-gray-500 italic mb-4">
Not currently enrolled or prospective. Add a plan to enter this info.
</p>
)}
{/* ACTIONS */}
<div className="mt-6 flex justify-end space-x-2">
<Button variant="secondary" onClick={() => onClose(null, null)}>
Cancel
</Button>
<Button variant="primary" onClick={handleSave}>
Save
</Button>
{!formData.retirement_start_date && (
<p className="mt-1 text-xs text-red-500">
Pick a Planned Retirement Date to run the simulation.
</p>
)}
</div>
{/* Show a preview if we have simulation data */}
{projectionData.length > 0 && (
<div className="mt-6 border border-gray-200 p-4 rounded">
<h4 className="text-lg font-semibold mb-2">
Simulation Preview (first 5 months):
</h4>
<pre className="bg-gray-50 p-2 rounded overflow-auto max-h-52 text-sm">
{JSON.stringify(projectionData.slice(0, 5), null, 2)}
</pre>
{loanPayoffMonth && (
<p className="mt-2 text-sm">Loan Payoff Month: {loanPayoffMonth}</p>
)}
</div>
)}
</div>
</div>
);
}