логика отображения времени
This commit is contained in:
parent
2ea14d81f8
commit
0d57508966
@ -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) {
|
||||||
|
// Попытка автоматической инициализации на основе текущих настроек
|
||||||
|
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.");
|
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;
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user