diff --git a/CHANGELOG.md b/CHANGELOG.md
index b986a9bc..db32b108 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+1.0.6 _ 2025-09-19 _ логика включения таймера
1.0.5 _ 2025-09-18 _ особые условия для вывода картинок
1.0.4 _ 2025-09-14 _ особые условия для вывода картинок
1.0.3 _ 2025-09-12 _ среднее время не учитывает нули
diff --git a/src/pages/startPage/StartPageSettings/BackBlockedWithTooltip.tsx b/src/pages/startPage/StartPageSettings/BackBlockedWithTooltip.tsx
new file mode 100644
index 00000000..b91cb484
--- /dev/null
+++ b/src/pages/startPage/StartPageSettings/BackBlockedWithTooltip.tsx
@@ -0,0 +1,81 @@
+import { Box, ClickAwayListener, Tooltip, Typography, useTheme } from "@mui/material";
+import CustomizedSwitch from "@/ui_kit/CustomSwitch";
+import { useState } from "react";
+import { useCurrentQuiz } from "@/stores/quizes/hooks";
+import { updateQuiz } from "@/stores/quizes/actions";
+
+export default function BackBlockedWithTooltip() {
+ const theme = useTheme();
+ const quiz = useCurrentQuiz();
+ const [open, setOpen] = useState(false);
+
+ if (!quiz) return null;
+
+ const enabled = Boolean((quiz as any)?.config?.questionTimerEnabled);
+ const checked = Boolean((quiz as any)?.config?.backBlocked);
+
+ const handleTooltipClose = () => setOpen(false);
+ const handleTooltipOpen = () => setOpen(true);
+
+ const tooltipText = "В режиме опроса с таймером, кнопка назад не работает";
+
+ return (
+
+
+ {enabled && (
+
+ )}
+
+
+ {
+ updateQuiz(quiz.id, (q) => {
+ (q as any).config.backBlocked = e.target.checked;
+ });
+ }}
+ disabled={enabled}
+ />
+
+ Запретить шаг назад
+
+
+
+
+
+ );
+}
+
+
diff --git a/src/pages/startPage/StartPageSettings/QuestionTimerSettings.tsx b/src/pages/startPage/StartPageSettings/QuestionTimerSettings.tsx
new file mode 100644
index 00000000..dd47347a
--- /dev/null
+++ b/src/pages/startPage/StartPageSettings/QuestionTimerSettings.tsx
@@ -0,0 +1,225 @@
+import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
+import CustomTextField from "@/ui_kit/CustomTextField";
+import moment from "moment";
+import { useEffect, useMemo, useRef, useState } from "react";
+import { updateQuiz } from "@root/quizes/actions";
+import { useCurrentQuiz } from "@root/quizes/hooks";
+import CustomizedSwitch from "@ui_kit/CustomSwitch";
+
+export default function QuestionTimerSettings() {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down(650));
+ const quiz = useCurrentQuiz();
+ const minutesRef = useRef(null);
+ const secondsRef = useRef(null);
+ const enabled = Boolean((quiz as any)?.config?.questionTimerEnabled);
+
+ const initialSeconds = useMemo(() => {
+ // Читаем из корректного поля, с fallback на возможное старое имя
+ const raw = quiz?.config.time_of_passing ?? 0;
+ const sec = Number(raw) || 0;
+ return Math.max(0, sec);
+ }, [quiz?.config.time_of_passing]);
+
+ const initial = useMemo(() => {
+ const d = moment.duration(initialSeconds, "seconds");
+ const m = Math.min(60, Math.max(0, d.minutes() + d.hours() * 60));
+ const s = Math.min(60, Math.max(0, d.seconds()));
+ return {
+ minutes: String(m),
+ seconds: String(s),
+ };
+ }, [initialSeconds]);
+
+ const [minutes, setMinutes] = useState(initial.minutes);
+ const [seconds, setSeconds] = useState(initial.seconds);
+
+ // Не синхронизируем обратно каждое изменение, чтобы не ломать ввод
+
+ const toDigits = (value: string): string => (value || "").replace(/\D+/g, "");
+
+ const clampTwoDigits = (value: string): string => {
+ const digits = toDigits(value).slice(0, 2);
+ if (digits.length === 0) return "";
+ // если пользователь выделил всё и печатает заново, берём именно введённые цифры
+ const num = Number(digits);
+ if (isNaN(num)) return "";
+ if (num > 60) return "60";
+ return digits; // не форматируем и не обнуляем ведущие нули
+ };
+
+ const persist = (mStr: string, sStr: string) => {
+ const m = Number(toDigits(mStr) || 0);
+ const s = Number(toDigits(sStr) || 0);
+ const total = Math.min(3660, Math.max(0, (isNaN(m) ? 0 : m) * 60 + (isNaN(s) ? 0 : s)));
+ updateQuiz(quiz!.id, (q) => {
+ // Пишем только в корректное поле модели
+ (q as any).config.time_of_passing = total;
+ });
+ };
+
+ const allowControlKey = (e: React.KeyboardEvent) => {
+ const allowed = [
+ "Backspace",
+ "Delete",
+ "ArrowLeft",
+ "ArrowRight",
+ "Tab",
+ "Home",
+ "End",
+ "Enter",
+ ];
+ return allowed.includes(e.key);
+ };
+
+ const handleDigitKeyDown = (e: React.KeyboardEvent) => {
+ if (allowControlKey(e)) return;
+ if (!/^[0-9]$/.test(e.key)) {
+ e.preventDefault();
+ }
+ };
+
+ const handleMinutesPaste = (e: React.ClipboardEvent) => {
+ e.preventDefault();
+ const text = e.clipboardData.getData("text");
+ const next = clampTwoDigits(text);
+ setMinutes(next);
+ if (toDigits(next).length >= 2 && secondsRef.current) {
+ secondsRef.current.focus();
+ secondsRef.current.select();
+ }
+ persist(next, seconds);
+ };
+
+ const handleSecondsPaste = (e: React.ClipboardEvent) => {
+ e.preventDefault();
+ const text = e.clipboardData.getData("text");
+ const next = clampTwoDigits(text);
+ setSeconds(next);
+ persist(minutes, next);
+ };
+
+ return (
+
+
+ {
+ updateQuiz(quiz!.id, (q) => {
+ (q as any).config.questionTimerEnabled = e.target.checked;
+ (q as any).config.backBlocked = true;
+ });
+ }}
+ />
+
+
+ Включить таймер вопросов
+
+
+
+
+
+ {
+ const next = clampTwoDigits(e.target.value);
+ setMinutes(next);
+ if (toDigits(next).length === 2 && secondsRef.current) {
+ secondsRef.current.focus();
+ secondsRef.current.select();
+ }
+ persist(next, seconds);
+ }}
+ onFocus={() => minutesRef.current?.select()}
+ onClick={() => minutesRef.current?.select()}
+ onKeyDown={handleDigitKeyDown as any}
+ onPaste={handleMinutesPaste as any}
+ InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric" } }}
+ sx={{
+ width: "51px",
+ height: "48px",
+ }}
+ inputRef={minutesRef}
+ />
+
+ мин.
+
+
+
+
+ {
+ const next = clampTwoDigits(e.target.value);
+ setSeconds(next);
+ persist(minutes, next);
+ }}
+ onFocus={() => secondsRef.current?.select()}
+ onClick={() => secondsRef.current?.select()}
+ onKeyDown={handleDigitKeyDown as any}
+ onPaste={handleSecondsPaste as any}
+ InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric" } }}
+ sx={{
+ width: "51px",
+ height: "48px",
+ }}
+ inputRef={secondsRef}
+ />
+
+ сек.
+
+
+
+
+ );
+}
+
+
diff --git a/src/pages/startPage/StartPageSettings.tsx b/src/pages/startPage/StartPageSettings/StartPageSettings.tsx
similarity index 89%
rename from src/pages/startPage/StartPageSettings.tsx
rename to src/pages/startPage/StartPageSettings/StartPageSettings.tsx
index effea858..6ea822f6 100755
--- a/src/pages/startPage/StartPageSettings.tsx
+++ b/src/pages/startPage/StartPageSettings/StartPageSettings.tsx
@@ -31,20 +31,22 @@ import { incrementCurrentStep, updateQuiz, uploadQuizImage } from "@root/quizes/
import { useCurrentQuiz } from "@root/quizes/hooks";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
+import QuestionTimerSettings from "./QuestionTimerSettings";
import SelectableButton from "@ui_kit/SelectableButton";
import { StartPagePreview } from "@ui_kit/StartPagePreview";
import { resizeFavIcon } from "@ui_kit/reactImageFileResizer";
import { useState } from "react";
import { createPortal } from "react-dom";
-import FaviconDropZone from "./FaviconDropZone";
-import ModalSizeImage from "./ModalSizeImage";
-import SelectableIconButton from "./SelectableIconButton";
-import { DropZone } from "./dropZone";
-import Extra from "./extra";
+import FaviconDropZone from "../FaviconDropZone";
+import ModalSizeImage from "../ModalSizeImage";
+import SelectableIconButton from "../SelectableIconButton";
+import { DropZone } from "../dropZone";
+import Extra from "../extra";
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
-import { VideoElement } from "./VideoElement";
-import UploadVideoModal from "../Questions/UploadVideoModal";
+import { VideoElement } from "../VideoElement";
+import UploadVideoModal from "../../Questions/UploadVideoModal";
import { SwitchAI } from "@/ui_kit/crutchFunctionAI";
+import BackBlockedWithTooltip from "./BackBlockedWithTooltip";
const designTypes = [
["standard", (color: string) => , "Standard"],
@@ -875,118 +877,8 @@ export default function StartPageSettings() {
Включить защиту от копирования
-
- {
- updateQuiz(quiz.id, (quiz) => {
- quiz.config.backBlocked = e.target.checked;
- });
- }}
- />
-
- Запретить шаг назад
-
-
-
-
- {
- updateQuiz(quiz.id, (quiz) => {
- quiz.config.backBlocked = e.target.checked;
- });
- }}
- />
-
-
- Включить таймер вопросов
-
-
-
-
-
- updateQuiz(quiz.id, (quiz) => {
- quiz.config.info.law = e.target.value;
- })
- }
- sx={{
- width: "51px",
- height: "48px"
- }}
- />
-
- мин.
-
-
-
-
-
-
- updateQuiz(quiz.id, (quiz) => {
- quiz.config.info.law = e.target.value;
- })
- }
- sx={{
- width: "51px",
- height: "48px"
- }}
- />
-
- сек.
-
-
-
-
-
+
+
{!isSmallMonitor && }
>
)}
diff --git a/src/ui_kit/CustomTextField.tsx b/src/ui_kit/CustomTextField.tsx
index 900eb433..75621450 100755
--- a/src/ui_kit/CustomTextField.tsx
+++ b/src/ui_kit/CustomTextField.tsx
@@ -1,4 +1,4 @@
-import type { ChangeEvent, FocusEvent, KeyboardEvent } from "react";
+import type { ChangeEvent, FocusEvent, KeyboardEvent, MouseEvent, ClipboardEvent, Ref } from "react";
import React, { useEffect, useState } from "react";
import type { InputProps, SxProps, Theme } from "@mui/material";
import {
@@ -20,6 +20,9 @@ interface CustomTextFieldProps {
onChange?: (event: ChangeEvent) => void;
onKeyDown?: (event: KeyboardEvent) => void;
onBlur?: (event: FocusEvent) => void;
+ onFocus?: (event: FocusEvent) => void;
+ onClick?: (event: MouseEvent) => void;
+ onPaste?: (event: ClipboardEvent) => void;
text?: string;
maxLength?: number;
sx?: SxProps;
@@ -29,6 +32,7 @@ interface CustomTextFieldProps {
rows?: number;
className?: string;
disabled?: boolean;
+ inputRef?: Ref;
}
export default function CustomTextField({
@@ -38,6 +42,9 @@ export default function CustomTextField({
onChange,
onKeyDown,
onBlur,
+ onFocus,
+ onClick,
+ onPaste,
text,
sx,
error,
@@ -49,6 +56,7 @@ export default function CustomTextField({
sxForm,
className,
disabled,
+ inputRef,
}: CustomTextFieldProps) {
const theme = useTheme();
@@ -77,8 +85,9 @@ export default function CustomTextField({
}
};
- const handleInputFocus = () => {
+ const handleInputFocus = (event: React.FocusEvent) => {
setIsInputActive(true);
+ if (onFocus) onFocus(event);
};
const handleInputBlur = (event: React.FocusEvent) => {
@@ -117,10 +126,14 @@ export default function CustomTextField({
onFocus={handleInputFocus}
onBlur={handleInputBlur}
onKeyDown={onKeyDown}
+ onClick={onClick}
+ onPaste={onPaste}
multiline={rows > 0}
rows={rows}
disabled={disabled}
disableUnderline
+ inputRef={inputRef}
+ {...InputProps}
sx={{
maxLength: maxLength,
borderRadius: "10px",
diff --git a/src/ui_kit/switchStepPages.tsx b/src/ui_kit/switchStepPages.tsx
index 26d5366c..a6c22ce1 100755
--- a/src/ui_kit/switchStepPages.tsx
+++ b/src/ui_kit/switchStepPages.tsx
@@ -16,7 +16,7 @@ const FormQuestionsPage = lazy(
const QuestionsPage = lazy(() => import("../pages/Questions/QuestionsPage"));
const ResultPage = lazy(() => import("../pages/ResultPage/ResultPage"));
const StartPageSettings = lazy(
- () => import("../pages/startPage/StartPageSettings"),
+ () => import("../pages/startPage/StartPageSettings/StartPageSettings"),
);
const StepOne = lazy(() => import("../pages/startPage/stepOne"));
const Steptwo = lazy(() => import("../pages/startPage/steptwo"));