frontAnswerer/lib/components/ViewPublicationPage/questions/Number/index.tsx

392 lines
13 KiB
TypeScript
Raw Normal View History

import { useQuizSettings } from "@contexts/QuizDataContext";
import type { QuizQuestionNumber } from "@model/questionTypes/number";
2024-04-23 14:45:49 +00:00
import { Box, Typography, useTheme } from "@mui/material";
import { useQuizViewStore } from "@stores/quizView";
2024-04-23 14:45:49 +00:00
import { CustomSlider } from "@ui_kit/CustomSlider";
import CustomTextField from "@ui_kit/CustomTextField";
import { quizThemes } from "@utils/themes/Publication/themePublication";
import type { ChangeEvent, SyntheticEvent } from "react";
import { useEffect, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
2024-04-23 14:45:49 +00:00
type NumberProps = {
currentQuestion: QuizQuestionNumber;
};
export const Number = ({ currentQuestion }: NumberProps) => {
const [inputValue, setInputValue] = useState<string>("0");
const [minRange, setMinRange] = useState<string>("0");
const [maxRange, setMaxRange] = useState<string>("100000000000");
const [reversedInputValue, setReversedInputValue] = useState<string>("0");
const [reversedMinRange, setReversedMinRange] = useState<string>("0");
2024-05-31 16:41:18 +00:00
const [reversedMaxRange, setReversedMaxRange] = useState<string>("100000000000");
const { settings } = useQuizSettings();
2024-04-23 14:45:49 +00:00
const { updateAnswer } = useQuizViewStore((state) => state);
const answers = useQuizViewStore((state) => state.answers);
const theme = useTheme();
2024-05-31 16:41:18 +00:00
const [minBorder, maxBorder] = currentQuestion.content.range.split("—").map(window.Number);
2024-04-23 14:45:49 +00:00
const min = minBorder < maxBorder ? minBorder : maxBorder;
const max = minBorder < maxBorder ? maxBorder : minBorder;
const reversed = minBorder > maxBorder;
2024-05-31 16:41:18 +00:00
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string;
2024-04-23 14:45:49 +00:00
const sliderValue =
answer ||
2024-05-31 16:41:18 +00:00
(reversed ? max + min - currentQuestion.content.start + "—" + max : currentQuestion.content.start + "—" + max);
2024-04-23 14:45:49 +00:00
useEffect(() => {
console.log("reversed:", reversed);
}, [reversed]);
const sendAnswerToBackend = async (value: string, noUpdate = false) => {
if (!noUpdate) {
updateAnswer(currentQuestion.id, value, 0);
2024-04-23 14:45:49 +00:00
}
};
const updateValueDebounced = useDebouncedCallback(async (value: string) => {
if (reversed) {
const newValue =
window.Number(value) < window.Number(min)
? String(min)
: window.Number(value) > window.Number(max)
2024-05-31 16:41:18 +00:00
? String(max)
: value;
2024-04-23 14:45:49 +00:00
setReversedInputValue(newValue);
2024-05-31 16:41:18 +00:00
updateAnswer(currentQuestion.id, String(max + min - window.Number(newValue)), 0);
2024-04-23 14:45:49 +00:00
await sendAnswerToBackend(String(window.Number(newValue)), true);
return;
}
const newValue =
window.Number(value) < window.Number(minRange)
? minRange
: window.Number(value) > window.Number(maxRange)
2024-05-31 16:41:18 +00:00
? maxRange
: value;
2024-04-23 14:45:49 +00:00
setInputValue(newValue);
await sendAnswerToBackend(newValue);
}, 1000);
2024-05-31 16:41:18 +00:00
const updateMinRangeDebounced = useDebouncedCallback(async (value: string, crowded = false) => {
if (reversed) {
const newMinRange = crowded
? window.Number(value.split("—")[1])
: max + min - window.Number(value.split("—")[0]) < min
2024-04-23 14:45:49 +00:00
? min
: max + min - window.Number(value.split("—")[0]);
2024-05-31 16:41:18 +00:00
const newMinValue = window.Number(value.split("—")[0]) > max ? String(max) : value.split("—")[0];
2024-04-23 14:45:49 +00:00
2024-05-31 16:41:18 +00:00
setReversedMinRange(crowded ? String(max + min - window.Number(newMinValue)) : newMinValue);
updateAnswer(currentQuestion.id, `${newMinRange}${value.split("—")[1]}`, 0);
await sendAnswerToBackend(`${newMinValue}${value.split("—")[1]}`, true);
return;
}
2024-04-23 14:45:49 +00:00
2024-05-31 16:41:18 +00:00
const newMinValue = crowded
? maxRange
: window.Number(value.split("—")[0]) < min
2024-04-23 14:45:49 +00:00
? String(min)
: value.split("—")[0];
2024-05-31 16:41:18 +00:00
setMinRange(newMinValue);
await sendAnswerToBackend(`${newMinValue}${value.split("—")[1]}`);
}, 1000);
const updateMaxRangeDebounced = useDebouncedCallback(async (value: string, crowded = false) => {
if (reversed) {
const newMaxRange = crowded
? window.Number(value.split("—")[1])
: max + min - window.Number(value.split("—")[1]) > max
2024-04-23 14:45:49 +00:00
? max
: max + min - window.Number(value.split("—")[1]);
2024-05-31 16:41:18 +00:00
const newMaxValue = window.Number(value.split("—")[1]) < min ? String(min) : value.split("—")[1];
2024-04-23 14:45:49 +00:00
2024-05-31 16:41:18 +00:00
setReversedMaxRange(crowded ? String(max + min - window.Number(newMaxValue)) : newMaxValue);
updateAnswer(currentQuestion.id, `${value.split("—")[0]}${newMaxRange}`, 0);
await sendAnswerToBackend(`${value.split("—")[0]}${newMaxValue}`, true);
2024-04-23 14:45:49 +00:00
2024-05-31 16:41:18 +00:00
return;
}
const newMaxValue = crowded
? minRange
: window.Number(value.split("—")[1]) > max
2024-04-23 14:45:49 +00:00
? String(max)
: value.split("—")[1];
2024-05-31 16:41:18 +00:00
setMaxRange(newMaxValue);
await sendAnswerToBackend(`${value.split("—")[0]}${newMaxValue}`);
}, 1000);
2024-04-23 14:45:49 +00:00
useEffect(() => {
if (answer) {
if (answer.includes("—")) {
if (reversed) {
2024-05-31 16:41:18 +00:00
setReversedMinRange(String(max + min - window.Number(answer.split("—")[0])));
setReversedMaxRange(String(max + min - window.Number(answer.split("—")[1])));
2024-04-23 14:45:49 +00:00
} else {
setMinRange(answer.split("—")[0]);
setMaxRange(answer.split("—")[1]);
}
} else {
if (reversed) {
setReversedInputValue(String(max + min - window.Number(answer)));
} else {
setInputValue(answer);
}
}
}
if (!answer) {
setMinRange(String(currentQuestion.content.start));
setMaxRange(String(max));
if (currentQuestion.content.chooseRange) {
setReversedMinRange(String(currentQuestion.content.start));
setReversedMaxRange(String(min));
}
setReversedInputValue(String(currentQuestion.content.start));
setInputValue(String(currentQuestion.content.start));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
2024-04-23 14:45:49 +00:00
}, []);
const onSliderChange = (_: Event, value: number | number[]) => {
2024-05-31 16:41:18 +00:00
const range = Array.isArray(value) ? `${value[0]}${value[1]}` : String(value);
2024-04-23 14:45:49 +00:00
updateAnswer(currentQuestion.id, range, 0);
};
2024-05-31 16:41:18 +00:00
const onChangeCommitted = async (_: Event | SyntheticEvent<Element, Event>, value: number | number[]) => {
2024-04-23 14:45:49 +00:00
if (currentQuestion.content.chooseRange && Array.isArray(value)) {
if (reversed) {
const newMinReversedValue = String(max + min - value[0]);
const newMaxReversedValue = String(max + min - value[1]);
setMinRange(String(value[0]));
setMaxRange(String(value[1]));
setReversedMinRange(newMinReversedValue);
setReversedMaxRange(newMaxReversedValue);
2024-05-31 16:41:18 +00:00
await sendAnswerToBackend(`${newMinReversedValue}${newMaxReversedValue}`, true);
2024-04-23 14:45:49 +00:00
return;
}
setMinRange(String(value[0]));
setMaxRange(String(value[1]));
await sendAnswerToBackend(`${value[0]}${value[1]}`);
return;
}
if (reversed) {
setReversedInputValue(String(max + min - window.Number(value)));
} else {
setInputValue(String(value));
}
await sendAnswerToBackend(String(value));
};
const changeValueLabelFormat = (value: number) => {
if (!reversed) {
return value;
}
2024-05-31 16:41:18 +00:00
const [minSliderBorder, maxSliderBorder] = sliderValue.split("—").map(window.Number);
2024-04-23 14:45:49 +00:00
if (value === minSliderBorder) {
return max + min - minSliderBorder;
}
return max + min - maxSliderBorder;
};
const onInputChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
const value = target.value.replace(/\D/g, "");
if (reversed) {
setReversedInputValue(value);
} else {
setInputValue(value);
}
updateValueDebounced(value);
};
const onMinInputChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
const newValue = target.value.replace(/\D/g, "");
if (reversed) {
setReversedMinRange(newValue);
if (window.Number(newValue) <= window.Number(reversedMaxRange)) {
const value = max + min - window.Number(reversedMaxRange);
updateMinRangeDebounced(`${value}${value}`, true);
return;
}
2024-05-31 16:41:18 +00:00
updateMinRangeDebounced(`${newValue}${max + min - window.Number(reversedMaxRange)}`);
2024-04-23 14:45:49 +00:00
return;
}
setMinRange(newValue);
if (window.Number(newValue) >= window.Number(maxRange)) {
updateMinRangeDebounced(`${maxRange}${maxRange}`, true);
return;
}
updateMinRangeDebounced(`${newValue}${maxRange}`);
};
const onMaxInputChange = ({ target }: ChangeEvent<HTMLInputElement>) => {
const newValue = target.value.replace(/\D/g, "");
if (reversed) {
setReversedMaxRange(newValue);
if (window.Number(newValue) >= window.Number(reversedMinRange)) {
const value = max + min - window.Number(reversedMinRange);
updateMaxRangeDebounced(`${value}${value}`, true);
return;
}
2024-05-31 16:41:18 +00:00
updateMaxRangeDebounced(`${max + min - window.Number(reversedMinRange)}${newValue}`);
2024-04-23 14:45:49 +00:00
return;
}
setMaxRange(newValue);
if (window.Number(newValue) <= window.Number(minRange)) {
updateMaxRangeDebounced(`${minRange}${minRange}`, true);
return;
}
updateMaxRangeDebounced(`${minRange}${newValue}`);
};
return (
<Box>
<Typography
variant="h5"
color={theme.palette.text.primary}
sx={{ wordBreak: "break-word" }}
>
2024-04-23 14:45:49 +00:00
{currentQuestion.title}
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
width: "100%",
marginTop: "20px",
gap: "30px",
padding: "0 30px",
}}
>
<CustomSlider
value={
currentQuestion.content.chooseRange
? sliderValue.split("—").length || 0 > 1
? sliderValue.split("—").map((item) => window.Number(item))
: [min, min + 1]
: window.Number(sliderValue.split("—")[0])
}
min={min}
max={max}
step={currentQuestion.content.step || 1}
onChange={onSliderChange}
onChangeCommitted={onChangeCommitted}
valueLabelFormat={changeValueLabelFormat}
sx={{
color: theme.palette.primary.main,
"& .MuiSlider-valueLabel": {
background: theme.palette.primary.main,
borderRadius: "8px",
minWidth: "60px",
height: "36px",
},
}}
/>
{!currentQuestion.content.chooseRange && (
<CustomTextField
placeholder="0"
value={reversed ? reversedInputValue : inputValue}
onChange={onInputChange}
sx={{
maxWidth: "80px",
borderColor: theme.palette.text.primary,
"& .MuiOutlinedInput-root": { background: "transparent" },
"& .MuiInputBase-input": { textAlign: "center", zIndex: 1 },
"& .MuiOutlinedInput-notchedOutline": {
2024-05-31 16:41:18 +00:00
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
2024-04-23 14:45:49 +00:00
borderColor: "#9A9AAF",
},
}}
/>
)}
{currentQuestion.content.chooseRange && (
<Box
sx={{
display: "flex",
gap: "15px",
alignItems: "center",
"& .MuiFormControl-root": { width: "auto" },
}}
>
<CustomTextField
placeholder="0"
value={reversed ? String(reversedMinRange) : minRange}
onChange={onMinInputChange}
sx={{
maxWidth: "80px",
borderColor: theme.palette.text.primary,
"& .MuiOutlinedInput-root": { background: "transparent" },
"& .MuiInputBase-input": { textAlign: "center", zIndex: 1 },
"& .MuiOutlinedInput-notchedOutline": {
2024-05-31 16:41:18 +00:00
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
2024-04-23 14:45:49 +00:00
borderColor: "#9A9AAF",
},
}}
/>
2025-02-23 21:46:51 +00:00
<Typography color={theme.palette.text.primary}>to</Typography>
2024-04-23 14:45:49 +00:00
<CustomTextField
placeholder="0"
value={reversed ? String(reversedMaxRange) : maxRange}
onChange={onMaxInputChange}
sx={{
maxWidth: "80px",
"& .MuiOutlinedInput-root": { background: "transparent" },
"& .MuiInputBase-input": { textAlign: "center", zIndex: 1 },
"& .MuiOutlinedInput-notchedOutline": {
2024-05-31 16:41:18 +00:00
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
2024-04-23 14:45:49 +00:00
borderColor: "#9A9AAF",
},
}}
/>
</Box>
)}
</Box>
</Box>
);
};