This commit is contained in:
ArtChaos189 2023-12-20 02:08:33 +03:00
parent 2828a5ebe2
commit fa2dedeed6
16 changed files with 1584 additions and 1734 deletions

@ -1,8 +1,4 @@
import type { import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared";
QuizQuestionBase,
QuestionHint,
PreviewRule,
} from "./shared";
export interface QuizQuestionPage extends QuizQuestionBase { export interface QuizQuestionPage extends QuizQuestionBase {
type: "page"; type: "page";
@ -15,6 +11,7 @@ export interface QuizQuestionPage extends QuizQuestionBase {
text: string; text: string;
picture: string; picture: string;
originalPicture: string; originalPicture: string;
useImage: boolean;
video: string; video: string;
hint: QuestionHint; hint: QuestionHint;
rule: PreviewRule; rule: PreviewRule;

@ -1,16 +1,16 @@
import { MessageIcon } from "@icons/messagIcon"; import { MessageIcon } from "@icons/messagIcon";
import { PointsIcon } from "@icons/questionsPage/PointsIcon"; import { PointsIcon } from "@icons/questionsPage/PointsIcon";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import {TextareaAutosize} from "@mui/base/TextareaAutosize"; import { TextareaAutosize } from "@mui/base/TextareaAutosize";
import { import {
Box, Box,
FormControl, FormControl,
IconButton, IconButton,
InputAdornment, InputAdornment,
Popover, Popover,
TextField, TextField,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField } from "@root/questions/actions"; import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField } from "@root/questions/actions";
import type { KeyboardEvent, ReactNode } from "react"; import type { KeyboardEvent, ReactNode } from "react";
@ -18,158 +18,147 @@ import { useState } from "react";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import type { QuestionVariant } from "../../../model/questionTypes/shared"; import type { QuestionVariant } from "../../../model/questionTypes/shared";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import { enqueueSnackbar } from "notistack";
type AnswerItemProps = { type AnswerItemProps = {
index: number; index: number;
questionId: string; questionId: string;
variant: QuestionVariant; variant: QuestionVariant;
largeCheck: boolean; largeCheck: boolean;
additionalContent?: ReactNode; disableKeyDown?: boolean;
additionalMobile?: ReactNode; additionalContent?: ReactNode;
additionalMobile?: ReactNode;
}; };
export const AnswerItem = ({ export const AnswerItem = ({
index, index,
variant, variant,
questionId, questionId,
largeCheck, largeCheck,
additionalContent, additionalContent,
additionalMobile, additionalMobile,
disableKeyDown,
}: AnswerItemProps) => { }: AnswerItemProps) => {
const theme = useTheme(); const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(790)); const isTablet = useMediaQuery(theme.breakpoints.down(790));
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const setQuestionVariantAnswer = useDebouncedCallback((value) => { const setQuestionVariantAnswer = useDebouncedCallback((value) => {
setQuestionVariantField(questionId, variant.id, "answer", value); setQuestionVariantField(questionId, variant.id, "answer", value);
}, 200); }, 200);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
setIsOpen(true); setIsOpen(true);
}; };
const handleClose = () => { const handleClose = () => {
setIsOpen(false); setIsOpen(false);
}; };
return ( return (
<Draggable draggableId={String(index)} index={index}> <Draggable draggableId={String(index)} index={index}>
{(provided) => ( {(provided) => (
<Box ref={provided.innerRef} {...provided.draggableProps}> <Box ref={provided.innerRef} {...provided.draggableProps}>
<FormControl <FormControl
key={index} key={index}
fullWidth fullWidth
variant="standard" variant="standard"
sx={{ sx={{
margin: isTablet ? " 15px 0 20px 0" : "0 0 15px 0", margin: isTablet ? " 15px 0 20px 0" : "0 0 15px 0",
borderRadius: "10px", borderRadius: "10px",
border: "1px solid rgba(0, 0, 0, 0.23)", border: "1px solid rgba(0, 0, 0, 0.23)",
background: "white", background: "white",
}}
>
<TextField
defaultValue={variant.answer}
fullWidth
focused={false}
placeholder={"Добавьте ответ"}
multiline={largeCheck}
onChange={({ target }) => {
setQuestionVariantAnswer(target.value || " ");
}}
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
if (disableKeyDown) {
enqueueSnackbar("100 максимальное количество вопросов");
} else if (event.code === "Enter" && !largeCheck) {
addQuestionVariant(questionId);
}
}}
InputProps={{
startAdornment: (
<>
<InputAdornment {...provided.dragHandleProps} position="start">
<PointsIcon style={{ color: "#9A9AAF", fontSize: "30px" }} />
</InputAdornment>
{additionalContent}
</>
),
endAdornment: (
<InputAdornment position="end">
<IconButton sx={{ padding: "0" }} aria-describedby="my-popover-id" onClick={handleClick}>
<MessageIcon
style={{
color: "#9A9AAF",
fontSize: "30px",
marginRight: "6.5px",
}} }}
/>
</IconButton>
<Popover
id="my-popover-id"
open={isOpen}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
> >
<TextField <TextareaAutosize
defaultValue={variant.answer} style={{ margin: "10px" }}
fullWidth placeholder="Подсказка для этого ответа"
focused={false} value={variant.hints}
placeholder={"Добавьте ответ"} onChange={(e) => setQuestionVariantAnswer(e.target.value || " ")}
multiline={largeCheck} onKeyDown={(event: KeyboardEvent<HTMLTextAreaElement>) => event.stopPropagation()}
onChange={({ target }) => { />
setQuestionVariantAnswer(target.value || " "); </Popover>
}} <IconButton sx={{ padding: "0" }} onClick={() => deleteQuestionVariant(questionId, variant.id)}>
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => { <DeleteIcon
if (event.code === "Enter" && !largeCheck) { style={{
addQuestionVariant(questionId); color: theme.palette.grey2.main,
} marginRight: "-1px",
}} }}
InputProps={{ />
startAdornment: ( </IconButton>
<> </InputAdornment>
<InputAdornment ),
{...provided.dragHandleProps} }}
position="start" sx={{
> "& .MuiInputBase-root": {
<PointsIcon padding: additionalContent ? "5px 13px" : "13px",
style={{ color: "#9A9AAF", fontSize: "30px" }} borderRadius: "10px",
/> background: "#ffffff",
</InputAdornment> "& input.MuiInputBase-input": {
{additionalContent} height: "22px",
</> },
), "& textarea.MuiInputBase-input": {
endAdornment: ( marginTop: "1px",
<InputAdornment position="end"> },
<IconButton "& .MuiOutlinedInput-notchedOutline": {
sx={{ padding: "0" }} border: "none",
aria-describedby="my-popover-id" },
onClick={handleClick} },
> }}
<MessageIcon inputProps={{
style={{ sx: { fontSize: "18px", lineHeight: "21px", py: 0, ml: "13px" },
color: "#9A9AAF", "data-cy": "quiz-variant-question-answer",
fontSize: "30px", }}
marginRight: "6.5px", />
}} {additionalMobile}
/> </FormControl>
</IconButton> </Box>
<Popover )}
id="my-popover-id" </Draggable>
open={isOpen} );
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
>
<TextareaAutosize
style={{ margin: "10px" }}
placeholder="Подсказка для этого ответа"
value={variant.hints}
onChange={e => setQuestionVariantAnswer(e.target.value || " ")}
onKeyDown={(
event: KeyboardEvent<HTMLTextAreaElement>
) => event.stopPropagation()}
/>
</Popover>
<IconButton
sx={{ padding: "0" }}
onClick={() => deleteQuestionVariant(questionId, variant.id)}
>
<DeleteIcon
style={{
color: theme.palette.grey2.main,
marginRight: "-1px",
}}
/>
</IconButton>
</InputAdornment>
),
}}
sx={{
"& .MuiInputBase-root": {
padding: additionalContent ? "5px 13px" : "13px",
borderRadius: "10px",
background: "#ffffff",
"& input.MuiInputBase-input": {
height: "22px",
},
"& textarea.MuiInputBase-input": {
marginTop: "1px",
},
"& .MuiOutlinedInput-notchedOutline": {
border: "none",
},
},
}}
inputProps={{
sx: { fontSize: "18px", lineHeight: "21px", py: 0, ml: "13px" },
"data-cy": "quiz-variant-question-answer",
}}
/>
{additionalMobile}
</FormControl>
</Box>
)}
</Draggable>
);
}; };

@ -6,44 +6,40 @@ import { DragDropContext, Droppable } from "react-beautiful-dnd";
import type { QuestionVariant, QuizQuestionsWithVariants } from "../../../model/questionTypes/shared"; import type { QuestionVariant, QuizQuestionsWithVariants } from "../../../model/questionTypes/shared";
import { AnswerItem } from "./AnswerItem"; import { AnswerItem } from "./AnswerItem";
type AnswerDraggableListProps = { type AnswerDraggableListProps = {
question: QuizQuestionsWithVariants; question: QuizQuestionsWithVariants;
additionalContent?: (variant: QuestionVariant, index: number) => ReactNode; additionalContent?: (variant: QuestionVariant, index: number) => ReactNode;
additionalMobile?: (variant: QuestionVariant, index: number) => ReactNode; additionalMobile?: (variant: QuestionVariant, index: number) => ReactNode;
}; };
export const AnswerDraggableList = ({ export const AnswerDraggableList = ({ question, additionalContent, additionalMobile }: AnswerDraggableListProps) => {
question, const onDragEnd = ({ destination, source }: DropResult) => {
additionalContent, if (destination) {
additionalMobile, reorderQuestionVariants(question.id, source.index, destination.index);
}: AnswerDraggableListProps) => { }
const onDragEnd = ({ destination, source }: DropResult) => { };
if (destination) {
reorderQuestionVariants(question.id, source.index, destination.index);
}
};
return ( return (
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable-answer-list"> <Droppable droppableId="droppable-answer-list">
{(provided) => ( {(provided) => (
<Box ref={provided.innerRef} {...provided.droppableProps}> <Box ref={provided.innerRef} {...provided.droppableProps}>
{question.content.variants.map((variant, index) => ( {question.content.variants.map((variant, index) => (
<AnswerItem <AnswerItem
key={variant.id} key={variant.id}
index={index} index={index}
questionId={question.id} disableKeyDown={question.content.variants.length >= 100}
largeCheck={("largeCheck" in question.content) ? question.content.largeCheck : false} questionId={question.id}
variant={variant} largeCheck={"largeCheck" in question.content ? question.content.largeCheck : false}
additionalContent={additionalContent?.(variant, index)} variant={variant}
additionalMobile={additionalMobile?.(variant, index)} additionalContent={additionalContent?.(variant, index)}
/> additionalMobile={additionalMobile?.(variant, index)}
))} />
{provided.placeholder} ))}
</Box> {provided.placeholder}
)} </Box>
</Droppable> )}
</DragDropContext> </Droppable>
); </DragDropContext>
);
}; };

