Added sorting and filtering for schools and loan repayment
This commit is contained in:
parent
ffe8e1fe72
commit
75e967c9f2
@ -7,7 +7,7 @@ function LoanRepayment({
|
|||||||
setResults,
|
setResults,
|
||||||
setLoading,
|
setLoading,
|
||||||
}) {
|
}) {
|
||||||
const [expectedSalary, setExpectedSalary] = useState('');
|
const [expectedSalary, setExpectedSalary] = useState(0);
|
||||||
const [tuitionType, setTuitionType] = useState('inState'); // Tuition type: inState or outOfState
|
const [tuitionType, setTuitionType] = useState('inState'); // Tuition type: inState or outOfState
|
||||||
const [interestRate, setInterestRate] = useState(5.5); // Interest rate
|
const [interestRate, setInterestRate] = useState(5.5); // Interest rate
|
||||||
const [loanTerm, setLoanTerm] = useState(10); // Loan term in years
|
const [loanTerm, setLoanTerm] = useState(10); // Loan term in years
|
||||||
@ -86,8 +86,8 @@ function LoanRepayment({
|
|||||||
const totalLoanCost = extraMonthlyPayment * monthsWithExtra;
|
const totalLoanCost = extraMonthlyPayment * monthsWithExtra;
|
||||||
|
|
||||||
let salary = Number(expectedSalary) || 0;
|
let salary = Number(expectedSalary) || 0;
|
||||||
let netGain = 'N/A';
|
let netGain = 0;
|
||||||
let monthlySalary = 'N/A';
|
let monthlySalary = 0;
|
||||||
|
|
||||||
if (salary > 0) {
|
if (salary > 0) {
|
||||||
const totalSalary = salary * loanTerm;
|
const totalSalary = salary * loanTerm;
|
||||||
@ -150,7 +150,8 @@ function LoanRepayment({
|
|||||||
<input type="number"
|
<input type="number"
|
||||||
value={currentSalary}
|
value={currentSalary}
|
||||||
onChange={(e) => setCurrentSalary(e.target.value)}
|
onChange={(e) => setCurrentSalary(e.target.value)}
|
||||||
placeholder="Enter your current salary" />
|
placeholder="Enter your current salary"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<label>Expected Salary:</label>
|
<label>Expected Salary:</label>
|
||||||
|
@ -100,40 +100,10 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 🔹 Filter Section Styling */
|
.filter-controls {
|
||||||
.filter-section {
|
display: flex;
|
||||||
background: #f8f8f8;
|
align-items: center;
|
||||||
padding: 10px;
|
gap: 20px;
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-section h3 {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-section label {
|
|
||||||
display: block;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-section input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-section select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Schools section: Grid layout with clear separation */
|
/* Schools section: Grid layout with clear separation */
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ClipLoader } from 'react-spinners';
|
import { ClipLoader } from 'react-spinners';
|
||||||
import LoanRepayment from './LoanRepayment.js';
|
import LoanRepayment from './LoanRepayment.js';
|
||||||
|
import SchoolFilters from './SchoolFilters';
|
||||||
import './PopoutPanel.css';
|
import './PopoutPanel.css';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
@ -15,6 +16,9 @@ function PopoutPanel({
|
|||||||
const [results, setResults] = useState([]); // Store loan repayment calculation results
|
const [results, setResults] = useState([]); // Store loan repayment calculation results
|
||||||
const [loadingCalculation, setLoadingCalculation] = useState(false);
|
const [loadingCalculation, setLoadingCalculation] = useState(false);
|
||||||
const [persistedROI, setPersistedROI] = useState({});
|
const [persistedROI, setPersistedROI] = useState({});
|
||||||
|
const [sortBy, setSortBy] = useState('tuition'); // Default sorting
|
||||||
|
const [tuitionRange, setTuitionRange] = useState([0, 50000]);
|
||||||
|
const [distanceRange, setDistanceRange] = useState([0, 200]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
jobDescription = null,
|
jobDescription = null,
|
||||||
@ -26,10 +30,9 @@ function PopoutPanel({
|
|||||||
} = data || {};
|
} = data || {};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
setResults([]);
|
setResults([]);
|
||||||
setIsCalculated(false);
|
setIsCalculated(false);
|
||||||
}, [schools]);
|
}, [sortBy, tuitionRange, distanceRange]);
|
||||||
|
|
||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
|
|
||||||
@ -59,6 +62,31 @@ function PopoutPanel({
|
|||||||
closePanel(); // Maintain existing close behavior
|
closePanel(); // Maintain existing close behavior
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 🔹 Apply Sorting & Filtering Directly at Render Time **/
|
||||||
|
const filteredAndSortedSchools = [...schools]
|
||||||
|
.filter(school => {
|
||||||
|
// Convert tuition to a number
|
||||||
|
const inStateCost = parseFloat(school['In_state cost']);
|
||||||
|
|
||||||
|
// Remove " mi" from distance and convert it to a number
|
||||||
|
const distance = parseFloat(school['distance'].replace(' mi', ''));
|
||||||
|
|
||||||
|
console.log(`Filtering School: ${school['INSTNM']} | Tuition: ${inStateCost} | Distance: ${distance}`);
|
||||||
|
|
||||||
|
return (
|
||||||
|
inStateCost >= tuitionRange[0] &&
|
||||||
|
inStateCost <= tuitionRange[1] &&
|
||||||
|
distance >= distanceRange[0] &&
|
||||||
|
distance <= distanceRange[1]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (sortBy === 'tuition') return a['In_state cost'] - b['In_state cost'];
|
||||||
|
if (sortBy === 'distance') return a['distance'] - b['distance'];
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="popout-panel">
|
<div className="popout-panel">
|
||||||
<button onClick={handleClosePanel}>Close</button>
|
<button onClick={handleClosePanel}>Close</button>
|
||||||
@ -124,16 +152,74 @@ function PopoutPanel({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Sorting & Filtering UI */}
|
||||||
|
<div className="filter-controls">
|
||||||
|
<div>
|
||||||
|
<label>Sort by:</label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="sort"
|
||||||
|
value="tuition"
|
||||||
|
checked={sortBy === 'tuition'}
|
||||||
|
onChange={() => setSortBy('tuition')}
|
||||||
|
/> Tuition
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="sort"
|
||||||
|
value="distance"
|
||||||
|
checked={sortBy === 'distance'}
|
||||||
|
onChange={() => setSortBy('distance')}
|
||||||
|
/> Distance
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>Tuition Range: ${tuitionRange[0]} - ${tuitionRange[1]}</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="50000"
|
||||||
|
step="500"
|
||||||
|
value={tuitionRange[0]}
|
||||||
|
onChange={(e) => setTuitionRange([Number(e.target.value), tuitionRange[1]])}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="50000"
|
||||||
|
step="500"
|
||||||
|
value={tuitionRange[1]}
|
||||||
|
onChange={(e) => setTuitionRange([tuitionRange[0], Number(e.target.value)])}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label>Distance Range: {distanceRange[0]} - {distanceRange[1]} mi</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="200"
|
||||||
|
step="5"
|
||||||
|
value={distanceRange[0]}
|
||||||
|
onChange={(e) => setDistanceRange([Number(e.target.value), distanceRange[1]])}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="200"
|
||||||
|
step="5"
|
||||||
|
value={distanceRange[1]}
|
||||||
|
onChange={(e) => setDistanceRange([distanceRange[0], Number(e.target.value)])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Schools Offering Programs Section */}
|
{/* Schools Offering Programs Section */}
|
||||||
<h3>Schools Offering Programs</h3>
|
<h3>Schools Offering Programs</h3>
|
||||||
<div className="schools-offering">
|
<div className="schools-offering">
|
||||||
{schools.length > 0 ? (
|
{filteredAndSortedSchools.length > 0 ? (
|
||||||
schools.map((school, index) => (
|
filteredAndSortedSchools.map((school, index) => (
|
||||||
<div key={index} className="school-card">
|
<div key={index} className="school-card">
|
||||||
<div><strong>{school['INSTNM']}</strong></div>
|
<div><strong>{school['INSTNM']}</strong></div>
|
||||||
<div>Degree Type: {school['CREDDESC'] || 'Degree type information is not available for this program'}</div>
|
<div>Degree Type: {school['CREDDESC'] || 'Degree type not available for this program'}</div>
|
||||||
<div>In-State Tuition: ${school['In_state cost'] || 'Tuition information is not available for this school'}</div>
|
<div>In-State Tuition: ${school['In_state cost'] || 'Tuition not available for this school'}</div>
|
||||||
<div>Out-of-State Tuition: ${school['Out_state cost'] || 'Tuition information is not available for this school'}</div>
|
<div>Out-of-State Tuition: ${school['Out_state cost'] || 'Tuition not available for this school'}</div>
|
||||||
<div>Distance: {school['distance'] || 'Distance to school not available'}</div>
|
<div>Distance: {school['distance'] || 'Distance to school not available'}</div>
|
||||||
<div>
|
<div>
|
||||||
Website: <a href={school['Website']} target="_blank" rel="noopener noreferrer">{school['Website']}</a>
|
Website: <a href={school['Website']} target="_blank" rel="noopener noreferrer">{school['Website']}</a>
|
||||||
@ -148,7 +234,7 @@ function PopoutPanel({
|
|||||||
{/* Loan Repayment Analysis */}
|
{/* Loan Repayment Analysis */}
|
||||||
<h3>Loan Repayment Analysis</h3>
|
<h3>Loan Repayment Analysis</h3>
|
||||||
<LoanRepayment
|
<LoanRepayment
|
||||||
schools={schools.map(school => ({
|
schools={filteredAndSortedSchools.map(school => ({
|
||||||
schoolName: school['INSTNM'],
|
schoolName: school['INSTNM'],
|
||||||
inState: parseFloat(school['In_state cost']) || 0,
|
inState: parseFloat(school['In_state cost']) || 0,
|
||||||
outOfState: parseFloat(school['Out_state cost']) || 0,
|
outOfState: parseFloat(school['Out_state cost']) || 0,
|
||||||
|
115
src/components/SchoolFilters
Normal file
115
src/components/SchoolFilters
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import './SchoolFilters.css';
|
||||||
|
|
||||||
|
function SchoolFilters({ schools, setFilteredSchools }) {
|
||||||
|
const [sortBy, setSortBy] = useState('tuition'); // Default: Sort by Tuition
|
||||||
|
const [tuitionRange, setTuitionRange] = useState([0, 50000]); // Example range
|
||||||
|
const [distanceRange, setDistanceRange] = useState([0, 200]); // Example range
|
||||||
|
|
||||||
|
// Sorting function
|
||||||
|
const sortSchools = (schools) => {
|
||||||
|
return [...schools].sort((a, b) => {
|
||||||
|
if (sortBy === 'tuition') return a.inState - b.inState;
|
||||||
|
if (sortBy === 'distance') return a.distance - b.distance;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filtering function
|
||||||
|
const filterSchools = (schools) => {
|
||||||
|
return schools.filter((school) =>
|
||||||
|
school.inState >= tuitionRange[0] &&
|
||||||
|
school.inState <= tuitionRange[1] &&
|
||||||
|
school.distance >= distanceRange[0] &&
|
||||||
|
school.distance <= distanceRange[1]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply sorting & filtering when values change
|
||||||
|
const updateFilteredSchools = () => {
|
||||||
|
let updatedSchools = sortSchools(schools);
|
||||||
|
updatedSchools = filterSchools(updatedSchools);
|
||||||
|
setFilteredSchools(updatedSchools);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Effect to update results on change
|
||||||
|
React.useEffect(() => {
|
||||||
|
updateFilteredSchools();
|
||||||
|
}, [sortBy, tuitionRange, distanceRange, schools]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="filters-container">
|
||||||
|
<h3>Sort & Filter Schools</h3>
|
||||||
|
|
||||||
|
{/* Sorting Options */}
|
||||||
|
<div className="sorting-options">
|
||||||
|
<label>Sort by:</label>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="sortTuition"
|
||||||
|
name="sort"
|
||||||
|
value="tuition"
|
||||||
|
checked={sortBy === 'tuition'}
|
||||||
|
onChange={() => setSortBy('tuition')}
|
||||||
|
/>
|
||||||
|
<label htmlFor="sortTuition">Tuition</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="sortDistance"
|
||||||
|
name="sort"
|
||||||
|
value="distance"
|
||||||
|
checked={sortBy === 'distance'}
|
||||||
|
onChange={() => setSortBy('distance')}
|
||||||
|
/>
|
||||||
|
<label htmlFor="sortDistance">Distance</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtering Options */}
|
||||||
|
<div className="filtering-options">
|
||||||
|
<label>Tuition Range: ${tuitionRange[0]} - ${tuitionRange[1]}</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="50000"
|
||||||
|
step="500"
|
||||||
|
value={tuitionRange[0]}
|
||||||
|
onChange={(e) => setTuitionRange([Number(e.target.value), tuitionRange[1]])}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="50000"
|
||||||
|
step="500"
|
||||||
|
value={tuitionRange[1]}
|
||||||
|
onChange={(e) => setTuitionRange([tuitionRange[0], Number(e.target.value)])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="filtering-options">
|
||||||
|
<label>Distance Range: {distanceRange[0]} - {distanceRange[1]} mi</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="200"
|
||||||
|
step="5"
|
||||||
|
value={distanceRange[0]}
|
||||||
|
onChange={(e) => setDistanceRange([Number(e.target.value), distanceRange[1]])}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="200"
|
||||||
|
step="5"
|
||||||
|
value={distanceRange[1]}
|
||||||
|
onChange={(e) => setDistanceRange([distanceRange[0], Number(e.target.value)])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SchoolFilters;
|
Loading…
Reference in New Issue
Block a user