send answers only on next question click
This commit is contained in:
parent
d7d20ed5a0
commit
821213a14c
@ -78,23 +78,26 @@ export async function getData(quizId: string): Promise<{
|
||||
error?: AxiosError;
|
||||
}> {
|
||||
try {
|
||||
const { data, headers } = await axios<GetQuizDataResponse>(domain + `/answer/v1.0.0/settings${window.location.search}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-Sessionkey": SESSIONS,
|
||||
"Content-Type": "application/json",
|
||||
DeviceType: DeviceType,
|
||||
Device: Device,
|
||||
OS: OSDevice,
|
||||
Browser: userAgent,
|
||||
},
|
||||
data: {
|
||||
quiz_id: quizId,
|
||||
limit: 100,
|
||||
page: 0,
|
||||
need_config: true,
|
||||
},
|
||||
});
|
||||
const { data, headers } = await axios<GetQuizDataResponse>(
|
||||
domain + `/answer/v1.0.0/settings${window.location.search}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-Sessionkey": SESSIONS,
|
||||
"Content-Type": "application/json",
|
||||
DeviceType: DeviceType,
|
||||
Device: Device,
|
||||
OS: OSDevice,
|
||||
Browser: userAgent,
|
||||
},
|
||||
data: {
|
||||
quiz_id: quizId,
|
||||
limit: 100,
|
||||
page: 0,
|
||||
need_config: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||
|
||||
//Тут ещё проверка на антифрод без парса конфига. Нам не интересно время если не нужно запрещать проходить чаще чем в сутки
|
||||
@ -143,10 +146,10 @@ type SendAnswerProps = {
|
||||
questionId: string;
|
||||
body: string | string[];
|
||||
qid: string;
|
||||
preview: boolean;
|
||||
preview?: boolean;
|
||||
};
|
||||
|
||||
export function sendAnswer({ questionId, body, qid, preview }: SendAnswerProps) {
|
||||
export function sendAnswer({ questionId, body, qid, preview = false }: SendAnswerProps) {
|
||||
if (preview) return;
|
||||
const formData = new FormData();
|
||||
|
||||
|
@ -167,6 +167,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
||||
useEffect(() => {
|
||||
vkMetrics.contactsFormOpened();
|
||||
yandexMetrics.contactsFormOpened();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -274,11 +275,17 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
||||
fontSize={"16px"}
|
||||
>
|
||||
С 
|
||||
<Link href={"https://shub.pena.digital/ppdd"} target="_blank">
|
||||
<Link
|
||||
href={"https://shub.pena.digital/ppdd"}
|
||||
target="_blank"
|
||||
>
|
||||
Положением об обработке персональных данных{" "}
|
||||
</Link>
|
||||
 и 
|
||||
<Link href={"https://shub.pena.digital/docs/privacy"} target="_blank">
|
||||
<Link
|
||||
href={"https://shub.pena.digital/docs/privacy"}
|
||||
target="_blank"
|
||||
>
|
||||
{" "}
|
||||
Политикой конфиденциальности{" "}
|
||||
</Link>
|
||||
|
@ -3,7 +3,7 @@ import { useRootContainerSize } from "@contexts/RootContainerWidthContext.ts";
|
||||
import { useQuizSettings } from "@contexts/QuizDataContext.ts";
|
||||
import { useIMask } from "react-imask";
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication.ts";
|
||||
import { FC, useState } from "react";
|
||||
import { FC, HTMLInputTypeAttribute, useState } from "react";
|
||||
import { CountrySelector } from "@/components/ViewPublicationPage/ContactForm/CustomInput/CountrySelector/CountrySelector.tsx";
|
||||
import { phoneMasksByCountry } from "@utils/phoneMasksByCountry.tsx";
|
||||
|
||||
@ -14,11 +14,12 @@ type InputProps = {
|
||||
onChange: TextFieldProps["onChange"];
|
||||
id: string;
|
||||
isPhone?: boolean;
|
||||
type?: HTMLInputTypeAttribute;
|
||||
};
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||
|
||||
export const CustomInput = ({ title, desc, Icon, onChange, isPhone }: InputProps) => {
|
||||
export const CustomInput = ({ title, desc, Icon, onChange, isPhone, type }: InputProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useRootContainerSize() < 600;
|
||||
const { settings } = useQuizSettings();
|
||||
@ -26,13 +27,18 @@ export const CustomInput = ({ title, desc, Icon, onChange, isPhone }: InputProps
|
||||
const { ref } = useIMask({ mask });
|
||||
return (
|
||||
<Box m="10px 0">
|
||||
<Typography mb="7px" color={theme.palette.text.primary} fontSize={"16px"}>
|
||||
<Typography
|
||||
mb="7px"
|
||||
color={theme.palette.text.primary}
|
||||
fontSize={"16px"}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
inputRef={isPhone ? ref : null}
|
||||
onChange={onChange}
|
||||
type={type}
|
||||
sx={{
|
||||
width: isMobile ? "100%" : "390px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
@ -57,7 +63,10 @@ export const CustomInput = ({ title, desc, Icon, onChange, isPhone }: InputProps
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Icon color="gray" backgroundColor={quizThemes[settings.cfg.theme].isLight ? "#F2F3F7" : "#F2F3F71A"} />
|
||||
<Icon
|
||||
color="gray"
|
||||
backgroundColor={quizThemes[settings.cfg.theme].isLight ? "#F2F3F7" : "#F2F3F71A"}
|
||||
/>
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
|
@ -52,6 +52,7 @@ export const Inputs = ({
|
||||
title={FC["email"].innerText || "Введите Email"}
|
||||
desc={FC["email"].text || "Email"}
|
||||
Icon={EmailIcon}
|
||||
type="email"
|
||||
/>
|
||||
);
|
||||
const Phone = (
|
||||
|
@ -2,7 +2,7 @@ import { ContactForm } from "@/components/ViewPublicationPage/ContactForm/Contac
|
||||
import { extractImageLinksFromQuestion } from "@/utils/extractImageLinks";
|
||||
import { useVKMetrics } from "@/utils/hooks/metrics/useVKMetrics";
|
||||
import { useYandexMetrics } from "@/utils/hooks/metrics/useYandexMetrics";
|
||||
import { sendAnswer } from "@api/quizRelase";
|
||||
import { sendQuestionAnswer } from "@/utils/sendQuestionAnswer";
|
||||
import { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
import { ThemeProvider, Typography } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
@ -37,8 +37,6 @@ export default function ViewPublicationPage() {
|
||||
useYandexMetrics(settings?.cfg?.yandexMetricsNumber);
|
||||
useVKMetrics(settings?.cfg?.vkMetricsNumber);
|
||||
|
||||
const isAnswer = answers.some((ans) => ans.questionId === currentQuestion?.id);
|
||||
|
||||
useEffect(
|
||||
function setFaviconAndTitle() {
|
||||
if (!changeFaviconAndTitle) return;
|
||||
@ -68,6 +66,8 @@ export default function ViewPublicationPage() {
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
const currentAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id);
|
||||
|
||||
let quizStepElement: ReactElement;
|
||||
switch (currentQuizStep) {
|
||||
case "startpage": {
|
||||
@ -94,20 +94,15 @@ export default function ViewPublicationPage() {
|
||||
nextButton={
|
||||
<NextButton
|
||||
isNextButtonEnabled={isNextButtonEnabled}
|
||||
moveToNextQuestion={async () => {
|
||||
if (!isAnswer) {
|
||||
try {
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: "",
|
||||
qid: quizId,
|
||||
preview,
|
||||
});
|
||||
} catch (e) {
|
||||
enqueueSnackbar("ответ не был засчитан");
|
||||
}
|
||||
}
|
||||
moveToNextQuestion={() => {
|
||||
moveToNextQuestion();
|
||||
|
||||
if (!currentAnswer || preview) return;
|
||||
|
||||
sendQuestionAnswer(quizId, currentQuestion, currentAnswer)?.catch((e) => {
|
||||
enqueueSnackbar("Ошибка при отправке ответа");
|
||||
console.error("Error sending answer", e);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
@ -1,27 +1,19 @@
|
||||
import { useState } from "react";
|
||||
import moment from "moment";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
import { sendAnswer } from "@api/quizRelase";
|
||||
import { useQuizViewStore } from "@/stores/quizView";
|
||||
import { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
|
||||
import CalendarIcon from "@icons/CalendarIcon";
|
||||
|
||||
import type { Moment } from "moment";
|
||||
import type { QuizQuestionDate } from "@model/questionTypes/date";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
import type { Moment } from "moment";
|
||||
import moment from "moment";
|
||||
|
||||
type DateProps = {
|
||||
currentQuestion: QuizQuestionDate;
|
||||
};
|
||||
|
||||
export const Date = ({ currentQuestion }: DateProps) => {
|
||||
const [isSending, setIsSending] = useState<boolean>(false);
|
||||
const { settings, quizId, preview } = useQuizSettings();
|
||||
const { settings } = useQuizSettings();
|
||||
const answers = useQuizViewStore((state) => state.answers);
|
||||
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||
const theme = useTheme();
|
||||
@ -29,28 +21,18 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
||||
const currentAnswer = moment(answer) || moment();
|
||||
|
||||
const onDateChange = async (date: Moment | null) => {
|
||||
if (isSending || !date) return;
|
||||
if (!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);
|
||||
updateAnswer(currentQuestion.id, date, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
|
@ -1,80 +1,41 @@
|
||||
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 type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||
import { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
|
||||
import { Box, FormControl, FormControlLabel, Radio, Typography, useTheme } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
||||
import type { MouseEvent } from "react";
|
||||
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||
import type { QuizQuestionEmoji } from "@model/questionTypes/emoji";
|
||||
|
||||
polyfillCountryFlagEmojis();
|
||||
|
||||
type EmojiVariantProps = {
|
||||
currentQuestion: QuizQuestionEmoji;
|
||||
questionId: string;
|
||||
variant: QuestionVariant;
|
||||
index: number;
|
||||
isSending: boolean;
|
||||
setIsSending: (isSending: boolean) => void;
|
||||
};
|
||||
|
||||
export const EmojiVariant = ({ currentQuestion, variant, index, isSending, setIsSending }: EmojiVariantProps) => {
|
||||
const { quizId, settings, preview } = useQuizSettings();
|
||||
export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps) => {
|
||||
const { settings } = useQuizSettings();
|
||||
const answers = useQuizViewStore((state) => state.answers);
|
||||
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
||||
const theme = useTheme();
|
||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||
const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {};
|
||||
|
||||
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(questionId, variant.id, variant.points || 0);
|
||||
|
||||
updateAnswer(
|
||||
currentQuestion.id,
|
||||
currentQuestion.content.variants[index].id,
|
||||
currentQuestion.content.variants[index].points || 0
|
||||
);
|
||||
} catch (error) {
|
||||
enqueueSnackbar("ответ не был засчитан");
|
||||
if (answer === variant.id) {
|
||||
deleteAnswer(questionId);
|
||||
}
|
||||
|
||||
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`,
|
||||
|
@ -1,10 +1,7 @@
|
||||
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 { Box, RadioGroup, Typography, useTheme } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
||||
import { EmojiVariant } from "./EmojiVariant";
|
||||
|
||||
polyfillCountryFlagEmojis();
|
||||
@ -14,7 +11,6 @@ type EmojiProps = {
|
||||
};
|
||||
|
||||
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();
|
||||
@ -22,7 +18,11 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<RadioGroup
|
||||
@ -47,10 +47,8 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
||||
{currentQuestion.content.variants.map((variant, index) => (
|
||||
<EmojiVariant
|
||||
key={variant.id}
|
||||
currentQuestion={currentQuestion}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
isSending={isSending}
|
||||
setIsSending={setIsSending}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
|
@ -1,69 +1,33 @@
|
||||
import { Box, FormControlLabel, Radio, useTheme } from "@mui/material";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
import { sendAnswer } from "@api/quizRelase";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||
import { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
|
||||
import { Box, FormControlLabel, Radio, useTheme } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
import type { MouseEvent } from "react";
|
||||
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||
import type { QuizQuestionImages } from "@model/questionTypes/images";
|
||||
|
||||
type ImagesProps = {
|
||||
currentQuestion: QuizQuestionImages;
|
||||
questionId: string;
|
||||
variant: QuestionVariant;
|
||||
isSending: boolean;
|
||||
setIsSending: (isSending: boolean) => void;
|
||||
index: number;
|
||||
};
|
||||
|
||||
export const ImageVariant = ({ currentQuestion, variant, isSending, setIsSending, index }: ImagesProps) => {
|
||||
const { settings, quizId, preview } = useQuizSettings();
|
||||
export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
|
||||
const { settings } = useQuizSettings();
|
||||
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 answer = answers.find((answer) => answer.questionId === questionId)?.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("ответ не был засчитан");
|
||||
updateAnswer(questionId, variant.id, variant.points || 0);
|
||||
|
||||
if (answer === variant.id) {
|
||||
deleteAnswer(questionId);
|
||||
}
|
||||
|
||||
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 (
|
||||
|
@ -1,19 +1,14 @@
|
||||
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";
|
||||
import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import { ImageVariant } from "./ImageVariant";
|
||||
|
||||
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;
|
||||
@ -22,7 +17,11 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<RadioGroup
|
||||
@ -47,10 +46,8 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
||||
{currentQuestion.content.variants.map((variant, index) => (
|
||||
<ImageVariant
|
||||
key={variant.id}
|
||||
currentQuestion={currentQuestion}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
isSending={isSending}
|
||||
setIsSending={setIsSending}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
|
@ -1,34 +1,26 @@
|
||||
import { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
import type { QuizQuestionNumber } from "@model/questionTypes/number";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
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 { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
|
||||
import type { ChangeEvent, SyntheticEvent } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
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 } = useQuizSettings();
|
||||
const { settings } = useQuizSettings();
|
||||
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||
const answers = useQuizViewStore((state) => state.answers);
|
||||
const theme = useTheme();
|
||||
@ -49,23 +41,9 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
}, [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("ответ не был засчитан");
|
||||
if (!noUpdate) {
|
||||
updateAnswer(currentQuestion.id, value, 0);
|
||||
}
|
||||
|
||||
setIsSending(false);
|
||||
};
|
||||
|
||||
const updateValueDebounced = useDebouncedCallback(async (value: string) => {
|
||||
@ -180,6 +158,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
setReversedInputValue(String(currentQuestion.content.start));
|
||||
setInputValue(String(currentQuestion.content.start));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onSliderChange = (_: Event, value: number | number[]) => {
|
||||
@ -305,7 +284,11 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
|
@ -1,12 +1,4 @@
|
||||
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 { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
|
||||
import FlagIcon from "@icons/questionsPage/FlagIcon";
|
||||
import StarIconMini from "@icons/questionsPage/StarIconMini";
|
||||
import HashtagIcon from "@icons/questionsPage/hashtagIcon";
|
||||
@ -14,37 +6,73 @@ 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";
|
||||
import { Box, Rating as RatingComponent, Typography, useTheme } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
|
||||
const RATING_FORM_BUTTONS = [
|
||||
{
|
||||
name: "star",
|
||||
icon: (color: string, width: number) => <StarIconMini width={width} color={color} />,
|
||||
icon: (color: string, width: number) => (
|
||||
<StarIconMini
|
||||
width={width}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "trophie",
|
||||
icon: (color: string, width: number) => <TropfyIcon width={width} color={color} />,
|
||||
icon: (color: string, width: number) => (
|
||||
<TropfyIcon
|
||||
width={width}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "flag",
|
||||
icon: (color: string, width: number) => <FlagIcon width={width} color={color} />,
|
||||
icon: (color: string, width: number) => (
|
||||
<FlagIcon
|
||||
width={width}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "heart",
|
||||
icon: (color: string, width: number) => <HeartIcon width={width} color={color} />,
|
||||
icon: (color: string, width: number) => (
|
||||
<HeartIcon
|
||||
width={width}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "like",
|
||||
icon: (color: string, width: number) => <LikeIcon width={width} color={color} />,
|
||||
icon: (color: string, width: number) => (
|
||||
<LikeIcon
|
||||
width={width}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "bubble",
|
||||
icon: (color: string, width: number) => <LightbulbIcon width={width} color={color} />,
|
||||
icon: (color: string, width: number) => (
|
||||
<LightbulbIcon
|
||||
width={width}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "hashtag",
|
||||
icon: (color: string, width: number) => <HashtagIcon width={width} color={color} />,
|
||||
icon: (color: string, width: number) => (
|
||||
<HashtagIcon
|
||||
width={width}
|
||||
color={color}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@ -53,38 +81,26 @@ type RatingProps = {
|
||||
};
|
||||
|
||||
export const Rating = ({ currentQuestion }: RatingProps) => {
|
||||
const [isSending, setIsSending] = useState<boolean>(false);
|
||||
const { quizId, preview } = useQuizSettings();
|
||||
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);
|
||||
updateAnswer(currentQuestion.id, String(value), 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
@ -98,7 +114,6 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
|
||||
>
|
||||
<Box sx={{ display: "inline-block", width: "100%" }}>
|
||||
<RatingComponent
|
||||
disabled={isSending}
|
||||
value={Number(answer || 0)}
|
||||
onChange={(_, value) => sendRating(value)}
|
||||
sx={{
|
||||
|
@ -1,68 +1,38 @@
|
||||
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 { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
|
||||
import type { QuizQuestionSelect } from "@model/questionTypes/select";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
|
||||
type SelectProps = {
|
||||
currentQuestion: QuizQuestionSelect;
|
||||
};
|
||||
|
||||
export const Select = ({ currentQuestion }: SelectProps) => {
|
||||
const [isSending, setIsSending] = useState<boolean>(false);
|
||||
const { quizId, settings, preview } = useQuizSettings();
|
||||
const { settings } = useQuizSettings();
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
updateAnswer(currentQuestion.id, String(value), 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
@ -74,7 +44,6 @@ export const Select = ({ currentQuestion }: SelectProps) => {
|
||||
}}
|
||||
>
|
||||
<SelectComponent
|
||||
disabled={isSending}
|
||||
placeholder={currentQuestion.content.default}
|
||||
activeItemIndex={answer ? Number(answer) : -1}
|
||||
items={currentQuestion.content.variants.map(({ answer }) => answer)}
|
||||
|
@ -14,11 +14,10 @@ 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) => {
|
||||
export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => {
|
||||
const { settings } = useQuizSettings();
|
||||
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||
const isMobile = useRootContainerSize() < 650;
|
||||
@ -26,12 +25,15 @@ export const TextNormal = ({ currentQuestion, answer, inputHC }: TextNormalProps
|
||||
|
||||
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" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
|
@ -41,11 +41,10 @@ const ORIENTATION = [
|
||||
interface TextSpecialProps {
|
||||
currentQuestion: QuizQuestionText;
|
||||
answer?: Answer;
|
||||
inputHC: (text: string) => void;
|
||||
stepNumber?: number | null;
|
||||
}
|
||||
|
||||
export const TextSpecial = ({ currentQuestion, answer, inputHC, stepNumber }: TextSpecialProps) => {
|
||||
export const TextSpecial = ({ currentQuestion, answer, stepNumber }: TextSpecialProps) => {
|
||||
const { settings } = useQuizSettings();
|
||||
const { updateAnswer } = useQuizViewStore((state) => state);
|
||||
const isHorizontal = ORIENTATION[Number(stepNumber) - 1].horizontal;
|
||||
@ -54,7 +53,6 @@ export const TextSpecial = ({ currentQuestion, answer, inputHC, stepNumber }: Te
|
||||
|
||||
const onInputChange = async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||
updateAnswer(currentQuestion.id, target.value, 0);
|
||||
inputHC(target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -75,7 +73,11 @@ export const TextSpecial = ({ currentQuestion, answer, inputHC, stepNumber }: Te
|
||||
gap: "20px",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
{isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
|
||||
|
@ -1,13 +1,7 @@
|
||||
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 { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import { TextNormal } from "./TextNormal";
|
||||
import { TextSpecial } from "./TextSpecial";
|
||||
|
||||
import type { QuizQuestionText } from "@model/questionTypes/text";
|
||||
|
||||
@ -17,42 +11,34 @@ type TextProps = {
|
||||
};
|
||||
|
||||
export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
|
||||
const [isSending, setIsSending] = useState<boolean>(false);
|
||||
const { settings, preview, quizId } = useQuizSettings();
|
||||
const { settings } = useQuizSettings();
|
||||
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} />
|
||||
<TextSpecial
|
||||
currentQuestion={currentQuestion}
|
||||
answer={answer}
|
||||
stepNumber={stepNumber}
|
||||
/>
|
||||
);
|
||||
|
||||
case undefined:
|
||||
return <TextNormal currentQuestion={currentQuestion} answer={answer} inputHC={inputHC} />;
|
||||
return (
|
||||
<TextNormal
|
||||
currentQuestion={currentQuestion}
|
||||
answer={answer}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return <TextNormal currentQuestion={currentQuestion} answer={answer} inputHC={inputHC} />;
|
||||
return (
|
||||
<TextNormal
|
||||
currentQuestion={currentQuestion}
|
||||
answer={answer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,123 +1,61 @@
|
||||
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 { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
|
||||
import { CheckboxIcon } from "@icons/Checkbox";
|
||||
import type { QuestionVariant } from "@model/questionTypes/shared";
|
||||
import { Checkbox, FormControlLabel, TextField as MuiTextField, Radio, TextFieldProps, useTheme } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
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,
|
||||
questionId,
|
||||
isMulti,
|
||||
variant,
|
||||
answer,
|
||||
index,
|
||||
own = false,
|
||||
isSending,
|
||||
setIsSending,
|
||||
}: {
|
||||
currentQuestion: QuizQuestionVariant;
|
||||
isMulti: boolean;
|
||||
questionId: string;
|
||||
variant: QuestionVariant;
|
||||
answer: string | string[] | undefined;
|
||||
index: number;
|
||||
own?: boolean;
|
||||
isSending: boolean;
|
||||
setIsSending: (a: boolean) => void;
|
||||
}) => {
|
||||
const { settings, quizId, preview } = useQuizSettings();
|
||||
const { settings } = useQuizSettings();
|
||||
const theme = useTheme();
|
||||
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
||||
|
||||
const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (isSending) {
|
||||
return;
|
||||
}
|
||||
const variantId = variant.id;
|
||||
|
||||
setIsSending(true);
|
||||
|
||||
const variantId = currentQuestion.content.variants[index].id;
|
||||
|
||||
if (currentQuestion.content.multi) {
|
||||
if (isMulti) {
|
||||
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.error(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
|
||||
return updateAnswer(
|
||||
questionId,
|
||||
currentAnswer.includes(variantId)
|
||||
? currentAnswer?.filter((item) => item !== variantId)
|
||||
: [...currentAnswer, variantId],
|
||||
variant.points || 0
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
enqueueSnackbar("ответ не был засчитан");
|
||||
}
|
||||
|
||||
updateAnswer(questionId, variantId, answer === variantId ? 0 : variant.points || 0);
|
||||
|
||||
if (answer === variantId) {
|
||||
try {
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: "",
|
||||
qid: quizId,
|
||||
preview,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
enqueueSnackbar("ответ не был засчитан");
|
||||
}
|
||||
deleteAnswer(currentQuestion.id);
|
||||
deleteAnswer(questionId);
|
||||
}
|
||||
|
||||
setIsSending(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={variant.id}
|
||||
disabled={isSending}
|
||||
sx={{
|
||||
margin: "0",
|
||||
borderRadius: "12px",
|
||||
@ -154,14 +92,22 @@ export const VariantItem = ({
|
||||
value={index}
|
||||
labelPlacement="start"
|
||||
control={
|
||||
currentQuestion.content.multi ? (
|
||||
isMulti ? (
|
||||
<Checkbox
|
||||
checked={!!answer?.includes(variant.id)}
|
||||
checkedIcon={<CheckboxIcon checked color={theme.palette.primary.main} />}
|
||||
checkedIcon={
|
||||
<CheckboxIcon
|
||||
checked
|
||||
color={theme.palette.primary.main}
|
||||
/>
|
||||
}
|
||||
icon={<CheckboxIcon />}
|
||||
/>
|
||||
) : (
|
||||
<Radio checkedIcon={<RadioCheck color={theme.palette.primary.main} />} icon={<RadioIcon />} />
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
label={own ? <TextField label="Другое..." /> : variant.answer}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Box, FormGroup, RadioGroup, Typography, useTheme } from "@mui/material";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { VariantItem } from "./VariantItem";
|
||||
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
|
||||
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
import moment from "moment";
|
||||
@ -14,13 +14,13 @@ type VariantProps = {
|
||||
};
|
||||
|
||||
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 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 answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer;
|
||||
const ownVariant = ownVariants.find((variant) => variant.id === currentQuestion.id);
|
||||
|
||||
const Group = currentQuestion.content.multi ? FormGroup : RadioGroup;
|
||||
@ -29,13 +29,18 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
|
||||
if (!ownVariant) {
|
||||
updateOwnVariant(currentQuestion.id, "");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
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" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
@ -71,23 +76,21 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
|
||||
{currentQuestion.content.variants.map((variant, index) => (
|
||||
<VariantItem
|
||||
key={variant.id}
|
||||
currentQuestion={currentQuestion}
|
||||
questionId={currentQuestion.id}
|
||||
isMulti={currentQuestion.content.multi}
|
||||
variant={variant}
|
||||
answer={answer}
|
||||
index={index}
|
||||
isSending={isSending}
|
||||
setIsSending={setIsSending}
|
||||
/>
|
||||
))}
|
||||
{currentQuestion.content.own && ownVariant && (
|
||||
<VariantItem
|
||||
own
|
||||
currentQuestion={currentQuestion}
|
||||
questionId={currentQuestion.id}
|
||||
isMulti={currentQuestion.content.multi}
|
||||
variant={ownVariant.variant}
|
||||
answer={answer}
|
||||
index={currentQuestion.content.variants.length + 2}
|
||||
isSending={isSending}
|
||||
setIsSending={setIsSending}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -1,75 +1,37 @@
|
||||
import { FormControlLabel, Radio, useTheme } from "@mui/material";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
|
||||
import { sendAnswer } from "@api/quizRelase";
|
||||
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||
import { useQuizSettings } from "@contexts/QuizDataContext";
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
|
||||
import { FormControlLabel, Radio, useTheme } from "@mui/material";
|
||||
import { useQuizViewStore } from "@stores/quizView";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
import type { MouseEvent } from "react";
|
||||
import type { QuestionVariant } from "@/model/questionTypes/shared";
|
||||
import type { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||
|
||||
type VarimgVariantProps = {
|
||||
currentQuestion: QuizQuestionVarImg;
|
||||
questionId: string;
|
||||
variant: QuestionVariant;
|
||||
index: number;
|
||||
isSending: boolean;
|
||||
setIsSending: (isSending: boolean) => void;
|
||||
};
|
||||
|
||||
export const VarimgVariant = ({ currentQuestion, variant, index, isSending, setIsSending }: VarimgVariantProps) => {
|
||||
const { settings, quizId, preview } = useQuizSettings();
|
||||
export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSending }: VarimgVariantProps) => {
|
||||
const { settings } = useQuizSettings();
|
||||
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
|
||||
const answers = useQuizViewStore((state) => state.answers);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||
const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {};
|
||||
|
||||
const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
setIsSending(true);
|
||||
updateAnswer(questionId, variant.id, variant.points || 0);
|
||||
|
||||
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 === variant.id) {
|
||||
deleteAnswer(questionId);
|
||||
}
|
||||
|
||||
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 (
|
||||
@ -110,7 +72,12 @@ export const VarimgVariant = ({ currentQuestion, variant, index, isSending, setI
|
||||
value={index}
|
||||
onClick={sendVariant}
|
||||
label={variant.answer}
|
||||
control={<Radio checkedIcon={<RadioCheck color={theme.palette.primary.main} />} icon={<RadioIcon />} />}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -26,7 +26,11 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color={theme.palette.text.primary}
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
@ -63,7 +67,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
{currentQuestion.content.variants.map((variant, index) => (
|
||||
<VarimgVariant
|
||||
key={variant.id}
|
||||
currentQuestion={currentQuestion}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
isSending={isSending}
|
||||
setIsSending={setIsSending}
|
||||
|
@ -14,7 +14,6 @@ type SelectProps = {
|
||||
colorMain?: string;
|
||||
colorPlaceholder?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export const Select = ({
|
||||
@ -26,7 +25,6 @@ export const Select = ({
|
||||
placeholder = "",
|
||||
colorMain = "#7E2AEA",
|
||||
colorPlaceholder = "#9A9AAF",
|
||||
disabled = false,
|
||||
}: SelectProps) => {
|
||||
const [activeItem, setActiveItem] = useState<number>(empty ? -1 : activeItemIndex);
|
||||
const theme = useTheme();
|
||||
@ -50,7 +48,11 @@ export const Select = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl disabled={disabled} fullWidth size="small" sx={{ width: "100%", height: "48px", ...sx }}>
|
||||
<FormControl
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{ width: "100%", height: "48px", ...sx }}
|
||||
>
|
||||
<MuiSelect
|
||||
displayEmpty
|
||||
renderValue={(value) =>
|
||||
|
@ -5,10 +5,11 @@ import { nanoid } from "nanoid";
|
||||
import { createContext, useContext } from "react";
|
||||
import { createStore, useStore } from "zustand";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
import { devtools } from "zustand/middleware";
|
||||
|
||||
export type Answer = string | string[] | Moment;
|
||||
|
||||
type QuestionAnswer = {
|
||||
export type QuestionAnswer = {
|
||||
questionId: string;
|
||||
answer: Answer;
|
||||
};
|
||||
@ -45,59 +46,102 @@ export function useQuizViewStore<U>(selector: (state: QuizViewStore & QuizViewAc
|
||||
|
||||
export const createQuizViewStore = () =>
|
||||
createStore<QuizViewStore & QuizViewActions>()(
|
||||
immer((set, get) => ({
|
||||
answers: [],
|
||||
ownVariants: [],
|
||||
points: {},
|
||||
pointsSum: 0,
|
||||
currentQuizStep: "startpage",
|
||||
updateAnswer(questionId, answer, points) {
|
||||
set((state) => {
|
||||
const index = state.answers.findIndex((answer) => questionId === answer.questionId);
|
||||
immer(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
answers: [],
|
||||
ownVariants: [],
|
||||
points: {},
|
||||
pointsSum: 0,
|
||||
currentQuizStep: "startpage",
|
||||
updateAnswer(questionId, answer, points) {
|
||||
set(
|
||||
(state) => {
|
||||
const index = state.answers.findIndex((answer) => questionId === answer.questionId);
|
||||
|
||||
if (index < 0) {
|
||||
state.answers.push({ questionId, answer });
|
||||
} else {
|
||||
state.answers[index] = { questionId, answer };
|
||||
}
|
||||
if (index < 0) {
|
||||
state.answers.push({ questionId, answer });
|
||||
} else {
|
||||
state.answers[index] = { questionId, answer };
|
||||
}
|
||||
|
||||
state.points = { ...state.points, ...{ [questionId]: points } };
|
||||
state.points = { ...state.points, ...{ [questionId]: points } };
|
||||
|
||||
state.pointsSum = Object.values(state.points).reduce((sum, value) => sum + value);
|
||||
});
|
||||
},
|
||||
deleteAnswer(questionId) {
|
||||
set((state) => {
|
||||
state.answers = state.answers.filter((answer) => questionId !== answer.questionId);
|
||||
});
|
||||
},
|
||||
updateOwnVariant(id, answer) {
|
||||
set((state) => {
|
||||
const index = state.ownVariants.findIndex((variant) => variant.id === id);
|
||||
|
||||
if (index < 0) {
|
||||
state.ownVariants.push({
|
||||
id,
|
||||
variant: {
|
||||
id: nanoid(),
|
||||
answer,
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
originalImageUrl: "",
|
||||
state.pointsSum = Object.values(state.points).reduce((sum, value) => sum + value);
|
||||
},
|
||||
false,
|
||||
{
|
||||
type: "updateAnswer",
|
||||
questionId,
|
||||
answer,
|
||||
points,
|
||||
}
|
||||
);
|
||||
},
|
||||
deleteAnswer(questionId) {
|
||||
set(
|
||||
(state) => {
|
||||
state.answers = state.answers.filter((answer) => questionId !== answer.questionId);
|
||||
},
|
||||
false,
|
||||
{
|
||||
type: "deleteAnswer",
|
||||
questionId,
|
||||
}
|
||||
);
|
||||
},
|
||||
updateOwnVariant(id, answer) {
|
||||
set(
|
||||
(state) => {
|
||||
const index = state.ownVariants.findIndex((variant) => variant.id === id);
|
||||
|
||||
if (index < 0) {
|
||||
state.ownVariants.push({
|
||||
id,
|
||||
variant: {
|
||||
id: nanoid(),
|
||||
answer,
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
originalImageUrl: "",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
state.ownVariants[index].variant.answer = answer;
|
||||
}
|
||||
},
|
||||
false,
|
||||
{
|
||||
type: "updateOwnVariant",
|
||||
id,
|
||||
answer,
|
||||
}
|
||||
);
|
||||
},
|
||||
deleteOwnVariant(id) {
|
||||
set(
|
||||
(state) => {
|
||||
state.ownVariants = state.ownVariants.filter((variant) => variant.id !== id);
|
||||
},
|
||||
false,
|
||||
{
|
||||
type: "deleteOwnVariant",
|
||||
id,
|
||||
}
|
||||
);
|
||||
},
|
||||
setCurrentQuizStep(step) {
|
||||
set({ currentQuizStep: step }, false, {
|
||||
type: "setCurrentQuizStep",
|
||||
step,
|
||||
});
|
||||
} else {
|
||||
state.ownVariants[index].variant.answer = answer;
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteOwnVariant(id) {
|
||||
set((state) => {
|
||||
state.ownVariants = state.ownVariants.filter((variant) => variant.id !== id);
|
||||
});
|
||||
},
|
||||
setCurrentQuizStep(step) {
|
||||
set({ currentQuizStep: step });
|
||||
},
|
||||
}))
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "QuizViewStore-" + nanoid(4),
|
||||
enabled: import.meta.env.DEV,
|
||||
trace: import.meta.env.DEV,
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
|
119
lib/utils/sendQuestionAnswer.ts
Normal file
119
lib/utils/sendQuestionAnswer.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { sendAnswer } from "@/api/quizRelase";
|
||||
import { RealTypedQuizQuestion } from "@/model/questionTypes/shared";
|
||||
import { QuestionAnswer } from "@/stores/quizView";
|
||||
import moment from "moment";
|
||||
import { notReachable } from "./notReachable";
|
||||
|
||||
export function sendQuestionAnswer(quizId: string, question: RealTypedQuizQuestion, questionAnswer: QuestionAnswer) {
|
||||
switch (question.type) {
|
||||
case "date": {
|
||||
if (!moment.isMoment(questionAnswer.answer)) throw new Error("Cannot send answer in date question");
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: moment(questionAnswer.answer).format("YYYY.MM.DD"),
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
case "emoji": {
|
||||
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
|
||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: variant.extendedText + " " + variant.answer,
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
case "file": {
|
||||
return;
|
||||
}
|
||||
case "images": {
|
||||
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
|
||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: `${variant.answer} <img style="width:100%; max-width:250px; max-height:250px" src="${variant.extendedText}"/>`,
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
case "number": {
|
||||
if (typeof questionAnswer.answer !== "string") throw new Error("Cannot send answer in select question");
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: questionAnswer.answer,
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
case "page": {
|
||||
return;
|
||||
}
|
||||
case "rating": {
|
||||
if (typeof questionAnswer.answer !== "string") throw new Error("Cannot send answer in select question");
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: String(questionAnswer.answer) + " из " + question.content.steps,
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
case "select": {
|
||||
if (typeof questionAnswer.answer !== "string") throw new Error("Cannot send answer in select question");
|
||||
|
||||
const variant = question.content.variants[Number(questionAnswer.answer)];
|
||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: variant.answer,
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
case "text": {
|
||||
if (moment.isMoment(questionAnswer.answer)) throw new Error("Cannot send Moment in text question");
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: questionAnswer.answer,
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
case "variant": {
|
||||
if (question.content.multi) {
|
||||
const answer = questionAnswer.answer;
|
||||
if (!Array.isArray(answer)) throw new Error("Cannot send answer in select question");
|
||||
|
||||
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: selectedVariants.map((v) => v.answer).join(", "),
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
|
||||
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
|
||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: variant.answer,
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
case "varimg": {
|
||||
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
|
||||
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
|
||||
|
||||
return sendAnswer({
|
||||
questionId: question.id,
|
||||
body: `${variant.answer} <img style="width:100%; max-width:250px; max-height:250px" src="${variant.extendedText}"/>`,
|
||||
qid: quizId,
|
||||
});
|
||||
}
|
||||
default:
|
||||
notReachable(question);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user