@ -13,7 +13,7 @@ import {
useTheme, useTheme,
Checkbox, Checkbox,
} from "@mui/material"; } from "@mui/material";
import { AnyTypedQuizQuestion, createBranchingRuleMain } from "../../../model/questionTypes/shared" import { AnyTypedQuizQuestion, createBranchingRuleMain } from "../../../model/questionTypes/shared";
import { Select } from "../Select"; import { Select } from "../Select";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
@ -27,45 +27,51 @@ import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
import { useUiTools } from "@root/uiTools/store"; import { useUiTools } from "@root/uiTools/store";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
export default function BranchingQuestions() { export default function BranchingQuestions() {
const theme = useTheme(); const theme = useTheme();
const { openedModalSettingsId } = useUiTools(); const { openedModalSettingsId } = useUiTools();
const [targetQuestion, setTargetQuestion] = useState<AnyTypedQuizQuestion | null>(getQuestionById(openedModalSettingsId) || getQuestionByContentId(openedModalSettingsId)) const [targetQuestion, setTargetQuestion] = useState<AnyTypedQuizQuestion | null>(
const [parentQuestion, setParentQuestion] = useState<AnyTypedQuizQuestion | null>(getQuestionByContentId(targetQuestion?.content.rule.parentId)) getQuestionById(openedModalSettingsId) || getQuestionByContentId(openedModalSettingsId)
);
const [parentQuestion, setParentQuestion] = useState<AnyTypedQuizQuestion | null>(
getQuestionByContentId(targetQuestion?.content.rule.parentId)
);
useLayoutEffect(() => { useLayoutEffect(() => {
if (parentQuestion === null) return if (parentQuestion === null) return;
if (parentQuestion.content.rule.main.length === 0) { if (parentQuestion.content.rule.main.length === 0) {
let mutate = JSON.parse(JSON.stringify(parentQuestion)) let mutate = JSON.parse(JSON.stringify(parentQuestion));
mutate.content.rule.main = [{ mutate.content.rule.main = [
next: targetQuestion.content.id, {
or: true, next: targetQuestion.content.id,
rules: [{ or: true,
question: parentQuestion.content.id, rules: [
answers: [] {
}] question: parentQuestion.content.id,
}] answers: [],
setParentQuestion(mutate) },
],
},
];
setParentQuestion(mutate);
} }
}) });
if (targetQuestion === null || parentQuestion === null) { if (targetQuestion === null || parentQuestion === null) {
enqueueSnackbar("Невозможно найти данные ветвления для этого вопроса") enqueueSnackbar("Невозможно найти данные ветвления для этого вопроса");
return <></> return <></>;
} }
const saveData = () => { const saveData = () => {
if (parentQuestion !== null) { if (parentQuestion !== null) {
updateQuestion(parentQuestion.content.id, question => question.content = parentQuestion.content) updateQuestion(parentQuestion.content.id, (question) => (question.content = parentQuestion.content));
} }
handleClose() handleClose();
};
}
const handleClose = () => { const handleClose = () => {
updateOpenedModalSettingsId() updateOpenedModalSettingsId();
}; };
return ( return (
@ -100,44 +106,50 @@ export default function BranchingQuestions() {
<Box sx={{ color: "#4d4d4d" }}> <Box sx={{ color: "#4d4d4d" }}>
<Typography component="span">{targetQuestion.title}</Typography> <Typography component="span">{targetQuestion.title}</Typography>
</Box> </Box>
<Tooltip <Tooltip title="Настройте условия, при которых данный вопрос будет отображаться в квизе." placement="top">
title="Настройте условия, при которых данный вопрос будет отображаться в квизе."
placement="top"
>
<Box> <Box>
<InfoIcon /> <InfoIcon />
</Box> </Box>
</Tooltip> </Tooltip>
</Box> </Box>
<Box <Box
sx={{ sx={{
height: "400px", height: "400px",
overflow: "auto" overflow: "auto",
}} }}
> >
{ {parentQuestion.content.rule.main.length ? (
parentQuestion.content.rule.main.length ? parentQuestion.content.rule.main.map((e: any, i: number) => {
parentQuestion.content.rule.main.map((e: any, i: number) => { if (e.next === targetQuestion.content.id) {
if (e.next === targetQuestion.content.id) { return (
return <TypeSwitch key={i} setParentQuestion={setParentQuestion} targetQuestion={targetQuestion} parentQuestion={parentQuestion} ruleIndex={i} /> <TypeSwitch
} else { key={i}
<></> setParentQuestion={setParentQuestion}
} targetQuestion={targetQuestion}
}) parentQuestion={parentQuestion}
: ruleIndex={i}
<TypeSwitch targetQuestion={targetQuestion} setParentQuestion={setParentQuestion} parentQuestion={parentQuestion} ruleIndex={0} /> />
} );
} else {
<></>;
}
})
) : (
<TypeSwitch
targetQuestion={targetQuestion}
setParentQuestion={setParentQuestion}
parentQuestion={parentQuestion}
ruleIndex={0}
/>
)}
</Box> </Box>
<Box <Box
sx={{ sx={{
margin: "20px 0 0 20px", margin: "20px 0 0 20px",
display: "flex", display: "flex",
flexDirection: "column" flexDirection: "column",
}} }}
> >
<Link <Link
@ -145,48 +157,45 @@ export default function BranchingQuestions() {
sx={{ sx={{
color: theme.palette.brightPurple.main, color: theme.palette.brightPurple.main,
marginBottom: "10px", marginBottom: "10px",
cursor: "pointer" cursor: "pointer",
}} }}
onClick={() => { onClick={() => {
const mutate = JSON.parse(JSON.stringify(parentQuestion)) const mutate = JSON.parse(JSON.stringify(parentQuestion));
mutate.content.rule.main.push(createBranchingRuleMain(targetQuestion.content.id, parentQuestion.content.id)) mutate.content.rule.main.push(
setParentQuestion(mutate) createBranchingRuleMain(targetQuestion.content.id, parentQuestion.content.id)
);
setParentQuestion(mutate);
}} }}
> >
Добавить условие Добавить условие
</Link> </Link>
<FormControlLabel
<FormControlLabel control={<Checkbox control={
<Checkbox
sx={{ sx={{
margin: 0 margin: 0,
}} }}
checked={parentQuestion.content.rule.default === targetQuestion.content.id} checked={parentQuestion.content.rule.default === targetQuestion.content.id}
onClick={() => {
onClick={() => { let mutate = JSON.parse(JSON.stringify(parentQuestion));
let mutate = JSON.parse(JSON.stringify(parentQuestion)) mutate.content.rule.default =
mutate.content.rule.default = parentQuestion.content.rule.default === targetQuestion.content.id ? "" : targetQuestion.content.id parentQuestion.content.rule.default === targetQuestion.content.id
setParentQuestion(mutate) ? ""
}} : targetQuestion.content.id;
/>} label="Следующий вопрос по-умолчанию" /> setParentQuestion(mutate);
}}
/>
}
label="Следующий вопрос по-умолчанию"
/>
</Box> </Box>
<Box sx={{ display: "flex", justifyContent: "end", gap: "10px", margin: "20px" }}> <Box sx={{ display: "flex", justifyContent: "end", gap: "10px", margin: "20px" }}>
<Button <Button variant="outlined" onClick={handleClose} sx={{ width: "100%", maxWidth: "130px" }}>
variant="outlined"
onClick={handleClose}
sx={{ width: "100%", maxWidth: "130px" }}
>
Отмена Отмена
</Button> </Button>
<Button <Button variant="contained" sx={{ width: "100%", maxWidth: "130px" }} onClick={saveData}>
variant="contained"
sx={{ width: "100%", maxWidth: "130px" }}
onClick={saveData}
>
Готово Готово
</Button> </Button>
</Box> </Box>

@ -18,18 +18,31 @@ import RatingIcon from "@icons/questionsPage/rating";
import Slider from "@icons/questionsPage/slider"; import Slider from "@icons/questionsPage/slider";
import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import { import {
Box, Button, Box,
Checkbox, Button,
FormControl, Checkbox,
FormControlLabel, FormControl,
IconButton, FormControlLabel,
InputAdornment, Modal, IconButton,
Paper, InputAdornment,
TextField, Typography, Modal,
useMediaQuery, Paper,
useTheme, TextField,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material"; } from "@mui/material";
import { copyQuestion, createUntypedQuestion, deleteQuestion, clearRuleForAll, toggleExpandQuestion, updateQuestion, updateUntypedQuestion, getQuestionByContentId, deleteQuestionWithTimeout } from "@root/questions/actions"; import {
copyQuestion,
createUntypedQuestion,
deleteQuestion,
clearRuleForAll,
toggleExpandQuestion,
updateQuestion,
updateUntypedQuestion,
getQuestionByContentId,
deleteQuestionWithTimeout,
} from "@root/questions/actions";
import { updateRootContentId } from "@root/quizes/actions"; import { updateRootContentId } from "@root/quizes/actions";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
@ -44,493 +57,449 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
interface Props { interface Props {
question: AnyTypedQuizQuestion | UntypedQuizQuestion; question: AnyTypedQuizQuestion | UntypedQuizQuestion;
draggableProps: DraggableProvidedDragHandleProps | null | undefined; draggableProps: DraggableProvidedDragHandleProps | null | undefined;
isDragging: boolean; isDragging: boolean;
index: number; index: number;
} }
export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) { export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) {
const { questions } = useQuestionsStore(); const maxLengthTextField = 225;
const [plusVisible, setPlusVisible] = useState<boolean>(false);
const [open, setOpen] = useState<boolean>(false);
const [openDelete, setOpenDelete] = useState<boolean>(false);
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(790));
const anchorRef = useRef(null);
const quiz = useCurrentQuiz();
const setTitle = useDebouncedCallback((title) => { const { questions } = useQuestionsStore();
const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion; const [plusVisible, setPlusVisible] = useState<boolean>(false);
const [isTextFieldtActive, setIsTextFieldtActive] = useState(false);
const [open, setOpen] = useState<boolean>(false);
const [openDelete, setOpenDelete] = useState<boolean>(false);
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(790));
const anchorRef = useRef(null);
const quiz = useCurrentQuiz();
updateQuestionFn(question.id, question => { const setTitle = useDebouncedCallback((title) => {
question.title = title; const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion;
updateQuestionFn(question.id, (question) => {
question.title = title;
});
}, 200);
const deleteFn = () => {
if (question.type !== null) {
if (question.content.rule.parentId === "root") {
//удалить из стора root и очистить rule всем вопросам
updateRootContentId(quiz.id, "");
clearRuleForAll();
deleteQuestion(question.id);
questions.forEach((q) => {
if (q.type === "result") {
deleteQuestion(q.id);
}
}); });
}, 200); } else if (question.content.rule.parentId.length > 0) {
//удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[];
const deleteFn = () => { //записываем потомков , а их результаты удаляем
if (question.type !== null) { const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам questions.forEach((targetQuestion) => {
updateRootContentId(quiz.id, ""); if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {
clearRuleForAll(); //если у вопроса совпал родитель с родителем => он потомок, в кучу его
deleteQuestion(question.id); if (targetQuestion.type === "result") {
questions.forEach(q => { deleteQuestion(targetQuestion.id);
if (q.type === "result") { } else {
deleteQuestion(q.id); if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
} getChildren(targetQuestion); //и ищем его потомков
}); }
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
const clearQuestions = [] as string[];
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
getChildren(targetQuestion); //и ищем его потомков
}
}
});
};
getChildren(question);
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = "";
question.content.rule.main = [];
question.content.rule.default = "";
});
});
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId);
const newRule = {};
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId;
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId;
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule;
});
deleteQuestion(question.id);
} }
});
};
getChildren(question);
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, (question) => {
question.content.rule.parentId = "";
question.content.rule.main = [];
question.content.rule.default = "";
});
});
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId);
const newRule = {};
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId;
newRule.default =
parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId;
newRule.children = [...parentQuestion.content.rule.children].splice(
parentQuestion.content.rule.children.indexOf(question.content.id),
1
);
deleteQuestion(question.id); updateQuestion(question.content.rule.parentId, (PQ) => {
} else { PQ.content.rule = newRule;
console.log("удаляю безтипогово"); });
deleteQuestion(question.id); deleteQuestion(question.id);
} }
};
return ( deleteQuestion(question.id);
<> } else {
<Paper console.log("удаляю безтипогово");
id={question.id} deleteQuestion(question.id);
data-cy="quiz-question-card" }
sx={{ };
maxWidth: "796px",
width: "100%", const handleInputFocus = () => {
borderRadius: "12px", setIsTextFieldtActive(true);
backgroundColor: question.expanded ? "white" : "#EEE4FC", };
border: question.expanded ? "none" : "1px solid #9A9AAF",
boxShadow: "0px 10px 30px #e7e7e7", const handleInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {
}} setIsTextFieldtActive(false);
> };
<Box
sx={{ return (
display: "flex", <>
alignItems: "center", <Paper
padding: isMobile ? "10px" : "20px 10px 20px 20px", id={question.id}
flexDirection: isMobile ? "column" : null, data-cy="quiz-question-card"
}} sx={{
> maxWidth: "796px",
<FormControl width: "100%",
variant="standard" borderRadius: "12px",
sx={{ backgroundColor: question.expanded ? "white" : "#EEE4FC",
p: 0, border: question.expanded ? "none" : "1px solid #9A9AAF",
maxWidth: isTablet ? "549px" : "640px", boxShadow: "0px 10px 30px #e7e7e7",
width: "100%", }}
marginRight: isMobile ? "0px" : "16.1px", >
}} <Box
sx={{
display: "flex",
alignItems: "center",
padding: isMobile ? "10px" : "20px 10px 20px 20px",
flexDirection: isMobile ? "column" : null,
}}
>
<FormControl
variant="standard"
sx={{
p: 0,
maxWidth: isTablet ? "549px" : "640px",
width: "100%",
marginRight: isMobile ? "0px" : "16.1px",
}}
>
<TextField
defaultValue={question.title}
placeholder={"Заголовок вопроса"}
onChange={({ target }: { target: HTMLInputElement }) => setTitle(target.value || " ")}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
inputProps={{
maxLength: maxLengthTextField,
}}
InputProps={{
startAdornment: (
<Box>
<InputAdornment
ref={anchorRef}
position="start"
sx={{ cursor: "pointer" }}
onClick={() => setOpen((isOpened) => !isOpened)}
> >
<TextField {IconAndrom(question.expanded, question.type)}
defaultValue={question.title} </InputAdornment>
placeholder={"Заголовок вопроса"} <ChooseAnswerModal
onChange={({ target }: { target: HTMLInputElement; }) => setTitle(target.value || " ")} open={open}
InputProps={{ onClose={() => setOpen(false)}
startAdornment: ( anchorRef={anchorRef}
<Box> question={question}
<InputAdornment questionType={question.type}
ref={anchorRef}
position="start"
sx={{ cursor: "pointer" }}
onClick={() => setOpen((isOpened) => !isOpened)}
>
{IconAndrom(question.expanded, question.type)}
</InputAdornment>
<ChooseAnswerModal
open={open}
onClose={() => setOpen(false)}
anchorRef={anchorRef}
question={question}
questionType={question.type}
/>
</Box>
),
}}
sx={{
margin: isMobile ? "10px 0" : 0,
"& .MuiInputBase-root": {
color: "#000000",
backgroundColor: question.expanded
? theme.palette.background.default
: "transparent",
height: "48px",
borderRadius: "10px",
".MuiOutlinedInput-notchedOutline": {
borderWidth: "1px !important",
border: !question.expanded ? "none" : null,
},
"& .MuiInputBase-input::placeholder": {
color: "#4D4D4D",
opacity: 0.8,
},
},
}}
inputProps={{
sx: {
fontSize: "18px",
lineHeight: "21px",
py: 0,
paddingLeft: question.type === null ? 0 : "18px",
},
"data-cy": "quiz-question-title",
}}
/>
</FormControl>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
width: isMobile ? "100%" : "auto",
position: "relative",
}}
>
<IconButton
sx={{ padding: "0", margin: "5px" }}
disableRipple
data-cy="expand-question"
onClick={() => toggleExpandQuestion(question.id)}
>
{question.expanded ? (
<ArrowDownIcon
style={{
width: "18px",
color: "#4D4D4D",
}}
/>
) : (
<ExpandLessIcon
sx={{
boxSizing: "border-box",
fill: theme.palette.brightPurple.main,
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
}}
/>
)}
</IconButton>
{question.expanded ? (
<></>
) : (
<Box
sx={{
display: "flex",
height: "30px",
borderRight: "solid 1px #4D4D4D",
}}
>
<FormControlLabel
control={
<Checkbox
icon={
<HideIcon
style={{
boxSizing: "border-box",
color: "#7E2AEA",
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
padding: "3px",
}}
/>
}
checkedIcon={<CrossedEyeIcon />}
/>
}
label={""}
sx={{
color: theme.palette.grey2.main,
ml: "-9px",
mr: 0,
userSelect: "none",
}}
/>
<IconButton
sx={{ padding: "0" }}
onClick={() => copyQuestion(question.id, question.quizId)}
>
<CopyIcon
style={{ color: theme.palette.brightPurple.main }}
/>
</IconButton>
<IconButton
sx={{
cursor: "pointer",
borderRadius: "6px",
padding: "0",
margin: "0 5px 0 10px",
}}
onClick={() => {
if(question.content.rule.parentId.length !== 0) {
setOpenDelete(true)
} else {
deleteQuestionWithTimeout(question.id, deleteFn);
}
}}
data-cy="delete-question"
>
<DeleteIcon
style={{ color: theme.palette.brightPurple.main }}
/>
</IconButton>
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
padding: "30px",
borderRadius: "10px",
background: "#FFFFFF",
}}
>
<Typography variant="h6" sx={{textAlign: "center"}}>
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы уверены, что хотите удалить вопрос?
</Typography>
<Box
sx={{
marginTop: "30px",
display: "flex",
justifyContent: "center",
gap: "15px",
}}
>
<Button
variant="contained"
sx={{ minWidth: "150px" }}
onClick={() => setOpenDelete(false)}
>
Отмена
</Button>
<Button
variant="contained"
sx={{ minWidth: "150px" }}
onClick={() => {
deleteQuestionWithTimeout(question.id, deleteFn);
}}
>
Подтвердить
</Button>
</Box>
</Box>
</Modal>
</Box>
)}
{question.type !== null &&
<Box
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "30px",
width: "30px",
marginLeft: "3px",
borderRadius: "50%",
fontSize: "16px",
color: question.expanded
? theme.palette.brightPurple.main
: "#FFF",
background: question.expanded
? "#EEE4FC"
: theme.palette.brightPurple.main,
}}
>
{question.page + 1}
</Box>
}
<IconButton
disableRipple
sx={{
padding: isMobile ? "0" : "0 5px",
right: isMobile ? "0" : null,
bottom: isMobile ? "0" : null,
}}
{...draggableProps}
>
<PointsIcon style={{ color: "#4D4D4D", fontSize: "30px" }} />
</IconButton>
</Box>
</Box>
{question.expanded && (
<Box
sx={{
display: "flex",
flexDirection: "column",
padding: 0,
borderRadius: "12px",
}}
>
{question.type === null ? (
<TypeQuestions question={question} />
) : (
<SwitchQuestionsPage question={question} />
)}
</Box>
)}
</Paper>
<Box
onMouseEnter={() => setPlusVisible(true)}
onMouseLeave={() => setPlusVisible(false)}
sx={{
maxWidth: "825px",
display: "flex",
alignItems: "center",
height: "40px",
cursor: "pointer",
}}
>
<Box
onClick={() => createUntypedQuestion(question.quizId, question.id)}
sx={{
display: plusVisible && !isDragging ? "flex" : "none",
width: "100%",
alignItems: "center",
columnGap: "10px",
}}
data-cy="create-question"
>
<Box
sx={{
boxSizing: "border-box",
width: "100%",
height: "1px",
backgroundPosition: "bottom",
backgroundRepeat: "repeat-x",
backgroundSize: "20px 1px",
backgroundImage:
"radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)",
}}
/> />
<PlusIcon /> </Box>
</Box> ),
</Box> endAdornment: isTextFieldtActive && question.title.length >= maxLengthTextField - 7 && (
</> <Box
); sx={{
display: "flex",
marginTop: "5px",
marginLeft: "auto",
position: "absolute",
bottom: "-28px",
right: "0",
}}
>
<Typography fontSize="14px">{question.title.length}</Typography>
<span>/</span>
<Typography fontSize="14px">{maxLengthTextField}</Typography>
</Box>
),
}}
sx={{
margin: isMobile ? "10px 0" : 0,
"& .MuiInputBase-root": {
color: "#000000",
backgroundColor: question.expanded ? theme.palette.background.default : "transparent",
height: "48px",
borderRadius: "10px",
".MuiOutlinedInput-notchedOutline": {
borderWidth: "1px !important",
border: !question.expanded ? "none" : null,
},
"& .MuiInputBase-input::placeholder": {
color: "#4D4D4D",
opacity: 0.8,
},
},
}}
/>
</FormControl>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
width: isMobile ? "100%" : "auto",
position: "relative",
}}
>
<IconButton
sx={{ padding: "0", margin: "5px" }}
disableRipple
data-cy="expand-question"
onClick={() => toggleExpandQuestion(question.id)}
>
{question.expanded ? (
<ArrowDownIcon
style={{
width: "18px",
color: "#4D4D4D",
}}
/>
) : (
<ExpandLessIcon
sx={{
boxSizing: "border-box",
fill: theme.palette.brightPurple.main,
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
}}
/>
)}
</IconButton>
{question.expanded ? (
<></>
) : (
<Box
sx={{
display: "flex",
height: "30px",
borderRight: "solid 1px #4D4D4D",
}}
>
<FormControlLabel
control={
<Checkbox
icon={
<HideIcon
style={{
boxSizing: "border-box",
color: "#7E2AEA",
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
padding: "3px",
}}
/>
}
checkedIcon={<CrossedEyeIcon />}
/>
}
label={""}
sx={{
color: theme.palette.grey2.main,
ml: "-9px",
mr: 0,
userSelect: "none",
}}
/>
<IconButton sx={{ padding: "0" }} onClick={() => copyQuestion(question.id, question.quizId)}>
<CopyIcon style={{ color: theme.palette.brightPurple.main }} />
</IconButton>
<IconButton
sx={{
cursor: "pointer",
borderRadius: "6px",
padding: "0",
margin: "0 5px 0 10px",
}}
onClick={() => {
if (question.content.rule.parentId.length !== 0) {
setOpenDelete(true);
} else {
deleteQuestionWithTimeout(question.id, deleteFn);
}
}}
data-cy="delete-question"
>
<DeleteIcon style={{ color: theme.palette.brightPurple.main }} />
</IconButton>
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
padding: "30px",
borderRadius: "10px",
background: "#FFFFFF",
}}
>
<Typography variant="h6" sx={{ textAlign: "center" }}>
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы
уверены, что хотите удалить вопрос?
</Typography>
<Box
sx={{
marginTop: "30px",
display: "flex",
justifyContent: "center",
gap: "15px",
}}
>
<Button variant="contained" sx={{ minWidth: "150px" }} onClick={() => setOpenDelete(false)}>
Отмена
</Button>
<Button
variant="contained"
sx={{ minWidth: "150px" }}
onClick={() => {
deleteQuestionWithTimeout(question.id, deleteFn);
}}
>
Подтвердить
</Button>
</Box>
</Box>
</Modal>
</Box>
)}
{question.type !== null && (
<Box
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "30px",
width: "30px",
marginLeft: "3px",
borderRadius: "50%",
fontSize: "16px",
color: question.expanded ? theme.palette.brightPurple.main : "#FFF",
background: question.expanded ? "#EEE4FC" : theme.palette.brightPurple.main,
}}
>
{question.page + 1}
</Box>
)}
<IconButton
disableRipple
sx={{
padding: isMobile ? "0" : "0 5px",
right: isMobile ? "0" : null,
bottom: isMobile ? "0" : null,
}}
{...draggableProps}
>
<PointsIcon style={{ color: "#4D4D4D", fontSize: "30px" }} />
</IconButton>
</Box>
</Box>
{question.expanded && (
<Box
sx={{
display: "flex",
flexDirection: "column",
padding: 0,
borderRadius: "12px",
}}
>
{question.type === null ? (
<TypeQuestions question={question} />
) : (
<SwitchQuestionsPage question={question} />
)}
</Box>
)}
</Paper>
<Box
onMouseEnter={() => setPlusVisible(true)}
onMouseLeave={() => setPlusVisible(false)}
sx={{
maxWidth: "825px",
display: "flex",
alignItems: "center",
height: "40px",
cursor: "pointer",
}}
>
<Box
onClick={() => createUntypedQuestion(question.quizId, question.id)}
sx={{
display: plusVisible && !isDragging ? "flex" : "none",
width: "100%",
alignItems: "center",
columnGap: "10px",
}}
data-cy="create-question"
>
<Box
sx={{
boxSizing: "border-box",
width: "100%",
height: "1px",
backgroundPosition: "bottom",
backgroundRepeat: "repeat-x",
backgroundSize: "20px 1px",
backgroundImage: "radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)",
}}
/>
<PlusIcon />
</Box>
</Box>
</>
);
} }
const IconAndrom = (isExpanded: boolean, questionType: QuestionType | null) => { const IconAndrom = (isExpanded: boolean, questionType: QuestionType | null) => {
switch (questionType) { switch (questionType) {
case "variant": case "variant":
return ( return <Answer color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
<Answer case "images":
color={isExpanded ? "#9A9AAF" : "#7E2AEA"} return <OptionsPict color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
sx={{ height: "22px", width: "20px" }} case "varimg":
/> return <OptionsAndPict color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
); case "emoji":
case "images": return <Emoji color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
return ( case "text":
<OptionsPict return <Input color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
color={isExpanded ? "#9A9AAF" : "#7E2AEA"} case "select":
sx={{ height: "22px", width: "20px" }} return <DropDown color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
/> case "date":
); return <Date color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
case "varimg": case "number":
return ( return <Slider color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
<OptionsAndPict case "file":
color={isExpanded ? "#9A9AAF" : "#7E2AEA"} return <Download color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
sx={{ height: "22px", width: "20px" }} case "page":
/> return <Page color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
); case "rating":
case "emoji": return <RatingIcon color={isExpanded ? "#9A9AAF" : "#7E2AEA"} sx={{ height: "22px", width: "20px" }} />;
return ( default:
<Emoji return <></>;
color={isExpanded ? "#9A9AAF" : "#7E2AEA"} }
sx={{ height: "22px", width: "20px" }}
/>
);
case "text":
return (
<Input
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "select":
return (
<DropDown
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "date":
return (
<Date
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "number":
return (
<Slider
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "file":
return (
<Download
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "page":
return (
<Page
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "rating":
return (
<RatingIcon
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
default:
return <></>;
}
}; };

@ -4,22 +4,23 @@ import { AnswerDraggableList } from "../AnswerDraggableList";
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon"; import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
import SwitchDropDown from "./switchDropDown"; import SwitchDropDown from "./switchDropDown";
import ButtonsOptions from "../ButtonsOptions"; import ButtonsOptions from "../ButtonsOptions";
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
import { addQuestionVariant } from "@root/questions/actions"; import { addQuestionVariant } from "@root/questions/actions";
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
interface Props { interface Props {
question: QuizQuestionSelect; question: QuizQuestionSelect;
} }
export default function DropDown({ question }: Props) { export default function DropDown({ question }: Props) {
const onClickAddAnAnswer = useAddAnswer();
const [switchState, setSwitchState] = useState("setting"); const [switchState, setSwitchState] = useState("setting");
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const SSHC = (data: string) => { const SSHC = (data: string) => {
setSwitchState(data); setSwitchState(data);
}; };
return ( return (
<> <>
@ -60,7 +61,7 @@ export default function DropDown({ question }: Props) {
mr: "4px", mr: "4px",
height: "19px", height: "19px",
}} }}
onClick={() => addQuestionVariant(question.id)} onClick={() => onClickAddAnAnswer(question)}
> >
Добавьте ответ Добавьте ответ
</Link> </Link>
@ -87,11 +88,7 @@ export default function DropDown({ question }: Props) {
)} )}
</Box> </Box>
</Box> </Box>
<ButtonsOptions <ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
switchState={switchState}
SSHC={SSHC}
question={question}
/>
<SwitchDropDown switchState={switchState} question={question} /> <SwitchDropDown switchState={switchState} question={question} />
</> </>
); );

@ -1,14 +1,7 @@
import { EmojiIcons } from "@icons/EmojiIocns"; import { EmojiIcons } from "@icons/EmojiIocns";
import AddEmoji from "@icons/questionsPage/addEmoji"; import AddEmoji from "@icons/questionsPage/addEmoji";
import PlusImage from "@icons/questionsPage/plus"; import PlusImage from "@icons/questionsPage/plus";
import { import { Box, Link, Popover, Typography, useMediaQuery, useTheme } from "@mui/material";
Box,
Link,
Popover,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { addQuestionVariant, updateQuestion } from "@root/questions/actions"; import { addQuestionVariant, updateQuestion } from "@root/questions/actions";
import { EmojiPicker } from "@ui_kit/EmojiPicker"; import { EmojiPicker } from "@ui_kit/EmojiPicker";
import { useState } from "react"; import { useState } from "react";
@ -17,225 +10,220 @@ import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
import { AnswerDraggableList } from "../AnswerDraggableList"; import { AnswerDraggableList } from "../AnswerDraggableList";
import ButtonsOptions from "../ButtonsOptions"; import ButtonsOptions from "../ButtonsOptions";
import SwitchEmoji from "./switchEmoji"; import SwitchEmoji from "./switchEmoji";
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
interface Props { interface Props {
question: QuizQuestionEmoji; question: QuizQuestionEmoji;
} }
export default function Emoji({ question }: Props) { export default function Emoji({ question }: Props) {
const [switchState, setSwitchState] = useState<string>("setting"); const [switchState, setSwitchState] = useState<string>("setting");
const [open, setOpen] = useState<boolean>(false); const onClickAddAnAnswer = useAddAnswer();
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>( const [open, setOpen] = useState<boolean>(false);
null const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(null);
); const [selectedVariant, setSelectedVariant] = useState<string | null>(null);
const [selectedVariant, setSelectedVariant] = useState<string | null>(null); const theme = useTheme();
const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const SSHC = (data: string) => { const SSHC = (data: string) => {
setSwitchState(data); setSwitchState(data);
}; };
return ( return (
<> <>
<Box sx={{ padding: "20px" }}> <Box sx={{ padding: "20px" }}>
<AnswerDraggableList <AnswerDraggableList
question={question} question={question}
additionalContent={(variant) => ( additionalContent={(variant) => (
<> <>
{!isTablet && ( {!isTablet && (
<Box sx={{ cursor: "pointer" }}> <Box sx={{ cursor: "pointer" }}>
<Box <Box
data-cy="choose-emoji-button" data-cy="choose-emoji-button"
onClick={({ currentTarget }) => { onClick={({ currentTarget }) => {
setAnchorElement(currentTarget); setAnchorElement(currentTarget);
setSelectedVariant(variant.id); setSelectedVariant(variant.id);
setOpen(true); setOpen(true);
}}
>
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: "5px",
}}
>
{variant.extendedText ? (
<Box
sx={{
height: "40px",
width: "60px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
background: "#EEE4FC",
borderRadius: "3px",
}}
>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
>
{variant.extendedText}
</Box>
<Box>
<PlusImage />
</Box>
</Box>
) : (
<AddEmoji />
)}
</Box>
</Box>
</Box>
)}
</>
)}
additionalMobile={(variant) => (
<>
{isTablet && (
<Box
onClick={({ currentTarget }) => {
setAnchorElement(currentTarget);
setSelectedVariant(variant.id);
setOpen(true);
}}
sx={{
display: "flex",
alignItems: "center",
m: "8px",
position: "relative",
}}
>
<Box
sx={{
width: "100%",
background: "#EEE4FC",
height: "40px",
}}
/>
{variant.extendedText ? (
<Box
sx={{
position: "absolute",
color: "#7E2AEA",
fontSize: "20px",
left: "45%",
right: "55%",
}}
>
{variant.extendedText}
</Box>
) : (
<EmojiIcons
style={{
position: "absolute",
color: "#7E2AEA",
fontSize: "20px",
left: "45%",
right: "55%",
}}
/>
)}
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "20px",
background: "#EEE4FC",
height: "40px",
color: "white",
backgroundColor: "#7E2AEA",
}}
>
+
</Box>
</Box>
)}
</>
)}
/>
<Popover
open={open}
anchorEl={anchorElement}
onClick={(event) => event.stopPropagation()}
onClose={() => setOpen(false)}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}} }}
sx={{ >
".MuiPaper-root.MuiPaper-rounded": { <Box
borderRadius: "10px", sx={{
},
}}
>
<EmojiPicker
onEmojiSelect={({ native }) => {
setOpen(false);
updateQuestion(question.id, question => {
if (question.type !== "emoji") return;
const variant = question.content.variants.find(v => v.id === selectedVariant);
if (!variant) return;
variant.extendedText = native;
});
}}
/>
</Popover>
<Box
sx={{
display: "flex", display: "flex",
justifyContent: "center",
alignItems: "center", alignItems: "center",
gap: "10px", gap: "5px",
marginBottom: isMobile ? "17px" : "20px", }}
}}
>
<Link
component="button"
variant="body2"
sx={{ color: theme.palette.brightPurple.main }}
onClick={() => addQuestionVariant(question.id)}
> >
Добавьте ответ {variant.extendedText ? (
</Link> <Box
{!isTablet && ( sx={{
<> height: "40px",
<Typography width: "60px",
sx={{ display: "flex",
fontWeight: 400, alignItems: "center",
lineHeight: "21.33px", justifyContent: "space-between",
color: theme.palette.grey2.main, background: "#EEE4FC",
fontSize: "16px", borderRadius: "3px",
}} }}
> >
или нажмите Enter <Box
</Typography> sx={{
<EnterIcon width: "100%",
style={{ display: "flex",
color: "#7E2AEA", justifyContent: "center",
fontSize: "24px", }}
marginLeft: "6px", >
}} {variant.extendedText}
/> </Box>
</> <Box>
)} <PlusImage />
</Box>
</Box>
) : (
<AddEmoji />
)}
</Box>
</Box>
</Box> </Box>
</Box> )}
<ButtonsOptions </>
switchState={switchState} )}
SSHC={SSHC} additionalMobile={(variant) => (
question={question} <>
/> {isTablet && (
<SwitchEmoji switchState={switchState} question={question} /> <Box
</> onClick={({ currentTarget }) => {
); setAnchorElement(currentTarget);
setSelectedVariant(variant.id);
setOpen(true);
}}
sx={{
display: "flex",
alignItems: "center",
m: "8px",
position: "relative",
}}
>
<Box
sx={{
width: "100%",
background: "#EEE4FC",
height: "40px",
}}
/>
{variant.extendedText ? (
<Box
sx={{
position: "absolute",
color: "#7E2AEA",
fontSize: "20px",
left: "45%",
right: "55%",
}}
>
{variant.extendedText}
</Box>
) : (
<EmojiIcons
style={{
position: "absolute",
color: "#7E2AEA",
fontSize: "20px",
left: "45%",
right: "55%",
}}
/>
)}
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "20px",
background: "#EEE4FC",
height: "40px",
color: "white",
backgroundColor: "#7E2AEA",
}}
>
+
</Box>
</Box>
)}
</>
)}
/>
<Popover
open={open}
anchorEl={anchorElement}
onClick={(event) => event.stopPropagation()}
onClose={() => setOpen(false)}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
sx={{
".MuiPaper-root.MuiPaper-rounded": {
borderRadius: "10px",
},
}}
>
<EmojiPicker
onEmojiSelect={({ native }) => {
setOpen(false);
updateQuestion(question.id, (question) => {
if (question.type !== "emoji") return;
const variant = question.content.variants.find((v) => v.id === selectedVariant);
if (!variant) return;
variant.extendedText = native;
});
}}
/>
</Popover>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
marginBottom: isMobile ? "17px" : "20px",
}}
>
<Link
component="button"
variant="body2"
sx={{ color: theme.palette.brightPurple.main }}
onClick={() => onClickAddAnAnswer(question)}
>
Добавьте ответ
</Link>
{!isTablet && (
<>
<Typography
sx={{
fontWeight: 400,
lineHeight: "21.33px",
color: theme.palette.grey2.main,
fontSize: "16px",
}}
>
или нажмите Enter
</Typography>
<EnterIcon
style={{
color: "#7E2AEA",
fontSize: "24px",
marginLeft: "6px",
}}
/>
</>
)}
</Box>
</Box>
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
<SwitchEmoji switchState={switchState} question={question} />
</>
);
} }

