// Dashboard.js import axios from 'axios'; import React, { useState, useCallback, useEffect } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js'; import { CareerSuggestions } from './CareerSuggestions.js'; import PopoutPanel from './PopoutPanel.js'; import './PopoutPanel.css'; import Chatbot from "./Chatbot.js"; import { Bar } from 'react-chartjs-2'; import { fetchSchools } from '../utils/apiUtils.js'; import './Dashboard.css'; ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); function Dashboard() { const location = useLocation() const navigate = useNavigate(); const [careerSuggestions, setCareerSuggestions] = useState([]); const [careerDetails, setCareerDetails] = useState(null); const [riaSecScores, setRiaSecScores] = useState([]); const [selectedCareer, setSelectedCareer] = useState(null); const [schools, setSchools] = useState([]); const [salaryData, setSalaryData] = useState([]); const [economicProjections, setEconomicProjections] = useState(null); const [tuitionData, setTuitionData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [userState, setUserState] = useState(null); const [areaTitle, setAreaTitle] = useState(null); const [userZipcode, setUserZipcode] = useState(null); const [riaSecDescriptions, setRiaSecDescriptions] = useState([]); const [selectedJobZone, setSelectedJobZone] = useState(''); const [careersWithJobZone, setCareersWithJobZone] = useState([]); // Store careers with job zone info const jobZoneLabels = { '1': 'Little or No Preparation', '2': 'Some Preparation Needed', '3': 'Medium Preparation Needed', '4': 'Considerable Preparation Needed', '5': 'Extensive Preparation Needed' }; // Dynamic API URL const apiUrl = process.env.REACT_APP_API_URL || ''; // Fetch job zone mappings after career suggestions are loaded useEffect(() => { const fetchJobZones = async () => { if (careerSuggestions.length === 0) return; const socCodes = careerSuggestions.map((career) => career.code); try { const response = await axios.post(`${apiUrl}/job-zones`, { socCodes }); const jobZoneData = response.data; const updatedCareers = careerSuggestions.map((career) => ({ ...career, job_zone: jobZoneData[career.code.slice(0, -3)]?.job_zone || null, // Extract correct value })); setCareersWithJobZone(updatedCareers); // Update state } catch (error) { console.error('Error fetching job zone information:', error); } }; fetchJobZones(); }, [careerSuggestions, apiUrl]); const filteredCareers = selectedJobZone ? careersWithJobZone.filter(career => { return ( career.job_zone !== null && career.job_zone !== undefined && typeof career.job_zone === 'number' && Number(career.job_zone) === Number(selectedJobZone) ); }) : careersWithJobZone; useEffect(() => { let descriptions = []; // Declare outside for scope accessibility if (location.state) { const { careerSuggestions: suggestions, riaSecScores: scores } = location.state || {}; descriptions = scores.map((score) => score.description || "No description available."); setCareerSuggestions(suggestions || []); setRiaSecScores(scores || []); setRiaSecDescriptions(descriptions); // Set descriptions } else { console.warn('No data found, redirecting to Interest Inventory'); navigate('/interest-inventory'); } }, [location.state, navigate]); useEffect(() => { const fetchUserProfile = async () => { try { const token = localStorage.getItem('token'); const profileResponse = await fetch(`${apiUrl}/user-profile`, { headers: { Authorization: `Bearer ${token}` }, }); if (profileResponse.ok) { const profileData = await profileResponse.json(); console.log('Fetched User Profile:', profileData); const { state, area, zipcode } = profileData; // Use 'area' instead of 'AREA_TITLE' setUserState(state); setAreaTitle(area && area.trim() ? area.trim() : ''); // Ensure 'area' is set correctly setUserZipcode(zipcode); // Set 'zipcode' in the state console.log('Profile Data Set:', { state, area, zipcode }); } else { console.error('Failed to fetch user profile'); } } catch (error) { console.error('Error fetching user profile:', error); } }; fetchUserProfile(); }, [apiUrl]); const handleCareerClick = useCallback( async (career) => { const socCode = career.code; // Extract SOC code from career object setSelectedCareer(career); // Set career first to trigger loading panel setLoading(true); // Enable loading state only when career is clicked setError(null); // Clear previous errors setCareerDetails({}); // Reset career details to avoid undefined errors setSchools([]); // Reset schools setSalaryData([]); // Reset salary data setEconomicProjections({}); // Reset economic projections setTuitionData([]); // Reset tuition data if (!socCode) { console.error('SOC Code is missing'); setError('SOC Code is missing'); return; } try { // Step 1: Fetch CIP Code const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`); if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code'); const { cipCode } = await cipResponse.json(); const cleanedCipCode = cipCode.replace('.', '').slice(0, 4); // Step 2: Fetch Job Description and Tasks const jobDetailsResponse = await fetch(`${apiUrl}/onet/career-description/${socCode}`); if (!jobDetailsResponse.ok) throw new Error('Failed to fetch job description'); const { description, tasks } = await jobDetailsResponse.json(); // Step 3: Fetch Data in Parallel for other career details const [filteredSchools, economicResponse, tuitionResponse, salaryResponse] = await Promise.all([ fetchSchools(cleanedCipCode, userState), axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`), axios.get(`${apiUrl}/tuition`, { params: { cipCode: cleanedCipCode, state: userState }}), axios.get(`${apiUrl}/salary`, { params: { socCode: socCode.split('.')[0], area: areaTitle }}), ]); // Handle Distance Calculation const schoolsWithDistance = await Promise.all(filteredSchools.map(async (school) => { const schoolAddress = `${school.Address}, ${school.City}, ${school.State} ${school.ZIP}`; const response = await axios.post(`${apiUrl}/maps/distance`, { userZipcode, destinations: schoolAddress, }); const { distance, duration } = response.data; return { ...school, distance, duration }; })); // Process Salary Data const salaryDataPoints = [ { percentile: '10th Percentile', value: salaryResponse.data.A_PCT10 || 0 }, { percentile: '25th Percentile', value: salaryResponse.data.A_PCT25 || 0 }, { percentile: 'Median', value: salaryResponse.data.A_MEDIAN || 0 }, { percentile: '75th Percentile', value: salaryResponse.data.A_PCT75 || 0 }, { percentile: '90th Percentile', value: salaryResponse.data.A_PCT90 || 0 }, ]; // Consolidate Career Details with Job Description and Tasks setCareerDetails({ ...career, jobDescription: description, tasks: tasks, economicProjections: economicResponse.data, salaryData: salaryDataPoints, schools: schoolsWithDistance, tuitionData: tuitionResponse.data, }); } catch (error) { console.error('Error processing career click:', error.message); setError('Failed to load data'); } finally { setLoading(false); } }, [userState, apiUrl, areaTitle, userZipcode] ); const chartData = { labels: riaSecScores.map((score) => score.area), datasets: [ { label: 'RIASEC Scores', data: riaSecScores.map((score) => score.score), backgroundColor: 'rgba(75, 192, 192, 0.2)', borderColor: 'rgba(75, 192, 192, 1)', borderWidth: 1, }, ], }; return (
Loading descriptions...
)}Career results and RIASEC scores are provided by O*Net, in conjunction with the Bureau of Labor Statistics, and the National Center for Education Statistics (NCES).