diff --git a/ CHANGELOG.md b/ CHANGELOG.md new file mode 100644 index 0000000..dee49d8 --- /dev/null +++ b/ CHANGELOG.md @@ -0,0 +1,3 @@ +1.0.2 Страничка результатов способна показать историю и правильность ответов для балловго квиза +1.0.1 Отображение для баллового квиза на страничке результатов списка +1.0.0 Добавлены фичи "мультиответ", "перенос строки в своём ответе", "свой ответ", "плейсхолдер своего ответа" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2355dbd..b4700dc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,9 +3,13 @@ include: file: "/templates/docker/build-template.gitlab-ci.yml" - project: "devops/pena-continuous-integration" file: "/templates/docker/deploy-template.gitlab-ci.yml" + - project: "devops/pena-continuous-integration" + file: "/templates/docker/service-discovery.gitlab-ci.yml" stages: - build - deploy + - service-discovery + build-app: tags: - frontbuild @@ -30,3 +34,6 @@ deploy-to-prod: tags: - front - prod + +service-discovery: + extends: .sd_artefacts_template diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index 202dea9..e576e61 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -3,6 +3,9 @@ services: respondent: container_name: respondent restart: unless-stopped + labels: + com.pena.domains: s.hbpn.link + com.pena.front_headers: "Access-Control-Allow-Origin $$http_origin always" image: $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID hostname: respondent tty: true diff --git a/lib/components/QuizAnswerer.tsx b/lib/components/QuizAnswerer.tsx index 83e20c3..653f511 100644 --- a/lib/components/QuizAnswerer.tsx +++ b/lib/components/QuizAnswerer.tsx @@ -20,9 +20,13 @@ import { ApologyPage } from "./ViewPublicationPage/ApologyPage"; import ViewPublicationPage from "./ViewPublicationPage/ViewPublicationPage"; import { HelmetProvider } from "react-helmet-async"; +import "moment/dist/locale/ru"; moment.locale("ru"); const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText; +console.log(localeText); +console.log(moment); + type Props = { quizSettings?: QuizSettings; quizId: string; diff --git a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx index 14d83c6..c57d4c9 100644 --- a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx @@ -32,8 +32,6 @@ type Props = { }; //Костыль для особого квиза. Для него не нужно показывать email адрес const isDisableEmail = window.location.pathname.includes("/377c7570-1bee-4320-ac1e-d731b6223ce8"); -console.log("isDisableEmail"); -console.log(isDisableEmail); export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { const theme = useTheme(); @@ -120,8 +118,6 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { async function handleShowResultsClick() { const FC = settings.cfg.formContact.fields; - console.log("некорректная"); - console.log(!isDisableEmail && FC["email"].used !== EMAIL_REGEXP.test(email)); if (!isDisableEmail && FC["email"].used !== EMAIL_REGEXP.test(email)) { return enqueueSnackbar("введена некорректная почта"); } diff --git a/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx b/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx index f06e218..19f9437 100644 --- a/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/Inputs/Inputs.tsx @@ -40,9 +40,6 @@ export const Inputs = ({ const { settings } = useQuizSettings(); const FC = settings.cfg.formContact.fields; - console.log("crutch.disableEmail"); - console.log(crutch.disableEmail); - if (!FC) return null; const Name = ( { Вопрос {stepNumber} из {questionsAmount} - + )} {prevButton} diff --git a/lib/components/ViewPublicationPage/ResultForm.tsx b/lib/components/ViewPublicationPage/ResultForm.tsx index 6d7da92..899b06c 100644 --- a/lib/components/ViewPublicationPage/ResultForm.tsx +++ b/lib/components/ViewPublicationPage/ResultForm.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; import { Box, Button, Link, Typography, useTheme } from "@mui/material"; import { useQuizViewStore } from "@/stores/quizView"; @@ -76,6 +76,16 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => { })(); }, []); + const choiceImgUrlQuestion = useMemo(() => { + if ( + resultQuestion.content.editedUrlImagesList !== undefined && + resultQuestion.content.editedUrlImagesList !== null + ) { + return resultQuestion.content.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"]; + } else { + return resultQuestion.content.back; + } + }, [resultQuestion]); return ( { }} /> )} - {resultQuestion?.content.useImage && resultQuestion.content.back && ( + {resultQuestion?.content.useImage && choiceImgUrlQuestion && ( { > resultImage state.answers); + const ownVariants = useQuizViewStore((state) => state.ownVariants); let currentQuizStep = useQuizViewStore((state) => state.currentQuizStep); const { currentQuestion, @@ -99,7 +100,7 @@ export default function ViewPublicationPage() { if (preview) return; - sendQuestionAnswer(quizId, currentQuestion, currentAnswer)?.catch((e) => { + sendQuestionAnswer(quizId, currentQuestion, currentAnswer, ownVariants)?.catch((e) => { enqueueSnackbar("Ошибка при отправке ответа"); console.error("Error sending answer", e); }); diff --git a/lib/components/ViewPublicationPage/questions/Date/DatePicker.tsx b/lib/components/ViewPublicationPage/questions/Date/DatePicker.tsx new file mode 100644 index 0000000..7a42ae0 --- /dev/null +++ b/lib/components/ViewPublicationPage/questions/Date/DatePicker.tsx @@ -0,0 +1,77 @@ +import { useQuizViewStore } from "@/stores/quizView"; +import { useQuizSettings } from "@contexts/QuizDataContext"; +import CalendarIcon from "@icons/CalendarIcon"; +import type { QuizQuestionDate } from "@model/questionTypes/date"; +import { Box, Typography, useTheme } from "@mui/material"; +import { DatePicker } from "@mui/x-date-pickers"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; +import type { Moment } from "moment"; +import moment from "moment"; + +type DateProps = { + currentQuestion: QuizQuestionDate; +}; + +export default ({ currentQuestion }: DateProps) => { + const { settings } = useQuizSettings(); + const answers = useQuizViewStore((state) => state.answers); + const { updateAnswer } = useQuizViewStore((state) => state); + const theme = useTheme(); + const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string; + const currentAnswer = moment(answer) || moment(); + + const onDateChange = async (date: Moment | null) => { + if (!date) return; + + updateAnswer(currentQuestion.id, date, 0); + }; + + return ( + + ( + + ), + }} + value={currentAnswer} + onChange={onDateChange} + slotProps={{ + openPickerButton: { sx: { p: 0 }, "data-cy": "open-datepicker" }, + layout: { + sx: { backgroundColor: theme.palette.background.default }, + }, + }} + sx={{ + "& .MuiInputBase-root": { + backgroundColor: settings.cfg.design + ? quizThemes[settings.cfg.theme].isLight + ? "#F2F3F7" + : "rgba(154,154,175, 0.2)" + : quizThemes[settings.cfg.theme].isLight + ? "white" + : theme.palette.background.default, + borderRadius: "10px", + maxWidth: "250px", + pr: "30px", + "& input": { py: "11px", pl: "20px", lineHeight: "19px" }, + "& fieldset": { borderColor: "#9A9AAF" }, + }, + }} + /> + + ); +}; diff --git a/lib/components/ViewPublicationPage/questions/Date/DateRange.tsx b/lib/components/ViewPublicationPage/questions/Date/DateRange.tsx new file mode 100644 index 0000000..a419a84 --- /dev/null +++ b/lib/components/ViewPublicationPage/questions/Date/DateRange.tsx @@ -0,0 +1,100 @@ +import { useQuizSettings } from "@/contexts/QuizDataContext"; +import { useQuizViewStore } from "@/stores/quizView"; +import type { QuizQuestionDate } from "@model/questionTypes/date"; +import { DateCalendar } from "@mui/x-date-pickers"; +import { quizThemes } from "@utils/themes/Publication/themePublication"; +import type { Moment } from "moment"; +import moment from "moment"; +import { Box, Paper, TextField, useTheme } from "@mui/material"; +import { useRootContainerSize } from "@/contexts/RootContainerWidthContext"; + +type DateProps = { + currentQuestion: QuizQuestionDate; +}; +console.log(moment.locale()); +export default ({ currentQuestion }: DateProps) => { + const theme = useTheme(); + const isMobile = useRootContainerSize() < 690; + const { settings } = useQuizSettings(); + const { updateAnswer } = useQuizViewStore((state) => state); + + const answers = useQuizViewStore((state) => state.answers); + const answer = (answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string) || ["0", "0"]; + + const currentFrom = Number(answer[0]) ? moment(Number(answer[0])) : moment().utc(); + const currentTo = Number(answer[1]) ? moment(Number(answer[1])) : moment().utc(); + + const onDateChange = async (date: Moment | null, index: number) => { + if (!date) return; + let newAnswer = [...answer]; + newAnswer[index] = (moment(date).unix() * 1000).toString(); + + updateAnswer(currentQuestion.id, newAnswer, 0); + }; + + return ( + + + От + onDateChange(data, 0)} + /> + + + До + onDateChange(data, 1)} + /> + + + ); +}; diff --git a/lib/components/ViewPublicationPage/questions/Date/index.tsx b/lib/components/ViewPublicationPage/questions/Date/index.tsx index 1e21a04..412faf6 100644 --- a/lib/components/ViewPublicationPage/questions/Date/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Date/index.tsx @@ -1,30 +1,14 @@ -import { useQuizViewStore } from "@/stores/quizView"; -import { useQuizSettings } from "@contexts/QuizDataContext"; -import CalendarIcon from "@icons/CalendarIcon"; import type { QuizQuestionDate } from "@model/questionTypes/date"; +import DateRange from "./DateRange"; +import DatePicker from "./DatePicker"; import { Box, Typography, useTheme } from "@mui/material"; -import { DatePicker } from "@mui/x-date-pickers"; -import { quizThemes } from "@utils/themes/Publication/themePublication"; -import type { Moment } from "moment"; -import moment from "moment"; type DateProps = { currentQuestion: QuizQuestionDate; }; export const Date = ({ currentQuestion }: DateProps) => { - const { settings } = useQuizSettings(); - const answers = useQuizViewStore((state) => state.answers); - const { updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); - const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string; - const currentAnswer = moment(answer) || moment(); - - const onDateChange = async (date: Moment | null) => { - if (!date) return; - - updateAnswer(currentQuestion.id, date, 0); - }; return ( @@ -35,52 +19,11 @@ export const Date = ({ currentQuestion }: DateProps) => { > {currentQuestion.title} - - ( - - ), - }} - value={currentAnswer} - onChange={onDateChange} - slotProps={{ - openPickerButton: { sx: { p: 0 }, "data-cy": "open-datepicker" }, - layout: { - sx: { backgroundColor: theme.palette.background.default }, - }, - }} - sx={{ - "& .MuiInputBase-root": { - backgroundColor: settings.cfg.design - ? quizThemes[settings.cfg.theme].isLight - ? "#F2F3F7" - : "rgba(154,154,175, 0.2)" - : quizThemes[settings.cfg.theme].isLight - ? "white" - : theme.palette.background.default, - borderRadius: "10px", - maxWidth: "250px", - pr: "30px", - "& input": { py: "11px", pl: "20px", lineHeight: "19px" }, - "& fieldset": { borderColor: "#9A9AAF" }, - }, - }} - /> - + {currentQuestion.content.isRange ? ( + + ) : ( + + )} ); }; diff --git a/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx b/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx index b32079b..20645db 100644 --- a/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Emoji/EmojiVariant.tsx @@ -1,6 +1,16 @@ import type { QuestionVariant } from "@/model/questionTypes/shared"; import { useQuizSettings } from "@contexts/QuizDataContext"; -import { Box, FormControl, FormControlLabel, Radio, Typography, useTheme } from "@mui/material"; +import { + Box, + Checkbox, + FormControl, + FormControlLabel, + Input, + Radio, + TextareaAutosize, + Typography, + useTheme, +} from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; @@ -14,18 +24,111 @@ type EmojiVariantProps = { questionId: string; variant: QuestionVariant; index: number; + isMulti: boolean; + own: boolean; + questionLargeCheck: boolean; + ownPlaceholder: string; + answer: string | string[] | undefined; }; -export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps) => { +interface OwnInputProps { + questionId: string; + variant: QuestionVariant; + largeCheck: boolean; + ownPlaceholder: string; +} +const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => { + const theme = useTheme(); + const ownVariants = useQuizViewStore((state) => state.ownVariants); + const { updateOwnVariant } = useQuizViewStore((state) => state); + + const ownAnswer = ownVariants[ownVariants.findIndex((v) => v.id === variant.id)]?.variant.answer || ""; + + return largeCheck ? ( + + ) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => { + updateOwnVariant(variant.id, e.target.value); + }} + /> + + ) : ( + ) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => { + updateOwnVariant(variant.id, e.target.value); + }} + /> + ); +}; + +export const EmojiVariant = ({ + answer, + variant, + index, + questionId, + isMulti, + own, + questionLargeCheck, + ownPlaceholder, +}: EmojiVariantProps) => { const { settings } = useQuizSettings(); const answers = useQuizViewStore((state) => state.answers); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); - const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {}; const onVariantClick = async (event: MouseEvent) => { event.preventDefault(); + const variantId = variant.id; + if (isMulti) { + const currentAnswer = typeof answer !== "string" ? answer || [] : []; + + return updateAnswer( + questionId, + currentAnswer.includes(variantId) + ? currentAnswer?.filter((item) => item !== variantId) + : [...currentAnswer, variantId], + variant.points || 0 + ); + } + updateAnswer(questionId, variant.id, variant.points || 0); if (answer === variant.id) { @@ -39,7 +142,7 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps) sx={{ borderRadius: "12px", border: `1px solid`, - borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF", + borderColor: answer?.includes(variant.id) ? theme.palette.primary.main : "#9A9AAF", overflow: "hidden", maxWidth: "317px", width: "100%", @@ -74,6 +177,17 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps) {variant.extendedText && {variant.extendedText}} + {own && ( + + Введите свой ответ + + )} } - icon={} - sx={{ position: "absolute", top: "-162px", right: "12px" }} - /> + isMulti ? ( + } + icon={} + sx={{ position: "absolute", top: "-162px", right: "12px" }} + /> + ) : ( + } + icon={} + sx={{ position: "absolute", top: "-162px", right: "12px" }} + /> + ) } label={ - - {variant.answer} - + own ? ( + + ) : ( + + {variant.answer} + + ) } /> diff --git a/lib/components/ViewPublicationPage/questions/Emoji/index.tsx b/lib/components/ViewPublicationPage/questions/Emoji/index.tsx index e179290..fc51532 100644 --- a/lib/components/ViewPublicationPage/questions/Emoji/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Emoji/index.tsx @@ -3,6 +3,7 @@ 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 moment from "moment"; polyfillCountryFlagEmojis(); @@ -16,6 +17,8 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => { const theme = useTheme(); const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); + return ( { }} > - {currentQuestion.content.variants.map((variant, index) => ( - - ))} + {currentQuestion.content.variants + .filter((v) => { + if (!v.isOwn) return true; + return v.isOwn && currentQuestion.content.own; + }) + .map((variant, index) => ( + + ))} diff --git a/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx b/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx index a14b5f5..ca6b63a 100644 --- a/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx +++ b/lib/components/ViewPublicationPage/questions/File/UploadedFile.tsx @@ -61,7 +61,10 @@ export const UploadedFile = ({ currentQuestion, setIsSending }: UploadedFileProp > {answer?.split("|")[0]} - + diff --git a/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx b/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx index 1077def..d552067 100644 --- a/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Images/ImageVariant.tsx @@ -1,42 +1,147 @@ -import type { QuestionVariant } from "@/model/questionTypes/shared"; +import { CheckboxIcon } from "@/assets/icons/Checkbox"; +import type { QuestionVariant, QuestionVariantWithEditedImages } from "@/model/questionTypes/shared"; import { useQuizSettings } from "@contexts/QuizDataContext"; -import { Box, FormControlLabel, Radio, useTheme } from "@mui/material"; +import { Box, Checkbox, FormControlLabel, Input, Radio, TextareaAutosize, Typography, useTheme } from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; import { quizThemes } from "@utils/themes/Publication/themePublication"; -import type { MouseEvent } from "react"; +import { useMemo, type MouseEvent } from "react"; +import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; type ImagesProps = { questionId: string; - variant: QuestionVariant; + variant: QuestionVariantWithEditedImages; index: number; + answer: string | string[] | undefined; + isMulti: boolean; + own: boolean; + questionLargeCheck: boolean; + ownPlaceholder: string; }; -export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => { +interface OwnInputProps { + questionId: string; + variant: QuestionVariant; + largeCheck: boolean; + ownPlaceholder: string; +} +const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => { + const theme = useTheme(); + const ownVariants = useQuizViewStore((state) => state.ownVariants); + const { updateOwnVariant } = useQuizViewStore((state) => state); + + const ownAnswer = ownVariants[ownVariants.findIndex((v) => v.id === variant.id)]?.variant.answer || ""; + + return largeCheck ? ( + + ) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => { + updateOwnVariant(variant.id, e.target.value); + }} + /> + + ) : ( + ) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => { + updateOwnVariant(variant.id, e.target.value); + }} + /> + ); +}; + +export const ImageVariant = ({ + questionId, + answer, + isMulti, + variant, + index, + own, + questionLargeCheck, + ownPlaceholder, +}: ImagesProps) => { const { settings } = useQuizSettings(); - const answers = useQuizViewStore((state) => state.answers); const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state); const theme = useTheme(); - const answer = answers.find((answer) => answer.questionId === questionId)?.answer; + const answers = useQuizViewStore((state) => state.answers); + const isMobile = useRootContainerSize() < 450; + const isTablet = useRootContainerSize() < 850; const onVariantClick = async (event: MouseEvent) => { event.preventDefault(); - updateAnswer(questionId, variant.id, variant.points || 0); + const variantId = variant.id; + if (isMulti) { + const currentAnswer = typeof answer !== "string" ? answer || [] : []; - if (answer === variant.id) { + return updateAnswer( + questionId, + currentAnswer.includes(variantId) + ? currentAnswer?.filter((item) => item !== variantId) + : [...currentAnswer, variantId], + variant.points || 0 + ); + } + + updateAnswer(questionId, variantId, variant.points || 0); + + if (answer === variantId) { deleteAnswer(questionId); } }; + const choiceImgUrl = useMemo(() => { + if (variant.editedUrlImagesList !== undefined && variant.editedUrlImagesList !== null) { + return variant.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"]; + } else { + return variant.extendedText; + } + }, []); + return ( { {variant.extendedText && ( { )} + {own && ( + + Введите свой ответ + + )} { "& .MuiFormControlLabel-label": { wordBreak: "break-word", height: variant.answer.length <= 60 ? undefined : "60px", - overflow: "auto", lineHeight: "normal", + overflow: "auto", + maxHeight: "100%", + width: "100%", "&::-webkit-scrollbar": { width: "4px", }, "&::-webkit-scrollbar-thumb": { - backgroundColor: "#b8babf", + backgroundColor: theme.palette.primary.main, }, + scrollbarColor: theme.palette.primary.main, }, }} value={index} control={ - } - icon={} - sx={{ - position: "absolute", - top: "-297px", - right: 0, - }} - /> + isMulti ? ( + } + icon={} + sx={{ + position: "absolute", + top: "-297px", + right: 0, + }} + /> + ) : ( + } + icon={} + sx={{ + position: "absolute", + top: "-297px", + right: 0, + }} + /> + ) + } + label={ + own ? ( + + ) : ( + variant.answer + ) } - label={variant.answer} /> ); diff --git a/lib/components/ViewPublicationPage/questions/Images/index.tsx b/lib/components/ViewPublicationPage/questions/Images/index.tsx index 5387b01..a371d66 100644 --- a/lib/components/ViewPublicationPage/questions/Images/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Images/index.tsx @@ -1,8 +1,9 @@ import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import type { QuizQuestionImages } from "@model/questionTypes/images"; import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; -import { useQuizViewStore } from "@stores/quizView"; +import { createQuizViewStore, useQuizViewStore } from "@stores/quizView"; import { ImageVariant } from "./ImageVariant"; +import moment from "moment"; type ImagesProps = { currentQuestion: QuizQuestionImages; @@ -15,6 +16,8 @@ export const Images = ({ currentQuestion }: ImagesProps) => { const isTablet = useRootContainerSize() < 1000; const isMobile = useRootContainerSize() < 500; + if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); + return ( { {currentQuestion.title} answer === id)} sx={{ display: "flex", @@ -43,14 +46,24 @@ export const Images = ({ currentQuestion }: ImagesProps) => { width: "100%", }} > - {currentQuestion.content.variants.map((variant, index) => ( - - ))} + {currentQuestion.content.variants + .filter((v) => { + if (!v.isOwn) return true; + return v.isOwn && currentQuestion.content.own; + }) + .map((variant, index) => ( + + ))} diff --git a/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx b/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx index 3090c9a..73760cb 100644 --- a/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx +++ b/lib/components/ViewPublicationPage/questions/Text/TextNormal.tsx @@ -8,7 +8,7 @@ import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import { quizThemes } from "@utils/themes/Publication/themePublication"; -import type { ChangeEvent } from "react"; +import { useMemo, type ChangeEvent } from "react"; import type { QuizQuestionText } from "@model/questionTypes/text"; interface TextNormalProps { @@ -21,12 +21,22 @@ export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => { const { settings } = useQuizSettings(); const { updateAnswer } = useQuizViewStore((state) => state); const isMobile = useRootContainerSize() < 650; + const isTablet = useRootContainerSize() < 850; const theme = useTheme(); const onInputChange = async ({ target }: ChangeEvent) => { updateAnswer(currentQuestion.id, target.value, 0); }; - + const choiceImgUrlQuestion = useMemo(() => { + if ( + currentQuestion.content.editedUrlImagesList !== undefined && + currentQuestion.content.editedUrlImagesList !== null + ) { + return currentQuestion.content.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"]; + } else { + return currentQuestion.content.back; + } + }, [currentQuestion]); return ( { "&:focus-visible": { borderColor: theme.palette.primary.main }, }} /> - {currentQuestion.content.back && currentQuestion.content.back !== " " && ( + {choiceImgUrlQuestion && choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && ( { > diff --git a/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx b/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx index 3835ed8..3e2afb3 100644 --- a/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx +++ b/lib/components/ViewPublicationPage/questions/Variant/VariantItem.tsx @@ -1,7 +1,17 @@ import { useQuizSettings } from "@contexts/QuizDataContext"; 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 { + Checkbox, + FormControlLabel, + Input, + TextField as MuiTextField, + Radio, + TextFieldProps, + TextareaAutosize, + Typography, + useTheme, +} from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; @@ -10,6 +20,70 @@ import type { FC, MouseEvent } from "react"; const TextField = MuiTextField as unknown as FC; +interface OwnInputProps { + questionId: string; + variant: QuestionVariant; + largeCheck: boolean; + ownPlaceholder: string; +} +const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => { + const theme = useTheme(); + const ownVariants = useQuizViewStore((state) => state.ownVariants); + const { updateOwnVariant } = useQuizViewStore((state) => state); + + const ownAnswer = ownVariants[ownVariants.findIndex((v) => v.id === variant.id)]?.variant.answer || ""; + + return largeCheck ? ( + ) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => { + updateOwnVariant(variant.id, e.target.value); + }} + /> + ) : ( + ) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => { + updateOwnVariant(variant.id, e.target.value); + }} + /> + ); +}; + export const VariantItem = ({ questionId, isMulti, @@ -17,13 +91,17 @@ export const VariantItem = ({ answer, index, own = false, + questionLargeCheck, + ownPlaceholder, }: { isMulti: boolean; questionId: string; variant: QuestionVariant; answer: string | string[] | undefined; index: number; - own?: boolean; + own: boolean; + questionLargeCheck: boolean; + ownPlaceholder: string; }) => { const { settings } = useQuizSettings(); const theme = useTheme(); @@ -57,7 +135,9 @@ export const VariantItem = ({ - } - icon={} + checkedIcon={} + icon={} /> ) : ( ) } - label={own ? : variant.answer} + label={ + own ? ( + <> + + Введите свой ответ + + + + ) : ( + variant.answer + ) + } onClick={sendVariant} /> ); diff --git a/lib/components/ViewPublicationPage/questions/Variant/index.tsx b/lib/components/ViewPublicationPage/questions/Variant/index.tsx index 68cd397..e990c0c 100644 --- a/lib/components/ViewPublicationPage/questions/Variant/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Variant/index.tsx @@ -1,5 +1,5 @@ import { Box, FormGroup, RadioGroup, Typography, useTheme } from "@mui/material"; -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; import { VariantItem } from "./VariantItem"; @@ -16,6 +16,7 @@ type VariantProps = { export const Variant = ({ currentQuestion }: VariantProps) => { const theme = useTheme(); const isMobile = useRootContainerSize() < 650; + const isTablet = useRootContainerSize() < 850; const answers = useQuizViewStore((state) => state.answers); const ownVariants = useQuizViewStore((state) => state.ownVariants); const updateOwnVariant = useQuizViewStore((state) => state.updateOwnVariant); @@ -32,6 +33,16 @@ export const Variant = ({ currentQuestion }: VariantProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const choiceImgUrlQuestion = useMemo(() => { + if ( + currentQuestion.content.editedUrlImagesList !== undefined && + currentQuestion.content.editedUrlImagesList !== null + ) { + return currentQuestion.content.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"]; + } else { + return currentQuestion.content.back; + } + }, [currentQuestion]); if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); return ( @@ -73,33 +84,31 @@ export const Variant = ({ currentQuestion }: VariantProps) => { gap: "20px", }} > - {currentQuestion.content.variants.map((variant, index) => ( - - ))} - {currentQuestion.content.own && ownVariant && ( - - )} + {currentQuestion.content.variants + .filter((v) => { + if (!v.isOwn) return true; + return v.isOwn && currentQuestion.content.own; + }) + .map((variant, index) => ( + + ))} - {currentQuestion.content.back && currentQuestion.content.back !== " " && ( + {choiceImgUrlQuestion && choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && ( diff --git a/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx b/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx index c50e1cf..9a597aa 100644 --- a/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx +++ b/lib/components/ViewPublicationPage/questions/Varimg/VarimgVariant.tsx @@ -1,28 +1,115 @@ -import type { QuestionVariant } from "@/model/questionTypes/shared"; +import type { QuestionVariant, QuestionVariantWithEditedImages } from "@/model/questionTypes/shared"; import { useQuizSettings } from "@contexts/QuizDataContext"; -import { FormControlLabel, Radio, useTheme } from "@mui/material"; +import { + FormControlLabel, + TextareaAutosize, + Radio, + useTheme, + Box, + Input, + FormControl, + InputLabel, + Typography, +} from "@mui/material"; import { useQuizViewStore } from "@stores/quizView"; import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; import { quizThemes } from "@utils/themes/Publication/themePublication"; -import type { MouseEvent } from "react"; +import { type MouseEvent } from "react"; +import { useDebouncedCallback } from "use-debounce"; type VarimgVariantProps = { questionId: string; - variant: QuestionVariant; + variant: QuestionVariantWithEditedImages; index: number; isSending: boolean; setIsSending: (isSending: boolean) => void; + questionLargeCheck: boolean; + isMulti: boolean; + answer: string | string[] | undefined; + ownPlaceholder: string; }; -export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSending }: VarimgVariantProps) => { - const { settings } = useQuizSettings(); - const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); - const answers = useQuizViewStore((state) => state.answers); +interface OwnInputProps { + questionId: string; + variant: QuestionVariant; + largeCheck: boolean; + ownPlaceholder: string; +} +const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => { + const theme = useTheme(); + const ownVariants = useQuizViewStore((state) => state.ownVariants); + const { updateOwnVariant } = useQuizViewStore((state) => state); + const ownAnswer = ownVariants[ownVariants.findIndex((v) => v.id === variant.id)]?.variant.answer || ""; + + return largeCheck ? ( + ) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => { + updateOwnVariant(variant.id, e.target.value); + }} + /> + ) : ( + ) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => { + updateOwnVariant(variant.id, e.target.value); + }} + /> + ); +}; + +export const VarimgVariant = ({ + questionId, + variant, + index, + isSending, + setIsSending, + questionLargeCheck, + ownPlaceholder, + answer, +}: VarimgVariantProps) => { const theme = useTheme(); - const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {}; + const { settings } = useQuizSettings(); + const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const sendVariant = async (event: MouseEvent) => { event.preventDefault(); @@ -34,50 +121,145 @@ export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSend } }; - return ( - } - icon={} + if (variant?.isOwn) { + return ( + + + Введите свой ответ + + + + ) : ( + variant.answer + ) + } + control={ + } + icon={} + /> + } /> - } - /> - ); + + ); + } else { + return ( + + ) : ( + variant.answer + ) + } + control={ + } + icon={} + /> + } + /> + ); + } }; diff --git a/lib/components/ViewPublicationPage/questions/Varimg/index.tsx b/lib/components/ViewPublicationPage/questions/Varimg/index.tsx index d0f9c3b..0a73dc1 100644 --- a/lib/components/ViewPublicationPage/questions/Varimg/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Varimg/index.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; import { VarimgVariant } from "./VarimgVariant"; @@ -9,6 +9,7 @@ import { useRootContainerSize } from "@contexts/RootContainerWidthContext"; import BlankImage from "@icons/BlankImage"; import type { QuizQuestionVarImg } from "@model/questionTypes/varimg"; +import moment from "moment"; type VarimgProps = { currentQuestion: QuizQuestionVarImg; @@ -17,13 +18,46 @@ type VarimgProps = { export const Varimg = ({ currentQuestion }: VarimgProps) => { const [isSending, setIsSending] = useState(false); const answers = useQuizViewStore((state) => state.answers); + const ownVariants = useQuizViewStore((state) => state.ownVariants); + const updateOwnVariant = useQuizViewStore((state) => state.updateOwnVariant); const theme = useTheme(); const isMobile = useRootContainerSize() < 650; + const isTablet = useRootContainerSize() < 850; const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; + const ownVariant = ownVariants.find((variant) => variant.id === currentQuestion.id); const variant = currentQuestion.content.variants.find(({ id }) => answer === id); + useEffect(() => { + if (!ownVariant) { + updateOwnVariant(currentQuestion.id, ""); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const choiceImgUrlAnswer = useMemo(() => { + if (variant !== undefined) { + if (variant.editedUrlImagesList !== undefined && variant.editedUrlImagesList !== null) { + return variant.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"]; + } else { + return variant.extendedText; + } + } + }, [variant]); + + const choiceImgUrlQuestion = useMemo(() => { + if ( + currentQuestion.content.editedUrlImagesList !== undefined && + currentQuestion.content.editedUrlImagesList !== null + ) { + return currentQuestion.content.editedUrlImagesList[isMobile ? "mobile" : isTablet ? "tablet" : "desktop"]; + } else { + return currentQuestion.content.back; + } + }, [variant]); + if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); + return ( { "&:active": { color: theme.palette.text.primary }, }} > - {currentQuestion.content.variants.map((variant, index) => ( - - ))} + {currentQuestion.content.variants + .filter((v) => { + if (!v.isOwn) return true; + return v.isOwn && currentQuestion.content.own; + }) + .map((variant, index) => ( + + ))} { }} > {answer ? ( - variant?.extendedText ? ( + choiceImgUrlAnswer ? ( ) : ( ) - ) : currentQuestion.content.back !== " " && - currentQuestion.content.back !== null && - currentQuestion.content.back.length > 0 ? ( + ) : choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && choiceImgUrlQuestion.length > 0 ? ( diff --git a/lib/model/questionTypes/date.ts b/lib/model/questionTypes/date.ts index 8c5970d..60e0a63 100644 --- a/lib/model/questionTypes/date.ts +++ b/lib/model/questionTypes/date.ts @@ -17,5 +17,6 @@ export interface QuizQuestionDate extends QuizQuestionBase { back: string | null; originalBack: string | null; autofill: boolean; + isRange?: boolean; }; } diff --git a/lib/model/questionTypes/emoji.ts b/lib/model/questionTypes/emoji.ts index a9db0cf..7be9d58 100644 --- a/lib/model/questionTypes/emoji.ts +++ b/lib/model/questionTypes/emoji.ts @@ -20,5 +20,7 @@ export interface QuizQuestionEmoji extends QuizQuestionBase { back: string | null; originalBack: string | null; autofill: boolean; + ownPlaceholder?: string; + isLargeCheck?: boolean; }; } diff --git a/lib/model/questionTypes/images.ts b/lib/model/questionTypes/images.ts index f310fc1..88084c9 100644 --- a/lib/model/questionTypes/images.ts +++ b/lib/model/questionTypes/images.ts @@ -1,4 +1,4 @@ -import type { QuestionHint, QuestionVariant, QuizQuestionBase, QuestionBranchingRule } from "./shared"; +import type { QuestionHint, QuestionVariantWithEditedImages, QuizQuestionBase, QuestionBranchingRule } from "./shared"; export interface QuizQuestionImages extends QuizQuestionBase { type: "images"; @@ -21,12 +21,14 @@ export interface QuizQuestionImages extends QuizQuestionBase { /** Чекбокс "Необязательный вопрос" */ required: boolean; /** Варианты (картинки) */ - variants: QuestionVariant[]; + variants: QuestionVariantWithEditedImages[]; hint: QuestionHint; rule: QuestionBranchingRule; back: string | null; originalBack: string | null; autofill: boolean; largeCheck: boolean; + ownPlaceholder?: string; + isLargeCheck?: boolean; }; } diff --git a/lib/model/questionTypes/result.ts b/lib/model/questionTypes/result.ts index 26a05ea..e777b56 100644 --- a/lib/model/questionTypes/result.ts +++ b/lib/model/questionTypes/result.ts @@ -1,4 +1,4 @@ -import type { QuizQuestionBase, QuestionBranchingRule, QuestionHint } from "./shared"; +import type { QuizQuestionBase, QuestionBranchingRule, QuestionHint, EditedUrlImagesList } from "./shared"; interface ResultQuestionBranchingRule extends QuestionBranchingRule { minScore?: number; @@ -15,6 +15,7 @@ export interface QuizQuestionResult extends QuizQuestionBase { price: [number] | [number, number]; useImage: boolean; rule: ResultQuestionBranchingRule; + editedUrlImagesList?: EditedUrlImagesList | null; hint: QuestionHint; autofill: boolean; usage: boolean; diff --git a/lib/model/questionTypes/shared.ts b/lib/model/questionTypes/shared.ts index 6f77a51..c32ebca 100644 --- a/lib/model/questionTypes/shared.ts +++ b/lib/model/questionTypes/shared.ts @@ -1,3 +1,4 @@ +import { type } from "os"; import type { QuizQuestionDate } from "./date"; import type { QuizQuestionEmoji } from "./emoji"; import type { QuizQuestionFile } from "./file"; @@ -20,6 +21,9 @@ export interface QuestionBranchingRuleMain { }[]; } +export type EditedImagesScreens = "desktop" | "tablet" | "mobile" | "small"; +export type EditedUrlImagesList = Record; + export interface QuestionBranchingRule { children: string[]; //список условий @@ -43,10 +47,15 @@ export type QuestionVariant = { hints: string; /** Дополнительное поле для текста, emoji, ссылки на картинку */ extendedText: string; + isOwn?: boolean; + isMulti?: boolean; /** Оригинал изображения (до кропа) */ originalImageUrl: string; points?: number; }; +export interface QuestionVariantWithEditedImages extends QuestionVariant { + editedUrlImagesList?: EditedUrlImagesList | null; +} export type QuestionType = | "variant" diff --git a/lib/model/questionTypes/text.ts b/lib/model/questionTypes/text.ts index 0fda214..abbf660 100644 --- a/lib/model/questionTypes/text.ts +++ b/lib/model/questionTypes/text.ts @@ -1,4 +1,4 @@ -import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule } from "./shared"; +import type { QuizQuestionBase, QuestionHint, QuestionBranchingRule, EditedUrlImagesList } from "./shared"; export interface QuizQuestionText extends QuizQuestionBase { type: "text"; @@ -14,6 +14,7 @@ export interface QuizQuestionText extends QuizQuestionBase { /** Чекбокс "Автозаполнение адреса" */ autofill: boolean; answerType: "single" | "multi" | "numberOnly"; + editedUrlImagesList?: EditedUrlImagesList | null; hint: QuestionHint; rule: QuestionBranchingRule; back: string | null; diff --git a/lib/model/questionTypes/variant.ts b/lib/model/questionTypes/variant.ts index 706875b..c1a57ca 100644 --- a/lib/model/questionTypes/variant.ts +++ b/lib/model/questionTypes/variant.ts @@ -1,4 +1,10 @@ -import type { QuizQuestionBase, QuestionVariant, QuestionHint, QuestionBranchingRule } from "./shared"; +import type { + QuizQuestionBase, + QuestionVariant, + QuestionHint, + QuestionBranchingRule, + EditedUrlImagesList, +} from "./shared"; export interface QuizQuestionVariant extends QuizQuestionBase { type: "variant"; @@ -14,6 +20,7 @@ export interface QuizQuestionVariant extends QuizQuestionBase { innerNameCheck: boolean; /** Чекбокс "Необязательный вопрос" */ required: boolean; + editedUrlImagesList?: EditedUrlImagesList | null; /** Поле "Внутреннее название вопроса" */ innerName: string; /** Варианты ответов */ @@ -23,5 +30,6 @@ export interface QuizQuestionVariant extends QuizQuestionBase { back: string | null; originalBack: string | null; autofill: boolean; + ownPlaceholder?: string; }; } diff --git a/lib/model/questionTypes/varimg.ts b/lib/model/questionTypes/varimg.ts index 84ad2bb..4b2e564 100644 --- a/lib/model/questionTypes/varimg.ts +++ b/lib/model/questionTypes/varimg.ts @@ -1,4 +1,10 @@ -import type { QuestionHint, QuestionVariant, QuizQuestionBase, QuestionBranchingRule } from "./shared"; +import type { + QuestionHint, + QuestionVariantWithEditedImages, + EditedUrlImagesList, + QuizQuestionBase, + QuestionBranchingRule, +} from "./shared"; export interface QuizQuestionVarImg extends QuizQuestionBase { type: "varimg"; @@ -12,7 +18,8 @@ export interface QuizQuestionVarImg extends QuizQuestionBase { innerName: string; /** Чекбокс "Необязательный вопрос" */ required: boolean; - variants: QuestionVariant[]; + variants: QuestionVariantWithEditedImages[]; + editedUrlImagesList?: EditedUrlImagesList | null; hint: QuestionHint; rule: QuestionBranchingRule; back: string | null; @@ -20,5 +27,8 @@ export interface QuizQuestionVarImg extends QuizQuestionBase { autofill: boolean; largeCheck: boolean; replText: string; + /** Чекбокс "Можно несколько" */ + multi?: boolean; + ownPlaceholder?: string; }; } diff --git a/lib/stores/quizView.ts b/lib/stores/quizView.ts index 2720189..db71ba7 100644 --- a/lib/stores/quizView.ts +++ b/lib/stores/quizView.ts @@ -14,7 +14,7 @@ export type QuestionAnswer = { answer: Answer; }; -type OwnVariant = { +export type OwnVariant = { id: string; variant: QuestionVariant; }; @@ -99,7 +99,7 @@ export const createQuizViewStore = () => state.ownVariants.push({ id, variant: { - id: nanoid(), + id: id, answer, extendedText: "", hints: "", diff --git a/lib/utils/hooks/useQuestionFlowControl.ts b/lib/utils/hooks/useQuestionFlowControl.ts index 78b85f4..821d6c3 100644 --- a/lib/utils/hooks/useQuestionFlowControl.ts +++ b/lib/utils/hooks/useQuestionFlowControl.ts @@ -33,6 +33,8 @@ export function useQuestionFlowControl() { //Изменение стейта (переменной currentQuestionId) ведёт к пересчёту что же за объект сейчас используется. Мы каждый раз просто ищем в списке const currentQuestion = sortedQuestions.find((question) => question.id === currentQuestionId) ?? sortedQuestions[0]; + // console.log(currentQuestion) + //Индекс текущего вопроса только если квиз линейный const linearQuestionIndex = //: number | null currentQuestion && sortedQuestions.every(({ content }) => content.rule.parentId !== "root") // null when branching enabled diff --git a/lib/utils/sendQuestionAnswer.ts b/lib/utils/sendQuestionAnswer.ts index bf66ce3..7ea867a 100644 --- a/lib/utils/sendQuestionAnswer.ts +++ b/lib/utils/sendQuestionAnswer.ts @@ -1,13 +1,14 @@ import { sendAnswer } from "@/api/quizRelase"; import { RealTypedQuizQuestion } from "@/model/questionTypes/shared"; -import { QuestionAnswer } from "@/stores/quizView"; +import { OwnVariant, QuestionAnswer, createQuizViewStore } from "@/stores/quizView"; import moment from "moment"; import { notReachable } from "./notReachable"; export function sendQuestionAnswer( quizId: string, question: RealTypedQuizQuestion, - questionAnswer: QuestionAnswer | undefined + questionAnswer: QuestionAnswer | undefined, + ownVariants: OwnVariant[] ) { if (!questionAnswer) { return sendAnswer({ @@ -18,15 +19,67 @@ export function sendQuestionAnswer( } switch (question.type) { case "date": { - if (!moment.isMoment(questionAnswer.answer)) throw new Error("Cannot send answer in date question"); + let answer = ""; + if (question.content.isRange) { + if (!Array.isArray(questionAnswer.answer)) throw new Error("Cannot send answer in range question"); + + let from = Number(questionAnswer.answer[0]); + let to = Number(questionAnswer.answer[1]); + + if ( + from !== 0 && + to !== 0 && + from !== Math.min(Number(questionAnswer.answer[0]), Number(questionAnswer.answer[1])) + ) { + from = Math.min(Number(questionAnswer.answer[0]), Number(questionAnswer.answer[1])); + to = Math.max(Number(questionAnswer.answer[0]), Number(questionAnswer.answer[1])); + } + + answer = `${!from ? "_" : moment(from).format("YYYY.MM.DD")} - ${!to ? "_" : moment(to).format("YYYY.MM.DD")}`; + } else { + if (!moment.isMoment(questionAnswer.answer)) throw new Error("Cannot send answer in date question"); + + answer = moment(questionAnswer.answer).format("YYYY.MM.DD"); + } return sendAnswer({ questionId: question.id, - body: moment(questionAnswer.answer).format("YYYY.MM.DD"), + body: answer, qid: quizId, }); } case "emoji": { + if (question.content.multi) { + const answer = questionAnswer.answer; + const ownVariant = Array.isArray(answer) + ? ownVariants[ownVariants.findIndex((variant) => answer.some((a: string) => a === variant.id))]?.variant || "" + : ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant || ""; + + if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); + + //Оставляем только выбранные варианты + const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id)); + + let answerString = ``; + selectedVariants.forEach((e) => { + if (e.isOwn) { + if (question.content.own && selectedVariants.some((v) => v.isOwn)) { + answerString += `\`${e.extendedText} ${ownVariant?.answer ?? ""}\`,`; + } + } else { + answerString += `\`${e.extendedText} ${e.answer ?? ""}\`,`; + } + }); + + answerString = answerString.slice(0, -1); + + return sendAnswer({ + questionId: question.id, + body: answerString, + 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}`); @@ -40,7 +93,40 @@ export function sendQuestionAnswer( return; } case "images": { + if (question.content.multi) { + const answer = questionAnswer.answer; + const ownAnswer = Array.isArray(answer) + ? ownVariants[ownVariants.findIndex((variant) => answer.some((a: string) => a === variant.id))]?.variant + ?.answer || "" + : ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer || + ""; + + if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); + + //Оставляем только выбранные варианты + const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id)); + + let answerString = ``; + selectedVariants.forEach((e) => { + if (!e.isOwn || (e.isOwn && question.content.own)) { + const body = { + Image: e.extendedText, + Description: e.isOwn ? ownAnswer : e.answer, + }; + answerString += `\`${JSON.stringify(body)}\`,`; + } + }); + answerString = answerString.slice(0, -1); + + return sendAnswer({ + questionId: question.id, + body: answerString, + 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}`); const body = { Image: variant.extendedText, @@ -99,13 +185,30 @@ export function sendQuestionAnswer( case "variant": { if (question.content.multi) { const answer = questionAnswer.answer; - if (!Array.isArray(answer)) throw new Error("Cannot send answer in select question"); + if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); + const ownAnswer = Array.isArray(answer) + ? ownVariants[ownVariants.findIndex((variant) => answer.some((a: string) => a === variant.id))]?.variant + ?.answer || "" + : ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer || + ""; + + //Оставляем только выбранные варианты const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id)); + let answerString = ``; + selectedVariants.forEach((e) => { + if (!e.isOwn) answerString += `\`${e.answer}\`,`; + }); + + if (question.content.own && selectedVariants.some((v) => v.isOwn)) { + answerString += `\`${ownAnswer}\`,`; + } + answerString = answerString.slice(0, -1); + return sendAnswer({ questionId: question.id, - body: selectedVariants.map((v) => v.answer).join(", "), + body: answerString, qid: quizId, }); } @@ -121,16 +224,19 @@ export function sendQuestionAnswer( } case "varimg": { const variant = question.content.variants.find((v) => v.id === questionAnswer.answer); + const ownAnswer = + ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer || ""; + if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`); const body = { Image: variant.extendedText, - Description: variant.answer, + Description: question.content.own ? ownAnswer : variant.answer, }; if (!body) throw new Error(`Body of answer in question ${question.id} is undefined`); return sendAnswer({ questionId: question.id, - body: JSON.stringify(body), + body: `\`${JSON.stringify(body)}\``, qid: quizId, }); } diff --git a/lib/utils/themes/dark.ts b/lib/utils/themes/dark.ts index 87857e4..05033b8 100644 --- a/lib/utils/themes/dark.ts +++ b/lib/utils/themes/dark.ts @@ -42,6 +42,9 @@ const darkTheme = createTheme({ navbarbg: { main: "#333647", }, + ownPlaceholder: { + main: "(51, 54, 71, 0.65)", + }, }, }); diff --git a/lib/utils/themes/light.ts b/lib/utils/themes/light.ts index 7e41652..15e7b78 100644 --- a/lib/utils/themes/light.ts +++ b/lib/utils/themes/light.ts @@ -46,6 +46,9 @@ const lightTheme = createTheme({ orange: { main: "#FB5607", }, + ownPlaceholder: { + main: "1,1,1,0.65", + }, navbarbg: { main: "#FFFFFF", }, diff --git a/lib/utils/themes/mui.d.ts b/lib/utils/themes/mui.d.ts index eb40eec..961aa4c 100644 --- a/lib/utils/themes/mui.d.ts +++ b/lib/utils/themes/mui.d.ts @@ -12,6 +12,7 @@ declare module "@mui/material/styles" { grey4: Palette["primary"]; orange: Palette["primary"]; navbarbg: Palette["primary"]; + ownPlaceholder: Palette["primary"]; } interface PaletteOptions { lightPurple?: PaletteOptions["primary"]; @@ -24,6 +25,7 @@ declare module "@mui/material/styles" { grey4?: PaletteOptions["primary"]; orange?: PaletteOptions["primary"]; navbarbg?: PaletteOptions["primary"]; + ownPlaceholder?: PaletteOptions["primary"]; } interface TypographyVariants { infographic: React.CSSProperties; diff --git a/package.json b/package.json index 5c88c7e..6c6600b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@frontend/squzanswerer", - "version": "1.0.55", + "version": "1.0.56", "type": "module", "main": "./dist-package/index.js", "module": "./dist-package/index.js",