@ -1,10 +1,4 @@
import { import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
Box,
Link,
Typography,
useMediaQuery,
useTheme
} from "@mui/material";
import { addQuestionVariant, uploadQuestionImage } from "@root/questions/actions"; import { addQuestionVariant, uploadQuestionImage } from "@root/questions/actions";
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
@ -17,165 +11,153 @@ import { UploadImageModal } from "../UploadImage/UploadImageModal";
import SwitchAnswerOptionsPict from "./switchOptionsPict"; import SwitchAnswerOptionsPict from "./switchOptionsPict";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { useDisclosure } from "../../../utils/useDisclosure"; import { useDisclosure } from "../../../utils/useDisclosure";
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
interface Props { interface Props {
question: QuizQuestionImages; question: QuizQuestionImages;
} }
export default function OptionsPicture({ question }: Props) { export default function OptionsPicture({ question }: Props) {
const theme = useTheme(); const theme = useTheme();
const quizQid = useCurrentQuiz()?.qid; const onClickAddAnAnswer = useAddAnswer();
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null); const quizQid = useCurrentQuiz()?.qid;
const [switchState, setSwitchState] = useState("setting"); const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const [switchState, setSwitchState] = useState("setting");
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const { const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
isCropModalOpen, const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
openCropModal, useCropModalState();
closeCropModal,
imageBlob,
originalImageUrl,
setCropModalImageBlob,
} = useCropModalState();
const SSHC = (data: string) => { const SSHC = (data: string) => {
setSwitchState(data); setSwitchState(data);
}; };
const handleImageUpload = async (file: File) => { const handleImageUpload = async (file: File) => {
if (!selectedVariantId) return; if (!selectedVariantId) return;
const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => {
if (!("variants" in question.content)) return; if (!("variants" in question.content)) return;
const variant = question.content.variants.find(variant => variant.id === selectedVariantId); const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
if (!variant) return; if (!variant) return;
variant.extendedText = url; variant.extendedText = url;
variant.originalImageUrl = url; variant.originalImageUrl = url;
}); });
closeImageUploadModal(); closeImageUploadModal();
openCropModal(file, url); openCropModal(file, url);
}; };
function handleCropModalSaveClick(imageBlob: Blob) { function handleCropModalSaveClick(imageBlob: Blob) {
if (!selectedVariantId) return; if (!selectedVariantId) return;
uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => {
if (!("variants" in question.content)) return; if (!("variants" in question.content)) return;
const variant = question.content.variants.find(variant => variant.id === selectedVariantId); const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
if (!variant) return; if (!variant) return;
variant.extendedText = url; variant.extendedText = url;
}); });
} }
return ( return (
<> <>
<Box sx={{ padding: "20px" }}> <Box sx={{ padding: "20px" }}>
<AnswerDraggableList <AnswerDraggableList
question={question} question={question}
additionalContent={(variant) => ( additionalContent={(variant) => (
<> <>
{!isMobile && ( {!isMobile && (
<AddOrEditImageButton <AddOrEditImageButton
imageSrc={variant.extendedText} imageSrc={variant.extendedText}
onImageClick={() => { onImageClick={() => {
setSelectedVariantId(variant.id); setSelectedVariantId(variant.id);
if (variant.extendedText) { if (variant.extendedText) {
return openCropModal( return openCropModal(variant.extendedText, variant.originalImageUrl);
variant.extendedText, }
variant.originalImageUrl
);
}
openImageUploadModal(); openImageUploadModal();
}} }}
onPlusClick={() => { onPlusClick={() => {
setSelectedVariantId(variant.id); setSelectedVariantId(variant.id);
openImageUploadModal(); openImageUploadModal();
}} }}
sx={{ mx: "10px" }} sx={{ mx: "10px" }}
/>
)}
</>
)}
additionalMobile={(variant) => (
<>
{isMobile && (
<AddOrEditImageButton
imageSrc={variant.extendedText}
onImageClick={() => {
setSelectedVariantId(variant.id);
if (variant.extendedText) {
return openCropModal(
variant.extendedText,
variant.originalImageUrl
);
}
openImageUploadModal();
}}
onPlusClick={() => {
setSelectedVariantId(variant.id);
openImageUploadModal();
}}
sx={{ m: "8px", width: "auto" }}
/>
)}
</>
)}
/> />
<UploadImageModal )}
isOpen={isImageUploadOpen} </>
onClose={closeImageUploadModal} )}
handleImageChange={handleImageUpload} additionalMobile={(variant) => (
/> <>
<CropModal {isMobile && (
isOpen={isCropModalOpen} <AddOrEditImageButton
imageBlob={imageBlob} imageSrc={variant.extendedText}
originalImageUrl={originalImageUrl} onImageClick={() => {
setCropModalImageBlob={setCropModalImageBlob} setSelectedVariantId(variant.id);
onClose={closeCropModal} if (variant.extendedText) {
onSaveImageClick={handleCropModalSaveClick} return openCropModal(variant.extendedText, variant.originalImageUrl);
/> }
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
<Link
component="button"
variant="body2"
sx={{ color: theme.palette.brightPurple.main }}
onClick={() => addQuestionVariant(question.id)}
>
Добавьте ответ
</Link>
{isMobile ? null : (
<>
<Typography
sx={{
fontWeight: 400,
lineHeight: "21.33px",
color: theme.palette.grey2.main,
fontSize: "16px",
}}
>
или нажмите Enter
</Typography>
<EnterIcon
style={{
color: "#7E2AEA",
fontSize: "24px",
marginLeft: "6px",
}}
/>
</>
)}
</Box>
</Box>
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
<SwitchAnswerOptionsPict switchState={switchState} question={question} />
</>
); openImageUploadModal();
}}
onPlusClick={() => {
setSelectedVariantId(variant.id);
openImageUploadModal();
}}
sx={{ m: "8px", width: "auto" }}
/>
)}
</>
)}
/>
<UploadImageModal
isOpen={isImageUploadOpen}
onClose={closeImageUploadModal}
handleImageChange={handleImageUpload}
/>
<CropModal
isOpen={isCropModalOpen}
imageBlob={imageBlob}
originalImageUrl={originalImageUrl}
setCropModalImageBlob={setCropModalImageBlob}
onClose={closeCropModal}
onSaveImageClick={handleCropModalSaveClick}
/>
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
<Link
component="button"
variant="body2"
sx={{ color: theme.palette.brightPurple.main }}
onClick={() => onClickAddAnAnswer(question)}
>
Добавьте ответ
</Link>
{isMobile ? null : (
<>
<Typography
sx={{
fontWeight: 400,
lineHeight: "21.33px",
color: theme.palette.grey2.main,
fontSize: "16px",
}}
>
или нажмите Enter
</Typography>
<EnterIcon
style={{
color: "#7E2AEA",
fontSize: "24px",
marginLeft: "6px",
}}
/>
</>
)}
</Box>
</Box>
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
<SwitchAnswerOptionsPict switchState={switchState} question={question} />
</>
);
} }

