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

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 { useQuizStore } from "@/stores/useQuizStore";
import { CustomCircularTimer } from "@/ui_kit/timer/CircularTimer";
import { useQuestionTimer } from "@/utils/hooks/FlowControlLogic/useQuestionTimer";
import { useState, useEffect } from "react";
type Props = {
currentQuestion: RealTypedQuizQuestion;
@ -42,7 +44,38 @@ export const Question = ({
questionSelect,
}: Props) => {
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 (
<Box
@ -105,12 +138,15 @@ export const Question = ({
gap: "13px",
}}
>
{
{timerEnabled && isTimerActive && (
<CustomCircularTimer
duration={60}
remaining={30}
duration={timerDuration}
remaining={remainingTime}
showTime={true}
size={76}
thickness={4}
/>
}
)}
<Link
target="_blank"
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 (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) {
return (

@ -1,5 +1,15 @@
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 }) => ({
"& .MuiCircularProgress-circle": {
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 progress = (remaining / duration) * 100;
return (
<Box sx={{ position: "relative", display: "inline-flex", width: 76, height: 76 }}>
<Box sx={{ position: "relative", display: "inline-flex", width: size, height: size }}>
{/* Серый фон */}
<Box
sx={{
border: "#9A9AAF solid 1px",
position: "absolute",
height: "72px",
width: "72px",
height: `${size - 4}px`,
width: `${size - 4}px`,
borderRadius: "100%",
top: "2px",
left: "2px",
@ -30,15 +54,16 @@ export const CustomCircularTimer: React.FC<CircularTimerProps> = ({ duration, re
<StyledCircularProgress
variant="determinate"
value={progress}
size={76}
thickness={4}
size={size}
thickness={thickness}
sx={{
color: "linear-gradient(135deg, #FC712F 0%, #7E2AEA 100%)",
color: color || "linear-gradient(135deg, #FC712F 0%, #7E2AEA 100%)",
position: "absolute",
"& .MuiCircularProgress-circle": {
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%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 56,
height: 56,
width: `${size - 20}px`,
height: `${size - 20}px`,
display: "flex",
alignItems: "center",
justifyContent: "center",
@ -85,12 +110,14 @@ export const CustomCircularTimer: React.FC<CircularTimerProps> = ({ duration, re
variant="body1"
fontWeight="bold"
sx={{
fontSize: "16px",
fontWeight: 400,
fontSize: size > 60 ? "16px" : "12px",
fontWeight: 600,
color: theme.palette.text.primary,
textAlign: "center",
lineHeight: 1,
}}
>
{remaining}
{showTime ? formatTime(remaining) : remaining}
</Typography>
</Box>
</Box>

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

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