Merge branch 'staging' into 'main'

подправлена стартовая станица мобилки, автофокус на инпуте, отправка данных...

See merge request frontend/squzanswerer!83
This commit is contained in:
Mikhail 2024-02-21 18:42:46 +00:00
commit 41f1b57c9a
18 changed files with 5422 additions and 411 deletions

@ -160,7 +160,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
sx={{ sx={{
width: isWide && !isMobile ? "100%" : isMobile ? undefined : "530px", width: isWide && !isMobile ? "100%" : isMobile ? undefined : "530px",
borderRadius: "4px", borderRadius: "4px",
height: "90vh", height: "100%",
display: isWide && !isMobile ? "flex" : undefined, display: isWide && !isMobile ? "flex" : undefined,
}} }}
> >

@ -1,4 +1,4 @@
import {Box, Link, useTheme} from "@mui/material"; import { Box, Link, useTheme } from "@mui/material";
import { Footer } from "./Footer"; import { Footer } from "./Footer";
import { Date } from "./questions/Date"; import { Date } from "./questions/Date";
@ -15,13 +15,12 @@ import { Varimg } from "./questions/Varimg";
import type { RealTypedQuizQuestion } from "../../model/questionTypes/shared"; import type { RealTypedQuizQuestion } from "../../model/questionTypes/shared";
import { useQuizData } from "@contexts/QuizDataContext";
import { NameplateLogoFQ } from "@icons/NameplateLogoFQ"; import { NameplateLogoFQ } from "@icons/NameplateLogoFQ";
import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark"; import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark";
import { useQuizData } from "@contexts/QuizDataContext";
import { notReachable } from "@utils/notReachable"; import { notReachable } from "@utils/notReachable";
import { quizThemes } from "@utils/themes/Publication/themePublication"; import { quizThemes } from "@utils/themes/Publication/themePublication";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
type Props = { type Props = {
currentQuestion: RealTypedQuizQuestion; currentQuestion: RealTypedQuizQuestion;
@ -38,12 +37,11 @@ export const Question = ({
}: Props) => { }: Props) => {
const theme = useTheme(); const theme = useTheme();
const { settings } = useQuizData(); const { settings } = useQuizData();
const isMobile = useRootContainerSize() < 650; console.log(currentQuestionStepNumber)
console.log(settings)
return ( return (
<Box sx={{ <Box sx={{
backgroundColor: theme.palette.background.default, backgroundColor: theme.palette.background.default,
height: isMobile ? "100%" : "100vh" height: "100%",
}}> }}>
<Box sx={{ <Box sx={{
height: "calc(100% - 75px)", height: "calc(100% - 75px)",
@ -56,11 +54,11 @@ console.log(settings)
flexDirection: "column", flexDirection: "column",
justifyContent: "space-between" justifyContent: "space-between"
}}> }}>
<QuestionByType key={currentQuestion.id} question={currentQuestion} /> <QuestionByType key={currentQuestion.id} question={currentQuestion} stepNumber={currentQuestionStepNumber} />
{quizThemes[settings.cfg.theme].isLight ? ( {quizThemes[settings.cfg.theme].isLight ? (
<Link target={"_blank"} href={"https://quiz.pena.digital"}> <Link target={"_blank"} href={"https://quiz.pena.digital"}>
<NameplateLogoFQ style={{ fontSize: "34px", width: "200px", height: "auto" }} /> <NameplateLogoFQ style={{ fontSize: "34px", width: "200px", height: "auto" }} />
</Link> </Link>
) : ( ) : (
<Link target={"_blank"} href={"https://quiz.pena.digital"}> <Link target={"_blank"} href={"https://quiz.pena.digital"}>
<NameplateLogoFQDark style={{ fontSize: "34px", width: "200px", height: "auto" }} /> <NameplateLogoFQDark style={{ fontSize: "34px", width: "200px", height: "auto" }} />
@ -77,15 +75,16 @@ console.log(settings)
); );
}; };
function QuestionByType({ question }: { function QuestionByType({ question, stepNumber }: {
question: RealTypedQuizQuestion; question: RealTypedQuizQuestion;
stepNumber: number | null;
}) { }) {
switch (question.type) { switch (question.type) {
case "variant": return <Variant currentQuestion={question} />; case "variant": return <Variant currentQuestion={question} />;
case "images": return <Images currentQuestion={question} />; case "images": return <Images currentQuestion={question} />;
case "varimg": return <Varimg currentQuestion={question} />; case "varimg": return <Varimg currentQuestion={question} />;
case "emoji": return <Emoji currentQuestion={question} />; case "emoji": return <Emoji currentQuestion={question} />;
case "text": return <Text currentQuestion={question} />; case "text": return <Text currentQuestion={question} stepNumber={stepNumber}/>;
case "select": return <Select currentQuestion={question} />; case "select": return <Select currentQuestion={question} />;
case "date": return <Date currentQuestion={question} />; case "date": return <Date currentQuestion={question} />;
case "number": return <Number currentQuestion={question} />; case "number": return <Number currentQuestion={question} />;

@ -23,6 +23,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useRootContainerSize() < 650; const isMobile = useRootContainerSize() < 650;
const { settings } = useQuizData(); const { settings } = useQuizData();
const spec = settings.cfg.spec
return ( return (
<Box <Box
@ -32,7 +33,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
height: "100%", height: "100%",
width: "100vw", width: "100%",
pt: "28px", pt: "28px",
overflow: "auto", overflow: "auto",
}} }}
@ -63,8 +64,8 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
component="img" component="img"
src={resultQuestion.content.back} src={resultQuestion.content.back}
sx={{ sx={{
width: isMobile ? "100%" : "490px", width: spec ? "100%" : isMobile ? "100%" : "490px",
height: isMobile ? "100%" : "280px", height: spec ? "auto" : isMobile ? "100%" : "280px",
}} }}
></Box> ></Box>
) )

@ -21,7 +21,7 @@ export const StartPageViewPublication = () => {
const handleCopyNumber = () => { const handleCopyNumber = () => {
navigator.clipboard.writeText(settings.cfg.info.phonenumber); navigator.clipboard.writeText(settings.cfg.info.phonenumber);
}; };
console.log(settings.cfg.startpage.background.type) console.log(settings.cfg.startpage.background.type);
const background = const background =
settings.cfg.startpage.background.type === "image" ? ( settings.cfg.startpage.background.type === "image" ? (
@ -45,9 +45,7 @@ export const StartPageViewPublication = () => {
width: width:
settings.cfg.startpageType === "centered" settings.cfg.startpageType === "centered"
? "550px" ? "550px"
: settings.cfg.startpageType === "expanded" : "100%",
? "100vw"
: "100%",
height: height:
settings.cfg.startpageType === "centered" settings.cfg.startpageType === "centered"
? "275px" ? "275px"
@ -304,7 +302,7 @@ function QuizPreviewLayoutByType({
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "flex-start", alignItems: "flex-start",
p: "25px", p: "25px",
height: "80%", height: "100%",
overflowY: "auto", overflowY: "auto",
overflowX: "hidden" overflowX: "hidden"
}} }}
@ -345,7 +343,7 @@ function QuizPreviewLayoutByType({
<StartPageMobile /> <StartPageMobile />
) : ( ) : (
<Box <Box
id="pain" id="pain"
sx={{ sx={{
display: "flex", display: "flex",
flexDirection: alignType === "left" ? (isMobile ? "column-reverse" : "row") : "row-reverse", flexDirection: alignType === "left" ? (isMobile ? "column-reverse" : "row") : "row-reverse",

@ -25,7 +25,7 @@ export const Date = ({ currentQuestion }: DateProps) => {
({ questionId }) => questionId === currentQuestion.id ({ questionId }) => questionId === currentQuestion.id
)?.answer as string; )?.answer as string;
const currentAnswer = moment(answer) || moment(); const currentAnswer = moment(answer) || moment();
const [readySend, setReadySend] = useState(true) const [isSending, setIsSending] = useState<boolean>(false);
return ( return (
<Box> <Box>
@ -53,29 +53,26 @@ export const Date = ({ currentQuestion }: DateProps) => {
}} }}
value={currentAnswer} value={currentAnswer}
onChange={async (date) => { onChange={async (date) => {
if (readySend) { if (isSending || !date) return;
setReadySend(false)
if (!date) {
return;
}
try { setIsSending(true);
await sendAnswer({ try {
questionId: currentQuestion.id, await sendAnswer({
body: moment(date).format("YYYY.MM.DD"), questionId: currentQuestion.id,
qid: quizId, body: moment(date).format("YYYY.MM.DD"),
}); qid: quizId,
});
updateAnswer( updateAnswer(
currentQuestion.id, currentQuestion.id,
date, date,
0 0
); );
} catch (e) { } catch (e) {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
}
setReadySend(true)
} }
setIsSending(false);
}} }}
slotProps={{ slotProps={{
openPickerButton: { openPickerButton: {

@ -36,7 +36,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
answers.find( answers.find(
({ questionId }) => questionId === currentQuestion.id ({ questionId }) => questionId === currentQuestion.id
) ?? {}; ) ?? {};
const [readySend, setReadySend] = useState(true) const [isSending, setIsSending] = useState<boolean>(false);
return ( return (
<Box> <Box>
@ -70,6 +70,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
{currentQuestion.content.variants.map((variant, index) => ( {currentQuestion.content.variants.map((variant, index) => (
<FormControl <FormControl
key={variant.id} key={variant.id}
disabled={isSending}
sx={{ sx={{
borderRadius: "12px", borderRadius: "12px",
border: `1px solid`, border: `1px solid`,
@ -130,45 +131,39 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
value={index} value={index}
onClick={async (event) => { onClick={async (event) => {
event.preventDefault(); event.preventDefault();
if (readySend) { if (isSending) return;
setReadySend(false)
try {
setIsSending(true);
try {
await sendAnswer({
questionId: currentQuestion.id,
body: currentQuestion.content.variants[index].extendedText + " " + currentQuestion.content.variants[index].answer,
qid: quizId,
});
updateAnswer(
currentQuestion.id,
currentQuestion.content.variants[index].id,
currentQuestion.content.variants[index].points || 0
);
} catch (e) {
enqueueSnackbar("ответ не был засчитан");
}
if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.id);
try {
await sendAnswer({ await sendAnswer({
questionId: currentQuestion.id, questionId: currentQuestion.id,
body: currentQuestion.content.variants[index].extendedText + " " + currentQuestion.content.variants[index].answer, body: "",
qid: quizId, qid: quizId,
}); });
updateAnswer(
currentQuestion.id,
currentQuestion.content.variants[index].id,
currentQuestion.content.variants[index].points || 0
);
} catch (e) { } catch (e) {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.id);
try {
await sendAnswer({
questionId: currentQuestion.id,
body: "",
qid: quizId,
});
} catch (e) {
enqueueSnackbar("ответ не был засчитан");
}
}
setReadySend(true)
} }
setIsSending(false);
}} }}
control={ control={

@ -16,6 +16,7 @@ import { enqueueSnackbar } from "notistack";
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 { useQuizData } from "@contexts/QuizDataContext"; import { useQuizData } from "@contexts/QuizDataContext";
import { useState } from "react";
type ImagesProps = { type ImagesProps = {
currentQuestion: QuizQuestionImages; currentQuestion: QuizQuestionImages;
@ -26,6 +27,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer; const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer;
const [isSending, setIsSending] = useState<boolean>(false);
const isTablet = useRootContainerSize() < 1000; const isTablet = useRootContainerSize() < 1000;
const isMobile = useRootContainerSize() < 500; const isMobile = useRootContainerSize() < 500;
@ -65,43 +67,44 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
borderRadius: "5px", borderRadius: "5px",
border: `1px solid`, border: `1px solid`,
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF", borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
transition: "opacity 0.5s ease",
opacity: isSending ? 0.5 : 1,
pointerEvents: isSending ? "none" : "auto",
}} }}
onClick={async (event) => { onClick={async (event) => {
event.preventDefault(); event.preventDefault();
if (isSending) return;
setIsSending(true);
try { try {
await sendAnswer({ await sendAnswer({
questionId: currentQuestion.id, 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}"/>`, body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
qid: quizId, qid: quizId,
}); });
updateAnswer( updateAnswer(
currentQuestion.id, currentQuestion.id,
currentQuestion.content.variants[index].id, currentQuestion.content.variants[index].id,
currentQuestion.content.variants[index].points || 0 currentQuestion.content.variants[index].points || 0
); );
} catch (e) { } catch (e) {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
if (answer === currentQuestion.content.variants[index].id) { if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.id); deleteAnswer(currentQuestion.id);
try { try {
await sendAnswer({ await sendAnswer({
questionId: currentQuestion.id, questionId: currentQuestion.id,
body: "", body: "",
qid: quizId, qid: quizId,
}); });
} catch (e) { } catch (e) {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
} }
setIsSending(false);
}} }}
> >
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}> <Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
@ -159,5 +162,4 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
</RadioGroup> </RadioGroup>
</Box> </Box>
); );
}; };

@ -32,6 +32,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
useState<string>("100000000000"); useState<string>("100000000000");
const theme = useTheme(); const theme = useTheme();
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const [isSending, setIsSending] = useState<boolean>(false);
const isMobile = useRootContainerSize() < 650; const isMobile = useRootContainerSize() < 650;
const [minBorder, maxBorder] = currentQuestion.content.range const [minBorder, maxBorder] = currentQuestion.content.range
@ -40,8 +41,13 @@ export const Number = ({ currentQuestion }: NumberProps) => {
const min = minBorder < maxBorder ? minBorder : maxBorder; const min = minBorder < maxBorder ? minBorder : maxBorder;
const max = minBorder < maxBorder ? maxBorder : minBorder; const max = minBorder < maxBorder ? maxBorder : minBorder;
const reversed = minBorder > maxBorder; const reversed = minBorder > maxBorder;
useEffect(() => {
console.log("reversed:", reversed)
}, [reversed])
const sendAnswerToBackend = async (value: string, noUpdate = false) => { const sendAnswerToBackend = async (value: string, noUpdate = false) => {
setIsSending(true);
try { try {
await sendAnswer({ await sendAnswer({
questionId: currentQuestion.id, questionId: currentQuestion.id,
@ -55,6 +61,8 @@ export const Number = ({ currentQuestion }: NumberProps) => {
} catch (e) { } catch (e) {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
setIsSending(false);
}; };
const updateValueDebounced = useDebouncedCallback(async (value: string) => { const updateValueDebounced = useDebouncedCallback(async (value: string) => {
@ -353,7 +361,7 @@ 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

@ -43,7 +43,7 @@ export const Page = ({ currentQuestion }: PageProps) => {
containerSX={{ containerSX={{
width: "100%", width: "100%",
height: "calc(100% - 270px)", height: "calc(100% - 270px)",
maxHeight: "80vh", maxHeight: "80%",
objectFit: "contain", objectFit: "contain",
}} }}
videoUrl={currentQuestion.content.video} videoUrl={currentQuestion.content.video}

@ -20,6 +20,7 @@ import { enqueueSnackbar } from "notistack";
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext"; import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
import type { QuizQuestionRating } from "../../../model/questionTypes/rating"; import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
import { useQuizData } from "@contexts/QuizDataContext"; import { useQuizData } from "@contexts/QuizDataContext";
import { useState } from "react";
type RatingProps = { type RatingProps = {
currentQuestion: QuizQuestionRating; currentQuestion: QuizQuestionRating;
@ -61,6 +62,7 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const isMobile = useRootContainerSize() < 650; const isMobile = useRootContainerSize() < 650;
const [isSending, setIsSending] = useState<boolean>(false);
const { answer } = const { answer } =
answers.find( answers.find(
({ questionId }) => questionId === currentQuestion.id ({ questionId }) => questionId === currentQuestion.id
@ -89,10 +91,10 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
}} }}
> >
<RatingComponent <RatingComponent
disabled={isSending}
value={Number(answer || 0)} value={Number(answer || 0)}
onChange={async (_, value) => { onChange={async (_, value) => {
setIsSending(true);
try { try {
await sendAnswer({ await sendAnswer({
@ -106,12 +108,15 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
} catch (e) { } catch (e) {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
setIsSending(false);
}} }}
sx={{ sx={{
height: "50px", height: "50px",
gap: isMobile ? undefined : "15px", gap: isMobile ? undefined : "15px",
justifyContent: isMobile ? "space-between" : undefined, justifyContent: isMobile ? "space-between" : undefined,
width: isMobile ? "100%" : undefined width: isMobile ? "100%" : undefined,
transition: "opacity 0.5s ease",
}} }}
max={currentQuestion.content.steps} max={currentQuestion.content.steps}
icon={form?.icon(theme.palette.primary.main)} icon={form?.icon(theme.palette.primary.main)}

@ -8,6 +8,7 @@ import { sendAnswer } from "@api/quizRelase";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
import { useQuizData } from "@contexts/QuizDataContext"; import { useQuizData } from "@contexts/QuizDataContext";
import { useState } from "react";
type SelectProps = { type SelectProps = {
currentQuestion: QuizQuestionSelect; currentQuestion: QuizQuestionSelect;
@ -16,6 +17,7 @@ type SelectProps = {
export const Select = ({ currentQuestion }: SelectProps) => { export const Select = ({ currentQuestion }: SelectProps) => {
const theme = useTheme(); const theme = useTheme();
const { quizId } = useQuizData(); const { quizId } = useQuizData();
const [isSending, setIsSending] = useState<boolean>(false);
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const { answer } = const { answer } =
answers.find( answers.find(
@ -24,7 +26,7 @@ export const Select = ({ currentQuestion }: SelectProps) => {
return ( return (
<Box> <Box>
<Typography variant="h5" color={theme.palette.text.primary} sx={{wordBreak: "break-word"}}>{currentQuestion.title}</Typography> <Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>{currentQuestion.title}</Typography>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -34,11 +36,13 @@ 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)}
colorMain={theme.palette.primary.main} colorMain={theme.palette.primary.main}
onChange={async (_, value) => { onChange={async (_, value) => {
setIsSending(true);
if (value < 0) { if (value < 0) {
deleteAnswer(currentQuestion.id); deleteAnswer(currentQuestion.id);
try { try {
@ -52,7 +56,7 @@ export const Select = ({ currentQuestion }: SelectProps) => {
} catch (e) { } catch (e) {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
return; return setIsSending(false);
} }
try { try {
@ -69,7 +73,7 @@ export const Select = ({ currentQuestion }: SelectProps) => {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
setIsSending(false);
}} }}
/> />
</Box> </Box>

@ -1,43 +1,102 @@
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import {Box, TextField, Typography, useTheme} from "@mui/material";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { updateAnswer, useQuizViewStore } from "@stores/quizView"; import { updateAnswer, useQuizViewStore } from "@stores/quizView";
import { sendAnswer } from "@api/quizRelase"; import { sendAnswer } from "@api/quizRelase";
import { useQuizData } from "@contexts/QuizDataContext";
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import {ChangeEvent, useEffect, useState} from "react";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import type { QuizQuestionText } from "../../../model/questionTypes/text"; import type { QuizQuestionText } from "../../../model/questionTypes/text";
import { useQuizData } from "@contexts/QuizDataContext";
type TextProps = { type TextProps = {
currentQuestion: QuizQuestionText; currentQuestion: QuizQuestionText;
stepNumber: number | null;
}; };
export const Text = ({ currentQuestion }: TextProps) => { const Orientation = [
{horizontal: true},
{horizontal: false},
{horizontal: true},
{horizontal: true},
{horizontal: false},
{horizontal: true},
{horizontal: true},
{horizontal: true},
{horizontal: true},
{horizontal: true},
{horizontal: true},
{horizontal: false},
{horizontal: true},
{horizontal: false},
{horizontal: true},
{horizontal: true},
{horizontal: true},
{horizontal: true},
{horizontal: false},
{horizontal: false},
{horizontal: true},
{horizontal: true},
{horizontal: true},
{horizontal: true},
]
export const Text = ({ currentQuestion, stepNumber }: TextProps) => {
const theme = useTheme(); const theme = useTheme();
const { settings } = useQuizData();
const spec = settings.cfg.spec
const { quizId } = useQuizData(); const { quizId } = useQuizData();
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const isMobile = useMediaQuery(theme.breakpoints.down(650)); const isMobile = useRootContainerSize() < 650;
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
const [isSending, setIsSending] = useState<boolean>(false);
const inputHC = useDebouncedCallback(async (text) => { const inputHC = useDebouncedCallback(async (text) => {
setIsSending(true);
try { try {
await sendAnswer({ await sendAnswer({
questionId: currentQuestion.id, questionId: currentQuestion.id,
body: text, body: text,
qid: quizId, qid: quizId,
}); });
} catch (e) { } catch (e) {
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
setIsSending(false);
}, 400); }, 400);
return ( useEffect(
() => () => {
inputHC.flush();
},
[inputHC]
);
switch (spec) {
case true:
return <TextSpecial currentQuestion={currentQuestion} answer={answer} inputHC={inputHC} stepNumber={stepNumber}/>;
case undefined:
return <TextNormal currentQuestion={currentQuestion} answer={answer} inputHC={inputHC} />;
default:
return <TextNormal currentQuestion={currentQuestion} answer={answer} inputHC={inputHC} />;
}
};
interface Props {
currentQuestion: QuizQuestionText;
answer: any,
inputHC: (a: string) => void;
stepNumber?: number | null;
}
const TextNormal = ({currentQuestion, answer, inputHC}: Props) => {
const isMobile = useRootContainerSize() < 650;
const theme = useTheme();
return(
<Box> <Box>
<Typography variant="h5" color={theme.palette.text.primary} sx={{wordBreak: "break-word"}}>{currentQuestion.title}</Typography> <Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>{currentQuestion.title}</Typography>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -47,21 +106,20 @@ export const Text = ({ currentQuestion }: TextProps) => {
alignItems: "center" alignItems: "center"
}} }}
> >
<CustomTextField <CustomTextField
placeholder={currentQuestion.content.placeholder} placeholder={currentQuestion.content.placeholder}
//@ts-ignore // @ts-ignore
value={answer || ""} value={answer || ""}
onChange={async ({ target }) => { onChange={async ({ target }) => {
updateAnswer(currentQuestion.id, target.value, 0); updateAnswer(currentQuestion.id, target.value, 0);
inputHC(target.value); inputHC(target.value);
} }}
} sx={{
sx={{ "&:focus-visible": {
"&:focus-visible": { borderColor: theme.palette.primary.main
borderColor: theme.palette.primary.main }
} }}
}} />
/>
{currentQuestion.content.back && currentQuestion.content.back !== " " && ( {currentQuestion.content.back && currentQuestion.content.back !== " " && (
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px", margin: "15px" }}> <Box sx={{ maxWidth: "400px", width: "100%", height: "300px", margin: "15px" }}>
<img <img
@ -74,5 +132,71 @@ export const Text = ({ currentQuestion }: TextProps) => {
)} )}
</Box> </Box>
</Box> </Box>
); )
}; };
const TextSpecial = ({currentQuestion, answer, inputHC, stepNumber}: Props) => {
const theme = useTheme();
const isMobile = useRootContainerSize() < 650;
const isHorizontal = Orientation[Number(stepNumber) -1].horizontal
return(
<Box sx={{display: "flex", flexDirection: isMobile? "column" : undefined, alignItems: isMobile ? "center" : undefined,}}>
<Box
sx={{
display: "flex",
width: "100%",
marginTop: "20px",
flexDirection: "column",
alignItems: "center",
gap: "20px"
}}
>
<Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>{currentQuestion.title}</Typography>
{isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
<Box sx={{margin: "30px", width: "50vw", maxHeight: "550px" }}>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
{
//@ts-ignore
(<TextField
autoFocus={true}
multiline
maxRows={4}
placeholder={currentQuestion.content.placeholder}
//@ts-ignore
value={answer || ""}
onChange={async ({ target }:ChangeEvent<HTMLInputElement>) => {
updateAnswer(currentQuestion.id, target.value, 0);
inputHC(target.value);
}
}
inputProps={{maxLength:400}}
sx={{
width: "100%",
"&:focus-visible": {
borderColor: theme.palette.primary.main
}
}}
/>)
}
</Box>
{!isHorizontal && currentQuestion.content.back && currentQuestion.content.back !== " " && (
<Box sx={{margin: "15px", width: "40vw" }}>
<img
key={currentQuestion.id}
src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
</Box>
)}
</Box>
)
}

@ -38,16 +38,6 @@ type VariantProps = {
currentQuestion: QuizQuestionVariant; currentQuestion: QuizQuestionVariant;
}; };
type VariantItemProps = {
currentQuestion: QuizQuestionVariant;
variant: QuestionVariant;
answer: string | string[] | undefined;
index: number;
own?: boolean;
readySend: boolean;
setReadySend: (a: boolean) => void
};
export const Variant = ({ currentQuestion }: VariantProps) => { export const Variant = ({ currentQuestion }: VariantProps) => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useRootContainerSize() < 650; const isMobile = useRootContainerSize() < 650;
@ -60,7 +50,7 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
(variant) => variant.id === currentQuestion.id (variant) => variant.id === currentQuestion.id
); );
const [readySend, setReadySend] = useState(true) const [isSending, setIsSending] = useState(false);
const Group = currentQuestion.content.multi ? FormGroup : RadioGroup; const Group = currentQuestion.content.multi ? FormGroup : RadioGroup;
@ -109,8 +99,8 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
// @ts-ignore // @ts-ignore
answer={answer} answer={answer}
index={index} index={index}
readySend={readySend} isSending={isSending}
setReadySend={setReadySend} setIsSending={setIsSending}
/> />
))} ))}
{currentQuestion.content.own && ownVariant && ( {currentQuestion.content.own && ownVariant && (
@ -121,8 +111,8 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
// @ts-ignore // @ts-ignore
answer={answer} answer={answer}
index={currentQuestion.content.variants.length + 2} index={currentQuestion.content.variants.length + 2}
readySend={readySend} isSending={isSending}
setReadySend={setReadySend} setIsSending={setIsSending}
/> />
)} )}
</Box> </Box>
@ -148,15 +138,24 @@ const VariantItem = ({
answer, answer,
index, index,
own = false, own = false,
readySend, isSending,
setReadySend setIsSending,
}: VariantItemProps) => { }: {
currentQuestion: QuizQuestionVariant;
variant: QuestionVariant;
answer: string | string[] | undefined;
index: number;
own?: boolean;
isSending: boolean;
setIsSending: (a: boolean) => void;
}) => {
const theme = useTheme(); const theme = useTheme();
const { settings, quizId } = useQuizData(); const { settings, quizId } = useQuizData();
return ( return (
<FormControlLabel <FormControlLabel
key={variant.id} key={variant.id}
disabled={isSending}
sx={{ sx={{
margin: "0", margin: "0",
borderRadius: "12px", borderRadius: "12px",
@ -205,74 +204,75 @@ const VariantItem = ({
label={own ? <TextField label="Другое..." /> : variant.answer} label={own ? <TextField label="Другое..." /> : variant.answer}
onClick={async (event) => { onClick={async (event) => {
event.preventDefault(); event.preventDefault();
if (readySend) { if (isSending) return;
setReadySend(false)
const variantId = currentQuestion.content.variants[index].id;
console.log(answer);
if (currentQuestion.content.multi) { setIsSending(true);
const currentAnswer = typeof answer !== "string" ? answer || [] : []; const variantId = currentQuestion.content.variants[index].id;
console.log(answer);
try { if (currentQuestion.content.multi) {
await sendAnswer({ const currentAnswer = typeof answer !== "string" ? answer || [] : [];
questionId: currentQuestion.id,
body: currentAnswer.includes(variantId)
? currentAnswer?.filter((item) => item !== variantId)
: [...currentAnswer, variantId],
qid: quizId,
});
updateAnswer(
currentQuestion.id,
currentAnswer.includes(variantId)
? currentAnswer?.filter((item) => item !== variantId)
: [...currentAnswer, variantId],
currentQuestion.content.variants[index].points || 0
);
} catch (e) {
console.log(e);
enqueueSnackbar("ответ не был засчитан");
}
return;
}
try { try {
await sendAnswer({ await sendAnswer({
questionId: currentQuestion.id, questionId: currentQuestion.id,
body: currentQuestion.content.variants[index].answer, body: currentAnswer.includes(variantId)
? currentAnswer?.filter((item) => item !== variantId)
: [...currentAnswer, variantId],
qid: quizId, qid: quizId,
}); });
updateAnswer(currentQuestion.id, variantId, updateAnswer(
answer === variantId ? 0 currentQuestion.id,
: currentAnswer.includes(variantId)
currentQuestion.content.variants[index].points || 0 ? currentAnswer?.filter((item) => item !== variantId)
: [...currentAnswer, variantId],
currentQuestion.content.variants[index].points || 0
); );
} catch (e) { } catch (e) {
console.log(e); console.log(e);
enqueueSnackbar("ответ не был засчитан"); enqueueSnackbar("ответ не был засчитан");
} }
if (answer === variantId) { setIsSending(false);
try { return;
await sendAnswer({
questionId: currentQuestion.id,
body: "",
qid: quizId,
});
} catch (e) {
console.log(e);
enqueueSnackbar("ответ не был засчитан");
}
deleteAnswer(currentQuestion.id);
}
setReadySend(true)
} }
try {
await sendAnswer({
questionId: currentQuestion.id,
body: currentQuestion.content.variants[index].answer,
qid: quizId,
});
updateAnswer(currentQuestion.id, variantId,
answer === variantId ? 0
:
currentQuestion.content.variants[index].points || 0
);
} catch (e) {
console.log(e);
enqueueSnackbar("ответ не был засчитан");
}
if (answer === variantId) {
try {
await sendAnswer({
questionId: currentQuestion.id,
body: "",
qid: quizId,
});
} catch (e) {
console.log(e);
enqueueSnackbar("ответ не был засчитан");
}
deleteAnswer(currentQuestion.id);
}
setIsSending(false);
}} }}
/> />
); );

@ -18,6 +18,7 @@ import { quizThemes } from "@utils/themes/Publication/themePublication";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext"; import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg"; import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
import { useState } from "react";
type VarimgProps = { type VarimgProps = {
currentQuestion: QuizQuestionVarImg; currentQuestion: QuizQuestionVarImg;
@ -28,6 +29,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const isMobile = useRootContainerSize() < 650; const isMobile = useRootContainerSize() < 650;
const [isSending, setIsSending] = useState<boolean>(false);
const { answer } = const { answer } =
answers.find( answers.find(
@ -39,7 +41,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
return ( return (
<Box> <Box>
<Typography variant="h5" color={theme.palette.text.primary} sx={{wordBreak: "break-word"}}>{currentQuestion.title}</Typography> <Typography variant="h5" color={theme.palette.text.primary} sx={{ wordBreak: "break-word" }}>{currentQuestion.title}</Typography>
<Box sx={{ <Box sx={{
display: "flex", display: "flex",
marginTop: "20px", marginTop: "20px",
@ -66,6 +68,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
{currentQuestion.content.variants.map((variant, index) => ( {currentQuestion.content.variants.map((variant, index) => (
<FormControlLabel <FormControlLabel
key={variant.id} key={variant.id}
disabled={isSending}
sx={{ sx={{
marginBottom: "15px", marginBottom: "15px",
borderRadius: "5px", borderRadius: "5px",
@ -88,14 +91,13 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
"&::-webkit-scrollbar-thumb": { "&::-webkit-scrollbar-thumb": {
backgroundColor: "#b8babf", backgroundColor: "#b8babf",
} }
} },
}} }}
value={index} value={index}
onClick={async (event) => { onClick={async (event) => {
event.preventDefault(); event.preventDefault();
setIsSending(true);
try { try {
await sendAnswer({ await sendAnswer({
@ -129,6 +131,8 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
} }
deleteAnswer(currentQuestion.id); deleteAnswer(currentQuestion.id);
} }
setIsSending(false);
}} }}
control={ control={
<Radio checkedIcon={<RadioCheck color={theme.palette.primary.main} />} icon={<RadioIcon />} /> <Radio checkedIcon={<RadioCheck color={theme.palette.primary.main} />} icon={<RadioIcon />} />
@ -165,20 +169,20 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
) : ( ) : (
<BlankImage /> <BlankImage />
) )
) : currentQuestion.content.back !== " " ) : currentQuestion.content.back !== " "
&& currentQuestion.content.back !== null && currentQuestion.content.back !== null
&& currentQuestion.content.back.length > 0 && currentQuestion.content.back.length > 0
? ( ? (
<img <img
src={currentQuestion.content.back} src={currentQuestion.content.back}
style={{ width: "100%", height: "100%", objectFit: "cover" }} style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt="" alt=""
/> />
) : (currentQuestion.content.replText !== " " && currentQuestion.content.replText.length > 0) ? currentQuestion.content.replText : variant?.extendedText || isMobile ? ( ) : (currentQuestion.content.replText !== " " && currentQuestion.content.replText.length > 0) ? currentQuestion.content.replText : variant?.extendedText || isMobile ? (
"Выберите вариант ответа ниже" "Выберите вариант ответа ниже"
) : ( ) : (
"Выберите вариант ответа слева" "Выберите вариант ответа слева"
)} )}
</Box> </Box>
{/* )} */} {/* )} */}

@ -20,6 +20,7 @@ type SelectProps = {
colorMain?: string; colorMain?: string;
colorPlaceholder?: string; colorPlaceholder?: string;
placeholder?: string; placeholder?: string;
disabled?: boolean;
}; };
export const Select = ({ export const Select = ({
@ -31,6 +32,7 @@ export const Select = ({
placeholder = "", placeholder = "",
colorMain = "#7E2AEA", colorMain = "#7E2AEA",
colorPlaceholder = "#9A9AAF", colorPlaceholder = "#9A9AAF",
disabled = false,
}: SelectProps) => { }: SelectProps) => {
const [activeItem, setActiveItem] = useState<number>( const [activeItem, setActiveItem] = useState<number>(
empty ? -1 : activeItemIndex empty ? -1 : activeItemIndex
@ -57,6 +59,7 @@ export const Select = ({
return ( return (
<FormControl <FormControl
disabled={disabled}
fullWidth fullWidth
size="small" size="small"
sx={{ width: "100%", height: "48px", ...sx }} sx={{ width: "100%", height: "48px", ...sx }}

@ -48,6 +48,7 @@ export type QuizSettings = {
}; };
export interface QuizConfig { export interface QuizConfig {
spec: undefined | true,
type: QuizType; type: QuizType;
noStartPage: boolean; noStartPage: boolean;
startpageType: QuizStartpageType; startpageType: QuizStartpageType;

5290
pub.js

File diff suppressed because one or more lines are too long

@ -7,7 +7,7 @@ import QuizAnswerer from "../lib/components/QuizAnswerer";
import { ApologyPage } from "../lib/components/ViewPublicationPage/ApologyPage"; import { ApologyPage } from "../lib/components/ViewPublicationPage/ApologyPage";
// const defaultQuizId = "45ef7f9c-784d-4e58-badb-f6b337f08ba0"; // branching // const defaultQuizId = "45ef7f9c-784d-4e58-badb-f6b337f08ba0"; // branching
const defaultQuizId = "cde381db-8ccb-402c-b55f-2c814be9bf25"; //looooong header const defaultQuizId = "26f2e98b-06ac-4e6c-b82e-0793e2768310"; //looooong header
// const defaultQuizId = "ad7f5a87-b833-4f5b-854e-453706ed655c"; // linear // const defaultQuizId = "ad7f5a87-b833-4f5b-854e-453706ed655c"; // linear
export default function App() { export default function App() {