124 lines
4.2 KiB
JavaScript
124 lines
4.2 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
||
|
||
/**
|
||
* Props:
|
||
* - socCode: e.g. "17-1011"
|
||
* - stateName: e.g. "Kentucky" (optional; if not provided, defaults to "United States")
|
||
*/
|
||
function EconomicProjections({ socCode, stateName }) {
|
||
const [data, setData] = useState(null); // { state: {...}, national: {...} }
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState(null);
|
||
|
||
useEffect(() => {
|
||
if (!socCode) return;
|
||
|
||
const fetchData = async () => {
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
try {
|
||
const encodedState = stateName ? encodeURIComponent(stateName) : '';
|
||
const url = `/api/projections/${socCode}?state=${encodedState}`;
|
||
const res = await fetch(url);
|
||
if (!res.ok) {
|
||
throw new Error(`Server responded with status ${res.status}`);
|
||
}
|
||
const json = await res.json();
|
||
setData(json);
|
||
} catch (err) {
|
||
console.error("Error fetching economic projections:", err);
|
||
setError("Failed to load economic projections.");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
fetchData();
|
||
}, [socCode, stateName]);
|
||
|
||
if (loading) return <div>Loading Projections...</div>;
|
||
if (error) return <div style={{ color: 'red' }}>{error}</div>;
|
||
if (!data) return null;
|
||
|
||
const { state, national } = data;
|
||
|
||
/**
|
||
* Safely parse the value to a number. If parsing fails, we just return the original string.
|
||
* Then we format with toLocaleString() if numeric.
|
||
*/
|
||
const formatNumber = (val) => {
|
||
if (val == null || val === '') return 'N/A';
|
||
|
||
// Coerce string -> number
|
||
const parsed = Number(val);
|
||
if (!isNaN(parsed)) {
|
||
// If it’s actually numeric
|
||
return parsed.toLocaleString();
|
||
} else {
|
||
// If it’s truly not numeric, return the raw value
|
||
return val;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="border p-2 my-3">
|
||
<h3 className="text-lg font-bold mb-3">
|
||
Economic Projections: {state?.occupationName ?? 'N/A'}
|
||
</h3>
|
||
<table className="w-full border text-sm">
|
||
<thead>
|
||
<tr className="bg-gray-100">
|
||
<th className="p-2 text-left">Metric</th>
|
||
<th className="p-2 text-left">{state?.area || 'State'}</th>
|
||
<th className="p-2 text-left">{national?.area || 'U.S.'}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td className="p-2">Base Year</td>
|
||
<td className="p-2">{state?.baseYear ?? 'N/A'}</td>
|
||
<td className="p-2">{national?.baseYear ?? 'N/A'}</td>
|
||
</tr>
|
||
<tr>
|
||
<td className="p-2">Base Employment</td>
|
||
<td className="p-2">{formatNumber(state?.base)}</td>
|
||
<td className="p-2">{formatNumber(national?.base)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td className="p-2">Projected Year</td>
|
||
<td className="p-2">{state?.projectedYear ?? 'N/A'}</td>
|
||
<td className="p-2">{national?.projectedYear ?? 'N/A'}</td>
|
||
</tr>
|
||
<tr>
|
||
<td className="p-2">Projected Employment</td>
|
||
<td className="p-2">{formatNumber(state?.projection)}</td>
|
||
<td className="p-2">{formatNumber(national?.projection)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td className="p-2">Employment Change</td>
|
||
<td className="p-2">{formatNumber(state?.change)}</td>
|
||
<td className="p-2">{formatNumber(national?.change)}</td>
|
||
</tr>
|
||
<tr>
|
||
<td className="p-2">Percent Change</td>
|
||
<td className="p-2">
|
||
{state?.percentChange != null ? `${state.percentChange}%` : 'N/A'}
|
||
</td>
|
||
<td className="p-2">
|
||
{national?.percentChange != null ? `${national.percentChange}%` : 'N/A'}
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td className="p-2">Annual Openings</td>
|
||
<td className="p-2">{formatNumber(state?.annualOpenings)}</td>
|
||
<td className="p-2">{formatNumber(national?.annualOpenings)}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default EconomicProjections;
|