fix utm visual selector
This commit is contained in:
parent
639929d825
commit
75aca56c01
@ -125,9 +125,7 @@ export default function QuizMarkCreate() {
|
|||||||
fullWidth
|
fullWidth
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={{
|
||||||
|
width: "137px",
|
||||||
width: "100%",
|
|
||||||
maxWidth: "118px",
|
|
||||||
height: "24px",
|
height: "24px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -26,21 +26,36 @@ const OverTime = () => {
|
|||||||
Math.max(0, (initialOverTime.endsAt || 0) - Date.now())
|
Math.max(0, (initialOverTime.endsAt || 0) - Date.now())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [isEditing, setIsEditing] = useState<boolean>(false);
|
||||||
|
|
||||||
const daysRef = useRef<HTMLInputElement | null>(null);
|
const daysRef = useRef<HTMLInputElement | null>(null);
|
||||||
const hoursRef = useRef<HTMLInputElement | null>(null);
|
const hoursRef = useRef<HTMLInputElement | null>(null);
|
||||||
const minutesRef = useRef<HTMLInputElement | null>(null);
|
const minutesRef = useRef<HTMLInputElement | null>(null);
|
||||||
const secondsRef = useRef<HTMLInputElement | null>(null);
|
const secondsRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
const isAnyTimeInputFocused = (): boolean => {
|
||||||
|
const active = document.activeElement as HTMLElement | null;
|
||||||
|
return [daysRef.current, hoursRef.current, minutesRef.current, secondsRef.current].some(
|
||||||
|
(ref) => ref !== null && ref === active
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const toDigits = (value: string): string => (value || "").replace(/\D+/g, "");
|
const toDigits = (value: string): string => (value || "").replace(/\D+/g, "");
|
||||||
const clampTwoDigits = (value: string): string => {
|
const clampTwoDigits = (value: string): string => {
|
||||||
const digits = toDigits(value).slice(0, 2);
|
const digits = toDigits(value).slice(0, 2);
|
||||||
if (digits.length === 0) return "";
|
if (digits.length === 0) return "";
|
||||||
const num = Number(digits);
|
const num = Number(digits);
|
||||||
if (isNaN(num)) return "";
|
if (isNaN(num)) return "";
|
||||||
if (num > 60) return "60";
|
return num > 60 ? "60" : digits;
|
||||||
return digits;
|
};
|
||||||
};
|
const clampTwoDigitsDays = (value: string): string => {
|
||||||
const clampTwoDigitsDays = (value: string): string => toDigits(value).slice(0, 2);
|
return toDigits(value).slice(0, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pad2 = (value: string): string => {
|
||||||
|
const d = toDigits(value);
|
||||||
|
return (d.length === 0 ? "00" : d.padStart(2, "0").slice(-2));
|
||||||
|
};
|
||||||
|
|
||||||
const allowControlKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const allowControlKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
const allowed = ["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab", "Home", "End", "Enter"];
|
const allowed = ["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab", "Home", "End", "Enter"];
|
||||||
@ -95,16 +110,6 @@ const OverTime = () => {
|
|||||||
const cfgEndsAt = (quiz as any)?.config?.overTime?.endsAt ?? 0;
|
const cfgEndsAt = (quiz as any)?.config?.overTime?.endsAt ?? 0;
|
||||||
const ms = Math.max(0, cfgEndsAt - Date.now());
|
const ms = Math.max(0, cfgEndsAt - Date.now());
|
||||||
setRemainingMs(ms);
|
setRemainingMs(ms);
|
||||||
if (ms <= 0) {
|
|
||||||
// выключаем флаг
|
|
||||||
setEnabled(false);
|
|
||||||
updateQuiz(quiz!.id, (q) => {
|
|
||||||
const cfg: any = (q as any).config;
|
|
||||||
if (!cfg.overTime) cfg.overTime = { enabled: false, endsAt: 0, description: "" };
|
|
||||||
cfg.overTime.enabled = false;
|
|
||||||
cfg.overTime.endsAt = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// первый расчёт по requestAnimationFrame для мгновенного обновления
|
// первый расчёт по requestAnimationFrame для мгновенного обновления
|
||||||
rafId = window.requestAnimationFrame(() => tick());
|
rafId = window.requestAnimationFrame(() => tick());
|
||||||
@ -135,6 +140,19 @@ const OverTime = () => {
|
|||||||
return { d, h, m, s };
|
return { d, h, m, s };
|
||||||
}, [remainingMs]);
|
}, [remainingMs]);
|
||||||
|
|
||||||
|
// Показываем обратный отсчёт в полях ввода, когда счётчик включён
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enabled || isEditing) return;
|
||||||
|
const dStr = String(rem.d).padStart(2, "0").slice(-2);
|
||||||
|
const hStr = fmt(rem.h).slice(-2);
|
||||||
|
const mStr = fmt(rem.m).slice(-2);
|
||||||
|
const sStr = fmt(rem.s).slice(-2);
|
||||||
|
setDays(dStr);
|
||||||
|
setHours(hStr);
|
||||||
|
setMinutes(mStr);
|
||||||
|
setSeconds(sStr);
|
||||||
|
}, [enabled, rem, isEditing]);
|
||||||
|
|
||||||
if (!quiz) return null;
|
if (!quiz) return null;
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", mt: "20px", padding: "15px", backgroundColor: "#F2F3F7", width: "100%", maxWidth: "357px", height: "295px", borderRadius: "8px" }}>
|
<Box sx={{ display: "flex", flexDirection: "column", mt: "20px", padding: "15px", backgroundColor: "#F2F3F7", width: "100%", maxWidth: "357px", height: "295px", borderRadius: "8px" }}>
|
||||||
@ -204,14 +222,22 @@ const OverTime = () => {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="00"
|
placeholder="00"
|
||||||
value={days}
|
value={days}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const next = clampTwoDigitsDays(e.target.value);
|
const next = clampTwoDigitsDays(e.target.value);
|
||||||
setDays(next);
|
setDays(next);
|
||||||
persistEndsAt(next, hours, minutes, seconds);
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={handleTwoDigitKeyDown(days) as any}
|
onKeyDown={handleTwoDigitKeyDown(days) as any}
|
||||||
inputRef={daysRef}
|
inputRef={daysRef}
|
||||||
onFocus={() => daysRef.current?.select()}
|
onFocus={() => { setIsEditing(true); daysRef.current?.select(); }}
|
||||||
|
onBlur={() => setTimeout(() => {
|
||||||
|
setIsEditing(isAnyTimeInputFocused());
|
||||||
|
const nd = pad2(days);
|
||||||
|
const nh = pad2(hours);
|
||||||
|
const nm = pad2(minutes);
|
||||||
|
const ns = pad2(seconds);
|
||||||
|
setDays(nd);
|
||||||
|
persistEndsAt(nd, nh, nm, ns);
|
||||||
|
}, 0)}
|
||||||
onClick={() => daysRef.current?.select()}
|
onClick={() => daysRef.current?.select()}
|
||||||
InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric", maxLength: 2 } }}
|
InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric", maxLength: 2 } }}
|
||||||
sx={{ height: "48px", width: "100%", maxWidth: "51px", backgroundColor: "white", p: "8px 6px" }}
|
sx={{ height: "48px", width: "100%", maxWidth: "51px", backgroundColor: "white", p: "8px 6px" }}
|
||||||
@ -231,14 +257,22 @@ const OverTime = () => {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="00"
|
placeholder="00"
|
||||||
value={hours}
|
value={hours}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const next = clampTwoDigits(e.target.value);
|
const next = clampTwoDigits(e.target.value);
|
||||||
setHours(next);
|
setHours(next);
|
||||||
persistEndsAt(days, next, minutes, seconds);
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={handleTwoDigitKeyDown(hours) as any}
|
onKeyDown={handleTwoDigitKeyDown(hours) as any}
|
||||||
inputRef={hoursRef}
|
inputRef={hoursRef}
|
||||||
onFocus={() => hoursRef.current?.select()}
|
onFocus={() => { setIsEditing(true); hoursRef.current?.select(); }}
|
||||||
|
onBlur={() => setTimeout(() => {
|
||||||
|
setIsEditing(isAnyTimeInputFocused());
|
||||||
|
const nd = pad2(days);
|
||||||
|
const nh = pad2(hours);
|
||||||
|
const nm = pad2(minutes);
|
||||||
|
const ns = pad2(seconds);
|
||||||
|
setHours(nh);
|
||||||
|
persistEndsAt(nd, nh, nm, ns);
|
||||||
|
}, 0)}
|
||||||
onClick={() => hoursRef.current?.select()}
|
onClick={() => hoursRef.current?.select()}
|
||||||
InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric", maxLength: 2 } }}
|
InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric", maxLength: 2 } }}
|
||||||
sx={{ height: "48px", width: "100%", maxWidth: "51px", backgroundColor: "white", p: "8px 6px" }}
|
sx={{ height: "48px", width: "100%", maxWidth: "51px", backgroundColor: "white", p: "8px 6px" }}
|
||||||
@ -258,14 +292,22 @@ const OverTime = () => {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="00"
|
placeholder="00"
|
||||||
value={minutes}
|
value={minutes}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const next = clampTwoDigits(e.target.value);
|
const next = clampTwoDigits(e.target.value);
|
||||||
setMinutes(next);
|
setMinutes(next);
|
||||||
persistEndsAt(days, hours, next, seconds);
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={handleTwoDigitKeyDown(minutes) as any}
|
onKeyDown={handleTwoDigitKeyDown(minutes) as any}
|
||||||
inputRef={minutesRef}
|
inputRef={minutesRef}
|
||||||
onFocus={() => minutesRef.current?.select()}
|
onFocus={() => { setIsEditing(true); minutesRef.current?.select(); }}
|
||||||
|
onBlur={() => setTimeout(() => {
|
||||||
|
setIsEditing(isAnyTimeInputFocused());
|
||||||
|
const nd = pad2(days);
|
||||||
|
const nh = pad2(hours);
|
||||||
|
const nm = pad2(minutes);
|
||||||
|
const ns = pad2(seconds);
|
||||||
|
setMinutes(nm);
|
||||||
|
persistEndsAt(nd, nh, nm, ns);
|
||||||
|
}, 0)}
|
||||||
onClick={() => minutesRef.current?.select()}
|
onClick={() => minutesRef.current?.select()}
|
||||||
InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric", maxLength: 2 } }}
|
InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric", maxLength: 2 } }}
|
||||||
sx={{ height: "48px", width: "100%", maxWidth: "51px", backgroundColor: "white", p: "8px 6px" }}
|
sx={{ height: "48px", width: "100%", maxWidth: "51px", backgroundColor: "white", p: "8px 6px" }}
|
||||||
@ -285,14 +327,22 @@ const OverTime = () => {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="00"
|
placeholder="00"
|
||||||
value={seconds}
|
value={seconds}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const next = clampTwoDigits(e.target.value);
|
const next = clampTwoDigits(e.target.value);
|
||||||
setSeconds(next);
|
setSeconds(next);
|
||||||
persistEndsAt(days, hours, minutes, next);
|
|
||||||
}}
|
}}
|
||||||
onKeyDown={handleTwoDigitKeyDown(seconds) as any}
|
onKeyDown={handleTwoDigitKeyDown(seconds) as any}
|
||||||
inputRef={secondsRef}
|
inputRef={secondsRef}
|
||||||
onFocus={() => secondsRef.current?.select()}
|
onFocus={() => { setIsEditing(true); secondsRef.current?.select(); }}
|
||||||
|
onBlur={() => setTimeout(() => {
|
||||||
|
setIsEditing(isAnyTimeInputFocused());
|
||||||
|
const nd = pad2(days);
|
||||||
|
const nh = pad2(hours);
|
||||||
|
const nm = pad2(minutes);
|
||||||
|
const ns = pad2(seconds);
|
||||||
|
setSeconds(ns);
|
||||||
|
persistEndsAt(nd, nh, nm, ns);
|
||||||
|
}, 0)}
|
||||||
onClick={() => secondsRef.current?.select()}
|
onClick={() => secondsRef.current?.select()}
|
||||||
InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric", maxLength: 2 } }}
|
InputProps={{ inputProps: { pattern: "\\d*", inputMode: "numeric", maxLength: 2 } }}
|
||||||
sx={{ height: "48px", width: "100%", maxWidth: "51px", backgroundColor: "white", p: "8px 6px" }}
|
sx={{ height: "48px", width: "100%", maxWidth: "51px", backgroundColor: "white", p: "8px 6px" }}
|
||||||
@ -309,13 +359,7 @@ const OverTime = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{enabled && (
|
{/* Удалён маленький таймер с обратным отсчётом */}
|
||||||
<Box sx={{ mt: "10px" }}>
|
|
||||||
<Typography sx={{ fontWeight: 500, color: theme.palette.grey3.main, fontSize: "16px" }}>
|
|
||||||
До конца: {rem.d} д {fmt(rem.h)}:{fmt(rem.m)}:{fmt(rem.s)}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user