// pages/evaluation.tsx "use client"; import { useState, useEffect, use } from "react"; import { useParams, useRouter } from "next/navigation"; import { axiosInstance } from "@/services/axios"; // ---------------------------------------------------------------- // Chart.js and react-chartjs-2 imports for the analysis chart. // ---------------------------------------------------------------- import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, } from "chart.js"; import { Bar } from "react-chartjs-2"; ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); // ---------------------------------------------------------------- // Shadcn/ui components – used here for Card and Button styling. // ---------------------------------------------------------------- import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; // ---------------------------------------------------------------- // Steps Definition – each step represents a cluster evaluation. // ---------------------------------------------------------------- const steps = [ { type: "intrensic", questionEndpoint: "/question/Intrinsic_value", submissionEndpoint: "/intrinsic", title: "System Quality", }, { type: "business", questionEndpoint: "/question/Business_value", submissionEndpoint: "/business", title: "Data/Info Quality", }, { type: "cost", questionEndpoint: "/question/Cost_value", submissionEndpoint: "/cost", title: "Cost Matrix", }, { type: "economic", questionEndpoint: "/question/ASGC_value", submissionEndpoint: "/ASGC", title: "ACGS Matrix", }, ]; // ---------------------------------------------------------------- // Type Definitions // ---------------------------------------------------------------- interface Question { _id: string; question_no: string; question_detail: string; type: string; answer_type: string; created_at: string; updated_at: string | null; answers: { [key: string]: number }; is_active: boolean; created_by: string; } interface UserInfo { _id: string; userId: string; type: string; name: string; org_name: string; created_at: string; updated_at: string | null; cluster: { [key: string]: boolean }; deltas: { [key: string]: number }; is_active: boolean; } interface ClusterResponse { cluster: string; title: string; answers: { [key: string]: number }; questionDetail: string; answersList: { [key: string]: number }; } // ---------------------------------------------------------------- // Sidebar Component – used on both evaluation and final analysis screens. // Hidden on mobile (below sm breakpoint) // ---------------------------------------------------------------- const Sidebar = ({ userInfo, currentStep, }: { userInfo: UserInfo; currentStep: number; }) => { return ( ); }; // ---------------------------------------------------------------- // Helper: Returns a background class for the score box based on score value. // ---------------------------------------------------------------- const getScoreBg = (scoreStr: string) => { // Map scores 1-5 to harmonious background colors. const scoreBgColors: { [key: string]: string } = { "1": "bg-red-100", "2": "bg-orange-100", "3": "bg-yellow-100", "4": "bg-blue-100", "5": "bg-green-100", }; return scoreBgColors[scoreStr] || "bg-white"; }; // ---------------------------------------------------------------- // Main EvaluationPage Component // ---------------------------------------------------------------- const EvaluationPage = () => { const { id } = useParams(); // from URL const router = useRouter(); const [userInfo, setUserInfo] = useState(null); const [question, setQuestion] = useState(null); // answers maps each answer key to its assigned score (as a string). const [answers, setAnswers] = useState<{ [key: string]: string }>({}); const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); // currentStep ranges from 0 to steps.length (if completed, show analysis) const [currentStep, setCurrentStep] = useState(0); // To store responses for analytics. const [clusterResponses, setClusterResponses] = useState([]); // Flag to ensure auto-assignment runs only once per question. const [autoAssigned, setAutoAssigned] = useState(false); // Used to trigger a pop effect on cards as they appear. const [autoPopCards, setAutoPopCards] = useState([]); // visibleCards controls which answer cards are visible in the grid. const [visibleCards, setVisibleCards] = useState([]); // ---------------------------------------------------------------- // 1. Fetch User Info // ---------------------------------------------------------------- useEffect(() => { if (!id) return; const fetchUserInfo = async () => { try { setLoading(true); const { data: responseData } = await axiosInstance.post( `/userInfo?userId=${id}` ); if (responseData.data) { setUserInfo(responseData.data); } } catch (error) { console.error("Error fetching user info:", error); } finally { setLoading(false); } }; fetchUserInfo(); }, [id]); // ---------------------------------------------------------------- // 2. Fetch Question for Current Step and Initialize Answers // ---------------------------------------------------------------- useEffect(() => { if (!id || currentStep >= steps.length) return; const fetchQuestion = async () => { try { setLoading(true); const endpoint = steps[currentStep].questionEndpoint; const { data: questionData } = await axiosInstance.post(endpoint, {}); if ( questionData && Array.isArray(questionData) && questionData.length > 0 ) { const fetchedQuestion = questionData[0]; setQuestion(fetchedQuestion); // Initialize all answer keys as "pool" const initialAnswers: { [key: string]: string } = {}; Object.keys(fetchedQuestion.answers).forEach((key) => { initialAnswers[key] = "pool"; }); setAnswers(initialAnswers); setAutoAssigned(false); setVisibleCards([]); // reset visible cards for new question } } catch (error) { console.error("Error fetching question:", error); } finally { setLoading(false); } }; fetchQuestion(); }, [currentStep, id]); // ---------------------------------------------------------------- // 3. Auto-assign random score to each answer card one by one with pop effect. // The card becomes visible only when assigned. // ---------------------------------------------------------------- useEffect(() => { if ( question && !autoAssigned && Object.values(answers).every((val) => val === "pool") ) { const keys = Object.keys(answers); keys.forEach((key, index) => { setTimeout(() => { const randomScore = (Math.floor(Math.random() * 5) + 1).toString(); setAnswers((prev) => ({ ...prev, [key]: randomScore, })); setAutoPopCards((prev) => [...prev, key]); // Remove the pop effect after 200ms. setTimeout(() => { setAutoPopCards((prev) => prev.filter((id) => id !== key)); }, 200); // Mark card as visible. setVisibleCards((prev) => [...prev, key]); if (index === keys.length - 1) { setAutoAssigned(true); } }, index * 200); }); } }, [question, autoAssigned, answers]); // ---------------------------------------------------------------- // 4. Handle Next Button Click: Validate and Submit // ---------------------------------------------------------------- const handleNext = async () => { if (!id || !question) return; if (Object.values(answers).some((val) => val === "pool")) { alert("Please wait until all answers are assigned."); return; } // if(userInfo?.cluster[steps[currentStep].type]){ // setCurrentStep(currentStep + 1); // setQuestion(null); // setAnswers({}); // } try { setSubmitting(true); const payloadAnswers: { [key: string]: number } = {}; Object.entries(answers).forEach(([key, value]) => { payloadAnswers[key] = parseInt(value, 10); }); const payload = { type: steps[currentStep].type, userId: id, answer: payloadAnswers, delta: 0, value: {}, }; const { data: submissionResponse } = await axiosInstance.post( steps[currentStep].submissionEndpoint, payload ); console.log("Submission response:", submissionResponse); setClusterResponses((prev) => [ ...prev, { cluster: steps[currentStep].type, title: steps[currentStep].title, answers: payloadAnswers, questionDetail: question.question_detail, answersList: question.answers, }, ]); setUserInfo((prev) => { if (!prev) return null; return { ...prev, cluster:{ ...prev.cluster, [steps[currentStep].type]: true, } }; }); setCurrentStep(currentStep + 1); setQuestion(null); setAnswers({}); } catch (error) { console.error("Error submitting data:", error); } finally { setSubmitting(false); } }; // ---------------------------------------------------------------- // 5. Handle Back Button Click: Return to Previous Step // ---------------------------------------------------------------- const handleBack = () => { if (currentStep > 0) { const prevResponse = clusterResponses[currentStep - 1]; if (prevResponse) { // Restore the previous question and answers setQuestion({ _id: "", question_no: "", question_detail: prevResponse.questionDetail, type: "", answer_type: "", created_at: "", updated_at: null, answers: prevResponse.answersList, is_active: true, created_by: "", }); const restoredAnswers: { [key: string]: string } = {}; Object.entries(prevResponse.answers).forEach(([key, value]) => { restoredAnswers[key] = value.toString(); }); setAnswers(restoredAnswers); setAutoAssigned(true); setVisibleCards(Object.keys(restoredAnswers)); } setCurrentStep(currentStep - 1); } }; // ---------------------------------------------------------------- // 6. Final Analysis Screen // Displays a grayscale bar chart (with the final bar in red, yellow, or green) // and a table of delta scores. The sidebar is preserved (hidden on mobile). // ---------------------------------------------------------------- const FinalAnalyticsScreen = () => { const [finalUserInfo, setFinalUserInfo] = useState(null); const [chartData, setChartData] = useState(null); const [loadingFinal, setLoadingFinal] = useState(false); useEffect(() => { if (!id) return; const fetchFinalUserInfo = async () => { try { setLoadingFinal(true); const { data: responseData } = await axiosInstance.post( `/userInfo?userId=${id}` ); if (responseData.data) { setFinalUserInfo(responseData.data); const deltaKeys = Object.keys(responseData.data.deltas); const deltaValues = deltaKeys.map( (key) => responseData.data.deltas[key] ); const defaultColor = "rgba(50, 50, 50, 0.8)"; // grayscale for all bars const lastValue = deltaValues[deltaValues.length - 1]; let finalBarColor = "rgba(255, 0, 0, 0.8)"; // red by default if (lastValue >= 4) { finalBarColor = "rgba(0, 128, 0, 0.8)"; // green } else if (lastValue >= 2) { finalBarColor = "rgba(255, 205, 0, 0.8)"; // yellow } const backgroundColors = deltaValues.map((v, i) => i === deltaValues.length - 1 ? finalBarColor : defaultColor ); setChartData({ labels: deltaKeys, datasets: [ { label: "Delta Values", data: deltaValues, backgroundColor: backgroundColors, borderColor: backgroundColors, borderWidth: 1, }, ], }); } } catch (error) { console.error("Error fetching final user info:", error); } finally { setLoadingFinal(false); } }; fetchFinalUserInfo(); }, [id]); if (loadingFinal) { return (

