url changes for prod and dev

This commit is contained in:
Josh 2024-12-28 20:59:08 +00:00
parent 9696943f9b
commit 67e3e962e7
10 changed files with 3764274 additions and 224 deletions

BIN
Institution_data.xlsx Normal file

Binary file not shown.

View File

@ -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}`); console.log('Loaded Tuition Data:', schoolsData.length);
const response = await axios.get(url); // Filter data by CIP Code and State
const data = response.data.results; 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:');
console.log(schoolsData.slice(0, 5)); // Logs first 5 rows for debugging
if (data?.length > 0) { return (
res.status(200).json(data); (!cipCode || cipCodeValue === cipCode) && // Exact CIP code match
} else { (!state || stateValue === state.toUpperCase().trim()) // Exact state match
res.status(404).json({ error: 'No tuition data found for the given CIP code and state.' }); );
} });
console.log('Filtered Tuition Data Count:', filteredData.length);
res.json(filteredData); // Send the filtered tuition data
} catch (error) { } catch (error) {
res.status(500).json({ console.error('Error reading tuition data:', error);
error: 'Failed to fetch tuition data from College Scorecard API', res.status(500).json({ error: 'Failed to load tuition data.' });
details: error.response?.data || error.message
});
} }
}); });
// 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

File diff suppressed because it is too large Load Diff

View File

@ -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,19 +93,21 @@ 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 },
}), }),
]); ]);

View File

@ -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}`);
console.log(`Cleaned SOC Code: ${cleanedSocCode}`);
}
try { try {
const response = await axios.get(`http://localhost:5001/api/projections/${cleanedSocCode}`); const cleanedSocCode = socCode.split('.')[0]; // Clean SOC code
console.log('Projection Response:', response.data); // Log the projection response const projectionsUrl = `${apiUrl}/projections/${cleanedSocCode}`;
setProjections(response.data); // Set the projections data in state // 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) { } catch (err) {
setError('Error fetching economic projections.'); setError('Error fetching economic projections.');
console.error('Error fetching projections:', err); 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>;

View File

@ -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">

View File

@ -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);
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 { return {
...salaryPoint, ...school,
totalSalary: totalSalary.toFixed(2), tuition,
netGainMinimum: netGainMinimum.toFixed(2), monthlyPayment: minimumMonthlyPayment.toFixed(2),
netGainExtra: netGainExtra.toFixed(2), totalLoanCost: totalLoanCost.toFixed(2),
netGain: netGain.toFixed(2),
}; };
}); });
setResults({ setResults(schoolResults);
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);
}
}; };
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>

View File

@ -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>
@ -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 parseFloat(matchingTuitionData?.['latest.cost.tuition_in_state'] * years) || 0; return {
}), schoolName: school['Institution Name'],
outOfState: schools.map((school) => { inState: parseFloat(matchingTuitionData?.['latest.cost.tuition_in_state'] * years) || 0,
const matchingTuitionData = tuitionData.find( outOfState: parseFloat(matchingTuitionData?.['latest.cost.tuition_out_of_state'] * years) || 0,
(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;

View 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);