dev1/src/components/InterestInventory.js

286 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { ClipLoader } from 'react-spinners';
import authFetch from '../utils/authFetch.js';
const InterestInventory = () => {
const [questions, setQuestions] = useState([]);
const [responses, setResponses] = useState({});
const [currentPage, setCurrentPage] = useState(1);
const [isSubmitting, setIsSubmitting] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [userProfile, setUserProfile] = useState(null);
const navigate = useNavigate();
const questionsPerPage = 6;
const totalPages = Math.ceil(questions.length / questionsPerPage) || 1;
useEffect(() => {
fetchQuestions();
fetchUserProfile();
}, []);
const fetchQuestions = async () => {
setLoading(true);
setError(null);
try {
const response = await authFetch('/api/onet/questions?start=1&end=60', {
method: 'GET',
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new Error(`Failed to fetch questions: ${response.statusText}`);
}
const data = await response.json();
if (data && Array.isArray(data.questions)) {
setQuestions(data.questions);
} else {
throw new Error('Invalid question format.');
}
} catch (err) {
setError(err.message);
console.error('Error fetching questions:', err.message);
} finally {
setLoading(false);
}
};
const fetchUserProfile = async () => {
try {
const res = await authFetch('/api/user-profile', { method: 'GET' });
if (!res || !res.ok) throw new Error('Failed to fetch user profile');
const data = await res.json();
setUserProfile(data);
} catch (err) {
console.error('Error fetching user profile:', err.message);
}
};
// Restore previously saved answers if available
useEffect(() => {
const storedAnswers = userProfile?.interest_inventory_answers;
if (questions.length === 60 && storedAnswers?.length === 60) {
const restored = {};
storedAnswers.split('').forEach((val, index) => {
restored[index + 1] = val;
});
setResponses(restored);
}
}, [questions, userProfile]);
const handleResponseChange = (questionIndex, value) => {
setResponses((prev) => ({
...prev,
[questionIndex]: value,
}));
};
const validateCurrentPage = () => {
const start = (currentPage - 1) * questionsPerPage;
const end = currentPage * questionsPerPage;
const currentQuestions = questions.slice(start, end);
const unanswered = currentQuestions.filter(
(q) => !responses[q.index] || responses[q.index] === '0'
);
if (unanswered.length > 0) {
alert('Please answer all questions before proceeding.');
return false;
}
return true;
};
const handleNextPage = () => {
if (!validateCurrentPage()) return;
setCurrentPage((prev) => prev + 1);
};
const handlePreviousPage = () => {
if (currentPage > 1) {
setCurrentPage((prev) => prev - 1);
}
};
const randomizeAnswers = () => {
const randomized = {};
questions.forEach((question) => {
randomized[question.index] = Math.floor(Math.random() * 5) + 1; // 15
});
setResponses(randomized);
};
const handleSubmit = async () => {
if (!validateCurrentPage()) return;
// Combine answers into a 60-char string
const answers = Array.from({ length: 60 }, (_, i) => responses[i + 1] || '0').join('');
// First save the answers to user profile
try {
await authFetch('/api/user-profile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
firstName: userProfile?.firstname,
lastName: userProfile?.lastname,
email: userProfile?.email,
zipCode: userProfile?.zipcode,
state: userProfile?.state,
area: userProfile?.area,
careerSituation: userProfile?.career_situation || null,
interest_inventory_answers: answers,
}),
});
} catch (err) {
console.error('Error saving answers to user profile:', err.message);
}
// Then submit to the O*Net logic
try {
setIsSubmitting(true);
setError(null);
const response = await authFetch(`${process.env.REACT_APP_API_URL}/onet/submit_answers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ answers }),
});
if (!response.ok) {
throw new Error(`Failed to submit answers: ${response.statusText}`);
}
const data = await response.json();
const { careers: careerSuggestions, riaSecScores } = data;
if (Array.isArray(careerSuggestions) && Array.isArray(riaSecScores)) {
navigate('/dashboard', { state: { careerSuggestions, riaSecScores } });
} else {
throw new Error('Invalid data format from the server.');
}
} catch (error) {
console.error('Error submitting answers:', error.message);
alert('Failed to submit answers. Please try again later.');
} finally {
setIsSubmitting(false);
}
};
// Compute which questions to show
const start = (currentPage - 1) * questionsPerPage;
const end = currentPage * questionsPerPage;
const currentQuestions = questions.slice(start, end);
// Calculate progress for the bar
const totalQuestions = 60;
const answeredCount = Object.keys(responses).filter((key) => responses[key] !== '0').length;
const progressPercent = Math.round((answeredCount / totalQuestions) * 100);
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-50 p-4">
{/* Card Container */}
<div className="w-full max-w-xl rounded bg-white p-6 shadow-md">
<h2 className="mb-4 text-center text-2xl font-semibold">
Interest Inventory
</h2>
{/* Loading & Error States */}
{loading && (
<div className="flex justify-center">
<ClipLoader size={35} color="#4A90E2" />
</div>
)}
{error && (
<p className="mb-4 rounded bg-red-50 p-2 text-sm text-red-600">
{error}
</p>
)}
{/* Progress Bar & Page Indicator */}
<div className="mb-4">
<p className="text-sm text-gray-600">
Page {currentPage} of {totalPages}
</p>
<div className="mt-2 h-2 w-full overflow-hidden rounded bg-gray-200">
<div
className="h-full bg-blue-600 transition-all"
style={{ width: `${progressPercent}%` }}
/>
</div>
<p className="mt-1 text-right text-xs text-gray-500">
{answeredCount} / {totalQuestions} answered
</p>
</div>
{/* Questions */}
<div className="space-y-4">
{currentQuestions.map((question) => (
<div key={question.index} className="flex flex-col">
<label className="mb-1 font-medium text-gray-700">
{question.text}
</label>
<select
className="rounded border border-gray-300 px-2 py-1 text-sm focus:border-blue-500 focus:outline-none"
onChange={(e) => handleResponseChange(question.index, e.target.value)}
value={responses[question.index] || '0'}
>
<option value="0">Select an option</option>
<option value="1">Strongly Dislike</option>
<option value="2">Dislike</option>
<option value="3">Neutral</option>
<option value="4">Like</option>
<option value="5">Strongly Like</option>
</select>
</div>
))}
</div>
{/* Pagination / Action Buttons */}
<div className="mt-6 flex flex-wrap items-center justify-between space-y-2 sm:space-y-0">
<div className="flex space-x-2">
{currentPage > 1 && (
<button
type="button"
onClick={handlePreviousPage}
className="rounded bg-gray-300 px-4 py-2 text-gray-700 hover:bg-gray-400"
>
Previous
</button>
)}
{currentPage < totalPages ? (
<button
type="button"
onClick={handleNextPage}
className="rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
Next
</button>
) : (
<button
type="button"
onClick={handleSubmit}
disabled={isSubmitting}
className="rounded bg-green-600 px-4 py-2 text-white hover:bg-green-700 disabled:bg-green-300"
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
)}
</div>
<button
type="button"
onClick={randomizeAnswers}
disabled={isSubmitting}
className="rounded bg-orange-500 px-4 py-2 text-white hover:bg-orange-600 disabled:bg-orange-300"
>
Randomize Answers
</button>
</div>
</div>
</div>
);
};
export default InterestInventory;