Limited Data flag issues fixed-ish. Loading bar to stretch most of the width of container.
This commit is contained in:
parent
a1a8f9c7dc
commit
ca7b230b25
@ -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>
|
||||||
|
@ -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,
|
||||||
|
@ -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 */
|
||||||
|
@ -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>
|
||||||
|
@ -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 */}
|
||||||
|
Loading…
Reference in New Issue
Block a user