124 lines
3.8 KiB
JavaScript
124 lines
3.8 KiB
JavaScript
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;
|