refactor: questions
This commit is contained in:
parent
e833fa2aa6
commit
40037b38ce
@ -25,147 +25,147 @@ import { DESIGN_LIST } from "@/utils/designList";
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currentQuestion: RealTypedQuizQuestion;
|
currentQuestion: RealTypedQuizQuestion;
|
||||||
currentQuestionStepNumber: number | null;
|
currentQuestionStepNumber: number | null;
|
||||||
nextButton: ReactNode;
|
nextButton: ReactNode;
|
||||||
prevButton: ReactNode;
|
prevButton: ReactNode;
|
||||||
questionSelect: ReactNode;
|
questionSelect: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Question = ({
|
export const Question = ({
|
||||||
currentQuestion,
|
currentQuestion,
|
||||||
currentQuestionStepNumber,
|
currentQuestionStepNumber,
|
||||||
nextButton,
|
nextButton,
|
||||||
prevButton,
|
prevButton,
|
||||||
questionSelect,
|
questionSelect,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { settings, show_badge } = useQuizData();
|
const { settings, show_badge } = useQuizData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundImage: settings.cfg.design
|
||||||
|
? `url(${DESIGN_LIST[settings.cfg.theme]})`
|
||||||
|
: null,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
background: settings.cfg.design
|
||||||
|
? quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "transparent"
|
||||||
|
: "linear-gradient(90deg,#272626, transparent)"
|
||||||
|
: theme.palette.background.default,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
overflow: "auto",
|
||||||
backgroundPosition: "center",
|
width: "100%",
|
||||||
backgroundSize: "cover",
|
flexGrow: 1,
|
||||||
backgroundImage: settings.cfg.design
|
scrollbarWidth: "none",
|
||||||
? `url(${DESIGN_LIST[settings.cfg.theme]})`
|
"&::-webkit-scrollbar": {
|
||||||
: null,
|
width: 0,
|
||||||
}}
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
minHeight: "100%",
|
||||||
|
maxWidth: "1440px",
|
||||||
|
padding: "40px 25px 20px",
|
||||||
|
margin: "0 auto",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<QuestionByType
|
||||||
|
key={currentQuestion.id}
|
||||||
|
question={currentQuestion}
|
||||||
|
stepNumber={currentQuestionStepNumber}
|
||||||
|
/>
|
||||||
|
{show_badge && (
|
||||||
|
<Link
|
||||||
|
target="_blank"
|
||||||
|
href="https://quiz.pena.digital"
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
mt: "20px",
|
||||||
display: "flex",
|
alignSelf: "end",
|
||||||
flexDirection: "column",
|
|
||||||
background: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "transparent"
|
|
||||||
: "linear-gradient(90deg,#272626, transparent)"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
overflow: "hidden"
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
{quizThemes[settings.cfg.theme].isLight ? (
|
||||||
sx={{
|
<NameplateLogoFQ
|
||||||
overflow: "auto",
|
style={{
|
||||||
width: "100%",
|
fontSize: "34px",
|
||||||
flexGrow: 1,
|
width: "200px",
|
||||||
scrollbarWidth: "none",
|
height: "auto",
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: 0,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Box
|
) : (
|
||||||
sx={{
|
<NameplateLogoFQDark
|
||||||
width: "100%",
|
style={{
|
||||||
minHeight: "100%",
|
fontSize: "34px",
|
||||||
maxWidth: "1440px",
|
width: "200px",
|
||||||
padding: "40px 25px 20px",
|
height: "auto",
|
||||||
margin: "0 auto",
|
}}
|
||||||
display: "flex",
|
/>
|
||||||
flexDirection: "column",
|
)}
|
||||||
justifyContent: "space-between",
|
</Link>
|
||||||
}}
|
)}
|
||||||
>
|
</Box>
|
||||||
<QuestionByType
|
|
||||||
key={currentQuestion.id}
|
|
||||||
question={currentQuestion}
|
|
||||||
stepNumber={currentQuestionStepNumber}
|
|
||||||
/>
|
|
||||||
{show_badge && (
|
|
||||||
<Link
|
|
||||||
target="_blank"
|
|
||||||
href="https://quiz.pena.digital"
|
|
||||||
sx={{
|
|
||||||
mt: "20px",
|
|
||||||
alignSelf: "end",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{quizThemes[settings.cfg.theme].isLight ? (
|
|
||||||
<NameplateLogoFQ
|
|
||||||
style={{
|
|
||||||
fontSize: "34px",
|
|
||||||
width: "200px",
|
|
||||||
height: "auto",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<NameplateLogoFQDark
|
|
||||||
style={{
|
|
||||||
fontSize: "34px",
|
|
||||||
width: "200px",
|
|
||||||
height: "auto",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
{questionSelect}
|
|
||||||
<Footer
|
|
||||||
stepNumber={currentQuestionStepNumber}
|
|
||||||
prevButton={prevButton}
|
|
||||||
nextButton={nextButton}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
{questionSelect}
|
||||||
|
<Footer
|
||||||
|
stepNumber={currentQuestionStepNumber}
|
||||||
|
prevButton={prevButton}
|
||||||
|
nextButton={nextButton}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function QuestionByType({
|
function QuestionByType({
|
||||||
question,
|
question,
|
||||||
stepNumber,
|
stepNumber,
|
||||||
}: {
|
}: {
|
||||||
question: RealTypedQuizQuestion;
|
question: RealTypedQuizQuestion;
|
||||||
stepNumber: number | null;
|
stepNumber: number | null;
|
||||||
}) {
|
}) {
|
||||||
switch (question.type) {
|
switch (question.type) {
|
||||||
case "variant":
|
case "variant":
|
||||||
return <Variant currentQuestion={question} />;
|
return <Variant currentQuestion={question} />;
|
||||||
case "images":
|
case "images":
|
||||||
return <Images currentQuestion={question} />;
|
return <Images currentQuestion={question} />;
|
||||||
case "varimg":
|
case "varimg":
|
||||||
return <Varimg currentQuestion={question} />;
|
return <Varimg currentQuestion={question} />;
|
||||||
case "emoji":
|
case "emoji":
|
||||||
return <Emoji currentQuestion={question} />;
|
return <Emoji currentQuestion={question} />;
|
||||||
case "text":
|
case "text":
|
||||||
return <Text currentQuestion={question} stepNumber={stepNumber} />;
|
return <Text currentQuestion={question} stepNumber={stepNumber} />;
|
||||||
case "select":
|
case "select":
|
||||||
return <Select currentQuestion={question} />;
|
return <Select currentQuestion={question} />;
|
||||||
case "date":
|
case "date":
|
||||||
return <Date currentQuestion={question} />;
|
return <Date currentQuestion={question} />;
|
||||||
case "number":
|
case "number":
|
||||||
return <Number currentQuestion={question} />;
|
return <Number currentQuestion={question} />;
|
||||||
case "file":
|
case "file":
|
||||||
return <File currentQuestion={question} />;
|
return <File currentQuestion={question} />;
|
||||||
case "page":
|
case "page":
|
||||||
return <Page currentQuestion={question} />;
|
return <Page currentQuestion={question} />;
|
||||||
case "rating":
|
case "rating":
|
||||||
return <Rating currentQuestion={question} />;
|
return <Rating currentQuestion={question} />;
|
||||||
default:
|
default:
|
||||||
notReachable(question);
|
notReachable(question);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,54 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { DatePicker } from "@mui/x-date-pickers";
|
import { DatePicker } from "@mui/x-date-pickers";
|
||||||
import { Box, Typography, useTheme } from "@mui/material";
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
import type { QuizQuestionDate } from "../../../model/questionTypes/date";
|
|
||||||
import CalendarIcon from "@icons/CalendarIcon";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizViewStore } from "@/stores/quizView";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { useState } from "react";
|
import CalendarIcon from "@icons/CalendarIcon";
|
||||||
import { useQuizViewStore } from "@/stores/quizView";
|
|
||||||
|
import type { Moment } from "moment";
|
||||||
|
import type { QuizQuestionDate } from "@model/questionTypes/date";
|
||||||
|
|
||||||
type DateProps = {
|
type DateProps = {
|
||||||
currentQuestion: QuizQuestionDate;
|
currentQuestion: QuizQuestionDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Date = ({ currentQuestion }: DateProps) => {
|
export const Date = ({ currentQuestion }: DateProps) => {
|
||||||
const theme = useTheme();
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
const { settings, quizId, preview } = useQuizData();
|
const { settings, quizId, preview } = useQuizData();
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
const updateAnswer = useQuizViewStore((state) => state.updateAnswer);
|
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const theme = useTheme();
|
||||||
const answer = answers.find(
|
const answer = answers.find(
|
||||||
({ questionId }) => questionId === currentQuestion.id
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
)?.answer as string;
|
)?.answer as string;
|
||||||
const currentAnswer = moment(answer) || moment();
|
const currentAnswer = moment(answer) || moment();
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
|
const onDateChange = async (date: Moment | null) => {
|
||||||
|
if (isSending || !date) return;
|
||||||
|
|
||||||
|
setIsSending(true);
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: moment(date).format("YYYY.MM.DD"),
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(currentQuestion.id, date, 0);
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@ -57,32 +80,9 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
value={currentAnswer}
|
value={currentAnswer}
|
||||||
onChange={async (date) => {
|
onChange={onDateChange}
|
||||||
if (isSending || !date) return;
|
|
||||||
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: moment(date).format("YYYY.MM.DD"),
|
|
||||||
qid: quizId,
|
|
||||||
preview,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, date, 0);
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
}}
|
|
||||||
slotProps={{
|
slotProps={{
|
||||||
openPickerButton: {
|
openPickerButton: { sx: { p: 0 }, "data-cy": "open-datepicker" },
|
||||||
sx: {
|
|
||||||
p: 0,
|
|
||||||
},
|
|
||||||
"data-cy": "open-datepicker",
|
|
||||||
},
|
|
||||||
layout: {
|
layout: {
|
||||||
sx: { backgroundColor: theme.palette.background.default },
|
sx: { backgroundColor: theme.palette.background.default },
|
||||||
},
|
},
|
||||||
@ -99,14 +99,8 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
|||||||
borderRadius: "10px",
|
borderRadius: "10px",
|
||||||
maxWidth: "250px",
|
maxWidth: "250px",
|
||||||
pr: "30px",
|
pr: "30px",
|
||||||
"& input": {
|
"& input": { py: "11px", pl: "20px", lineHeight: "19px" },
|
||||||
py: "11px",
|
"& fieldset": { borderColor: "#9A9AAF" },
|
||||||
pl: "20px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
},
|
|
||||||
"& fieldset": {
|
|
||||||
borderColor: "#9A9AAF",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
@ -1,221 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import RadioCheck from "@ui_kit/RadioCheck";
|
|
||||||
import RadioIcon from "@ui_kit/RadioIcon";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
|
|
||||||
polyfillCountryFlagEmojis();
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
type EmojiProps = {
|
|
||||||
currentQuestion: QuizQuestionEmoji;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { quizId, settings, preview } = useQuizData();
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const deleteAnswer = useQuizViewStore(state => state.deleteAnswer);
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup
|
|
||||||
name={currentQuestion.id}
|
|
||||||
value={currentQuestion.content.variants.findIndex(
|
|
||||||
({ id }) => answer === id
|
|
||||||
)}
|
|
||||||
onChange={({ target }) => {
|
|
||||||
updateAnswer(
|
|
||||||
currentQuestion.id,
|
|
||||||
currentQuestion.content.variants[Number(target.value)].answer,
|
|
||||||
currentQuestion.content.variants[Number(target.value)].points || 0
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{ display: "flex", width: "100%", gap: "42px", flexWrap: "wrap" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.variants.map((variant, index) => (
|
|
||||||
<FormControl
|
|
||||||
key={index}
|
|
||||||
disabled={isSending}
|
|
||||||
sx={{
|
|
||||||
borderRadius: "12px",
|
|
||||||
border: `1px solid`,
|
|
||||||
borderColor:
|
|
||||||
answer === variant.id
|
|
||||||
? theme.palette.primary.main
|
|
||||||
: "#9A9AAF",
|
|
||||||
overflow: "hidden",
|
|
||||||
maxWidth: "317px",
|
|
||||||
width: "100%",
|
|
||||||
height: "255px",
|
|
||||||
background: settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "rgba(255,255,255, 0.3)" : settings.cfg.design && quizThemes[settings.cfg.theme].isLight || quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#FFFFFF"
|
|
||||||
: "transparent",
|
|
||||||
"&:hover": { borderColor: theme.palette.primary.main },
|
|
||||||
}}
|
|
||||||
// value={index}
|
|
||||||
onClick={async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (isSending) return;
|
|
||||||
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body:
|
|
||||||
currentQuestion.content.variants[index].extendedText +
|
|
||||||
" " +
|
|
||||||
currentQuestion.content.variants[index].answer,
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(
|
|
||||||
currentQuestion.id,
|
|
||||||
currentQuestion.content.variants[index].id,
|
|
||||||
currentQuestion.content.variants[index].points || 0
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (answer === currentQuestion.content.variants[index].id) {
|
|
||||||
deleteAnswer(currentQuestion.id);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: "",
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "193px",
|
|
||||||
background: "#ffffff",
|
|
||||||
cursor: "pointer"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{variant.extendedText && (
|
|
||||||
<Typography fontSize={"100px"}>
|
|
||||||
{variant.extendedText}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
sx={{
|
|
||||||
margin: 0,
|
|
||||||
padding: "15px",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
display: "flex",
|
|
||||||
gap: "10px",
|
|
||||||
alignItems:
|
|
||||||
variant.answer.length <= 60 ? "center" : "flex-start",
|
|
||||||
position: "relative",
|
|
||||||
height: "80px",
|
|
||||||
justifyContent: "center",
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
|
||||||
overflow: "auto",
|
|
||||||
// paddingLeft: "45px",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: "#b8babf",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={index}
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
checkedIcon={
|
|
||||||
<RadioCheck color={theme.palette.primary.main} />
|
|
||||||
}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "-162px",
|
|
||||||
right: "12px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
<Box sx={{ display: "flex", gap: "10px" }}>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
wordBreak: "break-word",
|
|
||||||
lineHeight: "normal",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{variant.answer}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</RadioGroup>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,179 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
Radio,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
|
import type { MouseEvent } from "react";
|
||||||
|
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||||
|
import type { QuizQuestionEmoji } from "@model/questionTypes/emoji";
|
||||||
|
|
||||||
|
polyfillCountryFlagEmojis();
|
||||||
|
|
||||||
|
type EmojiVariantProps = {
|
||||||
|
currentQuestion: QuizQuestionEmoji;
|
||||||
|
variant: QuestionVariant;
|
||||||
|
index: number;
|
||||||
|
isSending: boolean;
|
||||||
|
setIsSending: (isSending: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EmojiVariant = ({
|
||||||
|
currentQuestion,
|
||||||
|
variant,
|
||||||
|
index,
|
||||||
|
isSending,
|
||||||
|
setIsSending,
|
||||||
|
}: EmojiVariantProps) => {
|
||||||
|
const { quizId, settings, preview } = useQuizData();
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const theme = useTheme();
|
||||||
|
const { answer } =
|
||||||
|
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
|
||||||
|
const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (isSending) return;
|
||||||
|
|
||||||
|
setIsSending(true);
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body:
|
||||||
|
currentQuestion.content.variants[index].extendedText +
|
||||||
|
" " +
|
||||||
|
currentQuestion.content.variants[index].answer,
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
currentQuestion.content.variants[index].id,
|
||||||
|
currentQuestion.content.variants[index].points || 0
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answer === currentQuestion.content.variants[index].id) {
|
||||||
|
deleteAnswer(currentQuestion.id);
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: "",
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
key={index}
|
||||||
|
disabled={isSending}
|
||||||
|
sx={{
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: `1px solid`,
|
||||||
|
borderColor:
|
||||||
|
answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
||||||
|
overflow: "hidden",
|
||||||
|
maxWidth: "317px",
|
||||||
|
width: "100%",
|
||||||
|
height: "255px",
|
||||||
|
background:
|
||||||
|
settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "rgba(255,255,255, 0.3)"
|
||||||
|
: (settings.cfg.design && quizThemes[settings.cfg.theme].isLight) ||
|
||||||
|
quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "#FFFFFF"
|
||||||
|
: "transparent",
|
||||||
|
"&:hover": { borderColor: theme.palette.primary.main },
|
||||||
|
}}
|
||||||
|
// value={index}
|
||||||
|
onClick={onVariantClick}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "193px",
|
||||||
|
background: "#ffffff",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{variant.extendedText && (
|
||||||
|
<Typography fontSize="100px">{variant.extendedText}</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<FormControlLabel
|
||||||
|
key={variant.id}
|
||||||
|
sx={{
|
||||||
|
margin: 0,
|
||||||
|
padding: "15px",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
alignItems: variant.answer.length <= 60 ? "center" : "flex-start",
|
||||||
|
position: "relative",
|
||||||
|
height: "80px",
|
||||||
|
justifyContent: "center",
|
||||||
|
"& .MuiFormControlLabel-label": {
|
||||||
|
wordBreak: "break-word",
|
||||||
|
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||||
|
overflow: "auto",
|
||||||
|
"&::-webkit-scrollbar": { width: "4px" },
|
||||||
|
"&::-webkit-scrollbar-thumb": {
|
||||||
|
backgroundColor: "#b8babf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& .MuiFormControlLabel-label.Mui-disabled": {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={index}
|
||||||
|
control={
|
||||||
|
<Radio
|
||||||
|
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||||
|
icon={<RadioIcon />}
|
||||||
|
sx={{ position: "absolute", top: "-162px", right: "12px" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Box sx={{ display: "flex", gap: "10px" }}>
|
||||||
|
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>
|
||||||
|
{variant.answer}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
70
lib/components/ViewPublicationPage/questions/Emoji/index.tsx
Normal file
70
lib/components/ViewPublicationPage/questions/Emoji/index.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
|
||||||
|
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
||||||
|
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
|
||||||
|
import type { QuizQuestionEmoji } from "@model/questionTypes/emoji";
|
||||||
|
import { EmojiVariant } from "./EmojiVariant";
|
||||||
|
|
||||||
|
polyfillCountryFlagEmojis();
|
||||||
|
|
||||||
|
type EmojiProps = {
|
||||||
|
currentQuestion: QuizQuestionEmoji;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const theme = useTheme();
|
||||||
|
const { answer } =
|
||||||
|
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<RadioGroup
|
||||||
|
name={currentQuestion.id}
|
||||||
|
value={currentQuestion.content.variants.findIndex(
|
||||||
|
({ id }) => answer === id
|
||||||
|
)}
|
||||||
|
onChange={({ target }) =>
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
currentQuestion.content.variants[Number(target.value)].answer,
|
||||||
|
currentQuestion.content.variants[Number(target.value)].points || 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", width: "100%", gap: "42px", flexWrap: "wrap" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.content.variants.map((variant, index) => (
|
||||||
|
<EmojiVariant
|
||||||
|
key={variant.id}
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
variant={variant}
|
||||||
|
isSending={isSending}
|
||||||
|
setIsSending={setIsSending}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,292 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
ButtonBase,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
Skeleton,
|
|
||||||
Typography,
|
|
||||||
useTheme
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import CloseBold from "@icons/CloseBold";
|
|
||||||
import UploadIcon from "@icons/UploadIcon";
|
|
||||||
|
|
||||||
import { sendAnswer, sendFile } from "@api/quizRelase";
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import Info from "@icons/Info";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
|
||||||
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
|
||||||
import { ACCEPT_SEND_FILE_TYPES_MAP, MAX_FILE_SIZE, UPLOAD_FILE_DESCRIPTIONS_MAP } from "../tools/fileUpload";
|
|
||||||
|
|
||||||
export type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null;
|
|
||||||
|
|
||||||
type FileProps = {
|
|
||||||
currentQuestion: QuizQuestionFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const File = ({ currentQuestion }: FileProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
const { quizId, preview } = useQuizData();
|
|
||||||
const [modalWarningType, setModalWarningType] = useState<ModalWarningType>(null);
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState<boolean>(false);
|
|
||||||
const isMobile = useRootContainerSize() < 500;
|
|
||||||
|
|
||||||
const answer = answers.find(
|
|
||||||
({ questionId }) => questionId === currentQuestion.id
|
|
||||||
)?.answer as string;
|
|
||||||
|
|
||||||
const uploadFile = async (file: File | undefined) => {
|
|
||||||
if (isSending) return;
|
|
||||||
if (!file) return;
|
|
||||||
console.log(file.size);
|
|
||||||
console.log(MAX_FILE_SIZE);
|
|
||||||
if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize");
|
|
||||||
|
|
||||||
const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].some(
|
|
||||||
fileType => file.name.toLowerCase().endsWith(fileType)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isFileTypeAccepted) return setModalWarningType("errorType");
|
|
||||||
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
const data = await sendFile({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: {
|
|
||||||
file: file,
|
|
||||||
name: file.name,
|
|
||||||
preview
|
|
||||||
},
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: `https://storage.yandexcloud.net/squizanswer/${quizId}/${currentQuestion.id}/${data!.data.fileIDMap[currentQuestion.id]}`,
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, `${file.name}|${URL.createObjectURL(file)}`, 0);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
setIsDropzoneHighlighted(false);
|
|
||||||
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
|
|
||||||
uploadFile(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>{currentQuestion.title}</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
maxWidth: answer?.split("|")[0] ? "640px" : "600px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{answer?.split("|")[0] ? (
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
|
||||||
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
padding: "5px 5px 5px 16px",
|
|
||||||
backgroundColor: theme.palette.primary.main,
|
|
||||||
borderRadius: "8px",
|
|
||||||
color: "#FFFFFF",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
overflow: "hidden",
|
|
||||||
gap: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{answer?.split("|")[0]}</Typography>
|
|
||||||
<IconButton
|
|
||||||
sx={{ p: 0 }}
|
|
||||||
onClick={async () => {
|
|
||||||
if (answer.length > 0) {
|
|
||||||
setIsSending(true);
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: "",
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log(answer);
|
|
||||||
updateAnswer(currentQuestion.id, "", 0);
|
|
||||||
setIsSending(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseBold />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isSending ?
|
|
||||||
<Skeleton
|
|
||||||
variant="rounded"
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "120px",
|
|
||||||
maxWidth: "560px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
<ButtonBase
|
|
||||||
component="label"
|
|
||||||
sx={{ justifyContent: "flex-start", width: "100%" }}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
onChange={e => uploadFile(e.target.files?.[0])}
|
|
||||||
hidden
|
|
||||||
accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")}
|
|
||||||
multiple
|
|
||||||
type="file"
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
onDragEnter={() => !answer?.split("|")[0] && setIsDropzoneHighlighted(true)}
|
|
||||||
onDragLeave={() => setIsDropzoneHighlighted(false)}
|
|
||||||
onDragOver={(e) => e.preventDefault()}
|
|
||||||
onDrop={onDrop}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: isMobile ? undefined : "120px",
|
|
||||||
display: "flex",
|
|
||||||
gap: "50px",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "33px 44px 33px 55px",
|
|
||||||
backgroundColor: "#F2F3F7",
|
|
||||||
border: `1px solid ${isDropzoneHighlighted ? "red" : "#9A9AAF"}`,
|
|
||||||
borderRadius: "8px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<UploadIcon />
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: "#9A9AAF",
|
|
||||||
// color: theme.palette.grey2.main,
|
|
||||||
fontWeight: 500,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].title}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: "#9A9AAF",
|
|
||||||
// color: theme.palette.grey2.main,
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].description}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</ButtonBase>
|
|
||||||
}
|
|
||||||
<Info
|
|
||||||
sx={{ width: "40px", height: "40px" }}
|
|
||||||
color={theme.palette.primary.main}
|
|
||||||
onClick={() => setModalWarningType(currentQuestion.content.type)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{answer && currentQuestion.content.type === "picture" && (
|
|
||||||
<img
|
|
||||||
src={answer.split("|")[1]}
|
|
||||||
alt=""
|
|
||||||
style={{
|
|
||||||
marginTop: "15px",
|
|
||||||
maxWidth: "300px",
|
|
||||||
maxHeight: "300px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{answer && currentQuestion.content.type === "video" && (
|
|
||||||
<video
|
|
||||||
src={answer.split("|")[1]}
|
|
||||||
style={{
|
|
||||||
marginTop: "15px",
|
|
||||||
maxWidth: "300px",
|
|
||||||
maxHeight: "300px",
|
|
||||||
objectFit: "cover",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Modal
|
|
||||||
open={modalWarningType !== null}
|
|
||||||
onClose={() => setModalWarningType(null)}
|
|
||||||
>
|
|
||||||
<Box sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
width: isMobile ? 300 : 400,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderRadius: 3,
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
}}>
|
|
||||||
<CurrentModal status={modalWarningType} />
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CurrentModal = ({ status }: { status: ModalWarningType; }) => {
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case null: return null;
|
|
||||||
case 'errorType': return <Typography>Выбран некорректный тип файла</Typography>;
|
|
||||||
case 'errorSize': return <Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>;
|
|
||||||
default: return (
|
|
||||||
<>
|
|
||||||
<Typography>Допустимые расширения файлов:</Typography>
|
|
||||||
<Typography>{
|
|
||||||
ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
176
lib/components/ViewPublicationPage/questions/File/UploadFile.tsx
Normal file
176
lib/components/ViewPublicationPage/questions/File/UploadFile.tsx
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, ButtonBase, Skeleton, Typography, useTheme } from "@mui/material";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { sendAnswer, sendFile } from "@api/quizRelase";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ACCEPT_SEND_FILE_TYPES_MAP,
|
||||||
|
MAX_FILE_SIZE,
|
||||||
|
UPLOAD_FILE_DESCRIPTIONS_MAP,
|
||||||
|
} from "@/components/ViewPublicationPage/tools/fileUpload";
|
||||||
|
|
||||||
|
import Info from "@icons/Info";
|
||||||
|
import UploadIcon from "@icons/UploadIcon";
|
||||||
|
|
||||||
|
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
||||||
|
import type { ModalWarningType } from "./index";
|
||||||
|
|
||||||
|
type UploadFileProps = {
|
||||||
|
currentQuestion: QuizQuestionFile;
|
||||||
|
setModalWarningType: (modalType: ModalWarningType) => void;
|
||||||
|
isSending: boolean;
|
||||||
|
setIsSending: (isSending: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UploadFile = ({
|
||||||
|
currentQuestion,
|
||||||
|
setModalWarningType,
|
||||||
|
isSending,
|
||||||
|
setIsSending,
|
||||||
|
}: UploadFileProps) => {
|
||||||
|
const { quizId, preview } = useQuizData();
|
||||||
|
const [isDropzoneHighlighted, setIsDropzoneHighlighted] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
const theme = useTheme();
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const isMobile = useRootContainerSize() < 500;
|
||||||
|
|
||||||
|
const answer = answers.find(
|
||||||
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
|
)?.answer as string;
|
||||||
|
|
||||||
|
const uploadFile = async (file: File | undefined) => {
|
||||||
|
if (isSending) return;
|
||||||
|
if (!file) return;
|
||||||
|
console.log(file.size);
|
||||||
|
console.log(MAX_FILE_SIZE);
|
||||||
|
if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize");
|
||||||
|
|
||||||
|
const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP[
|
||||||
|
currentQuestion.content.type
|
||||||
|
].some((fileType) => file.name.toLowerCase().endsWith(fileType));
|
||||||
|
|
||||||
|
if (!isFileTypeAccepted) return setModalWarningType("errorType");
|
||||||
|
|
||||||
|
setIsSending(true);
|
||||||
|
try {
|
||||||
|
const data = await sendFile({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: {
|
||||||
|
file: file,
|
||||||
|
name: file.name,
|
||||||
|
preview,
|
||||||
|
},
|
||||||
|
qid: quizId,
|
||||||
|
});
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: `https://storage.yandexcloud.net/squizanswer/${quizId}/${
|
||||||
|
currentQuestion.id
|
||||||
|
}/${data!.data.fileIDMap[currentQuestion.id]}`,
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
`${file.name}|${URL.createObjectURL(file)}`,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setIsDropzoneHighlighted(false);
|
||||||
|
|
||||||
|
const file = event.dataTransfer.files[0];
|
||||||
|
|
||||||
|
uploadFile(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
{isSending ? (
|
||||||
|
<Skeleton
|
||||||
|
variant="rounded"
|
||||||
|
sx={{ width: "100%", height: "120px", maxWidth: "560px" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ButtonBase
|
||||||
|
component="label"
|
||||||
|
sx={{ justifyContent: "flex-start", width: "100%" }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
onChange={({ target }) => uploadFile(target.files?.[0])}
|
||||||
|
hidden
|
||||||
|
accept={ACCEPT_SEND_FILE_TYPES_MAP[
|
||||||
|
currentQuestion.content.type
|
||||||
|
].join(",")}
|
||||||
|
multiple
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
onDragEnter={() =>
|
||||||
|
!answer?.split("|")[0] && setIsDropzoneHighlighted(true)
|
||||||
|
}
|
||||||
|
onDragLeave={() => setIsDropzoneHighlighted(false)}
|
||||||
|
onDragOver={(event) => event.preventDefault()}
|
||||||
|
onDrop={onDrop}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: isMobile ? undefined : "120px",
|
||||||
|
display: "flex",
|
||||||
|
gap: "50px",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "33px 44px 33px 55px",
|
||||||
|
backgroundColor: "#F2F3F7",
|
||||||
|
border: `1px solid ${isDropzoneHighlighted ? "red" : "#9A9AAF"}`,
|
||||||
|
borderRadius: "8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UploadIcon />
|
||||||
|
<Box>
|
||||||
|
<Typography sx={{ color: "#9A9AAF", fontWeight: 500 }}>
|
||||||
|
{
|
||||||
|
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
||||||
|
.title
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#9A9AAF",
|
||||||
|
fontSize: "16px",
|
||||||
|
lineHeight: "19px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
||||||
|
.description
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</ButtonBase>
|
||||||
|
)}
|
||||||
|
<Info
|
||||||
|
sx={{ width: "40px", height: "40px" }}
|
||||||
|
color={theme.palette.primary.main}
|
||||||
|
onClick={() => setModalWarningType(currentQuestion.content.type)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,75 @@
|
|||||||
|
import { Box, IconButton, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
|
||||||
|
import CloseBold from "@icons/CloseBold";
|
||||||
|
|
||||||
|
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
||||||
|
|
||||||
|
type UploadedFileProps = {
|
||||||
|
currentQuestion: QuizQuestionFile;
|
||||||
|
setIsSending: (isSending: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UploadedFile = ({
|
||||||
|
currentQuestion,
|
||||||
|
setIsSending,
|
||||||
|
}: UploadedFileProps) => {
|
||||||
|
const { quizId, preview } = useQuizData();
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const answer = answers.find(
|
||||||
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
|
)?.answer as string;
|
||||||
|
|
||||||
|
const deleteFile = async () => {
|
||||||
|
if (answer.length > 0) {
|
||||||
|
setIsSending(true);
|
||||||
|
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: "",
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAnswer(currentQuestion.id, "", 0);
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
||||||
|
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: "5px 5px 5px 16px",
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: "8px",
|
||||||
|
color: "#FFFFFF",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
overflow: "hidden",
|
||||||
|
gap: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{answer?.split("|")[0]}
|
||||||
|
</Typography>
|
||||||
|
<IconButton sx={{ p: 0 }} onClick={deleteFile}>
|
||||||
|
<CloseBold />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
134
lib/components/ViewPublicationPage/questions/File/index.tsx
Normal file
134
lib/components/ViewPublicationPage/questions/File/index.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, Modal, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import { UploadFile } from "./UploadFile";
|
||||||
|
import { UploadedFile } from "./UploadedFile";
|
||||||
|
|
||||||
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
|
||||||
|
import { ACCEPT_SEND_FILE_TYPES_MAP } from "@/components/ViewPublicationPage/tools/fileUpload";
|
||||||
|
|
||||||
|
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
||||||
|
|
||||||
|
export type ModalWarningType =
|
||||||
|
| "errorType"
|
||||||
|
| "errorSize"
|
||||||
|
| "picture"
|
||||||
|
| "video"
|
||||||
|
| "audio"
|
||||||
|
| "document"
|
||||||
|
| null;
|
||||||
|
|
||||||
|
type FileProps = {
|
||||||
|
currentQuestion: QuizQuestionFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const File = ({ currentQuestion }: FileProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const [modalWarningType, setModalWarningType] =
|
||||||
|
useState<ModalWarningType>(null);
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
const isMobile = useRootContainerSize() < 500;
|
||||||
|
|
||||||
|
const answer = answers.find(
|
||||||
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
|
)?.answer as string;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
maxWidth: answer?.split("|")[0] ? "640px" : "600px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{answer?.split("|")[0] ? (
|
||||||
|
<UploadedFile
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
setIsSending={setIsSending}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<UploadFile
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
setModalWarningType={setModalWarningType}
|
||||||
|
isSending={isSending}
|
||||||
|
setIsSending={setIsSending}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{answer && currentQuestion.content.type === "picture" && (
|
||||||
|
<img
|
||||||
|
src={answer.split("|")[1]}
|
||||||
|
style={{ marginTop: "15px", maxWidth: "300px", maxHeight: "300px" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{answer && currentQuestion.content.type === "video" && (
|
||||||
|
<video
|
||||||
|
src={answer.split("|")[1]}
|
||||||
|
style={{
|
||||||
|
marginTop: "15px",
|
||||||
|
maxWidth: "300px",
|
||||||
|
maxHeight: "300px",
|
||||||
|
objectFit: "cover",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Modal
|
||||||
|
open={modalWarningType !== null}
|
||||||
|
onClose={() => setModalWarningType(null)}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
width: isMobile ? 300 : 400,
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
borderRadius: 3,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CurrentModal status={modalWarningType} />
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CurrentModal = ({ status }: { status: ModalWarningType }) => {
|
||||||
|
switch (status) {
|
||||||
|
case null:
|
||||||
|
return null;
|
||||||
|
case "errorType":
|
||||||
|
return <Typography>Выбран некорректный тип файла</Typography>;
|
||||||
|
case "errorSize":
|
||||||
|
return (
|
||||||
|
<Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography>Допустимые расширения файлов:</Typography>
|
||||||
|
<Typography>
|
||||||
|
{ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -1,195 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
FormControlLabel,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
import RadioCheck from "@ui_kit/RadioCheck";
|
|
||||||
import RadioIcon from "@ui_kit/RadioIcon";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
|
||||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
|
|
||||||
type ImagesProps = {
|
|
||||||
currentQuestion: QuizQuestionImages;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Images = ({ currentQuestion }: ImagesProps) => {
|
|
||||||
const { quizId, preview } = useQuizData();
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const deleteAnswer = useQuizViewStore(state => state.deleteAnswer);
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
const theme = useTheme();
|
|
||||||
const answer = answers.find(
|
|
||||||
({ questionId }) => questionId === currentQuestion.id
|
|
||||||
)?.answer;
|
|
||||||
const { settings } = useQuizData();
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
const isTablet = useRootContainerSize() < 1000;
|
|
||||||
const isMobile = useRootContainerSize() < 500;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<RadioGroup
|
|
||||||
name={currentQuestion.id}
|
|
||||||
value={currentQuestion.content.variants.findIndex(
|
|
||||||
({ id }) => answer === id
|
|
||||||
)}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "grid",
|
|
||||||
gap: "15px",
|
|
||||||
gridTemplateColumns: isTablet
|
|
||||||
? isMobile
|
|
||||||
? "repeat(1, 1fr)"
|
|
||||||
: "repeat(2, 1fr)"
|
|
||||||
: "repeat(3, 1fr)",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.variants.map((variant, index) => (
|
|
||||||
<Box
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
cursor: "pointer",
|
|
||||||
borderRadius: "12px",
|
|
||||||
border: `1px solid`,
|
|
||||||
borderColor:
|
|
||||||
answer === variant.id
|
|
||||||
? theme.palette.primary.main
|
|
||||||
: "#9A9AAF",
|
|
||||||
"&:hover": { borderColor: theme.palette.primary.main },
|
|
||||||
background: settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "rgba(255,255,255, 0.3)" : settings.cfg.design && quizThemes[settings.cfg.theme].isLight || quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#FFFFFF"
|
|
||||||
: "transparent",
|
|
||||||
}}
|
|
||||||
onClick={async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (isSending) return;
|
|
||||||
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
updateAnswer(
|
|
||||||
currentQuestion.id,
|
|
||||||
currentQuestion.content.variants[index].id,
|
|
||||||
currentQuestion.content.variants[index].points || 0
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (answer === currentQuestion.content.variants[index].id) {
|
|
||||||
deleteAnswer(currentQuestion.id);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: "",
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
|
||||||
<Box sx={{ width: "100%", height: "300px" }}>
|
|
||||||
{variant.extendedText && (
|
|
||||||
<img
|
|
||||||
src={variant.extendedText}
|
|
||||||
alt=""
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
borderRadius: "12px 12px 0 0"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
sx={{
|
|
||||||
textAlign: "center",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
marginTop: "10px",
|
|
||||||
marginLeft: 0,
|
|
||||||
padding: "10px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems:
|
|
||||||
variant.answer.length <= 60 ? "center" : "flex-start",
|
|
||||||
justifyContent: "center",
|
|
||||||
position: "relative",
|
|
||||||
height: "80px",
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
|
||||||
overflow: "auto",
|
|
||||||
lineHeight: "normal",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: "#b8babf",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
value={index}
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
checkedIcon={
|
|
||||||
<RadioCheck color={theme.palette.primary.main} />
|
|
||||||
}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "-297px",
|
|
||||||
right: 0
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={variant.answer}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</RadioGroup>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,156 @@
|
|||||||
|
import { Box, FormControlLabel, Radio, useTheme } from "@mui/material";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
|
import type { MouseEvent } from "react";
|
||||||
|
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||||
|
import type { QuizQuestionImages } from "@model/questionTypes/images";
|
||||||
|
|
||||||
|
type ImagesProps = {
|
||||||
|
currentQuestion: QuizQuestionImages;
|
||||||
|
variant: QuestionVariant;
|
||||||
|
isSending: boolean;
|
||||||
|
setIsSending: (isSending: boolean) => void;
|
||||||
|
index: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ImageVariant = ({
|
||||||
|
currentQuestion,
|
||||||
|
variant,
|
||||||
|
isSending,
|
||||||
|
setIsSending,
|
||||||
|
index,
|
||||||
|
}: ImagesProps) => {
|
||||||
|
const { quizId, preview } = useQuizData();
|
||||||
|
const { settings } = useQuizData();
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const theme = useTheme();
|
||||||
|
const answer = answers.find(
|
||||||
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
|
)?.answer;
|
||||||
|
|
||||||
|
const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (isSending) return;
|
||||||
|
|
||||||
|
setIsSending(true);
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
currentQuestion.content.variants[index].id,
|
||||||
|
currentQuestion.content.variants[index].points || 0
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answer === currentQuestion.content.variants[index].id) {
|
||||||
|
deleteAnswer(currentQuestion.id);
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: "",
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: `1px solid`,
|
||||||
|
borderColor:
|
||||||
|
answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
||||||
|
"&:hover": { borderColor: theme.palette.primary.main },
|
||||||
|
background:
|
||||||
|
settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "rgba(255,255,255, 0.3)"
|
||||||
|
: (settings.cfg.design && quizThemes[settings.cfg.theme].isLight) ||
|
||||||
|
quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "#FFFFFF"
|
||||||
|
: "transparent",
|
||||||
|
}}
|
||||||
|
onClick={onVariantClick}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||||
|
<Box sx={{ width: "100%", height: "300px" }}>
|
||||||
|
{variant.extendedText && (
|
||||||
|
<img
|
||||||
|
src={variant.extendedText}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
borderRadius: "12px 12px 0 0",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<FormControlLabel
|
||||||
|
key={variant.id}
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
marginTop: "10px",
|
||||||
|
marginLeft: 0,
|
||||||
|
padding: "10px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: variant.answer.length <= 60 ? "center" : "flex-start",
|
||||||
|
justifyContent: "center",
|
||||||
|
position: "relative",
|
||||||
|
height: "80px",
|
||||||
|
"& .MuiFormControlLabel-label": {
|
||||||
|
wordBreak: "break-word",
|
||||||
|
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||||
|
overflow: "auto",
|
||||||
|
lineHeight: "normal",
|
||||||
|
"&::-webkit-scrollbar": {
|
||||||
|
width: "4px",
|
||||||
|
},
|
||||||
|
"&::-webkit-scrollbar-thumb": {
|
||||||
|
backgroundColor: "#b8babf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={index}
|
||||||
|
control={
|
||||||
|
<Radio
|
||||||
|
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||||
|
icon={<RadioIcon />}
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "-297px",
|
||||||
|
right: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={variant.answer}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,73 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import { ImageVariant } from "./ImageVariant";
|
||||||
|
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
|
|
||||||
|
import type { QuizQuestionImages } from "@model/questionTypes/images";
|
||||||
|
|
||||||
|
type ImagesProps = {
|
||||||
|
currentQuestion: QuizQuestionImages;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Images = ({ currentQuestion }: ImagesProps) => {
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const theme = useTheme();
|
||||||
|
const answer = answers.find(
|
||||||
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
|
)?.answer;
|
||||||
|
const isTablet = useRootContainerSize() < 1000;
|
||||||
|
const isMobile = useRootContainerSize() < 500;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<RadioGroup
|
||||||
|
name={currentQuestion.id}
|
||||||
|
value={currentQuestion.content.variants.findIndex(
|
||||||
|
({ id }) => answer === id
|
||||||
|
)}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "grid",
|
||||||
|
gap: "15px",
|
||||||
|
gridTemplateColumns: isTablet
|
||||||
|
? isMobile
|
||||||
|
? "repeat(1, 1fr)"
|
||||||
|
: "repeat(2, 1fr)"
|
||||||
|
: "repeat(3, 1fr)",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentQuestion.content.variants.map((variant, index) => (
|
||||||
|
<ImageVariant
|
||||||
|
key={variant.id}
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
variant={variant}
|
||||||
|
isSending={isSending}
|
||||||
|
setIsSending={setIsSending}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,477 +0,0 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
|
||||||
|
|
||||||
import { CustomSlider } from "@ui_kit/CustomSlider";
|
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
|
||||||
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import type { QuizQuestionNumber } from "@model/questionTypes/number";
|
|
||||||
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
|
|
||||||
import type { ChangeEvent, SyntheticEvent } from "react";
|
|
||||||
|
|
||||||
type NumberProps = {
|
|
||||||
currentQuestion: QuizQuestionNumber;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Number = ({ currentQuestion }: NumberProps) => {
|
|
||||||
const { settings, quizId, preview } = useQuizData();
|
|
||||||
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");
|
|
||||||
const [reversedMaxRange, setReversedMaxRange] =
|
|
||||||
useState<string>("100000000000");
|
|
||||||
const theme = useTheme();
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const deleteAnswer = useQuizViewStore(state => state.deleteAnswer);
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
const [minBorder, maxBorder] = currentQuestion.content.range
|
|
||||||
.split("—")
|
|
||||||
.map(window.Number);
|
|
||||||
const min = minBorder < maxBorder ? minBorder : maxBorder;
|
|
||||||
const max = minBorder < maxBorder ? maxBorder : minBorder;
|
|
||||||
const reversed = minBorder > maxBorder;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("reversed:", reversed);
|
|
||||||
}, [reversed]);
|
|
||||||
|
|
||||||
const sendAnswerToBackend = async (value: string, noUpdate = false) => {
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: value,
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!noUpdate) {
|
|
||||||
updateAnswer(currentQuestion.id, value, 0);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateValueDebounced = useDebouncedCallback(async (value: string) => {
|
|
||||||
if (reversed) {
|
|
||||||
const newValue =
|
|
||||||
window.Number(value) < window.Number(min)
|
|
||||||
? String(min)
|
|
||||||
: window.Number(value) > window.Number(max)
|
|
||||||
? String(max)
|
|
||||||
: value;
|
|
||||||
|
|
||||||
setReversedInputValue(newValue);
|
|
||||||
updateAnswer(
|
|
||||||
currentQuestion.id,
|
|
||||||
String(max + min - window.Number(newValue)),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
await sendAnswerToBackend(String(window.Number(newValue)), true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newValue =
|
|
||||||
window.Number(value) < window.Number(minRange)
|
|
||||||
? minRange
|
|
||||||
: window.Number(value) > window.Number(maxRange)
|
|
||||||
? maxRange
|
|
||||||
: value;
|
|
||||||
|
|
||||||
setInputValue(newValue);
|
|
||||||
await sendAnswerToBackend(newValue);
|
|
||||||
}, 1000);
|
|
||||||
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
|
|
||||||
? min
|
|
||||||
: max + min - window.Number(value.split("—")[0]);
|
|
||||||
|
|
||||||
const newMinValue =
|
|
||||||
window.Number(value.split("—")[0]) > max
|
|
||||||
? String(max)
|
|
||||||
: value.split("—")[0];
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMinValue = crowded
|
|
||||||
? maxRange
|
|
||||||
: window.Number(value.split("—")[0]) < min
|
|
||||||
? String(min)
|
|
||||||
: value.split("—")[0];
|
|
||||||
|
|
||||||
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
|
|
||||||
? max
|
|
||||||
: max + min - window.Number(value.split("—")[1]);
|
|
||||||
|
|
||||||
const newMaxValue =
|
|
||||||
window.Number(value.split("—")[1]) < min
|
|
||||||
? String(min)
|
|
||||||
: value.split("—")[1];
|
|
||||||
|
|
||||||
setReversedMaxRange(
|
|
||||||
crowded ? String(max + min - window.Number(newMaxValue)) : newMaxValue
|
|
||||||
);
|
|
||||||
updateAnswer(
|
|
||||||
currentQuestion.id,
|
|
||||||
`${value.split("—")[0]}—${newMaxRange}`,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
await sendAnswerToBackend(
|
|
||||||
`${value.split("—")[0]}—${newMaxValue}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMaxValue = crowded
|
|
||||||
? minRange
|
|
||||||
: window.Number(value.split("—")[1]) > max
|
|
||||||
? String(max)
|
|
||||||
: value.split("—")[1];
|
|
||||||
|
|
||||||
setMaxRange(newMaxValue);
|
|
||||||
await sendAnswerToBackend(`${value.split("—")[0]}—${newMaxValue}`);
|
|
||||||
},
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
const answer = answers.find(
|
|
||||||
({ questionId }) => questionId === currentQuestion.id
|
|
||||||
)?.answer as string;
|
|
||||||
|
|
||||||
const sliderValue =
|
|
||||||
answer ||
|
|
||||||
(reversed
|
|
||||||
? max + min - currentQuestion.content.start + "—" + max
|
|
||||||
: currentQuestion.content.start + "—" + max);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (answer) {
|
|
||||||
if (answer.includes("—")) {
|
|
||||||
if (reversed) {
|
|
||||||
setReversedMinRange(
|
|
||||||
String(max + min - window.Number(answer.split("—")[0]))
|
|
||||||
);
|
|
||||||
setReversedMaxRange(
|
|
||||||
String(max + min - window.Number(answer.split("—")[1]))
|
|
||||||
);
|
|
||||||
} 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));
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onSliderChange = (_: Event, value: number | number[]) => {
|
|
||||||
const range = Array.isArray(value)
|
|
||||||
? `${value[0]}—${value[1]}`
|
|
||||||
: String(value);
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, range, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChangeCommitted = async (
|
|
||||||
_: Event | SyntheticEvent<Element, Event>,
|
|
||||||
value: number | number[]
|
|
||||||
) => {
|
|
||||||
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);
|
|
||||||
await sendAnswerToBackend(
|
|
||||||
`${newMinReversedValue}—${newMaxReversedValue}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [minSliderBorder, maxSliderBorder] = sliderValue
|
|
||||||
.split("—")
|
|
||||||
.map(window.Number);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMinRangeDebounced(
|
|
||||||
`${newValue}—${max + min - window.Number(reversedMaxRange)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMaxRangeDebounced(
|
|
||||||
`${max + min - window.Number(reversedMinRange)}—${newValue}`
|
|
||||||
);
|
|
||||||
|
|
||||||
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" }}
|
|
||||||
>
|
|
||||||
{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": {
|
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
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": {
|
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
borderColor: "#9A9AAF"
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Typography color={theme.palette.text.primary}>до</Typography>
|
|
||||||
<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": {
|
|
||||||
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
borderColor: "#9A9AAF"
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
477
lib/components/ViewPublicationPage/questions/Number/index.tsx
Normal file
477
lib/components/ViewPublicationPage/questions/Number/index.tsx
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
|
import { CustomSlider } from "@ui_kit/CustomSlider";
|
||||||
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import type { QuizQuestionNumber } from "@model/questionTypes/number";
|
||||||
|
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
|
import type { ChangeEvent, SyntheticEvent } from "react";
|
||||||
|
|
||||||
|
type NumberProps = {
|
||||||
|
currentQuestion: QuizQuestionNumber;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Number = ({ currentQuestion }: NumberProps) => {
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
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");
|
||||||
|
const [reversedMaxRange, setReversedMaxRange] =
|
||||||
|
useState<string>("100000000000");
|
||||||
|
const { settings, quizId, preview } = useQuizData();
|
||||||
|
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [minBorder, maxBorder] = currentQuestion.content.range
|
||||||
|
.split("—")
|
||||||
|
.map(window.Number);
|
||||||
|
const min = minBorder < maxBorder ? minBorder : maxBorder;
|
||||||
|
const max = minBorder < maxBorder ? maxBorder : minBorder;
|
||||||
|
const reversed = minBorder > maxBorder;
|
||||||
|
|
||||||
|
const answer = answers.find(
|
||||||
|
({ questionId }) => questionId === currentQuestion.id
|
||||||
|
)?.answer as string;
|
||||||
|
|
||||||
|
const sliderValue =
|
||||||
|
answer ||
|
||||||
|
(reversed
|
||||||
|
? max + min - currentQuestion.content.start + "—" + max
|
||||||
|
: currentQuestion.content.start + "—" + max);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("reversed:", reversed);
|
||||||
|
}, [reversed]);
|
||||||
|
|
||||||
|
const sendAnswerToBackend = async (value: string, noUpdate = false) => {
|
||||||
|
setIsSending(true);
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: value,
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!noUpdate) {
|
||||||
|
updateAnswer(currentQuestion.id, value, 0);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateValueDebounced = useDebouncedCallback(async (value: string) => {
|
||||||
|
if (reversed) {
|
||||||
|
const newValue =
|
||||||
|
window.Number(value) < window.Number(min)
|
||||||
|
? String(min)
|
||||||
|
: window.Number(value) > window.Number(max)
|
||||||
|
? String(max)
|
||||||
|
: value;
|
||||||
|
|
||||||
|
setReversedInputValue(newValue);
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
String(max + min - window.Number(newValue)),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
await sendAnswerToBackend(String(window.Number(newValue)), true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue =
|
||||||
|
window.Number(value) < window.Number(minRange)
|
||||||
|
? minRange
|
||||||
|
: window.Number(value) > window.Number(maxRange)
|
||||||
|
? maxRange
|
||||||
|
: value;
|
||||||
|
|
||||||
|
setInputValue(newValue);
|
||||||
|
await sendAnswerToBackend(newValue);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
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
|
||||||
|
? min
|
||||||
|
: max + min - window.Number(value.split("—")[0]);
|
||||||
|
|
||||||
|
const newMinValue =
|
||||||
|
window.Number(value.split("—")[0]) > max
|
||||||
|
? String(max)
|
||||||
|
: value.split("—")[0];
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMinValue = crowded
|
||||||
|
? maxRange
|
||||||
|
: window.Number(value.split("—")[0]) < min
|
||||||
|
? String(min)
|
||||||
|
: value.split("—")[0];
|
||||||
|
|
||||||
|
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
|
||||||
|
? max
|
||||||
|
: max + min - window.Number(value.split("—")[1]);
|
||||||
|
|
||||||
|
const newMaxValue =
|
||||||
|
window.Number(value.split("—")[1]) < min
|
||||||
|
? String(min)
|
||||||
|
: value.split("—")[1];
|
||||||
|
|
||||||
|
setReversedMaxRange(
|
||||||
|
crowded ? String(max + min - window.Number(newMaxValue)) : newMaxValue
|
||||||
|
);
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
`${value.split("—")[0]}—${newMaxRange}`,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
await sendAnswerToBackend(
|
||||||
|
`${value.split("—")[0]}—${newMaxValue}`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMaxValue = crowded
|
||||||
|
? minRange
|
||||||
|
: window.Number(value.split("—")[1]) > max
|
||||||
|
? String(max)
|
||||||
|
: value.split("—")[1];
|
||||||
|
|
||||||
|
setMaxRange(newMaxValue);
|
||||||
|
await sendAnswerToBackend(`${value.split("—")[0]}—${newMaxValue}`);
|
||||||
|
},
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (answer) {
|
||||||
|
if (answer.includes("—")) {
|
||||||
|
if (reversed) {
|
||||||
|
setReversedMinRange(
|
||||||
|
String(max + min - window.Number(answer.split("—")[0]))
|
||||||
|
);
|
||||||
|
setReversedMaxRange(
|
||||||
|
String(max + min - window.Number(answer.split("—")[1]))
|
||||||
|
);
|
||||||
|
} 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));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSliderChange = (_: Event, value: number | number[]) => {
|
||||||
|
const range = Array.isArray(value)
|
||||||
|
? `${value[0]}—${value[1]}`
|
||||||
|
: String(value);
|
||||||
|
|
||||||
|
updateAnswer(currentQuestion.id, range, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeCommitted = async (
|
||||||
|
_: Event | SyntheticEvent<Element, Event>,
|
||||||
|
value: number | number[]
|
||||||
|
) => {
|
||||||
|
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);
|
||||||
|
await sendAnswerToBackend(
|
||||||
|
`${newMinReversedValue}—${newMaxReversedValue}`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [minSliderBorder, maxSliderBorder] = sliderValue
|
||||||
|
.split("—")
|
||||||
|
.map(window.Number);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMinRangeDebounced(
|
||||||
|
`${newValue}—${max + min - window.Number(reversedMaxRange)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMaxRangeDebounced(
|
||||||
|
`${max + min - window.Number(reversedMinRange)}—${newValue}`
|
||||||
|
);
|
||||||
|
|
||||||
|
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" }}
|
||||||
|
>
|
||||||
|
{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": {
|
||||||
|
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "white"
|
||||||
|
: theme.palette.background.default,
|
||||||
|
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": {
|
||||||
|
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "white"
|
||||||
|
: theme.palette.background.default,
|
||||||
|
borderColor: "#9A9AAF",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography color={theme.palette.text.primary}>до</Typography>
|
||||||
|
<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": {
|
||||||
|
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "white"
|
||||||
|
: theme.palette.background.default,
|
||||||
|
borderColor: "#9A9AAF",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,54 +0,0 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
|
||||||
import YoutubeEmbedIframe from "../tools/YoutubeEmbedIframe";
|
|
||||||
|
|
||||||
type PageProps = {
|
|
||||||
currentQuestion: QuizQuestionPage;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Page = ({ currentQuestion }: PageProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography variant="h5" sx={{ paddingBottom: "25px", color: theme.palette.text.primary, wordBreak: "break-word"}} >{currentQuestion.title}</Typography>
|
|
||||||
<Typography color={theme.palette.text.primary} sx={{wordBreak: "break-word"}}>{currentQuestion.content.text}</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
currentQuestion.content.useImage ? (
|
|
||||||
<Box sx={{ borderRadius: "12px", border: "1px solid #9A9AAF", overflow: "hidden" }}>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
alt=""
|
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "contain",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<YoutubeEmbedIframe
|
|
||||||
containerSX={{
|
|
||||||
width: "100%",
|
|
||||||
height: "calc(100% - 270px)",
|
|
||||||
maxHeight: "80%",
|
|
||||||
objectFit: "contain",
|
|
||||||
}}
|
|
||||||
videoUrl={currentQuestion.content.video}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
74
lib/components/ViewPublicationPage/questions/Page/index.tsx
Normal file
74
lib/components/ViewPublicationPage/questions/Page/index.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import YoutubeEmbedIframe from "@/components/ViewPublicationPage/tools/YoutubeEmbedIframe";
|
||||||
|
|
||||||
|
import type { QuizQuestionPage } from "@model/questionTypes/page";
|
||||||
|
|
||||||
|
type PageProps = {
|
||||||
|
currentQuestion: QuizQuestionPage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Page = ({ currentQuestion }: PageProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
paddingBottom: "25px",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
wordBreak: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.content.text}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentQuestion.content.useImage ? (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid #9A9AAF",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
key={currentQuestion.id}
|
||||||
|
src={currentQuestion.content.back}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "contain",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<YoutubeEmbedIframe
|
||||||
|
containerSX={{
|
||||||
|
width: "100%",
|
||||||
|
height: "calc(100% - 270px)",
|
||||||
|
maxHeight: "80%",
|
||||||
|
objectFit: "contain",
|
||||||
|
}}
|
||||||
|
videoUrl={currentQuestion.content.video}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,148 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
Rating as RatingComponent,
|
|
||||||
Typography,
|
|
||||||
useTheme
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import FlagIcon from "@icons/questionsPage/FlagIcon";
|
|
||||||
import StarIconMini from "@icons/questionsPage/StarIconMini";
|
|
||||||
import HashtagIcon from "@icons/questionsPage/hashtagIcon";
|
|
||||||
import HeartIcon from "@icons/questionsPage/heartIcon";
|
|
||||||
import LightbulbIcon from "@icons/questionsPage/lightbulbIcon";
|
|
||||||
import LikeIcon from "@icons/questionsPage/likeIcon";
|
|
||||||
import TropfyIcon from "@icons/questionsPage/tropfyIcon";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
|
||||||
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
type RatingProps = {
|
|
||||||
currentQuestion: QuizQuestionRating;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonRatingForm = [
|
|
||||||
{
|
|
||||||
name: "star",
|
|
||||||
icon: (color: string, width: number) => <StarIconMini width={width} color={color} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "trophie",
|
|
||||||
icon: (color: string, width: number) => <TropfyIcon width={width} color={color} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "flag",
|
|
||||||
icon: (color: string, width: number) => <FlagIcon width={width} color={color} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "heart",
|
|
||||||
icon: (color: string, width: number) => <HeartIcon width={width} color={color} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "like",
|
|
||||||
icon: (color: string, width: number) => <LikeIcon width={width} color={color} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bubble",
|
|
||||||
icon: (color: string, width: number) => <LightbulbIcon width={width} color={color} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "hashtag",
|
|
||||||
icon: (color: string, width: number) => <HashtagIcon width={width} color={color} />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const Rating = ({ currentQuestion }: RatingProps) => {
|
|
||||||
const { quizId, preview } = useQuizData();
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
const isTablet = useRootContainerSize() < 750;
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
const { answer } =
|
|
||||||
answers.find(
|
|
||||||
({ questionId }) => questionId === currentQuestion.id
|
|
||||||
) ?? {};
|
|
||||||
const form = buttonRatingForm.find(
|
|
||||||
({ name }) => name === currentQuestion.content.form
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>{currentQuestion.title}</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "20px",
|
|
||||||
marginTop: "20px",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "inline-block",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RatingComponent
|
|
||||||
disabled={isSending}
|
|
||||||
value={Number(answer || 0)}
|
|
||||||
onChange={async (_, value) => {
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: String(value) + " из " + currentQuestion.content.steps,
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, String(value), 0);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
height: "50px",
|
|
||||||
opacity: "1!important",
|
|
||||||
"& .MuiRating-root.Mui-disabled": { opacity: "1!important" },
|
|
||||||
"& .MuiRating-icon": {mr: isMobile ? undefined : "15px"}
|
|
||||||
}}
|
|
||||||
max={currentQuestion.content.steps}
|
|
||||||
icon={form?.icon(theme.palette.primary.main, isMobile ? 30 : isTablet ? 40 : 50)}
|
|
||||||
emptyIcon={form?.icon("#9A9AAF", isMobile ? 30 : isTablet ? 40 : 50)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
gap: 2,
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography sx={{
|
|
||||||
color: "#9A9AAF"
|
|
||||||
}}>
|
|
||||||
{currentQuestion.content.ratingNegativeDescription}
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ color: "#9A9AAF" }}>
|
|
||||||
{currentQuestion.content.ratingPositiveDescription}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
};
|
|
165
lib/components/ViewPublicationPage/questions/Rating/index.tsx
Normal file
165
lib/components/ViewPublicationPage/questions/Rating/index.tsx
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Rating as RatingComponent,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
|
||||||
|
import FlagIcon from "@icons/questionsPage/FlagIcon";
|
||||||
|
import StarIconMini from "@icons/questionsPage/StarIconMini";
|
||||||
|
import HashtagIcon from "@icons/questionsPage/hashtagIcon";
|
||||||
|
import HeartIcon from "@icons/questionsPage/heartIcon";
|
||||||
|
import LightbulbIcon from "@icons/questionsPage/lightbulbIcon";
|
||||||
|
import LikeIcon from "@icons/questionsPage/likeIcon";
|
||||||
|
import TropfyIcon from "@icons/questionsPage/tropfyIcon";
|
||||||
|
|
||||||
|
import type { QuizQuestionRating } from "@model/questionTypes/rating";
|
||||||
|
|
||||||
|
const RATING_FORM_BUTTONS = [
|
||||||
|
{
|
||||||
|
name: "star",
|
||||||
|
icon: (color: string, width: number) => (
|
||||||
|
<StarIconMini width={width} color={color} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trophie",
|
||||||
|
icon: (color: string, width: number) => (
|
||||||
|
<TropfyIcon width={width} color={color} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "flag",
|
||||||
|
icon: (color: string, width: number) => (
|
||||||
|
<FlagIcon width={width} color={color} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "heart",
|
||||||
|
icon: (color: string, width: number) => (
|
||||||
|
<HeartIcon width={width} color={color} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "like",
|
||||||
|
icon: (color: string, width: number) => (
|
||||||
|
<LikeIcon width={width} color={color} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bubble",
|
||||||
|
icon: (color: string, width: number) => (
|
||||||
|
<LightbulbIcon width={width} color={color} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hashtag",
|
||||||
|
icon: (color: string, width: number) => (
|
||||||
|
<HashtagIcon width={width} color={color} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type RatingProps = {
|
||||||
|
currentQuestion: QuizQuestionRating;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Rating = ({ currentQuestion }: RatingProps) => {
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
const { quizId, preview } = useQuizData();
|
||||||
|
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useRootContainerSize() < 650;
|
||||||
|
const isTablet = useRootContainerSize() < 750;
|
||||||
|
const { answer } =
|
||||||
|
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
const form = RATING_FORM_BUTTONS.find(
|
||||||
|
({ name }) => name === currentQuestion.content.form
|
||||||
|
);
|
||||||
|
|
||||||
|
const sendRating = async (value: number | null) => {
|
||||||
|
setIsSending(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: String(value) + " из " + currentQuestion.content.steps,
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(currentQuestion.id, String(value), 0);
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "20px",
|
||||||
|
marginTop: "20px",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "inline-block", width: "100%" }}>
|
||||||
|
<RatingComponent
|
||||||
|
disabled={isSending}
|
||||||
|
value={Number(answer || 0)}
|
||||||
|
onChange={(_, value) => sendRating(value)}
|
||||||
|
sx={{
|
||||||
|
height: "50px",
|
||||||
|
opacity: "1!important",
|
||||||
|
"& .MuiRating-root.Mui-disabled": { opacity: "1!important" },
|
||||||
|
"& .MuiRating-icon": { mr: isMobile ? undefined : "15px" },
|
||||||
|
}}
|
||||||
|
max={currentQuestion.content.steps}
|
||||||
|
icon={form?.icon(
|
||||||
|
theme.palette.primary.main,
|
||||||
|
isMobile ? 30 : isTablet ? 40 : 50
|
||||||
|
)}
|
||||||
|
emptyIcon={form?.icon(
|
||||||
|
"#9A9AAF",
|
||||||
|
isMobile ? 30 : isTablet ? 40 : 50
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 2,
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{ color: "#9A9AAF" }}>
|
||||||
|
{currentQuestion.content.ratingNegativeDescription}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ color: "#9A9AAF" }}>
|
||||||
|
{currentQuestion.content.ratingPositiveDescription}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,99 +0,0 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
|
||||||
|
|
||||||
import { Select as SelectComponent } from "../tools//Select";
|
|
||||||
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
|
|
||||||
type SelectProps = {
|
|
||||||
currentQuestion: QuizQuestionSelect;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Select = ({ currentQuestion }: SelectProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { quizId, settings, preview } = useQuizData();
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const deleteAnswer = useQuizViewStore(state => state.deleteAnswer);
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
const { answer } =
|
|
||||||
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectComponent
|
|
||||||
disabled={isSending}
|
|
||||||
placeholder={currentQuestion.content.default}
|
|
||||||
activeItemIndex={answer ? Number(answer) : -1}
|
|
||||||
items={currentQuestion.content.variants.map(({ answer }) => answer)}
|
|
||||||
colorMain={theme.palette.primary.main}
|
|
||||||
sx={{
|
|
||||||
"& .MuiSelect-select.MuiSelect-outlined": { zIndex: 1 },
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
background: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(255,255,255, 0.3)"
|
|
||||||
: "transparent",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onChange={async (_, value) => {
|
|
||||||
setIsSending(true);
|
|
||||||
if (value < 0) {
|
|
||||||
deleteAnswer(currentQuestion.id);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: "",
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
return setIsSending(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: String(
|
|
||||||
currentQuestion.content.variants[Number(value)].answer
|
|
||||||
),
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, String(value), 0);
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
102
lib/components/ViewPublicationPage/questions/Select/index.tsx
Normal file
102
lib/components/ViewPublicationPage/questions/Select/index.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { Select as SelectComponent } from "@/components/ViewPublicationPage/tools/Select";
|
||||||
|
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
|
||||||
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
|
import type { QuizQuestionSelect } from "@model/questionTypes/select";
|
||||||
|
|
||||||
|
type SelectProps = {
|
||||||
|
currentQuestion: QuizQuestionSelect;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Select = ({ currentQuestion }: SelectProps) => {
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
const { quizId, settings, preview } = useQuizData();
|
||||||
|
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const theme = useTheme();
|
||||||
|
const { answer } =
|
||||||
|
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
|
||||||
|
const sendSelectedAnswer = async (value: number) => {
|
||||||
|
setIsSending(true);
|
||||||
|
|
||||||
|
if (value < 0) {
|
||||||
|
deleteAnswer(currentQuestion.id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: "",
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
return setIsSending(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: String(currentQuestion.content.variants[Number(value)].answer),
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(currentQuestion.id, String(value), 0);
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectComponent
|
||||||
|
disabled={isSending}
|
||||||
|
placeholder={currentQuestion.content.default}
|
||||||
|
activeItemIndex={answer ? Number(answer) : -1}
|
||||||
|
items={currentQuestion.content.variants.map(({ answer }) => answer)}
|
||||||
|
colorMain={theme.palette.primary.main}
|
||||||
|
sx={{
|
||||||
|
"& .MuiSelect-select.MuiSelect-outlined": { zIndex: 1 },
|
||||||
|
"& .MuiOutlinedInput-notchedOutline": {
|
||||||
|
background: settings.cfg.design
|
||||||
|
? quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "#F2F3F7"
|
||||||
|
: "rgba(255,255,255, 0.3)"
|
||||||
|
: "transparent",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onChange={(_, value) => sendSelectedAnswer(value)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,286 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
TextField as MuiTextField,
|
|
||||||
TextFieldProps,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
|
||||||
|
|
||||||
import {Answer, useQuizViewStore} from "@stores/quizView";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { ChangeEvent, FC, useEffect, useState } from "react";
|
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
|
||||||
|
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
|
||||||
|
|
||||||
type TextProps = {
|
|
||||||
currentQuestion: QuizQuestionText;
|
|
||||||
stepNumber: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Orientation = [
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: false },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
{ horizontal: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
|
|
||||||
const { settings, preview } = useQuizData();
|
|
||||||
const spec = settings.cfg.spec;
|
|
||||||
const { quizId } = useQuizData();
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const { answer } =
|
|
||||||
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const inputHC = useDebouncedCallback(async (text) => {
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: text,
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
}, 400);
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
inputHC.flush();
|
|
||||||
},
|
|
||||||
[inputHC]
|
|
||||||
);
|
|
||||||
switch (spec) {
|
|
||||||
case true:
|
|
||||||
return (
|
|
||||||
<TextSpecial
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
answer={answer}
|
|
||||||
inputHC={inputHC}
|
|
||||||
stepNumber={stepNumber}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case undefined:
|
|
||||||
return (
|
|
||||||
<TextNormal
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
answer={answer}
|
|
||||||
inputHC={inputHC}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return (
|
|
||||||
<TextNormal
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
answer={answer}
|
|
||||||
inputHC={inputHC}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
currentQuestion: QuizQuestionText;
|
|
||||||
answer?: Answer;
|
|
||||||
inputHC: (a: string) => void;
|
|
||||||
stepNumber?: number | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TextNormal = ({ currentQuestion, answer, inputHC }: Props) => {
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
const theme = useTheme();
|
|
||||||
const { settings } = useQuizData();
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
flexDirection: isMobile ? "column-reverse" : undefined,
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CustomTextField
|
|
||||||
placeholder={currentQuestion.content.placeholder}
|
|
||||||
value={answer || ""}
|
|
||||||
onChange={async ({ target }) => {
|
|
||||||
updateAnswer(currentQuestion.id, target.value, 0);
|
|
||||||
inputHC(target.value);
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
"& .MuiOutlinedInput-root": {
|
|
||||||
background: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(255,255,255, 0.3)"
|
|
||||||
: "transparent",
|
|
||||||
},
|
|
||||||
"& .MuiOutlinedInput-notchedOutline": {
|
|
||||||
borderColor: "#9A9AAF"
|
|
||||||
},
|
|
||||||
"&:focus-visible": { borderColor: theme.palette.primary.main },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{currentQuestion.content.back &&
|
|
||||||
currentQuestion.content.back !== " " && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
maxWidth: "400px",
|
|
||||||
width: "100%",
|
|
||||||
height: "300px",
|
|
||||||
margin: "15px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TextSpecial = ({
|
|
||||||
currentQuestion,
|
|
||||||
answer,
|
|
||||||
inputHC,
|
|
||||||
stepNumber,
|
|
||||||
}: Props) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
const isHorizontal = Orientation[Number(stepNumber) - 1].horizontal;
|
|
||||||
const { settings } = useQuizData();
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: isMobile ? "column" : undefined,
|
|
||||||
alignItems: isMobile ? "center" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
width: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
{isHorizontal &&
|
|
||||||
currentQuestion.content.back &&
|
|
||||||
currentQuestion.content.back !== " " && (
|
|
||||||
<Box sx={{ margin: "30px", width: "50vw", maxHeight: "550px" }}>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{
|
|
||||||
<TextField
|
|
||||||
autoFocus={true}
|
|
||||||
multiline
|
|
||||||
maxRows={4}
|
|
||||||
placeholder={currentQuestion.content.placeholder}
|
|
||||||
value={answer || ""}
|
|
||||||
onChange={async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
updateAnswer(currentQuestion.id, target.value, 0);
|
|
||||||
inputHC(target.value);
|
|
||||||
}}
|
|
||||||
inputProps={{
|
|
||||||
maxLength: 400,
|
|
||||||
background: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#F2F3F7"
|
|
||||||
: "rgba(154,154,175, 0.2)"
|
|
||||||
: "transparent",
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
"& .MuiOutlinedInput-root": {
|
|
||||||
backgroundColor: settings.cfg.design
|
|
||||||
? "rgba(154,154,175, 0.2)"
|
|
||||||
: "#FFFFFF",
|
|
||||||
},
|
|
||||||
"&:focus-visible": {
|
|
||||||
borderColor: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
{!isHorizontal &&
|
|
||||||
currentQuestion.content.back &&
|
|
||||||
currentQuestion.content.back !== " " && (
|
|
||||||
<Box sx={{ margin: "15px", width: "40vw" }}>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,91 @@
|
|||||||
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
|
||||||
|
import { Answer, useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
|
|
||||||
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
|
import type { ChangeEvent } from "react";
|
||||||
|
import type { QuizQuestionText } from "@model/questionTypes/text";
|
||||||
|
|
||||||
|
interface TextNormalProps {
|
||||||
|
currentQuestion: QuizQuestionText;
|
||||||
|
answer?: Answer;
|
||||||
|
inputHC: (text: string) => void;
|
||||||
|
stepNumber?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextNormal = ({
|
||||||
|
currentQuestion,
|
||||||
|
answer,
|
||||||
|
inputHC,
|
||||||
|
}: TextNormalProps) => {
|
||||||
|
const { settings } = useQuizData();
|
||||||
|
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const isMobile = useRootContainerSize() < 650;
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const onInputChange = async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateAnswer(currentQuestion.id, target.value, 0);
|
||||||
|
inputHC(target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
flexDirection: isMobile ? "column-reverse" : undefined,
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomTextField
|
||||||
|
placeholder={currentQuestion.content.placeholder}
|
||||||
|
value={answer || ""}
|
||||||
|
onChange={onInputChange}
|
||||||
|
sx={{
|
||||||
|
"& .MuiOutlinedInput-root": {
|
||||||
|
background: settings.cfg.design
|
||||||
|
? quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "#F2F3F7"
|
||||||
|
: "rgba(255,255,255, 0.3)"
|
||||||
|
: "transparent",
|
||||||
|
},
|
||||||
|
"& .MuiOutlinedInput-notchedOutline": { borderColor: "#9A9AAF" },
|
||||||
|
"&:focus-visible": { borderColor: theme.palette.primary.main },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{currentQuestion.content.back &&
|
||||||
|
currentQuestion.content.back !== " " && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "400px",
|
||||||
|
width: "100%",
|
||||||
|
height: "300px",
|
||||||
|
margin: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
key={currentQuestion.id}
|
||||||
|
src={currentQuestion.content.back}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,152 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
TextField as MuiTextField,
|
||||||
|
TextFieldProps,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { Answer, useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
|
|
||||||
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
|
import type { ChangeEvent, FC } from "react";
|
||||||
|
import type { QuizQuestionText } from "@model/questionTypes/text";
|
||||||
|
|
||||||
|
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
||||||
|
|
||||||
|
const ORIENTATION = [
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: false },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: false },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: false },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: false },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: false },
|
||||||
|
{ horizontal: false },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
{ horizontal: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface TextSpecialProps {
|
||||||
|
currentQuestion: QuizQuestionText;
|
||||||
|
answer?: Answer;
|
||||||
|
inputHC: (text: string) => void;
|
||||||
|
stepNumber?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TextSpecial = ({
|
||||||
|
currentQuestion,
|
||||||
|
answer,
|
||||||
|
inputHC,
|
||||||
|
stepNumber,
|
||||||
|
}: TextSpecialProps) => {
|
||||||
|
const { settings } = useQuizData();
|
||||||
|
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const isHorizontal = ORIENTATION[Number(stepNumber) - 1].horizontal;
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useRootContainerSize() < 650;
|
||||||
|
|
||||||
|
const onInputChange = async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
updateAnswer(currentQuestion.id, target.value, 0);
|
||||||
|
inputHC(target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: isMobile ? "column" : undefined,
|
||||||
|
alignItems: isMobile ? "center" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
{isHorizontal &&
|
||||||
|
currentQuestion.content.back &&
|
||||||
|
currentQuestion.content.back !== " " && (
|
||||||
|
<Box sx={{ margin: "30px", width: "50vw", maxHeight: "550px" }}>
|
||||||
|
<img
|
||||||
|
key={currentQuestion.id}
|
||||||
|
src={currentQuestion.content.back}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{
|
||||||
|
<TextField
|
||||||
|
autoFocus={true}
|
||||||
|
multiline
|
||||||
|
maxRows={4}
|
||||||
|
placeholder={currentQuestion.content.placeholder}
|
||||||
|
value={answer || ""}
|
||||||
|
onChange={onInputChange}
|
||||||
|
inputProps={{
|
||||||
|
maxLength: 400,
|
||||||
|
background: settings.cfg.design
|
||||||
|
? quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "#F2F3F7"
|
||||||
|
: "rgba(154,154,175, 0.2)"
|
||||||
|
: "transparent",
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
"& .MuiOutlinedInput-root": {
|
||||||
|
backgroundColor: settings.cfg.design
|
||||||
|
? "rgba(154,154,175, 0.2)"
|
||||||
|
: "#FFFFFF",
|
||||||
|
},
|
||||||
|
"&:focus-visible": {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
{!isHorizontal &&
|
||||||
|
currentQuestion.content.back &&
|
||||||
|
currentQuestion.content.back !== " " && (
|
||||||
|
<Box sx={{ margin: "15px", width: "40vw" }}>
|
||||||
|
<img
|
||||||
|
key={currentQuestion.id}
|
||||||
|
src={currentQuestion.content.back}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
77
lib/components/ViewPublicationPage/questions/Text/index.tsx
Normal file
77
lib/components/ViewPublicationPage/questions/Text/index.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { TextSpecial } from "./TextSpecial";
|
||||||
|
import { TextNormal } from "./TextNormal";
|
||||||
|
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
|
||||||
|
import type { QuizQuestionText } from "@model/questionTypes/text";
|
||||||
|
|
||||||
|
type TextProps = {
|
||||||
|
currentQuestion: QuizQuestionText;
|
||||||
|
stepNumber: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
const { settings, preview } = useQuizData();
|
||||||
|
const { quizId } = useQuizData();
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const { answer } =
|
||||||
|
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
|
||||||
|
const inputHC = useDebouncedCallback(async (text) => {
|
||||||
|
setIsSending(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: text,
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
}, 400);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
inputHC.flush();
|
||||||
|
}, [inputHC]);
|
||||||
|
|
||||||
|
switch (settings.cfg.spec) {
|
||||||
|
case true:
|
||||||
|
return (
|
||||||
|
<TextSpecial
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
answer={answer}
|
||||||
|
inputHC={inputHC}
|
||||||
|
stepNumber={stepNumber}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case undefined:
|
||||||
|
return (
|
||||||
|
<TextNormal
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
answer={answer}
|
||||||
|
inputHC={inputHC}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<TextNormal
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
answer={answer}
|
||||||
|
inputHC={inputHC}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -1,307 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
Checkbox,
|
|
||||||
FormControlLabel,
|
|
||||||
FormGroup,
|
|
||||||
TextField as MuiTextField,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
TextFieldProps,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { FC, useEffect, useState } from "react";
|
|
||||||
|
|
||||||
import {
|
|
||||||
useQuizViewStore,
|
|
||||||
} from "@stores/quizView";
|
|
||||||
|
|
||||||
import { CheckboxIcon } from "@icons/Checkbox";
|
|
||||||
import RadioCheck from "@ui_kit/RadioCheck";
|
|
||||||
import RadioIcon from "@ui_kit/RadioIcon";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import type { QuestionVariant } from "../../../model/questionTypes/shared";
|
|
||||||
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
|
||||||
|
|
||||||
type VariantProps = {
|
|
||||||
currentQuestion: QuizQuestionVariant;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Variant = ({ currentQuestion }: VariantProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const ownVariants = useQuizViewStore(state => state.ownVariants);
|
|
||||||
const updateOwnVariant = useQuizViewStore(state => state.updateOwnVariant);
|
|
||||||
|
|
||||||
const { answer } =
|
|
||||||
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
const ownVariant = ownVariants.find(
|
|
||||||
(variant) => variant.id === currentQuestion.id
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isSending, setIsSending] = useState(false);
|
|
||||||
|
|
||||||
const Group = currentQuestion.content.multi ? FormGroup : RadioGroup;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ownVariant) {
|
|
||||||
updateOwnVariant(currentQuestion.id, "");
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (moment.isMoment(answer))
|
|
||||||
throw new Error("Answer is Moment in Variant question");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "20px",
|
|
||||||
flexDirection: isMobile ? "column-reverse" : undefined,
|
|
||||||
alignItems: isMobile ? "center" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Group
|
|
||||||
name={currentQuestion.id.toString()}
|
|
||||||
value={currentQuestion.content.variants.findIndex(
|
|
||||||
({ id }) => answer === id
|
|
||||||
)}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
flexBasis: "100%",
|
|
||||||
marginTop: "20px",
|
|
||||||
width: isMobile ? "100%" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
width: "100%",
|
|
||||||
gap: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.variants.map((variant, index) => (
|
|
||||||
<VariantItem
|
|
||||||
key={variant.id}
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
variant={variant}
|
|
||||||
answer={answer}
|
|
||||||
index={index}
|
|
||||||
isSending={isSending}
|
|
||||||
setIsSending={setIsSending}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{currentQuestion.content.own && ownVariant && (
|
|
||||||
<VariantItem
|
|
||||||
own
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
variant={ownVariant.variant}
|
|
||||||
answer={answer}
|
|
||||||
index={currentQuestion.content.variants.length + 2}
|
|
||||||
isSending={isSending}
|
|
||||||
setIsSending={setIsSending}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Group>
|
|
||||||
{currentQuestion.content.back &&
|
|
||||||
currentQuestion.content.back !== " " && (
|
|
||||||
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
|
|
||||||
<img
|
|
||||||
key={currentQuestion.id}
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const VariantItem = ({
|
|
||||||
currentQuestion,
|
|
||||||
variant,
|
|
||||||
answer,
|
|
||||||
index,
|
|
||||||
own = false,
|
|
||||||
isSending,
|
|
||||||
setIsSending,
|
|
||||||
}: {
|
|
||||||
currentQuestion: QuizQuestionVariant;
|
|
||||||
variant: QuestionVariant;
|
|
||||||
answer: string | string[] | undefined;
|
|
||||||
index: number;
|
|
||||||
own?: boolean;
|
|
||||||
isSending: boolean;
|
|
||||||
setIsSending: (a: boolean) => void;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { settings, quizId, preview } = useQuizData();
|
|
||||||
const deleteAnswer = useQuizViewStore(state => state.deleteAnswer);
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
disabled={isSending}
|
|
||||||
sx={{
|
|
||||||
margin: "0",
|
|
||||||
borderRadius: "12px",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
padding: "15px",
|
|
||||||
border: `1px solid`,
|
|
||||||
borderColor:
|
|
||||||
answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
|
||||||
backgroundColor: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#FFFFFF"
|
|
||||||
: "rgba(255,255,255, 0.3)"
|
|
||||||
: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
display: "flex",
|
|
||||||
maxWidth: "685px",
|
|
||||||
maxHeight: "85px",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: "100%",
|
|
||||||
"&:hover": { borderColor: theme.palette.primary.main },
|
|
||||||
"&.MuiFormControl-root": {
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
|
||||||
overflow: "auto",
|
|
||||||
lineHeight: "normal",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: "#b8babf",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={index}
|
|
||||||
labelPlacement="start"
|
|
||||||
control={
|
|
||||||
currentQuestion.content.multi ? (
|
|
||||||
<Checkbox
|
|
||||||
checked={!!answer?.includes(variant.id)}
|
|
||||||
checkedIcon={
|
|
||||||
<CheckboxIcon checked color={theme.palette.primary.main} />
|
|
||||||
}
|
|
||||||
icon={<CheckboxIcon />}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Radio
|
|
||||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
label={own ? <TextField label="Другое..." /> : variant.answer}
|
|
||||||
onClick={async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (isSending) return;
|
|
||||||
|
|
||||||
setIsSending(true);
|
|
||||||
const variantId = currentQuestion.content.variants[index].id;
|
|
||||||
console.log(answer);
|
|
||||||
|
|
||||||
if (currentQuestion.content.multi) {
|
|
||||||
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: currentAnswer.includes(variantId)
|
|
||||||
? currentAnswer?.filter((item) => item !== variantId)
|
|
||||||
: [...currentAnswer, variantId],
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(
|
|
||||||
currentQuestion.id,
|
|
||||||
currentAnswer.includes(variantId)
|
|
||||||
? currentAnswer?.filter((item) => item !== variantId)
|
|
||||||
: [...currentAnswer, variantId],
|
|
||||||
currentQuestion.content.variants[index].points || 0
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: currentQuestion.content.variants[index].answer,
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(
|
|
||||||
currentQuestion.id,
|
|
||||||
variantId,
|
|
||||||
answer === variantId
|
|
||||||
? 0
|
|
||||||
: currentQuestion.content.variants[index].points || 0
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (answer === variantId) {
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: "",
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
deleteAnswer(currentQuestion.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,186 @@
|
|||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
TextField as MuiTextField,
|
||||||
|
Radio,
|
||||||
|
TextFieldProps,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
|
||||||
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
|
import { CheckboxIcon } from "@icons/Checkbox";
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
|
import type { FC, MouseEvent } from "react";
|
||||||
|
import type { QuestionVariant } from "@model/questionTypes/shared";
|
||||||
|
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||||
|
|
||||||
|
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||||
|
|
||||||
|
export const VariantItem = ({
|
||||||
|
currentQuestion,
|
||||||
|
variant,
|
||||||
|
answer,
|
||||||
|
index,
|
||||||
|
own = false,
|
||||||
|
isSending,
|
||||||
|
setIsSending,
|
||||||
|
}: {
|
||||||
|
currentQuestion: QuizQuestionVariant;
|
||||||
|
variant: QuestionVariant;
|
||||||
|
answer: string | string[] | undefined;
|
||||||
|
index: number;
|
||||||
|
own?: boolean;
|
||||||
|
isSending: boolean;
|
||||||
|
setIsSending: (a: boolean) => void;
|
||||||
|
}) => {
|
||||||
|
const { settings, quizId, preview } = useQuizData();
|
||||||
|
const theme = useTheme();
|
||||||
|
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
||||||
|
|
||||||
|
const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (isSending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(true);
|
||||||
|
|
||||||
|
const variantId = currentQuestion.content.variants[index].id;
|
||||||
|
|
||||||
|
if (currentQuestion.content.multi) {
|
||||||
|
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: currentAnswer.includes(variantId)
|
||||||
|
? currentAnswer?.filter((item) => item !== variantId)
|
||||||
|
: [...currentAnswer, variantId],
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
currentAnswer.includes(variantId)
|
||||||
|
? currentAnswer?.filter((item) => item !== variantId)
|
||||||
|
: [...currentAnswer, variantId],
|
||||||
|
currentQuestion.content.variants[index].points || 0
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: currentQuestion.content.variants[index].answer,
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
variantId,
|
||||||
|
answer === variantId
|
||||||
|
? 0
|
||||||
|
: currentQuestion.content.variants[index].points || 0
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answer === variantId) {
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: "",
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
deleteAnswer(currentQuestion.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControlLabel
|
||||||
|
key={variant.id}
|
||||||
|
disabled={isSending}
|
||||||
|
sx={{
|
||||||
|
margin: "0",
|
||||||
|
borderRadius: "12px",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
padding: "15px",
|
||||||
|
border: `1px solid`,
|
||||||
|
borderColor:
|
||||||
|
answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
||||||
|
backgroundColor: settings.cfg.design
|
||||||
|
? quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "#FFFFFF"
|
||||||
|
: "rgba(255,255,255, 0.3)"
|
||||||
|
: quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "white"
|
||||||
|
: theme.palette.background.default,
|
||||||
|
display: "flex",
|
||||||
|
maxWidth: "685px",
|
||||||
|
maxHeight: "85px",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
"&:hover": { borderColor: theme.palette.primary.main },
|
||||||
|
"&.MuiFormControl-root": { width: "100%" },
|
||||||
|
"& .MuiFormControlLabel-label": {
|
||||||
|
wordBreak: "break-word",
|
||||||
|
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||||
|
overflow: "auto",
|
||||||
|
lineHeight: "normal",
|
||||||
|
"&::-webkit-scrollbar": { width: "4px" },
|
||||||
|
"&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" },
|
||||||
|
},
|
||||||
|
"& .MuiFormControlLabel-label.Mui-disabled": {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={index}
|
||||||
|
labelPlacement="start"
|
||||||
|
control={
|
||||||
|
currentQuestion.content.multi ? (
|
||||||
|
<Checkbox
|
||||||
|
checked={!!answer?.includes(variant.id)}
|
||||||
|
checkedIcon={
|
||||||
|
<CheckboxIcon checked color={theme.palette.primary.main} />
|
||||||
|
}
|
||||||
|
icon={<CheckboxIcon />}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Radio
|
||||||
|
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||||
|
icon={<RadioIcon />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
label={own ? <TextField label="Другое..." /> : variant.answer}
|
||||||
|
onClick={sendVariant}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
124
lib/components/ViewPublicationPage/questions/Variant/index.tsx
Normal file
124
lib/components/ViewPublicationPage/questions/Variant/index.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { isMoment } from "moment";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
FormGroup,
|
||||||
|
RadioGroup,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { VariantItem } from "./VariantItem";
|
||||||
|
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
|
|
||||||
|
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||||
|
|
||||||
|
type VariantProps = {
|
||||||
|
currentQuestion: QuizQuestionVariant;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Variant = ({ currentQuestion }: VariantProps) => {
|
||||||
|
const [isSending, setIsSending] = useState(false);
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const { ownVariants, updateOwnVariant } = useQuizViewStore((state) => state);
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useRootContainerSize() < 650;
|
||||||
|
|
||||||
|
const { answer } =
|
||||||
|
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
const ownVariant = ownVariants.find(
|
||||||
|
(variant) => variant.id === currentQuestion.id
|
||||||
|
);
|
||||||
|
|
||||||
|
const Group = currentQuestion.content.multi ? FormGroup : RadioGroup;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ownVariant) {
|
||||||
|
updateOwnVariant(currentQuestion.id, "");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (isMoment(answer)) throw new Error("Answer is Moment in Variant question");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "20px",
|
||||||
|
flexDirection: isMobile ? "column-reverse" : undefined,
|
||||||
|
alignItems: isMobile ? "center" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group
|
||||||
|
name={currentQuestion.id.toString()}
|
||||||
|
value={currentQuestion.content.variants.findIndex(
|
||||||
|
({ id }) => answer === id
|
||||||
|
)}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexBasis: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
width: isMobile ? "100%" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
width: "100%",
|
||||||
|
gap: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentQuestion.content.variants.map((variant, index) => (
|
||||||
|
<VariantItem
|
||||||
|
key={variant.id}
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
variant={variant}
|
||||||
|
answer={answer}
|
||||||
|
index={index}
|
||||||
|
isSending={isSending}
|
||||||
|
setIsSending={setIsSending}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{currentQuestion.content.own && ownVariant && (
|
||||||
|
<VariantItem
|
||||||
|
own
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
variant={ownVariant.variant}
|
||||||
|
answer={answer}
|
||||||
|
index={currentQuestion.content.variants.length + 2}
|
||||||
|
isSending={isSending}
|
||||||
|
setIsSending={setIsSending}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
|
{currentQuestion.content.back &&
|
||||||
|
currentQuestion.content.back !== " " && (
|
||||||
|
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
|
||||||
|
<img
|
||||||
|
key={currentQuestion.id}
|
||||||
|
src={currentQuestion.content.back}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,226 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
FormControlLabel,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
Typography,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView";
|
|
||||||
|
|
||||||
import RadioCheck from "@ui_kit/RadioCheck";
|
|
||||||
import RadioIcon from "@ui_kit/RadioIcon";
|
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
|
||||||
import BlankImage from "@icons/BlankImage";
|
|
||||||
import { useQuizData } from "@contexts/QuizDataContext";
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
|
||||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
type VarimgProps = {
|
|
||||||
currentQuestion: QuizQuestionVarImg;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|
||||||
const { settings, quizId, preview } = useQuizData();
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
const deleteAnswer = useQuizViewStore(state => state.deleteAnswer);
|
|
||||||
const updateAnswer = useQuizViewStore(state => state.updateAnswer);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const { answer } =
|
|
||||||
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
const variant = currentQuestion.content.variants.find(
|
|
||||||
({ id }) => answer === id
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
sx={{ wordBreak: "break-word" }}
|
|
||||||
>
|
|
||||||
{currentQuestion.title}
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
marginTop: "20px",
|
|
||||||
flexDirection: isMobile ? "column-reverse" : undefined,
|
|
||||||
gap: "30px",
|
|
||||||
alignItems: isMobile ? "center" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<RadioGroup
|
|
||||||
name={currentQuestion.id}
|
|
||||||
value={currentQuestion.content.variants.findIndex(
|
|
||||||
({ id }) => answer === id
|
|
||||||
)}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
flexBasis: "100%",
|
|
||||||
width: isMobile ? "100%" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
width: "100%",
|
|
||||||
gap: "20px",
|
|
||||||
"&:focus": { color: theme.palette.text.primary },
|
|
||||||
"&:active": { color: theme.palette.text.primary }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentQuestion.content.variants.map((variant, index) => (
|
|
||||||
<FormControlLabel
|
|
||||||
key={variant.id}
|
|
||||||
disabled={isSending}
|
|
||||||
sx={{
|
|
||||||
marginBottom: "15px",
|
|
||||||
borderRadius: "12px",
|
|
||||||
padding: "20px",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
backgroundColor: settings.cfg.design
|
|
||||||
? quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "#FFFFFF"
|
|
||||||
: "rgba(255,255,255, 0.3)"
|
|
||||||
: quizThemes[settings.cfg.theme].isLight
|
|
||||||
? "white"
|
|
||||||
: theme.palette.background.default,
|
|
||||||
border: `1px solid`,
|
|
||||||
borderColor:
|
|
||||||
answer === variant.id
|
|
||||||
? theme.palette.primary.main
|
|
||||||
: "#9A9AAF",
|
|
||||||
display: "flex",
|
|
||||||
margin: 0,
|
|
||||||
justifyContent: "space-between",
|
|
||||||
"&:hover": { borderColor: theme.palette.primary.main },
|
|
||||||
"& .MuiFormControlLabel-label": {
|
|
||||||
wordBreak: "break-word",
|
|
||||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
|
||||||
overflow: "auto",
|
|
||||||
lineHeight: "normal",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
width: "4px",
|
|
||||||
},
|
|
||||||
"&::-webkit-scrollbar-thumb": {
|
|
||||||
backgroundColor: "#b8babf",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
labelPlacement="start"
|
|
||||||
value={index}
|
|
||||||
onClick={async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(
|
|
||||||
currentQuestion.id,
|
|
||||||
currentQuestion.content.variants[index].id,
|
|
||||||
currentQuestion.content.variants[index].points || 0
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (answer === currentQuestion.content.variants[index].id) {
|
|
||||||
try {
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: "",
|
|
||||||
qid: quizId,
|
|
||||||
preview
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
|
||||||
}
|
|
||||||
deleteAnswer(currentQuestion.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
}}
|
|
||||||
control={
|
|
||||||
<Radio
|
|
||||||
checkedIcon={
|
|
||||||
<RadioCheck color={theme.palette.primary.main} />
|
|
||||||
}
|
|
||||||
icon={<RadioIcon />}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={variant.answer}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</RadioGroup>
|
|
||||||
{/* {(variant?.extendedText || currentQuestion.content.back) && ( */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
maxWidth: "450px",
|
|
||||||
width: "100%",
|
|
||||||
height: "450px",
|
|
||||||
border: "1px solid #9A9AAF",
|
|
||||||
borderRadius: "12px",
|
|
||||||
overflow: "hidden",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: "#9A9AAF30",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{answer ? (
|
|
||||||
variant?.extendedText ? (
|
|
||||||
<img
|
|
||||||
src={variant?.extendedText}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<BlankImage />
|
|
||||||
)
|
|
||||||
) : currentQuestion.content.back !== " " &&
|
|
||||||
currentQuestion.content.back !== null &&
|
|
||||||
currentQuestion.content.back.length > 0 ? (
|
|
||||||
<img
|
|
||||||
src={currentQuestion.content.back}
|
|
||||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
) : currentQuestion.content.replText !== " " &&
|
|
||||||
currentQuestion.content.replText.length > 0 ? (
|
|
||||||
currentQuestion.content.replText
|
|
||||||
) : variant?.extendedText || isMobile ? (
|
|
||||||
"Выберите вариант ответа ниже"
|
|
||||||
) : (
|
|
||||||
"Выберите вариант ответа слева"
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
{/* )} */}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,129 @@
|
|||||||
|
import { FormControlLabel, Radio, useTheme } from "@mui/material";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
|
||||||
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { useQuizData } from "@contexts/QuizDataContext";
|
||||||
|
|
||||||
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
|
import type { MouseEvent } from "react";
|
||||||
|
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||||
|
import type { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||||
|
|
||||||
|
type VarimgVariantProps = {
|
||||||
|
currentQuestion: QuizQuestionVarImg;
|
||||||
|
variant: QuestionVariant;
|
||||||
|
index: number;
|
||||||
|
isSending: boolean;
|
||||||
|
setIsSending: (isSending: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VarimgVariant = ({
|
||||||
|
currentQuestion,
|
||||||
|
variant,
|
||||||
|
index,
|
||||||
|
isSending,
|
||||||
|
setIsSending,
|
||||||
|
}: VarimgVariantProps) => {
|
||||||
|
const { settings, quizId, preview } = useQuizData();
|
||||||
|
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { answer } =
|
||||||
|
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
|
||||||
|
const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
setIsSending(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAnswer(
|
||||||
|
currentQuestion.id,
|
||||||
|
currentQuestion.content.variants[index].id,
|
||||||
|
currentQuestion.content.variants[index].points || 0
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answer === currentQuestion.content.variants[index].id) {
|
||||||
|
try {
|
||||||
|
await sendAnswer({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: "",
|
||||||
|
qid: quizId,
|
||||||
|
preview,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAnswer(currentQuestion.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControlLabel
|
||||||
|
key={variant.id}
|
||||||
|
disabled={isSending}
|
||||||
|
sx={{
|
||||||
|
marginBottom: "15px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
padding: "20px",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
backgroundColor: settings.cfg.design
|
||||||
|
? quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "#FFFFFF"
|
||||||
|
: "rgba(255,255,255, 0.3)"
|
||||||
|
: quizThemes[settings.cfg.theme].isLight
|
||||||
|
? "white"
|
||||||
|
: theme.palette.background.default,
|
||||||
|
border: `1px solid`,
|
||||||
|
borderColor:
|
||||||
|
answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
||||||
|
display: "flex",
|
||||||
|
margin: 0,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
"&:hover": { borderColor: theme.palette.primary.main },
|
||||||
|
"& .MuiFormControlLabel-label": {
|
||||||
|
wordBreak: "break-word",
|
||||||
|
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||||
|
overflow: "auto",
|
||||||
|
lineHeight: "normal",
|
||||||
|
"&::-webkit-scrollbar": { width: "4px" },
|
||||||
|
"&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" },
|
||||||
|
},
|
||||||
|
"& .MuiFormControlLabel-label.Mui-disabled": {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
labelPlacement="start"
|
||||||
|
value={index}
|
||||||
|
onClick={sendVariant}
|
||||||
|
label={variant.answer}
|
||||||
|
control={
|
||||||
|
<Radio
|
||||||
|
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||||
|
icon={<RadioIcon />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
130
lib/components/ViewPublicationPage/questions/Varimg/index.tsx
Normal file
130
lib/components/ViewPublicationPage/questions/Varimg/index.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import { VarimgVariant } from "./VarimgVariant";
|
||||||
|
|
||||||
|
import { useQuizViewStore } from "@stores/quizView";
|
||||||
|
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||||
|
|
||||||
|
import BlankImage from "@icons/BlankImage";
|
||||||
|
|
||||||
|
import type { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||||
|
|
||||||
|
type VarimgProps = {
|
||||||
|
currentQuestion: QuizQuestionVarImg;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||||
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useRootContainerSize() < 650;
|
||||||
|
|
||||||
|
const { answer } =
|
||||||
|
answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
const variant = currentQuestion.content.variants.find(
|
||||||
|
({ id }) => answer === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
sx={{ wordBreak: "break-word" }}
|
||||||
|
>
|
||||||
|
{currentQuestion.title}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
marginTop: "20px",
|
||||||
|
flexDirection: isMobile ? "column-reverse" : undefined,
|
||||||
|
gap: "30px",
|
||||||
|
alignItems: isMobile ? "center" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RadioGroup
|
||||||
|
name={currentQuestion.id}
|
||||||
|
value={currentQuestion.content.variants.findIndex(
|
||||||
|
({ id }) => answer === id
|
||||||
|
)}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexBasis: "100%",
|
||||||
|
width: isMobile ? "100%" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
gap: "20px",
|
||||||
|
"&:focus": { color: theme.palette.text.primary },
|
||||||
|
"&:active": { color: theme.palette.text.primary },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentQuestion.content.variants.map((variant, index) => (
|
||||||
|
<VarimgVariant
|
||||||
|
key={variant.id}
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
variant={variant}
|
||||||
|
isSending={isSending}
|
||||||
|
setIsSending={setIsSending}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "450px",
|
||||||
|
width: "100%",
|
||||||
|
height: "450px",
|
||||||
|
border: "1px solid #9A9AAF",
|
||||||
|
borderRadius: "12px",
|
||||||
|
overflow: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
backgroundColor: "#9A9AAF30",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{answer ? (
|
||||||
|
variant?.extendedText ? (
|
||||||
|
<img
|
||||||
|
src={variant?.extendedText}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<BlankImage />
|
||||||
|
)
|
||||||
|
) : currentQuestion.content.back !== " " &&
|
||||||
|
currentQuestion.content.back !== null &&
|
||||||
|
currentQuestion.content.back.length > 0 ? (
|
||||||
|
<img
|
||||||
|
src={currentQuestion.content.back}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
) : currentQuestion.content.replText !== " " &&
|
||||||
|
currentQuestion.content.replText.length > 0 ? (
|
||||||
|
currentQuestion.content.replText
|
||||||
|
) : variant?.extendedText || isMobile ? (
|
||||||
|
"Выберите вариант ответа ниже"
|
||||||
|
) : (
|
||||||
|
"Выберите вариант ответа слева"
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user