diff --git a/lib/model/api/getQuizData.ts b/lib/model/api/getQuizData.ts index 9fcc9ea..9b9d186 100644 --- a/lib/model/api/getQuizData.ts +++ b/lib/model/api/getQuizData.ts @@ -66,16 +66,20 @@ export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit 0 + questionTimerEnabled: parsedCfg.time_of_passing > 0, }; } diff --git a/lib/model/settingsData.ts b/lib/model/settingsData.ts index 0d13794..47d354c 100644 --- a/lib/model/settingsData.ts +++ b/lib/model/settingsData.ts @@ -54,6 +54,8 @@ export type QuizSettingsConfig = { pausable: boolean; cfg: QuizConfig; status: Status; + // Таймер вопросов + questionTimerEnabled?: boolean; }; export type QuizSettings = { @@ -65,6 +67,7 @@ export type QuizSettings = { }; export interface QuizConfig { + time_of_passing?: number; isUnSc?: boolean; spec: undefined | true; type: QuizType; diff --git a/lib/utils/hooks/FlowControlLogic/useAIQuiz.ts b/lib/utils/hooks/FlowControlLogic/useAIQuiz.ts index f318c82..4911651 100644 --- a/lib/utils/hooks/FlowControlLogic/useAIQuiz.ts +++ b/lib/utils/hooks/FlowControlLogic/useAIQuiz.ts @@ -9,10 +9,19 @@ import { useQuizViewStore } from "@stores/quizView"; import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; +import { useQuestionTimer } from "./useQuestionTimer"; export function useAIQuiz() { //Получаем инфо о квизе и список вопросов. - const { settings, questions, quizId, cnt, quizStep } = useQuizStore(); + const { settings, questions, quizId, cnt, quizStep, preview } = useQuizStore(); + + // Отладочная информация о настройках таймера + console.log("🤖 AI Quiz settings:", { + questionTimerEnabled: settings.questionTimerEnabled, + time_of_passing: settings.cfg.time_of_passing, + timerEnabled: Boolean(settings.questionTimerEnabled), + timerSeconds: settings.cfg.time_of_passing ?? 0, + }); //Список ответов на вопрос. Мы записываем ответы локально, параллельно отправляя на бек информацию о ответах const answers = useQuizViewStore((state) => state.answers); @@ -96,6 +105,19 @@ export function useAIQuiz() { nextQuestion: questions[quizStep - 1], }); + // Таймер авто-перехода между вопросами (AI) + useQuestionTimer({ + enabled: Boolean(settings.questionTimerEnabled), + seconds: settings.cfg.time_of_passing ?? 0, + quizId, + preview, + currentQuestion, + onNext: () => { + console.log("🤖 AI Quiz: Timer triggered moveToNextQuestion"); + moveToNextQuestion(); + }, + }); + return { currentQuestion, currentQuestionStepNumber: null, diff --git a/lib/utils/hooks/FlowControlLogic/useBranchingQuiz.ts b/lib/utils/hooks/FlowControlLogic/useBranchingQuiz.ts index 639a2bc..2188d12 100644 --- a/lib/utils/hooks/FlowControlLogic/useBranchingQuiz.ts +++ b/lib/utils/hooks/FlowControlLogic/useBranchingQuiz.ts @@ -9,10 +9,19 @@ import { useQuizViewStore } from "@stores/quizView"; import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; +import { useQuestionTimer } from "./useQuestionTimer"; export function useBranchingQuiz() { //Получаем инфо о квизе и список вопросов. - const { settings, questions, quizId, cnt } = useQuizStore(); + const { settings, questions, quizId, cnt, preview } = useQuizStore(); + + // Отладочная информация о настройках таймера + console.log("🌳 Branching Quiz settings:", { + questionTimerEnabled: settings.questionTimerEnabled, + time_of_passing: settings.cfg.time_of_passing, + timerEnabled: Boolean(settings.questionTimerEnabled), + timerSeconds: settings.cfg.time_of_passing ?? 0, + }); //Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page. //За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page @@ -166,6 +175,19 @@ export function useBranchingQuiz() { return next; }, [nextQuestionId, findResultPointsLogic, linearQuestionIndex, sortedQuestions, settings.cfg.score]); + // Таймер авто-перехода между вопросами + useQuestionTimer({ + enabled: Boolean(settings.questionTimerEnabled), + seconds: settings.cfg.time_of_passing ?? 0, + quizId, + preview, + currentQuestion, + onNext: () => { + console.log("🌳 Branching Quiz: Timer triggered moveToNextQuestion"); + moveToNextQuestion(); + }, + }); + //Показать визуалом юзеру результат const showResult = useCallback(() => { if (nextQuestion?.type !== "result") throw new Error("Current question is not result"); diff --git a/lib/utils/hooks/FlowControlLogic/useLinearQuiz.ts b/lib/utils/hooks/FlowControlLogic/useLinearQuiz.ts index 427011c..3426f8a 100644 --- a/lib/utils/hooks/FlowControlLogic/useLinearQuiz.ts +++ b/lib/utils/hooks/FlowControlLogic/useLinearQuiz.ts @@ -9,10 +9,19 @@ import { useQuizViewStore } from "@stores/quizView"; import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals"; import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals"; +import { useQuestionTimer } from "./useQuestionTimer"; export function useLinearQuiz() { //Получаем инфо о квизе и список вопросов. - const { settings, questions, quizId, cnt } = useQuizStore(); + const { settings, questions, quizId, cnt, preview } = useQuizStore(); + + // Отладочная информация о настройках таймера + console.log("📏 Linear Quiz settings:", { + questionTimerEnabled: settings.questionTimerEnabled, + time_of_passing: settings.cfg.time_of_passing, + timerEnabled: Boolean(settings.questionTimerEnabled), + timerSeconds: settings.cfg.time_of_passing ?? 0, + }); //Когда квиз линейный, не ветвящийся, мы идём по вопросам по их порядковому номеру. Это их page. //За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page @@ -166,6 +175,20 @@ export function useLinearQuiz() { return next; }, [nextQuestionId, findResultPointsLogic, linearQuestionIndex, sortedQuestions, settings.cfg.score]); + // Таймер авто-перехода между вопросами + useQuestionTimer({ + enabled: Boolean(settings.questionTimerEnabled), + seconds: settings.cfg.time_of_passing ?? 0, + quizId, + preview, + currentQuestion, + onNext: () => { + console.log("📏 Linear Quiz: Timer triggered moveToNextQuestion"); + // Программный переход к следующему вопросу + moveToNextQuestion(); + }, + }); + //Показать визуалом юзеру результат const showResult = useCallback(() => { if (nextQuestion?.type !== "result") throw new Error("Current question is not result"); diff --git a/lib/utils/hooks/FlowControlLogic/useQuestionTimer.ts b/lib/utils/hooks/FlowControlLogic/useQuestionTimer.ts new file mode 100644 index 0000000..1322351 --- /dev/null +++ b/lib/utils/hooks/FlowControlLogic/useQuestionTimer.ts @@ -0,0 +1,91 @@ +import { useEffect, useRef } from "react"; +import { enqueueSnackbar } from "notistack"; + +import { useQuizViewStore } from "@/stores/quizView"; +import { sendQuestionAnswer } from "@/utils/sendQuestionAnswer"; + +type Params = { + enabled: boolean; + seconds: number; + quizId: string; + preview: boolean; + currentQuestion: any; // Using any to avoid tight coupling with question union types + onNext: () => void; +}; + +export function useQuestionTimer({ enabled, seconds, quizId, preview, currentQuestion, onNext }: Params) { + const ownVariants = useQuizViewStore((state) => state.ownVariants); + const currentQuizStep = useQuizViewStore((state) => state.currentQuizStep); + const timeoutRef = useRef(null); + + useEffect(() => { + console.log("🕐 useQuestionTimer useEffect triggered", { + enabled, + seconds, + quizId, + preview, + currentQuestionId: currentQuestion?.id, + currentQuestionType: currentQuestion?.type, + currentQuizStep, + hasCurrentQuestion: !!currentQuestion, + }); + + if (!enabled) { + console.log("❌ Timer disabled"); + return; + } + if (!seconds || seconds <= 0) { + console.log("❌ Invalid seconds:", seconds); + return; + } + if (!currentQuestion) { + console.log("❌ No current question"); + return; + } + if (currentQuizStep !== "question") { + console.log("❌ Not on question step:", currentQuizStep); + return; + } + if (currentQuestion.type === "result") { + console.log("❌ Question is result type"); + return; + } + + console.log("✅ Starting timer for", seconds, "seconds"); + + // Сбрасываем предыдущий таймер + if (timeoutRef.current) { + console.log("🔄 Clearing previous timer"); + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + + timeoutRef.current = window.setTimeout(async () => { + console.log("⏰ Timer expired! Auto-advancing to next question"); + try { + if (!preview) { + console.log("📤 Sending empty answer for question:", currentQuestion.id); + // Отправляем пустую строку в ответе (questionAnswer === undefined) + await sendQuestionAnswer(quizId, currentQuestion, undefined, ownVariants); + console.log("✅ Empty answer sent successfully"); + } else { + console.log("👀 Preview mode - skipping answer send"); + } + } catch (e) { + console.error("❌ Error sending empty timed answer", e); + enqueueSnackbar("Ошибка при отправке ответа по таймеру"); + } finally { + console.log("➡️ Calling onNext()"); + onNext(); + } + }, seconds * 1000); + + return () => { + console.log("🧹 Cleaning up timer"); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + }, [enabled, seconds, quizId, preview, currentQuestion?.id, currentQuizStep, onNext]); +} diff --git a/lib/utils/sendQuestionAnswer.ts b/lib/utils/sendQuestionAnswer.ts index b2c4195..cb6fcc4 100644 --- a/lib/utils/sendQuestionAnswer.ts +++ b/lib/utils/sendQuestionAnswer.ts @@ -11,6 +11,7 @@ export async function sendQuestionAnswer( ownVariants: OwnVariant[] ) { if (!questionAnswer) { + console.log("📤 sendQuestionAnswer: Sending empty answer for question", question.id); return sendAnswer({ questionId: question.id, body: "",