логика отображения времени

This commit is contained in:
Nastya 2025-09-21 13:06:41 +03:00
parent 2ea14d81f8
commit 0d57508966
5 changed files with 113 additions and 21 deletions

@ -25,6 +25,8 @@ import { type ReactNode } from "react";
import { isProduction } from "@/utils/defineDomain"; import { isProduction } from "@/utils/defineDomain";
import { useQuizStore } from "@/stores/useQuizStore"; import { useQuizStore } from "@/stores/useQuizStore";
import { CustomCircularTimer } from "@/ui_kit/timer/CircularTimer"; import { CustomCircularTimer } from "@/ui_kit/timer/CircularTimer";
import { useQuestionTimer } from "@/utils/hooks/FlowControlLogic/useQuestionTimer";
import { useState, useEffect } from "react";
type Props = { type Props = {
currentQuestion: RealTypedQuizQuestion; currentQuestion: RealTypedQuizQuestion;
@ -42,7 +44,38 @@ export const Question = ({
questionSelect, questionSelect,
}: Props) => { }: Props) => {
const theme = useTheme(); const theme = useTheme();
const { settings, show_badge, quizId } = useQuizStore(); const { settings, show_badge, quizId, preview } = useQuizStore();
// Состояние для отслеживания оставшегося времени
const [remainingTime, setRemainingTime] = useState<number>(0);
const [isTimerActive, setIsTimerActive] = useState<boolean>(false);
// Получаем настройки таймера
const timerEnabled = Boolean(settings.questionTimerEnabled);
const timerDuration = settings.cfg.time_of_passing ?? 0;
// Эффект для обновления таймера
useEffect(() => {
if (timerEnabled && timerDuration > 0) {
setRemainingTime(timerDuration);
setIsTimerActive(true);
const interval = setInterval(() => {
setRemainingTime((prev) => {
if (prev <= 1) {
setIsTimerActive(false);
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(interval);
} else {
setIsTimerActive(false);
setRemainingTime(0);
}
}, [timerEnabled, timerDuration, currentQuestion.id]);
return ( return (
<Box <Box
@ -105,12 +138,15 @@ export const Question = ({
gap: "13px", gap: "13px",
}} }}
> >
{ {timerEnabled && isTimerActive && (
<CustomCircularTimer <CustomCircularTimer
duration={60} duration={timerDuration}
remaining={30} remaining={remainingTime}
showTime={true}
size={76}
thickness={4}
/> />
} )}
<Link <Link
target="_blank" target="_blank"
href={`https://${isProduction ? "" : "s"}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`} href={`https://${isProduction ? "" : "s"}quiz.pena.digital/answer/v1.0.0/logo?q=${quizId}`}

@ -68,7 +68,12 @@ export default function ViewPublicationPage() {
); );
if (settings.cfg.antifraud && recentlyCompleted) throw new Error("Quiz already completed"); if (settings.cfg.antifraud && recentlyCompleted) throw new Error("Quiz already completed");
if (currentQuizStep === "startpage" && settings.cfg.noStartPage) currentQuizStep = "question"; if (currentQuizStep === "startpage" && settings.cfg.noStartPage) {
// Обновляем состояние в store, а не только локальную переменную
const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep);
setCurrentQuizStep("question");
currentQuizStep = "question";
}
if (!currentQuestion) { if (!currentQuestion) {
return ( return (

@ -1,5 +1,15 @@
import { CircularProgress, Box, Typography, useTheme, styled } from "@mui/material"; import { CircularProgress, Box, Typography, useTheme, styled } from "@mui/material";
// Типизация для пропсов таймера
export interface CircularTimerProps {
duration: number; // Общая длительность в секундах
remaining: number; // Оставшееся время в секундах
showTime?: boolean; // Показывать ли время в формате mm:ss
size?: number; // Размер таймера
thickness?: number; // Толщина линии прогресса
color?: string; // Цвет прогресса
}
const StyledCircularProgress = styled(CircularProgress)(({ theme }) => ({ const StyledCircularProgress = styled(CircularProgress)(({ theme }) => ({
"& .MuiCircularProgress-circle": { "& .MuiCircularProgress-circle": {
strokeLinecap: "round", strokeLinecap: "round",
@ -7,19 +17,33 @@ const StyledCircularProgress = styled(CircularProgress)(({ theme }) => ({
}, },
})); }));
export const CustomCircularTimer: React.FC<CircularTimerProps> = ({ duration, remaining }) => { // Функция для форматирования времени в mm:ss
const formatTime = (seconds: number): string => {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
};
export const CustomCircularTimer: React.FC<CircularTimerProps> = ({
duration,
remaining,
showTime = true,
size = 76,
thickness = 4,
color,
}) => {
const theme = useTheme(); const theme = useTheme();
const progress = (remaining / duration) * 100; const progress = (remaining / duration) * 100;
return ( return (
<Box sx={{ position: "relative", display: "inline-flex", width: 76, height: 76 }}> <Box sx={{ position: "relative", display: "inline-flex", width: size, height: size }}>
{/* Серый фон */} {/* Серый фон */}
<Box <Box
sx={{ sx={{
border: "#9A9AAF solid 1px", border: "#9A9AAF solid 1px",
position: "absolute", position: "absolute",
height: "72px", height: `${size - 4}px`,
width: "72px", width: `${size - 4}px`,
borderRadius: "100%", borderRadius: "100%",
top: "2px", top: "2px",
left: "2px", left: "2px",
@ -30,15 +54,16 @@ export const CustomCircularTimer: React.FC<CircularTimerProps> = ({ duration, re
<StyledCircularProgress <StyledCircularProgress
variant="determinate" variant="determinate"
value={progress} value={progress}
size={76} size={size}
thickness={4} thickness={thickness}
sx={{ sx={{
color: "linear-gradient(135deg, #FC712F 0%, #7E2AEA 100%)", color: color || "linear-gradient(135deg, #FC712F 0%, #7E2AEA 100%)",
position: "absolute", position: "absolute",
"& .MuiCircularProgress-circle": { "& .MuiCircularProgress-circle": {
strokeLinecap: "round", strokeLinecap: "round",
stroke: "url(#timer-gradient)", // ← правильное использование stroke: color ? undefined : "url(#timer-gradient)",
strokeDasharray: color ? undefined : undefined,
}, },
}} }}
/> />
@ -74,8 +99,8 @@ export const CustomCircularTimer: React.FC<CircularTimerProps> = ({ duration, re
top: "50%", top: "50%",
left: "50%", left: "50%",
transform: "translate(-50%, -50%)", transform: "translate(-50%, -50%)",
width: 56, width: `${size - 20}px`,
height: 56, height: `${size - 20}px`,
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
@ -85,12 +110,14 @@ export const CustomCircularTimer: React.FC<CircularTimerProps> = ({ duration, re
variant="body1" variant="body1"
fontWeight="bold" fontWeight="bold"
sx={{ sx={{
fontSize: "16px", fontSize: size > 60 ? "16px" : "12px",
fontWeight: 400, fontWeight: 600,
color: theme.palette.text.primary, color: theme.palette.text.primary,
textAlign: "center",
lineHeight: 1,
}} }}
> >
{remaining} {showTime ? formatTime(remaining) : remaining}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>

@ -28,6 +28,7 @@ export function useQuestionTimer({ enabled, seconds, quizId, preview, currentQue
currentQuestionType: currentQuestion?.type, currentQuestionType: currentQuestion?.type,
currentQuizStep, currentQuizStep,
hasCurrentQuestion: !!currentQuestion, hasCurrentQuestion: !!currentQuestion,
timestamp: new Date().toISOString(),
}); });
if (!enabled) { if (!enabled) {

@ -2,6 +2,7 @@ import { useBranchingQuiz } from "./FlowControlLogic/useBranchingQuiz";
import { useLinearQuiz } from "./FlowControlLogic/useLinearQuiz"; import { useLinearQuiz } from "./FlowControlLogic/useLinearQuiz";
import { useAIQuiz } from "./FlowControlLogic/useAIQuiz"; import { useAIQuiz } from "./FlowControlLogic/useAIQuiz";
import { Status } from "@/model/settingsData"; import { Status } from "@/model/settingsData";
import { useQuizStore } from "@/stores/useQuizStore";
interface StatusData { interface StatusData {
status: Status; status: Status;
@ -11,6 +12,7 @@ interface StatusData {
// выбор способа управления в зависимости от статуса // выбор способа управления в зависимости от статуса
let cachedManager: () => ReturnType<typeof useLinearQuiz>; let cachedManager: () => ReturnType<typeof useLinearQuiz>;
export let statusOfQuiz: "line" | "branch" | "ai"; export let statusOfQuiz: "line" | "branch" | "ai";
let isInitialized = false;
function analyicStatus({ status, haveRoot }: StatusData) { function analyicStatus({ status, haveRoot }: StatusData) {
if (status === "ai") { if (status === "ai") {
@ -27,6 +29,7 @@ function analyicStatus({ status, haveRoot }: StatusData) {
} }
export const initDataManager = (data: StatusData) => { export const initDataManager = (data: StatusData) => {
console.log("🔧 Initializing DataManager with:", data);
analyicStatus(data); analyicStatus(data);
switch (statusOfQuiz) { switch (statusOfQuiz) {
case "line": case "line":
@ -39,12 +42,32 @@ export const initDataManager = (data: StatusData) => {
cachedManager = useAIQuiz; cachedManager = useAIQuiz;
break; break;
} }
isInitialized = true;
console.log("✅ DataManager initialized with type:", statusOfQuiz);
}; };
// Главный хук (интерфейс для потребителей) // Главный хук (интерфейс для потребителей)
export const useQuestionFlowControl = () => { export const useQuestionFlowControl = () => {
if (!cachedManager) { if (!cachedManager || !isInitialized) {
throw new Error("DataManager not initialized! Call initDataManager() first."); // Попытка автоматической инициализации на основе текущих настроек
const { settings } = useQuizStore.getState();
if (settings && settings.status) {
console.log("🔄 Auto-initializing DataManager with settings:", settings);
initDataManager({
status: settings.status,
haveRoot: settings.cfg.haveRoot,
});
} else {
throw new Error("DataManager not initialized! Call initDataManager() first.");
}
} }
return cachedManager(); return cachedManager();
}; };
// Функция для сброса состояния (полезна для HMR)
export const resetDataManager = () => {
console.log("🔄 Resetting DataManager");
cachedManager = null as any;
isInitialized = false;
statusOfQuiz = null as any;
};