202 lines
7.1 KiB
JavaScript
202 lines
7.1 KiB
JavaScript
// 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 { 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 [riaSecDescriptions, setRiaSecDescriptions] = useState([]);
|
|
|
|
// Dynamic API URL
|
|
const apiUrl = process.env.REACT_APP_API_URL || '/api';
|
|
|
|
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 } = profileData; // Use 'area' instead of 'AREA_TITLE'
|
|
setUserState(state);
|
|
setAreaTitle(area && area.trim() ? area.trim() : ''); // Ensure 'area' is set correctly
|
|
console.log('Profile Data Set:', { state, area });
|
|
} 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([]);
|
|
setSalaryData([]);
|
|
setEconomicProjections({});
|
|
setTuitionData([]);
|
|
|
|
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 Data in Parallel
|
|
const [filteredSchools, economicResponse, tuitionResponse, salaryResponse] = await Promise.all([
|
|
fetchSchools(cleanedCipCode, userState),
|
|
axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`),
|
|
axios.get(`${apiUrl}/tuition/${cleanedCipCode}`, {
|
|
params: { state: userState },
|
|
}),
|
|
axios.get(`${apiUrl}/salary`, {
|
|
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
|
}),
|
|
]);
|
|
|
|
// Step 3: Format 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 },
|
|
];
|
|
|
|
// Step 4: Consolidate Career Details
|
|
setCareerDetails({
|
|
...career,
|
|
economicProjections: economicResponse.data,
|
|
salaryData: salaryDataPoints,
|
|
schools: filteredSchools,
|
|
tuitionData: tuitionResponse.data,
|
|
});
|
|
} catch (error) {
|
|
console.error('Error processing career click:', error.message);
|
|
setError('Failed to load data');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
},
|
|
[userState, apiUrl, areaTitle]
|
|
);
|
|
|
|
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 (
|
|
<div className="dashboard">
|
|
<div className="career-suggestions-container">
|
|
<CareerSuggestions careerSuggestions={careerSuggestions} onCareerClick={handleCareerClick} />
|
|
</div>
|
|
|
|
{/* Right RIASEC Chart + Descriptions */}
|
|
<div className="riasec-container">
|
|
<div className="riasec-scores">
|
|
<h2>RIASEC Scores</h2>
|
|
<Bar data={chartData} />
|
|
</div>
|
|
|
|
<div className="riasec-descriptions">
|
|
<h3>RIASEC Personality Descriptions</h3>
|
|
{riaSecDescriptions.length > 0 ? (
|
|
<ul>
|
|
{riaSecDescriptions.map((desc, index) => (
|
|
<li key={index}>
|
|
<strong>{riaSecScores[index]?.area}:</strong> {desc}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p>Loading descriptions...</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{selectedCareer && (
|
|
<PopoutPanel
|
|
data={careerDetails}
|
|
schools={schools}
|
|
salaryData={salaryData}
|
|
economicProjections={economicProjections}
|
|
tuitionData={tuitionData}
|
|
closePanel={() => setSelectedCareer(null)}
|
|
loading={loading}
|
|
error={error}
|
|
userState={userState}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Dashboard;
|