timer
This commit is contained in:
parent
3bb940047c
commit
13de6c7f1c
@ -66,16 +66,20 @@ export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit<QuizS
|
|||||||
readyData.questions = items;
|
readyData.questions = items;
|
||||||
|
|
||||||
if (quizDataResponse?.settings !== undefined) {
|
if (quizDataResponse?.settings !== undefined) {
|
||||||
|
const parsedCfg = JSON.parse(quizDataResponse?.settings.cfg);
|
||||||
|
|
||||||
readyData.settings = {
|
readyData.settings = {
|
||||||
fp: quizDataResponse.settings.fp,
|
fp: quizDataResponse.settings.fp,
|
||||||
rep: quizDataResponse.settings.rep,
|
rep: quizDataResponse.settings.rep,
|
||||||
name: quizDataResponse.settings.name,
|
name: quizDataResponse.settings.name,
|
||||||
cfg: JSON.parse(quizDataResponse?.settings.cfg),
|
cfg: parsedCfg,
|
||||||
lim: quizDataResponse.settings.lim,
|
lim: quizDataResponse.settings.lim,
|
||||||
due: quizDataResponse.settings.due,
|
due: quizDataResponse.settings.due,
|
||||||
delay: quizDataResponse.settings.delay,
|
delay: quizDataResponse.settings.delay,
|
||||||
pausable: quizDataResponse.settings.pausable,
|
pausable: quizDataResponse.settings.pausable,
|
||||||
status: quizDataResponse.settings.status,
|
status: quizDataResponse.settings.status,
|
||||||
|
// Автоматически включаем таймер, если time_of_passing > 0
|
||||||
|
questionTimerEnabled: parsedCfg.time_of_passing > 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -54,6 +54,8 @@ export type QuizSettingsConfig = {
|
|||||||
pausable: boolean;
|
pausable: boolean;
|
||||||
cfg: QuizConfig;
|
cfg: QuizConfig;
|
||||||
status: Status;
|
status: Status;
|
||||||
|
// Таймер вопросов
|
||||||
|
questionTimerEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QuizSettings = {
|
export type QuizSettings = {
|
||||||
@ -65,6 +67,7 @@ export type QuizSettings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface QuizConfig {
|
export interface QuizConfig {
|
||||||
|
time_of_passing?: number;
|
||||||
isUnSc?: boolean;
|
isUnSc?: boolean;
|
||||||
spec: undefined | true;
|
spec: undefined | true;
|
||||||
type: QuizType;
|
type: QuizType;
|
||||||
|
|||||||
@ -9,10 +9,19 @@ import { useQuizViewStore } from "@stores/quizView";
|
|||||||
|
|
||||||
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
||||||
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
||||||
|
import { useQuestionTimer } from "./useQuestionTimer";
|
||||||
|
|
||||||
export function useAIQuiz() {
|
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);
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
@ -96,6 +105,19 @@ export function useAIQuiz() {
|
|||||||
nextQuestion: questions[quizStep - 1],
|
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 {
|
return {
|
||||||
currentQuestion,
|
currentQuestion,
|
||||||
currentQuestionStepNumber: null,
|
currentQuestionStepNumber: null,
|
||||||
|
|||||||
@ -9,10 +9,19 @@ import { useQuizViewStore } from "@stores/quizView";
|
|||||||
|
|
||||||
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
||||||
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
||||||
|
import { useQuestionTimer } from "./useQuestionTimer";
|
||||||
|
|
||||||
export function useBranchingQuiz() {
|
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 отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
//За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
||||||
@ -166,6 +175,19 @@ export function useBranchingQuiz() {
|
|||||||
return next;
|
return next;
|
||||||
}, [nextQuestionId, findResultPointsLogic, linearQuestionIndex, sortedQuestions, settings.cfg.score]);
|
}, [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(() => {
|
const showResult = useCallback(() => {
|
||||||
if (nextQuestion?.type !== "result") throw new Error("Current question is not result");
|
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 { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
|
||||||
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
|
||||||
|
import { useQuestionTimer } from "./useQuestionTimer";
|
||||||
|
|
||||||
export function useLinearQuiz() {
|
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 отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
//За корректность page отвечает конструктор квизов. Интересный факт, если в конструкторе удалить из середины вопрос, то случится куча запросов изменения вопросов с изменением этого page
|
||||||
@ -166,6 +175,20 @@ export function useLinearQuiz() {
|
|||||||
return next;
|
return next;
|
||||||
}, [nextQuestionId, findResultPointsLogic, linearQuestionIndex, sortedQuestions, settings.cfg.score]);
|
}, [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(() => {
|
const showResult = useCallback(() => {
|
||||||
if (nextQuestion?.type !== "result") throw new Error("Current question is not result");
|
if (nextQuestion?.type !== "result") throw new Error("Current question is not result");
|
||||||
|
|||||||
91
lib/utils/hooks/FlowControlLogic/useQuestionTimer.ts
Normal file
91
lib/utils/hooks/FlowControlLogic/useQuestionTimer.ts
Normal file
@ -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[]
|
ownVariants: OwnVariant[]
|
||||||
) {
|
) {
|
||||||
if (!questionAnswer) {
|
if (!questionAnswer) {
|
||||||
|
console.log("📤 sendQuestionAnswer: Sending empty answer for question", question.id);
|
||||||
return sendAnswer({
|
return sendAnswer({
|
||||||
questionId: question.id,
|
questionId: question.id,
|
||||||
body: "",
|
body: "",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user