|
|
@ -2,7 +2,8 @@ |
|
|
|
|
|
|
|
|
import { useRouter } from "next/navigation"; |
|
|
import { useRouter } from "next/navigation"; |
|
|
import { useEffect, useState } from "react"; |
|
|
import { useEffect, useState } from "react"; |
|
|
import { GoArrowLeft } from "react-icons/go"; |
|
|
|
|
|
|
|
|
import { GoArrowLeft, GoArrowRight } from "react-icons/go"; |
|
|
|
|
|
import { useI18n } from "@/translations/provider"; |
|
|
|
|
|
|
|
|
import Button from "./button"; |
|
|
import Button from "./button"; |
|
|
import { ExplanationUiFont } from "./explanation-ui-font"; |
|
|
import { ExplanationUiFont } from "./explanation-ui-font"; |
|
|
@ -73,24 +74,24 @@ function formatOptionLabel(str: string): string { |
|
|
// Remove trailing numbers in parentheses, e.g., (1) or (۱)
|
|
|
// Remove trailing numbers in parentheses, e.g., (1) or (۱)
|
|
|
result = result.replace(/\s*\([\d۱۲۳۴۵۶۷۸۹۰]+\)\s*$/, ""); |
|
|
result = result.replace(/\s*\([\d۱۲۳۴۵۶۷۸۹۰]+\)\s*$/, ""); |
|
|
|
|
|
|
|
|
if (result.startsWith(')')) { |
|
|
|
|
|
|
|
|
if (result.startsWith(")")) { |
|
|
result = result.slice(1).trim(); |
|
|
result = result.slice(1).trim(); |
|
|
} |
|
|
} |
|
|
if (result.endsWith('(')) { |
|
|
|
|
|
|
|
|
if (result.endsWith("(")) { |
|
|
result = result.slice(0, -1).trim(); |
|
|
result = result.slice(0, -1).trim(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let openCount = 0; |
|
|
let openCount = 0; |
|
|
let closeCount = 0; |
|
|
let closeCount = 0; |
|
|
for (let i = 0; i < result.length; i++) { |
|
|
for (let i = 0; i < result.length; i++) { |
|
|
if (result[i] === '(') openCount++; |
|
|
|
|
|
else if (result[i] === ')') closeCount++; |
|
|
|
|
|
|
|
|
if (result[i] === "(") openCount++; |
|
|
|
|
|
else if (result[i] === ")") closeCount++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (openCount > closeCount) { |
|
|
if (openCount > closeCount) { |
|
|
result = result + ')'.repeat(openCount - closeCount); |
|
|
|
|
|
|
|
|
result = result + ")".repeat(openCount - closeCount); |
|
|
} else if (closeCount > openCount) { |
|
|
} else if (closeCount > openCount) { |
|
|
result = '('.repeat(closeCount - openCount) + result; |
|
|
|
|
|
|
|
|
result = "(".repeat(closeCount - openCount) + result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (result.length > 0) { |
|
|
if (result.length > 0) { |
|
|
@ -113,6 +114,7 @@ export default function TestQuestionsFlow({ |
|
|
draftStorageKey, |
|
|
draftStorageKey, |
|
|
}: TestQuestionsFlowProps) { |
|
|
}: TestQuestionsFlowProps) { |
|
|
const router = useRouter(); |
|
|
const router = useRouter(); |
|
|
|
|
|
const { locale } = useI18n(); |
|
|
const [currentIndex, setCurrentIndex] = useState( |
|
|
const [currentIndex, setCurrentIndex] = useState( |
|
|
() => getStoredDraft(draftStorageKey, questions.length).currentIndex, |
|
|
() => getStoredDraft(draftStorageKey, questions.length).currentIndex, |
|
|
); |
|
|
); |
|
|
@ -121,6 +123,41 @@ export default function TestQuestionsFlow({ |
|
|
); |
|
|
); |
|
|
const [isSubmitting, setIsSubmitting] = useState(false); |
|
|
const [isSubmitting, setIsSubmitting] = useState(false); |
|
|
|
|
|
|
|
|
|
|
|
const isTargetTest = |
|
|
|
|
|
!!draftStorageKey && |
|
|
|
|
|
(draftStorageKey.includes("personality_test") || |
|
|
|
|
|
draftStorageKey.includes("glasser_5_needs_test")); |
|
|
|
|
|
|
|
|
|
|
|
const [maxVisitedIndex, setMaxVisitedIndex] = useState(() => { |
|
|
|
|
|
const initialIndex = getStoredDraft( |
|
|
|
|
|
draftStorageKey, |
|
|
|
|
|
questions.length, |
|
|
|
|
|
).currentIndex; |
|
|
|
|
|
const initialAnswers = getStoredDraft( |
|
|
|
|
|
draftStorageKey, |
|
|
|
|
|
questions.length, |
|
|
|
|
|
).answers; |
|
|
|
|
|
let highestAnswered = -1; |
|
|
|
|
|
for (let i = 0; i < questions.length; i++) { |
|
|
|
|
|
if (initialAnswers[questions[i].id] !== undefined) { |
|
|
|
|
|
highestAnswered = i; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
const furthestReached = |
|
|
|
|
|
highestAnswered !== -1 |
|
|
|
|
|
? Math.min(highestAnswered + 1, questions.length - 1) |
|
|
|
|
|
: 0; |
|
|
|
|
|
return Math.max(initialIndex, furthestReached); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
if (Object.keys(answers).length === 0) { |
|
|
|
|
|
setMaxVisitedIndex(currentIndex); |
|
|
|
|
|
} else if (currentIndex > maxVisitedIndex) { |
|
|
|
|
|
setMaxVisitedIndex(currentIndex); |
|
|
|
|
|
} |
|
|
|
|
|
}, [currentIndex, answers, maxVisitedIndex]); |
|
|
|
|
|
|
|
|
const currentQuestion = questions[currentIndex] ?? questions[0]; |
|
|
const currentQuestion = questions[currentIndex] ?? questions[0]; |
|
|
const totalQuestions = questions.length; |
|
|
const totalQuestions = questions.length; |
|
|
const isLastQuestion = currentIndex === totalQuestions - 1; |
|
|
const isLastQuestion = currentIndex === totalQuestions - 1; |
|
|
@ -157,6 +194,10 @@ export default function TestQuestionsFlow({ |
|
|
setCurrentIndex((prev) => Math.max(prev - 1, 0)); |
|
|
setCurrentIndex((prev) => Math.max(prev - 1, 0)); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleNext = () => { |
|
|
|
|
|
setCurrentIndex((prev) => Math.min(prev + 1, totalQuestions - 1)); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const handleFinishSubmit = async () => { |
|
|
const handleFinishSubmit = async () => { |
|
|
if (isSubmitting) return; |
|
|
if (isSubmitting) return; |
|
|
setIsSubmitting(true); |
|
|
setIsSubmitting(true); |
|
|
@ -187,7 +228,9 @@ export default function TestQuestionsFlow({ |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const progressPercent = ((currentIndex + 1) / totalQuestions) * 100; |
|
|
|
|
|
|
|
|
const progressPercent = |
|
|
|
|
|
(((isTargetTest ? maxVisitedIndex : currentIndex) + 1) / totalQuestions) * |
|
|
|
|
|
100; |
|
|
const areAllQuestionsAnswered = questions.every( |
|
|
const areAllQuestionsAnswered = questions.every( |
|
|
(question) => answers[question.id] !== undefined, |
|
|
(question) => answers[question.id] !== undefined, |
|
|
); |
|
|
); |
|
|
@ -286,7 +329,9 @@ export default function TestQuestionsFlow({ |
|
|
(() => { |
|
|
(() => { |
|
|
const parts = option.label.split(" - "); |
|
|
const parts = option.label.split(" - "); |
|
|
const title = formatOptionLabel(parts[0]); |
|
|
const title = formatOptionLabel(parts[0]); |
|
|
const description = formatOptionLabel(parts.slice(1).join(" - ")); |
|
|
|
|
|
|
|
|
const description = formatOptionLabel( |
|
|
|
|
|
parts.slice(1).join(" - "), |
|
|
|
|
|
); |
|
|
return ( |
|
|
return ( |
|
|
<span className="flex flex-col gap-1 text-start flex-1"> |
|
|
<span className="flex flex-col gap-1 text-start flex-1"> |
|
|
<span className="font-bold">{title}</span> |
|
|
<span className="font-bold">{title}</span> |
|
|
@ -303,7 +348,9 @@ export default function TestQuestionsFlow({ |
|
|
); |
|
|
); |
|
|
})() |
|
|
})() |
|
|
) : ( |
|
|
) : ( |
|
|
<span className="flex-1">{formatOptionLabel(option.label)}</span> |
|
|
|
|
|
|
|
|
<span className="flex-1"> |
|
|
|
|
|
{formatOptionLabel(option.label)} |
|
|
|
|
|
</span> |
|
|
)} |
|
|
)} |
|
|
</button> |
|
|
</button> |
|
|
); |
|
|
); |
|
|
@ -323,8 +370,23 @@ export default function TestQuestionsFlow({ |
|
|
className="flex items-center gap-1.5 group-14 font-semibold text-[#4A4A4A] disabled:opacity-30 disabled:pointer-events-none transition-opacity py-2 px-1" |
|
|
className="flex items-center gap-1.5 group-14 font-semibold text-[#4A4A4A] disabled:opacity-30 disabled:pointer-events-none transition-opacity py-2 px-1" |
|
|
> |
|
|
> |
|
|
<GoArrowLeft className="size-5" /> |
|
|
<GoArrowLeft className="size-5" /> |
|
|
<span>{previousLabel}</span> |
|
|
|
|
|
|
|
|
<span>{locale === "fa" ? "قبلی" : previousLabel}</span> |
|
|
|
|
|
</button> |
|
|
|
|
|
|
|
|
|
|
|
{/* Next Button */} |
|
|
|
|
|
{isTargetTest && |
|
|
|
|
|
!isLastQuestion && |
|
|
|
|
|
selectedValue !== undefined && |
|
|
|
|
|
currentIndex < maxVisitedIndex && ( |
|
|
|
|
|
<button |
|
|
|
|
|
type="button" |
|
|
|
|
|
onClick={handleNext} |
|
|
|
|
|
className="flex items-center gap-1.5 group-14 font-semibold text-[#4A4A4A] transition-opacity py-2 px-1" |
|
|
|
|
|
> |
|
|
|
|
|
<span>{locale === "fa" ? "بعدی" : "Next"}</span> |
|
|
|
|
|
<GoArrowRight className="size-5" /> |
|
|
</button> |
|
|
</button> |
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
{/* Finish Button on Last Question */} |
|
|
{/* Finish Button on Last Question */} |
|
|
{isLastQuestion ? ( |
|
|
{isLastQuestion ? ( |
|
|
|