From 7f73977cce2fc1a3d22eb55003e0126622e4dc00 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 21 May 2025 14:59:26 +0000 Subject: [PATCH] UI Skills fixes and Schools fixes --- src/components/EducationalProgramsPage.js | 225 ++++++++++++---------- 1 file changed, 124 insertions(+), 101 deletions(-) diff --git a/src/components/EducationalProgramsPage.js b/src/components/EducationalProgramsPage.js index 5ebb0c0..d9d08d0 100644 --- a/src/components/EducationalProgramsPage.js +++ b/src/components/EducationalProgramsPage.js @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import CareerSearch from './CareerSearch.js'; import { ONET_DEFINITIONS } from './definitions.js'; import { fetchSchools, clientGeocodeZip, haversineDistance } from '../utils/apiUtils.js'; @@ -31,28 +31,36 @@ function combineIMandLV(rows) { return Array.from(map.values()); } +function ensureHttp(urlString) { + if (!urlString) return ''; + // If it already starts with 'http://' or 'https://', just return as-is. + if (/^https?:\/\//i.test(urlString)) { + return urlString; + } + // Otherwise prepend 'https://' (or 'http://'). + return `https://${urlString}`; +} + // Convert numeric importance (1–5) to star or emoji compact representation function renderImportance(val) { - // Example: star approach (rounded) - // or you can do emojis like "πŸ”΄πŸŸ’πŸŸ‘" etc. const max = 5; const rounded = Math.round(val); const stars = 'β˜…'.repeat(rounded) + 'β˜†'.repeat(max - rounded); - return `${stars}`; // e.g. β˜…β˜…β˜…β˜…β˜† + return `${stars}`; } // Convert numeric level (0–7) to bar or block representation function renderLevel(val) { - // 7 is max, we’ll do a small row of squares const max = 7; const rounded = Math.round(val); - const filled = 'β– '.repeat(rounded); // 'β– ' is a filled block + const filled = 'β– '.repeat(rounded); const empty = 'β–‘'.repeat(max - rounded); - return `${filled}${empty}`; // e.g. β– β– β– β– β– β–‘β–‘ + return `${filled}${empty}`; } function EducationalProgramsPage() { const location = useLocation(); + const navigate = useNavigate(); const [socCode, setsocCode] = useState(location.state?.socCode || ''); const [cipCodes, setCipCodes] = useState(location.state?.cipCodes || []); @@ -87,24 +95,28 @@ function EducationalProgramsPage() { setCipCodes(cleanedCips); }; + // Fixed handleSelectSchool (removed extra brace) + const handleSelectSchool = (school) => { + const proceed = window.confirm( + 'You’re about to move to the financial planning portion of the app, which is reserved for premium subscribers. Do you want to continue?' + ); + if (proceed) { + navigate('/financial-planner', { state: { selectedSchool: school } }); + }; + }; + function getSearchLinks(ksaName, careerTitle) { - const combinedQuery = `${careerTitle} ${ksaName}`.trim(); - const encoded = encodeURIComponent(combinedQuery); - - const courseraUrl = `https://www.coursera.org/search?query=${encoded}`; + const combinedQuery = `${careerTitle} ${ksaName}`.trim(); + const encoded = encodeURIComponent(combinedQuery); + const courseraUrl = `https://www.coursera.org/search?query=${encoded}`; + const edxUrl = `https://www.edx.org/search?q=${encoded}`; - const edxUrl = `https://www.edx.org/search?q=${encoded}`; - - - const classCentralUrl = `https://www.classcentral.com/search?q=${encoded}`; - - return [ - { title: 'Coursera', url: courseraUrl }, - { title: 'edX', url: edxUrl }, - - ]; -} + return [ + { title: 'Coursera', url: courseraUrl }, + { title: 'edX', url: edxUrl }, + ]; + } // Load KSA data once useEffect(() => { @@ -138,22 +150,14 @@ function EducationalProgramsPage() { setKsaForCareer([]); return; } - // 1) filter by socCode let filtered = allKsaData.filter((r) => r.onetSocCode === socCode); - // 2) skip suppress=Y filtered = filtered.filter((r) => r.recommendSuppress !== 'Y'); - // 3) keep scaleIDs in [IM,LV] filtered = filtered.filter((r) => ['IM', 'LV'].includes(r.scaleID)); - // combine IM + LV let combined = combineIMandLV(filtered); - - // only keep items with importanceValue >=3 combined = combined.filter((item) => { return item.importanceValue !== null && item.importanceValue >= 3; }); - - // sort by importanceValue desc combined.sort((a, b) => (b.importanceValue || 0) - (a.importanceValue || 0)); setKsaForCareer(combined); @@ -195,7 +199,8 @@ function EducationalProgramsPage() { try { const fetchedSchools = await fetchSchools(cipCodes); - let userLat = null, userLng = null; + let userLat = null; + let userLng = null; if (userZip) { try { const geoResult = await clientGeocodeZip(userZip); @@ -230,6 +235,7 @@ function EducationalProgramsPage() { // Sort schools in useMemo const filteredAndSortedSchools = useMemo(() => { + if (!schools) return []; let result = [...schools]; @@ -266,12 +272,8 @@ function EducationalProgramsPage() { } else { // Sort by in-state tuition result.sort((a, b) => { - const tA = a['In_state cost'] - ? parseFloat(a['In_state cost']) - : Infinity; - const tB = b['In_state cost'] - ? parseFloat(b['In_state cost']) - : Infinity; + const tA = a['In_state cost'] ? parseFloat(a['In_state cost']) : Infinity; + const tB = b['In_state cost'] ? parseFloat(b['In_state cost']) : Infinity; return tA - tB; }); } @@ -279,54 +281,59 @@ function EducationalProgramsPage() { return result; }, [schools, inStateOnly, userState, maxTuition, maxDistance, sortBy]); - // Render the KSA as a table row with emoji + // Render a single KSA row function renderKsaRow(k, idx, careerTitle) { - // k is the object => { elementName, importanceValue, levelValue, ksa_type } - const elementName = k.elementName; - const impStars = renderImportance(k.importanceValue); - const lvlBars = k.levelValue !== null ? renderLevel(k.levelValue) : 'n/a'; - const links = getSearchLinks(elementName, careerTitle); - const definition = ONET_DEFINITIONS[elementName] || "No definition available"; - return ( - - {elementName}{' '} - - i + const elementName = k.elementName; + const impStars = renderImportance(k.importanceValue); + const lvlBars = k.levelValue !== null ? renderLevel(k.levelValue) : 'n/a'; + const isAbility = k.ksa_type === 'Ability'; + const links = !isAbility ? getSearchLinks(elementName, careerTitle) : null; + const definition = ONET_DEFINITIONS[elementName] || 'No definition available'; + + + return ( + + + {elementName}{' '} + + i - - {impStars} - {lvlBars} - - {links.map((link, i) => ( -
- - {link.title} - -
- ))} - - - ); -} + + {impStars} + {lvlBars} + {!isAbility && ( + + {links?.map((link, i) => ( +
+ + {link.title} + +
+ ))} + + )} + + ); + } - - // Knowledge / Skills / Abilities in 3 columns, each a small table + // Knowledge / Skills / Abilities in 3 columns function renderKsaSection() { if (loadingKsa) return

