Fixed Priorities Modal Interests by adding new InterestsMeaningModal.js
This commit is contained in:
parent
112d6eec56
commit
8166a1119c
@ -161,12 +161,12 @@ app.post('/api/register', async (req, res) => {
|
|||||||
// 1) Insert into user_profile
|
// 1) Insert into user_profile
|
||||||
const profileQuery = `
|
const profileQuery = `
|
||||||
INSERT INTO user_profile
|
INSERT INTO user_profile
|
||||||
(firstname, lastname, email, zipcode, state, area, career_situation)
|
(username, firstname, lastname, email, zipcode, state, area, career_situation)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`;
|
`;
|
||||||
pool.query(
|
pool.query(
|
||||||
profileQuery,
|
profileQuery,
|
||||||
[firstname, lastname, email, zipcode, state, area, career_situation],
|
[username, firstname, lastname, email, zipcode, state, area, career_situation],
|
||||||
(errProfile, resultProfile) => {
|
(errProfile, resultProfile) => {
|
||||||
if (errProfile) {
|
if (errProfile) {
|
||||||
console.error('Error inserting user_profile:', errProfile.message);
|
console.error('Error inserting user_profile:', errProfile.message);
|
||||||
@ -203,6 +203,7 @@ app.post('/api/register', async (req, res) => {
|
|||||||
// Optionally fetch or build a user object. We already know:
|
// Optionally fetch or build a user object. We already know:
|
||||||
// firstname, lastname, email, etc. from the request body.
|
// firstname, lastname, email, etc. from the request body.
|
||||||
const userPayload = {
|
const userPayload = {
|
||||||
|
username,
|
||||||
firstname,
|
firstname,
|
||||||
lastname,
|
lastname,
|
||||||
email,
|
email,
|
||||||
@ -367,14 +368,14 @@ app.post('/api/user-profile', (req, res) => {
|
|||||||
area,
|
area,
|
||||||
careerSituation,
|
careerSituation,
|
||||||
interest_inventory_answers,
|
interest_inventory_answers,
|
||||||
riasec_scores, // new JSON with { R:15, I:22, A:..., S:..., E:..., C:... }
|
riasec: riasec_scores, // NEW
|
||||||
career_priorities,
|
career_priorities,
|
||||||
career_list,
|
career_list,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// Check if profile row exists
|
// Check if profile row exists
|
||||||
pool.query(
|
pool.query(
|
||||||
'SELECT * FROM user_profile WHERE id = ?',
|
`SELECT * FROM user_profile WHERE id = ?`,
|
||||||
[profileId],
|
[profileId],
|
||||||
(err, results) => {
|
(err, results) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -417,27 +418,18 @@ app.post('/api/user-profile', (req, res) => {
|
|||||||
? userName
|
? userName
|
||||||
: existingRow?.username || null;
|
: existingRow?.username || null;
|
||||||
|
|
||||||
// final RIASEC JSON
|
// final RIASEC scores
|
||||||
// If you passed something in "riasec_scores", store as JSON
|
const finalRiasec = req.body.riasec_scores
|
||||||
// If none passed, keep existing or null
|
? JSON.stringify(req.body.riasec_scores)
|
||||||
let finalRiasecScores;
|
: existingRow.riasec_scores || null;
|
||||||
if (riasec_scores !== undefined) {
|
|
||||||
if (riasec_scores) {
|
|
||||||
finalRiasecScores = JSON.stringify(riasec_scores);
|
|
||||||
} else {
|
|
||||||
// if it's empty or falsey, set to null or keep existing
|
|
||||||
finalRiasecScores = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
finalRiasecScores = existingRow?.riasec_scores || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingRow) {
|
if (existingRow) {
|
||||||
// Update existing row
|
// Update existing row
|
||||||
const updateQuery = `
|
const updateQuery = `
|
||||||
UPDATE user_profile
|
UPDATE user_profile
|
||||||
SET
|
SET
|
||||||
username = ?,
|
username = ?, -- NEW
|
||||||
firstname = ?,
|
firstname = ?,
|
||||||
lastname = ?,
|
lastname = ?,
|
||||||
email = ?,
|
email = ?,
|
||||||
@ -446,7 +438,7 @@ app.post('/api/user-profile', (req, res) => {
|
|||||||
area = ?,
|
area = ?,
|
||||||
career_situation = ?,
|
career_situation = ?,
|
||||||
interest_inventory_answers = ?,
|
interest_inventory_answers = ?,
|
||||||
riasec_scores = ?, -- new field
|
riasec_scores = ?, -- NEW
|
||||||
career_priorities = ?,
|
career_priorities = ?,
|
||||||
career_list = ?
|
career_list = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
@ -461,10 +453,10 @@ app.post('/api/user-profile', (req, res) => {
|
|||||||
area || existingRow.area,
|
area || existingRow.area,
|
||||||
careerSituation || existingRow.career_situation,
|
careerSituation || existingRow.career_situation,
|
||||||
finalAnswers,
|
finalAnswers,
|
||||||
finalRiasecScores, // JSON string
|
finalRiasec,
|
||||||
finalCareerPriorities,
|
finalCareerPriorities,
|
||||||
finalCareerList,
|
finalCareerList,
|
||||||
profileId
|
profileId,
|
||||||
];
|
];
|
||||||
|
|
||||||
pool.query(updateQuery, params, (err2) => {
|
pool.query(updateQuery, params, (err2) => {
|
||||||
@ -474,14 +466,16 @@ app.post('/api/user-profile', (req, res) => {
|
|||||||
.status(500)
|
.status(500)
|
||||||
.json({ error: 'Failed to update user profile' });
|
.json({ error: 'Failed to update user profile' });
|
||||||
}
|
}
|
||||||
return res.status(200).json({ message: 'User profile updated successfully' });
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json({ message: 'User profile updated successfully' });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Insert a new profile
|
// Insert a new profile
|
||||||
const insertQuery = `
|
const insertQuery = `
|
||||||
INSERT INTO user_profile
|
INSERT INTO user_profile
|
||||||
(id, username, firstname, lastname, email, zipcode, state, area,
|
(id, username, firstname, lastname, email, zipcode, state, area,
|
||||||
career_situation, interest_inventory_answers, riasec_scores,
|
career_situation, interest_inventory_answers, riasec,
|
||||||
career_priorities, career_list)
|
career_priorities, career_list)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`;
|
`;
|
||||||
@ -496,7 +490,7 @@ app.post('/api/user-profile', (req, res) => {
|
|||||||
area,
|
area,
|
||||||
careerSituation || null,
|
careerSituation || null,
|
||||||
finalAnswers,
|
finalAnswers,
|
||||||
finalRiasecScores, // JSON string or null
|
finalRiasec,
|
||||||
finalCareerPriorities,
|
finalCareerPriorities,
|
||||||
finalCareerList,
|
finalCareerList,
|
||||||
];
|
];
|
||||||
@ -508,10 +502,9 @@ app.post('/api/user-profile', (req, res) => {
|
|||||||
.status(500)
|
.status(500)
|
||||||
.json({ error: 'Failed to create user profile' });
|
.json({ error: 'Failed to create user profile' });
|
||||||
}
|
}
|
||||||
return res.status(201).json({
|
return res
|
||||||
message: 'User profile created successfully',
|
.status(201)
|
||||||
id: profileId
|
.json({ message: 'User profile created successfully', id: profileId });
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
|||||||
import CareerSuggestions from './CareerSuggestions.js';
|
import CareerSuggestions from './CareerSuggestions.js';
|
||||||
import CareerPrioritiesModal from './CareerPrioritiesModal.js';
|
import CareerPrioritiesModal from './CareerPrioritiesModal.js';
|
||||||
import CareerModal from './CareerModal.js';
|
import CareerModal from './CareerModal.js';
|
||||||
|
import InterestMeaningModal from './InterestMeaningModal.js';
|
||||||
import CareerSearch from './CareerSearch.js';
|
import CareerSearch from './CareerSearch.js';
|
||||||
import { Button } from './ui/button.js';
|
import { Button } from './ui/button.js';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@ -28,8 +29,11 @@ const STATES = [
|
|||||||
{ name: 'West Virginia', code: 'WV' }, { name: 'Wisconsin', code: 'WI' }, { name: 'Wyoming', code: 'WY' },
|
{ name: 'West Virginia', code: 'WV' }, { name: 'Wisconsin', code: 'WI' }, { name: 'Wyoming', code: 'WY' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -------------- CIP HELPER FUNCTIONS --------------
|
// -------------- CIP HELPER FUNCTIONS --------------
|
||||||
|
|
||||||
|
|
||||||
// 1) Insert leading zero if there's only 1 digit before the decimal
|
// 1) Insert leading zero if there's only 1 digit before the decimal
|
||||||
function ensureTwoDigitsBeforeDecimal(cipStr) {
|
function ensureTwoDigitsBeforeDecimal(cipStr) {
|
||||||
// e.g. "4.0201" => "04.0201"
|
// e.g. "4.0201" => "04.0201"
|
||||||
@ -66,7 +70,21 @@ function CareerExplorer() {
|
|||||||
const [userZipcode, setUserZipcode] = useState(null);
|
const [userZipcode, setUserZipcode] = useState(null);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [pendingCareerForModal, setPendingCareerForModal] = useState(null);
|
const [pendingCareerForModal, setPendingCareerForModal] = useState(null);
|
||||||
|
|
||||||
|
const [showInterestMeaningModal, setShowInterestMeaningModal] = useState(false);
|
||||||
|
const [modalData, setModalData] = useState({
|
||||||
|
career: null,
|
||||||
|
askForInterest: false,
|
||||||
|
defaultInterest: 3,
|
||||||
|
defaultMeaning: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ...
|
||||||
|
const fitRatingMap = {
|
||||||
|
Best: 5,
|
||||||
|
Great: 4,
|
||||||
|
Good: 3,
|
||||||
|
};
|
||||||
// This is where we'll hold ALL final suggestions (with job_zone merged)
|
// This is where we'll hold ALL final suggestions (with job_zone merged)
|
||||||
const [careerSuggestions, setCareerSuggestions] = useState([]);
|
const [careerSuggestions, setCareerSuggestions] = useState([]);
|
||||||
|
|
||||||
@ -560,30 +578,48 @@ function CareerExplorer() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
// Add/Remove from comparison
|
// Add/Remove from comparison
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
const addCareerToList = (career) => {
|
const addCareerToList = (career) => {
|
||||||
|
// 1) get default (pre-calculated) ratings from your JSON
|
||||||
const masterRatings = getCareerRatingsBySocCode(career.code);
|
const masterRatings = getCareerRatingsBySocCode(career.code);
|
||||||
|
|
||||||
const fitRatingMap = {
|
// 2) figure out interest
|
||||||
Best: 5,
|
const userHasInventory = priorities.interests !== "I’m not sure yet";
|
||||||
Great: 4,
|
const defaultInterestValue =
|
||||||
Good: 3,
|
userHasInventory
|
||||||
};
|
? // if user has done inventory, we rely on fit rating or fallback to .json
|
||||||
|
(fitRatingMap[career.fit] || masterRatings.interests || 3)
|
||||||
|
: // otherwise, just start them at 3 (we'll ask in the modal)
|
||||||
|
3;
|
||||||
|
|
||||||
const interestsRating =
|
// 3) always ask for meaning, start at 3
|
||||||
priorities.interests === "I’m not sure yet"
|
const defaultMeaningValue = 3;
|
||||||
? parseInt(prompt("Rate your interest in this career (1-5):", "3"), 10)
|
|
||||||
: fitRatingMap[career.fit] || masterRatings.interests || 3;
|
// 4) open the InterestMeaningModal instead of using prompt()
|
||||||
|
setModalData({
|
||||||
|
career,
|
||||||
|
masterRatings,
|
||||||
|
askForInterest: !userHasInventory,
|
||||||
|
defaultInterest: defaultInterestValue,
|
||||||
|
defaultMeaning: defaultMeaningValue,
|
||||||
|
});
|
||||||
|
setShowInterestMeaningModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalSave = ({ interest, meaning }) => {
|
||||||
|
const { career, masterRatings, askForInterest, defaultInterest } = modalData;
|
||||||
|
if (!career) return;
|
||||||
|
|
||||||
|
// If we asked for interest, use the user's input; otherwise keep the default
|
||||||
|
const finalInterest = askForInterest && interest !== null
|
||||||
|
? interest
|
||||||
|
: defaultInterest;
|
||||||
|
|
||||||
|
const finalMeaning = meaning;
|
||||||
|
|
||||||
const meaningRating = parseInt(
|
|
||||||
prompt(
|
|
||||||
"How important do you feel this job is to society or the world? (1-5):",
|
|
||||||
"3"
|
|
||||||
),
|
|
||||||
10
|
|
||||||
);
|
|
||||||
|
|
||||||
const stabilityRating =
|
const stabilityRating =
|
||||||
career.ratings && career.ratings.stability !== undefined
|
career.ratings && career.ratings.stability !== undefined
|
||||||
@ -597,8 +633,8 @@ function CareerExplorer() {
|
|||||||
const careerWithUserRatings = {
|
const careerWithUserRatings = {
|
||||||
...career,
|
...career,
|
||||||
ratings: {
|
ratings: {
|
||||||
interests: interestsRating,
|
interests: finalInterest,
|
||||||
meaning: meaningRating,
|
meaning: finalMeaning,
|
||||||
stability: stabilityRating,
|
stability: stabilityRating,
|
||||||
growth: growthRating,
|
growth: growthRating,
|
||||||
balance: balanceRating,
|
balance: balanceRating,
|
||||||
@ -925,6 +961,16 @@ function CareerExplorer() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<InterestMeaningModal
|
||||||
|
show={showInterestMeaningModal}
|
||||||
|
onClose={() => setShowInterestMeaningModal(false)}
|
||||||
|
onSave={handleModalSave}
|
||||||
|
careerTitle={modalData.career?.title || ""}
|
||||||
|
askForInterest={modalData.askForInterest}
|
||||||
|
defaultInterest={modalData.defaultInterest}
|
||||||
|
defaultMeaning={modalData.defaultMeaning}
|
||||||
|
/>
|
||||||
|
|
||||||
{selectedCareer && (
|
{selectedCareer && (
|
||||||
<CareerModal
|
<CareerModal
|
||||||
career={selectedCareer}
|
career={selectedCareer}
|
||||||
|
@ -92,6 +92,7 @@ function CareerModal({ career, careerDetails, closeModal, addCareerToList }) {
|
|||||||
stability: stabilityRating,
|
stability: stabilityRating,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
closeModal();
|
||||||
}}
|
}}
|
||||||
className="text-white bg-green-500 hover:bg-green-600 rounded px-3 py-1"
|
className="text-white bg-green-500 hover:bg-green-600 rounded px-3 py-1"
|
||||||
>
|
>
|
||||||
|
@ -10,11 +10,12 @@ const CareerPrioritiesModal = ({ userProfile, onClose }) => {
|
|||||||
}
|
}
|
||||||
}, [userProfile]);
|
}, [userProfile]);
|
||||||
|
|
||||||
|
// Updated "interests" question:
|
||||||
const questions = [
|
const questions = [
|
||||||
{
|
{
|
||||||
id: 'interests',
|
id: 'interests',
|
||||||
text: 'What kinds of activities do you naturally enjoy?',
|
text: 'How important is it that your career aligns with your personal interests?',
|
||||||
options: ['I know my interests (completed inventory)', 'I’m not sure yet'],
|
options: ['Very important', 'Somewhat important', 'Not as important'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'meaning',
|
id: 'meaning',
|
||||||
@ -61,13 +62,11 @@ const CareerPrioritiesModal = ({ userProfile, onClose }) => {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving priorities:', error);
|
console.error('Error saving priorities:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const allAnswered = questions.every(q => responses[q.id]);
|
const allAnswered = questions.every(q => responses[q.id]);
|
||||||
|
|
||||||
@ -100,7 +99,9 @@ const CareerPrioritiesModal = ({ userProfile, onClose }) => {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={!allAnswered}
|
disabled={!allAnswered}
|
||||||
className={`px-4 py-2 rounded ${allAnswered ? 'bg-blue-600 text-white' : 'bg-gray-300 cursor-not-allowed'}`}
|
className={`px-4 py-2 rounded ${
|
||||||
|
allAnswered ? 'bg-blue-600 text-white' : 'bg-gray-300 cursor-not-allowed'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
Save Answers
|
Save Answers
|
||||||
</button>
|
</button>
|
||||||
|
90
src/components/InterestMeaningModal.js
Normal file
90
src/components/InterestMeaningModal.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Button } from './ui/button.js';
|
||||||
|
|
||||||
|
// This modal will ONLY ask for Interest & Meaning.
|
||||||
|
function InterestMeaningModal({
|
||||||
|
show,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
careerTitle,
|
||||||
|
askForInterest, // boolean
|
||||||
|
defaultInterest, // number
|
||||||
|
defaultMeaning, // number
|
||||||
|
}) {
|
||||||
|
const [interestValue, setInterestValue] = useState(defaultInterest || 3);
|
||||||
|
const [meaningValue, setMeaningValue] = useState(defaultMeaning || 3);
|
||||||
|
|
||||||
|
// If `defaultInterest` or `defaultMeaning` change while open, reset local state
|
||||||
|
useEffect(() => {
|
||||||
|
setInterestValue(defaultInterest || 3);
|
||||||
|
}, [defaultInterest]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMeaningValue(defaultMeaning || 3);
|
||||||
|
}, [defaultMeaning]);
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return null; // Do not render anything if show=false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
onSave({
|
||||||
|
interest: askForInterest ? interestValue : null,
|
||||||
|
meaning: meaningValue,
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-60 bg-black bg-opacity-40 flex items-center justify-center">
|
||||||
|
<div className="bg-white p-4 rounded shadow-lg w-full max-w-sm">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">
|
||||||
|
Add "{careerTitle}" to Comparison
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* If the user is "not sure yet," ask for interest rating */}
|
||||||
|
{askForInterest && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block font-medium mb-1">
|
||||||
|
Your Interest in This Career (1–5)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="5"
|
||||||
|
value={interestValue}
|
||||||
|
onChange={(e) => setInterestValue(Number(e.target.value))}
|
||||||
|
className="w-full border rounded px-3 py-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Always ask for meaning rating */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block font-medium mb-1">
|
||||||
|
How Meaningful is This Career to You? (1–5)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="5"
|
||||||
|
value={meaningValue}
|
||||||
|
onChange={(e) => setMeaningValue(Number(e.target.value))}
|
||||||
|
className="w-full border rounded px-3 py-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button onClick={onClose} className="bg-gray-300">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSave} className="bg-blue-600 text-white">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InterestMeaningModal;
|
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user