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) => {
try {
console.log(`Checking data for: ${career.title} (${career.code})`);
const headers = {
Authorization: `Bearer ${token}`,

View File

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

View File

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

View File

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

View File

@ -4,25 +4,27 @@ import './PopoutPanel.css';
import { useState, useEffect } from 'react';
function PopoutPanel({
isVisible,
data = {},
userState = 'N/A', // Passed explicitly from Dashboard
loading = false,
error = null,
closePanel,
}) {
console.log('PopoutPanel Props:', { data, loading, error, userState });
const [isCalculated, setIsCalculated] = useState(false);
const [results, setResults] = useState([]); // Store loan repayment calculation results
const [loadingCalculation, setLoadingCalculation] = useState(false);
// ✅ Reset `results` when a new career is selected
// ✅ Reset `results` when a new career is selected
useEffect(() => {
console.log(`Career changed to: ${data?.title}, clearing previous loan results.`);
setResults([]); // ✅ Clears results when a new career is selected
setIsCalculated(false); // ✅ Reset calculation state
}, [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
if (loading) {
return (
@ -54,6 +56,13 @@ useEffect(() => {
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 (
<div className="popout-panel">
<button onClick={closePanel}>Close</button>