url changes for prod and dev
This commit is contained in:
parent
9696943f9b
commit
67e3e962e7
BIN
Institution_data.xlsx
Normal file
BIN
Institution_data.xlsx
Normal file
Binary file not shown.
@ -8,6 +8,8 @@ import path from 'path';
|
|||||||
import { fileURLToPath } from 'url'; // Import fileURLToPath to handle the current file's URL
|
import { fileURLToPath } from 'url'; // Import fileURLToPath to handle the current file's URL
|
||||||
import { open } from 'sqlite'; // Use the open method directly from sqlite package
|
import { open } from 'sqlite'; // Use the open method directly from sqlite package
|
||||||
import sqlite3 from 'sqlite3';
|
import sqlite3 from 'sqlite3';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
|
||||||
dotenv.config({ path: path.resolve('/home/jcoakley/aptiva-dev1-app/.env') }); // Adjust the path based on your folder structure
|
dotenv.config({ path: path.resolve('/home/jcoakley/aptiva-dev1-app/.env') }); // Adjust the path based on your folder structure
|
||||||
console.log('ONET_USERNAME:', process.env.ONET_USERNAME);
|
console.log('ONET_USERNAME:', process.env.ONET_USERNAME);
|
||||||
@ -18,6 +20,8 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
const allowedOrigins = ['http://localhost:3000', 'http://34.16.120.118:3000', 'https://dev.aptivaai.com'];
|
const allowedOrigins = ['http://localhost:3000', 'http://34.16.120.118:3000', 'https://dev.aptivaai.com'];
|
||||||
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx'
|
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx'
|
||||||
|
const institutionJsonFilePath = path.join(__dirname, '../public/Institution_data.json');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 5001;
|
const PORT = process.env.PORT || 5001;
|
||||||
|
|
||||||
@ -65,7 +69,7 @@ app.use((req, res, next) => {
|
|||||||
'Authorization, Content-Type, Accept, Origin, X-Requested-With, Access-Control-Allow-Methods'
|
'Authorization, Content-Type, Accept, Origin, X-Requested-With, Access-Control-Allow-Methods'
|
||||||
);
|
);
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||||
} else if (req.path.includes('CIP_institution_mapping_fixed.json')) {
|
} else if (req.path.includes('Institution_data')) {
|
||||||
// Handle static JSON file CORS
|
// Handle static JSON file CORS
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*'); // Allow all origins for static file
|
res.setHeader('Access-Control-Allow-Origin', '*'); // Allow all origins for static file
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||||
@ -348,48 +352,72 @@ app.get('/api/cip/:socCode', (req, res) => {
|
|||||||
|
|
||||||
// Filtered schools endpoint
|
// Filtered schools endpoint
|
||||||
app.get('/api/schools', (req, res) => {
|
app.get('/api/schools', (req, res) => {
|
||||||
const { cipCode, state, level, type } = req.query;
|
const { cipCode, state } = req.query;
|
||||||
const filePath = path.join(__dirname, 'CIP_institution_mapping_fixed.json');
|
console.log('Query Params:', { cipCode, state });
|
||||||
|
|
||||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
try {
|
||||||
if (err) {
|
// Load Institution Data from JSON file
|
||||||
console.error('Error reading file:', err);
|
const schoolsData = JSON.parse(fs.readFileSync(institutionJsonFilePath, 'utf8'));
|
||||||
return res.status(500).json({ error: 'Failed to load data' });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
console.log('Loaded Schools Data:', schoolsData.length);
|
||||||
|
|
||||||
|
// Filter schools based on CIP Code and State
|
||||||
|
const filteredData = schoolsData.filter((school) => {
|
||||||
|
const cipCodeValue = school['CIP Code']?.toString().replace('.', '').slice(0, 4) || '';
|
||||||
|
const formattedCipCode = cipCode?.toString().replace('.', '').slice(0, 4) || '';
|
||||||
|
console.log('Comparing CIP Code:', cipCodeValue, 'with', formattedCipCode);
|
||||||
|
console.log('Comparing State:', school['State'], 'with', state);
|
||||||
|
return (
|
||||||
|
(!formattedCipCode || cipCodeValue.startsWith(formattedCipCode)) &&
|
||||||
|
(!state || school['State'] === state)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Filtered Schools Data:', filteredData.length);
|
||||||
|
res.json(filteredData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading or filtering schools data:', error.message);
|
||||||
|
res.status(500).json({ error: 'Failed to load or filter schools data.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Route to handle fetching tuition data using CIP code from College Scorecard API
|
// Route to handle fetching tuition data using CIP code from College Scorecard API
|
||||||
app.get('/api/tuition/:cipCode', async (req, res) => {
|
app.get('/api/tuition/:cipCode', (req, res) => {
|
||||||
const { cipCode } = req.params;
|
const { cipCode, state } = req.query;
|
||||||
const { state } = req.query;
|
console.log(`Received CIP Code: ${cipCode}, State: ${state}`);
|
||||||
|
|
||||||
console.log(`Received CIP Code: ${cipCode} for state: ${state}`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const apiKey = process.env.COLLEGE_SCORECARD_KEY;
|
const workbook = xlsx.readFile(institutionFilePath); // Load Excel file
|
||||||
const url = `https://api.data.gov/ed/collegescorecard/v1/schools?api_key=${apiKey}&programs.cip_4_digit.code=${cipCode}&school.state=${state}&fields=id,school.name,latest.cost.tuition.in_state,latest.cost.tuition.out_of_state`;
|
const sheet = workbook.Sheets[workbook.SheetNames[0]]; // First sheet
|
||||||
|
const schoolsData = xlsx.utils.sheet_to_json(sheet); // Convert to JSON
|
||||||
console.log(`Constructed URL: ${url}`);
|
|
||||||
|
|
||||||
const response = await axios.get(url);
|
console.log('Loaded Tuition Data:', schoolsData.length);
|
||||||
const data = response.data.results;
|
|
||||||
|
// Filter data by CIP Code and State
|
||||||
|
const filteredData = schoolsData.filter((school) => {
|
||||||
|
const cipCodeValue = school['CIP Code']?.toString().replace(/[^0-9]/g, ''); // Strip non-digits
|
||||||
|
const stateValue = school['State']?.toUpperCase().trim(); // Normalize state
|
||||||
|
|
||||||
console.log('College Scorecard API Response:', data); // Log the API response
|
console.log('CIP:', cipCodeValue, 'State:', stateValue); // Debug log each match attempt
|
||||||
|
console.log('Sample Records:');
|
||||||
if (data?.length > 0) {
|
console.log(schoolsData.slice(0, 5)); // Logs first 5 rows for debugging
|
||||||
res.status(200).json(data);
|
|
||||||
} else {
|
return (
|
||||||
res.status(404).json({ error: 'No tuition data found for the given CIP code and state.' });
|
(!cipCode || cipCodeValue === cipCode) && // Exact CIP code match
|
||||||
}
|
(!state || stateValue === state.toUpperCase().trim()) // Exact state match
|
||||||
} catch (error) {
|
);
|
||||||
res.status(500).json({
|
|
||||||
error: 'Failed to fetch tuition data from College Scorecard API',
|
|
||||||
details: error.response?.data || error.message
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('Filtered Tuition Data Count:', filteredData.length);
|
||||||
|
res.json(filteredData); // Send the filtered tuition data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading tuition data:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to load tuition data.' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Route to handle fetching economic projections for SOC code
|
// Route to handle fetching economic projections for SOC code
|
||||||
app.get('/api/projections/:socCode', (req, res) => {
|
app.get('/api/projections/:socCode', (req, res) => {
|
||||||
const { socCode } = req.params;
|
const { socCode } = req.params;
|
||||||
|
|||||||
3764081
public/Institution_data.json
Normal file
3764081
public/Institution_data.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -29,7 +29,8 @@ function Dashboard() {
|
|||||||
const [areaTitle, setAreaTitle] = useState(null);
|
const [areaTitle, setAreaTitle] = useState(null);
|
||||||
const [riaSecDescriptions, setRiaSecDescriptions] = useState([]);
|
const [riaSecDescriptions, setRiaSecDescriptions] = useState([]);
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_API_URL;
|
// Dynamic API URL
|
||||||
|
const apiUrl = process.env.REACT_APP_API_URL || '/api';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let descriptions = []; // Declare outside for scope accessibility
|
let descriptions = []; // Declare outside for scope accessibility
|
||||||
@ -49,7 +50,7 @@ function Dashboard() {
|
|||||||
const fetchUserProfile = async () => {
|
const fetchUserProfile = async () => {
|
||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
const profileResponse = await fetch(`http://localhost:5000/api/user-profile`, {
|
const profileResponse = await fetch(`${apiUrl}/user-profile`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ function Dashboard() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchUserProfile();
|
fetchUserProfile();
|
||||||
}, []);
|
}, [apiUrl]);
|
||||||
|
|
||||||
const handleCareerClick = useCallback(
|
const handleCareerClick = useCallback(
|
||||||
async (career) => {
|
async (career) => {
|
||||||
@ -92,22 +93,24 @@ function Dashboard() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Fetch CIP Code
|
// Step 1: Fetch CIP Code
|
||||||
const cipResponse = await fetch(`${apiUrl}/api/cip/${socCode}`);
|
const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`);
|
||||||
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
|
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
|
||||||
const { cipCode } = await cipResponse.json();
|
const { cipCode } = await cipResponse.json();
|
||||||
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
|
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Step 2: Fetch Data in Parallel
|
// Step 2: Fetch Data in Parallel
|
||||||
const [filteredSchools, economicResponse, tuitionResponse, salaryResponse] = await Promise.all([
|
const [filteredSchools, economicResponse, tuitionResponse, salaryResponse] = await Promise.all([
|
||||||
fetchSchools(cleanedCipCode, userState),
|
fetchSchools(cleanedCipCode, userState),
|
||||||
axios.get(`http://localhost:5001/api/projections/${socCode.split('.')[0]}`),
|
axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`),
|
||||||
axios.get(`http://localhost:5001/api/tuition/${cleanedCipCode}`, {
|
axios.get(`${apiUrl}/tuition/${cleanedCipCode}`, {
|
||||||
params: { state: userState },
|
params: { state: userState },
|
||||||
}),
|
}),
|
||||||
axios.get(`http://localhost:5001/api/salary`, {
|
axios.get(`${apiUrl}/salary`, {
|
||||||
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Step 3: Format Salary Data
|
// Step 3: Format Salary Data
|
||||||
const salaryDataPoints = [
|
const salaryDataPoints = [
|
||||||
|
|||||||
@ -7,33 +7,49 @@ function EconomicProjections({ socCode }) {
|
|||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
console.log(axios.defaults.baseURL); // Check if baseURL is set
|
console.log(axios.defaults.baseURL); // Check if baseURL is set
|
||||||
|
const apiUrl = process.env.REACT_APP_API_URL || '/api';
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (socCode) {
|
if (socCode) {
|
||||||
const cleanedSocCode = socCode.split('.')[0]; // Clean the SOC code inside the component
|
const cleanedSocCode = socCode.split('.')[0]; // Clean the SOC code inside the component
|
||||||
console.log(`Fetching economic projections for cleaned SOC code: ${cleanedSocCode}`);
|
console.log(`Fetching economic projections for cleaned SOC code: ${cleanedSocCode}`);
|
||||||
|
|
||||||
const fetchProjections = async () => {
|
const fetchProjections = async () => {
|
||||||
const projectionsUrl = process.env.NODE_ENV === 'production'
|
const projectionsUrl = `${apiUrl}/projections/${cleanedSocCode}`;
|
||||||
? '/api/projections/' // In production, routed through Nginx
|
// Log URL and SOC code only in development
|
||||||
: 'http://localhost:5001/api/projections/'; // In development, backend is running on localhost:5001
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
console.log(`Fetching projections from: ${projectionsUrl}`);
|
||||||
try {
|
console.log(`Cleaned SOC Code: ${cleanedSocCode}`);
|
||||||
const response = await axios.get(`http://localhost:5001/api/projections/${cleanedSocCode}`);
|
}
|
||||||
console.log('Projection Response:', response.data); // Log the projection response
|
try {
|
||||||
|
const cleanedSocCode = socCode.split('.')[0]; // Clean SOC code
|
||||||
setProjections(response.data); // Set the projections data in state
|
const projectionsUrl = `${apiUrl}/projections/${cleanedSocCode}`;
|
||||||
} catch (err) {
|
|
||||||
setError('Error fetching economic projections.');
|
// Log URL and SOC code only in development
|
||||||
console.error('Error fetching projections:', err);
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
console.log(`Fetching projections from: ${projectionsUrl}`);
|
||||||
|
console.log(`Cleaned SOC Code: ${cleanedSocCode}`);
|
||||||
}
|
}
|
||||||
};
|
// API call to fetch projections
|
||||||
|
const response = await axios.get(projectionsUrl);
|
||||||
|
|
||||||
|
if (response.status === 200 && response.data) {
|
||||||
|
setProjections(response.data); // Set projections
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
console.log('Projections Response:', response.data); // Log data in development
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid response from server.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('Error fetching economic projections.');
|
||||||
|
console.error('Projections Fetch Error:', err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
fetchProjections();
|
fetchProjections();
|
||||||
}
|
}
|
||||||
}, [socCode]); // This runs when the socCode prop changes
|
}, [socCode,apiUrl]); // This runs when the socCode prop changes
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div className="error">{error}</div>;
|
return <div className="error">{error}</div>;
|
||||||
|
|||||||
@ -18,9 +18,7 @@ const InterestInventory = () => {
|
|||||||
setLoading(true); // Start loading
|
setLoading(true); // Start loading
|
||||||
setError(null); // Reset error state
|
setError(null); // Reset error state
|
||||||
|
|
||||||
const baseUrl = process.env.REACT_APP_API_URL || 'http://localhost:5001'; // Default to localhost:5001 for development
|
const url = '/api/onet/questions?start=1&end=60';
|
||||||
const url = `${baseUrl}/api/onet/questions?start=1&end=60`; // Make sure the endpoint is correctly appended to the base URL
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -101,11 +99,8 @@ const InterestInventory = () => {
|
|||||||
try {
|
try {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
setError(null); // Clear previous errors
|
setError(null); // Clear previous errors
|
||||||
const baseUrl = process.env.NODE_ENV === 'production'
|
const url = `${process.env.REACT_APP_API_URL}/onet/submit_answers`;
|
||||||
? '/api/onet/submit_answers' // In production, this is proxied by Nginx to server2 (port 5001)
|
const response = await fetch(url, {
|
||||||
: 'http://localhost:5001/api/onet/submit_answers'; // In development, server2 runs on port 5001
|
|
||||||
|
|
||||||
const response = await fetch(baseUrl, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ answers }),
|
body: JSON.stringify({ answers }),
|
||||||
@ -165,7 +160,7 @@ const InterestInventory = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p>No questions available. Please try again later.</p>
|
<p>Loading questions.</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="pagination-buttons">
|
<div className="pagination-buttons">
|
||||||
|
|||||||
@ -1,28 +1,19 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
function LoanRepayment({ tuitionCosts, salaryData, earningHorizon }) {
|
function LoanRepayment({ schools, salaryData, earningHorizon }) {
|
||||||
const [tuitionType, setTuitionType] = useState('inState'); // 'inState' or 'outOfState'
|
const [tuitionType, setTuitionType] = useState('inState'); // 'inState' or 'outOfState'
|
||||||
const [interestRate, setInterestRate] = useState(5.5); // Default federal loan interest rate
|
const [interestRate, setInterestRate] = useState(5.5); // Default federal loan interest rate
|
||||||
const [loanTerm, setLoanTerm] = useState(10); // Default loan term (10 years)
|
const [loanTerm, setLoanTerm] = useState(10); // Default loan term (10 years)
|
||||||
const [extraPayment, setExtraPayment] = useState(0); // Extra monthly payment
|
const [extraPayment, setExtraPayment] = useState(0); // Extra monthly payment
|
||||||
const [results, setResults] = useState(null);
|
const [results, setResults] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
const [sortBy, setSortBy] = useState('monthlyPayment'); // Default sort option
|
||||||
|
|
||||||
// Validation function
|
// Validation function
|
||||||
const validateInputs = () => {
|
const validateInputs = () => {
|
||||||
console.log('Validating inputs...');
|
if (!schools || schools.length === 0 || !salaryData || salaryData.length === 0) {
|
||||||
console.log('Tuition Costs:', tuitionCosts);
|
setError('School or salary data is missing.');
|
||||||
console.log('Salary Data:', salaryData);
|
|
||||||
console.log('Tuition Type:', tuitionType);
|
|
||||||
|
|
||||||
if (!tuitionCosts || !salaryData || salaryData.length === 0) {
|
|
||||||
setError('Tuition or salary data is missing.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tuitionCosts[tuitionType] || tuitionCosts[tuitionType].length === 0) {
|
|
||||||
setError(`No tuition costs available for ${tuitionType} tuition.`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,30 +32,23 @@ function LoanRepayment({ tuitionCosts, salaryData, earningHorizon }) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setError(null); // Clear errors if all inputs are valid
|
setError(null); // Clear errors if valid
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Loan calculation function
|
// Loan calculation function for all schools
|
||||||
|
// Calculate loan details for each school
|
||||||
const calculateLoanDetails = () => {
|
const calculateLoanDetails = () => {
|
||||||
if (!validateInputs()) return;
|
const schoolResults = schools.map((school) => {
|
||||||
|
const tuition = tuitionType === 'inState' ? school.inState : school.outOfState;
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const tuition = tuitionCosts[tuitionType].reduce((total, year) => total + year, 0);
|
|
||||||
const monthlyRate = interestRate / 12 / 100;
|
const monthlyRate = interestRate / 12 / 100;
|
||||||
const loanTermMonths = loanTerm * 12;
|
const loanTermMonths = loanTerm * 12;
|
||||||
|
|
||||||
console.log('Total Tuition:', tuition);
|
// Calculate minimum monthly payment
|
||||||
console.log('Monthly Interest Rate:', monthlyRate);
|
|
||||||
|
|
||||||
// Minimum Monthly Payment
|
|
||||||
const minimumMonthlyPayment = tuition * (monthlyRate * Math.pow(1 + monthlyRate, loanTermMonths)) /
|
const minimumMonthlyPayment = tuition * (monthlyRate * Math.pow(1 + monthlyRate, loanTermMonths)) /
|
||||||
(Math.pow(1 + monthlyRate, loanTermMonths) - 1);
|
(Math.pow(1 + monthlyRate, loanTermMonths) - 1);
|
||||||
|
|
||||||
console.log('Minimum Monthly Payment:', minimumMonthlyPayment);
|
// Total loan cost with extra payments
|
||||||
|
|
||||||
// Loan details with extra payments
|
|
||||||
const extraMonthlyPayment = minimumMonthlyPayment + extraPayment;
|
const extraMonthlyPayment = minimumMonthlyPayment + extraPayment;
|
||||||
let remainingBalance = tuition;
|
let remainingBalance = tuition;
|
||||||
let monthsWithExtra = 0;
|
let monthsWithExtra = 0;
|
||||||
@ -74,59 +58,31 @@ function LoanRepayment({ tuitionCosts, salaryData, earningHorizon }) {
|
|||||||
const interest = remainingBalance * monthlyRate;
|
const interest = remainingBalance * monthlyRate;
|
||||||
const principal = extraMonthlyPayment - interest;
|
const principal = extraMonthlyPayment - interest;
|
||||||
remainingBalance -= principal;
|
remainingBalance -= principal;
|
||||||
|
|
||||||
if (remainingBalance < 0) remainingBalance = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loanTermWithExtraMonths = monthsWithExtra;
|
const totalLoanCost = extraMonthlyPayment * monthsWithExtra;
|
||||||
|
|
||||||
// Total Loan Costs
|
// Calculate net gain
|
||||||
const totalLoanCostMinimum = minimumMonthlyPayment * loanTermMonths;
|
const salary = salaryData[0].value; // 10th percentile salary as default
|
||||||
const totalLoanCostExtra = extraMonthlyPayment * loanTermWithExtraMonths;
|
const totalSalary = salary * earningHorizon;
|
||||||
|
const netGain = totalSalary - totalLoanCost;
|
||||||
|
|
||||||
console.log('Total Loan Cost (Minimum Payment):', totalLoanCostMinimum);
|
return {
|
||||||
console.log('Total Loan Cost (With Extra Payments):', totalLoanCostExtra);
|
...school,
|
||||||
|
|
||||||
// Results for each salary percentile
|
|
||||||
const salaryResults = salaryData.map((salaryPoint) => {
|
|
||||||
let totalSalary = 0;
|
|
||||||
for (let i = 0; i < earningHorizon; i++) {
|
|
||||||
totalSalary += salaryPoint.value * Math.pow(1 + salaryPoint.growthRate, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
const netGainMinimum = totalSalary - totalLoanCostMinimum;
|
|
||||||
const netGainExtra = totalSalary - totalLoanCostExtra;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...salaryPoint,
|
|
||||||
totalSalary: totalSalary.toFixed(2),
|
|
||||||
netGainMinimum: netGainMinimum.toFixed(2),
|
|
||||||
netGainExtra: netGainExtra.toFixed(2),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
setResults({
|
|
||||||
tuition,
|
tuition,
|
||||||
minimumMonthlyPayment: minimumMonthlyPayment.toFixed(2),
|
monthlyPayment: minimumMonthlyPayment.toFixed(2),
|
||||||
totalLoanCostMinimum: totalLoanCostMinimum.toFixed(2),
|
totalLoanCost: totalLoanCost.toFixed(2),
|
||||||
loanTermMonths,
|
netGain: netGain.toFixed(2),
|
||||||
extraMonthlyPayment: extraMonthlyPayment.toFixed(2),
|
};
|
||||||
totalLoanCostExtra: totalLoanCostExtra.toFixed(2),
|
});
|
||||||
loanTermWithExtraMonths,
|
|
||||||
salaryResults,
|
setResults(schoolResults);
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Calculation Error:', error);
|
|
||||||
setError('An error occurred during the calculation.');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>Loan Repayment and ROI Analysis</h2>
|
<h2>Loan Repayment and ROI Analysis</h2>
|
||||||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
Tuition Type:
|
Tuition Type:
|
||||||
@ -137,67 +93,33 @@ function LoanRepayment({ tuitionCosts, salaryData, earningHorizon }) {
|
|||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Interest Rate (%):
|
Interest Rate (%):
|
||||||
<input
|
<input type="number" value={interestRate} onChange={(e) => setInterestRate(Number(e.target.value))} />
|
||||||
type="number"
|
|
||||||
value={interestRate}
|
|
||||||
onChange={(e) => setInterestRate(Number(e.target.value))}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Loan Term (Years):
|
Loan Term (Years):
|
||||||
<input
|
<input type="number" value={loanTerm} onChange={(e) => setLoanTerm(Number(e.target.value))} />
|
||||||
type="number"
|
|
||||||
value={loanTerm}
|
|
||||||
onChange={(e) => setLoanTerm(Number(e.target.value))}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Extra Monthly Payment ($):
|
Extra Monthly Payment ($):
|
||||||
<input
|
<input type="number" value={extraPayment} onChange={(e) => setExtraPayment(Number(e.target.value))} />
|
||||||
type="number"
|
|
||||||
value={extraPayment}
|
|
||||||
onChange={(e) => setExtraPayment(Number(e.target.value))}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
<button onClick={calculateLoanDetails} disabled={loading}>
|
<button onClick={calculateLoanDetails} disabled={loading}>
|
||||||
{loading ? 'Calculating...' : 'Calculate'}
|
{loading ? 'Calculating...' : 'Calculate'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{results && (
|
{results.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3>Loan Details</h3>
|
<h3>Comparison by School</h3>
|
||||||
<p>Total Tuition: ${results.tuition.toFixed(2)}</p>
|
{results.map((result, index) => (
|
||||||
<p>Minimum Monthly Payment: ${results.minimumMonthlyPayment}</p>
|
<div key={index}>
|
||||||
<p>Total Loan Cost (Minimum Payment): ${results.totalLoanCostMinimum}</p>
|
<h4>{result.schoolName}</h4>
|
||||||
<p>Loan Term (Minimum Payment): {results.loanTermMonths / 12} years</p>
|
<p>Total Tuition: ${result.tuition}</p>
|
||||||
<p>Extra Monthly Payment: ${results.extraMonthlyPayment}</p>
|
<p>Monthly Payment: ${result.monthlyPayment}</p>
|
||||||
<p>Total Loan Cost (With Extra Payments): ${results.totalLoanCostExtra}</p>
|
<p>Total Loan Cost: ${result.totalLoanCost}</p>
|
||||||
<p>Loan Term (With Extra Payments): {(results.loanTermWithExtraMonths / 12).toFixed(1)} years</p>
|
<p>Net Gain: ${result.netGain}</p>
|
||||||
|
</div>
|
||||||
<h3>Net Gain by Salary Percentiles</h3>
|
))}
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Percentile</th>
|
|
||||||
<th>Starting Salary</th>
|
|
||||||
<th>Total Salary</th>
|
|
||||||
<th>Net Gain (Min Payment)</th>
|
|
||||||
<th>Net Gain (Extra Payment)</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{results.salaryResults.map((item, index) => (
|
|
||||||
<tr key={index}>
|
|
||||||
<td>{item.percentile}</td>
|
|
||||||
<td>${item.value.toLocaleString()}</td>
|
|
||||||
<td>${item.totalSalary}</td>
|
|
||||||
<td>${item.netGainMinimum}</td>
|
|
||||||
<td>${item.netGainExtra}</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// PopoutPanel.js
|
|
||||||
import { ClipLoader } from 'react-spinners';
|
import { ClipLoader } from 'react-spinners';
|
||||||
import LoanRepayment from './LoanRepayment.js';
|
import LoanRepayment from './LoanRepayment.js';
|
||||||
import './PopoutPanel.css';
|
import './PopoutPanel.css';
|
||||||
@ -10,7 +9,6 @@ function PopoutPanel({
|
|||||||
error = null,
|
error = null,
|
||||||
closePanel
|
closePanel
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
console.log('PopoutPanel Props:', { data, loading, error, userState });
|
console.log('PopoutPanel Props:', { data, loading, error, userState });
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@ -43,20 +41,9 @@ function PopoutPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle empty data gracefully
|
|
||||||
if (!data || Object.keys(data).length === 0) {
|
|
||||||
return (
|
|
||||||
<div className="popout-panel">
|
|
||||||
<button onClick={closePanel}>Close</button>
|
|
||||||
<h2>No Career Data Available</h2>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safely access nested data with fallbacks
|
// Safely access nested data with fallbacks
|
||||||
const {
|
const {
|
||||||
title = 'Career Details',
|
title = 'Career Details',
|
||||||
description = 'No description available.',
|
|
||||||
economicProjections = {},
|
economicProjections = {},
|
||||||
salaryData = [],
|
salaryData = [],
|
||||||
schools = [],
|
schools = [],
|
||||||
@ -76,12 +63,10 @@ function PopoutPanel({
|
|||||||
return 4; // Default to 4 years if unspecified
|
return 4; // Default to 4 years if unspecified
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('PopoutPanel Props:', { data, schools, salaryData, economicProjections, tuitionData, loading, error, userState });
|
|
||||||
return (
|
return (
|
||||||
<div className="popout-panel">
|
<div className="popout-panel">
|
||||||
<button onClick={closePanel}>Close</button>
|
<button onClick={closePanel}>Close</button>
|
||||||
<h2>{data.title || 'Career Details'}</h2>
|
<h2>{title}</h2>
|
||||||
<p>Description: {data.description || 'No description available.'}</p>
|
|
||||||
|
|
||||||
{/* Schools Offering Programs */}
|
{/* Schools Offering Programs */}
|
||||||
<h3>Schools Offering Programs</h3>
|
<h3>Schools Offering Programs</h3>
|
||||||
@ -93,7 +78,7 @@ function PopoutPanel({
|
|||||||
tuition['school.name']?.toLowerCase().trim() ===
|
tuition['school.name']?.toLowerCase().trim() ===
|
||||||
school['Institution Name']?.toLowerCase().trim()
|
school['Institution Name']?.toLowerCase().trim()
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={index}>
|
<li key={index}>
|
||||||
<strong>{school['Institution Name']}</strong>
|
<strong>{school['Institution Name']}</strong>
|
||||||
@ -111,6 +96,7 @@ function PopoutPanel({
|
|||||||
<p>No schools available.</p>
|
<p>No schools available.</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{/* Economic Projections */}
|
{/* Economic Projections */}
|
||||||
<h3>Economic Projections for {userState}</h3>
|
<h3>Economic Projections for {userState}</h3>
|
||||||
{economicProjections && typeof economicProjections === 'object' ? (
|
{economicProjections && typeof economicProjections === 'object' ? (
|
||||||
@ -151,26 +137,19 @@ function PopoutPanel({
|
|||||||
{/* Loan Repayment Analysis */}
|
{/* Loan Repayment Analysis */}
|
||||||
{tenthPercentileSalary > 0 && (
|
{tenthPercentileSalary > 0 && (
|
||||||
<LoanRepayment
|
<LoanRepayment
|
||||||
tuitionCosts={{
|
schools={schools.map((school) => {
|
||||||
inState: schools.map((school) => {
|
const matchingTuitionData = tuitionData.find(
|
||||||
const matchingTuitionData = tuitionData.find(
|
(tuition) =>
|
||||||
(tuition) =>
|
tuition['school.name']?.toLowerCase().trim() ===
|
||||||
tuition['school.name']?.toLowerCase().trim() ===
|
school['Institution Name']?.toLowerCase().trim()
|
||||||
school['Institution Name']?.toLowerCase().trim()
|
);
|
||||||
);
|
const years = getProgramLength(school['CREDDESC']);
|
||||||
const years = getProgramLength(school['CREDDESC']);
|
return {
|
||||||
return parseFloat(matchingTuitionData?.['latest.cost.tuition_in_state'] * years) || 0;
|
schoolName: school['Institution Name'],
|
||||||
}),
|
inState: parseFloat(matchingTuitionData?.['latest.cost.tuition_in_state'] * years) || 0,
|
||||||
outOfState: schools.map((school) => {
|
outOfState: parseFloat(matchingTuitionData?.['latest.cost.tuition_out_of_state'] * years) || 0,
|
||||||
const matchingTuitionData = tuitionData.find(
|
};
|
||||||
(tuition) =>
|
})}
|
||||||
tuition['school.name']?.toLowerCase().trim() ===
|
|
||||||
school['Institution Name']?.toLowerCase().trim()
|
|
||||||
);
|
|
||||||
const years = getProgramLength(school['CREDDESC']);
|
|
||||||
return parseFloat(matchingTuitionData?.['latest.cost.tuition_out_of_state'] * years) || 0;
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
salaryData={[{ percentile: '10th Percentile', value: tenthPercentileSalary, growthRate: 0.03 }]}
|
salaryData={[{ percentile: '10th Percentile', value: tenthPercentileSalary, growthRate: 0.03 }]}
|
||||||
earningHorizon={10}
|
earningHorizon={10}
|
||||||
/>
|
/>
|
||||||
@ -180,4 +159,3 @@ function PopoutPanel({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default PopoutPanel;
|
export default PopoutPanel;
|
||||||
|
|
||||||
|
|||||||
27
src/utils/convert-to-json.js
Normal file
27
src/utils/convert-to-json.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import xlsx from 'xlsx';
|
||||||
|
import { writeFileSync } from 'fs';
|
||||||
|
import { join, dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
// Get the directory name
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
// Define file paths
|
||||||
|
const excelFilePath = join(__dirname, '../../Institution_data.xlsx');
|
||||||
|
const jsonFilePath = join(__dirname, 'Institution_data.json');
|
||||||
|
|
||||||
|
// Load Excel file
|
||||||
|
const workbook = xlsx.readFile(excelFilePath);
|
||||||
|
|
||||||
|
// Select the first worksheet
|
||||||
|
const sheetName = workbook.SheetNames[0];
|
||||||
|
const worksheet = workbook.Sheets[sheetName];
|
||||||
|
|
||||||
|
// Convert worksheet to JSON
|
||||||
|
const jsonData = xlsx.utils.sheet_to_json(worksheet);
|
||||||
|
|
||||||
|
// Write JSON data to file
|
||||||
|
writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 2));
|
||||||
|
|
||||||
|
console.log('JSON file created successfully at', jsonFilePath);
|
||||||
Loading…
Reference in New Issue
Block a user