Added Progress Bar on Career Suggestion load
This commit is contained in:
parent
f104020afa
commit
b0640e07db
@ -6,46 +6,56 @@ const apiUrl = process.env.REACT_APP_API_URL || ''; // ✅ Load API URL directly
|
|||||||
|
|
||||||
export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle, onCareerClick }) {
|
export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle, onCareerClick }) {
|
||||||
const [updatedCareers, setUpdatedCareers] = useState([]);
|
const [updatedCareers, setUpdatedCareers] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!careerSuggestions || careerSuggestions.length === 0) return;
|
if (!careerSuggestions || careerSuggestions.length === 0) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const token = localStorage.getItem('token'); // Get auth token
|
const token = localStorage.getItem('token'); // Get auth token
|
||||||
|
|
||||||
const checkCareerDataAvailability = async () => {
|
const checkCareerDataAvailability = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setProgress(0);
|
||||||
|
const totalSteps = careerSuggestions.length * 4; // Each career has 4 API checks
|
||||||
|
let completedSteps = 0;
|
||||||
|
|
||||||
|
const updateProgress = () => {
|
||||||
|
completedSteps += 1;
|
||||||
|
setProgress((completedSteps / totalSteps) * 100);
|
||||||
|
};
|
||||||
|
|
||||||
const careerPromises = careerSuggestions.map(async (career) => {
|
const careerPromises = careerSuggestions.map(async (career) => {
|
||||||
try {
|
try {
|
||||||
console.log(`Checking data for: ${career.title} (${career.code})`);
|
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 = {
|
const headers = {
|
||||||
Authorization: `Bearer ${token}`, // Match Dashboard.js headers
|
Authorization: `Bearer ${token}`,
|
||||||
Accept: 'application/json', // Ensure JSON response
|
Accept: 'application/json',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to fetch and check JSON responses
|
|
||||||
const fetchJSON = async (url) => {
|
const fetchJSON = async (url) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(url, { headers });
|
const response = await axios.get(url, { headers });
|
||||||
|
updateProgress(); // ✅ Update progress on success
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`⚠️ Error fetching ${url}:`, error.response?.status, error.response?.data);
|
console.warn(`⚠️ Error fetching ${url}:`, error.response?.status);
|
||||||
return null; // Return null if request fails
|
updateProgress(); // ✅ Update progress even if failed
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 1: Fetch CIP Code
|
// Step 1: Fetch CIP Code
|
||||||
const cipData = await fetchJSON(`${apiUrl}/cip/${career.code}`);
|
const cipData = await fetchJSON(`${apiUrl}/cip/${career.code}`);
|
||||||
const isCipMissing = !cipData || Object.keys(cipData).length === 0;
|
const isCipMissing = !cipData || Object.keys(cipData).length === 0;
|
||||||
console.log(`CIP Code for ${career.code}:`, cipData);
|
|
||||||
|
|
||||||
// Step 2: Fetch Job Description & Tasks
|
// Step 2: Fetch Job Description & Tasks
|
||||||
const jobDetailsData = await fetchJSON(`${apiUrl}/onet/career-description/${career.code}`);
|
const jobDetailsData = await fetchJSON(`${apiUrl}/onet/career-description/${career.code}`);
|
||||||
const isJobDetailsMissing = !jobDetailsData || Object.keys(jobDetailsData).length === 0;
|
const isJobDetailsMissing = !jobDetailsData || Object.keys(jobDetailsData).length === 0;
|
||||||
console.log(`Job Details for ${career.code}:`, jobDetailsData);
|
|
||||||
|
|
||||||
// Step 3: Fetch Salary & Economic Projections in Parallel
|
// Step 3: Fetch Salary & Economic Projections in Parallel
|
||||||
const [economicData, salaryResponse] = await Promise.all([
|
const [economicData, salaryResponse] = await Promise.all([
|
||||||
@ -53,49 +63,47 @@ export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle
|
|||||||
axios.get(`${apiUrl}/salary`, {
|
axios.get(`${apiUrl}/salary`, {
|
||||||
params: { socCode: career.code.split('.')[0], area: areaTitle },
|
params: { socCode: career.code.split('.')[0], area: areaTitle },
|
||||||
headers,
|
headers,
|
||||||
}).catch((error) => error.response), // Catch error for 404 handling
|
}).then((res) => {
|
||||||
|
updateProgress();
|
||||||
|
return res.data;
|
||||||
|
}).catch((error) => {
|
||||||
|
updateProgress();
|
||||||
|
return error.response?.status === 404 ? null : error.response;
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isEconomicMissing = !economicData || Object.keys(economicData).length === 0;
|
const isEconomicMissing = !economicData || Object.keys(economicData).length === 0;
|
||||||
console.log(`Economic Data for ${career.code}:`, economicData);
|
const isSalaryMissing = !salaryResponse;
|
||||||
|
|
||||||
// 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;
|
const isLimitedData = isCipMissing || isJobDetailsMissing || isEconomicMissing || isSalaryMissing;
|
||||||
|
|
||||||
return { ...career, limitedData: isLimitedData };
|
return { ...career, limitedData: isLimitedData };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error checking API response for ${career.title}:`, error);
|
console.error(`Error checking API response for ${career.title}:`, error);
|
||||||
return { ...career, limitedData: true }; // Mark as limited if any request fails
|
return { ...career, limitedData: true };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedCareerList = await Promise.all(careerPromises);
|
const updatedCareerList = await Promise.all(careerPromises);
|
||||||
|
|
||||||
console.log("✅ Final Updated Careers with limitedData:", updatedCareerList);
|
|
||||||
setUpdatedCareers(updatedCareerList);
|
setUpdatedCareers(updatedCareerList);
|
||||||
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
checkCareerDataAvailability();
|
checkCareerDataAvailability();
|
||||||
}, [careerSuggestions, apiUrl, userState, areaTitle]);
|
}, [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>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="progress-container">
|
||||||
|
<div className="progress-bar" style={{ width: `${progress}%` }}>
|
||||||
|
{Math.round(progress)}%
|
||||||
|
</div>
|
||||||
|
<p>Loading Career Suggestions...</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="career-suggestions-grid">
|
<div className="career-suggestions-grid">
|
||||||
{updatedCareers.map((career) => (
|
{updatedCareers.map((career) => (
|
||||||
<button
|
<button
|
||||||
@ -107,6 +115,7 @@ export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,35 @@ h2 {
|
|||||||
background-color: #0056b3;
|
background-color: #0056b3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Progress Bar */
|
||||||
|
.progress-container {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Striped Progress Bar */
|
||||||
|
.progress-bar {
|
||||||
|
height: 12px;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
#007bff,
|
||||||
|
#007bff 10px,
|
||||||
|
#5a9bff 10px,
|
||||||
|
#5a9bff 20px
|
||||||
|
);
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.3s ease-in-out;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
/* RIASEC Section */
|
/* RIASEC Section */
|
||||||
.riasec-container {
|
.riasec-container {
|
||||||
|
@ -34,6 +34,8 @@ function Dashboard() {
|
|||||||
const [selectedJobZone, setSelectedJobZone] = useState('');
|
const [selectedJobZone, setSelectedJobZone] = useState('');
|
||||||
const [careersWithJobZone, setCareersWithJobZone] = useState([]); // Store careers with job zone info
|
const [careersWithJobZone, setCareersWithJobZone] = useState([]); // Store careers with job zone info
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const jobZoneLabels = {
|
const jobZoneLabels = {
|
||||||
'1': 'Little or No Preparation',
|
'1': 'Little or No Preparation',
|
||||||
'2': 'Some Preparation Needed',
|
'2': 'Some Preparation Needed',
|
||||||
|
Loading…
Reference in New Issue
Block a user