Optimized re-rendering of CareerSuggestions and Popoutpanel

This commit is contained in:
Josh 2025-03-05 14:08:02 +00:00
parent c351324964
commit 3934e59369
5 changed files with 135 additions and 71 deletions

View File

@ -30,7 +30,7 @@ export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle
const careerPromises = careerSuggestions.map(async (career) => { const careerPromises = careerSuggestions.map(async (career) => {
try { try {
console.log(`Checking data for: ${career.title} (${career.code})`);
const headers = { const headers = {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,

View File

@ -154,31 +154,46 @@ h2 {
.filter-container { .filter-container {
width: 100%; width: 100%;
max-width: 900px; /* Ensures alignment */ max-width: 900px; /* Ensures alignment */
min-width: 200px; /* Ensures dropdowns have enough space */
gap: 20px; /* Add spacing between filters */
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 14px;
flex-wrap: nowrap; /* Ensures responsiveness */
justify-content: flex-start; justify-content: flex-start;
background: #ffffff; background: #ffffff;
padding: 10px 15px; padding: 6px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 10px; margin-bottom: 10px;
} }
.filter-group {
display: flex;
align-items: center;
gap: 10px; /* Space between label and dropdown */
}
/* Style Dropdown */ /* Style Dropdown */
.filter-container label { .filter-container label {
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: bold;
white-space: nowrap; /* Prevents labels from wrapping */
color: #333; color: #333;
margin-right: 10px; margin-right: 10px;
} }
.filter-container select { .filter-container select {
padding: 8px; padding: 8px;
font-weight: bold;
font-size: 14px; font-size: 14px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
background-color: #fff; background-color: #fff;
margin-right: 8px; /* Space between label and dropdown */
white-space: nowrap; /* Prevents label from wrapping */
cursor: pointer; cursor: pointer;
min-width: 220px; /* Ensures dropdowns have enough width */
} }
/* RIASEC Scores */ /* RIASEC Scores */

View File

@ -1,6 +1,6 @@
// Dashboard.js // Dashboard.js
import axios from 'axios'; import axios from 'axios';
import React, { 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';
@ -11,6 +11,7 @@ import { Bar } from 'react-chartjs-2';
import { fetchSchools } from '../utils/apiUtils.js'; import { fetchSchools } from '../utils/apiUtils.js';
import './Dashboard.css'; import './Dashboard.css';
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
function Dashboard() { function Dashboard() {
@ -33,8 +34,7 @@ function Dashboard() {
const [riaSecDescriptions, setRiaSecDescriptions] = useState([]); const [riaSecDescriptions, setRiaSecDescriptions] = useState([]);
const [selectedJobZone, setSelectedJobZone] = useState(''); const [selectedJobZone, setSelectedJobZone] = useState('');
const [careersWithJobZone, setCareersWithJobZone] = useState([]); // Store careers with job zone info const [careersWithJobZone, setCareersWithJobZone] = useState([]); // Store careers with job zone info
const [selectedFit, setSelectedFit] = useState('');
const jobZoneLabels = { const jobZoneLabels = {
'1': 'Little or No Preparation', '1': 'Little or No Preparation',
@ -44,6 +44,12 @@ function Dashboard() {
'5': 'Extensive Preparation Needed' '5': 'Extensive Preparation Needed'
}; };
const fitLabels = {
'Best': 'Best - Very Strong Match',
'Great': 'Great - Strong Match',
'Good': 'Good - Less Strong Match'
};
// Dynamic API URL // Dynamic API URL
const apiUrl = process.env.REACT_APP_API_URL || ''; const apiUrl = process.env.REACT_APP_API_URL || '';
@ -71,19 +77,39 @@ function Dashboard() {
fetchJobZones(); fetchJobZones();
}, [careerSuggestions, apiUrl]); }, [careerSuggestions, apiUrl]);
const filteredCareers = selectedJobZone const filteredCareers = useMemo(() => {
? careersWithJobZone.filter(career => { return careersWithJobZone.filter((career) => {
const jobZoneMatches = selectedJobZone
return ( ? career.job_zone !== null &&
career.job_zone !== null &&
career.job_zone !== undefined && career.job_zone !== undefined &&
typeof career.job_zone === 'number' && typeof career.job_zone === 'number' &&
Number(career.job_zone) === Number(selectedJobZone) Number(career.job_zone) === Number(selectedJobZone)
: true;
const fitMatches = selectedFit ? career.fit === selectedFit : true;
return jobZoneMatches && fitMatches;
});
}, [careersWithJobZone, selectedJobZone, selectedFit]);
const memoizedCareerSuggestions = useMemo(() => filteredCareers, [filteredCareers]);
const memoizedPopoutPanel = useMemo(() => {
return (
<PopoutPanel
isVisible={!!selectedCareer} // ✅ Ensures it's only visible when needed
data={careerDetails}
schools={schools}
salaryData={salaryData}
economicProjections={economicProjections}
tuitionData={tuitionData}
closePanel={() => setSelectedCareer(null)}
loading={loading}
error={error}
userState={userState}
/>
); );
}) }, [selectedCareer, careerDetails, schools, salaryData, economicProjections, tuitionData, loading, error, userState]);
: careersWithJobZone;
useEffect(() => { useEffect(() => {
let descriptions = []; // Declare outside for scope accessibility let descriptions = []; // Declare outside for scope accessibility
@ -222,6 +248,7 @@ function Dashboard() {
return ( return (
<div className="dashboard"> <div className="dashboard">
<div className="filter-container"> <div className="filter-container">
<div className="filter-group">
<label htmlFor="preparation-filter">Filter by Preparation Level:</label> <label htmlFor="preparation-filter">Filter by Preparation Level:</label>
<select <select
id="preparation-filter" id="preparation-filter"
@ -235,9 +262,25 @@ function Dashboard() {
</select> </select>
</div> </div>
<div className="filter-group">
<label htmlFor="fit-filter">Filter by Fit:</label>
<select
id="fit-filter"
value={selectedFit}
onChange={(e) => setSelectedFit(e.target.value)}
>
<option value="">All Fit Levels</option>
{Object.entries(fitLabels).map(([key, label]) => (
<option key={key} value={key}>{label}</option>
))}
</select>
</div>
</div>
<div className="dashboard-content"> <div className="dashboard-content">
<div className="career-suggestions-container"> <div className="career-suggestions-container">
<CareerSuggestions careerSuggestions={filteredCareers} onCareerClick={handleCareerClick} /> <CareerSuggestions careerSuggestions={memoizedCareerSuggestions} onCareerClick={handleCareerClick} />
</div> </div>
<div className="riasec-container"> <div className="riasec-container">
@ -262,20 +305,8 @@ function Dashboard() {
</div> </div>
</div> </div>
{memoizedPopoutPanel}
{selectedCareer && (
<PopoutPanel
data={careerDetails}
schools={schools}
salaryData={salaryData}
economicProjections={economicProjections}
tuitionData={tuitionData}
closePanel={() => setSelectedCareer(null)}
loading={loading}
error={error}
userState={userState}
/>
)}
{/* Pass context to Chatbot */} {/* Pass context to Chatbot */}
<div className="chatbot-widget"> <div className="chatbot-widget">

View File

@ -13,6 +13,15 @@
z-index: 1000; /* Ensures it is above other elements */ z-index: 1000; /* Ensures it is above other elements */
} }
.popout-panel.hidden {
display: none;
}
.popout-panel.visible {
display: block;
}
/* Mobile responsiveness */ /* Mobile responsiveness */
@media (max-width: 768px) { @media (max-width: 768px) {
.popout-panel { .popout-panel {

View File

@ -4,25 +4,27 @@ import './PopoutPanel.css';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
function PopoutPanel({ function PopoutPanel({
isVisible,
data = {}, data = {},
userState = 'N/A', // Passed explicitly from Dashboard userState = 'N/A', // Passed explicitly from Dashboard
loading = false, loading = false,
error = null, error = null,
closePanel, closePanel,
}) { }) {
console.log('PopoutPanel Props:', { data, loading, error, userState });
const [isCalculated, setIsCalculated] = useState(false); const [isCalculated, setIsCalculated] = useState(false);
const [results, setResults] = useState([]); // Store loan repayment calculation results const [results, setResults] = useState([]); // Store loan repayment calculation results
const [loadingCalculation, setLoadingCalculation] = useState(false); const [loadingCalculation, setLoadingCalculation] = useState(false);
// ✅ Reset `results` when a new career is selected // ✅ Reset `results` when a new career is selected
useEffect(() => { useEffect(() => {
console.log(`Career changed to: ${data?.title}, clearing previous loan results.`); console.log(`Career changed to: ${data?.title}, clearing previous loan results.`);
setResults([]); // ✅ Clears results when a new career is selected setResults([]); // ✅ Clears results when a new career is selected
setIsCalculated(false); // ✅ Reset calculation state setIsCalculated(false); // ✅ Reset calculation state
}, [data]); // Runs whenever `data` changes (i.e., new career is selected) }, [data]); // Runs whenever `data` changes (i.e., new career is selected)
if (!isVisible) return null; // ✅ Prevents rendering instead of hiding the whole dashboard
// Handle loading state // Handle loading state
if (loading) { if (loading) {
return ( return (
@ -54,6 +56,13 @@ useEffect(() => {
return 4; // Default to 4 years if unspecified return 4; // Default to 4 years if unspecified
}; };
function handleClosePanel() {
setResults([]); // Clear only LoanRepayment results
setIsCalculated(false); // Reset calculation state
closePanel(); // Maintain existing close behavior
}
return ( return (
<div className="popout-panel"> <div className="popout-panel">
<button onClick={closePanel}>Close</button> <button onClick={closePanel}>Close</button>