dev1/src/components/PremiumOnboarding/CollegeOnboarding.js

624 lines
21 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import Modal from '../../components/ui/modal.js';
import FinancialAidWizard from '../../components/FinancialAidWizard.js';
function CollegeOnboarding({ nextStep, prevStep, data, setData, careerProfileId }) {
// CIP / iPEDS local states (purely for CIP data and suggestions)
const [schoolData, setSchoolData] = useState([]);
const [icTuitionData, setIcTuitionData] = useState([]);
const [schoolSuggestions, setSchoolSuggestions] = useState([]);
const [programSuggestions, setProgramSuggestions] = useState([]);
const [availableProgramTypes, setAvailableProgramTypes] = useState([]);
// Show/hide the financial aid wizard
const [showAidWizard, setShowAidWizard] = useState(false);
// Destructure parent data
const {
college_enrollment_status = '',
selected_school = '',
selected_program = '',
program_type = '',
academic_calendar = 'semester', // <-- ACADEMIC CALENDAR
annual_financial_aid = '',
is_online = false,
existing_college_debt = '',
expected_graduation = '',
interest_rate = 5.5,
loan_term = 10,
extra_payment = '',
expected_salary = '',
is_in_state = false,
is_in_district = false,
loan_deferral_until_graduation = false,
credit_hours_per_year = '',
hours_completed = '',
credit_hours_required = '',
tuition_paid = '',
} = data;
// Local states for auto/manual logic on tuition & program length
const [manualTuition, setManualTuition] = useState('');
const [autoTuition, setAutoTuition] = useState(0);
const [manualProgramLength, setManualProgramLength] = useState('');
const [autoProgramLength, setAutoProgramLength] = useState('0.00');
// -- universal handleChange for all parent fields except tuition/program_length
const handleParentFieldChange = (e) => {
const { name, value, type, checked } = e.target;
let val = value;
if (type === 'checkbox') {
val = checked;
}
if (['interest_rate','loan_term','extra_payment','expected_salary'].includes(name)) {
val = parseFloat(val) || 0;
} else if (
['annual_financial_aid','existing_college_debt','credit_hours_per_year',
'hours_completed','credit_hours_required','tuition_paid'].includes(name)
) {
val = val === '' ? '' : parseFloat(val);
}
setData(prev => ({ ...prev, [name]: val }));
};
const handleManualTuitionChange = (e) => {
setManualTuition(e.target.value);
};
const handleManualProgramLengthChange = (e) => {
setManualProgramLength(e.target.value);
};
// Fetch CIP data (example)
useEffect(() => {
async function fetchCipData() {
try {
const res = await fetch('/cip_institution_mapping_new.json');
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);
}
}
fetchCipData();
}, []);
// Fetch iPEDS data (example)
useEffect(() => {
async function fetchIpedsData() {
try {
const res = await fetch('/ic2023_ay.csv');
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);
}
}
fetchIpedsData();
}, []);
// Handle school name input
const handleSchoolChange = (e) => {
const value = e.target.value;
setData(prev => ({
...prev,
selected_school: value,
selected_program: '',
program_type: '',
credit_hours_required: '',
}));
const filtered = schoolData.filter(s =>
s.INSTNM.toLowerCase().includes(value.toLowerCase())
);
const uniqueSchools = [...new Set(filtered.map(s => s.INSTNM))];
setSchoolSuggestions(uniqueSchools.slice(0, 10));
setProgramSuggestions([]);
setAvailableProgramTypes([]);
};
const handleSchoolSelect = (schoolName) => {
setData(prev => ({
...prev,
selected_school: schoolName,
selected_program: '',
program_type: '',
credit_hours_required: '',
}));
setSchoolSuggestions([]);
setProgramSuggestions([]);
setAvailableProgramTypes([]);
};
const handleProgramChange = (e) => {
const value = e.target.value;
setData(prev => ({ ...prev, selected_program: value }));
if (!value) {
setProgramSuggestions([]);
return;
}
const filtered = schoolData.filter(
s => s.INSTNM.toLowerCase() === selected_school.toLowerCase() &&
s.CIPDESC.toLowerCase().includes(value.toLowerCase())
);
const uniquePrograms = [...new Set(filtered.map(s => s.CIPDESC))];
setProgramSuggestions(uniquePrograms.slice(0, 10));
};
const handleProgramSelect = (prog) => {
setData(prev => ({ ...prev, selected_program: prog }));
setProgramSuggestions([]);
};
const handleProgramTypeSelect = (e) => {
const val = e.target.value;
setData(prev => ({
...prev,
program_type: val,
credit_hours_required: '',
}));
setManualProgramLength('');
setAutoProgramLength('0.00');
};
// once we have school + program, load possible program types
useEffect(() => {
if (!selected_program || !selected_school || !schoolData.length) return;
const possibleTypes = schoolData
.filter(
s => s.INSTNM.toLowerCase() === selected_school.toLowerCase() &&
s.CIPDESC === selected_program
)
.map(s => s.CREDDESC);
setAvailableProgramTypes([...new Set(possibleTypes)]);
}, [selected_program, selected_school, schoolData]);
// auto-calc tuition
useEffect(() => {
if (!icTuitionData.length) return;
if (!selected_school || !program_type || !credit_hours_per_year) return;
const found = schoolData.find(
s => s.INSTNM.toLowerCase() === selected_school.toLowerCase()
);
if (!found) return;
const unitId = found.UNITID;
if (!unitId) return;
const match = icTuitionData.find(row => row.UNITID === 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, selected_school, program_type,
credit_hours_per_year, is_in_state, is_in_district, schoolData
]);
// auto-calc program length
useEffect(() => {
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);
const calcLength = yrs.toFixed(2);
setAutoProgramLength(calcLength);
}, [program_type, hours_completed, credit_hours_per_year, credit_hours_required]);
// final handleSubmit
const handleSubmit = () => {
const chosenTuition = manualTuition.trim() === ''
? autoTuition
: parseFloat(manualTuition);
const chosenProgramLength = manualProgramLength.trim() === ''
? autoProgramLength
: manualProgramLength;
setData(prev => ({
...prev,
tuition: chosenTuition,
program_length: chosenProgramLength
}));
nextStep();
};
// displayedTuition / displayedProgramLength
const displayedTuition = (manualTuition.trim() === '' ? autoTuition : manualTuition);
const displayedProgramLength = (manualProgramLength.trim() === '' ? autoProgramLength : manualProgramLength);
return (
<div className="max-w-md mx-auto p-6 space-y-4">
<h2 className="text-2xl font-semibold">College Details</h2>
{(college_enrollment_status === 'currently_enrolled' ||
college_enrollment_status === 'prospective_student') ? (
<div className="space-y-4">
{/* In District, In State, Online, etc. */}
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="is_in_district"
checked={is_in_district}
onChange={handleParentFieldChange}
className="h-4 w-4"
/>
<label className="font-medium">In District?</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="is_in_state"
checked={is_in_state}
onChange={handleParentFieldChange}
className="h-4 w-4"
/>
<label className="font-medium">In State Tuition?</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="is_online"
checked={is_online}
onChange={handleParentFieldChange}
className="h-4 w-4"
/>
<label className="font-medium">Program is Fully Online</label>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
name="loan_deferral_until_graduation"
checked={loan_deferral_until_graduation}
onChange={handleParentFieldChange}
className="h-4 w-4"
/>
<label className="font-medium">Defer Loan Payments until Graduation?</label>
</div>
{/* School / Program */}
<div className="space-y-1">
<label className="block font-medium">School Name*</label>
<input
name="selected_school"
value={selected_school}
onChange={handleSchoolChange}
list="school-suggestions"
placeholder="Enter school name..."
className="w-full border rounded p-2"
/>
<datalist id="school-suggestions">
{schoolSuggestions.map((sch, idx) => (
<option
key={idx}
value={sch}
onClick={() => handleSchoolSelect(sch)}
/>
))}
</datalist>
</div>
<div className="space-y-1">
<label className="block font-medium">Program Name*</label>
<input
name="selected_program"
value={selected_program}
onChange={handleProgramChange}
list="program-suggestions"
placeholder="Enter program name..."
className="w-full border rounded p-2"
/>
<datalist id="program-suggestions">
{programSuggestions.map((prog, idx) => (
<option
key={idx}
value={prog}
onClick={() => handleProgramSelect(prog)}
/>
))}
</datalist>
</div>
<div className="space-y-1">
<label className="block font-medium">Program Type*</label>
<select
name="program_type"
value={program_type}
onChange={handleProgramTypeSelect}
className="w-full border rounded p-2"
>
<option value="">Select Program Type</option>
{availableProgramTypes.map((t, i) => (
<option key={i} value={t}>{t}</option>
))}
</select>
</div>
{/* Academic Calendar (just re-added) */}
<div className="space-y-1">
<label className="block font-medium">Academic Calendar</label>
<select
name="academic_calendar"
value={academic_calendar}
onChange={handleParentFieldChange}
className="w-full border rounded p-2"
>
<option value="semester">Semester</option>
<option value="quarter">Quarter</option>
<option value="trimester">Trimester</option>
<option value="other">Other</option>
</select>
</div>
{/* If Grad/Professional or other that needs credit_hours_required */}
{(program_type === 'Graduate/Professional Certificate' ||
program_type === 'First Professional Degree' ||
program_type === 'Doctoral Degree') && (
<div className="space-y-1">
<label className="block font-medium">Credit Hours Required</label>
<input
type="number"
name="credit_hours_required"
value={credit_hours_required}
onChange={handleParentFieldChange}
placeholder="e.g. 30"
className="w-full border rounded p-2"
/>
</div>
)}
<div className="space-y-1">
<label className="block font-medium">Credit Hours Per Year</label>
<input
type="number"
name="credit_hours_per_year"
value={credit_hours_per_year}
onChange={handleParentFieldChange}
placeholder="e.g. 24"
className="w-full border rounded p-2"
/>
</div>
{/* Tuition (auto or override) */}
<div className="space-y-1">
<label className="block font-medium">Yearly Tuition</label>
<input
type="number"
value={displayedTuition}
onChange={handleManualTuitionChange}
placeholder="Leave blank to use auto, or type an override"
className="w-full border rounded p-2"
/>
</div>
{/* Annual Financial Aid with "Need Help?" Wizard button */}
<div className="space-y-1">
<label className="block font-medium">(Estimated) Annual Financial Aid</label>
<div className="flex space-x-2">
<input
type="number"
name="annual_financial_aid"
value={annual_financial_aid}
onChange={handleParentFieldChange}
placeholder="e.g. 2000"
className="w-full border rounded p-2"
/>
<button
type="button"
onClick={() => setShowAidWizard(true)}
className="bg-blue-600 text-center px-3 py-2 rounded"
>
Need Help?
</button>
</div>
</div>
<div className="space-y-1">
<label className="block font-medium">Existing College Loan Debt</label>
<input
type="number"
name="existing_college_debt"
value={existing_college_debt}
onChange={handleParentFieldChange}
placeholder="e.g. 2000"
className="w-full border rounded p-2"
/>
</div>
{/* If "currently_enrolled" show Hours Completed + Program Length */}
{college_enrollment_status === 'currently_enrolled' && (
<>
<div className="space-y-1">
<label className="block font-medium">Hours Completed</label>
<input
type="number"
name="hours_completed"
value={hours_completed}
onChange={handleParentFieldChange}
placeholder="Credit hours done"
className="w-full border rounded p-2"
/>
</div>
<div className="space-y-1">
<label className="block font-medium">Program Length</label>
<input
type="number"
value={displayedProgramLength}
onChange={handleManualProgramLengthChange}
placeholder="Leave blank to use auto, or type an override"
className="w-full border rounded p-2"
/>
</div>
</>
)}
<div className="space-y-1">
<label className="block font-medium">Expected Graduation</label>
<input
type="date"
name="expected_graduation"
value={expected_graduation}
onChange={handleParentFieldChange}
className="w-full border rounded p-2"
/>
</div>
<div className="space-y-1">
<label className="block font-medium">Loan Interest Rate (%)</label>
<input
type="number"
name="interest_rate"
value={interest_rate}
onChange={handleParentFieldChange}
placeholder="e.g. 5.5"
className="w-full border rounded p-2"
/>
</div>
<div className="space-y-1">
<label className="block font-medium">Loan Term (years)</label>
<input
type="number"
name="loan_term"
value={loan_term}
onChange={handleParentFieldChange}
placeholder="e.g. 10"
className="w-full border rounded p-2"
/>
</div>
<div className="space-y-1">
<label className="block font-medium">Extra Monthly Payment</label>
<input
type="number"
name="extra_payment"
value={extra_payment}
onChange={handleParentFieldChange}
placeholder="Optional"
className="w-full border rounded p-2"
/>
</div>
<div className="space-y-1">
<label className="block font-medium">Expected Salary After Graduation</label>
<input
type="number"
name="expected_salary"
value={expected_salary}
onChange={handleParentFieldChange}
placeholder="e.g. 65000"
className="w-full border rounded p-2"
/>
</div>
</div>
) : (
<p>Not currently enrolled or prospective student. Skipping college onboarding.</p>
)}
<div className="pt-4">
<button
onClick={prevStep}
style={{ marginRight: '1rem' }}
className="bg-gray-200 hover:bg-gray-300 text-gray-700 font-semibold py-2 px-4 rounded"
>
Previous
</button>
<button
onClick={handleSubmit}
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded"
>
Finish Onboarding
</button>
</div>
{/* RENDER THE MODAL WITH FINANCIAL AID WIZARD IF showAidWizard === true */}
{showAidWizard && (
<Modal onClose={() => setShowAidWizard(false)}>
<FinancialAidWizard
onAidEstimated={(estimate) => {
// Update the annual_financial_aid with the wizard's result
setData(prev => ({
...prev,
annual_financial_aid: estimate
}));
}}
onClose={() => setShowAidWizard(false)}
/>
</Modal>
)}
</div>
);
}
export default CollegeOnboarding;