Fixed navigation issue in Dashboard where it reverted to INterest Inventory
This commit is contained in:
parent
34fda5760d
commit
54d0fcb4e6
@ -3,13 +3,21 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import React, { useMemo, useState, useCallback, useEffect } from 'react';
|
import React, { useMemo, useState, useCallback, useEffect } from 'react';
|
||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from 'chart.js';
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
BarElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
} from 'chart.js';
|
||||||
|
|
||||||
import { CareerSuggestions } from './CareerSuggestions.js';
|
import { CareerSuggestions } from './CareerSuggestions.js';
|
||||||
import PopoutPanel from './PopoutPanel.js';
|
import PopoutPanel from './PopoutPanel.js';
|
||||||
import MilestoneTracker from './MilestoneTracker.js';
|
import MilestoneTracker from './MilestoneTracker.js';
|
||||||
import CareerSearch from './CareerSearch.js'; // <--- Import your new search
|
import CareerSearch from './CareerSearch.js'; // <--- Import your new search
|
||||||
import Chatbot from "./Chatbot.js";
|
import Chatbot from './Chatbot.js';
|
||||||
|
|
||||||
import './Dashboard.css';
|
import './Dashboard.css';
|
||||||
import { Bar } from 'react-chartjs-2';
|
import { Bar } from 'react-chartjs-2';
|
||||||
@ -51,12 +59,10 @@ function Dashboard() {
|
|||||||
const [sessionHandled, setSessionHandled] = useState(false);
|
const [sessionHandled, setSessionHandled] = useState(false);
|
||||||
|
|
||||||
// ============= NEW State =============
|
// ============= NEW State =============
|
||||||
// Holds the full career object { title, soc_code, cip_code } typed in CareerSearch
|
|
||||||
const [pendingCareerForModal, setPendingCareerForModal] = useState(null);
|
const [pendingCareerForModal, setPendingCareerForModal] = useState(null);
|
||||||
|
|
||||||
// We'll treat "loading" as "loadingSuggestions"
|
// We'll treat "loading" as "loadingSuggestions"
|
||||||
const loadingSuggestions = loading;
|
const loadingSuggestions = loading;
|
||||||
// We'll consider the popout panel visible if there's a selectedCareer
|
|
||||||
const popoutVisible = !!selectedCareer;
|
const popoutVisible = !!selectedCareer;
|
||||||
|
|
||||||
// ============= Auth & URL Setup =============
|
// ============= Auth & URL Setup =============
|
||||||
@ -64,9 +70,9 @@ function Dashboard() {
|
|||||||
|
|
||||||
// AUTH fetch
|
// AUTH fetch
|
||||||
const authFetch = async (url, options = {}, onUnauthorized) => {
|
const authFetch = async (url, options = {}, onUnauthorized) => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem('token');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.log("Token is missing, triggering session expired modal.");
|
console.log('Token is missing, triggering session expired modal.');
|
||||||
if (typeof onUnauthorized === 'function') onUnauthorized();
|
if (typeof onUnauthorized === 'function') onUnauthorized();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -79,21 +85,21 @@ function Dashboard() {
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, finalOptions);
|
const res = await fetch(url, finalOptions);
|
||||||
console.log("Response Status:", res.status);
|
console.log('Response Status:', res.status);
|
||||||
if (res.status === 401 || res.status === 403) {
|
if (res.status === 401 || res.status === 403) {
|
||||||
console.log("Session expired, triggering session expired modal.");
|
console.log('Session expired, triggering session expired modal.');
|
||||||
if (typeof onUnauthorized === 'function') onUnauthorized();
|
if (typeof onUnauthorized === 'function') onUnauthorized();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Fetch error:", err);
|
console.error('Fetch error:', err);
|
||||||
if (typeof onUnauthorized === 'function') onUnauthorized();
|
if (typeof onUnauthorized === 'function') onUnauthorized();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============= User Profile Fetch =============
|
// ============= Fetch user profile =============
|
||||||
const fetchUserProfile = async () => {
|
const fetchUserProfile = async () => {
|
||||||
const res = await authFetch(`${apiUrl}/user-profile`);
|
const res = await authFetch(`${apiUrl}/user-profile`);
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
@ -103,32 +109,93 @@ function Dashboard() {
|
|||||||
setUserState(profileData.state);
|
setUserState(profileData.state);
|
||||||
setAreaTitle(profileData.area.trim() || '');
|
setAreaTitle(profileData.area.trim() || '');
|
||||||
setUserZipcode(profileData.zipcode);
|
setUserZipcode(profileData.zipcode);
|
||||||
|
// Store entire userProfile if needed
|
||||||
|
setUserProfile(profileData);
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to fetch user profile');
|
console.error('Failed to fetch user profile');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============= Lifecycle: Load Profile, Setup =============
|
// We'll store the userProfile for reference
|
||||||
|
const [userProfile, setUserProfile] = useState(null);
|
||||||
|
|
||||||
|
// ============= Lifecycle: Load Profile =============
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUserProfile();
|
fetchUserProfile();
|
||||||
}, [apiUrl]); // load once
|
}, [apiUrl]); // load once
|
||||||
|
|
||||||
// ============= jobZone & fit Setup =============
|
// ============= jobZone & Fit Setup =============
|
||||||
const jobZoneLabels = {
|
const jobZoneLabels = {
|
||||||
'1': 'Little or No Preparation',
|
'1': 'Little or No Preparation',
|
||||||
'2': 'Some Preparation Needed',
|
'2': 'Some Preparation Needed',
|
||||||
'3': 'Medium Preparation Needed',
|
'3': 'Medium Preparation Needed',
|
||||||
'4': 'Considerable Preparation Needed',
|
'4': 'Considerable Preparation Needed',
|
||||||
'5': 'Extensive Preparation Needed'
|
'5': 'Extensive Preparation Needed',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fitLabels = {
|
const fitLabels = {
|
||||||
'Best': 'Best - Very Strong Match',
|
Best: 'Best - Very Strong Match',
|
||||||
'Great': 'Great - Strong Match',
|
Great: 'Great - Strong Match',
|
||||||
'Good': 'Good - Less Strong Match'
|
Good: 'Good - Less Strong Match',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch job zones for each career suggestion
|
// ============= "Mimic" InterestInventory submission if user has 60 answers =============
|
||||||
|
const mimicInterestInventorySubmission = async (answers) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setProgress(0);
|
||||||
|
const response = await authFetch(`${apiUrl}/onet/submit_answers`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ answers }),
|
||||||
|
});
|
||||||
|
if (!response || !response.ok) {
|
||||||
|
throw new Error('Failed to submit stored answers');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const { careers, riaSecScores } = data;
|
||||||
|
|
||||||
|
// This sets location.state, so the next effect sees it as if we came from InterestInventory
|
||||||
|
navigate('/dashboard', {
|
||||||
|
state: { careerSuggestions: careers, riaSecScores },
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error mimicking submission:', err);
|
||||||
|
alert('We could not load your saved answers. Please retake the Interest Inventory.');
|
||||||
|
navigate('/interest-inventory');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============= On Page Load: get careerSuggestions from location.state, or mimic =============
|
||||||
|
useEffect(() => {
|
||||||
|
// If we have location.state from InterestInventory, proceed as normal
|
||||||
|
if (location.state) {
|
||||||
|
let descriptions = [];
|
||||||
|
const { careerSuggestions: suggestions, riaSecScores: scores } = location.state || {};
|
||||||
|
descriptions = (scores || []).map((score) => score.description || 'No description available.');
|
||||||
|
setCareerSuggestions(suggestions || []);
|
||||||
|
setRiaSecScores(scores || []);
|
||||||
|
setRiaSecDescriptions(descriptions);
|
||||||
|
} else {
|
||||||
|
// We came here directly; wait for userProfile, then check answers
|
||||||
|
if (!userProfile) return; // wait until userProfile is loaded
|
||||||
|
|
||||||
|
const storedAnswers = userProfile.interest_inventory_answers;
|
||||||
|
if (storedAnswers && storedAnswers.length === 60) {
|
||||||
|
// Mimic the submission so we get suggestions
|
||||||
|
mimicInterestInventorySubmission(storedAnswers);
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
'We need your Interest Inventory answers to generate career suggestions. Redirecting...'
|
||||||
|
);
|
||||||
|
navigate('/interest-inventory');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [location.state, navigate, userProfile]);
|
||||||
|
|
||||||
|
// ============= jobZone fetch =============
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchJobZones = async () => {
|
const fetchJobZones = async () => {
|
||||||
if (careerSuggestions.length === 0) return;
|
if (careerSuggestions.length === 0) return;
|
||||||
@ -148,7 +215,7 @@ function Dashboard() {
|
|||||||
fetchJobZones();
|
fetchJobZones();
|
||||||
}, [careerSuggestions, apiUrl]);
|
}, [careerSuggestions, apiUrl]);
|
||||||
|
|
||||||
// Filter careers by job zone, fit
|
// ============= Filter by job zone, fit =============
|
||||||
const filteredCareers = useMemo(() => {
|
const filteredCareers = useMemo(() => {
|
||||||
return careersWithJobZone.filter((career) => {
|
return careersWithJobZone.filter((career) => {
|
||||||
const jobZoneMatches = selectedJobZone
|
const jobZoneMatches = selectedJobZone
|
||||||
@ -163,7 +230,7 @@ function Dashboard() {
|
|||||||
});
|
});
|
||||||
}, [careersWithJobZone, selectedJobZone, selectedFit]);
|
}, [careersWithJobZone, selectedJobZone, selectedFit]);
|
||||||
|
|
||||||
// Merge updated data into chatbot context
|
// ============= Merge data into chatbot context =============
|
||||||
const updateChatbotContext = (updatedData) => {
|
const updateChatbotContext = (updatedData) => {
|
||||||
setChatbotContext((prevContext) => {
|
setChatbotContext((prevContext) => {
|
||||||
const mergedContext = {
|
const mergedContext = {
|
||||||
@ -179,56 +246,6 @@ function Dashboard() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Our final array for CareerSuggestions
|
|
||||||
const memoizedCareerSuggestions = useMemo(() => filteredCareers, [filteredCareers]);
|
|
||||||
|
|
||||||
// ============= Popout Panel Setup =============
|
|
||||||
const memoizedPopoutPanel = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<PopoutPanel
|
|
||||||
isVisible={!!selectedCareer}
|
|
||||||
data={careerDetails}
|
|
||||||
schools={schools}
|
|
||||||
salaryData={salaryData}
|
|
||||||
economicProjections={economicProjections}
|
|
||||||
tuitionData={tuitionData}
|
|
||||||
closePanel={() => setSelectedCareer(null)}
|
|
||||||
loading={loading}
|
|
||||||
error={error}
|
|
||||||
userState={userState}
|
|
||||||
results={results}
|
|
||||||
updateChatbotContext={updateChatbotContext}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
selectedCareer,
|
|
||||||
careerDetails,
|
|
||||||
schools,
|
|
||||||
salaryData,
|
|
||||||
economicProjections,
|
|
||||||
tuitionData,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
userState,
|
|
||||||
results
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ============= On Page Load: get careerSuggestions from location.state, etc. =============
|
|
||||||
useEffect(() => {
|
|
||||||
let descriptions = [];
|
|
||||||
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);
|
|
||||||
} else {
|
|
||||||
console.warn('No data found, redirecting to Interest Inventory');
|
|
||||||
navigate('/interest-inventory');
|
|
||||||
}
|
|
||||||
}, [location.state, navigate]);
|
|
||||||
|
|
||||||
// Once userState, areaTitle, userZipcode, etc. are set, update chatbot
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
careerSuggestions.length > 0 &&
|
careerSuggestions.length > 0 &&
|
||||||
@ -240,15 +257,15 @@ function Dashboard() {
|
|||||||
const newChatbotContext = {
|
const newChatbotContext = {
|
||||||
careerSuggestions: [...careersWithJobZone],
|
careerSuggestions: [...careersWithJobZone],
|
||||||
riaSecScores: [...riaSecScores],
|
riaSecScores: [...riaSecScores],
|
||||||
userState: userState || "",
|
userState: userState || '',
|
||||||
areaTitle: areaTitle || "",
|
areaTitle: areaTitle || '',
|
||||||
userZipcode: userZipcode || "",
|
userZipcode: userZipcode || '',
|
||||||
};
|
};
|
||||||
setChatbotContext(newChatbotContext);
|
setChatbotContext(newChatbotContext);
|
||||||
}
|
}
|
||||||
}, [careerSuggestions, riaSecScores, userState, areaTitle, userZipcode, careersWithJobZone]);
|
}, [careerSuggestions, riaSecScores, userState, areaTitle, userZipcode, careersWithJobZone]);
|
||||||
|
|
||||||
// ============= handleCareerClick (for tile clicks) =============
|
// ============= handleCareerClick =============
|
||||||
const handleCareerClick = useCallback(
|
const handleCareerClick = useCallback(
|
||||||
async (career) => {
|
async (career) => {
|
||||||
console.log('[handleCareerClick] career =>', career);
|
console.log('[handleCareerClick] career =>', career);
|
||||||
@ -284,7 +301,9 @@ function Dashboard() {
|
|||||||
// Salary
|
// Salary
|
||||||
let salaryResponse;
|
let salaryResponse;
|
||||||
try {
|
try {
|
||||||
salaryResponse = await axios.get(`${apiUrl}/salary`, { params: { socCode: socCode.split('.')[0], area: areaTitle } });
|
salaryResponse = await axios.get(`${apiUrl}/salary`, {
|
||||||
|
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
salaryResponse = { data: {} };
|
salaryResponse = { data: {} };
|
||||||
}
|
}
|
||||||
@ -300,73 +319,77 @@ function Dashboard() {
|
|||||||
// Tuition
|
// Tuition
|
||||||
let tuitionResponse;
|
let tuitionResponse;
|
||||||
try {
|
try {
|
||||||
tuitionResponse = await axios.get(`${apiUrl}/tuition`, { params: { cipCode: cleanedCipCode, state: userState } });
|
tuitionResponse = await axios.get(`${apiUrl}/tuition`, {
|
||||||
|
params: { cipCode: cleanedCipCode, state: userState },
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
tuitionResponse = { data: {} };
|
tuitionResponse = { data: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch schools
|
// Fetch schools
|
||||||
const filteredSchools = await fetchSchools(cleanedCipCode, userState);
|
const filteredSchools = await fetchSchools(cleanedCipCode, userState);
|
||||||
const schoolsWithDistance = await Promise.all(filteredSchools.map(async (school) => {
|
const schoolsWithDistance = await Promise.all(
|
||||||
const schoolAddress = `${school.Address}, ${school.City}, ${school.State} ${school.ZIP}`;
|
filteredSchools.map(async (school) => {
|
||||||
try {
|
const schoolAddress = `${school.Address}, ${school.City}, ${school.State} ${school.ZIP}`;
|
||||||
const response = await axios.post(`${apiUrl}/maps/distance`, {
|
try {
|
||||||
userZipcode,
|
const response = await axios.post(`${apiUrl}/maps/distance`, {
|
||||||
destinations: schoolAddress,
|
userZipcode,
|
||||||
});
|
destinations: schoolAddress,
|
||||||
const { distance, duration } = response.data;
|
});
|
||||||
return { ...school, distance, duration };
|
const { distance, duration } = response.data;
|
||||||
} catch (error) {
|
return { ...school, distance, duration };
|
||||||
return { ...school, distance: 'N/A', duration: 'N/A' };
|
} catch (error) {
|
||||||
}
|
return { ...school, distance: 'N/A', duration: 'N/A' };
|
||||||
}));
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Build salary array
|
// Build salary array
|
||||||
const sData = salaryResponse.data || {};
|
const sData = salaryResponse.data || {};
|
||||||
const salaryDataPoints = sData && Object.keys(sData).length > 0
|
const salaryDataPoints =
|
||||||
? [
|
sData && Object.keys(sData).length > 0
|
||||||
{
|
? [
|
||||||
percentile: "10th Percentile",
|
{
|
||||||
regionalSalary: parseInt(sData.regional?.regional_PCT10, 10) || 0,
|
percentile: '10th Percentile',
|
||||||
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,
|
percentile: '25th Percentile',
|
||||||
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,
|
percentile: 'Median',
|
||||||
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,
|
percentile: '75th Percentile',
|
||||||
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,
|
percentile: '90th Percentile',
|
||||||
nationalSalary: parseInt(sData.national?.national_PCT90, 10) || 0
|
regionalSalary: parseInt(sData.regional?.regional_PCT90, 10) || 0,
|
||||||
},
|
nationalSalary: parseInt(sData.national?.national_PCT90, 10) || 0,
|
||||||
]
|
},
|
||||||
: [];
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
// Build final details
|
// Build final details
|
||||||
const updatedCareerDetails = {
|
const updatedCareerDetails = {
|
||||||
...career,
|
...career,
|
||||||
jobDescription: description,
|
jobDescription: description,
|
||||||
tasks: tasks,
|
tasks: tasks,
|
||||||
economicProjections: (economicResponse.data || {}),
|
economicProjections: economicResponse.data || {},
|
||||||
salaryData: salaryDataPoints,
|
salaryData: salaryDataPoints,
|
||||||
schools: schoolsWithDistance,
|
schools: schoolsWithDistance,
|
||||||
tuitionData: (tuitionResponse.data || []),
|
tuitionData: tuitionResponse.data || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
setCareerDetails(updatedCareerDetails);
|
setCareerDetails(updatedCareerDetails);
|
||||||
updateChatbotContext({ careerDetails: updatedCareerDetails });
|
updateChatbotContext({ careerDetails: updatedCareerDetails });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing career click:', error.message);
|
console.error('Error processing career click:', error.message);
|
||||||
setError('Failed to load data');
|
setError('Failed to load data');
|
||||||
@ -377,20 +400,20 @@ function Dashboard() {
|
|||||||
[userState, apiUrl, areaTitle, userZipcode, updateChatbotContext]
|
[userState, apiUrl, areaTitle, userZipcode, updateChatbotContext]
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============= Letting typed careers open PopoutPanel =============
|
// ============= Let typed careers open PopoutPanel =============
|
||||||
// Called if the user picks a career in "CareerSearch" => { title, soc_code, cip_code }
|
const handleCareerFromSearch = useCallback(
|
||||||
const handleCareerFromSearch = useCallback((obj) => {
|
(obj) => {
|
||||||
// Convert to shape used by handleCareerClick => { code, title, cipCode }
|
const adapted = {
|
||||||
const adapted = {
|
code: obj.soc_code,
|
||||||
code: obj.soc_code,
|
title: obj.title,
|
||||||
title: obj.title,
|
cipCode: obj.cip_code,
|
||||||
cipCode: obj.cip_code
|
};
|
||||||
};
|
console.log('[Dashboard -> handleCareerFromSearch] adapted =>', adapted);
|
||||||
console.log('[Dashboard -> handleCareerFromSearch] adapted =>', adapted);
|
handleCareerClick(adapted);
|
||||||
handleCareerClick(adapted);
|
},
|
||||||
}, [handleCareerClick]);
|
[handleCareerClick]
|
||||||
|
);
|
||||||
|
|
||||||
// If the user typed a career and clicked confirm
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pendingCareerForModal) {
|
if (pendingCareerForModal) {
|
||||||
console.log('[useEffect] pendingCareerForModal =>', pendingCareerForModal);
|
console.log('[useEffect] pendingCareerForModal =>', pendingCareerForModal);
|
||||||
@ -433,6 +456,38 @@ function Dashboard() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============= Popout Panel Setup =============
|
||||||
|
const memoizedPopoutPanel = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<PopoutPanel
|
||||||
|
isVisible={!!selectedCareer}
|
||||||
|
data={careerDetails}
|
||||||
|
schools={schools}
|
||||||
|
salaryData={salaryData}
|
||||||
|
economicProjections={economicProjections}
|
||||||
|
tuitionData={tuitionData}
|
||||||
|
closePanel={() => setSelectedCareer(null)}
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
userState={userState}
|
||||||
|
results={results}
|
||||||
|
updateChatbotContext={updateChatbotContext}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
selectedCareer,
|
||||||
|
careerDetails,
|
||||||
|
schools,
|
||||||
|
salaryData,
|
||||||
|
economicProjections,
|
||||||
|
tuitionData,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
userState,
|
||||||
|
results,
|
||||||
|
updateChatbotContext,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
{showSessionExpiredModal && (
|
{showSessionExpiredModal && (
|
||||||
@ -441,16 +496,19 @@ function Dashboard() {
|
|||||||
<h3>Session Expired</h3>
|
<h3>Session Expired</h3>
|
||||||
<p>Your session has expired or is invalid.</p>
|
<p>Your session has expired or is invalid.</p>
|
||||||
<div className="modal-actions">
|
<div className="modal-actions">
|
||||||
<button className="confirm-btn" onClick={() => setShowSessionExpiredModal(false)}>
|
<button
|
||||||
|
className="confirm-btn"
|
||||||
|
onClick={() => setShowSessionExpiredModal(false)}
|
||||||
|
>
|
||||||
Stay Signed In
|
Stay Signed In
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="confirm-btn"
|
className="confirm-btn"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem('token');
|
||||||
localStorage.removeItem("UserId");
|
localStorage.removeItem('UserId');
|
||||||
setShowSessionExpiredModal(false);
|
setShowSessionExpiredModal(false);
|
||||||
navigate("/signin");
|
navigate('/signin');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sign In Again
|
Sign In Again
|
||||||
@ -464,7 +522,6 @@ function Dashboard() {
|
|||||||
|
|
||||||
<div className="dashboard-content">
|
<div className="dashboard-content">
|
||||||
{/* ====== 1) The new CareerSearch bar ====== */}
|
{/* ====== 1) The new CareerSearch bar ====== */}
|
||||||
|
|
||||||
|
|
||||||
{/* Existing filters + suggestions */}
|
{/* Existing filters + suggestions */}
|
||||||
<div className="career-suggestions-container">
|
<div className="career-suggestions-container">
|
||||||
@ -475,7 +532,7 @@ function Dashboard() {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginBottom: '15px',
|
marginBottom: '15px',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: '15px'
|
gap: '15px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<label>
|
<label>
|
||||||
@ -487,7 +544,9 @@ function Dashboard() {
|
|||||||
>
|
>
|
||||||
<option value="">All Preparation Levels</option>
|
<option value="">All Preparation Levels</option>
|
||||||
{Object.entries(jobZoneLabels).map(([zone, label]) => (
|
{Object.entries(jobZoneLabels).map(([zone, label]) => (
|
||||||
<option key={zone} value={zone}>{label}</option>
|
<option key={zone} value={zone}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
@ -501,7 +560,9 @@ function Dashboard() {
|
|||||||
>
|
>
|
||||||
<option value="">All Fit Levels</option>
|
<option value="">All Fit Levels</option>
|
||||||
{Object.entries(fitLabels).map(([key, label]) => (
|
{Object.entries(fitLabels).map(([key, label]) => (
|
||||||
<option key={key} value={key}>{label}</option>
|
<option key={key} value={key}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
@ -509,14 +570,15 @@ function Dashboard() {
|
|||||||
<CareerSearch
|
<CareerSearch
|
||||||
onCareerSelected={(careerObj) => {
|
onCareerSelected={(careerObj) => {
|
||||||
console.log('[Dashboard] onCareerSelected =>', careerObj);
|
console.log('[Dashboard] onCareerSelected =>', careerObj);
|
||||||
// Set the "pendingCareerForModal" so our useEffect fires below
|
// Set the "pendingCareerForModal" so our useEffect fires
|
||||||
setPendingCareerForModal(careerObj);
|
setPendingCareerForModal(careerObj);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CareerSuggestions
|
<CareerSuggestions
|
||||||
careerSuggestions={memoizedCareerSuggestions}
|
careerSuggestions={filteredCareers}
|
||||||
onCareerClick={handleCareerClick}
|
onCareerClick={handleCareerClick}
|
||||||
setLoading={setLoading}
|
setLoading={setLoading}
|
||||||
setProgress={setProgress}
|
setProgress={setProgress}
|
||||||
@ -568,14 +630,38 @@ function Dashboard() {
|
|||||||
borderTop: '1px solid #ccc',
|
borderTop: '1px solid #ccc',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: '#666',
|
color: '#666',
|
||||||
textAlign: 'center'
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Career results and RIASEC scores are provided by
|
Career results and RIASEC scores are provided by
|
||||||
<a href="https://www.onetcenter.org" target="_blank" rel="noopener noreferrer"> O*Net</a>, in conjunction with the
|
<a
|
||||||
<a href="https://www.bls.gov" target="_blank" rel="noopener noreferrer"> Bureau of Labor Statistics</a>, and the
|
href="https://www.onetcenter.org"
|
||||||
<a href="https://nces.ed.gov" target="_blank" rel="noopener noreferrer"> National Center for Education Statistics (NCES)</a>.
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
O*Net
|
||||||
|
</a>
|
||||||
|
, in conjunction with the
|
||||||
|
<a
|
||||||
|
href="https://www.bls.gov"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
Bureau of Labor Statistics
|
||||||
|
</a>
|
||||||
|
, and the
|
||||||
|
<a
|
||||||
|
href="https://nces.ed.gov"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
National Center for Education Statistics (NCES)
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user