Merge branch 'staging'
This commit is contained in:
commit
a461c8dc3d
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
||||
1.0.2 Страничка результатов способна показать историю и правильность ответов для балловго квиза
|
||||
1.0.1 Отображение для баллового квиза на страничке результатов списка
|
||||
1.0.0 Добавлены фичи "мультиответ", "перенос строки в своём ответе", "свой ответ", "плейсхолдер своего ответа"
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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("введена некорректная почта");
|
||||
}
|
||||
|
@ -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 = (
|
||||
<CustomInput
|
||||
|
@ -43,7 +43,10 @@ export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
|
||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
||||
Вопрос {stepNumber} из {questionsAmount}
|
||||
</Typography>
|
||||
<Stepper activeStep={stepNumber} steps={questionsAmount} />
|
||||
<Stepper
|
||||
activeStep={stepNumber}
|
||||
steps={questionsAmount}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{prevButton}
|
||||
|
@ -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 (
|
||||
<Box
|
||||
sx={{
|
||||
@ -166,7 +176,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{resultQuestion?.content.useImage && resultQuestion.content.back && (
|
||||
{resultQuestion?.content.useImage && choiceImgUrlQuestion && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
@ -176,7 +186,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
||||
>
|
||||
<img
|
||||
alt="resultImage"
|
||||
src={resultQuestion.content.back}
|
||||
src={choiceImgUrlQuestion}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: spec ? "auto" : isMobile ? "236px" : "306px",
|
||||
|
@ -20,8 +20,9 @@ import NextButton from "./tools/NextButton";
|
||||
import PrevButton from "./tools/PrevButton";
|
||||
|
||||
export default function ViewPublicationPage() {
|
||||
const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle } = useQuizSettings();
|
||||
const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle, questions } = useQuizSettings();
|
||||
const answers = useQuizViewStore((state) => 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);
|
||||
});
|
||||
|
@ -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 (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<DatePicker
|
||||
format="DD/MM/YYYY"
|
||||
slots={{
|
||||
openPickerIcon: () => (
|
||||
<CalendarIcon
|
||||
sx={{
|
||||
"& path": { stroke: theme.palette.primary.main },
|
||||
"& rect": { stroke: theme.palette.primary.main },
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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" },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
100
lib/components/ViewPublicationPage/questions/Date/DateRange.tsx
Normal file
100
lib/components/ViewPublicationPage/questions/Date/DateRange.tsx
Normal file
@ -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 (
|
||||
<Paper
|
||||
sx={{
|
||||
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,
|
||||
width: isMobile ? "min-content" : "auto",
|
||||
display: "inline-flex",
|
||||
flexWrap: "wrap",
|
||||
marginTop: "20px",
|
||||
p: "20px",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>От</span>
|
||||
<DateCalendar
|
||||
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" },
|
||||
},
|
||||
}}
|
||||
value={currentFrom}
|
||||
onChange={(data) => onDateChange(data, 0)}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<span style={{ marginLeft: "25px", color: theme.palette.text.primary }}>До</span>
|
||||
<DateCalendar
|
||||
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" },
|
||||
},
|
||||
}}
|
||||
value={currentTo}
|
||||
onChange={(data) => onDateChange(data, 1)}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
@ -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 (
|
||||
<Box>
|
||||
@ -35,52 +19,11 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
||||
>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<DatePicker
|
||||
format="DD/MM/YYYY"
|
||||
slots={{
|
||||
openPickerIcon: () => (
|
||||
<CalendarIcon
|
||||
sx={{
|
||||
"& path": { stroke: theme.palette.primary.main },
|
||||
"& rect": { stroke: theme.palette.primary.main },
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
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" },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{currentQuestion.content.isRange ? (
|
||||
<DateRange currentQuestion={currentQuestion} />
|
||||
) : (
|
||||
<DatePicker currentQuestion={currentQuestion} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -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 ? (
|
||||
<Box sx={{ overflow: "auto" }}>
|
||||
<TextareaAutosize
|
||||
placeholder={ownPlaceholder || "|"}
|
||||
style={{
|
||||
resize: "none",
|
||||
width: "100%",
|
||||
fontSize: "16px",
|
||||
color: ownAnswer.length === 0 ? theme.palette.ownPlaceholder.main : theme.palette.text.primary,
|
||||
letterSpacing: "-0.4px",
|
||||
wordSpacing: "-3px",
|
||||
outline: "0px none",
|
||||
backgroundColor: "inherit",
|
||||
border: "none",
|
||||
//@ts-ignore
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
"&::placeholder": {
|
||||
color: theme.palette.ownPlaceholder.main,
|
||||
opacity: 0.65,
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
scrollbarColor: theme.palette.primary.main,
|
||||
overflow: "auto",
|
||||
}}
|
||||
value={ownAnswer}
|
||||
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
updateOwnVariant(variant.id, e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Input
|
||||
placeholder={ownPlaceholder || "|"}
|
||||
sx={{
|
||||
backgroundColor: "inherit",
|
||||
width: "100%",
|
||||
fontSize: "18px",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
value={ownAnswer}
|
||||
disableUnderline
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
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<HTMLDivElement>) => {
|
||||
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 && <Typography fontSize="100px">{variant.extendedText}</Typography>}
|
||||
</Box>
|
||||
</Box>
|
||||
{own && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: "14px",
|
||||
pl: "15px",
|
||||
}}
|
||||
>
|
||||
Введите свой ответ
|
||||
</Typography>
|
||||
)}
|
||||
<FormControlLabel
|
||||
key={variant.id}
|
||||
sx={{
|
||||
@ -85,6 +199,7 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
|
||||
alignItems: variant.answer.length <= 60 ? "center" : "flex-start",
|
||||
position: "relative",
|
||||
height: "80px",
|
||||
overflow: "auto",
|
||||
justifyContent: "center",
|
||||
"& .MuiFormControlLabel-label": {
|
||||
wordBreak: "break-word",
|
||||
@ -92,8 +207,9 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
|
||||
overflow: "auto",
|
||||
"&::-webkit-scrollbar": { width: "4px" },
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: "#b8babf",
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
scrollbarColor: theme.palette.primary.main,
|
||||
},
|
||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
||||
color: theme.palette.text.primary,
|
||||
@ -101,16 +217,34 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
|
||||
}}
|
||||
value={index}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
sx={{ position: "absolute", top: "-162px", right: "12px" }}
|
||||
/>
|
||||
isMulti ? (
|
||||
<Checkbox
|
||||
checked={!!answer?.includes(variant.id)}
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
sx={{ position: "absolute", top: "-162px", right: "12px" }}
|
||||
/>
|
||||
) : (
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
sx={{ position: "absolute", top: "-162px", right: "12px" }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", gap: "10px" }}>
|
||||
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>{variant.answer}</Typography>
|
||||
</Box>
|
||||
own ? (
|
||||
<OwnInput
|
||||
questionId={questionId}
|
||||
variant={variant}
|
||||
largeCheck={questionLargeCheck}
|
||||
ownPlaceholder={ownPlaceholder || "|"}
|
||||
/>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", gap: "10px" }}>
|
||||
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>{variant.answer}</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
|
@ -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 (
|
||||
<Box>
|
||||
<Typography
|
||||
@ -44,14 +47,24 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", width: "100%", gap: "42px", flexWrap: "wrap" }}>
|
||||
{currentQuestion.content.variants.map((variant, index) => (
|
||||
<EmojiVariant
|
||||
key={variant.id}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
{currentQuestion.content.variants
|
||||
.filter((v) => {
|
||||
if (!v.isOwn) return true;
|
||||
return v.isOwn && currentQuestion.content.own;
|
||||
})
|
||||
.map((variant, index) => (
|
||||
<EmojiVariant
|
||||
key={variant.id}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
index={index}
|
||||
isMulti={Boolean(currentQuestion.content.multi)}
|
||||
own={Boolean(variant.isOwn)}
|
||||
questionLargeCheck={true}
|
||||
answer={answer}
|
||||
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
|
@ -61,7 +61,10 @@ export const UploadedFile = ({ currentQuestion, setIsSending }: UploadedFileProp
|
||||
>
|
||||
{answer?.split("|")[0]}
|
||||
</Typography>
|
||||
<IconButton sx={{ p: 0 }} onClick={deleteFile}>
|
||||
<IconButton
|
||||
sx={{ p: 0 }}
|
||||
onClick={deleteFile}
|
||||
>
|
||||
<CloseBold />
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
@ -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 ? (
|
||||
<Box sx={{ overflow: "auto" }}>
|
||||
<TextareaAutosize
|
||||
placeholder={ownPlaceholder || "|"}
|
||||
style={{
|
||||
resize: "none",
|
||||
width: "100%",
|
||||
fontSize: "16px",
|
||||
color: ownAnswer.length === 0 ? theme.palette.ownPlaceholder.main : theme.palette.text.primary,
|
||||
letterSpacing: "-0.4px",
|
||||
wordSpacing: "-3px",
|
||||
outline: "0px none",
|
||||
backgroundColor: "inherit",
|
||||
border: "none",
|
||||
//@ts-ignore
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
"&::placeholder": {
|
||||
color: theme.palette.ownPlaceholder.main,
|
||||
opacity: 0.65,
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
scrollbarColor: theme.palette.primary.main,
|
||||
}}
|
||||
value={ownAnswer}
|
||||
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
updateOwnVariant(variant.id, e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Input
|
||||
placeholder={ownPlaceholder || "|"}
|
||||
sx={{
|
||||
backgroundColor: "inherit",
|
||||
width: "100%",
|
||||
fontSize: "18px",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
value={ownAnswer}
|
||||
disableUnderline
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
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<HTMLDivElement>) => {
|
||||
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 (
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
cursor: "pointer",
|
||||
borderRadius: "12px",
|
||||
border: `1px solid`,
|
||||
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
||||
borderColor: !!answer?.includes(variant.id) ? theme.palette.primary.main : "#9A9AAF",
|
||||
"&:hover": { borderColor: theme.palette.primary.main },
|
||||
background:
|
||||
settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
|
||||
@ -51,7 +156,7 @@ export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
|
||||
<Box sx={{ width: "100%", height: "300px" }}>
|
||||
{variant.extendedText && (
|
||||
<img
|
||||
src={variant.extendedText}
|
||||
src={choiceImgUrl}
|
||||
alt=""
|
||||
style={{
|
||||
display: "block",
|
||||
@ -64,6 +169,17 @@ export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{own && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: "14px",
|
||||
pl: "15px",
|
||||
}}
|
||||
>
|
||||
Введите свой ответ
|
||||
</Typography>
|
||||
)}
|
||||
<FormControlLabel
|
||||
key={variant.id}
|
||||
sx={{
|
||||
@ -80,29 +196,57 @@ export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
|
||||
"& .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={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "-297px",
|
||||
right: 0,
|
||||
}}
|
||||
/>
|
||||
isMulti ? (
|
||||
<Checkbox
|
||||
id="cock"
|
||||
checked={!!answer?.includes(variant.id)}
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "-297px",
|
||||
right: 0,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "-297px",
|
||||
right: 0,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
label={
|
||||
own ? (
|
||||
<OwnInput
|
||||
questionId={questionId}
|
||||
variant={variant}
|
||||
largeCheck={questionLargeCheck}
|
||||
ownPlaceholder={ownPlaceholder || "|"}
|
||||
/>
|
||||
) : (
|
||||
variant.answer
|
||||
)
|
||||
}
|
||||
label={variant.answer}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
@ -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 (
|
||||
<Box>
|
||||
<Typography
|
||||
@ -25,7 +28,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<RadioGroup
|
||||
name={currentQuestion.id}
|
||||
name={currentQuestion.id.toString()}
|
||||
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -43,14 +46,24 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{currentQuestion.content.variants.map((variant, index) => (
|
||||
<ImageVariant
|
||||
key={variant.id}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
{currentQuestion.content.variants
|
||||
.filter((v) => {
|
||||
if (!v.isOwn) return true;
|
||||
return v.isOwn && currentQuestion.content.own;
|
||||
})
|
||||
.map((variant, index) => (
|
||||
<ImageVariant
|
||||
key={variant.id}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
index={index}
|
||||
answer={answer}
|
||||
isMulti={Boolean(currentQuestion.content.multi)}
|
||||
own={Boolean(variant.isOwn)}
|
||||
questionLargeCheck={true}
|
||||
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
|
@ -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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<Box>
|
||||
<Typography
|
||||
@ -61,7 +71,7 @@ export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => {
|
||||
"&:focus-visible": { borderColor: theme.palette.primary.main },
|
||||
}}
|
||||
/>
|
||||
{currentQuestion.content.back && currentQuestion.content.back !== " " && (
|
||||
{choiceImgUrlQuestion && choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && (
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "400px",
|
||||
@ -72,7 +82,7 @@ export const TextNormal = ({ currentQuestion, answer }: TextNormalProps) => {
|
||||
>
|
||||
<img
|
||||
key={currentQuestion.id}
|
||||
src={currentQuestion.content.back}
|
||||
src={choiceImgUrlQuestion}
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
alt=""
|
||||
/>
|
||||
|
@ -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<TextFieldProps>;
|
||||
|
||||
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 ? (
|
||||
<TextareaAutosize
|
||||
placeholder={ownPlaceholder || "|"}
|
||||
style={{
|
||||
resize: "none",
|
||||
width: "100%",
|
||||
fontSize: "16px",
|
||||
color: ownAnswer.length === 0 ? theme.palette.ownPlaceholder.main : theme.palette.text.primary,
|
||||
letterSpacing: "-0.4px",
|
||||
wordSpacing: "-3px",
|
||||
outline: "0px none",
|
||||
backgroundColor: "inherit",
|
||||
border: "none",
|
||||
//@ts-ignore
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
"&::placeholder": {
|
||||
color: theme.palette.ownPlaceholder.main,
|
||||
opacity: "0.65!important",
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
scrollbarColor: theme.palette.primary.main,
|
||||
}}
|
||||
value={ownAnswer}
|
||||
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
updateOwnVariant(variant.id, e.target.value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
placeholder={ownPlaceholder || "|"}
|
||||
sx={{
|
||||
backgroundColor: "inherit",
|
||||
width: "100%",
|
||||
fontSize: "18px",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
value={ownAnswer}
|
||||
disableUnderline
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
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 = ({
|
||||
<FormControlLabel
|
||||
key={variant.id}
|
||||
sx={{
|
||||
position: "relative",
|
||||
margin: "0",
|
||||
mt: own ? "10px" : "0",
|
||||
borderRadius: "12px",
|
||||
color: theme.palette.text.primary,
|
||||
padding: "15px",
|
||||
@ -78,12 +158,15 @@ export const VariantItem = ({
|
||||
"&:hover": { borderColor: theme.palette.primary.main },
|
||||
"&.MuiFormControl-root": { width: "100%" },
|
||||
"& .MuiFormControlLabel-label": {
|
||||
width: "100%",
|
||||
maxHeight: "100%",
|
||||
wordBreak: "break-word",
|
||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||
overflow: "auto",
|
||||
lineHeight: "normal",
|
||||
"&::-webkit-scrollbar": { width: "4px" },
|
||||
"&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" },
|
||||
"&::-webkit-scrollbar-thumb": { backgroundColor: theme.palette.primary.main },
|
||||
scrollbarColor: theme.palette.primary.main,
|
||||
},
|
||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
||||
color: theme.palette.text.primary,
|
||||
@ -93,15 +176,10 @@ export const VariantItem = ({
|
||||
labelPlacement="start"
|
||||
control={
|
||||
isMulti ? (
|
||||
<Checkbox
|
||||
<Radio
|
||||
checked={!!answer?.includes(variant.id)}
|
||||
checkedIcon={
|
||||
<CheckboxIcon
|
||||
checked
|
||||
color={theme.palette.primary.main}
|
||||
/>
|
||||
}
|
||||
icon={<CheckboxIcon />}
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
) : (
|
||||
<Radio
|
||||
@ -110,7 +188,30 @@ export const VariantItem = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
label={own ? <TextField label="Другое..." /> : variant.answer}
|
||||
label={
|
||||
own ? (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: "14px",
|
||||
position: "absolute",
|
||||
top: "-23px",
|
||||
}}
|
||||
>
|
||||
Введите свой ответ
|
||||
</Typography>
|
||||
<OwnInput
|
||||
questionId={questionId}
|
||||
variant={variant}
|
||||
largeCheck={questionLargeCheck}
|
||||
ownPlaceholder={ownPlaceholder || "|"}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
variant.answer
|
||||
)
|
||||
}
|
||||
onClick={sendVariant}
|
||||
/>
|
||||
);
|
||||
|
@ -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) => (
|
||||
<VariantItem
|
||||
key={variant.id}
|
||||
questionId={currentQuestion.id}
|
||||
isMulti={currentQuestion.content.multi}
|
||||
variant={variant}
|
||||
answer={answer}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
{currentQuestion.content.own && ownVariant && (
|
||||
<VariantItem
|
||||
own
|
||||
questionId={currentQuestion.id}
|
||||
isMulti={currentQuestion.content.multi}
|
||||
variant={ownVariant.variant}
|
||||
answer={answer}
|
||||
index={currentQuestion.content.variants.length + 2}
|
||||
/>
|
||||
)}
|
||||
{currentQuestion.content.variants
|
||||
.filter((v) => {
|
||||
if (!v.isOwn) return true;
|
||||
return v.isOwn && currentQuestion.content.own;
|
||||
})
|
||||
.map((variant, index) => (
|
||||
<VariantItem
|
||||
key={variant.id}
|
||||
questionId={currentQuestion.id}
|
||||
isMulti={currentQuestion.content.multi}
|
||||
variant={variant}
|
||||
answer={answer}
|
||||
index={index}
|
||||
own={Boolean(variant.isOwn)}
|
||||
questionLargeCheck={currentQuestion.content.largeCheck}
|
||||
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Group>
|
||||
{currentQuestion.content.back && currentQuestion.content.back !== " " && (
|
||||
{choiceImgUrlQuestion && choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && (
|
||||
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
|
||||
<img
|
||||
key={currentQuestion.id}
|
||||
src={currentQuestion.content.back}
|
||||
src={choiceImgUrlQuestion}
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
alt=""
|
||||
/>
|
||||
|
@ -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 ? (
|
||||
<TextareaAutosize
|
||||
placeholder={ownPlaceholder || "|"}
|
||||
style={{
|
||||
resize: "none",
|
||||
width: "100%",
|
||||
fontSize: "16px",
|
||||
color: ownAnswer.length === 0 ? theme.palette.ownPlaceholder.main : theme.palette.text.primary,
|
||||
letterSpacing: "-0.4px",
|
||||
wordSpacing: "-3px",
|
||||
outline: "0px none",
|
||||
backgroundColor: "inherit",
|
||||
border: "none",
|
||||
//@ts-ignore
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
"&::placeholder": {
|
||||
color: theme.palette.ownPlaceholder.main,
|
||||
opacity: "0.65",
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
scrollbarColor: theme.palette.primary.main,
|
||||
maxHeight: "44px",
|
||||
overflow: "auto",
|
||||
}}
|
||||
value={ownAnswer}
|
||||
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
updateOwnVariant(variant.id, e.target.value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
placeholder={ownPlaceholder || "|"}
|
||||
sx={{
|
||||
backgroundColor: "inherit",
|
||||
width: "100%",
|
||||
fontSize: "18px",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
value={ownAnswer}
|
||||
disableUnderline
|
||||
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
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<HTMLLabelElement>) => {
|
||||
event.preventDefault();
|
||||
@ -34,50 +121,145 @@ export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSend
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={variant.id}
|
||||
disabled={isSending}
|
||||
sx={{
|
||||
marginBottom: "15px",
|
||||
borderRadius: "12px",
|
||||
padding: "20px",
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: settings.cfg.design
|
||||
? quizThemes[settings.cfg.theme].isLight
|
||||
? "#FFFFFF"
|
||||
: "rgba(255,255,255, 0.3)"
|
||||
: quizThemes[settings.cfg.theme].isLight
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
border: `1px solid`,
|
||||
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
||||
display: "flex",
|
||||
margin: 0,
|
||||
justifyContent: "space-between",
|
||||
"&:hover": { borderColor: theme.palette.primary.main },
|
||||
"& .MuiFormControlLabel-label": {
|
||||
wordBreak: "break-word",
|
||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||
overflow: "auto",
|
||||
lineHeight: "normal",
|
||||
"&::-webkit-scrollbar": { width: "4px" },
|
||||
"&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" },
|
||||
},
|
||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
labelPlacement="start"
|
||||
value={index}
|
||||
onClick={sendVariant}
|
||||
label={variant.answer}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
if (variant?.isOwn) {
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: "14px",
|
||||
pl: "15px",
|
||||
}}
|
||||
>
|
||||
Введите свой ответ
|
||||
</Typography>
|
||||
|
||||
<FormControlLabel
|
||||
key={variant.id}
|
||||
disabled={isSending}
|
||||
sx={{
|
||||
marginBottom: "15px",
|
||||
borderRadius: "12px",
|
||||
padding: "20px",
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: settings.cfg.design
|
||||
? quizThemes[settings.cfg.theme].isLight
|
||||
? "#FFFFFF"
|
||||
: "rgba(255,255,255, 0.3)"
|
||||
: quizThemes[settings.cfg.theme].isLight
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
border: `1px solid`,
|
||||
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
||||
display: "flex",
|
||||
margin: 0,
|
||||
justifyContent: "space-between",
|
||||
"&:hover": { borderColor: theme.palette.primary.main },
|
||||
"& .MuiFormControlLabel-label": {
|
||||
wordBreak: "break-word",
|
||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||
overflow: "auto",
|
||||
lineHeight: "normal",
|
||||
width: "100%",
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
scrollbarColor: theme.palette.primary.main,
|
||||
},
|
||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
labelPlacement="start"
|
||||
value={index}
|
||||
onClick={sendVariant}
|
||||
label={
|
||||
variant?.isOwn ? (
|
||||
<OwnInput
|
||||
questionId={questionId}
|
||||
variant={variant}
|
||||
largeCheck={questionLargeCheck}
|
||||
ownPlaceholder={ownPlaceholder || "|"}
|
||||
/>
|
||||
) : (
|
||||
variant.answer
|
||||
)
|
||||
}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={variant.id}
|
||||
disabled={isSending}
|
||||
sx={{
|
||||
marginBottom: "15px",
|
||||
borderRadius: "12px",
|
||||
padding: "20px",
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: settings.cfg.design
|
||||
? quizThemes[settings.cfg.theme].isLight
|
||||
? "#FFFFFF"
|
||||
: "rgba(255,255,255, 0.3)"
|
||||
: quizThemes[settings.cfg.theme].isLight
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
border: `1px solid`,
|
||||
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
|
||||
display: "flex",
|
||||
margin: 0,
|
||||
justifyContent: "space-between",
|
||||
"&:hover": { borderColor: theme.palette.primary.main },
|
||||
"& .MuiFormControlLabel-label": {
|
||||
wordBreak: "break-word",
|
||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||
overflow: "auto",
|
||||
lineHeight: "normal",
|
||||
width: "100%",
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
"&::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
scrollbarColor: theme.palette.primary.main,
|
||||
},
|
||||
"& .MuiFormControlLabel-label.Mui-disabled": {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
labelPlacement="start"
|
||||
value={index}
|
||||
onClick={sendVariant}
|
||||
label={
|
||||
variant?.isOwn ? (
|
||||
<OwnInput
|
||||
questionId={questionId}
|
||||
variant={variant}
|
||||
largeCheck={questionLargeCheck}
|
||||
ownPlaceholder={ownPlaceholder || "|"}
|
||||
/>
|
||||
) : (
|
||||
variant.answer
|
||||
)
|
||||
}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -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<boolean>(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 (
|
||||
<Box>
|
||||
<Typography
|
||||
@ -64,16 +98,25 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
"&:active": { color: theme.palette.text.primary },
|
||||
}}
|
||||
>
|
||||
{currentQuestion.content.variants.map((variant, index) => (
|
||||
<VarimgVariant
|
||||
key={variant.id}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
isSending={isSending}
|
||||
setIsSending={setIsSending}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
{currentQuestion.content.variants
|
||||
.filter((v) => {
|
||||
if (!v.isOwn) return true;
|
||||
return v.isOwn && currentQuestion.content.own;
|
||||
})
|
||||
.map((variant, index) => (
|
||||
<VarimgVariant
|
||||
key={variant.id}
|
||||
questionId={currentQuestion.id}
|
||||
variant={variant}
|
||||
isSending={isSending}
|
||||
setIsSending={setIsSending}
|
||||
index={index}
|
||||
questionLargeCheck={currentQuestion.content.largeCheck}
|
||||
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
|
||||
isMulti={Boolean(currentQuestion.content?.multi)}
|
||||
answer={answer}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</RadioGroup>
|
||||
<Box
|
||||
@ -93,21 +136,19 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
}}
|
||||
>
|
||||
{answer ? (
|
||||
variant?.extendedText ? (
|
||||
choiceImgUrlAnswer ? (
|
||||
<img
|
||||
key={variant.extendedText}
|
||||
src={variant.extendedText}
|
||||
key={choiceImgUrlAnswer}
|
||||
src={choiceImgUrlAnswer}
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
alt=""
|
||||
/>
|
||||
) : (
|
||||
<BlankImage />
|
||||
)
|
||||
) : currentQuestion.content.back !== " " &&
|
||||
currentQuestion.content.back !== null &&
|
||||
currentQuestion.content.back.length > 0 ? (
|
||||
) : choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && choiceImgUrlQuestion.length > 0 ? (
|
||||
<img
|
||||
src={currentQuestion.content.back}
|
||||
src={choiceImgUrlQuestion}
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
alt=""
|
||||
/>
|
||||
|
@ -17,5 +17,6 @@ export interface QuizQuestionDate extends QuizQuestionBase {
|
||||
back: string | null;
|
||||
originalBack: string | null;
|
||||
autofill: boolean;
|
||||
isRange?: boolean;
|
||||
};
|
||||
}
|
||||
|
@ -20,5 +20,7 @@ export interface QuizQuestionEmoji extends QuizQuestionBase {
|
||||
back: string | null;
|
||||
originalBack: string | null;
|
||||
autofill: boolean;
|
||||
ownPlaceholder?: string;
|
||||
isLargeCheck?: boolean;
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<EditedImagesScreens, string>;
|
||||
|
||||
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"
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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: "",
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ const darkTheme = createTheme({
|
||||
navbarbg: {
|
||||
main: "#333647",
|
||||
},
|
||||
ownPlaceholder: {
|
||||
main: "(51, 54, 71, 0.65)",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -46,6 +46,9 @@ const lightTheme = createTheme({
|
||||
orange: {
|
||||
main: "#FB5607",
|
||||
},
|
||||
ownPlaceholder: {
|
||||
main: "1,1,1,0.65",
|
||||
},
|
||||
navbarbg: {
|
||||
main: "#FFFFFF",
|
||||
},
|
||||
|
2
lib/utils/themes/mui.d.ts
vendored
2
lib/utils/themes/mui.d.ts
vendored
@ -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;
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user