url changes for prod and dev
This commit is contained in:
parent
9696943f9b
commit
b3cb86001f
8
.env.production
Normal file
8
.env.production
Normal file
@ -0,0 +1,8 @@
|
||||
ONET_USERNAME=aptivaai
|
||||
ONET_PASSWORD=2296ahq
|
||||
|
||||
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
|
||||
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||
COLLEGE_SCORECARD_KEY = BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
|
||||
|
||||
REACT_APP_API_URL=/api
|
||||
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 { open } from 'sqlite'; // Use the open method directly from sqlite package
|
||||
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
|
||||
console.log('ONET_USERNAME:', process.env.ONET_USERNAME);
|
||||
@ -18,6 +20,8 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
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 institutionJsonFilePath = path.join(__dirname, '../public/Institution_data.json');
|
||||
|
||||
const app = express();
|
||||
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'
|
||||
);
|
||||
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
|
||||
res.setHeader('Access-Control-Allow-Origin', '*'); // Allow all origins for static file
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||
@ -348,48 +352,72 @@ app.get('/api/cip/:socCode', (req, res) => {
|
||||
|
||||
// Filtered schools endpoint
|
||||
app.get('/api/schools', (req, res) => {
|
||||
const { cipCode, state, level, type } = req.query;
|
||||
const filePath = path.join(__dirname, 'CIP_institution_mapping_fixed.json');
|
||||
const { cipCode, state } = req.query;
|
||||
console.log('Query Params:', { cipCode, state });
|
||||
|
||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error('Error reading file:', err);
|
||||
return res.status(500).json({ error: 'Failed to load data' });
|
||||
}
|
||||
try {
|
||||
// Load Institution Data from JSON file
|
||||
const schoolsData = JSON.parse(fs.readFileSync(institutionJsonFilePath, 'utf8'));
|
||||
|
||||
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
|
||||
app.get('/api/tuition/:cipCode', async (req, res) => {
|
||||
const { cipCode } = req.params;
|
||||
const { state } = req.query;
|
||||
|
||||
console.log(`Received CIP Code: ${cipCode} for state: ${state}`);
|
||||
app.get('/api/tuition/:cipCode', (req, res) => {
|
||||
const { cipCode, state } = req.query;
|
||||
console.log(`Received CIP Code: ${cipCode}, State: ${state}`);
|
||||
|
||||
try {
|
||||
const apiKey = process.env.COLLEGE_SCORECARD_KEY;
|
||||
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`;
|
||||
|
||||
console.log(`Constructed URL: ${url}`);
|
||||
const workbook = xlsx.readFile(institutionFilePath); // Load Excel file
|
||||
const sheet = workbook.Sheets[workbook.SheetNames[0]]; // First sheet
|
||||
const schoolsData = xlsx.utils.sheet_to_json(sheet); // Convert to JSON
|
||||
|
||||
const response = await axios.get(url);
|
||||
const data = response.data.results;
|
||||
console.log('Loaded Tuition Data:', schoolsData.length);
|
||||
|
||||
// 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
|
||||
|
||||
if (data?.length > 0) {
|
||||
res.status(200).json(data);
|
||||
} else {
|
||||
res.status(404).json({ error: 'No tuition data found for the given CIP code and state.' });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch tuition data from College Scorecard API',
|
||||
details: error.response?.data || error.message
|
||||
console.log('CIP:', cipCodeValue, 'State:', stateValue); // Debug log each match attempt
|
||||
console.log('Sample Records:');
|
||||
console.log(schoolsData.slice(0, 5)); // Logs first 5 rows for debugging
|
||||
|
||||
return (
|
||||
(!cipCode || cipCodeValue === cipCode) && // Exact CIP code match
|
||||
(!state || stateValue === state.toUpperCase().trim()) // Exact state match
|
||||
);
|
||||
});
|
||||
|
||||
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
|
||||
app.get('/api/projections/:socCode', (req, res) => {
|
||||
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 [riaSecDescriptions, setRiaSecDescriptions] = useState([]);
|
||||
|
||||
const apiUrl = process.env.REACT_APP_API_URL;
|
||||
// Dynamic API URL
|
||||
const apiUrl = process.env.REACT_APP_API_URL || '/api';
|
||||
|
||||
useEffect(() => {
|
||||
let descriptions = []; // Declare outside for scope accessibility
|
||||
@ -49,7 +50,7 @@ function Dashboard() {
|
||||
const fetchUserProfile = async () => {
|
||||
try {
|
||||
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}` },
|
||||
});
|
||||
|
||||
@ -70,7 +71,7 @@ function Dashboard() {
|
||||
};
|
||||
|
||||
fetchUserProfile();
|
||||
}, []);
|
||||
}, [apiUrl]);
|
||||
|
||||
const handleCareerClick = useCallback(
|
||||
async (career) => {
|
||||
@ -92,22 +93,24 @@ function Dashboard() {
|
||||
|
||||
try {
|
||||
// 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');
|
||||
const { cipCode } = await cipResponse.json();
|
||||
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
|
||||
|
||||
|
||||
|
||||
// Step 2: Fetch Data in Parallel
|
||||
const [filteredSchools, economicResponse, tuitionResponse, salaryResponse] = await Promise.all([
|
||||
fetchSchools(cleanedCipCode, userState),
|
||||
axios.get(`http://localhost:5001/api/projections/${socCode.split('.')[0]}`),
|
||||
axios.get(`http://localhost:5001/api/tuition/${cleanedCipCode}`, {
|
||||
params: { state: userState },
|
||||
}),
|
||||
axios.get(`http://localhost:5001/api/salary`, {
|
||||
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
||||
}),
|
||||
]);
|
||||
axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`),
|
||||
axios.get(`${apiUrl}/tuition/${cleanedCipCode}`, {
|
||||
params: { state: userState },
|
||||
}),
|
||||
axios.get(`${apiUrl}/salary`, {
|
||||
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
||||
}),
|
||||
]);
|
||||
|
||||
// Step 3: Format Salary Data
|
||||
const salaryDataPoints = [
|
||||
|
||||
@ -7,33 +7,49 @@ function EconomicProjections({ socCode }) {
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
console.log(axios.defaults.baseURL); // Check if baseURL is set
|
||||
|
||||
const apiUrl = process.env.REACT_APP_API_URL || '/api';
|
||||
useEffect(() => {
|
||||
if (socCode) {
|
||||
const cleanedSocCode = socCode.split('.')[0]; // Clean the SOC code inside the component
|
||||
console.log(`Fetching economic projections for cleaned SOC code: ${cleanedSocCode}`);
|
||||
|
||||
const fetchProjections = async () => {
|
||||
const projectionsUrl = process.env.NODE_ENV === 'production'
|
||||
? '/api/projections/' // In production, routed through Nginx
|
||||
: 'http://localhost:5001/api/projections/'; // In development, backend is running on localhost:5001
|
||||
|
||||
try {
|
||||
const response = await axios.get(`http://localhost:5001/api/projections/${cleanedSocCode}`);
|
||||
console.log('Projection Response:', response.data); // Log the projection response
|
||||
|
||||
setProjections(response.data); // Set the projections data in state
|
||||
} catch (err) {
|
||||
setError('Error fetching economic projections.');
|
||||
console.error('Error fetching projections:', err);
|
||||
const projectionsUrl = `${apiUrl}/projections/${cleanedSocCode}`;
|
||||
// Log URL and SOC code only in development
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log(`Fetching projections from: ${projectionsUrl}`);
|
||||
console.log(`Cleaned SOC Code: ${cleanedSocCode}`);
|
||||
}
|
||||
try {
|
||||
const cleanedSocCode = socCode.split('.')[0]; // Clean SOC code
|
||||
const projectionsUrl = `${apiUrl}/projections/${cleanedSocCode}`;
|
||||
|
||||
// Log URL and SOC code only in development
|
||||
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();
|
||||
}
|
||||
}, [socCode]); // This runs when the socCode prop changes
|
||||
}, [socCode,apiUrl]); // This runs when the socCode prop changes
|
||||
|
||||
if (error) {
|
||||
return <div className="error">{error}</div>;
|
||||
|
||||
@ -18,9 +18,7 @@ const InterestInventory = () => {
|
||||
setLoading(true); // Start loading
|
||||
setError(null); // Reset error state
|
||||
|
||||
const baseUrl = process.env.REACT_APP_API_URL || 'http://localhost:5001'; // Default to localhost:5001 for development
|
||||
const url = `${baseUrl}/api/onet/questions?start=1&end=60`; // Make sure the endpoint is correctly appended to the base URL
|
||||
|
||||
const url = '/api/onet/questions?start=1&end=60';
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
@ -101,11 +99,8 @@ const InterestInventory = () => {
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
setError(null); // Clear previous errors
|
||||
const baseUrl = process.env.NODE_ENV === 'production'
|
||||
? '/api/onet/submit_answers' // In production, this is proxied by Nginx to server2 (port 5001)
|
||||
: 'http://localhost:5001/api/onet/submit_answers'; // In development, server2 runs on port 5001
|
||||
|
||||
const response = await fetch(baseUrl, {
|
||||
const url = `${process.env.REACT_APP_API_URL}/onet/submit_answers`;
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ answers }),
|
||||
@ -165,7 +160,7 @@ const InterestInventory = () => {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p>No questions available. Please try again later.</p>
|
||||
<p>Loading questions.</p>
|
||||
)}
|
||||
|
||||
<div className="pagination-buttons">
|
||||
|
||||
@ -1,28 +1,19 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
function LoanRepayment({ tuitionCosts, salaryData, earningHorizon }) {
|
||||
function LoanRepayment({ schools, salaryData, earningHorizon }) {
|
||||
const [tuitionType, setTuitionType] = useState('inState'); // 'inState' or 'outOfState'
|
||||
const [interestRate, setInterestRate] = useState(5.5); // Default federal loan interest rate
|
||||
const [loanTerm, setLoanTerm] = useState(10); // Default loan term (10 years)
|
||||
const [extraPayment, setExtraPayment] = useState(0); // Extra monthly payment
|
||||
const [results, setResults] = useState(null);
|
||||
const [results, setResults] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [sortBy, setSortBy] = useState('monthlyPayment'); // Default sort option
|
||||
|
||||
// Validation function
|
||||
const validateInputs = () => {
|
||||
console.log('Validating inputs...');
|
||||
console.log('Tuition Costs:', tuitionCosts);
|
||||
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.`);
|
||||
if (!schools || schools.length === 0 || !salaryData || salaryData.length === 0) {
|
||||
setError('School or salary data is missing.');
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -41,30 +32,23 @@ function LoanRepayment({ tuitionCosts, salaryData, earningHorizon }) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setError(null); // Clear errors if all inputs are valid
|
||||
setError(null); // Clear errors if valid
|
||||
return true;
|
||||
};
|
||||
|
||||
// Loan calculation function
|
||||
// Loan calculation function for all schools
|
||||
// Calculate loan details for each school
|
||||
const calculateLoanDetails = () => {
|
||||
if (!validateInputs()) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const tuition = tuitionCosts[tuitionType].reduce((total, year) => total + year, 0);
|
||||
const schoolResults = schools.map((school) => {
|
||||
const tuition = tuitionType === 'inState' ? school.inState : school.outOfState;
|
||||
const monthlyRate = interestRate / 12 / 100;
|
||||
const loanTermMonths = loanTerm * 12;
|
||||
|
||||
console.log('Total Tuition:', tuition);
|
||||
console.log('Monthly Interest Rate:', monthlyRate);
|
||||
|
||||
// Minimum Monthly Payment
|
||||
// Calculate minimum monthly payment
|
||||
const minimumMonthlyPayment = tuition * (monthlyRate * Math.pow(1 + monthlyRate, loanTermMonths)) /
|
||||
(Math.pow(1 + monthlyRate, loanTermMonths) - 1);
|
||||
|
||||
console.log('Minimum Monthly Payment:', minimumMonthlyPayment);
|
||||
|
||||
// Loan details with extra payments
|
||||
// Total loan cost with extra payments
|
||||
const extraMonthlyPayment = minimumMonthlyPayment + extraPayment;
|
||||
let remainingBalance = tuition;
|
||||
let monthsWithExtra = 0;
|
||||
@ -74,59 +58,31 @@ function LoanRepayment({ tuitionCosts, salaryData, earningHorizon }) {
|
||||
const interest = remainingBalance * monthlyRate;
|
||||
const principal = extraMonthlyPayment - interest;
|
||||
remainingBalance -= principal;
|
||||
|
||||
if (remainingBalance < 0) remainingBalance = 0;
|
||||
}
|
||||
|
||||
const loanTermWithExtraMonths = monthsWithExtra;
|
||||
const totalLoanCost = extraMonthlyPayment * monthsWithExtra;
|
||||
|
||||
// Total Loan Costs
|
||||
const totalLoanCostMinimum = minimumMonthlyPayment * loanTermMonths;
|
||||
const totalLoanCostExtra = extraMonthlyPayment * loanTermWithExtraMonths;
|
||||
// Calculate net gain
|
||||
const salary = salaryData[0].value; // 10th percentile salary as default
|
||||
const totalSalary = salary * earningHorizon;
|
||||
const netGain = totalSalary - totalLoanCost;
|
||||
|
||||
console.log('Total Loan Cost (Minimum Payment):', totalLoanCostMinimum);
|
||||
console.log('Total Loan Cost (With Extra Payments):', totalLoanCostExtra);
|
||||
|
||||
// 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({
|
||||
return {
|
||||
...school,
|
||||
tuition,
|
||||
minimumMonthlyPayment: minimumMonthlyPayment.toFixed(2),
|
||||
totalLoanCostMinimum: totalLoanCostMinimum.toFixed(2),
|
||||
loanTermMonths,
|
||||
extraMonthlyPayment: extraMonthlyPayment.toFixed(2),
|
||||
totalLoanCostExtra: totalLoanCostExtra.toFixed(2),
|
||||
loanTermWithExtraMonths,
|
||||
salaryResults,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculation Error:', error);
|
||||
setError('An error occurred during the calculation.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
monthlyPayment: minimumMonthlyPayment.toFixed(2),
|
||||
totalLoanCost: totalLoanCost.toFixed(2),
|
||||
netGain: netGain.toFixed(2),
|
||||
};
|
||||
});
|
||||
|
||||
setResults(schoolResults);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Loan Repayment and ROI Analysis</h2>
|
||||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
||||
|
||||
<div>
|
||||
<label>
|
||||
Tuition Type:
|
||||
@ -137,67 +93,33 @@ function LoanRepayment({ tuitionCosts, salaryData, earningHorizon }) {
|
||||
</label>
|
||||
<label>
|
||||
Interest Rate (%):
|
||||
<input
|
||||
type="number"
|
||||
value={interestRate}
|
||||
onChange={(e) => setInterestRate(Number(e.target.value))}
|
||||
/>
|
||||
<input type="number" value={interestRate} onChange={(e) => setInterestRate(Number(e.target.value))} />
|
||||
</label>
|
||||
<label>
|
||||
Loan Term (Years):
|
||||
<input
|
||||
type="number"
|
||||
value={loanTerm}
|
||||
onChange={(e) => setLoanTerm(Number(e.target.value))}
|
||||
/>
|
||||
<input type="number" value={loanTerm} onChange={(e) => setLoanTerm(Number(e.target.value))} />
|
||||
</label>
|
||||
<label>
|
||||
Extra Monthly Payment ($):
|
||||
<input
|
||||
type="number"
|
||||
value={extraPayment}
|
||||
onChange={(e) => setExtraPayment(Number(e.target.value))}
|
||||
/>
|
||||
<input type="number" value={extraPayment} onChange={(e) => setExtraPayment(Number(e.target.value))} />
|
||||
</label>
|
||||
<button onClick={calculateLoanDetails} disabled={loading}>
|
||||
{loading ? 'Calculating...' : 'Calculate'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{results && (
|
||||
{results.length > 0 && (
|
||||
<div>
|
||||
<h3>Loan Details</h3>
|
||||
<p>Total Tuition: ${results.tuition.toFixed(2)}</p>
|
||||
<p>Minimum Monthly Payment: ${results.minimumMonthlyPayment}</p>
|
||||
<p>Total Loan Cost (Minimum Payment): ${results.totalLoanCostMinimum}</p>
|
||||
<p>Loan Term (Minimum Payment): {results.loanTermMonths / 12} years</p>
|
||||
<p>Extra Monthly Payment: ${results.extraMonthlyPayment}</p>
|
||||
<p>Total Loan Cost (With Extra Payments): ${results.totalLoanCostExtra}</p>
|
||||
<p>Loan Term (With Extra Payments): {(results.loanTermWithExtraMonths / 12).toFixed(1)} years</p>
|
||||
|
||||
<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>
|
||||
<h3>Comparison by School</h3>
|
||||
{results.map((result, index) => (
|
||||
<div key={index}>
|
||||
<h4>{result.schoolName}</h4>
|
||||
<p>Total Tuition: ${result.tuition}</p>
|
||||
<p>Monthly Payment: ${result.monthlyPayment}</p>
|
||||
<p>Total Loan Cost: ${result.totalLoanCost}</p>
|
||||
<p>Net Gain: ${result.netGain}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
// PopoutPanel.js
|
||||
import { ClipLoader } from 'react-spinners';
|
||||
import LoanRepayment from './LoanRepayment.js';
|
||||
import './PopoutPanel.css';
|
||||
@ -10,7 +9,6 @@ function PopoutPanel({
|
||||
error = null,
|
||||
closePanel
|
||||
}) {
|
||||
|
||||
console.log('PopoutPanel Props:', { data, loading, error, userState });
|
||||
|
||||
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
|
||||
const {
|
||||
title = 'Career Details',
|
||||
description = 'No description available.',
|
||||
economicProjections = {},
|
||||
salaryData = [],
|
||||
schools = [],
|
||||
@ -76,12 +63,10 @@ function PopoutPanel({
|
||||
return 4; // Default to 4 years if unspecified
|
||||
};
|
||||
|
||||
console.log('PopoutPanel Props:', { data, schools, salaryData, economicProjections, tuitionData, loading, error, userState });
|
||||
return (
|
||||
<div className="popout-panel">
|
||||
<button onClick={closePanel}>Close</button>
|
||||
<h2>{data.title || 'Career Details'}</h2>
|
||||
<p>Description: {data.description || 'No description available.'}</p>
|
||||
<h2>{title}</h2>
|
||||
|
||||
{/* Schools Offering Programs */}
|
||||
<h3>Schools Offering Programs</h3>
|
||||
@ -93,7 +78,7 @@ function PopoutPanel({
|
||||
tuition['school.name']?.toLowerCase().trim() ===
|
||||
school['Institution Name']?.toLowerCase().trim()
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<li key={index}>
|
||||
<strong>{school['Institution Name']}</strong>
|
||||
@ -111,6 +96,7 @@ function PopoutPanel({
|
||||
<p>No schools available.</p>
|
||||
)}
|
||||
|
||||
|
||||
{/* Economic Projections */}
|
||||
<h3>Economic Projections for {userState}</h3>
|
||||
{economicProjections && typeof economicProjections === 'object' ? (
|
||||
@ -151,26 +137,19 @@ function PopoutPanel({
|
||||
{/* Loan Repayment Analysis */}
|
||||
{tenthPercentileSalary > 0 && (
|
||||
<LoanRepayment
|
||||
tuitionCosts={{
|
||||
inState: schools.map((school) => {
|
||||
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_in_state'] * years) || 0;
|
||||
}),
|
||||
outOfState: schools.map((school) => {
|
||||
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;
|
||||
}),
|
||||
}}
|
||||
schools={schools.map((school) => {
|
||||
const matchingTuitionData = tuitionData.find(
|
||||
(tuition) =>
|
||||
tuition['school.name']?.toLowerCase().trim() ===
|
||||
school['Institution Name']?.toLowerCase().trim()
|
||||
);
|
||||
const years = getProgramLength(school['CREDDESC']);
|
||||
return {
|
||||
schoolName: school['Institution Name'],
|
||||
inState: parseFloat(matchingTuitionData?.['latest.cost.tuition_in_state'] * years) || 0,
|
||||
outOfState: parseFloat(matchingTuitionData?.['latest.cost.tuition_out_of_state'] * years) || 0,
|
||||
};
|
||||
})}
|
||||
salaryData={[{ percentile: '10th Percentile', value: tenthPercentileSalary, growthRate: 0.03 }]}
|
||||
earningHorizon={10}
|
||||
/>
|
||||
@ -180,4 +159,3 @@ function 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