Loading final analysis...

); } if (!finalUserInfo) { return (

Failed to load final analysis data.

); } return (

Final Analysis

{chartData ? ( ) : (

No delta data available.

)}

Delta Scores

{Object.entries(finalUserInfo.deltas).map(([key, value]) => ( ))}
Delta Score
{key.replaceAll("_"," ")} {value.toFixed(3)}
{/* Responsive Chart Container Styles */}
); }; // ---------------------------------------------------------------- // 7. Loading / Error UI // ---------------------------------------------------------------- if (loading && !question) { return (

Loading...

); } if (!userInfo) { return (

User not found or invalid ID.

); } // If all steps have been completed, show the final analysis screen. if (currentStep >= steps.length) { return ; } // ---------------------------------------------------------------- // 8. Prepare Answer Cards as an Array // ---------------------------------------------------------------- const answerCards = Object.entries(answers).map(([key, score]) => ({ id: key, score, })); // ---------------------------------------------------------------- // 9. Render Layout with Sidebar and Main Panel // ---------------------------------------------------------------- return (
{/* Header */}

{steps[currentStep].title}

{question?.question_detail}

{/* Answer Cards Grid */}
{answerCards .filter((card) => visibleCards.includes(card.id)) .map((card) => (
{card.id}
{card.score !== "pool" ? card.score : "-"}
))}
{/* Footer with Back & Next Buttons */}
{/* Pop Animation CSS */}
); }; export default EvaluationPage;