dev1/src/components/LoanRepayment.js

266 lines
10 KiB
JavaScript

import React, { useState } from 'react';
import { Button } from './ui/button.js';
/**
* LoanRepayment
* ────────────────────────────────────────────────────────────────
* If `schools.length === 0` we first show a quick-fill estimator
* that lets a free-tier user type a school name, choose “degree type”,
* and enter an annual-tuition guess. We then create a minimal
* school object and push it into `schools`, after which the full
* repayment form (your original code) appears.
*/
function LoanRepayment({
/* ORIGINAL PROPS */
schools = [],
salaryData,
setResults,
setLoading,
setPersistedROI,
programLength,
/* NEW: parent must hand us a setter so we can inject the stub */
setSchools,
}) {
/* ------------------------- quick-fill state ------------------------- */
const [scratch, setScratch] = useState({
school: '',
programType: '',
tuition: '',
});
/* ------------------------- calculator state ------------------------ */
const [expectedSalary, setExpectedSalary] = useState(0);
const [tuitionType, setTuitionType] = useState('inState');
const [interestRate, setInterestRate] = useState(5.5);
const [loanTerm, setLoanTerm] = useState(10);
const [extraPayment, setExtraPayment] = useState(0);
const [currentSalary, setCurrentSalary] = useState(0);
const [error, setError] = useState(null);
/* ------------------------- validation ------------------------------ */
const validateInputs = () => {
if (!schools?.length) { setError('Missing school data.'); return false; }
if (isNaN(interestRate)||interestRate<=0) { setError('Interest rate > 0'); return false; }
if (isNaN(loanTerm)||loanTerm<=0) { setError('Loan term > 0'); return false; }
if (isNaN(extraPayment)||extraPayment<0) { setError('Extra pmt ≥ 0'); return false; }
if (isNaN(currentSalary)||currentSalary<0){ setError('Current salary ≥0');return false; }
if (isNaN(expectedSalary)||expectedSalary<0){setError('Expected salary ≥0');return false;}
setError(null);
return true;
};
/* ------------------------- main calculation ----------------------- */
const calculateLoanDetails = () => {
if (!validateInputs()) return;
setLoading?.(true);
const pickTuition = (school, resid, grad) => {
const tryNum = v => isNaN(v) ? 0 : Number(v);
if (grad) {
return resid === 'inState'
? tryNum(school.inStateGraduate) || tryNum(school.inState) || tryNum(school.tuition)
: tryNum(school.outStateGraduate) || tryNum(school.outOfState)|| tryNum(school.tuition);
}
/* under-grad */
return resid === 'inState'
? tryNum(school.inState) || tryNum(school.inStateGraduate) || tryNum(school.tuition)
: tryNum(school.outOfState) || tryNum(school.outStateGraduate) || tryNum(school.tuition);
};
const results = schools.map((school) => {
/* your existing repayment logic — unchanged */
const programLen = Number(school.programLength);
let ugYears=0, gradYears=0;
if (school.degreeType.includes('Associate')) ugYears=2;
else if (school.degreeType.includes('Bachelor')) ugYears=4;
else if (school.degreeType.includes('Master')) { ugYears=4; gradYears=2; }
else if (school.degreeType.includes('First Professional')||school.degreeType.includes('Doctoral'))
{ ugYears=4; gradYears=4; }
else if (school.degreeType.includes('Certificate')) ugYears=1;
else { ugYears=Math.min(programLen,4); gradYears=Math.max(programLen-4,0); }
const ugTuit = pickTuition(school, tuitionType, /*grad?*/ false);
const gradTuit = pickTuition(school, tuitionType, /*grad?*/ true);
let totalTuition = ugYears * ugTuit +
gradYears* gradTuit;
const r = Number(interestRate)/12/100;
const n = Number(loanTerm)*12;
const pmtMin = totalTuition * (r*Math.pow(1+r,n))/(Math.pow(1+r,n)-1);
const pmt = Number(pmtMin)+Number(extraPayment);
let bal=totalTuition, months=0;
while (bal>0 && months<n*2){
months++;
const interest = bal*r;
bal -= Math.max(pmt - interest,0);
}
const totalCost = pmt*months;
/* safe net-gain estimate */
const salary = Number(expectedSalary)||0;
const cur = Number(currentSalary)||0;
const netGain = salary
? (salary*loanTerm - totalCost - cur*loanTerm*Math.pow(1.03,loanTerm)).toFixed(2)
: (-totalCost).toFixed(2);
return {
...school,
totalTuition : totalTuition.toFixed(2),
monthlyPayment: pmtMin.toFixed(2),
totalMonthlyPayment: pmt.toFixed(2),
totalLoanCost : totalCost.toFixed(2),
netGain,
monthlySalary : (salary/12).toFixed(2),
};
});
setResults?.(results);
setLoading?.(false);
};
/* ================================================================= */
/* QUICK-FILL PANEL (only when schools.length === 0) */
/* ================================================================= */
if (!schools || schools.length === 0) {
const ready = scratch.tuition;
return (
<div className="border rounded p-6 space-y-4 max-w-md mx-auto">
<h2 className="text-lg font-semibold text-center">
Estimate student-loan payments
</h2>
<input
className="border rounded p-2 w-full"
placeholder="School name *"
value={scratch.school}
onChange={(e)=>setScratch({...scratch,school:e.target.value})}
/>
<select
className="border rounded p-2 w-full"
value={scratch.programType}
onChange={(e)=>setScratch({...scratch,programType:e.target.value})}
>
<option value="">Degree / program type *</option>
<option>Associate's Degree</option>
<option>Bachelor's Degree</option>
<option>Master's Degree</option>
<option>Doctoral Degree</option>
<option>Graduate/Professional Certificate</option>
<option>First Professional Degree</option>
</select>
<input
type="number"
className="border rounded p-2 w-full"
placeholder="Estimated annual tuition *"
value={scratch.tuition}
onChange={(e)=>setScratch({...scratch,tuition:e.target.value})}
/>
<Button
className="w-full"
disabled={!ready}
onClick={()=>{
const t = Number(scratch.tuition);
const stub = {
INSTNM : scratch.school || '(self-entered)', // ← if blank
degreeType: scratch.programType || 'Unknown', // ← if blank
programLength: 4,
inState: t,
outOfState: t,
inStateGraduate: t,
outStateGraduate: t,
};
setSchools([stub]); // now main form will appear
}}
>
Continue
</Button>
</div>
);
}
/* ================================================================= */
/* ORIGINAL DETAILED REPAYMENT FORM */
/* ================================================================= */
return (
<div className="loan-repayment-container max-w-xl mx-auto">
<form onSubmit={(e)=>{e.preventDefault();calculateLoanDetails();}}
className="space-y-4 border rounded p-6">
{/* Tuition type */}
<div className="input-group">
<label className="block font-medium">Tuition Type</label>
<select
value={tuitionType}
onChange={(e)=>setTuitionType(e.target.value)}
className="border rounded p-2 w-full"
>
<option value="inState">In-State</option>
<option value="outOfState">Out-of-State</option>
</select>
</div>
{/* Interest rate */}
<div className="input-group">
<label className="block font-medium">Interest Rate (%)</label>
<input type="number"
value={interestRate}
onChange={(e)=>setInterestRate(e.target.value)}
className="border rounded p-2 w-full"/>
</div>
{/* Loan term */}
<div className="input-group">
<label className="block font-medium">Loan Term (years)</label>
<input type="number"
value={loanTerm}
onChange={(e)=>setLoanTerm(e.target.value)}
className="border rounded p-2 w-full"/>
</div>
{/* Extra payment */}
<div className="input-group">
<label className="block font-medium">Extra Monthly Payment</label>
<input type="number"
value={extraPayment}
onChange={(e)=>setExtraPayment(e.target.value)}
className="border rounded p-2 w-full"/>
</div>
{/* Current salary */}
<div className="input-group">
<label className="block font-medium">Current Salary</label>
<input type="number"
value={currentSalary}
onChange={(e)=>setCurrentSalary(e.target.value)}
className="border rounded p-2 w-full"/>
</div>
{/* Expected salary */}
<div className="input-group">
<label className="block font-medium">Expected Salary</label>
<input type="number"
value={expectedSalary}
onChange={(e)=>setExpectedSalary(e.target.value)}
className="border rounded p-2 w-full"/>
</div>
{/* Submit */}
<div className="text-right">
<Button type="submit">Calculate</Button>
</div>
{error && <div className="text-red-600 text-sm">{error}</div>}
</form>
</div>
);
}
export default LoanRepayment;