dev1/src/components/PremiumOnboarding/CollegeOnboarding.js

563 lines
18 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.

import React, { useState, useEffect } from 'react';
import authFetch from '../../utils/authFetch.js';
function CollegeOnboarding({ nextStep, prevStep, data, setData, careerPathId }) {
// 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([]);
// ---- DESCTRUCTURE PARENT DATA FOR ALL FIELDS EXCEPT TUITION/PROGRAM_LENGTH ----
// We'll store user "typed" values for tuition/program_length in local states,
// but everything else comes directly from `data`.
const {
college_enrollment_status = '',
selected_school = '',
selected_program = '',
program_type = '',
academic_calendar = 'semester',
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 = true,
is_in_district = false,
loan_deferral_until_graduation = false,
credit_hours_per_year = '',
hours_completed = '',
credit_hours_required = '',
tuition_paid = '',
// We do NOT consume data.tuition or data.program_length directly here
// because we store them in local states (manualTuition, manualProgramLength).
} = data;
// ---- 1. LOCAL STATES for auto/manual logic on TWO fields ----
// manualTuition: user typed override
// autoTuition: iPEDS calculation
const [manualTuition, setManualTuition] = useState(''); // '' means no manual override
const [autoTuition, setAutoTuition] = useState(0);
// same approach for program_length
const [manualProgramLength, setManualProgramLength] = useState('');
const [autoProgramLength, setAutoProgramLength] = useState('0.00');
// ------------------------------------------
// Universal handleChange for all parent fields
// ------------------------------------------
const handleParentFieldChange = (e) => {
const { name, value, type, checked } = e.target;
let val = value;
if (type === 'checkbox') {
val = checked;
}
// parse numeric fields that are NOT tuition or program_length
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 }));
};
// ------------------------------------------
// handleManualTuition, handleManualProgramLength
// for local fields
// ------------------------------------------
const handleManualTuitionChange = (e) => {
// user typed something => override
setManualTuition(e.target.value); // store as string for partial typing
};
const handleManualProgramLengthChange = (e) => {
setManualProgramLength(e.target.value);
};
// ------------------------------------------
// CIP Data fetch once
// ------------------------------------------
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();
}, []);
// ------------------------------------------
// iPEDS Data fetch once
// ------------------------------------------
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();
}, []);
// ------------------------------------------
// handleSchoolChange, handleProgramChange, etc. => update parent fields
// ------------------------------------------
const handleSchoolChange = (e) => {
const value = e.target.value;
setData(prev => ({
...prev,
selected_school: value,
selected_program: '',
program_type: '',
credit_hours_required: '',
}));
// CIP suggestions
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(''); // reset manual override
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 => store in local autoTuition
// ------------------------------------------
useEffect(() => {
// do we have enough to calc?
if (!icTuitionData.length) return;
if (!selected_school || !program_type || !credit_hours_per_year) return;
// find row
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;
// grad or undergrad
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;
// threshold
if (chpy < 24 && partTimeRate) {
estimate = partTimeRate * chpy;
} else {
estimate = fullTimeTuition;
}
setAutoTuition(Math.round(estimate));
// We do NOT auto-update parent's data. We'll do that in handleSubmit or if you prefer, you can store it in parent's data anyway.
}, [
icTuitionData, selected_school, program_type,
credit_hours_per_year, is_in_state, is_in_district, schoolData
]);
// ------------------------------------------
// Auto-calc Program Length => store in local autoProgramLength
// ------------------------------------------
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]);
// ------------------------------------------
// handleSubmit => merges final chosen values
// ------------------------------------------
const handleSubmit = () => {
const chosenTuition = manualTuition.trim() === ''
? autoTuition
: parseFloat(manualTuition);
const chosenProgramLength = manualProgramLength.trim() === ''
? autoProgramLength
: manualProgramLength;
// Update parents data (collegeData)
setData(prev => ({
...prev,
tuition: chosenTuition, // match name used by parent or server
program_length: chosenProgramLength // match name used by parent
}));
// Then go to the next step in the parents wizard
nextStep();
};
// The displayed tuition => (manualTuition !== '' ? manualTuition : autoTuition)
const displayedTuition = (manualTuition.trim() === '' ? autoTuition : manualTuition);
// The displayed program length => (manualProgramLength !== '' ? manualProgramLength : autoProgramLength)
const displayedProgramLength = (manualProgramLength.trim() === '' ? autoProgramLength : manualProgramLength);
return (
<div>
<h2>College Details</h2>
{(college_enrollment_status === 'currently_enrolled' ||
college_enrollment_status === 'prospective_student') ? (
<>
<label>School Name*</label>
<input
name="selected_school"
value={selected_school}
onChange={handleSchoolChange}
list="school-suggestions"
placeholder="Enter school name..."
/>
<datalist id="school-suggestions">
{schoolSuggestions.map((sch, idx) => (
<option
key={idx}
value={sch}
onClick={() => handleSchoolSelect(sch)}
/>
))}
</datalist>
<label>Program Name*</label>
<input
name="selected_program"
value={selected_program}
onChange={handleProgramChange}
list="program-suggestions"
placeholder="Enter program name..."
/>
<datalist id="program-suggestions">
{programSuggestions.map((prog, idx) => (
<option
key={idx}
value={prog}
onClick={() => handleProgramSelect(prog)}
/>
))}
</datalist>
<label>Program Type*</label>
<select
name="program_type"
value={program_type}
onChange={handleProgramTypeSelect}
>
<option value="">Select Program Type</option>
{availableProgramTypes.map((t, i) => (
<option key={i} value={t}>{t}</option>
))}
</select>
{(program_type === 'Graduate/Professional Certificate' ||
program_type === 'First Professional Degree' ||
program_type === 'Doctoral Degree') && (
<>
<label>Credit Hours Required</label>
<input
type="number"
name="credit_hours_required"
value={credit_hours_required}
onChange={handleParentFieldChange}
placeholder="e.g. 30"
/>
</>
)}
<label>Credit Hours Per Year</label>
<input
type="number"
name="credit_hours_per_year"
value={credit_hours_per_year}
onChange={handleParentFieldChange}
placeholder="e.g. 24"
/>
<label>Yearly Tuition</label>
{/* If user typed a custom value => manualTuition, else autoTuition */}
<input
type="number"
value={displayedTuition}
onChange={handleManualTuitionChange}
placeholder="Leave blank to use auto, or type an override"
/>
<label>Annual Financial Aid</label>
<input
type="number"
name="annual_financial_aid"
value={annual_financial_aid}
onChange={handleParentFieldChange}
placeholder="e.g. 2000"
/>
<label>Existing College Loan Debt</label>
<input
type="number"
name="existing_college_debt"
value={existing_college_debt}
onChange={handleParentFieldChange}
placeholder="e.g. 2000"
/>
{college_enrollment_status === 'currently_enrolled' && (
<>
<label>Tuition Paid</label>
<input
type="number"
name="tuition_paid"
value={tuition_paid}
onChange={handleParentFieldChange}
placeholder="Already paid"
/>
<label>Hours Completed</label>
<input
type="number"
name="hours_completed"
value={hours_completed}
onChange={handleParentFieldChange}
placeholder="Credit hours done"
/>
<label>Program Length</label>
{/* If user typed a custom => manualProgramLength, else autoProgramLength */}
<input
type="number"
value={displayedProgramLength}
onChange={handleManualProgramLengthChange}
placeholder="Leave blank to use auto, or type an override"
/>
</>
)}
<label>Expected Graduation</label>
<input
type="date"
name="expected_graduation"
value={expected_graduation}
onChange={handleParentFieldChange}
/>
<label>Loan Interest Rate (%)</label>
<input
type="number"
name="interest_rate"
value={interest_rate}
onChange={handleParentFieldChange}
placeholder="e.g. 5.5"
/>
<label>Loan Term (years)</label>
<input
type="number"
name="loan_term"
value={loan_term}
onChange={handleParentFieldChange}
placeholder="e.g. 10"
/>
<label>Extra Monthly Payment</label>
<input
type="number"
name="extra_payment"
value={extra_payment}
onChange={handleParentFieldChange}
placeholder="Optional"
/>
<label>Expected Salary After Graduation</label>
<input
type="number"
name="expected_salary"
value={expected_salary}
onChange={handleParentFieldChange}
placeholder="e.g. 65000"
/>
<label>
<input
type="checkbox"
name="is_in_district"
checked={is_in_district}
onChange={handleParentFieldChange}
/>
In District?
</label>
<label>
<input
type="checkbox"
name="is_in_state"
checked={is_in_state}
onChange={handleParentFieldChange}
/>
In State Tuition?
</label>
<label>
<input
type="checkbox"
name="is_online"
checked={is_online}
onChange={handleParentFieldChange}
/>
Program is Fully Online
</label>
<label>
<input
type="checkbox"
name="loan_deferral_until_graduation"
checked={loan_deferral_until_graduation}
onChange={handleParentFieldChange}
/>
Defer Loan Payments until Graduation?
</label>
</>
) : (
<p>Not currently enrolled or prospective student. Skipping college onboarding.</p>
)}
<button onClick={prevStep} style={{ marginRight: '1rem' }}> Previous</button>
<button onClick={handleSubmit}>Finish Onboarding</button>
</div>
);
}
export default CollegeOnboarding;