diff --git a/src/model/questionTypes/shared.ts b/src/model/questionTypes/shared.ts index fb1ed933..2d95ac3b 100644 --- a/src/model/questionTypes/shared.ts +++ b/src/model/questionTypes/shared.ts @@ -60,7 +60,6 @@ export interface QuizQuestionBase { type?: QuestionType | null; expanded: boolean; openedModalSettings: boolean; - required: boolean; deleted: boolean; deleteTimeoutId: number; content: { diff --git a/src/pages/Landing/Landing.tsx b/src/pages/Landing/Landing.tsx index c4c94ed8..fc1ea03e 100644 --- a/src/pages/Landing/Landing.tsx +++ b/src/pages/Landing/Landing.tsx @@ -24,7 +24,7 @@ export default function Landing() {
- + {/* */} diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index 096939b8..2dd51e94 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -157,6 +157,12 @@ function CsComponent({ }, [modalQuestionTargetContentId]) const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => { + + + //запрещаем работу родителя-ребенка если это один и тот же вопрос + if (parentNodeContentId === targetNodeContentId) return + + const cy = cyRef?.current const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length //если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа diff --git a/src/pages/Questions/DataOptions/settingData.tsx b/src/pages/Questions/DataOptions/settingData.tsx index ebd53df8..59d9d20b 100644 --- a/src/pages/Questions/DataOptions/settingData.tsx +++ b/src/pages/Questions/DataOptions/settingData.tsx @@ -86,10 +86,10 @@ export default function SettingsData({ question }: SettingsDataProps) { { - updateQuestion(question.id, question => { - question.required = !target.checked; + updateQuestion(question.id, question => { + question.content.required = !target.checked; }); }} /> @@ -109,7 +109,7 @@ export default function SettingsData({ question }: SettingsDataProps) { label={"Внутреннее название вопроса"} checked={question.content.innerNameCheck} handleChange={({ target }) => { - updateQuestion(question.id, question => { + updateQuestion(question.id, question => { question.content.innerNameCheck = target.checked; question.content.innerName = target.checked ? question.content.innerName : ""; }); diff --git a/src/pages/Questions/DropDown/settingDropDown.tsx b/src/pages/Questions/DropDown/settingDropDown.tsx index 895a27f1..1cffb25e 100644 --- a/src/pages/Questions/DropDown/settingDropDown.tsx +++ b/src/pages/Questions/DropDown/settingDropDown.tsx @@ -128,10 +128,10 @@ export default function SettingDropDown({ question }: SettingDropDownProps) { { - updateQuestion(question.id, question => { - question.required = !e.target.checked; + updateQuestion(question.id, question => { + question.content.required = !e.target.checked; }); }} /> @@ -141,7 +141,7 @@ export default function SettingDropDown({ question }: SettingDropDownProps) { label={"Внутреннее название вопроса"} checked={question.content.innerNameCheck} handleChange={({ target }) => { - updateQuestion(question.id, question => { + updateQuestion(question.id, question => { question.content.innerNameCheck = target.checked; question.content.innerName = target.checked ? question.content.innerName : ""; }); diff --git a/src/pages/Questions/Emoji/settingEmoji.tsx b/src/pages/Questions/Emoji/settingEmoji.tsx index ec2753e9..ffc3cb22 100644 --- a/src/pages/Questions/Emoji/settingEmoji.tsx +++ b/src/pages/Questions/Emoji/settingEmoji.tsx @@ -85,11 +85,11 @@ export default function SettingEmoji({ question }: SettingEmojiProps) { updateQuestion(question.id, question => { + checked={!question.content.required} + handleChange={({ target }) => updateQuestion(question.id, question => { if (question.type !== "emoji") return; - question.content.required = !e.target.checked; + question.content.required = !target.checked; })} /> updateQuestion(question.id, question => { + handleChange={({ target }) => updateQuestion(question.id, question => { question.content.innerNameCheck = target.checked; question.content.innerName = target.checked ? question.content.innerName : ""; })} diff --git a/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx b/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx index 4f1bbe2b..76c4993d 100644 --- a/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx +++ b/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx @@ -111,11 +111,11 @@ export default function SettingOptionsAndPict({ question }: SettingOptionsAndPic updateQuestion(question.id, question => { + checked={!question.content.required} + handleChange={({ target }) => updateQuestion(question.id, question => { if (question.type !== "varimg") return; - question.content.required = target.checked; + question.content.required = !target.checked; })} /> @@ -126,7 +126,7 @@ export default function SettingOptionsAndPict({ question }: SettingOptionsAndPic }} label={"Внутреннее название вопроса"} checked={question.content.innerNameCheck} - handleChange={({ target }) => updateQuestion(question.id, question => { + handleChange={({ target }) => updateQuestion(question.id, question => { question.content.innerNameCheck = target.checked; question.content.innerName = ""; })} diff --git a/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx b/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx index f47b281b..40ae6b7d 100644 --- a/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx +++ b/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx @@ -211,11 +211,11 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro updateQuestion(question.id, question => { + checked={!question.content.required} + handleChange={({ target }) => updateQuestion(question.id, question => { if (question.type !== "images") return; - question.content.required = target.checked; + question.content.required = !target.checked; }) } /> diff --git a/src/pages/Questions/OwnTextField/settingTextField.tsx b/src/pages/Questions/OwnTextField/settingTextField.tsx index ad74ac37..4bc77e3c 100644 --- a/src/pages/Questions/OwnTextField/settingTextField.tsx +++ b/src/pages/Questions/OwnTextField/settingTextField.tsx @@ -169,10 +169,10 @@ export default function SettingTextField({ alignItems: isMobile ? "flex-end" : "center", }} label={"Необязательный вопрос"} - checked={!question.required} + checked={!question.content.required} handleChange={(e) => { - updateQuestion(question.id, question => { - question.required = !e.target.checked; + updateQuestion(question.id, question => { + question.content.required = !e.target.checked; }); }} /> @@ -193,7 +193,7 @@ export default function SettingTextField({ label={"Внутреннее название вопроса"} checked={question.content.innerNameCheck} handleChange={({ target }) => { - updateQuestion(question.id, question => { + updateQuestion(question.id, question => { question.content.innerNameCheck = target.checked; question.content.innerName = target.checked ? question.content.innerName diff --git a/src/pages/Questions/RatingOptions/settingRating.tsx b/src/pages/Questions/RatingOptions/settingRating.tsx index e42b0294..4c54921e 100644 --- a/src/pages/Questions/RatingOptions/settingRating.tsx +++ b/src/pages/Questions/RatingOptions/settingRating.tsx @@ -148,12 +148,12 @@ export default function SettingSlider({ question }: SettingSliderProps) { { - updateQuestion(question.id, question => { + updateQuestion(question.id, question => { if (question.type !== "rating") return; - question.required = !e.target.checked; + question.content.required = !e.target.checked; }); }} /> diff --git a/src/pages/Questions/SliderOptions/settingSlider.tsx b/src/pages/Questions/SliderOptions/settingSlider.tsx index c6aef2d1..4677c8fb 100644 --- a/src/pages/Questions/SliderOptions/settingSlider.tsx +++ b/src/pages/Questions/SliderOptions/settingSlider.tsx @@ -78,12 +78,12 @@ export default function SettingSlider({ question }: SettingSliderProps) { { - updateQuestion(question.id, question => { + updateQuestion(question.id, question => { if (question.type !== "number") return; - question.required = !e.target.checked; + question.content.required = !e.target.checked; }); }} /> diff --git a/src/pages/Questions/UploadFile/settingUpload.tsx b/src/pages/Questions/UploadFile/settingUpload.tsx index a7e4cdc2..8968ac4a 100644 --- a/src/pages/Questions/UploadFile/settingUpload.tsx +++ b/src/pages/Questions/UploadFile/settingUpload.tsx @@ -59,10 +59,10 @@ export default function SettingsUpload({ question }: SettingsUploadProps) { mr: isMobile ? "0px" : "16px", }} label={"Необязательный вопрос"} - checked={!question.required} + checked={!question.content.required} handleChange={(e) => { - updateQuestion(question.id, question => { - question.required = !e.target.checked; + updateQuestion(question.id, question => { + question.content.required = !e.target.checked; }); }} /> @@ -82,7 +82,7 @@ export default function SettingsUpload({ question }: SettingsUploadProps) { label={"Внутреннее название вопроса"} checked={question.content.innerNameCheck} handleChange={({ target }) => { - updateQuestion(question.id, question => { + updateQuestion(question.id, question => { question.content.innerNameCheck = target.checked; question.content.innerName = target.checked ? question.content.innerName : ""; }); diff --git a/src/pages/Questions/answerOptions/responseSettings.tsx b/src/pages/Questions/answerOptions/responseSettings.tsx index 8ed5dd2e..8f4f50fe 100644 --- a/src/pages/Questions/answerOptions/responseSettings.tsx +++ b/src/pages/Questions/answerOptions/responseSettings.tsx @@ -1,9 +1,9 @@ import { - Box, - Tooltip, - Typography, - useMediaQuery, - useTheme, + Box, + Tooltip, + Typography, + useMediaQuery, + useTheme, } from "@mui/material"; import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; @@ -12,165 +12,166 @@ import { useDebouncedCallback } from "use-debounce"; import InfoIcon from "../../../assets/icons/InfoIcon"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; - interface Props { - question: QuizQuestionVariant; + question: QuizQuestionVariant; } export default function ResponseSettings({ question }: Props) { - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(900)); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(900)); - const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const updateQuestionInnerName = useDebouncedCallback((value) => { - setQuestionInnerName(question.id, value); - }, 200); + const updateQuestionInnerName = useDebouncedCallback((value) => { + setQuestionInnerName(question.id, value); + }, 200); - return ( - + + - + { + updateQuestion(question.id, (question) => { + if (!("largeCheck" in question.content)) return; + + question.content.largeCheck = target.checked; + }); + }} + /> + { + updateQuestion(question.id, (question) => { + if (!("multi" in question.content)) return; + + question.content.multi = target.checked; + }); + }} + /> + { + updateQuestion(question.id, (question) => { + if (!("own" in question.content)) return; + + question.content.own = target.checked; + }); + }} + /> + + + + Настройки вопросов + + { + updateQuestion(question.id, (question) => { + question.content.required = !target.checked; + }); + }} + /> + + { + updateQuestion(question.id, (question) => { + question.content.innerNameCheck = target.checked; + question.content.innerName = target.checked + ? question.content.innerName + : ""; + }); + }} + /> + {isMobile && ( + - - Настройки ответов - - { - updateQuestion(question.id, question => { - if (!("largeCheck" in question.content)) return; - - question.content.largeCheck = target.checked; - }); - }} - /> - { - updateQuestion(question.id, question => { - if (!("multi" in question.content)) return; - - question.content.multi = target.checked; - }); - }} - /> - { - updateQuestion(question.id, question => { - if (!("own" in question.content)) return; - - question.content.own = target.checked; - }); - }} - /> - - - - Настройки вопросов - - { - updateQuestion(question.id, question => { - question.required = !target.checked; - }); - }} - /> - - { - updateQuestion(question.id, question => { - question.content.innerNameCheck = target.checked; - question.content.innerName = target.checked ? question.content.innerName : ""; - }); - }} - /> - {isMobile && ( - - - - - - )} - - {question.content.innerNameCheck && ( - updateQuestionInnerName(target.value)} - /> - )} - + + + + + )} - ); + {question.content.innerNameCheck && ( + updateQuestionInnerName(target.value)} + /> + )} + + + ); } diff --git a/src/pages/ViewPublicationPage/Footer.tsx b/src/pages/ViewPublicationPage/Footer.tsx index ef121c43..8e473c18 100644 --- a/src/pages/ViewPublicationPage/Footer.tsx +++ b/src/pages/ViewPublicationPage/Footer.tsx @@ -3,17 +3,14 @@ import { Box, Button, useTheme } from "@mui/material"; import { useQuizViewStore } from "@root/quizView"; -import type { - AnyTypedQuizQuestion, - QuizQuestionBase, -} from "../../model/questionTypes/shared"; +import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared"; import { getQuestionByContentId } from "@root/questions/actions"; import { enqueueSnackbar } from "notistack"; type FooterProps = { questions: AnyTypedQuizQuestion[]; setCurrentQuestion: (step: AnyTypedQuizQuestion) => void; - question: QuizQuestionBase; + question: AnyTypedQuizQuestion; }; export const Footer = ({ @@ -21,9 +18,6 @@ export const Footer = ({ questions, question, }: FooterProps) => { - const [disabledQuestionsId, setDisabledQuestionsId] = useState>( - new Set() - ); const [disablePreviousButton, setDisablePreviousButton] = useState(false); const [disableNextButton, setDisableNextButton] = useState(false); @@ -58,11 +52,17 @@ export const Footer = ({ ({ questionId }) => questionId === question.content.id ); - if (question.required && answer?.changed) { + if ("required" in question.content && question.content.required && answer) { setDisableNextButton(false); + + return; } - if (question.required && !answer?.changed) { + if ( + "required" in question.content && + question.content.required && + !answer + ) { setDisableNextButton(true); return; @@ -103,7 +103,7 @@ export const Footer = ({ let readyBeNextQuestion = ""; - question.content.rule.main.forEach(({ next, rules }) => { + (question as QuizQuestionBase).content.rule.main.forEach(({ next, rules }) => { let longerArray = Math.max( rules[0].answers.length, [answer?.answer].length diff --git a/src/pages/ViewPublicationPage/StartPageViewPublication.tsx b/src/pages/ViewPublicationPage/StartPageViewPublication.tsx index 9c6a731e..37beaf7f 100644 --- a/src/pages/ViewPublicationPage/StartPageViewPublication.tsx +++ b/src/pages/ViewPublicationPage/StartPageViewPublication.tsx @@ -1,142 +1,295 @@ import { - Box, - Button, - Typography, - useMediaQuery, - useTheme, + Box, + Button, + ButtonBase, + Link, + Paper, + Typography, + useMediaQuery, + useTheme, } from "@mui/material"; -import { useParams } from "react-router-dom"; +import { useCurrentQuiz } from "@root/quizes/hooks"; +import YoutubeEmbedIframe from "../../ui_kit/StartPagePreview/YoutubeEmbedIframe"; +import { QuizStartpageAlignType, QuizStartpageType } from "@model/quizSettings"; +import { notReachable } from "../../utils/notReachable"; +import { useUADevice } from "../../utils/hooks/useUADevice"; - -import { useQuizes } from "@root/quizes/hooks"; - -type StartPageViewPublicationProps = { - setVisualStartPage: (bool: boolean) => void; - showNextButton:boolean -}; - -export const StartPageViewPublication = ({ - setVisualStartPage, - showNextButton -}: StartPageViewPublicationProps) => { - const quizId = Number(useParams().quizId); - const { quizes } = useQuizes(); +export const StartPageViewPublication = () => { + console.log("startpage") const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(630)); - const quiz = quizes.find(({ backendId }) => quizId === backendId); - const isMediaFileExist = - quiz?.config.startpage.background.desktop || - quiz?.config.startpage.background.video; + const quiz = useCurrentQuiz(); + const { isMobileDevice } = useUADevice(); + console.log(quiz) + + if (!quiz) return null; + + const handleCopyNumber = () => { + navigator.clipboard.writeText(quiz.config.info.phonenumber); + }; + + const background = quiz.config.startpage.background.type === "image" + ? quiz.config.startpage.background.desktop + ? ( + + ) + : null + : quiz.config.startpage.background.type === "video" + ? quiz.config.startpage.background.video + ? ( + + ) + : null + : null; return ( - - - + + - {quiz?.config.startpage.background.mobile && ( - - )} - - {quiz?.config.info.orgname} - - - - - {quiz?.name} - - - {quiz?.config.startpage.description} - - - - - - - - {quiz?.config.info.phonenumber} - - - {quiz?.config.info.law} - - - - {!isTablet && isMediaFileExist && ( - - {quiz?.config.startpage.background.mobile && ( - - )} - {quiz.config.startpage.background.type === "video" && - quiz.config.startpage.background.video && ( - - )} - + + {quiz.config.info.orgname} + + + + + {quiz.config.info.site} + + + } + quizMainBlock={<> + + {quiz.name} + + {quiz.config.startpage.description} + + + + + + + {quiz.config.info.clickable ? ( + isMobileDevice ? ( + + + {quiz.config.info.phonenumber} + + + ) : ( + + + {quiz.config.info.phonenumber} + + + ) + ) : ( + + {quiz.config.info.phonenumber} + + )} + + {quiz.config.info.law} + + + } + backgroundBlock={background} + startpageType={quiz.config.startpageType} + alignType={quiz.config.startpage.position} + /> + ); +} + +function QuizPreviewLayoutByType({ quizHeaderBlock, quizMainBlock, backgroundBlock, startpageType, alignType }: { + quizHeaderBlock: JSX.Element; + quizMainBlock: JSX.Element; + backgroundBlock: JSX.Element | null; + startpageType: QuizStartpageType; + alignType: QuizStartpageAlignType; +}) { + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(630)); + + switch (startpageType) { + case null: + case "standard": { + return ( + + + {quizHeaderBlock} + {quizMainBlock} + + + {backgroundBlock} + + + ); + } + case "expanded": { + return ( + + + {quizHeaderBlock} + {quizMainBlock} + + + {backgroundBlock} + + + ); + } + case "centered": { + return ( + + {quizHeaderBlock} + {backgroundBlock && + + {backgroundBlock} + + } + {quizMainBlock} + + ); + } + default: notReachable(startpageType); + } +} + +const startpageAlignTypeToJustifyContent: Record = { + left: "start", + center: "center", + right: "end", }; diff --git a/src/pages/ViewPublicationPage/index.tsx b/src/pages/ViewPublicationPage/index.tsx index 38911b8f..66a521cc 100644 --- a/src/pages/ViewPublicationPage/index.tsx +++ b/src/pages/ViewPublicationPage/index.tsx @@ -5,11 +5,29 @@ import { StartPageViewPublication } from "./StartPageViewPublication"; import { Question } from "./Question"; import { useQuestions } from "@root/questions/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks"; +import useSWR from "swr"; +import { quizApi } from "@api/quiz"; +import { setQuizes } from "@root/quizes/actions"; +import { isAxiosError } from "axios"; +import { devlog } from "@frontend/kitui"; import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared"; +import { enqueueSnackbar } from "notistack"; export const ViewPage = () => { + useSWR("quizes", () => quizApi.getList(), { + onSuccess: setQuizes, + onError: error => { + const message = isAxiosError(error) ? (error.response?.data ?? "") : ""; + + devlog("Error getting quiz list", error); + enqueueSnackbar(`Не удалось получить квизы. ${message}`); + }, +}); + + const quiz = useCurrentQuiz(); + console.log(quiz) const { questions } = useQuestions(); const [visualStartPage, setVisualStartPage] = useState( !quiz?.config.noStartPage @@ -30,10 +48,7 @@ export const ViewPage = () => { return ( {visualStartPage ? ( - + ) : ( )} diff --git a/src/pages/ViewPublicationPage/questions/Number.tsx b/src/pages/ViewPublicationPage/questions/Number.tsx index c5a39293..590da466 100644 --- a/src/pages/ViewPublicationPage/questions/Number.tsx +++ b/src/pages/ViewPublicationPage/questions/Number.tsx @@ -57,8 +57,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { currentQuestion.content.id, currentQuestion.content.chooseRange ? `${currentQuestion.content.start}—${max}` - : String(currentQuestion.content.start), - false + : String(currentQuestion.content.start) ); setMinRange(String(currentQuestion.content.start)); diff --git a/src/pages/ViewPublicationPage/questions/Rating.tsx b/src/pages/ViewPublicationPage/questions/Rating.tsx index 3ad5c674..b8565db8 100644 --- a/src/pages/ViewPublicationPage/questions/Rating.tsx +++ b/src/pages/ViewPublicationPage/questions/Rating.tsx @@ -7,6 +7,12 @@ import { import { useQuizViewStore, updateAnswer } from "@root/quizView"; +import TropfyIcon from "@icons/questionsPage/tropfyIcon"; +import FlagIcon from "@icons/questionsPage/FlagIcon"; +import HeartIcon from "@icons/questionsPage/heartIcon"; +import LikeIcon from "@icons/questionsPage/likeIcon"; +import LightbulbIcon from "@icons/questionsPage/lightbulbIcon"; +import HashtagIcon from "@icons/questionsPage/hashtagIcon"; import StarIconMini from "@icons/questionsPage/StarIconMini"; import type { QuizQuestionRating } from "../../../model/questionTypes/rating"; @@ -15,6 +21,37 @@ type RatingProps = { currentQuestion: QuizQuestionRating; }; +const buttonRatingForm = [ + { + name: "star", + icon: (color: string) => , + }, + { + name: "trophie", + icon: (color: string) => , + }, + { + name: "flag", + icon: (color: string) => , + }, + { + name: "heart", + icon: (color: string) => , + }, + { + name: "like", + icon: (color: string) => , + }, + { + name: "bubble", + icon: (color: string) => , + }, + { + name: "hashtag", + icon: (color: string) => , + }, +]; + export const Rating = ({ currentQuestion }: RatingProps) => { const { answers } = useQuizViewStore(); const theme = useTheme(); @@ -22,54 +59,44 @@ export const Rating = ({ currentQuestion }: RatingProps) => { answers.find( ({ questionId }) => questionId === currentQuestion.content.id ) ?? {}; + const form = buttonRatingForm.find( + ({ name }) => name === currentQuestion.content.form + ); return ( {currentQuestion.title} - - updateAnswer(currentQuestion.content.id, String(value)) - } - sx={{ height: "50px", gap: "15px" }} - max={currentQuestion.content.steps} - icon={ - - } - emptyIcon={ - - } - /> + + {currentQuestion.content.ratingNegativeDescription} + - - {currentQuestion.content.ratingNegativeDescription} - - - {currentQuestion.content.ratingPositiveDescription} - + + updateAnswer(currentQuestion.content.id, String(value)) + } + sx={{ height: "50px", gap: "15px" }} + max={currentQuestion.content.steps} + icon={form?.icon(theme.palette.brightPurple.main)} + emptyIcon={form?.icon(theme.palette.grey2.main)} + /> + + {currentQuestion.content.ratingPositiveDescription} + ); diff --git a/src/pages/ViewPublicationPage/questions/Variant.tsx b/src/pages/ViewPublicationPage/questions/Variant.tsx index e8486b5a..12b3945b 100644 --- a/src/pages/ViewPublicationPage/questions/Variant.tsx +++ b/src/pages/ViewPublicationPage/questions/Variant.tsx @@ -1,3 +1,4 @@ +import { useEffect } from "react"; import { Box, Typography, @@ -6,32 +7,56 @@ import { FormControlLabel, Radio, Checkbox, + TextField, useTheme, } from "@mui/material"; -import { useQuizViewStore, updateAnswer, deleteAnswer } from "@root/quizView"; +import { + useQuizViewStore, + updateAnswer, + deleteAnswer, + updateOwnVariant, + deleteOwnVariant, +} from "@root/quizView"; import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; import { CheckboxIcon } from "@icons/Checkbox"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; +import type { QuestionVariant } from "../../../model/questionTypes/shared"; type VariantProps = { stepNumber: number; currentQuestion: QuizQuestionVariant; }; +type VariantItemProps = { + currentQuestion: QuizQuestionVariant; + variant: QuestionVariant; + answer: string | string[] | undefined; + index: number; + own?: boolean; +}; + export const Variant = ({ currentQuestion }: VariantProps) => { - const { answers } = useQuizViewStore(); - const theme = useTheme(); + const { answers, ownVariants } = useQuizViewStore(); const { answer } = answers.find( ({ questionId }) => questionId === currentQuestion.content.id ) ?? {}; + const ownVariant = ownVariants.find( + (variant) => variant.contentId === currentQuestion.content.id + ); const Group = currentQuestion.content.multi ? FormGroup : RadioGroup; + useEffect(() => { + if (!ownVariant) { + updateOwnVariant(currentQuestion.content.id, ""); + } + }, []); + return ( {currentQuestion.title} @@ -60,58 +85,23 @@ export const Variant = ({ currentQuestion }: VariantProps) => { }} > {currentQuestion.content.variants.map((variant, index) => ( - } - icon={} - /> - ) : ( - } icon={} /> - ) - } - label={variant.answer} - onClick={(event) => { - event.preventDefault(); - const variantId = currentQuestion.content.variants[index].id; - - if (currentQuestion.content.multi) { - const currentAnswer = - typeof answer !== "string" ? answer || [] : []; - - updateAnswer( - currentQuestion.content.id, - currentAnswer?.includes(variantId) - ? currentAnswer?.filter((item) => item !== variantId) - : [...currentAnswer, variantId] - ); - - return; - } - - updateAnswer(currentQuestion.content.id, variantId); - - if (answer === variantId) { - deleteAnswer(currentQuestion.content.id); - } - }} + currentQuestion={currentQuestion} + variant={variant} + answer={answer} + index={index} /> ))} + {currentQuestion.content.own && ownVariant && ( + + )} {currentQuestion.content.back && ( @@ -127,3 +117,66 @@ export const Variant = ({ currentQuestion }: VariantProps) => { ); }; + +const VariantItem = ({ + currentQuestion, + variant, + answer, + index, + own = false, +}: VariantItemProps) => { + const theme = useTheme(); + + return ( + } + icon={} + /> + ) : ( + } icon={} /> + ) + } + label={own ? : variant.answer} + onClick={(event) => { + event.preventDefault(); + const variantId = currentQuestion.content.variants[index].id; + + if (currentQuestion.content.multi) { + const currentAnswer = typeof answer !== "string" ? answer || [] : []; + + updateAnswer( + currentQuestion.content.id, + currentAnswer.includes(variantId) + ? currentAnswer?.filter((item) => item !== variantId) + : [...currentAnswer, variantId] + ); + + return; + } + + updateAnswer(currentQuestion.content.id, variantId); + + if (answer === variantId) { + deleteAnswer(currentQuestion.content.id); + } + }} + /> + ); +}; diff --git a/src/pages/startPage/StartPageSettings.tsx b/src/pages/startPage/StartPageSettings.tsx index cf279d3c..fa64b178 100755 --- a/src/pages/startPage/StartPageSettings.tsx +++ b/src/pages/startPage/StartPageSettings.tsx @@ -331,74 +331,7 @@ export default function StartPageSettings() { - - } - checkedIcon={} - /> - } - label="мобильная версия" - sx={{ - color: theme.palette.brightPurple.main, - textDecorationLine: "underline", - textDecorationColor: theme.palette.brightPurple.main, - ml: "-9px", - userSelect: "none", - "& .css-14o5ia4-MuiTypography-root": { - fontSize: "16px" - } - }} - onClick={() => { - MobileVersionHC(!mobileVersion); - }} - /> - {mobileVersion ? ( - - - Изображение для мобильной версии - - { - uploadQuizImage(quiz.id, file, (quiz, url) => { - quiz.config.startpage.background.mobile = url; - quiz.config.startpage.background.originalMobile = url; - }); - }} - onImageSaveClick={file => { - uploadQuizImage(quiz.id, file, (quiz, url) => { - quiz.config.startpage.background.mobile = url; - }); - }} - onDeleteClick={() => { - updateQuiz(quiz.id, quiz => { - quiz.config.startpage.background.mobile = null; - }); - }} - /> - - ) : ( - <> - )} - + - - Настройки видео - - updateQuiz(quiz.id, quiz => { - quiz.config.startpage.background.cycle = e.target.checked; - })} - /> - - Изображение для мобильной версии - - { - uploadQuizImage(quiz.id, file, (quiz, url) => { - quiz.config.startpage.background.mobile = url; - quiz.config.startpage.background.originalMobile = url; - }); - }} - onImageSaveClick={file => { - uploadQuizImage(quiz.id, file, (quiz, url) => { - quiz.config.startpage.background.mobile = url; - }); - }} - onDeleteClick={() => { - updateQuiz(quiz.id, quiz => { - quiz.config.startpage.background.mobile = null; - }); - }} - /> + - - Расположение элементов - {designType !== "centered" && - - updateQuiz(quiz.id, quiz => { - quiz.config.startpage.position = "left"; - })} - isActive={quiz.config.startpage.position === "left"} - Icon={AlignLeftIcon} - /> - updateQuiz(quiz.id, quiz => { - quiz.config.startpage.position = "center"; - })} - isActive={quiz.config.startpage.position === "center"} - Icon={AlignCenterIcon} - sx={{ display: designType === "standard" ? "none" : "flex" }} - /> - updateQuiz(quiz.id, quiz => { - quiz.config.startpage.position = "right"; - })} - isActive={quiz.config.startpage.position === "right"} - Icon={AlignRightIcon} - /> - + <> + + Расположение элементов + + + updateQuiz(quiz.id, quiz => { + quiz.config.startpage.position = "left"; + })} + isActive={quiz.config.startpage.position === "left"} + Icon={AlignLeftIcon} + /> + updateQuiz(quiz.id, quiz => { + quiz.config.startpage.position = "center"; + })} + isActive={quiz.config.startpage.position === "center"} + Icon={AlignCenterIcon} + sx={{ display: designType === "standard" ? "none" : "flex" }} + /> + updateQuiz(quiz.id, quiz => { + quiz.config.startpage.position = "right"; + })} + isActive={quiz.config.startpage.position === "right"} + Icon={AlignRightIcon} + /> + + + } {(isTablet || !isSmallMonitor) && ( <> diff --git a/src/stores/questions/actions.ts b/src/stores/questions/actions.ts index 4b287057..b853445b 100644 --- a/src/stores/questions/actions.ts +++ b/src/stores/questions/actions.ts @@ -176,9 +176,9 @@ const REQUEST_DEBOUNCE = 200; const requestQueue = new RequestQueue(); let requestTimeoutId: ReturnType; -export const updateQuestion = ( +export const updateQuestion = ( questionId: string, - updateFn: (question: AnyTypedQuizQuestion) => void, + updateFn: (question: T) => void, skipQueue = false, ) => { setProducedState(state => { @@ -186,7 +186,7 @@ export const updateQuestion = ( if (!question) return; if (question.type === null) throw new Error("Cannot update untyped question, use 'updateUntypedQuestion' instead"); - updateFn(question); + updateFn(question as T); }, { type: "updateQuestion", questionId, diff --git a/src/stores/quizView.ts b/src/stores/quizView.ts index ae68a8fe..523579a0 100644 --- a/src/stores/quizView.ts +++ b/src/stores/quizView.ts @@ -1,21 +1,28 @@ import { create } from "zustand"; import { devtools } from "zustand/middleware"; +import type { QuestionVariant } from "../model/questionTypes/shared"; + type Answer = { questionId: string; answer: string | string[]; - // Поле отвечающее за первое изменение ответа, нужно для галочки "Необязательный вопрос" - changed: boolean; +}; + +type OwnVariant = { + contentId: string; + variant: QuestionVariant; }; interface QuizViewStore { answers: Answer[]; + ownVariants: OwnVariant[]; } export const useQuizViewStore = create()( devtools( (set, get) => ({ answers: [], + ownVariants: [], }), { name: "quizView", @@ -23,20 +30,16 @@ export const useQuizViewStore = create()( ) ); -export const updateAnswer = ( - questionId: string, - answer: string | string[], - changed = true -) => { +export const updateAnswer = (questionId: string, answer: string | string[]) => { const answers = [...useQuizViewStore.getState().answers]; const answerIndex = answers.findIndex( (answer) => questionId === answer.questionId ); if (answerIndex < 0) { - answers.push({ questionId, answer, changed }); + answers.push({ questionId, answer }); } else { - answers[answerIndex] = { questionId, answer, changed }; + answers[answerIndex] = { questionId, answer }; } useQuizViewStore.setState({ answers }); @@ -50,3 +53,44 @@ export const deleteAnswer = (questionId: string) => { useQuizViewStore.setState({ answers: filteredItems }); }; + +export const updateOwnVariant = (contentId: string, answer: string) => { + const ownVariants = [...useQuizViewStore.getState().ownVariants]; + const ownVariantIndex = ownVariants.findIndex( + (variant) => variant.contentId === contentId + ); + + if (ownVariantIndex < 0) { + ownVariants.push({ + contentId, + variant: { + id: getRandom(), + answer, + extendedText: "", + hints: "", + originalImageUrl: "", + }, + }); + } else { + ownVariants[ownVariantIndex].variant.answer = answer; + } + + useQuizViewStore.setState({ ownVariants }); +}; + +export const deleteOwnVariant = (contentId: string) => { + const ownVariants = [...useQuizViewStore.getState().ownVariants]; + + const filteredOwnVariants = ownVariants.filter( + (variant) => variant.contentId !== contentId + ); + + useQuizViewStore.setState({ ownVariants: filteredOwnVariants }); +}; + +function getRandom() { + const min = Math.ceil(1000000); + const max = Math.floor(10000000); + + return String(Math.floor(Math.random() * (max - min)) + min); +} diff --git a/src/ui_kit/StartPagePreview/YoutubeEmbedIframe.tsx b/src/ui_kit/StartPagePreview/YoutubeEmbedIframe.tsx index 5e83bf19..677690e5 100644 --- a/src/ui_kit/StartPagePreview/YoutubeEmbedIframe.tsx +++ b/src/ui_kit/StartPagePreview/YoutubeEmbedIframe.tsx @@ -1,12 +1,13 @@ -import { Box } from "@mui/material"; +import { Box, SxProps } from "@mui/material"; interface Props { videoUrl: string; + containerSX?: SxProps; } -export default function YoutubeEmbedIframe({ videoUrl }: Props) { +export default function YoutubeEmbedIframe({ videoUrl, containerSX }: Props) { const extractYoutubeVideoId = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/gi; const videoId = extractYoutubeVideoId.exec(videoUrl)?.[1]; if (!videoId) return null; @@ -21,7 +22,8 @@ export default function YoutubeEmbedIframe({ videoUrl }: Props) { "& iframe": { width: "100%", height: "100%", - } + }, + ...containerSX }}>