Limited Data flag issues fixed-ish. Loading bar to stretch most of the width of container.

This commit is contained in:
Josh 2025-03-10 17:40:12 +00:00
parent a1a8f9c7dc
commit ca7b230b25
5 changed files with 176 additions and 145 deletions

View File

@ -30,8 +30,6 @@ export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle
const careerPromises = careerSuggestions.map(async (career) => { const careerPromises = careerSuggestions.map(async (career) => {
try { try {
const headers = { const headers = {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
Accept: 'application/json', Accept: 'application/json',
@ -49,16 +47,10 @@ export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle
} }
}; };
// Step 1: Fetch CIP Code // Fetch Data in Parallel
const cipData = await fetchJSON(`${apiUrl}/cip/${career.code}`); const [cipData, jobDetailsData, economicData, salaryResponse] = await Promise.all([
const isCipMissing = !cipData || Object.keys(cipData).length === 0; fetchJSON(`${apiUrl}/cip/${career.code}`),
fetchJSON(`${apiUrl}/onet/career-description/${career.code}`),
// Step 2: Fetch Job Description & Tasks
const jobDetailsData = await fetchJSON(`${apiUrl}/onet/career-description/${career.code}`);
const isJobDetailsMissing = !jobDetailsData || Object.keys(jobDetailsData).length === 0;
// Step 3: Fetch Salary & Economic Projections in Parallel
const [economicData, salaryResponse] = await Promise.all([
fetchJSON(`${apiUrl}/projections/${career.code.split('.')[0]}`), fetchJSON(`${apiUrl}/projections/${career.code.split('.')[0]}`),
axios.get(`${apiUrl}/salary`, { axios.get(`${apiUrl}/salary`, {
params: { socCode: career.code.split('.')[0], area: areaTitle }, params: { socCode: career.code.split('.')[0], area: areaTitle },
@ -68,25 +60,43 @@ export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle
return res.data; return res.data;
}).catch((error) => { }).catch((error) => {
updateProgress(); updateProgress();
return error.response?.status === 404 ? null : error.response; if (error.response?.status === 404) {
console.warn(`⚠️ Salary data missing for ${career.title} (${career.code})`);
return null;
}
return error.response;
}), }),
]); ]);
const isEconomicMissing = !economicData || Object.keys(economicData).length === 0; const isCipMissing = !cipData || Object.keys(cipData).length === 0;
const isSalaryMissing = !salaryResponse; const isJobDetailsMissing = !jobDetailsData || Object.keys(jobDetailsData).length === 0;
const isEconomicMissing = !economicData || Object.values(economicData).every(val => val === "N/A" || val === "*");
const isSalaryMissing = salaryResponse === null || salaryResponse === undefined;
// ✅ Log only when needed
if (isSalaryMissing) {
console.warn(`⚠️ Missing Salary Data for ${career.title} (${career.code})`);
} else {
console.log(`✅ Salary Data Available for ${career.title} (${career.code})`);
}
const isLimitedData = isCipMissing || isJobDetailsMissing || isEconomicMissing || isSalaryMissing; const isLimitedData = isCipMissing || isJobDetailsMissing || isEconomicMissing || isSalaryMissing;
if (isLimitedData) console.log(`⚠️ Setting limitedData for ${career.title} (${career.code})`);
return { ...career, limitedData: isLimitedData }; return { ...career, limitedData: isLimitedData };
} catch (error) { } catch (error) {
console.error(`Error checking API response for ${career.title}:`, error); console.error(`Error checking API response for ${career.title}:`, error);
return { ...career, limitedData: true }; return { ...career, limitedData: true };
} }
}); });
try {
const updatedCareerList = await Promise.all(careerPromises); const updatedCareerList = await Promise.all(careerPromises);
setUpdatedCareers(updatedCareerList); setUpdatedCareers(updatedCareerList);
} finally {
setLoading(false); setLoading(false);
}
}; };
checkCareerDataAvailability(); checkCareerDataAvailability();
@ -98,7 +108,9 @@ export function CareerSuggestions({ careerSuggestions = [], userState, areaTitle
{loading ? ( {loading ? (
<div className="progress-container"> <div className="progress-container">
<div className="progress-bar" style={{ width: `${progress}%` }}> <div className="progress-bar" style={{
width: `${progress}%`,
maxWidth: "100%", }}>
{Math.round(progress)}% {Math.round(progress)}%
</div> </div>
<p>Loading Career Suggestions...</p> <p>Loading Career Suggestions...</p>

View File

@ -22,11 +22,11 @@ const Chatbot = ({ context }) => {
Your role is to not only provide career suggestions but to analyze them based on salary potential, job stability, education costs, and market trends. Your role is to not only provide career suggestions but to analyze them based on salary potential, job stability, education costs, and market trends.
Use the following user-specific data: Use the following user-specific data:
- Career Suggestions: ${context.careerSuggestions.map((c) => c.title).join(", ") || "No suggestions available."} - Career Suggestions: ${context.data.careerSuggestions?.map((c) => c.title).join(", ") || "No suggestions available."}
- Selected Career: ${context.selectedCareer?.title || "None"} - Selected Career: ${context.data.selectedCareer?.title || "None"}
- Schools: ${context.schools.map((s) => s["INSTNM"]).join(", ") || "No schools available."} - Schools: ${context.data.schools?.map((s) => s["INSTNM"]).join(", ") || "No schools available."}
- Median Salary: ${ - Median Salary: ${
context.salaryData.find((s) => s.percentile === "Median")?.value || "Unavailable" context.data.salaryData?.find((s) => s.percentile === "Median")?.value || "Unavailable"
} }
- ROI (Return on Investment): If available, use education costs vs. salary potential to guide users. - ROI (Return on Investment): If available, use education costs vs. salary potential to guide users.
@ -41,7 +41,6 @@ const Chatbot = ({ context }) => {
- "If you prefer stability, Y is a better long-term bet." - "If you prefer stability, Y is a better long-term bet."
`; `;
const messagesToSend = [ const messagesToSend = [
{ role: "system", content: contextSummary }, // Inject AptivaAI data on every request { role: "system", content: contextSummary }, // Inject AptivaAI data on every request
...messages, ...messages,

View File

@ -54,7 +54,7 @@ h2 {
/* Career Suggestions Section */ /* Career Suggestions Section */
.career-suggestions-container { .career-suggestions-container {
flex: 1.5; /* Ensures it takes the majority of space */ flex: 1.5; /* Ensures it takes the majority of space */
width: 60%; width: 100%;
max-width: 75%; max-width: 75%;
background-color: #ffffff; background-color: #ffffff;
padding: 15px; padding: 15px;
@ -115,6 +115,8 @@ h2 {
margin: 20px 0; margin: 20px 0;
padding: 10px; padding: 10px;
text-align: center; text-align: center;
position: relative; /* Ensures proper layout */
overflow: hidden; /* Prevents extra spacing */
} }
/* Striped Progress Bar */ /* Striped Progress Bar */

View File

@ -35,6 +35,7 @@ function Dashboard() {
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 [selectedFit, setSelectedFit] = useState('');
const [results, setResults] = useState([]); // Add results state
const jobZoneLabels = { const jobZoneLabels = {
'1': 'Little or No Preparation', '1': 'Little or No Preparation',
@ -107,6 +108,7 @@ const memoizedCareerSuggestions = useMemo(() => filteredCareers, [filteredCareer
loading={loading} loading={loading}
error={error} error={error}
userState={userState} userState={userState}
results={results}
/> />
); );
}, [selectedCareer, careerDetails, schools, salaryData, economicProjections, tuitionData, loading, error, userState]); }, [selectedCareer, careerDetails, schools, salaryData, economicProjections, tuitionData, loading, error, userState]);
@ -185,43 +187,71 @@ const memoizedCareerSuggestions = useMemo(() => filteredCareers, [filteredCareer
const { description, tasks } = await jobDetailsResponse.json(); const { description, tasks } = await jobDetailsResponse.json();
// Step 3: Fetch Data in Parallel for other career details // Step 3: Fetch Data in Parallel for other career details
const [filteredSchools, economicResponse, tuitionResponse, salaryResponse] = await Promise.all([ // Salary API call with error handling
fetchSchools(cleanedCipCode, userState), let salaryResponse;
axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`), try {
axios.get(`${apiUrl}/tuition`, { params: { cipCode: cleanedCipCode, state: userState }}), salaryResponse = await axios.get(`${apiUrl}/salary`, { params: { socCode: socCode.split('.')[0], area: areaTitle }});
axios.get(`${apiUrl}/salary`, { params: { socCode: socCode.split('.')[0], area: areaTitle }}), } catch (error) {
]); console.warn(`⚠️ Salary data not available for ${career.title} (${socCode})`);
salaryResponse = { data: {} }; // Prevents breaking the whole update
}
// Projections API call with error handling
let economicResponse;
try {
economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`);
} catch (error) {
console.warn(`⚠️ Economic projections not available for ${career.title} (${socCode})`);
economicResponse = { data: {} }; // Prevents breaking the whole update
}
// Tuition API call with error handling
let tuitionResponse;
try {
tuitionResponse = await axios.get(`${apiUrl}/tuition`, { params: { cipCode: cleanedCipCode, state: userState }});
} catch (error) {
console.warn(`⚠️ Tuition data not available for ${career.title} (${socCode})`);
tuitionResponse = { data: {} };
}
// Fetch schools separately (this one seems to be working fine)
const filteredSchools = await fetchSchools(cleanedCipCode, userState);
// Handle Distance Calculation // Handle Distance Calculation
const schoolsWithDistance = await Promise.all(filteredSchools.map(async (school) => { const schoolsWithDistance = await Promise.all(filteredSchools.map(async (school) => {
const schoolAddress = `${school.Address}, ${school.City}, ${school.State} ${school.ZIP}`; const schoolAddress = `${school.Address}, ${school.City}, ${school.State} ${school.ZIP}`;
try {
const response = await axios.post(`${apiUrl}/maps/distance`, { const response = await axios.post(`${apiUrl}/maps/distance`, {
userZipcode, userZipcode,
destinations: schoolAddress, destinations: schoolAddress,
}); });
const { distance, duration } = response.data; const { distance, duration } = response.data;
return { ...school, distance, duration }; return { ...school, distance, duration };
} catch (error) {
console.warn(`⚠️ Distance calculation failed for ${school.INSTNM}`);
return { ...school, distance: 'N/A', duration: 'N/A' };
}
})); }));
// Process Salary Data // Process Salary Data
const salaryDataPoints = [ const salaryDataPoints = salaryResponse.data && Object.keys(salaryResponse.data).length > 0 ? [
{ percentile: '10th Percentile', value: salaryResponse.data.A_PCT10 || 0 }, { percentile: '10th Percentile', value: salaryResponse.data.A_PCT10 || 0 },
{ percentile: '25th Percentile', value: salaryResponse.data.A_PCT25 || 0 }, { percentile: '25th Percentile', value: salaryResponse.data.A_PCT25 || 0 },
{ percentile: 'Median', value: salaryResponse.data.A_MEDIAN || 0 }, { percentile: 'Median', value: salaryResponse.data.A_MEDIAN || 0 },
{ percentile: '75th Percentile', value: salaryResponse.data.A_PCT75 || 0 }, { percentile: '75th Percentile', value: salaryResponse.data.A_PCT75 || 0 },
{ percentile: '90th Percentile', value: salaryResponse.data.A_PCT90 || 0 }, { percentile: '90th Percentile', value: salaryResponse.data.A_PCT90 || 0 },
]; ] : [];
// Consolidate Career Details with Job Description and Tasks
setCareerDetails({ setCareerDetails({
...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 || [],
}); });
} 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');
@ -232,6 +262,8 @@ const memoizedCareerSuggestions = useMemo(() => filteredCareers, [filteredCareer
[userState, apiUrl, areaTitle, userZipcode] [userState, apiUrl, areaTitle, userZipcode]
); );
console.log('Updated careerDetails:', careerDetails);
const chartData = { const chartData = {
labels: riaSecScores.map((score) => score.area), labels: riaSecScores.map((score) => score.area),
datasets: [ datasets: [
@ -322,6 +354,7 @@ const memoizedCareerSuggestions = useMemo(() => filteredCareers, [filteredCareer
userState, userState,
areaTitle, areaTitle,
userZipcode, userZipcode,
results, // Pass results to Chatbot
}} }}
/> />
</div> </div>

View File

@ -11,7 +11,6 @@ function PopoutPanel({
error = null, error = null,
closePanel, closePanel,
}) { }) {
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);
@ -26,28 +25,14 @@ const {
schools = [], schools = [],
} = data || {}; } = data || {};
useEffect(() => { useEffect(() => {
setResults([]); setResults([]);
setIsCalculated(false); setIsCalculated(false);
}, [schools]); }, [schools]);
if (!isVisible) return null; if (!isVisible) return null;
if (loading || loadingCalculation) {
return (
<div className="popout-panel">
<button className="close-btn" onClick={closePanel}>X</button>
<h2>Loading Career Details...</h2>
<ClipLoader size={35} color="#4A90E2" />
</div>
);
}
if (!isVisible) return null;
// Handle loading state
if (loading || loadingCalculation) { if (loading || loadingCalculation) {
return ( return (
<div className="popout-panel"> <div className="popout-panel">
@ -76,7 +61,7 @@ if (loading || loadingCalculation) {
return ( return (
<div className="popout-panel"> <div className="popout-panel">
<button onClick={closePanel}>Close</button> <button onClick={handleClosePanel}>Close</button>
<h2>{title}</h2> <h2>{title}</h2>
{/* Job Description and Tasks */} {/* Job Description and Tasks */}