This commit is contained in:
Nastya 2025-09-20 02:13:19 +03:00
parent 3bb940047c
commit 13de6c7f1c
7 changed files with 170 additions and 4 deletions

@ -66,16 +66,20 @@ export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit<QuizS
readyData.questions = items;
if (quizDataResponse?.settings !== undefined) {
const parsedCfg = JSON.parse(quizDataResponse?.settings.cfg);
readyData.settings = {
fp: quizDataResponse.settings.fp,
rep: quizDataResponse.settings.rep,
name: quizDataResponse.settings.name,
cfg: JSON.parse(quizDataResponse?.settings.cfg),
cfg: parsedCfg,
lim: quizDataResponse.settings.lim,
due: quizDataResponse.settings.due,
delay: quizDataResponse.settings.delay,
pausable: quizDataResponse.settings.pausable,
status: quizDataResponse.settings.status,
// Автоматически включаем таймер, если time_of_passing > 0
questionTimerEnabled: parsedCfg.time_of_passing > 0,
};
}

@ -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;

@ -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,

@ -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");

@ -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");

@ -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<number | null>(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]);
}

@ -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: "",