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) => {
|
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}`,
|
||||||
|
@ -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 */
|
||||||
|
@ -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
|
||||||
|
? career.job_zone !== null &&
|
||||||
|
career.job_zone !== undefined &&
|
||||||
|
typeof career.job_zone === 'number' &&
|
||||||
|
Number(career.job_zone) === Number(selectedJobZone)
|
||||||
|
: true;
|
||||||
|
|
||||||
return (
|
const fitMatches = selectedFit ? career.fit === selectedFit : true;
|
||||||
career.job_zone !== null &&
|
|
||||||
career.job_zone !== undefined &&
|
|
||||||
typeof career.job_zone === 'number' &&
|
|
||||||
Number(career.job_zone) === Number(selectedJobZone)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: careersWithJobZone;
|
|
||||||
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
let descriptions = []; // Declare outside for scope accessibility
|
let descriptions = []; // Declare outside for scope accessibility
|
||||||
@ -221,61 +247,66 @@ function Dashboard() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
<div className="filter-container">
|
<div className="filter-container">
|
||||||
<label htmlFor="preparation-filter">Filter by Preparation Level:</label>
|
<div className="filter-group">
|
||||||
<select
|
<label htmlFor="preparation-filter">Filter by Preparation Level:</label>
|
||||||
id="preparation-filter"
|
<select
|
||||||
value={selectedJobZone}
|
id="preparation-filter"
|
||||||
onChange={(e) => setSelectedJobZone(Number(e.target.value))}
|
value={selectedJobZone}
|
||||||
>
|
onChange={(e) => setSelectedJobZone(Number(e.target.value))}
|
||||||
<option value="">All Preparation Levels</option>
|
>
|
||||||
{Object.entries(jobZoneLabels).map(([zone, label]) => (
|
<option value="">All Preparation Levels</option>
|
||||||
<option key={zone} value={zone}>{label}</option>
|
{Object.entries(jobZoneLabels).map(([zone, label]) => (
|
||||||
))}
|
<option key={zone} value={zone}>{label}</option>
|
||||||
</select>
|
))}
|
||||||
</div>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="dashboard-content">
|
<div className="filter-group">
|
||||||
<div className="career-suggestions-container">
|
<label htmlFor="fit-filter">Filter by Fit:</label>
|
||||||
<CareerSuggestions careerSuggestions={filteredCareers} onCareerClick={handleCareerClick} />
|
<select
|
||||||
</div>
|
id="fit-filter"
|
||||||
|
value={selectedFit}
|
||||||
<div className="riasec-container">
|
onChange={(e) => setSelectedFit(e.target.value)}
|
||||||
<div className="riasec-scores">
|
>
|
||||||
<h2>RIASEC Scores</h2>
|
<option value="">All Fit Levels</option>
|
||||||
<Bar data={chartData} />
|
{Object.entries(fitLabels).map(([key, label]) => (
|
||||||
</div>
|
<option key={key} value={key}>{label}</option>
|
||||||
<div className="riasec-descriptions">
|
))}
|
||||||
<h3>RIASEC Personality Descriptions</h3>
|
</select>
|
||||||
{riaSecDescriptions.length > 0 ? (
|
</div>
|
||||||
<ul>
|
</div>
|
||||||
{riaSecDescriptions.map((desc, index) => (
|
|
||||||
<li key={index}>
|
|
||||||
<strong>{riaSecScores[index]?.area}:</strong> {desc}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
) : (
|
|
||||||
<p>Loading descriptions...</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{selectedCareer && (
|
<div className="dashboard-content">
|
||||||
<PopoutPanel
|
<div className="career-suggestions-container">
|
||||||
data={careerDetails}
|
<CareerSuggestions careerSuggestions={memoizedCareerSuggestions} onCareerClick={handleCareerClick} />
|
||||||
schools={schools}
|
</div>
|
||||||
salaryData={salaryData}
|
|
||||||
economicProjections={economicProjections}
|
<div className="riasec-container">
|
||||||
tuitionData={tuitionData}
|
<div className="riasec-scores">
|
||||||
closePanel={() => setSelectedCareer(null)}
|
<h2>RIASEC Scores</h2>
|
||||||
loading={loading}
|
<Bar data={chartData} />
|
||||||
error={error}
|
</div>
|
||||||
userState={userState}
|
<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 */}
|
{/* Pass context to Chatbot */}
|
||||||
<div className="chatbot-widget">
|
<div className="chatbot-widget">
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user