Fetch schools in EducationalProgramsPage.js
This commit is contained in:
parent
146061a9b9
commit
77cd3b6845
@ -391,13 +391,21 @@ app.get('/api/cip/:socCode', (req, res) => {
|
|||||||
* Single schools / tuition / etc. routes
|
* Single schools / tuition / etc. routes
|
||||||
**************************************************/
|
**************************************************/
|
||||||
app.get('/api/schools', (req, res) => {
|
app.get('/api/schools', (req, res) => {
|
||||||
const { cipCode, state } = req.query;
|
// 1) Read `cipCodes` from query (comma-separated string)
|
||||||
console.log('Query Params:', { cipCode });
|
const { cipCodes } = req.query;
|
||||||
if (!cipCode || !state) {
|
|
||||||
return res.status(400).json({ error: 'CIP Code is required' });
|
if (!cipCodes ) {
|
||||||
|
return res.status(400).json({ error: 'cipCodes (comma-separated) and state are required.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const matchedCIP = cipCode.replace('.', '').slice(0, 4);
|
// 2) Convert `cipCodes` to array => e.g. "1101,1103,1104" => ["1101","1103","1104"]
|
||||||
|
const cipArray = cipCodes.split(',').map((c) => c.trim()).filter(Boolean);
|
||||||
|
if (cipArray.length === 0) {
|
||||||
|
return res.status(400).json({ error: 'No valid CIP codes were provided.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Load your raw schools data
|
||||||
let schoolsData = [];
|
let schoolsData = [];
|
||||||
try {
|
try {
|
||||||
const rawData = fs.readFileSync(institutionFilePath, 'utf8');
|
const rawData = fs.readFileSync(institutionFilePath, 'utf8');
|
||||||
@ -406,41 +414,82 @@ app.get('/api/schools', (req, res) => {
|
|||||||
console.error('Error parsing institution data:', err.message);
|
console.error('Error parsing institution data:', err.message);
|
||||||
return res.status(500).json({ error: 'Failed to load schools data.' });
|
return res.status(500).json({ error: 'Failed to load schools data.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4) Filter any school whose CIP code matches ANY of the CIP codes in the array
|
||||||
|
// Convert the school's CIP code the same way you do in your old logic (remove dot, slice, etc.)
|
||||||
const filtered = schoolsData.filter((s) => {
|
const filtered = schoolsData.filter((s) => {
|
||||||
const scip = s['CIPCODE']?.toString().replace('.', '').slice(0, 4);
|
const scip = s['CIPCODE']?.toString().replace('.', '').slice(0, 4);
|
||||||
return scip.startsWith(matchedCIP);
|
return cipArray.some((cip) => scip.startsWith(cip));
|
||||||
});
|
});
|
||||||
console.log('Filtered schools:', filtered.length);
|
|
||||||
res.json(filtered);
|
// 5) (Optional) Deduplicate if you suspect overlaps among CIP codes.
|
||||||
|
// E.g. by a “UNITID” or unique property:
|
||||||
|
const uniqueMap = new Map();
|
||||||
|
for (const school of filtered) {
|
||||||
|
const key = school.UNITID || school.INSTNM; // pick your unique field
|
||||||
|
if (!uniqueMap.has(key)) {
|
||||||
|
uniqueMap.set(key, school);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const deduped = Array.from(uniqueMap.values());
|
||||||
|
|
||||||
|
console.log('Unique schools found:', deduped.length);
|
||||||
|
res.json(deduped);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error reading Institution data:', err.message);
|
console.error('Error reading Institution data:', err.message);
|
||||||
res.status(500).json({ error: 'Failed to load schools data.' });
|
res.status(500).json({ error: 'Failed to load schools data.' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// tuition
|
// tuition
|
||||||
app.get('/api/tuition', (req, res) => {
|
app.get('/api/tuition', (req, res) => {
|
||||||
const { cipCode, state } = req.query;
|
const { cipCodes, state } = req.query;
|
||||||
console.log(`Received CIP: ${cipCode}, State: ${state}`);
|
if (!cipCodes || !state) {
|
||||||
if (!cipCode || !state) {
|
return res.status(400).json({ error: 'cipCodes and state are required.' });
|
||||||
return res.status(400).json({ error: 'CIP Code and State are required.' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw = fs.readFileSync(institutionFilePath, 'utf8');
|
const raw = fs.readFileSync(institutionFilePath, 'utf8');
|
||||||
const schoolsData = JSON.parse(raw);
|
const schoolsData = JSON.parse(raw);
|
||||||
|
|
||||||
|
const cipArray = cipCodes.split(',').map((c) => c.trim()).filter(Boolean);
|
||||||
|
if (!cipArray.length) {
|
||||||
|
return res.status(400).json({ error: 'No valid CIP codes.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter logic
|
||||||
const filtered = schoolsData.filter((school) => {
|
const filtered = schoolsData.filter((school) => {
|
||||||
const cval = school['CIPCODE']?.toString().replace(/[^0-9]/g, '');
|
const cval = school['CIPCODE']?.toString().replace(/\./g, '').slice(0, 4);
|
||||||
const sVal = school['State']?.toUpperCase().trim();
|
const sVal = school['State']?.toUpperCase().trim();
|
||||||
return cval.startsWith(cipCode) && sVal === state.toUpperCase().trim();
|
|
||||||
|
// Check if cval starts with ANY CIP in cipArray
|
||||||
|
const matchesCip = cipArray.some((cip) => cval.startsWith(cip));
|
||||||
|
const matchesState = sVal === state.toUpperCase().trim();
|
||||||
|
|
||||||
|
return matchesCip && matchesState;
|
||||||
});
|
});
|
||||||
console.log('Filtered Tuition Data Count:', filtered.length);
|
|
||||||
res.json(filtered);
|
// Optionally deduplicate by UNITID
|
||||||
|
const uniqueMap = new Map();
|
||||||
|
for (const school of filtered) {
|
||||||
|
const key = school.UNITID || school.INSTNM; // or something else unique
|
||||||
|
if (!uniqueMap.has(key)) {
|
||||||
|
uniqueMap.set(key, school);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deduped = Array.from(uniqueMap.values());
|
||||||
|
console.log('Filtered Tuition Data Count:', deduped.length);
|
||||||
|
|
||||||
|
res.json(deduped);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error reading tuition data:', err.message);
|
console.error('Error reading tuition data:', err.message);
|
||||||
res.status(500).json({ error: 'Failed to load tuition data.' });
|
res.status(500).json({ error: 'Failed to load tuition data.' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**************************************************
|
/**************************************************
|
||||||
* SINGLE route for projections from economicproj.json
|
* SINGLE route for projections from economicproj.json
|
||||||
**************************************************/
|
**************************************************/
|
||||||
|
@ -8,42 +8,43 @@ const CareerSearch = ({ onCareerSelected }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCareerData = async () => {
|
const fetchCareerData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/career_clusters.json');
|
const response = await fetch('/careers_with_ratings.json');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Create a Map keyed by title, storing one object per unique title
|
// Create a Map keyed by career title, so we only keep one object per unique title
|
||||||
const uniqueByTitle = new Map();
|
const uniqueByTitle = new Map();
|
||||||
|
|
||||||
const clusters = Object.keys(data);
|
// data is presumably an array like:
|
||||||
for (let i = 0; i < clusters.length; i++) {
|
// [
|
||||||
const clusterKey = clusters[i];
|
// { soc_code: "15-1241.00", title: "Computer Network Architects", cip_codes: [...], ... },
|
||||||
const subdivisions = Object.keys(data[clusterKey]);
|
// { soc_code: "15-1299.07", title: "Blockchain Engineers", cip_codes: [...], ... },
|
||||||
|
// ...
|
||||||
for (let j = 0; j < subdivisions.length; j++) {
|
// ]
|
||||||
const subKey = subdivisions[j];
|
for (const c of data) {
|
||||||
const careersList = data[clusterKey][subKey] || [];
|
// Make sure we have a valid title, soc_code, and cip_codes
|
||||||
|
if (c.title && c.soc_code && c.cip_codes) {
|
||||||
for (let k = 0; k < careersList.length; k++) {
|
// Only store the first unique title found
|
||||||
const c = careersList[k];
|
|
||||||
if (c.title && c.soc_code && c.cip_code !== undefined) {
|
|
||||||
if (!uniqueByTitle.has(c.title)) {
|
if (!uniqueByTitle.has(c.title)) {
|
||||||
uniqueByTitle.set(c.title, {
|
uniqueByTitle.set(c.title, {
|
||||||
title: c.title,
|
title: c.title,
|
||||||
soc_code: c.soc_code,
|
soc_code: c.soc_code,
|
||||||
cip_code: c.cip_code
|
// NOTE: We store the array of CIPs in `cip_code`.
|
||||||
|
cip_code: c.cip_codes,
|
||||||
|
limited_data: c.limited_data,
|
||||||
|
ratings: c.ratings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Convert the map into an array
|
||||||
const dedupedArr = [...uniqueByTitle.values()];
|
const dedupedArr = [...uniqueByTitle.values()];
|
||||||
setCareerObjects(dedupedArr);
|
setCareerObjects(dedupedArr);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading or parsing career_clusters.json:', error);
|
console.error('Error loading or parsing careers_with_ratings.json:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchCareerData();
|
fetchCareerData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1,75 +1,93 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
// Your existing search component
|
|
||||||
import CareerSearch from './CareerSearch.js';
|
import CareerSearch from './CareerSearch.js';
|
||||||
|
|
||||||
// The existing utility calls
|
|
||||||
import { fetchSchools, clientGeocodeZip, haversineDistance } from '../utils/apiUtils.js';
|
import { fetchSchools, clientGeocodeZip, haversineDistance } from '../utils/apiUtils.js';
|
||||||
|
|
||||||
// A simple Button component (if you don’t already import from elsewhere)
|
|
||||||
function Button({ onClick, children, ...props }) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-500"
|
|
||||||
onClick={onClick}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EducationalProgramsPage
|
|
||||||
* - If we have a CIP code (from location.state or otherwise), we fetch + display schools.
|
|
||||||
* - If no CIP code is provided, user sees a CareerSearch to pick a career => sets CIP code.
|
|
||||||
* - Then the user can filter & sort (tuition, distance, optional in-state only).
|
|
||||||
*/
|
|
||||||
function EducationalProgramsPage() {
|
function EducationalProgramsPage() {
|
||||||
// 1) Get CIP code from React Router location.state (if available)
|
// 1) Read an array of CIP codes from route state (if provided),
|
||||||
// If no CIP code in route state, default to an empty string
|
// or default to an empty array.
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [cipCode, setCipCode] = useState(location.state?.cipCode || '');
|
const [cipCodes, setCipCodes] = useState(location.state?.cipCodes || []);
|
||||||
|
|
||||||
// Optionally, you can also read userState / userZip from location.state or from user’s profile
|
// userState / userZip from route state or user profile
|
||||||
const [userState, setUserState] = useState(location.state?.userState || '');
|
const [userState, setUserState] = useState(location.state?.userState || '');
|
||||||
const [userZip, setUserZip] = useState(location.state?.userZip || '');
|
const [userZip, setUserZip] = useState(location.state?.userZip || '');
|
||||||
|
|
||||||
// ============ Data + UI state ============
|
// For UI
|
||||||
const [schools, setSchools] = useState([]);
|
const [schools, setSchools] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
// Filter states
|
// Filters
|
||||||
const [sortBy, setSortBy] = useState('tuition'); // 'tuition' or 'distance'
|
const [sortBy, setSortBy] = useState('tuition'); // or 'distance'
|
||||||
const [maxTuition, setMaxTuition] = useState(99999);
|
const [maxTuition, setMaxTuition] = useState(20000);
|
||||||
const [maxDistance, setMaxDistance] = useState(99999);
|
const [maxDistance, setMaxDistance] = useState(100);
|
||||||
// Optional “in-state only” toggle
|
|
||||||
const [inStateOnly, setInStateOnly] = useState(false);
|
const [inStateOnly, setInStateOnly] = useState(false);
|
||||||
|
const [careerTitle, setCareerTitle] = useState(location.state?.careerTitle || '');
|
||||||
|
|
||||||
// ============ Handle Career Search -> CIP code ============
|
// ============== If user picks a career from CareerSearch ==============
|
||||||
const handleCareerSelected = (foundObj) => {
|
const handleCareerSelected = (foundObj) => {
|
||||||
// foundObj = { title, soc_code, cip_code } from CareerSearch
|
setCareerTitle(foundObj.title || '');
|
||||||
if (foundObj?.cip_code) {
|
let rawCips = [];
|
||||||
setCipCode(foundObj.cip_code);
|
if (Array.isArray(foundObj.cip_code)) {
|
||||||
|
// e.g. [11.0101, 11.0301, 11.0802, ...]
|
||||||
|
rawCips = foundObj.cip_code;
|
||||||
|
} else {
|
||||||
|
// single CIP code scenario
|
||||||
|
rawCips = [foundObj.cip_code];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean each CIP code (remove the dot, slice to 4 digits)
|
||||||
|
// e.g. "11.0101" => "1101"
|
||||||
|
const cleanedCips = rawCips.map((code) => {
|
||||||
|
const codeStr = code.toString();
|
||||||
|
return codeStr.replace('.', '').slice(0,4);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now store them in state
|
||||||
|
setCipCodes(cleanedCips);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============ Fetch + Compute Distance once we have a CIP code ============
|
// pseudo-code snippet
|
||||||
|
useEffect(() => {
|
||||||
|
async function loadUserProfile() {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
console.warn('No token found, cannot load user-profile.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await fetch('/api/user-profile', {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Failed to fetch user profile');
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
// data.zipcode => "30102"
|
||||||
|
setUserZip(data.zipcode || '');
|
||||||
|
setUserState(data.state || '');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading user profile:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadUserProfile();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ============== Fetch schools once we have CIP codes ==============
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If no CIP code is set yet, do nothing.
|
if (!cipCodes.length) return; // no CIP codes => show CareerSearch fallback
|
||||||
if (!cipCode) return;
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1) Fetch schools by CIP code (and userState if your API still uses it)
|
// 1) Call fetchSchools with an array of CIP codes
|
||||||
const fetchedSchools = await fetchSchools(cipCode, userState);
|
// so we can do the "comma-separated" request in the utility.
|
||||||
|
const fetchedSchools = await fetchSchools(cipCodes);
|
||||||
|
|
||||||
// 2) Optionally geocode user ZIP to compute distances
|
// 2) Optionally geocode user ZIP for distance
|
||||||
let userLat = null;
|
let userLat = null;
|
||||||
let userLng = null;
|
let userLng = null;
|
||||||
if (userZip) {
|
if (userZip) {
|
||||||
@ -82,7 +100,7 @@ function EducationalProgramsPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Compute distance for each school (if lat/lng is available)
|
// 3) Compute distance
|
||||||
const schoolsWithDistance = fetchedSchools.map((sch) => {
|
const schoolsWithDistance = fetchedSchools.map((sch) => {
|
||||||
const lat2 = sch.LATITUDE ? parseFloat(sch.LATITUDE) : null;
|
const lat2 = sch.LATITUDE ? parseFloat(sch.LATITUDE) : null;
|
||||||
const lon2 = sch.LONGITUD ? parseFloat(sch.LONGITUD) : null;
|
const lon2 = sch.LONGITUD ? parseFloat(sch.LONGITUD) : null;
|
||||||
@ -90,14 +108,13 @@ function EducationalProgramsPage() {
|
|||||||
if (userLat && userLng && lat2 && lon2) {
|
if (userLat && userLng && lat2 && lon2) {
|
||||||
const distMiles = haversineDistance(userLat, userLng, lat2, lon2);
|
const distMiles = haversineDistance(userLat, userLng, lat2, lon2);
|
||||||
return { ...sch, distance: distMiles.toFixed(1) };
|
return { ...sch, distance: distMiles.toFixed(1) };
|
||||||
} else {
|
|
||||||
return { ...sch, distance: null };
|
|
||||||
}
|
}
|
||||||
|
return { ...sch, distance: null };
|
||||||
});
|
});
|
||||||
|
|
||||||
setSchools(schoolsWithDistance);
|
setSchools(schoolsWithDistance);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching/processing schools:', err);
|
console.error('[EducationalProgramsPage] error:', err);
|
||||||
setError('Failed to load schools.');
|
setError('Failed to load schools.');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -105,59 +122,63 @@ function EducationalProgramsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [cipCode, userState, userZip]);
|
}, [cipCodes, userState, userZip]);
|
||||||
|
|
||||||
// ============ Filter + Sort ============
|
// ============== Filter & Sort in useMemo ==============
|
||||||
const filteredAndSortedSchools = useMemo(() => {
|
const filteredAndSortedSchools = useMemo(() => {
|
||||||
if (!schools) return [];
|
if (!schools) return [];
|
||||||
let result = [...schools];
|
let result = [...schools];
|
||||||
|
|
||||||
// 1) (Optional) In-state only
|
// 1) In-state
|
||||||
if (inStateOnly && userState) {
|
if (inStateOnly && userState) {
|
||||||
result = result.filter((sch) => sch.STABBR === userState);
|
const userAbbr = userState.trim().toUpperCase();
|
||||||
|
result = result.filter((sch) => {
|
||||||
|
const schoolAbbr = sch.State ? sch.State.trim().toUpperCase() : '';
|
||||||
|
return schoolAbbr === userAbbr;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Filter by max tuition
|
|
||||||
// We’ll use “In_state cost” if your data references that, or you can adapt.
|
// 2) Max tuition
|
||||||
result = result.filter((sch) => {
|
result = result.filter((sch) => {
|
||||||
const inStateCost = sch['In_state cost']
|
const cost = sch['In_state cost']
|
||||||
? parseFloat(sch['In_state cost'])
|
? parseFloat(sch['In_state cost'])
|
||||||
: 999999;
|
: 999999;
|
||||||
return inStateCost <= maxTuition;
|
return cost <= maxTuition;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 3) Filter by max distance
|
// 3) Max distance
|
||||||
result = result.filter((sch) => {
|
result = result.filter((sch) => {
|
||||||
if (sch.distance === null) {
|
if (sch.distance === null) return true; // keep unknown
|
||||||
// If distance is unknown, decide if you want to include or exclude it
|
|
||||||
return true; // let’s include unknown
|
|
||||||
}
|
|
||||||
return parseFloat(sch.distance) <= maxDistance;
|
return parseFloat(sch.distance) <= maxDistance;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4) Sort
|
// 4) Sort
|
||||||
if (sortBy === 'distance') {
|
if (sortBy === 'distance') {
|
||||||
result.sort((a, b) => {
|
result.sort((a, b) => {
|
||||||
const distA = a.distance !== null ? parseFloat(a.distance) : Infinity;
|
const distA = a.distance ? parseFloat(a.distance) : Infinity;
|
||||||
const distB = b.distance !== null ? parseFloat(b.distance) : Infinity;
|
const distB = b.distance ? parseFloat(b.distance) : Infinity;
|
||||||
return distA - distB;
|
return distA - distB;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// sort by tuition
|
// Sort by in-state tuition
|
||||||
result.sort((a, b) => {
|
result.sort((a, b) => {
|
||||||
const tA = a['In_state cost'] ? parseFloat(a['In_state cost']) : Infinity;
|
const tA = a['In_state cost']
|
||||||
const tB = b['In_state cost'] ? parseFloat(b['In_state cost']) : Infinity;
|
? parseFloat(a['In_state cost'])
|
||||||
|
: Infinity;
|
||||||
|
const tB = b['In_state cost']
|
||||||
|
? parseFloat(b['In_state cost'])
|
||||||
|
: Infinity;
|
||||||
return tA - tB;
|
return tA - tB;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, [schools, sortBy, maxTuition, maxDistance, inStateOnly, userState]);
|
}, [schools, inStateOnly, userState, maxTuition, maxDistance, sortBy]);
|
||||||
|
|
||||||
// ============ Render UI ============
|
// ============== Render ==============
|
||||||
|
// Show the fallback (CareerSearch) if we have no CIP codes
|
||||||
// 1) If we have NO CIP code yet, show the fallback “CareerSearch”
|
if (!cipCodes.length) {
|
||||||
if (!cipCode) {
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<h2 className="text-2xl font-bold mb-4">Educational Programs</h2>
|
<h2 className="text-2xl font-bold mb-4">Educational Programs</h2>
|
||||||
@ -172,7 +193,7 @@ function EducationalProgramsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) If we DO have a CIP code, show the filterable school list
|
// If CIP codes exist but we’re loading
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className="p-4">Loading schools...</div>;
|
return <div className="p-4">Loading schools...</div>;
|
||||||
}
|
}
|
||||||
@ -187,9 +208,9 @@ function EducationalProgramsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<h2 className="text-2xl font-bold mb-4">
|
<h2>Schools for: {careerTitle || 'Unknown Career'}</h2>
|
||||||
Schools Offering Programs for CIP: {cipCode}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{/* Filter Bar */}
|
{/* Filter Bar */}
|
||||||
<div className="mb-4 flex flex-wrap items-center space-x-4">
|
<div className="mb-4 flex flex-wrap items-center space-x-4">
|
||||||
@ -228,7 +249,6 @@ function EducationalProgramsPage() {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{/* Optional: In-State Only Toggle */}
|
|
||||||
{userState && (
|
{userState && (
|
||||||
<label className="inline-flex items-center space-x-2 text-sm text-gray-600">
|
<label className="inline-flex items-center space-x-2 text-sm text-gray-600">
|
||||||
<input
|
<input
|
||||||
|
@ -47,19 +47,26 @@ export function haversineDistance(lat1, lon1, lat2, lon2) {
|
|||||||
return R * c;
|
return R * c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch schools
|
export async function fetchSchools(cipCodes) {
|
||||||
export const fetchSchools = async (cipCode) => {
|
|
||||||
try {
|
try {
|
||||||
const apiUrl = process.env.REACT_APP_API_URL || '';
|
const apiUrl = process.env.REACT_APP_API_URL || '';
|
||||||
|
|
||||||
|
// 1) If `cipCodes` is a single string => wrap in array
|
||||||
|
let codesArray = Array.isArray(cipCodes) ? cipCodes : [cipCodes];
|
||||||
|
|
||||||
|
// 2) Turn that array into a comma-separated string
|
||||||
|
// e.g. ["1101","1409"] => "1101,1409"
|
||||||
|
const cipParam = codesArray.join(',');
|
||||||
|
|
||||||
|
// 3) Call your endpoint with `?cipCodes=1101,1409&state=NY`
|
||||||
const response = await axios.get(`${apiUrl}/schools`, {
|
const response = await axios.get(`${apiUrl}/schools`, {
|
||||||
params: {
|
params: {
|
||||||
cipCode
|
cipCodes: cipParam,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
return response.data;
|
||||||
return response.data; // Return filtered data
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching schools:', error);
|
console.error('Error fetching schools:', error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user