Fixed loading for CareerExplorer, and no reload on filter

This commit is contained in:
Josh 2025-05-21 18:56:32 +00:00
parent 3932194079
commit a053da72e5
4 changed files with 361 additions and 244 deletions

View File

@ -89,6 +89,7 @@ function App() {
// Logout
const handleLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('careerSuggestionsCache');
setIsAuthenticated(false);
setUser(null);
navigate('/signin');

View File

@ -8,7 +8,7 @@ import CareerSearch from './CareerSearch.js';
import { Button } from './ui/button.js';
import axios from 'axios';
const STATES = [
const STATES = [
{ name: 'Alabama', code: 'AL' }, { name: 'Alaska', code: 'AK' }, { name: 'Arizona', code: 'AZ' },
{ name: 'Arkansas', code: 'AR' }, { name: 'California', code: 'CA' }, { name: 'Colorado', code: 'CO' },
{ name: 'Connecticut', code: 'CT' }, { name: 'Delaware', code: 'DE' }, { name: 'District of Columbia', code: 'DC' },
@ -26,9 +26,9 @@ import axios from 'axios';
{ name: 'Tennessee', code: 'TN' }, { name: 'Texas', code: 'TX' }, { name: 'Utah', code: 'UT' },
{ name: 'Vermont', code: 'VT' }, { name: 'Virginia', code: 'VA' }, { name: 'Washington', code: 'WA' },
{ name: 'West Virginia', code: 'WV' }, { name: 'Wisconsin', code: 'WI' }, { name: 'Wyoming', code: 'WY' },
];
];
// -------------- CIP HELPER FUNCTIONS --------------
// -------------- CIP HELPER FUNCTIONS --------------
// 1) Insert leading zero if there's only 1 digit before the decimal
function ensureTwoDigitsBeforeDecimal(cipStr) {
@ -55,6 +55,7 @@ function CareerExplorer() {
const location = useLocation();
const apiUrl = process.env.REACT_APP_API_URL || '';
// ---------- Component States ----------
const [userProfile, setUserProfile] = useState(null);
const [masterCareerRatings, setMasterCareerRatings] = useState([]);
const [careerList, setCareerList] = useState([]);
@ -65,8 +66,10 @@ function CareerExplorer() {
const [userZipcode, setUserZipcode] = useState(null);
const [error, setError] = useState(null);
const [pendingCareerForModal, setPendingCareerForModal] = useState(null);
// This is where we'll hold ALL final suggestions (with job_zone merged)
const [careerSuggestions, setCareerSuggestions] = useState([]);
const [careersWithJobZone, setCareersWithJobZone] = useState([]);
const [salaryData, setSalaryData] = useState([]);
const [economicProjections, setEconomicProjections] = useState(null);
const [selectedJobZone, setSelectedJobZone] = useState('');
@ -89,7 +92,143 @@ function CareerExplorer() {
Good: 'Good - Less Strong Match',
};
// ===================== Load user profile =====================
// --------------------------------------------------
// fetchSuggestions - combined suggestions + job zone
// --------------------------------------------------
const fetchSuggestions = async (answers, profileData) => {
if (!answers) {
setCareerSuggestions([]);
localStorage.removeItem('careerSuggestionsCache');
// Reset loading & progress if userProfile has no answers
setLoading(true);
setProgress(0);
setLoading(false);
return;
}
try {
setLoading(true);
setProgress(0);
// 1) O*NET answers -> initial career list
const submitRes = await axios.post(`${apiUrl}/onet/submit_answers`, {
answers,
state: profileData.state,
area: profileData.area,
});
const { careers = [] } = submitRes.data || {};
const flattened = careers.flat();
// We'll do an extra single call for job zones + 4 calls for each career:
// => total steps = 1 (jobZones) + (flattened.length * 4)
let totalSteps = 1 + (flattened.length * 4);
let completedSteps = 0;
// Increments the global progress bar
const increment = () => {
completedSteps++;
const pct = Math.round((completedSteps / totalSteps) * 100);
setProgress(pct);
};
// A helper that does a GET request, increments progress on success/fail
const fetchWithProgress = async (url, params) => {
try {
const res = await axios.get(url, { params });
increment();
return res.data;
} catch (err) {
increment();
return {}; // or null
}
};
// 2) job zones (one call for all SOC codes)
const socCodes = flattened.map((c) => c.code);
const zonesRes = await axios.post(`${apiUrl}/job-zones`, { socCodes }).catch(() => null);
// increment progress for this single request
increment();
const jobZoneData = zonesRes?.data || {};
// 3) For each career, also fetch CIP, job details, projections, salary
const enrichedPromiseArray = flattened.map(async (career) => {
const strippedSoc = career.code.split('.')[0];
// build URLs
const cipUrl = `${apiUrl}/cip/${career.code}`;
const jobDetailsUrl = `${apiUrl}/onet/career-description/${career.code}`;
const economicUrl = `${apiUrl}/projections/${strippedSoc}`;
const salaryParams = { socCode: strippedSoc, area: profileData.area };
// We'll fetch them in parallel with our custom fetchWithProgress:
const [cipRaw, jobRaw, ecoRaw, salRaw] = await Promise.all([
fetchWithProgress(cipUrl),
fetchWithProgress(jobDetailsUrl),
fetchWithProgress(economicUrl),
fetchWithProgress(`${apiUrl}/salary`, salaryParams),
]);
// parse data
const cip = cipRaw || {};
const jobDetails = jobRaw || {};
const economic = ecoRaw || {};
const salary = salRaw || {};
// Check if data is missing
const isCipMissing = !cip || Object.keys(cip).length === 0;
const isJobDetailsMissing = !jobDetails || Object.keys(jobDetails).length === 0;
const isEconomicMissing =
!economic || Object.values(economic).every((val) => val === 'N/A' || val === '*');
const isSalaryMissing = !salary || Object.keys(salary).length === 0;
const isLimitedData =
isCipMissing || isJobDetailsMissing || isEconomicMissing || isSalaryMissing;
return {
...career,
job_zone: jobZoneData[career.code.slice(0, -3)]?.job_zone || null,
limitedData: isLimitedData,
};
});
// Wait for everything to finish
const finalEnrichedCareers = await Promise.all(enrichedPromiseArray);
// Store final suggestions in local storage
localStorage.setItem('careerSuggestionsCache', JSON.stringify(finalEnrichedCareers));
// Update React state
setCareerSuggestions(finalEnrichedCareers);
} catch (err) {
console.error('[fetchSuggestions] Error:', err);
setCareerSuggestions([]);
localStorage.removeItem('careerSuggestionsCache');
} finally {
// Hide spinner
setLoading(false);
}
};
// --------------------------------------
// On mount, load suggestions from cache
// --------------------------------------
useEffect(() => {
const cached = localStorage.getItem('careerSuggestionsCache');
if (cached) {
const parsed = JSON.parse(cached);
if (parsed?.length) {
setCareerSuggestions(parsed);
}
}
}, []);
// --------------------------------------
// Load user profile
// --------------------------------------
useEffect(() => {
setLoading(true);
@ -102,49 +241,21 @@ function CareerExplorer() {
if (res.status === 200) {
const profileData = res.data;
console.log('[fetchUserProfile] loaded profileData =>', profileData);
setUserProfile(profileData);
setUserState(profileData.state);
setAreaTitle(profileData.area);
setUserZipcode(profileData.zipcode);
// If they have a saved career list
if (profileData.career_list) {
setCareerList(JSON.parse(profileData.career_list));
}
// If they have interest inventory, fetch suggestions
if (profileData.interest_inventory_answers) {
const answers = profileData.interest_inventory_answers;
const careerSuggestionsRes = await axios.post(`${apiUrl}/onet/submit_answers`, {
answers,
state: profileData.state,
area: profileData.area,
});
const { careers = [] } = careerSuggestionsRes.data || {};
setCareerSuggestions(careers.flat());
} else {
setCareerSuggestions([]);
}
// Check if priorities answered
const priorities = profileData.career_priorities
? JSON.parse(profileData.career_priorities)
: {};
const allAnswered = ['interests','meaning','stability','growth','balance','recognition']
.every((key) => priorities[key]);
if (!allAnswered) {
setShowModal(true);
}
} else {
setShowModal(true);
}
} catch (err) {
console.error('Error fetching user profile:', err);
setShowModal(true);
} finally {
setLoading(false);
}
};
@ -152,39 +263,23 @@ function CareerExplorer() {
fetchUserProfile();
}, [apiUrl]);
// ===================== If location.state has careerSuggestions =====================
// ------------------------------------------------------
// If user came from Interest Inventory => auto-fetch
// ------------------------------------------------------
useEffect(() => {
if (location.state?.careerSuggestions) {
setCareerSuggestions(location.state.careerSuggestions);
if (
location.state?.fromInterestInventory &&
userProfile?.interest_inventory_answers
) {
fetchSuggestions(userProfile.interest_inventory_answers, userProfile);
// remove that state so refresh doesn't re-fetch
navigate('.', { replace: true });
}
}, [location.state]);
}, [location.state, userProfile, fetchSuggestions, navigate]);
// ===================== Fetch job zones for suggestions =====================
useEffect(() => {
const fetchJobZones = async () => {
if (!careerSuggestions.length) return;
const flatSuggestions = careerSuggestions.flat();
const socCodes = flatSuggestions.map((career) => career.code);
try {
const response = await axios.post(`${apiUrl}/job-zones`, { socCodes });
const jobZoneData = response.data;
const updatedCareers = flatSuggestions.map((career) => ({
...career,
job_zone: jobZoneData[career.code.slice(0, -3)]?.job_zone || null,
}));
setCareersWithJobZone([...updatedCareers]);
} catch (error) {
console.error('Error fetching job zone information:', error);
}
};
fetchJobZones();
}, [careerSuggestions, apiUrl]);
// ===================== handleCareerClick (detail fetch) =====================
// ------------------------------------------------------
// handleCareerClick (detail fetch for CIP, Salary, etc.)
// ------------------------------------------------------
const handleCareerClick = useCallback(
async (career) => {
console.log('[handleCareerClick] career =>', career);
@ -195,8 +290,6 @@ function CareerExplorer() {
setSalaryData([]);
setEconomicProjections({});
setSelectedCareer(career);
if (!socCode) {
console.error('SOC Code is missing');
setError('SOC Code is missing');
@ -211,7 +304,9 @@ function CareerExplorer() {
setError(
`We're sorry, but specific details for "${career.title}" are not available at this time.`
);
setCareerDetails({ error: `We're sorry, but detailed info for "${career.title}" isn't available right now.`});
setCareerDetails({
error: `We're sorry, but detailed info for "${career.title}" isn't available right now.`,
});
setLoading(false);
return;
}
@ -219,9 +314,13 @@ function CareerExplorer() {
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
// Job details
const jobDetailsResponse = await fetch(`${apiUrl}/onet/career-description/${socCode}`);
if (!jobDetailsResponse.ok){
setCareerDetails({ error: `We're sorry, but detailed info for "${career.title}" isn't available right now.`});
const jobDetailsResponse = await fetch(
`${apiUrl}/onet/career-description/${socCode}`
);
if (!jobDetailsResponse.ok) {
setCareerDetails({
error: `We're sorry, but detailed info for "${career.title}" isn't available right now.`,
});
setLoading(false);
return;
}
@ -237,35 +336,64 @@ function CareerExplorer() {
salaryResponse = { data: {} };
}
// Build salary array
const sData = salaryResponse.data || {};
const salaryDataPoints =
sData && Object.keys(sData).length > 0
? [
{
percentile: '10th Percentile',
regionalSalary: parseInt(sData.regional?.regional_PCT10, 10) || 0,
nationalSalary: parseInt(sData.national?.national_PCT10, 10) || 0,
regionalSalary: parseInt(
sData.regional?.regional_PCT10,
10
) || 0,
nationalSalary: parseInt(
sData.national?.national_PCT10,
10
) || 0,
},
{
percentile: '25th Percentile',
regionalSalary: parseInt(sData.regional?.regional_PCT25, 10) || 0,
nationalSalary: parseInt(sData.national?.national_PCT25, 10) || 0,
regionalSalary: parseInt(
sData.regional?.regional_PCT25,
10
) || 0,
nationalSalary: parseInt(
sData.national?.national_PCT25,
10
) || 0,
},
{
percentile: 'Median',
regionalSalary: parseInt(sData.regional?.regional_MEDIAN, 10) || 0,
nationalSalary: parseInt(sData.national?.national_MEDIAN, 10) || 0,
regionalSalary: parseInt(
sData.regional?.regional_MEDIAN,
10
) || 0,
nationalSalary: parseInt(
sData.national?.national_MEDIAN,
10
) || 0,
},
{
percentile: '75th Percentile',
regionalSalary: parseInt(sData.regional?.regional_PCT75, 10) || 0,
nationalSalary: parseInt(sData.national?.national_PCT75, 10) || 0,
regionalSalary: parseInt(
sData.regional?.regional_PCT75,
10
) || 0,
nationalSalary: parseInt(
sData.national?.national_PCT75,
10
) || 0,
},
{
percentile: '90th Percentile',
regionalSalary: parseInt(sData.regional?.regional_PCT90, 10) || 0,
nationalSalary: parseInt(sData.national?.national_PCT90, 10) || 0,
regionalSalary: parseInt(
sData.regional?.regional_PCT90,
10
) || 0,
nationalSalary: parseInt(
sData.national?.national_PCT90,
10
) || 0,
},
]
: [];
@ -274,9 +402,12 @@ function CareerExplorer() {
const fullStateName = getFullStateName(userState);
let economicResponse = { data: {} };
try {
economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`, {
economicResponse = await axios.get(
`${apiUrl}/projections/${socCode.split('.')[0]}`,
{
params: { state: fullStateName },
});
}
);
} catch (error) {
economicResponse = { data: {} };
}
@ -300,10 +431,12 @@ function CareerExplorer() {
setLoading(false);
}
},
[userState, apiUrl, areaTitle, userZipcode]
[userState, apiUrl, areaTitle]
);
// ===================== handleCareerFromSearch =====================
// ------------------------------------------------------
// handleCareerFromSearch
// ------------------------------------------------------
const handleCareerFromSearch = useCallback(
(obj) => {
const adapted = {
@ -317,6 +450,9 @@ function CareerExplorer() {
[handleCareerClick]
);
// ------------------------------------------------------
// pendingCareerForModal effect
// ------------------------------------------------------
useEffect(() => {
if (pendingCareerForModal) {
console.log('[useEffect] pendingCareerForModal =>', pendingCareerForModal);
@ -325,7 +461,9 @@ function CareerExplorer() {
}
}, [pendingCareerForModal, handleCareerFromSearch]);
// ===================== Load careers_with_ratings for CIP arrays =====================
// ------------------------------------------------------
// Load careers_with_ratings for CIP arrays
// ------------------------------------------------------
useEffect(() => {
fetch('/careers_with_ratings.json')
.then((res) => {
@ -336,17 +474,33 @@ function CareerExplorer() {
.catch((err) => console.error('Error fetching career ratings:', err));
}, []);
// ------------------------------------------------------
// Derived data / Helpers
// ------------------------------------------------------
const priorities = useMemo(() => {
return userProfile?.career_priorities ? JSON.parse(userProfile.career_priorities) : {};
return userProfile?.career_priorities
? JSON.parse(userProfile.career_priorities)
: {};
}, [userProfile]);
const priorityKeys = ['interests', 'meaning', 'stability', 'growth', 'balance', 'recognition'];
const priorityKeys = [
'interests',
'meaning',
'stability',
'growth',
'balance',
'recognition',
];
const getCareerRatingsBySocCode = (socCode) => {
return masterCareerRatings.find((c) => c.soc_code === socCode)?.ratings || {};
return (
masterCareerRatings.find((c) => c.soc_code === socCode)?.ratings || {}
);
};
// ===================== Save comparison list to backend =====================
// ------------------------------------------------------
// Save comparison list to backend
// ------------------------------------------------------
const saveCareerListToBackend = async (newCareerList) => {
try {
const token = localStorage.getItem('token');
@ -373,7 +527,9 @@ function CareerExplorer() {
}
};
// ===================== Add/Remove from comparison =====================
// ------------------------------------------------------
// Add/Remove from comparison
// ------------------------------------------------------
const addCareerToList = (career) => {
const masterRatings = getCareerRatingsBySocCode(career.code);
@ -389,7 +545,10 @@ function CareerExplorer() {
: fitRatingMap[career.fit] || masterRatings.interests || 3;
const meaningRating = parseInt(
prompt("How important do you feel this job is to society or the world? (1-5):", "3"),
prompt(
"How important do you feel this job is to society or the world? (1-5):",
"3"
),
10
);
@ -433,7 +592,9 @@ function CareerExplorer() {
});
};
// ===================== Let user pick a career from comparison => "Select for Education" =====================
// ------------------------------------------------------
// "Select for Education" => navigate with CIP codes
// ------------------------------------------------------
const handleSelectForEducation = (career) => {
// 1) Confirm
const confirmed = window.confirm(
@ -450,8 +611,7 @@ function CareerExplorer() {
// 3) Clean CIP codes
const rawCips = matching.cip_codes || [];
const cleanedCips = cleanCipCodes(rawCips); // from top-level function
console.log('cleanedCips =>', cleanedCips);
const cleanedCips = cleanCipCodes(rawCips);
// 4) Navigate
navigate('/educational-programs', {
@ -465,22 +625,26 @@ function CareerExplorer() {
});
};
// ===================== Filter logic for jobZone, Fit =====================
// ------------------------------------------------------
// Filter logic for jobZone, Fit
// ------------------------------------------------------
const filteredCareers = useMemo(() => {
return careersWithJobZone.filter((career) => {
return careerSuggestions.filter((career) => {
// If user selected a jobZone, check if career.job_zone matches
const jobZoneMatches = selectedJobZone
? career.job_zone !== null &&
career.job_zone !== undefined &&
Number(career.job_zone) === Number(selectedJobZone)
: true;
// If user selected a fit, check if career.fit matches
const fitMatches = selectedFit ? career.fit === selectedFit : true;
return jobZoneMatches && fitMatches;
});
}, [careersWithJobZone, selectedJobZone, selectedFit]);
}, [careerSuggestions, selectedJobZone, selectedFit]);
// Weighted “match score” logic. (unchanged)
// Weighted "match score" logic. (unchanged)
const priorityWeight = (priority, response) => {
const weightMap = {
interests: {
@ -516,6 +680,9 @@ function CareerExplorer() {
return weightMap[priority][response] || 1;
};
// ------------------------------------------------------
// Loading Overlay
// ------------------------------------------------------
const renderLoadingOverlay = () => {
if (!loading) return null;
return (
@ -535,15 +702,20 @@ function CareerExplorer() {
);
};
// ------------------------------------------------------
// Render
// ------------------------------------------------------
return (
<div className="career-explorer-container bg-white p-6 rounded shadow">
{renderLoadingOverlay()}
{showModal && (
<CareerPrioritiesModal
userProfile={userProfile}
onClose={() => setShowModal(false)}
/>
)}
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold">
Explore Careers - use the tools below to find your perfect career
@ -581,12 +753,30 @@ function CareerExplorer() {
const balanceRating = ratings.balance || 3;
const recognitionRating = ratings.recognition || 3;
const userInterestsWeight = priorityWeight('interests', priorities.interests || 'Im not sure yet');
const userMeaningWeight = priorityWeight('meaning', priorities.meaning);
const userStabilityWeight = priorityWeight('stability', priorities.stability);
const userGrowthWeight = priorityWeight('growth', priorities.growth);
const userBalanceWeight = priorityWeight('balance', priorities.balance);
const userRecognitionWeight = priorityWeight('recognition', priorities.recognition);
const userInterestsWeight = priorityWeight(
'interests',
priorities.interests || 'Im not sure yet'
);
const userMeaningWeight = priorityWeight(
'meaning',
priorities.meaning
);
const userStabilityWeight = priorityWeight(
'stability',
priorities.stability
);
const userGrowthWeight = priorityWeight(
'growth',
priorities.growth
);
const userBalanceWeight = priorityWeight(
'balance',
priorities.balance
);
const userRecognitionWeight = priorityWeight(
'recognition',
priorities.recognition
);
const totalWeight =
userInterestsWeight +
@ -615,7 +805,9 @@ function CareerExplorer() {
<td className="border p-2">{growthRating}</td>
<td className="border p-2">{balanceRating}</td>
<td className="border p-2">{recognitionRating}</td>
<td className="border p-2 font-bold">{matchScore.toFixed(1)}%</td>
<td className="border p-2 font-bold">
{matchScore.toFixed(1)}%
</td>
<td className="border p-2 space-x-2">
<Button
className="bg-red-600 text-black-500"
@ -624,9 +816,11 @@ function CareerExplorer() {
Remove
</Button>
{/* New Button -> "Select for Education" */}
<Button
className="bg-green-600 text-white"
className="bg-green-600 text-white px-2 py-1 text-xs
sm:text-sm
whitespace-nowrap
"
onClick={() => handleSelectForEducation(career)}
>
Plan your Education/Skills
@ -667,18 +861,27 @@ function CareerExplorer() {
</option>
))}
</select>
<Button
onClick={() => {
if (!userProfile?.interest_inventory_answers) {
alert('No interest inventory answers. Complete the interest inventory first!');
return;
}
fetchSuggestions(userProfile.interest_inventory_answers, userProfile);
}}
className="bg-green-600 text-white px-3 py-1 text-xs sm:text-sm"
>
Reload Career Suggestions
</Button>
</div>
{/* Now we pass the *filteredCareers* into the CareerSuggestions component */}
<CareerSuggestions
careerSuggestions={filteredCareers}
onCareerClick={(career) => {
setSelectedCareer(career);
handleCareerClick(career);
}}
setLoading={setLoading}
setProgress={setProgress}
userState={userState}
areaTitle={areaTitle}
/>
{selectedCareer && (
@ -695,17 +898,29 @@ function CareerExplorer() {
<div className="mt-6 text-xs text-gray-500 border-t pt-2">
Career results and details provided by
<a href="https://www.onetcenter.org" target="_blank" rel="noopener noreferrer">
<a
href="https://www.onetcenter.org"
target="_blank"
rel="noopener noreferrer"
>
{' '}
O*Net
</a>
, in partnership with
<a href="https://www.bls.gov" target="_blank" rel="noopener noreferrer">
<a
href="https://www.bls.gov"
target="_blank"
rel="noopener noreferrer"
>
{' '}
Bureau of Labor Statistics
</a>
and
<a href="https://nces.ed.gov" target="_blank" rel="noopener noreferrer">
<a
href="https://nces.ed.gov"
target="_blank"
rel="noopener noreferrer"
>
{' '}
NCES
</a>

View File

@ -1,112 +1,13 @@
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 || '';
import React from 'react';
import './Dashboard.css'; // or Tailwind classes
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) => (
{careerSuggestions.map((career) => (
<button
key={career.code}
className={`career-button ${career.limitedData ? 'limited-data' : ''}`}

View File

@ -156,7 +156,7 @@ const InterestInventory = () => {
const { careers: careerSuggestions, riaSecScores } = data;
if (Array.isArray(careerSuggestions) && Array.isArray(riaSecScores)) {
navigate('/career-explorer', { state: { careerSuggestions, riaSecScores } });
navigate('/career-explorer', { state: { careerSuggestions, riaSecScores, fromInterestInventory: true } });
} else {
throw new Error('Invalid data format from the server.');
}