dev1/src/components/CareerModal.js

254 lines
10 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { fetchSchools, clientGeocodeZip, haversineDistance} from '../utils/apiUtils.js';
const apiUrl = process.env.REACT_APP_API_URL;
const STATES = [
{ name: 'Alabama', code: 'AL' },
{ name: 'Alaska', code: 'AK' },
{ name: 'Arizona', code: 'AZ' },
{ name: 'Arkansas', code: 'AR' },
{ name: 'California', code: 'CA' },
{ name: 'Colorado', code: 'CO' },
{ name: 'Connecticut', code: 'CT' },
{ name: 'Delaware', code: 'DE' },
{ name: 'District of Columbia', code: 'DC' },
{ name: 'Florida', code: 'FL' },
{ name: 'Georgia', code: 'GA' },
{ name: 'Hawaii', code: 'HI' },
{ name: 'Idaho', code: 'ID' },
{ name: 'Illinois', code: 'IL' },
{ name: 'Indiana', code: 'IN' },
{ name: 'Iowa', code: 'IA' },
{ name: 'Kansas', code: 'KS' },
{ name: 'Kentucky', code: 'KY' },
{ name: 'Louisiana', code: 'LA' },
{ name: 'Maine', code: 'ME' },
{ name: 'Maryland', code: 'MD' },
{ name: 'Massachusetts', code: 'MA' },
{ name: 'Michigan', code: 'MI' },
{ name: 'Minnesota', code: 'MN' },
{ name: 'Mississippi', code: 'MS' },
{ name: 'Missouri', code: 'MO' },
{ name: 'Montana', code: 'MT' },
{ name: 'Nebraska', code: 'NE' },
{ name: 'Nevada', code: 'NV' },
{ name: 'New Hampshire', code: 'NH' },
{ name: 'New Jersey', code: 'NJ' },
{ name: 'New Mexico', code: 'NM' },
{ name: 'New York', code: 'NY' },
{ name: 'North Carolina', code: 'NC' },
{ name: 'North Dakota', code: 'ND' },
{ name: 'Ohio', code: 'OH' },
{ name: 'Oklahoma', code: 'OK' },
{ name: 'Oregon', code: 'OR' },
{ name: 'Pennsylvania', code: 'PA' },
{ name: 'Rhode Island', code: 'RI' },
{ name: 'South Carolina', code: 'SC' },
{ name: 'South Dakota', code: 'SD' },
{ name: 'Tennessee', code: 'TN' },
{ name: 'Texas', code: 'TX' },
{ name: 'Utah', code: 'UT' },
{ name: 'Vermont', code: 'VT' },
{ name: 'Virginia', code: 'VA' },
{ name: 'Washington', code: 'WA' },
{ name: 'West Virginia', code: 'WV' },
{ name: 'Wisconsin', code: 'WI' },
{ name: 'Wyoming', code: 'WY' },
];
// 2) Helper to convert state code => full name
function getFullStateName(code) {
const found = STATES.find((s) => s.code === code?.toUpperCase());
return found ? found.name : '';
}
function CareerModal({ career, userState, areaTitle, userZipcode, closeModal, addCareerToList }) {
const [careerDetails, setCareerDetails] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const handleCareerClick = async () => {
const socCode = career.code;
setLoading(true);
setError(null);
try {
const cipResponse = await fetch(`${apiUrl}/cip/${socCode}`);
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
const { cipCode } = await cipResponse.json();
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
const jobDetailsResponse = await fetch(`${apiUrl}/onet/career-description/${socCode}`);
if (!jobDetailsResponse.ok) throw new Error('Failed to fetch job description');
const { description, tasks } = await jobDetailsResponse.json();
const salaryResponse = await axios.get(`${apiUrl}/salary`, {
params: { socCode: socCode.split('.')[0], area: areaTitle },
}).catch(() => ({ data: {} }));
const economicResponse = await axios.get(`${apiUrl}/projections/${socCode.split('.')[0]}`, {
params: { state: getFullStateName(userState) },
}).catch(() => ({ data: {} }));
const sData = salaryResponse.data || {};
const salaryDataPoints = sData && Object.keys(sData).length > 0 ? [
{ percentile: '10th', regionalSalary: sData.regional?.regional_PCT10 || 0, nationalSalary: sData.national?.national_PCT10 || 0 },
{ percentile: '25th', regionalSalary: sData.regional?.regional_PCT25 || 0, nationalSalary: sData.national?.national_PCT25 || 0 },
{ percentile: 'Median', regionalSalary: sData.regional?.regional_MEDIAN || 0, nationalSalary: sData.national?.national_MEDIAN || 0 },
{ percentile: '75th', regionalSalary: sData.regional?.regional_PCT75 || 0, nationalSalary: sData.national?.national_PCT75 || 0 },
{ percentile: '90th', regionalSalary: sData.regional?.regional_PCT90 || 0, nationalSalary: sData.national?.national_PCT90 || 0 },
] : [];
setCareerDetails({
...career,
jobDescription: description,
tasks,
economicProjections: economicResponse.data || {},
salaryData: salaryDataPoints,
});
} catch (error) {
console.error(error);
setError('Failed to load career details.');
} finally {
setLoading(false);
}
};
handleCareerClick();
}, [career, userState, areaTitle, userZipcode]);
if (loading) return <div>Loading...</div>;
if (error) return <div>{error}</div>;
const calculateStabilityRating = (salaryData) => {
const medianSalaryObj = salaryData.find(s => s.percentile === 'Median');
const medianSalary = medianSalaryObj?.regionalSalary || medianSalaryObj?.nationalSalary || 0;
if (medianSalary >= 90000) return 5;
if (medianSalary >= 70000) return 4;
if (medianSalary >= 50000) return 3;
if (medianSalary >= 30000) return 2;
return 1;
};
return (
<div className="fixed inset-0 bg-gray-900 bg-opacity-70 flex justify-center items-center overflow-auto z-50">
<div className="bg-white rounded-lg shadow-lg w-full max-w-5xl p-6 m-4 max-h-[90vh] overflow-y-auto">
<div className="flex justify-between items-center mb-4 pb-2 border-b">
<h2 className="text-2xl font-bold text-blue-600">{careerDetails.title}</h2>
<div className="flex gap-2">
<button
onClick={() => {
const stabilityRating = calculateStabilityRating(careerDetails.salaryData);
addCareerToList({
...careerDetails,
ratings: {
stability: stabilityRating
}
});
}}
className="text-white bg-green-500 hover:bg-green-600 rounded px-3 py-1"
>
Add to Comparison
</button>
<button
onClick={closeModal}
className="text-white bg-red-500 hover:bg-red-600 rounded px-3 py-1"
>
Close
</button>
</div>
</div>
{/* Job Description */}
<div className="mb-4">
<h3 className="text-lg font-semibold mb-1">Job Description:</h3>
<p className="text-gray-700">{careerDetails.jobDescription}</p>
</div>
{/* Tasks (full width) */}
<div className="mb-4 border-t pt-3">
<h3 className="text-lg font-semibold mb-2">Tasks:</h3>
<ul className="list-disc pl-5 space-y-1">
{careerDetails.tasks.map((task, i) => (
<li key={i}>{task}</li>
))}
</ul>
</div>
{/* Salary and Economic Projections side-by-side */}
<div className="flex flex-col md:flex-row gap-4 border-t pt-3">
{/* Salary Data */}
<div className="md:w-1/2 overflow-x-auto">
<h3 className="text-lg font-semibold mb-2">Salary Data:</h3>
<table className="w-full text-left border border-gray-300 rounded">
<thead className="bg-gray-100">
<tr>
<th className="px-3 py-2 border-b">Percentile</th>
<th className="px-3 py-2 border-b">Regional Salary</th>
<th className="px-3 py-2 border-b">National Salary</th>
</tr>
</thead>
<tbody>
{careerDetails.salaryData.map((s, i) => (
<tr key={i}>
<td className="px-3 py-2 border-b">{s.percentile}</td>
<td className="px-3 py-2 border-b">${s.regionalSalary.toLocaleString()}</td>
<td className="px-3 py-2 border-b">${s.nationalSalary.toLocaleString()}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Economic Projections */}
<div className="md:w-1/2 overflow-x-auto">
<h3 className="text-lg font-semibold mb-2">Economic Projections:</h3>
<table className="w-full text-left border border-gray-300 rounded">
<thead className="bg-gray-100">
<tr>
<th className="px-3 py-2 border-b">Region</th>
<th className="px-3 py-2 border-b">Current Jobs</th>
<th className="px-3 py-2 border-b">Jobs in 10 yrs</th>
<th className="px-3 py-2 border-b">Growth %</th>
<th className="px-3 py-2 border-b">Annual Openings</th>
</tr>
</thead>
<tbody>
{careerDetails.economicProjections.state && (
<tr>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.state.area}</td>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.state.base.toLocaleString()}</td>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.state.projection.toLocaleString()}</td>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.state.percentChange}%</td>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.state.annualOpenings.toLocaleString()}</td>
</tr>
)}
{careerDetails.economicProjections.national && (
<tr>
<td className="px-3 py-2 border-b">National</td>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.national.base.toLocaleString()}</td>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.national.projection.toLocaleString()}</td>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.national.percentChange}%</td>
<td className="px-3 py-2 border-b">{careerDetails.economicProjections.national.annualOpenings.toLocaleString()}</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
);
}
export default CareerModal;