Fixed bug where loan repayment calculation persisted between career clicks if popoutpanel left open
This commit is contained in:
parent
6a4b0eea9e
commit
5ecf834c67
@ -1,29 +1,114 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import './CareerSuggestions.css';
|
import axios from 'axios';
|
||||||
|
import './Dashboard.css';
|
||||||
|
|
||||||
export function CareerSuggestions({ careerSuggestions = [], onCareerClick }) {
|
const apiUrl = process.env.REACT_APP_API_URL || ''; // ✅ Load API URL directly
|
||||||
if (!Array.isArray(careerSuggestions) || careerSuggestions.length === 0) {
|
|
||||||
return <p>No career suggestions available. Please check back later.</p>;
|
export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle, onCareerClick }) {
|
||||||
|
const [updatedCareers, setUpdatedCareers] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!careerSuggestions || careerSuggestions.length === 0) return;
|
||||||
|
|
||||||
|
const token = localStorage.getItem('token'); // Get auth token
|
||||||
|
|
||||||
|
const checkCareerDataAvailability = async () => {
|
||||||
|
const careerPromises = careerSuggestions.map(async (career) => {
|
||||||
|
try {
|
||||||
|
console.log(`Checking data for: ${career.title} (${career.code})`);
|
||||||
|
|
||||||
|
console.log(`Fetching CIP Code from: ${apiUrl}/cip/${career.code}`);
|
||||||
|
console.log(`Fetching Job Description from: ${apiUrl}/onet/career-description/${career.code}`);
|
||||||
|
console.log(`Fetching Economic Projections from: ${apiUrl}/projections/${career.code.split('.')[0]}`);
|
||||||
|
console.log(`Fetching Salary Data from: ${apiUrl}/salary?socCode=${career.code.split('.')[0]}`);
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${token}`, // Match Dashboard.js headers
|
||||||
|
Accept: 'application/json', // Ensure JSON response
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to fetch and check JSON responses
|
||||||
|
const fetchJSON = async (url) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, { headers });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`⚠️ Error fetching ${url}:`, error.response?.status, error.response?.data);
|
||||||
|
return null; // Return null if request fails
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 1: Fetch CIP Code
|
||||||
|
const cipData = await fetchJSON(`${apiUrl}/cip/${career.code}`);
|
||||||
|
const isCipMissing = !cipData || Object.keys(cipData).length === 0;
|
||||||
|
console.log(`CIP Code for ${career.code}:`, cipData);
|
||||||
|
|
||||||
|
// Step 2: Fetch Job Description & Tasks
|
||||||
|
const jobDetailsData = await fetchJSON(`${apiUrl}/onet/career-description/${career.code}`);
|
||||||
|
const isJobDetailsMissing = !jobDetailsData || Object.keys(jobDetailsData).length === 0;
|
||||||
|
console.log(`Job Details for ${career.code}:`, jobDetailsData);
|
||||||
|
|
||||||
|
// Step 3: Fetch Salary & Economic Projections in Parallel
|
||||||
|
const [economicData, salaryResponse] = await Promise.all([
|
||||||
|
fetchJSON(`${apiUrl}/projections/${career.code.split('.')[0]}`),
|
||||||
|
axios.get(`${apiUrl}/salary`, {
|
||||||
|
params: { socCode: career.code.split('.')[0], area: areaTitle },
|
||||||
|
headers,
|
||||||
|
}).catch((error) => error.response), // Catch error for 404 handling
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isEconomicMissing = !economicData || Object.keys(economicData).length === 0;
|
||||||
|
console.log(`Economic Data for ${career.code}:`, economicData);
|
||||||
|
|
||||||
|
// Salary check: If it fails with 404, we know it's missing
|
||||||
|
const isSalaryMissing = salaryResponse?.status === 404;
|
||||||
|
if (isSalaryMissing) {
|
||||||
|
console.log(`⚠️ Missing Salary Data for ${career.title} (${career.code})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugging: Log when a career is missing data
|
||||||
|
if (isCipMissing || isJobDetailsMissing || isEconomicMissing || isSalaryMissing) {
|
||||||
|
console.log(`⚠️ Limited Data for ${career.title} (${career.code})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set `limitedData` to true if any required data is missing
|
||||||
|
const isLimitedData = isCipMissing || isJobDetailsMissing || isEconomicMissing || isSalaryMissing;
|
||||||
|
|
||||||
|
return { ...career, limitedData: isLimitedData };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error checking API response for ${career.title}:`, error);
|
||||||
|
return { ...career, limitedData: true }; // Mark as limited if any request fails
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedCareerList = await Promise.all(careerPromises);
|
||||||
|
|
||||||
|
console.log("✅ Final Updated Careers with limitedData:", updatedCareerList);
|
||||||
|
setUpdatedCareers(updatedCareerList);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkCareerDataAvailability();
|
||||||
|
}, [careerSuggestions, apiUrl, userState, areaTitle]);
|
||||||
|
|
||||||
|
if (updatedCareers.length === 0) {
|
||||||
|
return <p>Loading career suggestions...</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>Career Suggestions</h2>
|
<h2>Career Suggestions</h2>
|
||||||
<div className="career-suggestions-grid">
|
<div className="career-suggestions-grid">
|
||||||
{careerSuggestions.map((career) => {
|
{updatedCareers.map((career) => (
|
||||||
return (
|
<button
|
||||||
<button
|
key={career.code}
|
||||||
key={career.code}
|
className={`career-button ${career.limitedData ? 'limited-data' : ''}`}
|
||||||
className="career-button"
|
onClick={() => onCareerClick(career)}
|
||||||
onClick={() => onCareerClick(career)} // Directly pass the career to onCareerClick
|
>
|
||||||
>
|
{career.title} {career.limitedData && <span className="warning-icon">⚠️</span>}
|
||||||
{career.title}
|
</button>
|
||||||
</button>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CareerSuggestions;
|
export default CareerSuggestions;
|
||||||
|
@ -87,16 +87,20 @@ h2 {
|
|||||||
border-radius: 3px; /* Less rounded */
|
border-radius: 3px; /* Less rounded */
|
||||||
}
|
}
|
||||||
|
|
||||||
.career-button.warning {
|
/* Limited Data Warning: Yellow Button */
|
||||||
border: 2px solid black; /* Example warning border */
|
.career-button.limited-data {
|
||||||
background-color: #f8d7da; /* Example background color */
|
background-color: lightblue !important;
|
||||||
|
color: whitesmoke;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 2px solid #ff9800; /* Orange border for emphasis */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Warning Icon */
|
/* Warning Icon */
|
||||||
.warning-icon {
|
.warning-icon {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: yellow; /* Yellow to indicate limited data */
|
color: yellow; /* Dark orange to indicate caution */
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.career-button:hover {
|
.career-button:hover {
|
||||||
|
@ -137,6 +137,7 @@ function Dashboard() {
|
|||||||
setEconomicProjections({}); // Reset economic projections
|
setEconomicProjections({}); // Reset economic projections
|
||||||
setTuitionData([]); // Reset tuition data
|
setTuitionData([]); // Reset tuition data
|
||||||
|
|
||||||
|
|
||||||
if (!socCode) {
|
if (!socCode) {
|
||||||
console.error('SOC Code is missing');
|
console.error('SOC Code is missing');
|
||||||
setError('SOC Code is missing');
|
setError('SOC Code is missing');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
function PopoutPanel({
|
function PopoutPanel({
|
||||||
data = {},
|
data = {},
|
||||||
@ -16,6 +16,13 @@ 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);
|
||||||
|
|
||||||
|
// ✅ Reset `results` when a new career is selected
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(`Career changed to: ${data?.title}, clearing previous loan results.`);
|
||||||
|
setResults([]); // ✅ Clears results when a new career is selected
|
||||||
|
setIsCalculated(false); // ✅ Reset calculation state
|
||||||
|
}, [data]); // Runs whenever `data` changes (i.e., new career is selected)
|
||||||
|
|
||||||
// Handle loading state
|
// Handle loading state
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@ -153,7 +160,7 @@ function PopoutPanel({
|
|||||||
{/* Results Display */}
|
{/* Results Display */}
|
||||||
{results.length > 0 && (
|
{results.length > 0 && (
|
||||||
<div className="results-container">
|
<div className="results-container">
|
||||||
<h3>Comparisons by School over the life of the loan - assumes a starting salary in the lowest 10%</h3>
|
<h3>Comparisons by School over the life of the loan</h3>
|
||||||
{results.map((result, index) => (
|
{results.map((result, index) => (
|
||||||
<div className="school-result-card" key={index}>
|
<div className="school-result-card" key={index}>
|
||||||
<h4>{result.schoolName} - {result.degreeType || 'Degree type not available'}</h4>
|
<h4>{result.schoolName} - {result.degreeType || 'Degree type not available'}</h4>
|
||||||
|
Loading…
Reference in New Issue
Block a user