@ -1,5 +1,5 @@
import { VideofileIcon } from "@icons/questionsPage/VideofileIcon"; import { VideofileIcon } from "@icons/questionsPage/VideofileIcon";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material";
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions"; import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
@ -14,253 +14,252 @@ import { UploadVideoModal } from "../UploadVideoModal";
import SwitchPageOptions from "./switchPageOptions"; import SwitchPageOptions from "./switchPageOptions";
import { useDisclosure } from "../../../utils/useDisclosure"; import { useDisclosure } from "../../../utils/useDisclosure";
type Props = { type Props = {
disableInput?: boolean; disableInput?: boolean;
question: QuizQuestionPage; question: QuizQuestionPage;
}; };
export default function PageOptions({ disableInput, question }: Props) { export default function PageOptions({ disableInput, question }: Props) {
const [openVideoModal, setOpenVideoModal] = useState<boolean>(false); const [openVideoModal, setOpenVideoModal] = useState<boolean>(false);
const [switchState, setSwitchState] = useState("setting"); const [switchState, setSwitchState] = useState("setting");
const theme = useTheme(); const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(980)); const isTablet = useMediaQuery(theme.breakpoints.down(980));
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(780)); const isMobile = useMediaQuery(theme.breakpoints.down(780));
const quizQid = useCurrentQuiz()?.qid; const quizQid = useCurrentQuiz()?.qid;
const { const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
isCropModalOpen, useCropModalState();
openCropModal, const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
closeCropModal,
imageBlob,
originalImageUrl,
setCropModalImageBlob,
} = useCropModalState();
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
const setText = useDebouncedCallback((value) => { const setText = useDebouncedCallback((value) => {
updateQuestion(question.id, question => { updateQuestion(question.id, (question) => {
if (question.type !== "page") return; if (question.type !== "page") return;
question.content.text = value; question.content.text = value;
}); });
}, 200); }, 200);
const SSHC = (data: string) => { const SSHC = (data: string) => {
setSwitchState(data); setSwitchState(data);
}; };
async function handleImageUpload(file: File) { async function handleImageUpload(file: File) {
const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => {
if (question.type !== "page") return; if (question.type !== "page") return;
question.content.picture = url; question.content.picture = url;
question.content.originalPicture = url; question.content.originalPicture = url;
}); });
closeImageUploadModal(); closeImageUploadModal();
openCropModal(file, url); openCropModal(file, url);
} }
function handleCropModalSaveClick(imageBlob: Blob) { function handleCropModalSaveClick(imageBlob: Blob) {
uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => {
if (question.type !== "page") return; if (question.type !== "page") return;
question.content.picture = url; question.content.picture = url;
}); });
} }
return ( console.log(question.content.useImage);
<>
<Box return (
sx={{ <>
width: isTablet ? "auto" : "100%", <Box
maxWidth: isFigmaTablet ? "549px" : "640px", sx={{
display: "flex", width: isTablet ? "auto" : "100%",
px: "20px", maxWidth: isFigmaTablet ? "549px" : "640px",
flexDirection: "column", display: "flex",
gap: isMobile ? "25px" : "20px", px: "20px",
}} flexDirection: "column",
gap: isMobile ? "25px" : "20px",
}}
>
<Box sx={{ display: disableInput ? "none" : "", mt: isMobile ? "15px" : "0px" }}>
<CustomTextField
placeholder={"Можно добавить текст"}
text={question.content.text}
onChange={({ target }) => setText(target.value)}
/>
</Box>
<Box
sx={{
mb: "20px",
ml: isTablet ? "0px" : "60px",
display: "flex",
alignItems: "center",
gap: "28px",
justifyContent: isMobile ? "space-between" : null,
}}
>
<Box
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: "20px",
}}
>
<AddOrEditImageButton
imageSrc={question.content.picture}
onImageClick={() => {
if (question.content.picture) {
return openCropModal(question.content.picture, question.content.originalPicture);
}
openImageUploadModal();
}}
onPlusClick={() => {
openImageUploadModal();
}}
/>
<Typography
sx={{
display: isMobile ? "none" : "block",
fontWeight: 400,
fontSize: "16px",
lineHeight: "18.96px",
color: question.content.useImage ? "#7E2AEA" : "#9A9AAF",
}}
onClick={() =>
updateQuestion(question.id, (question) => ((question as QuizQuestionPage).content.useImage = true))
}
> >
<Box sx={{ display: disableInput ? "none" : "", mt: isMobile ? "15px" : "0px" }}> Изображение
<CustomTextField </Typography>
placeholder={"Можно добавить текст"} </Box>
text={question.content.text} <UploadImageModal
onChange={({ target }) => setText(target.value)} isOpen={isImageUploadOpen}
/> onClose={closeImageUploadModal}
</Box> handleImageChange={handleImageUpload}
/>
<CropModal
isOpen={isCropModalOpen}
imageBlob={imageBlob}
originalImageUrl={originalImageUrl}
setCropModalImageBlob={setCropModalImageBlob}
onClose={closeCropModal}
onSaveImageClick={handleCropModalSaveClick}
/>
<Typography> или</Typography>
<Box
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
{isMobile ? (
<Box
sx={{
display: "flex",
alignItems: "center",
width: "120px",
position: "relative",
}}
>
<Box <Box
sx={{ sx={{
mb: "20px", width: "100%",
ml: isTablet ? "0px" : "60px", background: "#EEE4FC",
display: "flex", height: "40px",
alignItems: "center", display: "flex",
gap: "28px", alignItems: "center",
justifyContent: isMobile ? "space-between" : null, justifyContent: "center",
}} borderTopLeftRadius: "4px",
borderBottomLeftRadius: "4px",
}}
> >
<Box <VideofileIcon
sx={{ style={{
cursor: "pointer", color: "#7E2AEA",
display: "flex", fontSize: "20px",
alignItems: "center", }}
gap: "20px", />
}}
>
<AddOrEditImageButton
imageSrc={question.content.picture}
onImageClick={() => {
if (question.content.picture) {
return openCropModal(
question.content.picture,
question.content.originalPicture
);
}
openImageUploadModal();
}}
onPlusClick={() => {
openImageUploadModal();
}}
/>
<Typography
sx={{
display: isMobile ? "none" : "block",
fontWeight: 400,
fontSize: "16px",
lineHeight: "18.96px",
color: theme.palette.grey2.main,
}}
>
Изображение
</Typography>
</Box>
<UploadImageModal
isOpen={isImageUploadOpen}
onClose={closeImageUploadModal}
handleImageChange={handleImageUpload}
/>
<CropModal
isOpen={isCropModalOpen}
imageBlob={imageBlob}
originalImageUrl={originalImageUrl}
setCropModalImageBlob={setCropModalImageBlob}
onClose={closeCropModal}
onSaveImageClick={handleCropModalSaveClick}
/>
<Typography> или</Typography>
<Box
sx={{
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
{isMobile ? (
<Box
sx={{
display: "flex",
alignItems: "center",
width: "120px",
position: "relative",
}}
>
<Box
sx={{
width: "100%",
background: "#EEE4FC",
height: "40px",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderTopLeftRadius: "4px",
borderBottomLeftRadius: "4px",
}}
>
<VideofileIcon
style={{
color: "#7E2AEA",
fontSize: "20px",
}}
/>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "20px",
background: "#EEE4FC",
height: "40px",
color: "white",
backgroundColor: "#7E2AEA",
borderTopRightRadius: "4px",
borderBottomRightRadius: "4px",
}}
>
+
</Box>
</Box>
) : (
<Box
sx={{
width: "60px",
height: "40px",
background: "#EEE4FC",
display: "flex",
justifyContent: "space-between",
}}
>
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "center", width: "100%" }}>
<VideofileIcon fontSize="22px" color="#7E2AEA" />
</Box>
<span
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#7E2AEA",
height: "100%",
width: "25px",
color: "white",
fontSize: "15px",
}}
>
+
</span>
</Box>
)}
<Typography
sx={{
display: isMobile ? "none" : "block",
fontWeight: 400,
fontSize: "16px",
lineHeight: "18.96px",
color: theme.palette.grey2.main,
}}
>
Видео
</Typography>
</Box>
<UploadVideoModal
open={openVideoModal}
onClose={() => setOpenVideoModal(false)}
video={question.content.video}
onUpload={(url) => {
updateQuestion(question.id, question => {
if (question.type !== "page") return;
question.content.video = url;
});
}}
/>
</Box> </Box>
</Box> <Box
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} /> sx={{
<SwitchPageOptions switchState={switchState} question={question} /> display: "flex",
</> justifyContent: "center",
); alignItems: "center",
width: "20px",
background: "#EEE4FC",
height: "40px",
color: "white",
backgroundColor: "#7E2AEA",
borderTopRightRadius: "4px",
borderBottomRightRadius: "4px",
}}
>
+
</Box>
</Box>
) : (
<Box
sx={{
width: "60px",
height: "40px",
background: "#EEE4FC",
display: "flex",
justifyContent: "space-between",
}}
>
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "center", width: "100%" }}>
<VideofileIcon fontSize="22px" color="#7E2AEA" />
</Box>
<span
onClick={() => setOpenVideoModal(true)}
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#7E2AEA",
height: "100%",
width: "25px",
color: "white",
fontSize: "15px",
}}
>
+
</span>
</Box>
)}
<Typography
sx={{
display: isMobile ? "none" : "block",
fontWeight: 400,
fontSize: "16px",
lineHeight: "18.96px",
color: question.content.useImage ? "#9A9AAF" : "#7E2AEA",
}}
onClick={() =>
updateQuestion(question.id, (question) => ((question as QuizQuestionPage).content.useImage = false))
}
>
Видео
</Typography>
</Box>
<UploadVideoModal
open={openVideoModal}
onClose={() => setOpenVideoModal(false)}
video={question.content.video}
onUpload={(url) => {
updateQuestion(question.id, (question) => {
if (question.type !== "page") return;
question.content.video = url;
});
}}
/>
</Box>
</Box>
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
<SwitchPageOptions switchState={switchState} question={question} />
</>
);
} }

