user test results and testlist in profile
This commit is contained in:
parent
01446071e5
commit
cba24b05c9
@ -10,7 +10,7 @@
|
|||||||
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
<title>test-ai</title>
|
<title>HoshiAI</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
const baseURL = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
baseURL: "http://127.0.0.1:8000",
|
baseURL,
|
||||||
timeout: 30 * 1000,
|
timeout: 30 * 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import axiosInstance from "./axiosInstance";
|
import axiosInstance from "./axiosInstance";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type GetQuestionsParams = {
|
type GetQuestionsParams = {
|
||||||
page?: number;
|
page?: number;
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|||||||
@ -28,8 +28,8 @@ export const getUserTestById = async (userTestId: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getUserTests = async () => {
|
export const getUserTests = async () => {
|
||||||
const res = await axiosInstance.get<UserTestType[]>("/api/user-tests/me");
|
const res = await axiosInstance.get<{data: UserTestType[]}>("/api/user-tests/me");
|
||||||
return res.data;
|
return res.data.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const submitAnswer = async (data: SubmitAnswerPayload) => {
|
export const submitAnswer = async (data: SubmitAnswerPayload) => {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Box, Button, Typography } from "@mui/material";
|
import { Box, Button, Typography } from "@mui/material";
|
||||||
|
import { arraysEqual } from "../../utils/functions";
|
||||||
|
|
||||||
interface Variant {
|
interface Variant {
|
||||||
id: number;
|
id: number;
|
||||||
@ -9,31 +10,67 @@ interface Variant {
|
|||||||
interface LearningAnswersProps {
|
interface LearningAnswersProps {
|
||||||
variants?: Variant[];
|
variants?: Variant[];
|
||||||
correctAnswers: number[] | string[];
|
correctAnswers: number[] | string[];
|
||||||
type: "single" | "text";
|
type: "single" | "multiple" | "text";
|
||||||
|
userAnswers?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const LearningAnswers = ({
|
const LearningAnswers = ({
|
||||||
variants = [],
|
variants = [],
|
||||||
correctAnswers = [],
|
correctAnswers = [],
|
||||||
type,
|
type,
|
||||||
|
userAnswers,
|
||||||
}: LearningAnswersProps) => {
|
}: LearningAnswersProps) => {
|
||||||
const [showAnswer, setShowAnswer] = useState(false);
|
const [showAnswer, setShowAnswer] = useState(false);
|
||||||
|
|
||||||
|
const hasAnswered = userAnswers !== null && userAnswers !== undefined;
|
||||||
|
const isEmptyAnswer = hasAnswered && userAnswers.length === 0;
|
||||||
|
const isCorrect = hasAnswered
|
||||||
|
? arraysEqual(correctAnswers as number[], userAnswers as number[])
|
||||||
|
: false;
|
||||||
|
const normalizedUserAnswers = userAnswers ?? [];
|
||||||
|
|
||||||
|
const getUserAnswerText = () => {
|
||||||
|
if (!normalizedUserAnswers.length) return "";
|
||||||
|
const selectedVariants = variants.filter((v) =>
|
||||||
|
normalizedUserAnswers.includes(v.id)
|
||||||
|
);
|
||||||
|
return selectedVariants.map((v) => v.text).join(", ");
|
||||||
|
};
|
||||||
|
|
||||||
if (type === "text") {
|
if (type === "text") {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
{showAnswer && (
|
{hasAnswered ? (
|
||||||
|
isEmptyAnswer ? (
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ mt: 1, p: 1, border: "1px solid #ccc", borderRadius: 2 }}
|
||||||
|
>
|
||||||
|
You didn’t answer
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
color="primary"
|
||||||
|
sx={{ mt: 1, p: 1, border: "1px solid #ccc", borderRadius: 2 }}
|
||||||
|
>
|
||||||
|
Your answer: {userAnswers.join(", ")}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{(hasAnswered || showAnswer) && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
color="success.main"
|
color="success.main"
|
||||||
sx={{ mt: 1, p: 1, border: "1px solid #ccc", borderRadius: 2 }}
|
sx={{ mt: 1, p: 1, border: "1px solid #ccc", borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
Correct answer:{" "}
|
Correct answer: {correctAnswers.join(", ")}
|
||||||
{Array.isArray(correctAnswers)
|
|
||||||
? correctAnswers.join(", ")
|
|
||||||
: correctAnswers}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!hasAnswered && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -42,12 +79,30 @@ const LearningAnswers = ({
|
|||||||
>
|
>
|
||||||
Reveal Answer
|
Reveal Answer
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
|
{hasAnswered ? (
|
||||||
|
isEmptyAnswer ? (
|
||||||
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
|
||||||
|
You didn’t answer
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
color={isCorrect ? "success.main" : "error.main"}
|
||||||
|
sx={{ mb: 1 }}
|
||||||
|
>
|
||||||
|
Your answer: {getUserAnswerText()} —{" "}
|
||||||
|
{isCorrect ? "Correct" : "Incorrect"}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
|
||||||
{variants.map((variant) => (
|
{variants.map((variant) => (
|
||||||
<Box
|
<Box
|
||||||
key={variant.id}
|
key={variant.id}
|
||||||
@ -57,8 +112,12 @@ const LearningAnswers = ({
|
|||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
border: "1px solid #ccc",
|
border: "1px solid #ccc",
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
showAnswer && (correctAnswers as number[]).includes(variant.id)
|
showAnswer || hasAnswered
|
||||||
|
? (correctAnswers as number[]).includes(variant.id)
|
||||||
? "success.light"
|
? "success.light"
|
||||||
|
: normalizedUserAnswers.includes(variant.id)
|
||||||
|
? "error.light"
|
||||||
|
: "background.paper"
|
||||||
: "background.paper",
|
: "background.paper",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -66,14 +125,16 @@ const LearningAnswers = ({
|
|||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{!hasAnswered && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
onClick={() => setShowAnswer(true)}
|
onClick={() => setShowAnswer(!showAnswer)}
|
||||||
>
|
>
|
||||||
Reveal Answer
|
{showAnswer ? "Hide Answer" : "Reveal Answer"}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
80
src/components/TestCard/TestCard.tsx
Normal file
80
src/components/TestCard/TestCard.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { Box, Button, Chip, Typography } from "@mui/material";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { formatDate } from "../../utils/functions";
|
||||||
|
import type { UserTestType } from "../shared/types/TestTypes";
|
||||||
|
|
||||||
|
interface TestCardProps {
|
||||||
|
test: UserTestType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TestCard = ({ test }: TestCardProps) => {
|
||||||
|
const title = test.test?.title || "User Test";
|
||||||
|
|
||||||
|
let statusLabel = "";
|
||||||
|
let statusColor: "primary" | "success" | "error" = "primary";
|
||||||
|
|
||||||
|
if (test.is_completed) {
|
||||||
|
statusLabel = "Completed";
|
||||||
|
statusColor = "success";
|
||||||
|
} else if (!test.is_available) {
|
||||||
|
statusLabel = "Expired";
|
||||||
|
statusColor = "error";
|
||||||
|
} else {
|
||||||
|
statusLabel = "Active";
|
||||||
|
statusColor = "primary";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: 2,
|
||||||
|
p: 2,
|
||||||
|
mb: 2,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6">{title}</Typography>
|
||||||
|
{test.closed_at && (
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
Expires at: {formatDate(test.closed_at)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{test.is_completed && test.score !== undefined && (
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
Score: {test.score}%
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||||
|
<Chip label={statusLabel} color={statusColor} />
|
||||||
|
{test.is_available && !test.is_completed && (
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to={`/tests/${test.id}`}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Continue Test
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{test.is_completed && (
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to={`/tests/${test.id}`}
|
||||||
|
variant="contained"
|
||||||
|
color="success"
|
||||||
|
>
|
||||||
|
View Results
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TestCard;
|
||||||
@ -1,12 +1,15 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { completeUserTest } from "../../api/testApi";
|
import { completeUserTest } from "../../api/testApi";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
export const useCompleteTest = () => {
|
export const useCompleteTest = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: completeUserTest,
|
mutationFn: completeUserTest,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
toast.success(data.message);
|
toast.success(data.message);
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["current-test"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["user-tests"] });
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.response?.data?.message);
|
toast.error(error.response?.data?.message);
|
||||||
|
|||||||
@ -4,6 +4,6 @@ import { getUserTests } from "../../api/testApi";
|
|||||||
export const useGetUserTests = () => {
|
export const useGetUserTests = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["user-tests"],
|
queryKey: ["user-tests"],
|
||||||
queryFn: () => getUserTests,
|
queryFn: () => getUserTests(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { startTest } from "../../api/testApi";
|
import { startTest } from "../../api/testApi";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export const useStartTest = () => {
|
export const useStartTest = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: startTest,
|
mutationFn: startTest,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["user-tests"] });
|
||||||
toast.success("Test Started");
|
toast.success("Test Started");
|
||||||
navigate(`/tests/${data.id}`);
|
navigate(`/tests/${data.id}`);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { submitAnswer } from "../../api/testApi";
|
import { submitAnswer } from "../../api/testApi";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
export const useSubmitAnswer = () => {
|
export const useSubmitAnswer = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: submitAnswer,
|
mutationFn: submitAnswer,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
toast.success(data.message);
|
toast.success(data.message);
|
||||||
console.log(data);
|
queryClient.invalidateQueries({ queryKey: ["current-test"] });
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.response?.data?.message || "Something went wrong");
|
toast.error(error.response?.data?.message || "Something went wrong");
|
||||||
|
|||||||
@ -1,11 +1,25 @@
|
|||||||
import { Box, Button, Typography } from "@mui/material";
|
import { Box, Button, Tab, Tabs, Typography } from "@mui/material";
|
||||||
import { useAuth } from "../../context/AuthContext";
|
import { useAuth } from "../../context/AuthContext";
|
||||||
import Container from "../../components/shared/Container";
|
import Container from "../../components/shared/Container";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useGetUserTests } from "../../hooks/Tests/useGetUserTests";
|
||||||
|
import TestCard from "../../components/TestCard/TestCard";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const ProfilePage = () => {
|
const ProfilePage = () => {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { data: tests, isLoading, error } = useGetUserTests();
|
||||||
|
|
||||||
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
|
|
||||||
|
const activeTests = tests?.filter((t) => t.is_available && !t.is_completed);
|
||||||
|
const completedTests = tests?.filter((t) => t.is_completed);
|
||||||
|
const expiredTests = tests?.filter((t) => !t.is_available && !t.is_completed);
|
||||||
|
|
||||||
|
const handleTabChange = (_: React.SyntheticEvent, newValue: number) => {
|
||||||
|
setTabIndex(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
@ -36,6 +50,43 @@ const ProfilePage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box sx={{ mt: 4 }}>
|
||||||
|
<Typography variant="h5" mb={2}>
|
||||||
|
Your Tests
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Tabs value={tabIndex} onChange={handleTabChange}>
|
||||||
|
<Tab label={`Active (${activeTests?.length || 0})`} />
|
||||||
|
<Tab label={`Completed (${completedTests?.length || 0})`} />
|
||||||
|
<Tab label={`Expired (${expiredTests?.length || 0})`} />
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 2, display: "flex", flexDirection: "column", gap: 2 }}>
|
||||||
|
{isLoading && <Typography>Loading tests...</Typography>}
|
||||||
|
{error && (
|
||||||
|
<Typography color="error">Failed to load tests.</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tabIndex === 0 &&
|
||||||
|
(activeTests?.length ? (
|
||||||
|
activeTests.map((t) => <TestCard key={t.id} test={t} />)
|
||||||
|
) : (
|
||||||
|
<Typography>No active tests</Typography>
|
||||||
|
))}
|
||||||
|
{tabIndex === 1 &&
|
||||||
|
(completedTests?.length ? (
|
||||||
|
completedTests.map((t) => <TestCard key={t.id} test={t} />)
|
||||||
|
) : (
|
||||||
|
<Typography>No completed tests</Typography>
|
||||||
|
))}
|
||||||
|
{tabIndex === 2 &&
|
||||||
|
(expiredTests?.length ? (
|
||||||
|
expiredTests.map((t) => <TestCard key={t.id} test={t} />)
|
||||||
|
) : (
|
||||||
|
<Typography>No expired tests</Typography>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { Box, Button, Pagination, Typography } from "@mui/material";
|
|||||||
import TestQuestion from "../../components/TestQuestion/TestQuestion";
|
import TestQuestion from "../../components/TestQuestion/TestQuestion";
|
||||||
import { useSubmitAnswer } from "../../hooks/Tests/useSubmitAnswer";
|
import { useSubmitAnswer } from "../../hooks/Tests/useSubmitAnswer";
|
||||||
import { useCompleteTest } from "../../hooks/Tests/useCompleteTest";
|
import { useCompleteTest } from "../../hooks/Tests/useCompleteTest";
|
||||||
|
import LearningAnswers from "../../components/Answers/Answers";
|
||||||
|
import { formatDate } from "../../utils/functions";
|
||||||
|
|
||||||
export const TestPage = () => {
|
export const TestPage = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@ -13,6 +15,7 @@ export const TestPage = () => {
|
|||||||
const [currentQuestion, setCurrentQuestion] = useState(1);
|
const [currentQuestion, setCurrentQuestion] = useState(1);
|
||||||
const submitAnswerMutation = useSubmitAnswer();
|
const submitAnswerMutation = useSubmitAnswer();
|
||||||
const completeTestMutation = useCompleteTest();
|
const completeTestMutation = useCompleteTest();
|
||||||
|
const allAnswered = test?.answers?.every((ans) => ans.answer !== null);
|
||||||
|
|
||||||
const handleCompleteTest = () => {
|
const handleCompleteTest = () => {
|
||||||
if (test) completeTestMutation.mutate(test.id);
|
if (test) completeTestMutation.mutate(test.id);
|
||||||
@ -54,15 +57,34 @@ export const TestPage = () => {
|
|||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (test.is_completed)
|
if (test.is_completed) {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Typography variant="h6" color="textSecondary">
|
<Typography variant="h2" sx={{ mb: 3 }}>
|
||||||
You have already completed this test.
|
Results
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Typography variant="h5" color="secondary" sx={{ mb: 3 }}>
|
||||||
|
Your score: {test.score}%
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{test?.answers?.map((ans) => (
|
||||||
|
<Box key={ans.question_id} sx={{ mb: 4 }}>
|
||||||
|
<Typography variant="h6">{ans.question.title}</Typography>
|
||||||
|
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||||
|
{ans.question.description}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<LearningAnswers
|
||||||
|
type={ans.question.type as "single" | "multiple" | "text"}
|
||||||
|
variants={ans.question.variants}
|
||||||
|
correctAnswers={ans.question.correct_answers}
|
||||||
|
userAnswers={ans.answer as number[]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
const questionsAndAnswers = test.answers;
|
const questionsAndAnswers = test.answers;
|
||||||
const currentQA = questionsAndAnswers?.[currentQuestion - 1];
|
const currentQA = questionsAndAnswers?.[currentQuestion - 1];
|
||||||
|
|
||||||
@ -78,17 +100,7 @@ export const TestPage = () => {
|
|||||||
>
|
>
|
||||||
<Typography variant="h2">{test.test?.title ?? "User Test"}</Typography>
|
<Typography variant="h2">{test.test?.title ?? "User Test"}</Typography>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
Expires at:{" "}
|
Expires at: {test.closed_at ? formatDate(test.closed_at) : "N/A"}
|
||||||
{test.closed_at
|
|
||||||
? new Date(test.closed_at).toLocaleString("en-GB", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "2-digit",
|
|
||||||
day: "2-digit",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: false,
|
|
||||||
})
|
|
||||||
: "N/A"}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{currentQA ? (
|
{currentQA ? (
|
||||||
@ -120,16 +132,27 @@ export const TestPage = () => {
|
|||||||
page={currentQuestion}
|
page={currentQuestion}
|
||||||
onChange={(_, page) => setCurrentQuestion(page)}
|
onChange={(_, page) => setCurrentQuestion(page)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{currentQuestion == questionsAndAnswers?.length && (
|
{currentQuestion == questionsAndAnswers?.length && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleCompleteTest}
|
onClick={handleCompleteTest}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="success"
|
color="success"
|
||||||
|
disabled={!allAnswered}
|
||||||
sx={{ mt: 15, fontSize: "32px" }}
|
sx={{ mt: 15, fontSize: "32px" }}
|
||||||
>
|
>
|
||||||
Complete test
|
Complete test
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{currentQuestion == questionsAndAnswers?.length && !allAnswered && (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="error"
|
||||||
|
sx={{ fontWeight: 500, textAlign: "center", mt: 2 }}
|
||||||
|
>
|
||||||
|
You must answer all questions before completing the test
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
29
src/utils/functions.ts
Normal file
29
src/utils/functions.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export function arraysEqual(a: number[], b: number[]): boolean {
|
||||||
|
if (a === b) return true;
|
||||||
|
if (!a || !b) return false;
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
|
||||||
|
a = [...a].sort((x, y) => x - y);
|
||||||
|
b = [...b].sort((x, y) => x - y);
|
||||||
|
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatDate = (dateString: string | undefined) => {
|
||||||
|
if (!dateString) return "N/A";
|
||||||
|
|
||||||
|
const date = new Date(dateString);
|
||||||
|
|
||||||
|
const pad = (num: number) => num.toString().padStart(2, "0");
|
||||||
|
|
||||||
|
const day = pad(date.getDate());
|
||||||
|
const month = pad(date.getMonth() + 1);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const hours = pad(date.getHours());
|
||||||
|
const minutes = pad(date.getMinutes());
|
||||||
|
|
||||||
|
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user