Optimized re-rendering of CareerSuggestions and Popoutpanel
This commit is contained in:
parent
b0640e07db
commit
0bee813b97
@ -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}`,
|
||||
|
@ -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 */
|
||||
|
@ -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">
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user