@ -1,11 +1,4 @@
import { import { Box, Button, ButtonBase, Modal, Typography, useTheme } from "@mui/material";
Box,
Button,
ButtonBase,
Modal,
Typography,
useTheme,
} from "@mui/material";
import SelectableButton from "@ui_kit/SelectableButton"; import SelectableButton from "@ui_kit/SelectableButton";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useState } from "react"; import { useState } from "react";
@ -22,14 +15,8 @@ type HelpQuestionsProps = {
onUpload: (number: string) => void; onUpload: (number: string) => void;
}; };
export const UploadVideoModal = ({ export const UploadVideoModal = ({ open, onClose, video, onUpload }: HelpQuestionsProps) => {
open, const [backgroundTypeModal, setBackgroundTypeModal] = useState<BackgroundTypeModal>("linkVideo");
onClose,
video,
onUpload,
}: HelpQuestionsProps) => {
const [backgroundTypeModal, setBackgroundTypeModal] =
useState<BackgroundTypeModal>("linkVideo");
const theme = useTheme(); const theme = useTheme();
const handleDrop = (event: DragEvent<HTMLDivElement>) => { const handleDrop = (event: DragEvent<HTMLDivElement>) => {
@ -42,12 +29,7 @@ export const UploadVideoModal = ({
}; };
return ( return (
<Modal <Modal open={open} onClose={onClose} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description">
open={open}
onClose={onClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box <Box
sx={{ sx={{
position: "absolute", position: "absolute",
@ -70,10 +52,11 @@ export const UploadVideoModal = ({
}} }}
> >
<Typography sx={{ color: "#9A9AAF" }}> <Typography sx={{ color: "#9A9AAF" }}>
Видео можно вставить с любого хостинга: YouTube, Vimeo или загрузить Видео можно вставить с любого хостинга: YouTube, Vimeo или загрузить собственное
собственное
</Typography> </Typography>
<Button variant="contained">Готово</Button> <Button onClick={onClose} variant="contained">
Готово
</Button>
</Box> </Box>
<Box sx={{ padding: "20px", gap: "10px", display: "flex" }}> <Box sx={{ padding: "20px", gap: "10px", display: "flex" }}>
<SelectableButton <SelectableButton
@ -93,9 +76,7 @@ export const UploadVideoModal = ({
</Box> </Box>
{backgroundTypeModal === "linkVideo" ? ( {backgroundTypeModal === "linkVideo" ? (
<Box sx={{ padding: "20px" }}> <Box sx={{ padding: "20px" }}>
<Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}> <Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}>Ссылка на видео</Typography>
Ссылка на видео
</Typography>
<CustomTextField <CustomTextField
placeholder={"http://example.com"} placeholder={"http://example.com"}
text={video} text={video}
@ -104,13 +85,8 @@ export const UploadVideoModal = ({
</Box> </Box>
) : ( ) : (
<Box sx={{ padding: "20px" }}> <Box sx={{ padding: "20px" }}>
<Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}> <Typography sx={{ paddingBottom: "15px", fontWeight: "500" }}>Загрузите видео</Typography>
Загрузите видео <ButtonBase component="label" sx={{ justifyContent: "flex-start", width: "100%" }}>
</Typography>
<ButtonBase
component="label"
sx={{ justifyContent: "flex-start", width: "100%" }}
>
<input <input
onChange={({ target }) => { onChange={({ target }) => {
if (target.files?.length) { if (target.files?.length) {
@ -123,9 +99,7 @@ export const UploadVideoModal = ({
type="file" type="file"
/> />
<Box <Box
onDragOver={(event: DragEvent<HTMLDivElement>) => onDragOver={(event: DragEvent<HTMLDivElement>) => event.preventDefault()}
event.preventDefault()
}
onDrop={handleDrop} onDrop={handleDrop}
sx={{ sx={{
width: "580px", width: "580px",
@ -140,12 +114,8 @@ export const UploadVideoModal = ({
> >
<UploadIcon /> <UploadIcon />
<Box sx={{ color: "#9A9AAF" }}> <Box sx={{ color: "#9A9AAF" }}>
<Typography sx={{ fontWeight: "500" }}> <Typography sx={{ fontWeight: "500" }}>Добавить видео</Typography>
Добавить видео <Typography sx={{ fontSize: "16px" }}>Принимает .mp4 и .mov формат максимум 100мб</Typography>
</Typography>
<Typography sx={{ fontSize: "16px" }}>
Принимает .mp4 и .mov формат максимум 100мб
</Typography>
</Box> </Box>
</Box> </Box>
</ButtonBase> </ButtonBase>

@ -6,90 +6,87 @@ import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
import SwitchAnswerOptions from "./switchAnswerOptions"; import SwitchAnswerOptions from "./switchAnswerOptions";
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
import { addQuestionVariant } from "@root/questions/actions"; import { addQuestionVariant } from "@root/questions/actions";
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
interface Props { interface Props {
question: QuizQuestionVariant; question: QuizQuestionVariant;
} }
export default function AnswerOptions({ question }: Props) { export default function AnswerOptions({ question }: Props) {
const [switchState, setSwitchState] = useState("setting"); const onClickAddAnAnswer = useAddAnswer();
const theme = useTheme(); const [switchState, setSwitchState] = useState("setting");
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790));
const SSHC = (data: string) => { const SSHC = (data: string) => {
setSwitchState(data); setSwitchState(data);
}; };
return ( return (
<> <>
<Box sx={{ padding: "0 20px 20px 20px" }}> <Box sx={{ padding: "0 20px 20px 20px" }}>
{question.content.variants.length === 0 ? ( {question.content.variants.length === 0 ? (
<Typography <Typography
sx={{ sx={{
padding: "0 0 33px 80px", padding: "0 0 33px 80px",
fontWeight: 400, fontWeight: 400,
fontSize: "18px", fontSize: "18px",
lineHeight: "21.33px", lineHeight: "21.33px",
color: theme.palette.grey2.main, color: theme.palette.grey2.main,
}} }}
> >
Добавьте ответ Добавьте ответ
</Typography> </Typography>
) : ( ) : (
<AnswerDraggableList question={question} /> <AnswerDraggableList question={question} />
)} )}
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
marginBottom: "17px", marginBottom: "17px",
}} }}
> >
<Link <Link
component="button" component="button"
variant="body2" variant="body2"
sx={{ sx={{
color: theme.palette.brightPurple.main, color: theme.palette.brightPurple.main,
fontWeight: "400", fontWeight: "400",
fontSize: "16px", fontSize: "16px",
mr: "4px", mr: "4px",
height: "19px", height: "19px",
}} }}
onClick={() => addQuestionVariant(question.id)} onClick={() => onClickAddAnAnswer(question)}
> >
Добавьте ответ Добавьте ответ
</Link> </Link>
{isMobile ? null : ( {isMobile ? null : (
<> <>
<Typography <Typography
sx={{ sx={{
fontWeight: 400, fontWeight: 400,
lineHeight: "21.33px", lineHeight: "21.33px",
color: theme.palette.grey2.main, color: theme.palette.grey2.main,
fontSize: "16px", fontSize: "16px",
}} }}
> >
или нажмите Enter или нажмите Enter
</Typography> </Typography>
<EnterIcon <EnterIcon
style={{ style={{
color: "#7E2AEA", color: "#7E2AEA",
fontSize: "24px", fontSize: "24px",
marginLeft: "6px", marginLeft: "6px",
}} }}
/> />
</> </>
)} )}
</Box> </Box>
</Box> </Box>
<ButtonsOptionsAndPict <ButtonsOptionsAndPict switchState={switchState} SSHC={SSHC} question={question} />
switchState={switchState} <SwitchAnswerOptions switchState={switchState} question={question} />
SSHC={SSHC} </>
question={question} );
/>
<SwitchAnswerOptions switchState={switchState} question={question} />
</>
);
} }

@ -12,45 +12,48 @@ import { useEffect, useRef, useState } from "react";
import { WhenCard } from "./cards/WhenCard"; import { WhenCard } from "./cards/WhenCard";
import { ResultCard, checkEmptyData } from "./cards/ResultCard"; import { ResultCard, checkEmptyData } from "./cards/ResultCard";
import { EmailSettingsCard } from "./cards/EmailSettingsCard"; import { EmailSettingsCard } from "./cards/EmailSettingsCard";
import { useCurrentQuiz } from "@root/quizes/hooks" import { useCurrentQuiz } from "@root/quizes/hooks";
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { createFrontResult, deleteQuestion } from "@root/questions/actions"; import { createFrontResult, deleteQuestion } from "@root/questions/actions";
import { QuizQuestionResult } from "@model/questionTypes/result"; import { QuizQuestionResult } from "@model/questionTypes/result";
export const ResultSettings = () => { export const ResultSettings = () => {
const { questions } = useQuestionsStore() const { questions } = useQuestionsStore();
const quiz = useCurrentQuiz() const quiz = useCurrentQuiz();
const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result") const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result");
const [quizExpand, setQuizExpand] = useState(true) const [quizExpand, setQuizExpand] = useState(true);
const [resultContract, setResultContract] = useState(true) const [resultContract, setResultContract] = useState(true);
const isReadyToLeaveRef = useRef(true); const isReadyToLeaveRef = useRef(true);
useEffect(function calcIsReadyToLeave(){ useEffect(
let isReadyToLeave = true; function calcIsReadyToLeave() {
results.forEach((result) => { let isReadyToLeave = true;
if (checkEmptyData({ resultData: result })) { results.forEach((result) => {
isReadyToLeave = false; if (checkEmptyData({ resultData: result })) {
} isReadyToLeave = false;
}); }
isReadyToLeaveRef.current = isReadyToLeave; });
}, [results]) isReadyToLeaveRef.current = isReadyToLeave;
},
[results]
);
useEffect(() => { useEffect(() => {
return () => { return () => {
if (isReadyToLeaveRef.current === false) alert("Пожалуйста, проверьте, что вы заполнили все результаты"); if (isReadyToLeaveRef.current === false) alert("Пожалуйста, проверьте, что вы заполнили все результаты");
}; };
}, []); }, []);
return ( return (
<Box sx={{ maxWidth: "796px" }}> <Box sx={{ maxWidth: "796px" }}>
<Box sx={{ <Box
display: "flex", sx={{
alignItems: "center", display: "flex",
margin: "60px 0 40px 0", alignItems: "center",
}}> margin: "60px 0 40px 0",
<Typography variant="h5"> }}
Настройки результатов >
</Typography> <Typography variant="h5">Настройки результатов</Typography>
<Info /> <Info />
<Button <Button
disableRipple disableRipple
@ -73,14 +76,10 @@ export const ResultSettings = () => {
</Button> </Button>
</Box> </Box>
<WhenCard quizExpand={quizExpand} /> <WhenCard quizExpand={quizExpand} />
{quiz.config.resultInfo.when === "email" && <EmailSettingsCard quizExpand={quizExpand} />} {quiz.config.resultInfo.when === "email" && <EmailSettingsCard quizExpand={quizExpand} />}
<Box sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}>
<Box
sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}
>
<Typography variant="p1" sx={{ color: "#4D4D4D", fontSize: "14px" }}> <Typography variant="p1" sx={{ color: "#4D4D4D", fontSize: "14px" }}>
Создайте результат Создайте результат
</Typography> </Typography>
@ -105,9 +104,9 @@ export const ResultSettings = () => {
</Button> </Button>
</Box> </Box>
{ {results.map((resultQuestion) => (
results.map((resultQuestion) => <ResultCard resultContract={resultContract} resultData={resultQuestion} key={resultQuestion.id} />) <ResultCard resultContract={resultContract} resultData={resultQuestion} key={resultQuestion.id} />
} ))}
<Modal <Modal
open={false} open={false}
// onClose={handleClose} // onClose={handleClose}

@ -1,14 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { import { Box, Button, IconButton, SxProps, Theme, Typography, useTheme, useMediaQuery } from "@mui/material";
Box,
Button,
IconButton,
SxProps,
Theme,
Typography,
useTheme,
useMediaQuery,
} from "@mui/material";
import { SwitchSetting } from "./SwichResult"; import { SwitchSetting } from "./SwichResult";
import Info from "@icons/Info"; import Info from "@icons/Info";
@ -92,13 +83,7 @@ export const SettingForm = () => {
Показывать результат Показывать результат
</Typography> </Typography>
<IconButton> <IconButton>
<svg <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
width="30"
height="30"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="30" height="30" rx="6" fill="#EEE4FC" /> <rect width="30" height="30" rx="6" fill="#EEE4FC" />
<path <path
d="M22.5 11.25L15 18.75L7.5 11.25" d="M22.5 11.25L15 18.75L7.5 11.25"
@ -136,27 +121,16 @@ export const SettingForm = () => {
))} ))}
</Box> </Box>
{typeActive === "e-mail" ? ( {typeActive === "e-mail" ? (
<SwitchSetting <SwitchSetting icon={listChecks} text="Показывать несколько результатов" />
icon={listChecks}
text="Показывать несколько результатов"
/>
) : ( ) : (
<> <>
<SwitchSetting <SwitchSetting icon={listChecks} text="Показывать несколько результатов" />
icon={listChecks}
text="Показывать несколько результатов"
/>
<SwitchSetting icon={ShareNetwork} text="Поделиться результатами" /> <SwitchSetting icon={ShareNetwork} text="Поделиться результатами" />
<SwitchSetting <SwitchSetting icon={ArrowCounterClockWise} text="Кнопка `Пройти тест заново`" />
icon={ArrowCounterClockWise}
text="Кнопка `Пройти тест заново`"
/>
</> </>
)} )}
</Box> </Box>
<Box <Box sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}>
sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}
>
<Typography variant="p1" sx={{ color: "#4D4D4D", fontSize: "14px" }}> <Typography variant="p1" sx={{ color: "#4D4D4D", fontSize: "14px" }}>
Создайте результат Создайте результат
</Typography> </Typography>

@ -1,8 +1,7 @@
import * as React from "react"; import * as React from "react";
import { getQuestionByContentId, updateQuestion, uploadQuestionImage } from "@root/questions/actions" import { getQuestionByContentId, updateQuestion, uploadQuestionImage } from "@root/questions/actions";
import { useCurrentQuiz } from "@root/quizes/hooks" import { useCurrentQuiz } from "@root/quizes/hooks";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
@ -20,7 +19,7 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
FormControl, FormControl,
Popover Popover,
} from "@mui/material"; } from "@mui/material";
import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
@ -38,7 +37,7 @@ interface Props {
} }
export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult }) => { export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult }) => {
let check = true let check = true;
if ( if (
resultData.title.length > 0 || resultData.title.length > 0 ||
resultData.description.length > 0 || resultData.description.length > 0 ||
@ -47,14 +46,15 @@ export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult
resultData.content.innerName.length > 0 || resultData.content.innerName.length > 0 ||
resultData.content.text.length > 0 || resultData.content.text.length > 0 ||
resultData.content.video.length > 0 || resultData.content.video.length > 0 ||
resultData.content.hint.text.length > 0 resultData.content.hint.text.length > 0
) check = false )
return check check = false;
} return check;
};
const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => { const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => {
const checkEmpty = checkEmptyData({ resultData }) const checkEmpty = checkEmptyData({ resultData });
const question = getQuestionByContentId(resultData.content.rule.parentId) const question = getQuestionByContentId(resultData.content.rule.parentId);
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null); const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
@ -66,20 +66,18 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => {
}; };
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const id = open ? 'simple-popover' : undefined; const id = open ? "simple-popover" : undefined;
return ( return (
<> <>
<Info <Info
sx={{ sx={{
"MuiIconButton-root": { "MuiIconButton-root": {
boxShadow: "0 0 10px 10px red",
boxShadow: "0 0 10px 10px red" },
}
}} }}
className={checkEmpty ? "blink" : ""} className={checkEmpty ? "blink" : ""}
onClick={handleClick} onClick={handleClick}
/> />
<Popover <Popover
id={id} id={id}
@ -87,40 +85,35 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => {
anchorEl={anchorEl} anchorEl={anchorEl}
onClose={handleClose} onClose={handleClose}
anchorOrigin={{ anchorOrigin={{
vertical: 'bottom', vertical: "bottom",
horizontal: 'left', horizontal: "left",
}} }}
> >
<Paper <Paper
sx={{ sx={{
p: '20px', p: "20px",
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
flexDirection: "column" flexDirection: "column",
}} }}
> >
<Typography> <Typography>
{resultData?.content.rule.parentId === "line" ? "Единый результат в конце прохождения опросника без ветвления" {resultData?.content.rule.parentId === "line"
: ? "Единый результат в конце прохождения опросника без ветвления"
`Заголовок вопроса, после которого появится результат: "${question?.title || "нет заголовка"}"` : `Заголовок вопроса, после которого появится результат: "${question?.title || "нет заголовка"}"`}
}
</Typography> </Typography>
{checkEmpty && {checkEmpty && <Typography color="red">Вы не заполнили этот результат никакими данными</Typography>}
<Typography color="red">
Вы не заполнили этот результат никакими данными
</Typography>
}
</Paper> </Paper>
</Popover> </Popover>
</> </>
) );
} };
export const ResultCard = ({ resultContract, resultData }: Props) => { export const ResultCard = ({ resultContract, resultData }: Props) => {
console.log("resultData", resultData) console.log("resultData", resultData);
console.log(resultData.content.video);
const quizQid = useCurrentQuiz()?.qid; const quizQid = useCurrentQuiz()?.qid;
const theme = useTheme(); const theme = useTheme();
@ -128,30 +121,20 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isTablet = useMediaQuery(theme.breakpoints.down(800)); const isTablet = useMediaQuery(theme.breakpoints.down(800));
const [expand, setExpand] = React.useState(true) const [expand, setExpand] = React.useState(true);
const [resultCardSettings, setResultCardSettings] = React.useState(false) const [resultCardSettings, setResultCardSettings] = React.useState(false);
const [buttonPlus, setButtonPlus] = React.useState(true) const [buttonPlus, setButtonPlus] = React.useState(true);
React.useEffect(() => { React.useEffect(() => {
setExpand(true) setExpand(true);
}, [resultContract]) }, [resultContract]);
const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
const { useCropModalState();
isCropModalOpen,
openCropModal,
closeCropModal,
imageBlob,
originalImageUrl,
setCropModalImageBlob,
} = useCropModalState();
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
async function handleImageUpload(file: File) { async function handleImageUpload(file: File) {
const url = await uploadQuestionImage(resultData.id, quizQid, file, (question, url) => { const url = await uploadQuestionImage(resultData.id, quizQid, file, (question, url) => {
question.content.back = url; question.content.back = url;
question.content.originalBack = url; question.content.originalBack = url;
}); });
@ -165,9 +148,6 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
}); });
} }
return ( return (
<Paper <Paper
data-cy="quiz-question-card" data-cy="quiz-question-card"
@ -178,7 +158,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
backgroundColor: expand ? "white" : "#EEE4FC", backgroundColor: expand ? "white" : "#EEE4FC",
border: expand ? "none" : "1px solid #9A9AAF", border: expand ? "none" : "1px solid #9A9AAF",
boxShadow: "0px 10px 30px #e7e7e7", boxShadow: "0px 10px 30px #e7e7e7",
m: "20px 0" m: "20px 0",
}} }}
> >
<Box <Box
@ -189,7 +169,6 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
flexDirection: isMobile ? "column" : null, flexDirection: isMobile ? "column" : null,
justifyContent: "space-between", justifyContent: "space-between",
minHeight: "40px", minHeight: "40px",
}} }}
> >
<FormControl <FormControl
@ -204,14 +183,14 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
<TextField <TextField
value={resultData.title} value={resultData.title}
placeholder={"Заголовок результата"} placeholder={"Заголовок результата"}
onChange={({ target }: { target: HTMLInputElement; }) => updateQuestion(resultData.id, question => question.title = target.value)} onChange={({ target }: { target: HTMLInputElement }) =>
updateQuestion(resultData.id, (question) => (question.title = target.value))
}
sx={{ sx={{
margin: isMobile ? "10px 0" : 0, margin: isMobile ? "10px 0" : 0,
"& .MuiInputBase-root": { "& .MuiInputBase-root": {
color: "#000000", color: "#000000",
backgroundColor: expand backgroundColor: expand ? theme.palette.background.default : "transparent",
? theme.palette.background.default
: "transparent",
height: "48px", height: "48px",
borderRadius: "10px", borderRadius: "10px",
".MuiOutlinedInput-notchedOutline": { ".MuiOutlinedInput-notchedOutline": {
@ -292,7 +271,10 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
<CustomTextField <CustomTextField
value={resultData.title} value={resultData.title}
placeholder={"Заголовок результата"} placeholder={"Заголовок результата"}
onChange={({ target }: { target: HTMLInputElement; }) => updateQuestion(resultData.id, question => question.title = target.value)} /> onChange={({ target }: { target: HTMLInputElement }) =>
updateQuestion(resultData.id, (question) => (question.title = target.value))
}
/>
<IconButton <IconButton
sx={{ padding: "0", margin: "5px" }} sx={{ padding: "0", margin: "5px" }}
disableRipple disableRipple
@ -302,18 +284,18 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
<ExpandLessIconBG /> <ExpandLessIconBG />
</IconButton> </IconButton>
<InfoView resultData={resultData} /> <InfoView resultData={resultData} />
</Box> </Box>
<Box <Box
sx={{ sx={{
margin: "20px 0" margin: "20px 0",
}} }}
> >
<CustomTextField <CustomTextField
value={resultData.description} value={resultData.description}
onChange={({ target }: { target: HTMLInputElement; }) => updateQuestion(resultData.id, (question) => question.description = target.value)} onChange={({ target }: { target: HTMLInputElement }) =>
updateQuestion(resultData.id, (question) => (question.description = target.value))
}
placeholder={"Заголовок пожирнее"} placeholder={"Заголовок пожирнее"}
sx={{ sx={{
borderRadius: "8px", borderRadius: "8px",
@ -324,9 +306,10 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
</Box> </Box>
<TextField <TextField
value={resultData.content.text} value={resultData.content.text}
onChange={({ target }: { target: HTMLInputElement; }) => updateQuestion(resultData.id, (question) => question.content.text = target.value)} onChange={({ target }: { target: HTMLInputElement }) =>
updateQuestion(resultData.id, (question) => (question.content.text = target.value))
}
fullWidth fullWidth
placeholder="Описание" placeholder="Описание"
multiline multiline
@ -350,14 +333,12 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
}} }}
/> />
<Box <Box
sx={{ sx={{
mt: "20px", mt: "20px",
display: "flex", display: "flex",
gap: "10px", gap: "10px",
flexDirection: "column" flexDirection: "column",
}} }}
> >
<Box <Box
@ -374,7 +355,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
}, },
}} }}
variant="text" variant="text"
onClick={() => updateQuestion(resultData.id, (question) => question.content.useImage = true)} onClick={() => updateQuestion(resultData.id, (question) => (question.content.useImage = true))}
> >
Изображение Изображение
</Button> </Button>
@ -387,7 +368,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
}, },
}} }}
variant="text" variant="text"
onClick={() => updateQuestion(resultData.id, (question) => question.content.useImage = false)} onClick={() => updateQuestion(resultData.id, (question) => (question.content.useImage = false))}
> >
Видео Видео
</Button> </Button>
@ -414,25 +395,21 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
/> />
</Box> </Box>
{ {resultData.content.useImage && (
resultData.content.useImage &&
<Box <Box
sx={{ sx={{
cursor: "pointer", cursor: "pointer",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "20px", gap: "20px",
mb: "30px" mb: "30px",
}} }}
> >
<AddOrEditImageButton <AddOrEditImageButton
imageSrc={resultData.content.back} imageSrc={resultData.content.back}
onImageClick={() => { onImageClick={() => {
if (resultData.content.back) { if (resultData.content.back) {
return openCropModal( return openCropModal(resultData.content.back, resultData.content.originalBack);
resultData.content.back,
resultData.content.originalBack
);
} }
openImageUploadModal(); openImageUploadModal();
@ -442,106 +419,99 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
}} }}
/> />
</Box> </Box>
} )}
{ {!resultData.content.useImage && (
!resultData.content.useImage &&
<Box <Box
sx={{ sx={{
cursor: "pointer", cursor: "pointer",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "20px", gap: "20px",
mb: "30px" mb: "30px",
}} }}
> >
<CustomTextField <CustomTextField
placeholder="URL видео" placeholder="URL видео"
text={resultData.content.video ?? ""} text={resultData.content.video ?? ""}
onChange={e => updateQuestion(resultData.id, q => { onChange={(e) =>
q.content.video = e.target.value; updateQuestion(resultData.id, (q) => {
})} q.content.video = e.target.value;
})
}
/> />
</Box> </Box>
} )}
</Box> </Box>
{buttonPlus ? (
<Button
{ onClick={() => {
buttonPlus ? setButtonPlus(false);
<Button }}
onClick={() => { sx={{
setButtonPlus(false) display: "inline flex",
}} height: "48px",
sx={{ padding: "10px 20px",
display: "inline flex", justifyContent: "center",
height: "48px", alignItems: "center",
padding: "10px 20px", gap: "8px",
justifyContent: "center", flexShrink: 0,
alignItems: "center", borderRadius: "8px",
gap: "8px", border: "1px solid #9A9AAF",
flexShrink: 0, background: " #F2F3F7",
borderRadius: "8px", color: "#9A9AAF",
border: "1px solid #9A9AAF", mb: "30px",
background: " #F2F3F7", }}
color: "#9A9AAF", >
mb: "30px" Кнопка +
}} </Button>
> ) : (
Кнопка + <Box
</Button> sx={{
: mb: "30px",
<Box }}
sx={{ >
mb: "30px" <Box>
}} <Typography component={"span"} sx={{ weight: "500", fontSize: "18px", mb: "10px" }}>
> Призыв к действию
<Box> </Typography>
<Typography component={"span"} sx={{ weight: "500", fontSize: "18px", mb: "10px" }}> <IconButton
Призыв к действию onClick={() => {
</Typography> setButtonPlus(true);
<IconButton updateQuestion(resultData.id, (q) => (q.content.hint.text = ""));
onClick={() => {
setButtonPlus(true)
updateQuestion(resultData.id, (q) => q.content.hint.text = "")
}}
>
<Trash />
</IconButton>
</Box>
<TextField
value={resultData.content.hint.text}
onChange={({ target }: { target: HTMLInputElement; }) => updateQuestion(resultData.id, (question) => question.content.hint.text = target.value)}
fullWidth
placeholder="Например: узнать подробнее"
sx={{
"& .MuiInputBase-root": {
backgroundColor: "#F2F3F7",
width: "409px",
height: "48px",
borderRadius: "8px",
},
}} }}
inputProps={{ >
sx: { <Trash />
height: "85px", </IconButton>
borderRadius: "10px",
fontSize: "18px",
lineHeight: "21px",
py: 0,
},
}}
/>
</Box> </Box>
}
<TextField
value={resultData.content.hint.text}
onChange={({ target }: { target: HTMLInputElement }) =>
updateQuestion(resultData.id, (question) => (question.content.hint.text = target.value))
}
fullWidth
placeholder="Например: узнать подробнее"
sx={{
"& .MuiInputBase-root": {
backgroundColor: "#F2F3F7",
width: "409px",
height: "48px",
borderRadius: "8px",
},
}}
inputProps={{
sx: {
height: "85px",
borderRadius: "10px",
fontSize: "18px",
lineHeight: "21px",
py: 0,
},
}}
/>
</Box>
)}
</Box> </Box>
<Box <Box
sx={{ sx={{
@ -561,50 +531,42 @@ export const ResultCard = ({ resultContract, resultData }: Props) => {
> >
<MiniButtonSetting <MiniButtonSetting
onClick={() => { onClick={() => {
setResultCardSettings(!resultCardSettings) setResultCardSettings(!resultCardSettings);
}} }}
sx={{ sx={{
backgroundColor: backgroundColor: resultCardSettings ? theme.palette.brightPurple.main : "transparent",
resultCardSettings color: resultCardSettings ? "#ffffff" : theme.palette.grey3.main,
? theme.palette.brightPurple.main
: "transparent",
color:
resultCardSettings ? "#ffffff" : theme.palette.grey3.main,
"&:hover": { "&:hover": {
backgroundColor: resultCardSettings ? "#581CA7" : "#7E2AEA", backgroundColor: resultCardSettings ? "#581CA7" : "#7E2AEA",
color: "white" color: "white",
} },
}} }}
> >
<SettingIcon <SettingIcon color={resultCardSettings ? "#ffffff" : theme.palette.grey3.main} />
color={
resultCardSettings ? "#ffffff" : theme.palette.grey3.main
}
/>
{!isTablet && "Настройки"} {!isTablet && "Настройки"}
</MiniButtonSetting> </MiniButtonSetting>
</Box> </Box>
</Box> </Box>
</Box> </Box>
{ {resultCardSettings && (
resultCardSettings &&
<Box <Box
sx={{ sx={{
backgroundColor: "white", backgroundColor: "white",
p: "20px", p: "20px",
borderRadius: "0 0 12px 12px" borderRadius: "0 0 12px 12px",
}} }}
> >
<CustomTextField <CustomTextField
placeholder={"Внутреннее описание вопроса"} placeholder={"Внутреннее описание вопроса"}
value={resultData.innerName} value={resultData.innerName}
onChange={({ target }: { target: HTMLInputElement; }) => updateQuestion(resultData.id, (question) => question.content.innerName = target.value)} onChange={({ target }: { target: HTMLInputElement }) =>
updateQuestion(resultData.id, (question) => (question.content.innerName = target.value))
}
/> />
</Box> </Box>
} )}
</> </>
) )}
} </Paper>
</Paper > );
) };
}

@ -46,7 +46,12 @@ export default function QuizCard({ quiz, openCount = 0, applicationCount = 0, co
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`, 0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`,
}} }}
> >
<Typography variant="h5">{quiz.name}</Typography> <Typography
sx={{ textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap", widows: "100%" }}
variant="h5"
>
{quiz.name}
</Typography>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",

@ -0,0 +1,17 @@
import { useSnackbar } from "notistack";
import { addQuestionVariant } from "@root/questions/actions";
import { QuizQuestionsWithVariants } from "@model/questionTypes/shared";
export const useAddAnswer = () => {
const { enqueueSnackbar } = useSnackbar();
const onClickAddAnAnswer = (question: QuizQuestionsWithVariants) => {
if (question.content.variants.length >= 10) {
enqueueSnackbar("100 максимальное количество вопросов");
} else {
addQuestionVariant(question.id);
}
};
return onClickAddAnAnswer;
};