diff --git a/src/App.js b/src/App.js index c38cb52..f260c4e 100644 --- a/src/App.js +++ b/src/App.js @@ -17,7 +17,6 @@ import SignIn from './components/SignIn.js'; import SignUp from './components/SignUp.js'; import PlanningLanding from './components/PlanningLanding.js'; import CareerExplorer from './components/CareerExplorer.js'; -import EducationalPrograms from './components/EducationalPrograms.js'; import PreparingLanding from './components/PreparingLanding.js'; import EducationalProgramsPage from './components/EducationalProgramsPage.js'; import EnhancingLanding from './components/EnhancingLanding.js'; @@ -126,6 +125,11 @@ function App() { Career Explorer +
  • + + Skills/Educational Planner + +
  • Interest Inventory diff --git a/src/components/CareerExplorer.js b/src/components/CareerExplorer.js index be992ea..aa49cc6 100644 --- a/src/components/CareerExplorer.js +++ b/src/components/CareerExplorer.js @@ -1,73 +1,57 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import CareerSuggestions from './CareerSuggestions.js'; import CareerPrioritiesModal from './CareerPrioritiesModal.js'; import CareerModal from './CareerModal.js'; import CareerSearch from './CareerSearch.js'; -import {Button} from './ui/button.js'; +import { Button } from './ui/button.js'; import axios from 'axios'; -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' }, - { name: 'Florida', code: 'FL' }, - { name: 'Georgia', code: 'GA' }, - { name: 'Hawaii', code: 'HI' }, - { name: 'Idaho', code: 'ID' }, - { name: 'Illinois', code: 'IL' }, - { name: 'Indiana', code: 'IN' }, - { name: 'Iowa', code: 'IA' }, - { name: 'Kansas', code: 'KS' }, - { name: 'Kentucky', code: 'KY' }, - { name: 'Louisiana', code: 'LA' }, - { name: 'Maine', code: 'ME' }, - { name: 'Maryland', code: 'MD' }, - { name: 'Massachusetts', code: 'MA' }, - { name: 'Michigan', code: 'MI' }, - { name: 'Minnesota', code: 'MN' }, - { name: 'Mississippi', code: 'MS' }, - { name: 'Missouri', code: 'MO' }, - { name: 'Montana', code: 'MT' }, - { name: 'Nebraska', code: 'NE' }, - { name: 'Nevada', code: 'NV' }, - { name: 'New Hampshire', code: 'NH' }, - { name: 'New Jersey', code: 'NJ' }, - { name: 'New Mexico', code: 'NM' }, - { name: 'New York', code: 'NY' }, - { name: 'North Carolina', code: 'NC' }, - { name: 'North Dakota', code: 'ND' }, - { name: 'Ohio', code: 'OH' }, - { name: 'Oklahoma', code: 'OK' }, - { name: 'Oregon', code: 'OR' }, - { name: 'Pennsylvania', code: 'PA' }, - { name: 'Rhode Island', code: 'RI' }, - { name: 'South Carolina', code: 'SC' }, - { name: 'South Dakota', code: 'SD' }, - { 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' }, -]; + 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' }, + { name: 'Florida', code: 'FL' }, { name: 'Georgia', code: 'GA' }, { name: 'Hawaii', code: 'HI' }, + { name: 'Idaho', code: 'ID' }, { name: 'Illinois', code: 'IL' }, { name: 'Indiana', code: 'IN' }, + { name: 'Iowa', code: 'IA' }, { name: 'Kansas', code: 'KS' }, { name: 'Kentucky', code: 'KY' }, + { name: 'Louisiana', code: 'LA' }, { name: 'Maine', code: 'ME' }, { name: 'Maryland', code: 'MD' }, + { name: 'Massachusetts', code: 'MA' }, { name: 'Michigan', code: 'MI' }, { name: 'Minnesota', code: 'MN' }, + { name: 'Mississippi', code: 'MS' }, { name: 'Missouri', code: 'MO' }, { name: 'Montana', code: 'MT' }, + { name: 'Nebraska', code: 'NE' }, { name: 'Nevada', code: 'NV' }, { name: 'New Hampshire', code: 'NH' }, + { name: 'New Jersey', code: 'NJ' }, { name: 'New Mexico', code: 'NM' }, { name: 'New York', code: 'NY' }, + { name: 'North Carolina', code: 'NC' }, { name: 'North Dakota', code: 'ND' }, { name: 'Ohio', code: 'OH' }, + { name: 'Oklahoma', code: 'OK' }, { name: 'Oregon', code: 'OR' }, { name: 'Pennsylvania', code: 'PA' }, + { name: 'Rhode Island', code: 'RI' }, { name: 'South Carolina', code: 'SC' }, { name: 'South Dakota', code: 'SD' }, + { 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 -------------- + +// 1) Insert leading zero if there's only 1 digit before the decimal +function ensureTwoDigitsBeforeDecimal(cipStr) { + // e.g. "4.0201" => "04.0201" + return cipStr.replace(/^(\d)\./, '0$1.'); +} + +// 2) Clean an array of CIP codes, e.g. ["4.0201", "14.0901"] => ["0402", "1409"] +function cleanCipCodes(cipArray) { + return cipArray.map((code) => { + let codeStr = code.toString(); + codeStr = ensureTwoDigitsBeforeDecimal(codeStr); // ensure "04.0201" + return codeStr.replace('.', '').slice(0, 4); // => "040201" => "0402" + }); +} function getFullStateName(code) { const found = STATES.find((s) => s.code === code?.toUpperCase()); return found ? found.name : ''; } -function CareerExplorer({ }) { +function CareerExplorer() { + const navigate = useNavigate(); const location = useLocation(); const apiUrl = process.env.REACT_APP_API_URL || ''; @@ -105,242 +89,243 @@ function CareerExplorer({ }) { Good: 'Good - Less Strong Match', }; + // ===================== Load user profile ===================== useEffect(() => { setLoading(true); - const fetchUserProfile = async () => { - try { - const token = localStorage.getItem('token'); - const res = await axios.get(`${apiUrl}/user-profile`, { - headers: { Authorization: `Bearer ${token}` }, - }); - if (res.status === 200) { - const profileData = res.data; - console.log('[fetchUserProfile] loaded profileData =>', profileData); + const fetchUserProfile = async () => { + try { + const token = localStorage.getItem('token'); + const res = await axios.get(`${apiUrl}/user-profile`, { + headers: { Authorization: `Bearer ${token}` }, + }); - // 1) Set userProfile and all relevant states using `profileData`: - setUserProfile(profileData); - setUserState(profileData.state); - setAreaTitle(profileData.area); - setUserZipcode(profileData.zipcode); + if (res.status === 200) { + const profileData = res.data; + console.log('[fetchUserProfile] loaded profileData =>', profileData); - // 2) Load saved career list if it exists - if (profileData.career_list) { - setCareerList(JSON.parse(profileData.career_list)); - } + setUserProfile(profileData); + setUserState(profileData.state); + setAreaTitle(profileData.area); + setUserZipcode(profileData.zipcode); - // 3) If user has interest inventory answers, 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, - }); + // If they have a saved career list + if (profileData.career_list) { + setCareerList(JSON.parse(profileData.career_list)); + } - const { careers = [] } = careerSuggestionsRes.data || {}; - setCareerSuggestions(careers.flat()); + // 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 { - // No inventory => no suggestions (or do something else here) - setCareerSuggestions([]); - } - - // 4) Check if all priorities are 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) { - // If user hasn't answered them all, show the priorities modal setShowModal(true); } - } else { - // Not a 200 response => fallback + } catch (err) { + console.error('Error fetching user profile:', err); setShowModal(true); + setLoading(false); } - } catch (err) { - console.error('Error fetching user profile:', err); - setShowModal(true); // fallback if error - setLoading(false); - } - }; + }; - fetchUserProfile(); -}, [apiUrl]); + fetchUserProfile(); + }, [apiUrl]); + // ===================== If location.state has careerSuggestions ===================== useEffect(() => { if (location.state?.careerSuggestions) { setCareerSuggestions(location.state.careerSuggestions); } }, [location.state]); - // Fetch Job Zones if suggestions are provided + // ===================== Fetch job zones for suggestions ===================== useEffect(() => { - const fetchJobZones = async () => { - if (!careerSuggestions.length) return; + const fetchJobZones = async () => { + if (!careerSuggestions.length) return; - const flatSuggestions = careerSuggestions.flat(); - const socCodes = flatSuggestions.map((career) => career.code); + 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, - })); + 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, + })); - // IMPORTANT: Ensure this actually sets a new array - setCareersWithJobZone([...updatedCareers]); - } catch (error) { - console.error('Error fetching job zone information:', error); - } - }; + setCareersWithJobZone([...updatedCareers]); + } catch (error) { + console.error('Error fetching job zone information:', error); + } + }; - fetchJobZones(); -}, [careerSuggestions, apiUrl]); + fetchJobZones(); + }, [careerSuggestions, apiUrl]); - const handleCareerClick = useCallback( - async (career) => { - console.log('[handleCareerClick] career =>', career); - const socCode = career.code; - setSelectedCareer(career); + // ===================== handleCareerClick (detail fetch) ===================== + const handleCareerClick = useCallback( + async (career) => { + console.log('[handleCareerClick] career =>', career); + const socCode = career.code; + setSelectedCareer(career); setError(null); setCareerDetails(null); setSalaryData([]); setEconomicProjections({}); - // We can set selectedCareer immediately so that our Modal condition is met. - setSelectedCareer(career); - - if (!socCode) { - console.error('SOC Code is missing'); - setError('SOC Code is missing'); - setLoading(false); - return; - } + setSelectedCareer(career); - try { - // CIP fetch - const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`); - if (!cipResponse.ok) {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.`}); - setLoading(false); - return; - } - const { cipCode } = await cipResponse.json(); - 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.`}); - setLoading(false); - return; -} - const { description, tasks } = await jobDetailsResponse.json(); - - // Salary - let salaryResponse; - try { - salaryResponse = await axios.get(`${apiUrl}/salary`, { - params: { socCode: socCode.split('.')[0], area: areaTitle }, - }); - } catch (error) { - salaryResponse = { data: {} }; + if (!socCode) { + console.error('SOC Code is missing'); + setError('SOC Code is missing'); + setLoading(false); + return; } - // 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, - }, - { - percentile: '25th Percentile', - 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, - }, - { - percentile: '75th Percentile', - 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, - }, - ] - : []; - - // Economic - const fullStateName = getFullStateName(userState); - let economicResponse = { data: {} }; try { - economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`, { - params: { state: fullStateName }, - }); - } catch (error) { - economicResponse = { data: {} }; - } + // CIP fetch + const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`); + if (!cipResponse.ok) { + 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.`}); + setLoading(false); + return; + } + const { cipCode } = await cipResponse.json(); + const cleanedCipCode = cipCode.replace('.', '').slice(0, 4); - // Build final details - const updatedCareerDetails = { - ...career, - jobDescription: description, - tasks, - salaryData: salaryDataPoints, - economicProjections: economicResponse.data || {}, - }; + // 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.`}); + setLoading(false); + return; + } + const { description, tasks } = await jobDetailsResponse.json(); - // Now set the fully fetched data - setCareerDetails(updatedCareerDetails); + // Salary + let salaryResponse; + try { + salaryResponse = await axios.get(`${apiUrl}/salary`, { + params: { socCode: socCode.split('.')[0], area: areaTitle }, + }); + } catch (error) { + salaryResponse = { data: {} }; + } - } catch (error) { - console.error('Error processing career click:', error.message); - setCareerDetails({ - error: `We're sorry, but detailed info for "${career.title}" isn't available right now.` - }); - } finally { - setLoading(false); - } - }, - [userState, apiUrl, areaTitle, userZipcode] - ); - - // ============= Let typed careers open PopoutPanel ============= - const handleCareerFromSearch = useCallback( - (obj) => { - const adapted = { - code: obj.soc_code, - title: obj.title, - cipCode: obj.cip_code, + // 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, + }, + { + percentile: '25th Percentile', + 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, + }, + { + percentile: '75th Percentile', + 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, + }, + ] + : []; + + // Economic + const fullStateName = getFullStateName(userState); + let economicResponse = { data: {} }; + try { + economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`, { + params: { state: fullStateName }, + }); + } catch (error) { + economicResponse = { data: {} }; + } + + // Build final details + const updatedCareerDetails = { + ...career, + jobDescription: description, + tasks, + salaryData: salaryDataPoints, + economicProjections: economicResponse.data || {}, }; - console.log('[Dashboard -> handleCareerFromSearch] adapted =>', adapted); - handleCareerClick(adapted); - }, - [handleCareerClick] - ); - - useEffect(() => { - if (pendingCareerForModal) { - console.log('[useEffect] pendingCareerForModal =>', pendingCareerForModal); - handleCareerFromSearch(pendingCareerForModal); - setPendingCareerForModal(null); - } - }, [pendingCareerForModal, handleCareerFromSearch]); + setCareerDetails(updatedCareerDetails); + } catch (error) { + console.error('Error processing career click:', error.message); + setCareerDetails({ + error: `We're sorry, but detailed info for "${career.title}" isn't available right now.`, + }); + } finally { + setLoading(false); + } + }, + [userState, apiUrl, areaTitle, userZipcode] + ); + + // ===================== handleCareerFromSearch ===================== + const handleCareerFromSearch = useCallback( + (obj) => { + const adapted = { + code: obj.soc_code, + title: obj.title, + cipCode: obj.cip_code, + }; + console.log('[Dashboard -> handleCareerFromSearch] adapted =>', adapted); + handleCareerClick(adapted); + }, + [handleCareerClick] + ); + + useEffect(() => { + if (pendingCareerForModal) { + console.log('[useEffect] pendingCareerForModal =>', pendingCareerForModal); + handleCareerFromSearch(pendingCareerForModal); + setPendingCareerForModal(null); + } + }, [pendingCareerForModal, handleCareerFromSearch]); + + // ===================== Load careers_with_ratings for CIP arrays ===================== useEffect(() => { fetch('/careers_with_ratings.json') .then((res) => { @@ -350,7 +335,7 @@ function CareerExplorer({ }) { .then((data) => setMasterCareerRatings(data)) .catch((err) => console.error('Error fetching career ratings:', err)); }, []); - + const priorities = useMemo(() => { return userProfile?.career_priorities ? JSON.parse(userProfile.career_priorities) : {}; }, [userProfile]); @@ -358,103 +343,129 @@ function CareerExplorer({ }) { 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 ===================== const saveCareerListToBackend = async (newCareerList) => { - try { - const token = localStorage.getItem('token'); - await axios.post(`${apiUrl}/user-profile`, { - - firstName: userProfile?.firstname, - lastName: userProfile?.lastname, - email: userProfile?.email, - zipCode: userProfile?.zipcode, - state: userProfile?.state, - area: userProfile?.area, - careerSituation: userProfile?.career_situation, - - // For the rest, ensure we're not overwriting if not needed - interest_inventory_answers: userProfile?.interest_inventory_answers, - career_priorities: userProfile?.career_priorities, - - // IMPORTANT: We convert the newCareerList to JSON if your DB expects text - career_list: JSON.stringify(newCareerList), - }, { - headers: { Authorization: `Bearer ${token}` }, - }); - } catch (err) { - console.error('Error saving career_list:', err); - // optional: show a user-friendly error - } -}; - - const addCareerToList = (career) => { - const masterRatings = getCareerRatingsBySocCode(career.code); - - const fitRatingMap = { - Best: 5, - Great: 4, - Good: 3, - }; - - const interestsRating = - priorities.interests === "I’m not sure yet" - ? parseInt(prompt("Rate your interest in this career (1-5):", "3"), 10) - : 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"), - 10 - ); - - const stabilityRating = - career.ratings && career.ratings.stability !== undefined - ? career.ratings.stability - : masterRatings.stability || 3; - - const growthRating = masterRatings.growth || 3; - const balanceRating = masterRatings.balance || 3; - const recognitionRating = masterRatings.recognition || 3; - - const careerWithUserRatings = { - ...career, - ratings: { - interests: interestsRating, - meaning: meaningRating, - stability: stabilityRating, - growth: growthRating, - balance: balanceRating, - recognition: recognitionRating, - }, - }; - - setCareerList((prevList) => { - if (prevList.some((c) => c.code === career.code)) { - alert("Career already in comparison list."); - return prevList; + try { + const token = localStorage.getItem('token'); + await axios.post( + `${apiUrl}/user-profile`, + { + firstName: userProfile?.firstname, + lastName: userProfile?.lastname, + email: userProfile?.email, + zipCode: userProfile?.zipcode, + state: userProfile?.state, + area: userProfile?.area, + careerSituation: userProfile?.career_situation, + interest_inventory_answers: userProfile?.interest_inventory_answers, + career_priorities: userProfile?.career_priorities, + career_list: JSON.stringify(newCareerList), + }, + { + headers: { Authorization: `Bearer ${token}` }, + } + ); + } catch (err) { + console.error('Error saving career_list:', err); } + }; - const newList = [...prevList, careerWithUserRatings]; - // Call the API to save - saveCareerListToBackend(newList); - return newList; - }); -}; + // ===================== Add/Remove from comparison ===================== + const addCareerToList = (career) => { + const masterRatings = getCareerRatingsBySocCode(career.code); + const fitRatingMap = { + Best: 5, + Great: 4, + Good: 3, + }; + + const interestsRating = + priorities.interests === "I’m not sure yet" + ? parseInt(prompt("Rate your interest in this career (1-5):", "3"), 10) + : 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"), + 10 + ); + + const stabilityRating = + career.ratings && career.ratings.stability !== undefined + ? career.ratings.stability + : masterRatings.stability || 3; + + const growthRating = masterRatings.growth || 3; + const balanceRating = masterRatings.balance || 3; + const recognitionRating = masterRatings.recognition || 3; + + const careerWithUserRatings = { + ...career, + ratings: { + interests: interestsRating, + meaning: meaningRating, + stability: stabilityRating, + growth: growthRating, + balance: balanceRating, + recognition: recognitionRating, + }, + }; + + setCareerList((prevList) => { + if (prevList.some((c) => c.code === career.code)) { + alert("Career already in comparison list."); + return prevList; + } + const newList = [...prevList, careerWithUserRatings]; + saveCareerListToBackend(newList); + return newList; + }); + }; const removeCareerFromList = (careerCode) => { - setCareerList((prevList) => { - const newList = prevList.filter((c) => c.code !== careerCode); - // Call the API to save - saveCareerListToBackend(newList); - return newList; - }); -}; + setCareerList((prevList) => { + const newList = prevList.filter((c) => c.code !== careerCode); + saveCareerListToBackend(newList); + return newList; + }); + }; - + // ===================== Let user pick a career from comparison => "Select for Education" ===================== + const handleSelectForEducation = (career) => { + // 1) Confirm + const confirmed = window.confirm( + `Are you sure you want to move on to Educational Programs for ${career.title}?` + ); + if (!confirmed) return; - // Filtering logic (Job Zone and Fit) + // 2) Look up CIP codes from masterCareerRatings by SOC code + const matching = masterCareerRatings.find((r) => r.soc_code === career.code); + if (!matching) { + alert(`No CIP codes found for ${career.title}.`); + return; + } + + // 3) Clean CIP codes + const rawCips = matching.cip_codes || []; + const cleanedCips = cleanCipCodes(rawCips); // from top-level function + console.log('cleanedCips =>', cleanedCips); + + // 4) Navigate + navigate('/educational-programs', { + state: { + cipCodes: cleanedCips, + careerTitle: career.title, + userZip: userZipcode, + userState: userState, + }, + }); + }; + + + // ===================== Filter logic for jobZone, Fit ===================== const filteredCareers = useMemo(() => { return careersWithJobZone.filter((career) => { const jobZoneMatches = selectedJobZone @@ -468,6 +479,7 @@ function CareerExplorer({ }) { }); }, [careersWithJobZone, selectedJobZone, selectedFit]); + // Weighted “match score” logic. (unchanged) const priorityWeight = (priority, response) => { const weightMap = { interests: { @@ -502,28 +514,27 @@ function CareerExplorer({ }) { }; return weightMap[priority][response] || 1; }; - + const renderLoadingOverlay = () => { - if (!loading) return null; - return ( -
    -
    -
    -
    -
    -

    - {progress}% — Loading Career Suggestions... -

    -
    -
    - ); - }; + if (!loading) return null; + return ( +
    +
    +
    +
    +
    +

    + {progress}% — Loading Career Suggestions... +

    +
    +
    + ); + }; return ( -
    {renderLoadingOverlay()} {showModal && ( @@ -533,14 +544,15 @@ function CareerExplorer({ }) { /> )}
    -

    Explore Careers - use the tools in this area to find your perfect career

    +

    + Explore Careers - use the tools below to find your perfect career +

    { - console.log('[Dashboard] onCareerSelected =>', careerObj); - // Set the "pendingCareerForModal" so our useEffect fires - setPendingCareerForModal(careerObj); - }} - /> + onCareerSelected={(careerObj) => { + console.log('[Dashboard] onCareerSelected =>', careerObj); + setPendingCareerForModal(careerObj); + }} + />

    Career Comparison

    @@ -550,7 +562,9 @@ function CareerExplorer({ }) { Career {priorityKeys.map((priority) => ( - {priority} + + {priority} + ))} Match Actions @@ -559,7 +573,7 @@ function CareerExplorer({ }) { {careerList.map((career) => { const ratings = career.ratings || {}; - const interestsRating = ratings.interests || 3; // default to 3 if not rated via InterestInventory + const interestsRating = ratings.interests || 3; const meaningRating = ratings.meaning || 3; const stabilityRating = ratings.stability || 3; const growthRating = ratings.growth || 3; @@ -601,17 +615,30 @@ function CareerExplorer({ }) { {balanceRating} {recognitionRating} {matchScore.toFixed(1)}% - - + + + + {/* New Button -> "Select for Education" */} + ); })} - - ) :

    No careers added to comparison.

    } - + ) : ( +

    No careers added to comparison.

    + )}
    @@ -632,7 +661,9 @@ function CareerExplorer({ }) { > {Object.entries(fitLabels).map(([key, label]) => ( - + ))}
    @@ -649,25 +680,35 @@ function CareerExplorer({ }) { areaTitle={areaTitle} /> - {selectedCareer && ( - { - setSelectedCareer(null); - setCareerDetails(null); - }} - addCareerToList={addCareerToList} - /> - )} + {selectedCareer && ( + { + setSelectedCareer(null); + setCareerDetails(null); + }} + addCareerToList={addCareerToList} + /> + )}
    Career results and details provided by - O*Net, - in partnership with - Bureau of Labor Statistics + + {' '} + O*Net + + , in partnership with + + {' '} + Bureau of Labor Statistics + and - NCES. + + {' '} + NCES + + .
    );