Loading KSA data...

; if (ksaError) return

{ksaError}

; if (!socCode) return

Please select a career to see KSA data.

; if (!ksaForCareer.length) { - return

No Knowledge, Skills, and Abilites data found for {careerTitle}

; + return

No Knowledge, Skills, and Abilities data found for {careerTitle}

; } - // Separate them const knowledge = ksaForCareer.filter((k) => k.ksa_type === 'Knowledge'); const skillRows = ksaForCareer.filter((k) => k.ksa_type === 'Skill'); const abilities = ksaForCareer.filter((k) => k.ksa_type === 'Ability'); @@ -334,10 +341,10 @@ function EducationalProgramsPage() { return (

- Knowledge, Skills, and Abilites needed for: {careerTitle || 'Unknown Career'} + Knowledge, Skills, and Abilities needed for:{' '} + {careerTitle || 'Unknown Career'}

- {/* 3-column layout */}
{/* Knowledge */}
@@ -393,6 +400,19 @@ function EducationalProgramsPage() { Ability Importance Level + + {/* Info icon for "Why no courses?" */} + + Why no courses? + + i + + + @@ -406,7 +426,7 @@ function EducationalProgramsPage() {
); - } + } // <-- MAKE SURE WE DO NOT HAVE EXTRA BRACKETS HERE // If no CIP codes => fallback if (!cipCodes.length) { @@ -438,7 +458,7 @@ function EducationalProgramsPage() { return (
- {/* KSA Section (3 columns, each a small table) */} + {/* KSA Section */} {renderKsaSection()} {/* School List */} @@ -492,40 +512,43 @@ function EducationalProgramsPage() { )}
- {filteredAndSortedSchools.length > 0 ? ( -
- {filteredAndSortedSchools.map((school, idx) => ( +
+ {filteredAndSortedSchools.map((school, idx) => { + // 1) Ensure the website has a protocol: + const displayWebsite = ensureHttp(school['Website']); + + return (
- {school['INSTNM'] || 'Unnamed School'} -

Degree Type: {school['CREDDESC'] || 'N/A'}

-

In-State Tuition: ${school['In_state cost'] || 'N/A'}

-

Out-of-State Tuition: ${school['Out_state cost'] || 'N/A'}

-

- Distance:{' '} - {school.distance !== null ? `${school.distance} mi` : 'N/A'} -

-

- Website:{' '} + {school['Website'] ? ( - {school['Website']} + {school['INSTNM'] || 'Unnamed School'} ) : ( - 'N/A' + school['INSTNM'] || 'Unnamed School' )} -

+ +

Degree Type: {school['CREDDESC'] || 'N/A'}

+

In-State Tuition: ${school['In_state cost'] || 'N/A'}

+

Out-of-State Tuition: ${school['Out_state cost'] || 'N/A'}

+

Distance: {school.distance !== null ? `${school.distance} mi` : 'N/A'}

+ +
- ))} -
- ) : ( -

No schools matching your filters.

- )} + ); + })}
+
); }