UX issues, schools issues
This commit is contained in:
parent
e07dc32b51
commit
9696943f9b
@ -171,7 +171,7 @@ app.post('/api/login', (req, res) => {
|
||||
}
|
||||
|
||||
// Generate JWT
|
||||
const token = jwt.sign({ userId: row.user_id }, SECRET_KEY, { expiresIn: '1h' });
|
||||
const token = jwt.sign({ userId: row.user_id }, SECRET_KEY, { expiresIn: '2h' });
|
||||
res.status(200).json({ token });
|
||||
});
|
||||
});
|
||||
|
@ -346,10 +346,16 @@ app.get('/api/cip/:socCode', (req, res) => {
|
||||
res.status(404).json({ error: 'CIP code not found for this SOC code' });
|
||||
});
|
||||
|
||||
app.get('/api/CIP_institution_mapping_fixed.json', (req, res) => {
|
||||
const filePath = path.join(__dirname, 'CIP_institution_mapping_fixed.json'); // Adjust the path if needed
|
||||
res.sendFile(filePath);
|
||||
});
|
||||
// Filtered schools endpoint
|
||||
app.get('/api/schools', (req, res) => {
|
||||
const { cipCode, state, level, type } = req.query;
|
||||
const filePath = path.join(__dirname, 'CIP_institution_mapping_fixed.json');
|
||||
|
||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error('Error reading file:', err);
|
||||
return res.status(500).json({ error: 'Failed to load data' });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
26
src/App.css
26
src/App.css
@ -1,18 +1,25 @@
|
||||
/* src/App.css */
|
||||
|
||||
/* Body styling for the entire application */
|
||||
body {
|
||||
background-color: #e9ecef; /* Light gray-blue background */
|
||||
/* General body and root styling for full-page coverage */
|
||||
body, #root {
|
||||
background-color: #f4f7fb; /* Light gray-blue background */
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh; /* Full viewport height */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Main container to center and constrain content */
|
||||
/* Main App container for consistent centering */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background-color: #ffffff; /* White background for sections */
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Main App styling */
|
||||
@ -110,3 +117,14 @@ input, select {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.dashboard {
|
||||
grid-template-columns: 1fr; /* Stack vertically on smaller screens */
|
||||
}
|
||||
|
||||
.riasec-scores, .riasec-descriptions {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ export function CareerSuggestions({ careerSuggestions = [], onCareerClick }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>Career Suggestions</h3>
|
||||
<h2>Career Suggestions</h2>
|
||||
<ul>
|
||||
{careerSuggestions.map((career, index) => {
|
||||
return (
|
||||
|
@ -3,8 +3,8 @@
|
||||
.dashboard {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); /* Ensure responsive layout */
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
background-color: #f4f7fa;
|
||||
}
|
||||
|
||||
@ -108,3 +108,26 @@ h2 {
|
||||
.career-item:hover {
|
||||
background-color: #e6f7ff; /* Lighter blue when hovering */
|
||||
}
|
||||
|
||||
|
||||
.riasec-descriptions {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
background-color: #f1f8ff;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.riasec-descriptions ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.riasec-descriptions li {
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.riasec-descriptions strong {
|
||||
color: #007bff;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ function Dashboard() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [careerSuggestions, setCareerSuggestions] = useState([]);
|
||||
const [careerDetails, setCareerDetails] = useState(null);
|
||||
const [riaSecScores, setRiaSecScores] = useState([]);
|
||||
const [selectedCareer, setSelectedCareer] = useState(null);
|
||||
const [schools, setSchools] = useState([]);
|
||||
@ -26,13 +27,18 @@ function Dashboard() {
|
||||
const [error, setError] = useState(null);
|
||||
const [userState, setUserState] = useState(null);
|
||||
const [areaTitle, setAreaTitle] = useState(null);
|
||||
const [riaSecDescriptions, setRiaSecDescriptions] = useState([]);
|
||||
|
||||
const apiUrl = process.env.REACT_APP_API_URL;
|
||||
|
||||
useEffect(() => {
|
||||
let descriptions = []; // Declare outside for scope accessibility
|
||||
if (location.state) {
|
||||
const { careerSuggestions: suggestions, riaSecScores: scores } = location.state || {};
|
||||
descriptions = scores.map((score) => score.description || "No description available.");
|
||||
setCareerSuggestions(suggestions || []);
|
||||
setRiaSecScores(scores || []);
|
||||
setRiaSecDescriptions(descriptions); // Set descriptions
|
||||
} else {
|
||||
console.warn('No data found, redirecting to Interest Inventory');
|
||||
navigate('/interest-inventory');
|
||||
@ -68,7 +74,16 @@ function Dashboard() {
|
||||
|
||||
const handleCareerClick = useCallback(
|
||||
async (career) => {
|
||||
const socCode = career.code;
|
||||
const socCode = career.code; // Extract SOC code from career object
|
||||
setSelectedCareer(career); // Set career first to trigger loading panel
|
||||
setLoading(true); // Enable loading state only when career is clicked
|
||||
setError(null); // Clear previous errors
|
||||
setCareerDetails({}); // Reset career details to avoid undefined errors
|
||||
setSchools([]);
|
||||
setSalaryData([]);
|
||||
setEconomicProjections({});
|
||||
setTuitionData([]);
|
||||
|
||||
if (!socCode) {
|
||||
console.error('SOC Code is missing');
|
||||
setError('SOC Code is missing');
|
||||
@ -76,35 +91,25 @@ function Dashboard() {
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Step 1: Fetch CIP Code
|
||||
const cipResponse = await fetch(`${apiUrl}/api/cip/${socCode}`);
|
||||
|
||||
if (!cipResponse.ok) {
|
||||
console.error('Failed to fetch CIP Code');
|
||||
setError('Failed to fetch CIP Code');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cipResponse.ok) throw new Error('Failed to fetch CIP Code');
|
||||
const { cipCode } = await cipResponse.json();
|
||||
if (!cipCode) throw new Error('Failed to fetch CIP Code');
|
||||
|
||||
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
|
||||
|
||||
const filteredSchools = await fetchSchools(cleanedCipCode, userState);
|
||||
const cleanedSocCode = socCode.split('.')[0];
|
||||
|
||||
const economicResponse = await axios.get(`http://localhost:5001/api/projections/${cleanedSocCode}`);
|
||||
const tuitionResponse = await axios.get(`http://localhost:5001/api/tuition/${cleanedCipCode}`, {
|
||||
// Step 2: Fetch Data in Parallel
|
||||
const [filteredSchools, economicResponse, tuitionResponse, salaryResponse] = await Promise.all([
|
||||
fetchSchools(cleanedCipCode, userState),
|
||||
axios.get(`http://localhost:5001/api/projections/${socCode.split('.')[0]}`),
|
||||
axios.get(`http://localhost:5001/api/tuition/${cleanedCipCode}`, {
|
||||
params: { state: userState },
|
||||
});
|
||||
|
||||
console.log('Salary Data Request Params:', { socCode: cleanedSocCode, area: areaTitle });
|
||||
const salaryResponse = await axios.get(`http://localhost:5001/api/salary`, {
|
||||
params: { socCode: cleanedSocCode, area: areaTitle },
|
||||
});
|
||||
}),
|
||||
axios.get(`http://localhost:5001/api/salary`, {
|
||||
params: { socCode: socCode.split('.')[0], area: areaTitle },
|
||||
}),
|
||||
]);
|
||||
|
||||
// Step 3: Format Salary Data
|
||||
const salaryDataPoints = [
|
||||
{ percentile: '10th Percentile', value: salaryResponse.data.A_PCT10 || 0 },
|
||||
{ percentile: '25th Percentile', value: salaryResponse.data.A_PCT25 || 0 },
|
||||
@ -113,11 +118,14 @@ function Dashboard() {
|
||||
{ percentile: '90th Percentile', value: salaryResponse.data.A_PCT90 || 0 },
|
||||
];
|
||||
|
||||
setSelectedCareer(career);
|
||||
setSchools(filteredSchools);
|
||||
setEconomicProjections(economicResponse.data);
|
||||
setTuitionData(tuitionResponse.data);
|
||||
setSalaryData(salaryDataPoints);
|
||||
// Step 4: Consolidate Career Details
|
||||
setCareerDetails({
|
||||
...career,
|
||||
economicProjections: economicResponse.data,
|
||||
salaryData: salaryDataPoints,
|
||||
schools: filteredSchools,
|
||||
tuitionData: tuitionResponse.data,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error processing career click:', error.message);
|
||||
setError('Failed to load data');
|
||||
@ -144,25 +152,43 @@ function Dashboard() {
|
||||
return (
|
||||
<div className="dashboard">
|
||||
<div className="career-suggestions-container">
|
||||
<h2>Career Suggestions</h2>
|
||||
<CareerSuggestions careerSuggestions={careerSuggestions} onCareerClick={handleCareerClick} />
|
||||
</div>
|
||||
|
||||
{/* Right RIASEC Chart + Descriptions */}
|
||||
<div className="riasec-container">
|
||||
<div className="riasec-scores">
|
||||
<h2>RIASEC Scores</h2>
|
||||
<Bar data={chartData} />
|
||||
</div>
|
||||
|
||||
<div className="riasec-descriptions">
|
||||
<h3>RIASEC Personality Descriptions</h3>
|
||||
{riaSecDescriptions.length > 0 ? (
|
||||
<ul>
|
||||
{riaSecDescriptions.map((desc, index) => (
|
||||
<li key={index}>
|
||||
<strong>{riaSecScores[index]?.area}:</strong> {desc}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>Loading descriptions...</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedCareer && (
|
||||
<PopoutPanel
|
||||
career={selectedCareer}
|
||||
schools={schools || []}
|
||||
salaryData={salaryData || []}
|
||||
economicProjections={economicProjections || {}}
|
||||
tuitionData={tuitionData || {}}
|
||||
data={careerDetails}
|
||||
schools={schools}
|
||||
salaryData={salaryData}
|
||||
economicProjections={economicProjections}
|
||||
tuitionData={tuitionData}
|
||||
closePanel={() => setSelectedCareer(null)}
|
||||
loading={loading}
|
||||
error={error}
|
||||
userState={userState}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,78 +1,121 @@
|
||||
/* src/components/InterestInventory.css */
|
||||
|
||||
/* Container Styling */
|
||||
.interest-inventory-container {
|
||||
max-width: 600px;
|
||||
margin: 40px auto; /* Center the container */
|
||||
padding: 20px;
|
||||
background-color: #8d98f8; /* White background */
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid #060c02;
|
||||
}
|
||||
background-color: #f0f8ff; /* Sky-white background */
|
||||
border-radius: 12px; /* Rounded borders */
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Subtle shadow effect */
|
||||
border: 4px solid #87ceeb; /* Sky-blue trim */
|
||||
}
|
||||
|
||||
.interest-inventory-container h2 {
|
||||
color: #0e0202;
|
||||
/* Header Styling */
|
||||
.interest-inventory-container h2 {
|
||||
color: #0056b3; /* Dark blue header */
|
||||
text-align: center;
|
||||
font-size: 28px; /* Larger font size for emphasis */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.question {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Label styling for each question */
|
||||
.question label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Question Container */
|
||||
.question {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Label Styling */
|
||||
.question label {
|
||||
display: block;
|
||||
font-size: 18px; /* Slightly larger text for questions */
|
||||
font-weight: 600; /* Semi-bold text */
|
||||
margin-bottom: 8px;
|
||||
color: #0f0101;
|
||||
}
|
||||
color: #333; /* Darker text for readability */
|
||||
}
|
||||
|
||||
/* Set fixed width and max-width for dropdowns within InterestInventory */
|
||||
.question select {
|
||||
width: auto; /* Allow it to adjust based on content */
|
||||
max-width: 300px; /* Set a maximum width to prevent stretching */
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
/* Dropdown Styling */
|
||||
.question select {
|
||||
width: 80%; /* Use percentage for responsiveness */
|
||||
max-width: 400px; /* Limit maximum size */
|
||||
padding: 10px;
|
||||
border: 1px solid #87ceeb; /* Sky-blue border */
|
||||
border-radius: 8px; /* Rounded edges */
|
||||
font-size: 16px;
|
||||
}
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Subtle shadow */
|
||||
transition: border-color 0.3s ease;
|
||||
display: block;
|
||||
margin: 0 auto; /* Center the dropdown */
|
||||
}
|
||||
|
||||
/* Button styling for a uniform appearance */
|
||||
button {
|
||||
margin-top: 20px;
|
||||
/* Dropdown Hover Effect */
|
||||
.question select:hover {
|
||||
border-color: #007bff; /* Brighter blue on hover */
|
||||
}
|
||||
|
||||
/* Dropdown Focus Effect */
|
||||
.question select:focus {
|
||||
border-color: #0056b3;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
button {
|
||||
margin: 10px 5px; /* Add spacing between buttons */
|
||||
padding: 10px 20px;
|
||||
background-color: #007bff;
|
||||
background-color: #007bff; /* Blue button */
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px; /* Rounded edges */
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
/* Hover Effect for Buttons */
|
||||
button:hover {
|
||||
background-color: #0056b3; /* Darker blue */
|
||||
}
|
||||
|
||||
/* Submission message styling */
|
||||
.submit-message {
|
||||
color: #28a745;
|
||||
/* Submit Button Styling */
|
||||
button:disabled {
|
||||
background-color: #a0aec0;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Submission Message Styling */
|
||||
.submit-message {
|
||||
color: #28a745; /* Green success text */
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.validation-error-container {
|
||||
margin-top: 10px; /* Position the error message below the questions */
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
/* Validation Error Message */
|
||||
.validation-error {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.unanswered {
|
||||
/* Highlight Unanswered Questions */
|
||||
.unanswered {
|
||||
border: 2px solid #b22222; /* Darker red (Firebrick) */
|
||||
background-color: #ee6161; /* Light red background remains unchanged */
|
||||
}
|
||||
background-color: #ffe6e6; /* Light red background */
|
||||
}
|
||||
|
||||
/* Pagination Buttons Styling */
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Randomize Answers Button */
|
||||
.randomize-button {
|
||||
background-color: #17a2b8; /* Teal button */
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.randomize-button:hover {
|
||||
background-color: #138496; /* Darker teal */
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ClipLoader } from 'react-spinners';
|
||||
import './InterestInventory.css';
|
||||
|
||||
const InterestInventory = () => {
|
||||
@ -9,9 +10,14 @@ const InterestInventory = () => {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const questionsPerPage = 6;
|
||||
const navigate = useNavigate();
|
||||
const [careerSuggestions, setCareerSuggestions] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
|
||||
const fetchQuestions = async () => {
|
||||
setLoading(true); // Start loading
|
||||
setError(null); // Reset error state
|
||||
|
||||
const baseUrl = process.env.REACT_APP_API_URL || 'http://localhost:5001'; // Default to localhost:5001 for development
|
||||
const url = `${baseUrl}/api/onet/questions?start=1&end=60`; // Make sure the endpoint is correctly appended to the base URL
|
||||
|
||||
@ -27,17 +33,17 @@ const InterestInventory = () => {
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Adjusted to properly access the questions array
|
||||
if (data && Array.isArray(data.questions)) {
|
||||
setQuestions(data.questions);
|
||||
console.log("Questions fetched:", data.questions);
|
||||
} else {
|
||||
console.error("Invalid question format:", data);
|
||||
setQuestions([]);
|
||||
throw new Error("Invalid question format.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching questions:", error.message);
|
||||
alert("Failed to load questions. Please try again later.");
|
||||
setError(error.message); // Set error message
|
||||
} finally {
|
||||
setLoading(false); // Stop loading
|
||||
}
|
||||
};
|
||||
|
||||
@ -94,6 +100,7 @@ const InterestInventory = () => {
|
||||
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
setError(null); // Clear previous errors
|
||||
const baseUrl = process.env.NODE_ENV === 'production'
|
||||
? '/api/onet/submit_answers' // In production, this is proxied by Nginx to server2 (port 5001)
|
||||
: 'http://localhost:5001/api/onet/submit_answers'; // In development, server2 runs on port 5001
|
||||
@ -135,6 +142,9 @@ const InterestInventory = () => {
|
||||
return (
|
||||
<div className="interest-inventory-container">
|
||||
<h2>Interest Inventory</h2>
|
||||
{loading && <ClipLoader size={35} color="#4A90E2" />}
|
||||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
||||
|
||||
{questions.length > 0 ? (
|
||||
<div className="questions-container">
|
||||
{currentQuestions.map((question) => (
|
||||
|
@ -1,25 +1,17 @@
|
||||
// PopoutPanel.js
|
||||
import ClipLoader from 'react-spinners/ClipLoader.js';
|
||||
import { ClipLoader } from 'react-spinners';
|
||||
import LoanRepayment from './LoanRepayment.js';
|
||||
import './PopoutPanel.css';
|
||||
|
||||
function PopoutPanel({
|
||||
career = {},
|
||||
schools = [],
|
||||
salaryData = {},
|
||||
economicProjections = {},
|
||||
tuitionData = {},
|
||||
data = {},
|
||||
userState = 'N/A', // Passed explicitly from Dashboard
|
||||
loading = false,
|
||||
error = null,
|
||||
closePanel
|
||||
}) {
|
||||
console.log('PopoutPanel Props:', { career, schools, salaryData, economicProjections, tuitionData, loading, error });
|
||||
|
||||
// Validation Checks
|
||||
const isValidCareer = career && career.title && career.code;
|
||||
const isValidSchools = Array.isArray(schools) && schools.length > 0;
|
||||
const isValidSalaryData = salaryData && Object.keys(salaryData).length > 0;
|
||||
const isValidProjections = economicProjections && Object.keys(economicProjections).length > 0;
|
||||
console.log('PopoutPanel Props:', { data, loading, error, userState });
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@ -41,43 +33,86 @@ function PopoutPanel({
|
||||
);
|
||||
}
|
||||
|
||||
if (!career) {
|
||||
// Handle empty data gracefully
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
return (
|
||||
<div className="popout-panel">
|
||||
<button className="close-btn" onClick={closePanel}>X</button>
|
||||
<h2>No Career Selected</h2>
|
||||
<button onClick={closePanel}>Close</button>
|
||||
<h2>No Career Data Available</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle empty data gracefully
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
return (
|
||||
<div className="popout-panel">
|
||||
<button onClick={closePanel}>Close</button>
|
||||
<h2>No Career Data Available</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Safely access nested data with fallbacks
|
||||
const {
|
||||
title = 'Career Details',
|
||||
description = 'No description available.',
|
||||
economicProjections = {},
|
||||
salaryData = [],
|
||||
schools = [],
|
||||
tuitionData = []
|
||||
} = data;
|
||||
|
||||
const tenthPercentileSalary = salaryData?.find(
|
||||
(point) => point.percentile === '10th Percentile'
|
||||
)?.value || 0;
|
||||
|
||||
const getProgramLength = (degreeType) => {
|
||||
if (degreeType?.includes("Associate")) return 2;
|
||||
if (degreeType?.includes("Bachelor")) return 4;
|
||||
if (degreeType?.includes("Master")) return 6;
|
||||
if (degreeType?.includes("Doctoral") || degreeType?.includes("First Professional")) return 8;
|
||||
if (degreeType?.includes("Certificate")) return 1;
|
||||
return 4; // Default to 4 years if unspecified
|
||||
};
|
||||
|
||||
console.log('PopoutPanel Props:', { data, schools, salaryData, economicProjections, tuitionData, loading, error, userState });
|
||||
return (
|
||||
<div className="popout-panel">
|
||||
<button className="close-btn" onClick={closePanel}>X</button>
|
||||
<h2>{career?.title || 'Career Details'}</h2>
|
||||
<button onClick={closePanel}>Close</button>
|
||||
<h2>{data.title || 'Career Details'}</h2>
|
||||
<p>Description: {data.description || 'No description available.'}</p>
|
||||
|
||||
{/* Schools Offering Programs */}
|
||||
<h3>Schools Offering Programs</h3>
|
||||
{Array.isArray(schools) && schools.length > 0 ? (
|
||||
<ul>
|
||||
{schools.map((school, index) => (
|
||||
{schools.map((school, index) => {
|
||||
const matchingTuitionData = tuitionData.find(
|
||||
(tuition) =>
|
||||
tuition['school.name']?.toLowerCase().trim() ===
|
||||
school['Institution Name']?.toLowerCase().trim()
|
||||
);
|
||||
|
||||
return (
|
||||
<li key={index}>
|
||||
<strong>{school['Institution Name']}</strong> <br />
|
||||
In-State Tuition: ${school.inStateTuition || 'N/A'} <br />
|
||||
Out-of-State Tuition: ${school.outOfStateTuition || 'N/A'}
|
||||
<strong>{school['Institution Name']}</strong>
|
||||
<br />
|
||||
Degree Type: {school['CREDDESC'] || 'N/A'}
|
||||
<br />
|
||||
In-State Tuition: ${matchingTuitionData?.['latest.cost.tuition.in_state'] || 'N/A'}
|
||||
<br />
|
||||
Out-of-State Tuition: ${matchingTuitionData?.['latest.cost.tuition.out_of_state'] || 'N/A'}
|
||||
</li>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<p>No schools available.</p>
|
||||
)}
|
||||
|
||||
{/* Economic Projections */}
|
||||
<h3>Economic Projections</h3>
|
||||
<h3>Economic Projections for {userState}</h3>
|
||||
{economicProjections && typeof economicProjections === 'object' ? (
|
||||
<ul>
|
||||
<li>2022 Employment: {economicProjections['2022 Employment'] || 'N/A'}</li>
|
||||
@ -117,8 +152,24 @@ function PopoutPanel({
|
||||
{tenthPercentileSalary > 0 && (
|
||||
<LoanRepayment
|
||||
tuitionCosts={{
|
||||
inState: schools.map((s) => parseFloat(s.inStateTuition) || 0),
|
||||
outOfState: schools.map((s) => parseFloat(s.outOfStateTuition) || 0),
|
||||
inState: schools.map((school) => {
|
||||
const matchingTuitionData = tuitionData.find(
|
||||
(tuition) =>
|
||||
tuition['school.name']?.toLowerCase().trim() ===
|
||||
school['Institution Name']?.toLowerCase().trim()
|
||||
);
|
||||
const years = getProgramLength(school['CREDDESC']);
|
||||
return parseFloat(matchingTuitionData?.['latest.cost.tuition_in_state'] * years) || 0;
|
||||
}),
|
||||
outOfState: schools.map((school) => {
|
||||
const matchingTuitionData = tuitionData.find(
|
||||
(tuition) =>
|
||||
tuition['school.name']?.toLowerCase().trim() ===
|
||||
school['Institution Name']?.toLowerCase().trim()
|
||||
);
|
||||
const years = getProgramLength(school['CREDDESC']);
|
||||
return parseFloat(matchingTuitionData?.['latest.cost.tuition_out_of_state'] * years) || 0;
|
||||
}),
|
||||
}}
|
||||
salaryData={[{ percentile: '10th Percentile', value: tenthPercentileSalary, growthRate: 0.03 }]}
|
||||
earningHorizon={10}
|
||||
@ -129,3 +180,4 @@ function PopoutPanel({
|
||||
}
|
||||
|
||||
export default PopoutPanel;
|
||||
|
||||
|
@ -8,30 +8,36 @@
|
||||
}
|
||||
|
||||
.signin-form {
|
||||
width: 300px;
|
||||
width: 100%; /* Make the form responsive */
|
||||
max-width: 350px; /* Set maximum width for the form */
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
border-radius: 8px; /* Optional rounded corners */
|
||||
}
|
||||
|
||||
.signin-input {
|
||||
width: 100%;
|
||||
.signin-input {
|
||||
width: 90%; /* Adjust input width to leave margin */
|
||||
max-width: 280px; /* Optional max width */
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
margin: 10px auto; /* Center input fields with auto margin */
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
display: block; /* Ensures inputs take full width of their container */
|
||||
}
|
||||
|
||||
.signin-button {
|
||||
width: 100%;
|
||||
.signin-button {
|
||||
width: 90%; /* Match input width */
|
||||
max-width: 280px; /* Optional max width */
|
||||
padding: 10px;
|
||||
background-color: #4CAF50;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
margin-top: 10px; /* Add space above the button */
|
||||
}
|
||||
|
||||
.signin-button:hover {
|
||||
background-color: #45a049;
|
||||
|
@ -1,23 +1,23 @@
|
||||
// components/SignIn.js
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import './SignIn.css';
|
||||
|
||||
function SignIn({ setIsAuthenticated }) {
|
||||
console.log('SignIn component loaded');
|
||||
console.log('Rendering SignIn component');
|
||||
console.log('Authentication state in SignIn:', setIsAuthenticated);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const usernameRef = useRef('');
|
||||
const passwordRef = useRef('');
|
||||
const [error, setError] = useState(''); // Still needs state for UI updates
|
||||
|
||||
const handleSignIn = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
setError('');
|
||||
|
||||
const username = usernameRef.current.value;
|
||||
const password = passwordRef.current.value;
|
||||
|
||||
if (!username || !password) {
|
||||
setError('Please enter both username and password');
|
||||
return;
|
||||
@ -48,7 +48,6 @@ function SignIn({ setIsAuthenticated }) {
|
||||
|
||||
// Call setIsAuthenticated(true) to update the state
|
||||
setIsAuthenticated(true);
|
||||
|
||||
navigate('/getting-started'); // Redirect to GettingStarted after SignIn
|
||||
} catch (error) {
|
||||
console.error('Sign-In Error:', error.message);
|
||||
@ -56,7 +55,6 @@ function SignIn({ setIsAuthenticated }) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="signin-container">
|
||||
<div className="signin-form">
|
||||
@ -66,22 +64,20 @@ function SignIn({ setIsAuthenticated }) {
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
ref={usernameRef} // Use ref instead of state
|
||||
className="signin-input"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
ref={passwordRef} // Use ref instead of state
|
||||
className="signin-input"
|
||||
/>
|
||||
<button type="submit" className="signin-button">
|
||||
Sign In
|
||||
</button>
|
||||
</form>
|
||||
<p>Don’t have an account? <Link to="/signup">Sign Up</Link></p> {/* Sign-Up Link */}
|
||||
<p>Don’t have an account? <Link to="/signup">Sign Up</Link></p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -19,22 +19,21 @@ export const fetchAreasByState = async (state) => {
|
||||
};
|
||||
|
||||
|
||||
//fetch schools
|
||||
export const fetchSchools = async (cipCode, userState) => {
|
||||
// Fetch schools
|
||||
export const fetchSchools = async (cipCode, state = '', level = '', type = '') => {
|
||||
try {
|
||||
const response = await axios.get('http://127.0.0.1:5001/api/CIP_institution_mapping_fixed.json');
|
||||
const schoolsData = response.data;
|
||||
const response = await axios.get('http://127.0.0.1:5001/api/schools', {
|
||||
params: {
|
||||
cipCode,
|
||||
state,
|
||||
level,
|
||||
type,
|
||||
},
|
||||
});
|
||||
|
||||
const cleanedCipCode = cipCode.replace('.', '').slice(0, 4);
|
||||
return schoolsData.filter(
|
||||
(school) =>
|
||||
typeof school['CIP Code'] === 'string' &&
|
||||
school['CIP Code'].replace('.', '') === cleanedCipCode &&
|
||||
school['State'] === userState
|
||||
);
|
||||
return response.data; // Return filtered data
|
||||
} catch (error) {
|
||||
console.error('Error fetching schools:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user