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