dev1/src/components/CareerSuggestions.js
2025-05-13 19:49:08 +00:00

124 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import './Dashboard.css'; // or replace with Tailwind classes if desired
const apiUrl = process.env.REACT_APP_API_URL || '';
export function CareerSuggestions({
careerSuggestions = [],
userState,
areaTitle,
setLoading,
setProgress,
onCareerClick,
}) {
const [updatedCareers, setUpdatedCareers] = useState([]);
useEffect(() => {
// If no careers provided, stop any loading state
if (!careerSuggestions || careerSuggestions.length === 0) {
setLoading(false);
return;
}
const token = localStorage.getItem('token');
const checkCareerDataAvailability = async () => {
setLoading(true);
setProgress(0);
// Each career has 4 external calls
const totalSteps = careerSuggestions.length * 4;
let completedSteps = 0;
// Helper function to increment the global progress
const updateProgress = () => {
completedSteps += 1;
const percent = Math.round((completedSteps / totalSteps) * 100);
setProgress(percent);
};
// Universal fetch helper
const fetchJSON = async (url, params) => {
try {
const response = await axios.get(url, {
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/json',
},
params: params || {},
});
updateProgress(); // increment if success
return response.data;
} catch (error) {
updateProgress(); // increment even on failure
return null;
}
};
// Map over careerSuggestions to fetch CIP, job details, economic, salary data in parallel
const careerPromises = careerSuggestions.map(async (career) => {
try {
// e.g. "15-1199.00" => "15-1199"
const strippedSoc = career.code.split('.')[0];
const [cipData, jobDetailsData, economicData, salaryData] = await Promise.all([
fetchJSON(`${apiUrl}/cip/${career.code}`),
fetchJSON(`${apiUrl}/onet/career-description/${career.code}`),
fetchJSON(`${apiUrl}/projections/${strippedSoc}`),
fetchJSON(`${apiUrl}/salary`, {
socCode: strippedSoc,
area: areaTitle,
}),
]);
// Evaluate if any data is missing
const isCipMissing = !cipData || Object.keys(cipData).length === 0;
const isJobDetailsMissing = !jobDetailsData || Object.keys(jobDetailsData).length === 0;
const isEconomicMissing =
!economicData ||
Object.values(economicData).every((val) => val === 'N/A' || val === '*');
const isSalaryMissing = !salaryData;
const isLimitedData =
isCipMissing || isJobDetailsMissing || isEconomicMissing || isSalaryMissing;
return {
...career,
limitedData: isLimitedData,
};
} catch (err) {
// If any errors occur mid-logic, mark it limited
return { ...career, limitedData: true };
}
});
try {
const updatedCareerList = await Promise.all(careerPromises);
setUpdatedCareers(updatedCareerList);
} finally {
setLoading(false);
}
};
checkCareerDataAvailability();
}, [careerSuggestions, userState, areaTitle, setLoading, setProgress]);
return (
<div className="career-suggestions-grid">
{updatedCareers.map((career) => (
<button
key={career.code}
className={`career-button ${career.limitedData ? 'limited-data' : ''}`}
onClick={() => onCareerClick(career)}
>
{career.title}
{career.limitedData && <span className="warning-icon"> </span>}
</button>
))}
</div>
);
}
export default CareerSuggestions;