commit
77c68856a4
23
src/assets/icons/NameplateLogoFQ.tsx
Normal file
23
src/assets/icons/NameplateLogoFQ.tsx
Normal file
File diff suppressed because one or more lines are too long
@ -12,6 +12,7 @@ export const QUIZ_QUESTION_RESULT: Omit<QuizQuestionResult, "id" | "backendId">
|
|||||||
innerName: "",
|
innerName: "",
|
||||||
text: "",
|
text: "",
|
||||||
price: [0],
|
price: [0],
|
||||||
useImage: true
|
useImage: true,
|
||||||
|
usage: true
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -18,5 +18,6 @@ export interface QuizQuestionResult extends QuizQuestionBase {
|
|||||||
rule: QuestionBranchingRule,
|
rule: QuestionBranchingRule,
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
usage: boolean
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,9 @@ export default function Component() {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const [select, setSelect] = React.useState(0);
|
|
||||||
const userId = useUserStore((state) => state.userId);
|
const userId = useUserStore((state) => state.userId);
|
||||||
const navigate = useNavigate();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const onClick = () => (userId ? navigate("/list") : navigate("/signin"));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionStyled
|
<SectionStyled
|
||||||
tag={"header"}
|
tag={"header"}
|
||||||
@ -69,8 +65,10 @@ export default function Component() {
|
|||||||
{/* ))}*/}
|
{/* ))}*/}
|
||||||
{/*</Box>*/}
|
{/*</Box>*/}
|
||||||
<Button
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to={"/signin"}
|
||||||
|
state={{ backgroundLocation: location }}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={onClick}
|
|
||||||
sx={{
|
sx={{
|
||||||
color: "black",
|
color: "black",
|
||||||
border: "1px solid black",
|
border: "1px solid black",
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,113 +1,45 @@
|
|||||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
import Cytoscape from "cytoscape";
|
import Cytoscape from "cytoscape";
|
||||||
import { Button, Box } from "@mui/material";
|
|
||||||
import CytoscapeComponent from "react-cytoscapejs";
|
import CytoscapeComponent from "react-cytoscapejs";
|
||||||
import popper from "cytoscape-popper";
|
import popper from "cytoscape-popper";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { Button, Box } from "@mui/material";
|
||||||
import { updateRootContentId } from "@root/quizes/actions"
|
|
||||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
|
|
||||||
import { useQuestionsStore } from "@root/questions/store";
|
|
||||||
import { deleteQuestion, updateQuestion, getQuestionByContentId, clearRuleForAll, createFrontResult } from "@root/questions/actions";
|
|
||||||
import { updateCanCreatePublic, updateModalInfoWhyCantCreate, updateOpenedModalSettingsId, } from "@root/uiTools/actions";
|
|
||||||
import { cleardragQuestionContentId } from "@root/uiTools/actions";
|
|
||||||
import { withErrorBoundary } from "react-error-boundary";
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import { updateRootContentId } from "@root/quizes/actions";
|
||||||
|
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||||
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
|
import { useUiTools } from "@root/uiTools/store";
|
||||||
|
import {
|
||||||
|
deleteQuestion,
|
||||||
|
updateQuestion,
|
||||||
|
getQuestionByContentId,
|
||||||
|
clearRuleForAll,
|
||||||
|
createResult,
|
||||||
|
} from "@root/questions/actions";
|
||||||
|
import {
|
||||||
|
updateModalInfoWhyCantCreate,
|
||||||
|
updateOpenedModalSettingsId
|
||||||
|
} from "@root/uiTools/actions";
|
||||||
|
import { cleardragQuestionContentId } from "@root/uiTools/actions";
|
||||||
|
import { updateDeleteId } from "@root/uiTools/actions";
|
||||||
|
|
||||||
|
import { DeleteNodeModal } from "../DeleteNodeModal";
|
||||||
import { ProblemIcon } from "@ui_kit/ProblemIcon";
|
import { ProblemIcon } from "@ui_kit/ProblemIcon";
|
||||||
|
|
||||||
|
import { useRemoveNode } from "./hooks/useRemoveNode";
|
||||||
|
import { usePopper } from "./hooks/usePopper";
|
||||||
|
|
||||||
import { storeToNodes } from "./helper";
|
import { storeToNodes } from "./helper";
|
||||||
|
import { stylesheet } from "./style/stylesheet";
|
||||||
|
import "./style/styles.css";
|
||||||
|
|
||||||
import "./styles.css";
|
import type { Core } from "cytoscape";
|
||||||
|
|
||||||
import type {
|
|
||||||
Stylesheet,
|
|
||||||
Core,
|
|
||||||
NodeSingular,
|
|
||||||
AbstractEventObject,
|
|
||||||
ElementDefinition,
|
|
||||||
} from "cytoscape";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { useUiTools } from "@root/uiTools/store";
|
|
||||||
|
|
||||||
type PopperItem = {
|
|
||||||
id: () => string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Modifier = {
|
|
||||||
name: string;
|
|
||||||
options: unknown;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PopperConfig = {
|
|
||||||
popper: {
|
|
||||||
placement: string;
|
|
||||||
modifiers?: Modifier[];
|
|
||||||
};
|
|
||||||
content: (items: PopperItem[]) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Popper = {
|
|
||||||
update: () => Promise<void>;
|
|
||||||
setOptions: (modifiers: { modifiers?: Modifier[] }) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type NodeSingularWithPopper = NodeSingular & {
|
|
||||||
popper: (config: PopperConfig) => Popper;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stylesheet: Stylesheet[] = [
|
|
||||||
{
|
|
||||||
selector: "node",
|
|
||||||
style: {
|
|
||||||
shape: "round-rectangle",
|
|
||||||
width: 130,
|
|
||||||
height: 130,
|
|
||||||
backgroundColor: "#FFFFFF",
|
|
||||||
label: "data(label)",
|
|
||||||
"font-size": "16",
|
|
||||||
color: "#4D4D4D",
|
|
||||||
"text-halign": "center",
|
|
||||||
"text-valign": "center",
|
|
||||||
"text-wrap": "wrap",
|
|
||||||
"text-max-width": "80",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector: "[?eroticeyeblink]",
|
|
||||||
style: {
|
|
||||||
"border-width": "4px",
|
|
||||||
"border-style": "solid",
|
|
||||||
"border-color": "#7e2aea",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector: ".multiline-auto",
|
|
||||||
style: {
|
|
||||||
"text-wrap": "wrap",
|
|
||||||
"text-max-width": "80",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector: "edge",
|
|
||||||
style: {
|
|
||||||
width: 30,
|
|
||||||
"line-color": "#DEDFE7",
|
|
||||||
"curve-style": "taxi",
|
|
||||||
"taxi-direction": "horizontal",
|
|
||||||
"taxi-turn": 60,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector: ":selected",
|
|
||||||
style: {
|
|
||||||
"border-style": "solid",
|
|
||||||
"border-width": 1.5,
|
|
||||||
"border-color": "#9A9AAF",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
Cytoscape.use(popper);
|
Cytoscape.use(popper);
|
||||||
|
|
||||||
interface Props {
|
interface CsComponentProps {
|
||||||
modalQuestionParentContentId: string;
|
modalQuestionParentContentId: string;
|
||||||
modalQuestionTargetContentId: string;
|
modalQuestionTargetContentId: string;
|
||||||
setOpenedModalQuestions: (open: boolean) => void;
|
setOpenedModalQuestions: (open: boolean) => void;
|
||||||
@ -115,15 +47,13 @@ interface Props {
|
|||||||
setModalQuestionTargetContentId: (id: string) => void;
|
setModalQuestionTargetContentId: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function CsComponent({
|
function CsComponent({
|
||||||
modalQuestionParentContentId,
|
modalQuestionParentContentId,
|
||||||
modalQuestionTargetContentId,
|
modalQuestionTargetContentId,
|
||||||
setOpenedModalQuestions,
|
setOpenedModalQuestions,
|
||||||
setModalQuestionParentContentId,
|
setModalQuestionParentContentId,
|
||||||
setModalQuestionTargetContentId
|
setModalQuestionTargetContentId
|
||||||
}: Props) {
|
}: CsComponentProps) {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
const { dragQuestionContentId, desireToOpenABranchingModal, canCreatePublic } = useUiTools()
|
const { dragQuestionContentId, desireToOpenABranchingModal, canCreatePublic } = useUiTools()
|
||||||
@ -138,6 +68,25 @@ function CsComponent({
|
|||||||
const crossesContainer = useRef<HTMLDivElement | null>(null);
|
const crossesContainer = useRef<HTMLDivElement | null>(null);
|
||||||
const gearsContainer = useRef<HTMLDivElement | null>(null);
|
const gearsContainer = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const { layoutOptions } = usePopper({
|
||||||
|
layoutsContainer,
|
||||||
|
plusesContainer,
|
||||||
|
crossesContainer,
|
||||||
|
gearsContainer,
|
||||||
|
setModalQuestionParentContentId,
|
||||||
|
setOpenedModalQuestions,
|
||||||
|
setStartCreate,
|
||||||
|
setStartRemove,
|
||||||
|
});
|
||||||
|
const { removeNode } = useRemoveNode({
|
||||||
|
cyRef,
|
||||||
|
layoutOptions,
|
||||||
|
layoutsContainer,
|
||||||
|
plusesContainer,
|
||||||
|
crossesContainer,
|
||||||
|
gearsContainer,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@ -181,7 +130,7 @@ function CsComponent({
|
|||||||
if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) {
|
if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) {
|
||||||
clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren })
|
clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren })
|
||||||
cy?.data('changed', true)
|
cy?.data('changed', true)
|
||||||
createFrontResult(quiz.backendId, targetQuestion.content.id)
|
createResult(quiz?.backendId, targetQuestion.content.id)
|
||||||
const es = cy?.add([
|
const es = cy?.add([
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
@ -196,7 +145,7 @@ function CsComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
cy?.layout(lyopts).run()
|
cy?.layout(layoutOptions).run()
|
||||||
cy?.center(es)
|
cy?.center(es)
|
||||||
} else {
|
} else {
|
||||||
enqueueSnackbar("Добавляемый вопрос не найден")
|
enqueueSnackbar("Добавляемый вопрос не найден")
|
||||||
@ -208,10 +157,10 @@ function CsComponent({
|
|||||||
const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion
|
const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion
|
||||||
|
|
||||||
|
|
||||||
//смотрим не добавлен ли родителю result. Если да - убираем его. Веточкам result не нужен
|
//смотрим не добавлен ли родителю result. Если да - делаем его неактивным. Веточкам result не нужен
|
||||||
trashQuestions.forEach((targetQuestion) => {
|
trashQuestions.forEach((targetQuestion) => {
|
||||||
if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) {
|
if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) {
|
||||||
deleteQuestion(targetQuestion.id);
|
updateQuestion(targetQuestion.id, (q) => q.content.usage = false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -234,262 +183,44 @@ function CsComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => {
|
|
||||||
const deleteNodes = [] as string[]
|
|
||||||
const deleteEdges: any = []
|
|
||||||
const cy = cyRef?.current
|
|
||||||
|
|
||||||
const findChildrenToDelete = (node) => {
|
|
||||||
|
|
||||||
//Узнаём грани, идущие от этой ноды
|
|
||||||
cy?.$('edge[source = "' + node.id() + '"]')?.toArray().forEach((edge) => {
|
|
||||||
const edgeData = edge.data()
|
|
||||||
|
|
||||||
//записываем id грани для дальнейшего удаления
|
|
||||||
deleteEdges.push(edge)
|
|
||||||
//ищем ноду на конце грани, записываем её ID для дальнейшего удаления
|
|
||||||
const targetNode = cy?.$("#" + edgeData.target)
|
|
||||||
deleteNodes.push(targetNode.data().id)
|
|
||||||
//вызываем функцию для анализа потомков уже у этой ноды
|
|
||||||
findChildrenToDelete(targetNode)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
findChildrenToDelete(cy?.getElementById(targetNodeContentId))
|
|
||||||
|
|
||||||
const targetQuestion = getQuestionByContentId(targetNodeContentId)
|
|
||||||
|
|
||||||
if (targetQuestion.content.rule.parentId === "root" && quiz) {
|
|
||||||
updateRootContentId(quiz?.id, "")
|
|
||||||
updateQuestion(targetNodeContentId, question => {
|
|
||||||
question.content.rule.parentId = ""
|
|
||||||
question.content.rule.main = []
|
|
||||||
question.content.rule.children = []
|
|
||||||
question.content.rule.default = ""
|
|
||||||
})
|
|
||||||
trashQuestions.forEach(q => {
|
|
||||||
if (q.type === "result") {
|
|
||||||
deleteQuestion(q.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
clearRuleForAll()
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
|
|
||||||
if (targetNodeContentId && parentQuestionContentId) {
|
|
||||||
if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0)
|
|
||||||
createFrontResult(quiz.backendId, parentQuestionContentId)
|
|
||||||
clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId })
|
|
||||||
cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке
|
|
||||||
|
|
||||||
deleteNodes.forEach((nodeId) => {//Ноды
|
|
||||||
cy?.remove(cy?.$("#" + nodeId))
|
|
||||||
removeButtons(nodeId)
|
|
||||||
updateQuestion(nodeId, question => {
|
|
||||||
question.content.rule.parentId = ""
|
|
||||||
question.content.rule.main = []
|
|
||||||
question.content.rule.default = ""
|
|
||||||
question.content.rule.children = []
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
deleteEdges.forEach((edge: any) => {//Грани
|
|
||||||
cy?.remove(edge)
|
|
||||||
})
|
|
||||||
|
|
||||||
removeButtons(targetNodeContentId)
|
|
||||||
cy?.data('changed', true)
|
|
||||||
cy?.layout(lyopts).run()
|
|
||||||
|
|
||||||
//удаляем result всех потомков
|
|
||||||
trashQuestions.forEach((qr) => {
|
|
||||||
if (qr.type === "result") {
|
|
||||||
if (deleteNodes.includes(qr.content.rule.parentId) || qr.content.rule.parentId === targetQuestion.content.id) {
|
|
||||||
deleteQuestion(qr.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
updateQuestion(targetQuestionContentId, question => {
|
|
||||||
question.content.rule.parentId = ""
|
|
||||||
question.content.rule.children = []
|
|
||||||
question.content.rule.main = []
|
|
||||||
question.content.rule.default = ""
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
//чистим rule родителя
|
|
||||||
const parentQuestion = getQuestionByContentId(parentQuestionContentId)
|
|
||||||
const newRule = {}
|
|
||||||
const newChildren = [...parentQuestion.content.rule.children]
|
|
||||||
newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1);
|
|
||||||
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу
|
|
||||||
newRule.parentId = parentQuestion.content.rule.parentId
|
|
||||||
newRule.default = parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default
|
|
||||||
newRule.children = newChildren
|
|
||||||
|
|
||||||
updateQuestion(parentQuestionContentId, (PQ) => {
|
|
||||||
PQ.content.rule = newRule
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (startCreate) {
|
if (startCreate) {
|
||||||
addNode({ parentNodeContentId: startCreate });
|
addNode({ parentNodeContentId: startCreate });
|
||||||
cleardragQuestionContentId()
|
cleardragQuestionContentId();
|
||||||
setStartCreate("");
|
setStartCreate("");
|
||||||
}
|
}
|
||||||
}, [startCreate]);
|
}, [startCreate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (startRemove) {
|
if (startRemove) {
|
||||||
removeNode({ targetNodeContentId: startRemove });
|
updateDeleteId(startRemove);
|
||||||
setStartRemove("");
|
setStartRemove("");
|
||||||
}
|
}
|
||||||
}, [startRemove]);
|
}, [startRemove]);
|
||||||
|
|
||||||
|
|
||||||
const readyLO = (e) => {
|
|
||||||
if (e.cy.data('firstNode') === 'nonroot') {
|
|
||||||
e.cy.data('firstNode', 'root')
|
|
||||||
e.cy.nodes().sort((a, b) => (a.data('root') ? 1 : -1)).layout(lyopts).run()
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
|
|
||||||
e.cy.data('changed', false)
|
|
||||||
e.cy.removeData('firstNode')
|
|
||||||
}
|
|
||||||
|
|
||||||
//удаляем иконки
|
|
||||||
e.cy.nodes().forEach((ele: any) => {
|
|
||||||
const data = ele.data()
|
|
||||||
data.id && removeButtons(data.id);
|
|
||||||
})
|
|
||||||
initialPopperIcons(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
const lyopts = {
|
|
||||||
name: 'preset',
|
|
||||||
|
|
||||||
positions: (e) => {
|
|
||||||
if (!e.cy().data('changed')) {
|
|
||||||
return e.data('oldPos')
|
|
||||||
}
|
|
||||||
const id = e.id()
|
|
||||||
const incomming = e.cy().edges(`[target="${id}"]`)
|
|
||||||
const layer = 0
|
|
||||||
e.removeData('lastChild')
|
|
||||||
|
|
||||||
if (incomming.length === 0) {
|
|
||||||
if (e.cy().data('firstNode') === undefined)
|
|
||||||
e.cy().data('firstNode', 'root')
|
|
||||||
e.data('root', true)
|
|
||||||
const children = e.cy().edges(`[source="${id}"]`).targets()
|
|
||||||
e.data('layer', layer)
|
|
||||||
e.data('children', children.length)
|
|
||||||
const queue = []
|
|
||||||
children.forEach(n => {
|
|
||||||
queue.push({ task: n, layer: layer + 1 })
|
|
||||||
})
|
|
||||||
while (queue.length) {
|
|
||||||
const task = queue.pop()
|
|
||||||
task.task.data('layer', task.layer)
|
|
||||||
task.task.removeData('subtreeWidth')
|
|
||||||
const children = e.cy().edges(`[source="${task.task.id()}"]`).targets()
|
|
||||||
task.task.data('children', children.length)
|
|
||||||
if (children.length !== 0) {
|
|
||||||
children.forEach(n => queue.push({ task: n, layer: task.layer + 1 }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queue.push({ parent: e, children: children })
|
|
||||||
while (queue.length) {
|
|
||||||
const task = queue.pop()
|
|
||||||
if (task.children.length === 0) {
|
|
||||||
task.parent.data('subtreeWidth', task.parent.height() + 50)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const unprocessed = task?.children.filter(e => {
|
|
||||||
return (e.data('subtreeWidth') === undefined)
|
|
||||||
})
|
|
||||||
if (unprocessed.length !== 0) {
|
|
||||||
queue.push(task)
|
|
||||||
unprocessed.forEach(t => {
|
|
||||||
queue.push({ parent: t, children: t.cy().edges(`[source="${t.id()}"]`).targets() })
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
task?.parent.data('subtreeWidth', task.children.reduce((p, n) => p + n.data('subtreeWidth'), 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
const pos = { x: 0, y: 0 }
|
|
||||||
e.data('oldPos', pos)
|
|
||||||
|
|
||||||
queue.push({ task: children, parent: e })
|
|
||||||
while (queue.length) {
|
|
||||||
const task = queue.pop()
|
|
||||||
const oldPos = task.parent.data('oldPos')
|
|
||||||
let yoffset = oldPos.y - task.parent.data('subtreeWidth') / 2
|
|
||||||
task.task.forEach(n => {
|
|
||||||
const width = n.data('subtreeWidth')
|
|
||||||
|
|
||||||
n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 })
|
|
||||||
yoffset += width
|
|
||||||
queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
e.cy().data('changed', false)
|
|
||||||
return pos
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const opos = e.data('oldPos')
|
|
||||||
if (opos) {
|
|
||||||
return opos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, // map of (node id) => (position obj); or function(node){ return somPos; }
|
|
||||||
zoom: undefined, // the zoom level to set (prob want fit = false if set)
|
|
||||||
pan: true, // the pan level to set (prob want fit = false if set)
|
|
||||||
fit: false, // whether to fit to viewport
|
|
||||||
padding: 30, // padding on fit
|
|
||||||
animate: false, // whether to transition the node positions
|
|
||||||
animationDuration: 500, // duration of animation in ms if enabled
|
|
||||||
animationEasing: undefined, // easing of animation if enabled
|
|
||||||
animateFilter: function (node, i) { return false; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
|
|
||||||
ready: readyLO, // callback on layoutready
|
|
||||||
transform: function (node, position) { return position; } // transform a given node position. Useful for changing flow direction in discrete layouts
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.querySelector("#root")?.addEventListener("mouseup", cleardragQuestionContentId);
|
document
|
||||||
|
.querySelector("#root")
|
||||||
|
?.addEventListener("mouseup", cleardragQuestionContentId);
|
||||||
const cy = cyRef.current;
|
const cy = cyRef.current;
|
||||||
const eles = cy?.add(storeToNodes(questions.filter((question: AnyTypedQuizQuestion) => (question.type !== "result" && question.type !== null))))
|
const eles = cy?.add(
|
||||||
cy.data('changed', true)
|
storeToNodes(
|
||||||
|
questions.filter(
|
||||||
|
(question) => question.type && question.type !== "result"
|
||||||
|
) as AnyTypedQuizQuestion[]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
cy?.data("changed", true);
|
||||||
// cy.data('changed', true)
|
// cy.data('changed', true)
|
||||||
const elecs = eles.layout(lyopts).run()
|
const elecs = eles?.layout(layoutOptions).run();
|
||||||
cy?.on('add', () => cy.data('changed', true))
|
cy?.on("add", () => cy.data("changed", true));
|
||||||
cy?.fit()
|
cy?.fit();
|
||||||
//cy?.layout().run()
|
//cy?.layout().run()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.querySelector("#root")?.removeEventListener("mouseup", cleardragQuestionContentId);
|
document
|
||||||
|
.querySelector("#root")
|
||||||
|
?.removeEventListener("mouseup", cleardragQuestionContentId);
|
||||||
layoutsContainer.current?.remove();
|
layoutsContainer.current?.remove();
|
||||||
plusesContainer.current?.remove();
|
plusesContainer.current?.remove();
|
||||||
crossesContainer.current?.remove();
|
crossesContainer.current?.remove();
|
||||||
@ -498,297 +229,6 @@ function CsComponent({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const removeButtons = (id: string) => {
|
|
||||||
layoutsContainer.current
|
|
||||||
?.querySelector(`.popper-layout[data-id='${id}']`)
|
|
||||||
?.remove();
|
|
||||||
plusesContainer.current
|
|
||||||
?.querySelector(`.popper-plus[data-id='${id}']`)
|
|
||||||
?.remove();
|
|
||||||
crossesContainer.current
|
|
||||||
?.querySelector(`.popper-cross[data-id='${id}']`)
|
|
||||||
?.remove();
|
|
||||||
gearsContainer.current
|
|
||||||
?.querySelector(`.popper-gear[data-id='${id}']`)
|
|
||||||
?.remove();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const initialPopperIcons = (e) => {
|
|
||||||
const cy = e.cy
|
|
||||||
|
|
||||||
const container =
|
|
||||||
(document.body.querySelector(
|
|
||||||
".__________cytoscape_container"
|
|
||||||
) as HTMLDivElement) || null;
|
|
||||||
|
|
||||||
if (!container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
container.style.overflow = "hidden";
|
|
||||||
|
|
||||||
if (!plusesContainer.current) {
|
|
||||||
plusesContainer.current = document.createElement("div");
|
|
||||||
plusesContainer.current.setAttribute("id", "popper-pluses");
|
|
||||||
container.append(plusesContainer.current);
|
|
||||||
}
|
|
||||||
if (!crossesContainer.current) {
|
|
||||||
crossesContainer.current = document.createElement("div");
|
|
||||||
crossesContainer.current.setAttribute("id", "popper-crosses");
|
|
||||||
container.append(crossesContainer.current);
|
|
||||||
}
|
|
||||||
if (!gearsContainer.current) {
|
|
||||||
gearsContainer.current = document.createElement("div");
|
|
||||||
gearsContainer.current.setAttribute("id", "popper-gears");
|
|
||||||
container.append(gearsContainer.current);
|
|
||||||
}
|
|
||||||
if (!layoutsContainer.current) {
|
|
||||||
layoutsContainer.current = document.createElement("div");
|
|
||||||
layoutsContainer.current.setAttribute("id", "popper-layouts");
|
|
||||||
container.append(layoutsContainer.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ext = cy.extent()
|
|
||||||
const nodesInView = cy.nodes().filter(n => {
|
|
||||||
const bb = n.boundingBox()
|
|
||||||
return bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2
|
|
||||||
})
|
|
||||||
|
|
||||||
nodesInView
|
|
||||||
.toArray()
|
|
||||||
?.forEach((item) => {
|
|
||||||
const node = item as NodeSingularWithPopper;
|
|
||||||
|
|
||||||
const layoutsPopper = node.popper({
|
|
||||||
popper: {
|
|
||||||
placement: "left",
|
|
||||||
modifiers: [{ name: "flip", options: { boundary: node } }],
|
|
||||||
},
|
|
||||||
content: ([item]) => {
|
|
||||||
const itemId = item.id();
|
|
||||||
const itemElement = layoutsContainer.current?.querySelector(
|
|
||||||
`.popper-layout[data-id='${itemId}']`
|
|
||||||
);
|
|
||||||
if (itemElement) {
|
|
||||||
return itemElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const layoutElement = document.createElement("div");
|
|
||||||
layoutElement.style.zIndex = "0"
|
|
||||||
layoutElement.classList.add("popper-layout");
|
|
||||||
layoutElement.setAttribute("data-id", item.id());
|
|
||||||
layoutElement.addEventListener("mouseup", () => {
|
|
||||||
//Узнаём грани, идущие от этой ноды
|
|
||||||
setModalQuestionParentContentId(item.id())
|
|
||||||
setOpenedModalQuestions(true)
|
|
||||||
});
|
|
||||||
layoutsContainer.current?.appendChild(layoutElement);
|
|
||||||
|
|
||||||
return layoutElement;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const plusesPopper = node.popper({
|
|
||||||
popper: {
|
|
||||||
placement: "right",
|
|
||||||
modifiers: [{ name: "flip", options: { boundary: node } }],
|
|
||||||
},
|
|
||||||
content: ([item]) => {
|
|
||||||
const itemId = item.id();
|
|
||||||
const itemElement = plusesContainer.current?.querySelector(
|
|
||||||
`.popper-plus[data-id='${itemId}']`
|
|
||||||
);
|
|
||||||
if (itemElement) {
|
|
||||||
return itemElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const plusElement = document.createElement("div");
|
|
||||||
plusElement.classList.add("popper-plus");
|
|
||||||
plusElement.setAttribute("data-id", item.id());
|
|
||||||
plusElement.style.zIndex = "1"
|
|
||||||
plusElement.addEventListener("mouseup", () => {
|
|
||||||
setStartCreate(node.id());
|
|
||||||
});
|
|
||||||
|
|
||||||
plusesContainer.current?.appendChild(plusElement);
|
|
||||||
|
|
||||||
return plusElement;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const crossesPopper = node.popper({
|
|
||||||
popper: {
|
|
||||||
placement: "top-end",
|
|
||||||
modifiers: [{ name: "flip", options: { boundary: node } }],
|
|
||||||
},
|
|
||||||
content: ([item]) => {
|
|
||||||
const itemId = item.id();
|
|
||||||
const itemElement = crossesContainer.current?.querySelector(
|
|
||||||
`.popper-cross[data-id='${itemId}']`
|
|
||||||
);
|
|
||||||
if (itemElement) {
|
|
||||||
return itemElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const crossElement = document.createElement("div");
|
|
||||||
crossElement.classList.add("popper-cross");
|
|
||||||
crossElement.setAttribute("data-id", item.id());
|
|
||||||
crossElement.style.zIndex = "2"
|
|
||||||
crossesContainer.current?.appendChild(crossElement);
|
|
||||||
crossElement.addEventListener("mouseup", () => {
|
|
||||||
setStartRemove(node.id())
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return crossElement;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
let gearsPopper = null
|
|
||||||
if (node.data().root !== true) {
|
|
||||||
gearsPopper = node.popper({
|
|
||||||
popper: {
|
|
||||||
placement: "left",
|
|
||||||
modifiers: [{ name: "flip", options: { boundary: node } }],
|
|
||||||
},
|
|
||||||
content: ([item]) => {
|
|
||||||
const itemId = item.id();
|
|
||||||
|
|
||||||
const itemElement = gearsContainer.current?.querySelector(
|
|
||||||
`.popper-gear[data-id='${itemId}']`
|
|
||||||
);
|
|
||||||
if (itemElement) {
|
|
||||||
return itemElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gearElement = document.createElement("div");
|
|
||||||
gearElement.classList.add("popper-gear");
|
|
||||||
gearElement.setAttribute("data-id", item.id());
|
|
||||||
gearElement.style.zIndex = "1"
|
|
||||||
gearsContainer.current?.appendChild(gearElement);
|
|
||||||
gearElement.addEventListener("mouseup", (e) => {
|
|
||||||
updateOpenedModalSettingsId(item.id())
|
|
||||||
});
|
|
||||||
|
|
||||||
return gearElement;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const update = async () => {
|
|
||||||
await plusesPopper.update();
|
|
||||||
await crossesPopper.update();
|
|
||||||
await gearsPopper?.update();
|
|
||||||
await layoutsPopper.update();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onZoom = (event: AbstractEventObject) => {
|
|
||||||
const zoom = event.cy.zoom();
|
|
||||||
|
|
||||||
//update();
|
|
||||||
|
|
||||||
crossesPopper.setOptions({
|
|
||||||
modifiers: [
|
|
||||||
{ name: "flip", options: { boundary: node } },
|
|
||||||
{ name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
layoutsPopper.setOptions({
|
|
||||||
modifiers: [
|
|
||||||
{ name: "flip", options: { boundary: node } },
|
|
||||||
{ name: "offset", options: { offset: [0, -130 * zoom] } },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
plusesPopper.setOptions({
|
|
||||||
modifiers: [
|
|
||||||
{ name: "flip", options: { boundary: node } },
|
|
||||||
{ name: "offset", options: { offset: [0, 0 * zoom] } },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
gearsPopper?.setOptions({
|
|
||||||
modifiers: [
|
|
||||||
{ name: "flip", options: { boundary: node } },
|
|
||||||
{ name: "offset", options: { offset: [0, 0] } },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
layoutsContainer.current
|
|
||||||
?.querySelectorAll("#popper-layouts > .popper-layout")
|
|
||||||
.forEach((item) => {
|
|
||||||
const element = item as HTMLDivElement;
|
|
||||||
element.style.width = `${130 * zoom}px`;
|
|
||||||
element.style.height = `${130 * zoom}px`;
|
|
||||||
});
|
|
||||||
|
|
||||||
plusesContainer.current
|
|
||||||
?.querySelectorAll("#popper-pluses > .popper-plus")
|
|
||||||
.forEach((item) => {
|
|
||||||
const element = item as HTMLDivElement;
|
|
||||||
element.style.width = `${40 * zoom}px`;
|
|
||||||
element.style.height = `${40 * zoom}px`;
|
|
||||||
element.style.fontSize = `${40 * zoom}px`;
|
|
||||||
element.style.borderRadius = `${6 * zoom}px`;
|
|
||||||
});
|
|
||||||
|
|
||||||
crossesContainer.current
|
|
||||||
?.querySelectorAll("#popper-crosses > .popper-cross")
|
|
||||||
.forEach((item) => {
|
|
||||||
const element = item as HTMLDivElement;
|
|
||||||
element.style.width = `${24 * zoom}px`;
|
|
||||||
element.style.height = `${24 * zoom}px`;
|
|
||||||
element.style.fontSize = `${24 * zoom}px`;
|
|
||||||
element.style.borderRadius = `${6 * zoom}px`;
|
|
||||||
});
|
|
||||||
|
|
||||||
gearsContainer?.current
|
|
||||||
?.querySelectorAll("#popper-gears > .popper-gear")
|
|
||||||
.forEach((item) => {
|
|
||||||
const element = item as HTMLDivElement;
|
|
||||||
element.style.width = `${60 * zoom}px`;
|
|
||||||
element.style.height = `${40 * zoom}px`;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//node?.on("position", update);
|
|
||||||
let pressed = false
|
|
||||||
let hide = false
|
|
||||||
cy?.on('mousedown', () => { pressed = true })
|
|
||||||
cy?.on('mouseup', () => {
|
|
||||||
pressed = false
|
|
||||||
hide = false
|
|
||||||
|
|
||||||
|
|
||||||
const gc = gearsContainer.current
|
|
||||||
if (gc) gc.style.display = 'block'
|
|
||||||
const pc = plusesContainer.current
|
|
||||||
const xc = crossesContainer.current
|
|
||||||
const lc = layoutsContainer.current
|
|
||||||
if (pc) pc.style.display = 'block'
|
|
||||||
if (xc) xc.style.display = 'block'
|
|
||||||
if (lc) lc.style.display = 'block'
|
|
||||||
update()
|
|
||||||
})
|
|
||||||
|
|
||||||
cy?.on('mousemove', () => {
|
|
||||||
if (pressed && !hide) {
|
|
||||||
hide = true
|
|
||||||
const gc = gearsContainer.current
|
|
||||||
if (gc) gc.style.display = 'none'
|
|
||||||
const pc = plusesContainer.current
|
|
||||||
const xc = crossesContainer.current
|
|
||||||
const lc = layoutsContainer.current
|
|
||||||
if (pc) pc.style.display = 'none'
|
|
||||||
if (xc) xc.style.display = 'none'
|
|
||||||
if (lc) lc.style.display = 'block'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cy?.on("zoom render", onZoom);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -818,19 +258,20 @@ function CsComponent({
|
|||||||
// elements={createGraphElements(tree, quiz)}
|
// elements={createGraphElements(tree, quiz)}
|
||||||
style={{ height: "480px", background: "#F2F3F7" }}
|
style={{ height: "480px", background: "#F2F3F7" }}
|
||||||
stylesheet={stylesheet}
|
stylesheet={stylesheet}
|
||||||
layout={(lyopts)}
|
layout={(layoutOptions)}
|
||||||
cy={(cy) => {
|
cy={(cy) => {
|
||||||
cyRef.current = cy;
|
cyRef.current = cy;
|
||||||
}}
|
}}
|
||||||
autoungrabify={true}
|
autoungrabify={true}
|
||||||
/>
|
/>
|
||||||
|
<DeleteNodeModal />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function Clear() {
|
function Clear() {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
updateRootContentId(quiz.id, "")
|
updateRootContentId(quiz?.id, "")
|
||||||
clearRuleForAll()
|
clearRuleForAll()
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Box } from "@mui/material"
|
import { Box } from "@mui/material"
|
||||||
import { useEffect, useRef, useLayoutEffect } from "react";
|
import { useEffect, useRef, useLayoutEffect } from "react";
|
||||||
import { deleteQuestion, clearRuleForAll, updateQuestion } from "@root/questions/actions"
|
import { deleteQuestion, clearRuleForAll, updateQuestion, createResult } from "@root/questions/actions"
|
||||||
import { updateOpenedModalSettingsId } from "@root/uiTools/actions"
|
import { updateOpenedModalSettingsId } from "@root/uiTools/actions"
|
||||||
import { updateRootContentId } from "@root/quizes/actions"
|
import { updateRootContentId } from "@root/quizes/actions"
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||||
@ -34,11 +34,7 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon
|
|||||||
if (dragQuestionContentId) {
|
if (dragQuestionContentId) {
|
||||||
updateRootContentId(quiz?.id, dragQuestionContentId)
|
updateRootContentId(quiz?.id, dragQuestionContentId)
|
||||||
updateQuestion(dragQuestionContentId, (question) => question.content.rule.parentId = "root")
|
updateQuestion(dragQuestionContentId, (question) => question.content.rule.parentId = "root")
|
||||||
//если были результаты - удалить
|
createResult(quiz?.backendId, dragQuestionContentId)
|
||||||
questions.forEach((q) => {
|
|
||||||
if (q.type === 'result') deleteQuestion(q.id)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
enqueueSnackbar("Нет информации о взятом опроснике")
|
enqueueSnackbar("Нет информации о взятом опроснике")
|
||||||
@ -61,10 +57,7 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon
|
|||||||
if (modalQuestionTargetContentId) {
|
if (modalQuestionTargetContentId) {
|
||||||
updateRootContentId(quiz?.id, modalQuestionTargetContentId)
|
updateRootContentId(quiz?.id, modalQuestionTargetContentId)
|
||||||
updateQuestion(modalQuestionTargetContentId, (question) => question.content.rule.parentId = "root")
|
updateQuestion(modalQuestionTargetContentId, (question) => question.content.rule.parentId = "root")
|
||||||
//если были результаты - удалить
|
createResult(quiz?.backendId, modalQuestionTargetContentId)
|
||||||
questions.forEach((q) => {
|
|
||||||
if (q.type === 'result') deleteQuestion(q.id)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
enqueueSnackbar("Нет информации о взятом опроснике")
|
enqueueSnackbar("Нет информации о взятом опроснике")
|
||||||
@ -90,4 +83,4 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon
|
|||||||
+
|
+
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
480
src/pages/Questions/BranchingMap/hooks/usePopper.ts
Normal file
480
src/pages/Questions/BranchingMap/hooks/usePopper.ts
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
||||||
|
|
||||||
|
import type { MutableRefObject } from "react";
|
||||||
|
import type {
|
||||||
|
PresetLayoutOptions,
|
||||||
|
LayoutEventObject,
|
||||||
|
NodeSingular,
|
||||||
|
AbstractEventObject,
|
||||||
|
} from "cytoscape";
|
||||||
|
|
||||||
|
type usePopperArgs = {
|
||||||
|
layoutsContainer: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
plusesContainer: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
crossesContainer: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
gearsContainer: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
setModalQuestionParentContentId: (id: string) => void;
|
||||||
|
setOpenedModalQuestions: (open: boolean) => void;
|
||||||
|
setStartCreate: (id: string) => void;
|
||||||
|
setStartRemove: (id: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PopperItem = {
|
||||||
|
id: () => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Modifier = {
|
||||||
|
name: string;
|
||||||
|
options: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PopperConfig = {
|
||||||
|
popper: {
|
||||||
|
placement: string;
|
||||||
|
modifiers?: Modifier[];
|
||||||
|
};
|
||||||
|
content: (items: PopperItem[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Popper = {
|
||||||
|
update: () => Promise<void>;
|
||||||
|
setOptions: (modifiers: { modifiers?: Modifier[] }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NodeSingularWithPopper = NodeSingular & {
|
||||||
|
popper: (config: PopperConfig) => Popper;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePopper = ({
|
||||||
|
layoutsContainer,
|
||||||
|
plusesContainer,
|
||||||
|
crossesContainer,
|
||||||
|
gearsContainer,
|
||||||
|
setModalQuestionParentContentId,
|
||||||
|
setOpenedModalQuestions,
|
||||||
|
setStartCreate,
|
||||||
|
setStartRemove,
|
||||||
|
}: usePopperArgs) => {
|
||||||
|
const removeButtons = (id: string) => {
|
||||||
|
layoutsContainer.current
|
||||||
|
?.querySelector(`.popper-layout[data-id='${id}']`)
|
||||||
|
?.remove();
|
||||||
|
plusesContainer.current
|
||||||
|
?.querySelector(`.popper-plus[data-id='${id}']`)
|
||||||
|
?.remove();
|
||||||
|
crossesContainer.current
|
||||||
|
?.querySelector(`.popper-cross[data-id='${id}']`)
|
||||||
|
?.remove();
|
||||||
|
gearsContainer.current
|
||||||
|
?.querySelector(`.popper-gear[data-id='${id}']`)
|
||||||
|
?.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialPopperIcons = ({ cy }: LayoutEventObject) => {
|
||||||
|
const container =
|
||||||
|
(document.body.querySelector(
|
||||||
|
".__________cytoscape_container"
|
||||||
|
) as HTMLDivElement) || null;
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.style.overflow = "hidden";
|
||||||
|
|
||||||
|
if (!plusesContainer.current) {
|
||||||
|
plusesContainer.current = document.createElement("div");
|
||||||
|
plusesContainer.current.setAttribute("id", "popper-pluses");
|
||||||
|
container.append(plusesContainer.current);
|
||||||
|
}
|
||||||
|
if (!crossesContainer.current) {
|
||||||
|
crossesContainer.current = document.createElement("div");
|
||||||
|
crossesContainer.current.setAttribute("id", "popper-crosses");
|
||||||
|
container.append(crossesContainer.current);
|
||||||
|
}
|
||||||
|
if (!gearsContainer.current) {
|
||||||
|
gearsContainer.current = document.createElement("div");
|
||||||
|
gearsContainer.current.setAttribute("id", "popper-gears");
|
||||||
|
container.append(gearsContainer.current);
|
||||||
|
}
|
||||||
|
if (!layoutsContainer.current) {
|
||||||
|
layoutsContainer.current = document.createElement("div");
|
||||||
|
layoutsContainer.current.setAttribute("id", "popper-layouts");
|
||||||
|
container.append(layoutsContainer.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = cy.extent();
|
||||||
|
const nodesInView = cy.nodes().filter((n) => {
|
||||||
|
const bb = n.boundingBox();
|
||||||
|
return (
|
||||||
|
bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
nodesInView.toArray()?.forEach((item) => {
|
||||||
|
const node = item as NodeSingularWithPopper;
|
||||||
|
|
||||||
|
const layoutsPopper = node.popper({
|
||||||
|
popper: {
|
||||||
|
placement: "left",
|
||||||
|
modifiers: [{ name: "flip", options: { boundary: node } }],
|
||||||
|
},
|
||||||
|
content: ([item]) => {
|
||||||
|
const itemId = item.id();
|
||||||
|
const itemElement = layoutsContainer.current?.querySelector(
|
||||||
|
`.popper-layout[data-id='${itemId}']`
|
||||||
|
);
|
||||||
|
if (itemElement) {
|
||||||
|
return itemElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutElement = document.createElement("div");
|
||||||
|
layoutElement.style.zIndex = "0";
|
||||||
|
layoutElement.classList.add("popper-layout");
|
||||||
|
layoutElement.setAttribute("data-id", item.id());
|
||||||
|
layoutElement.addEventListener("mouseup", () => {
|
||||||
|
//Узнаём грани, идущие от этой ноды
|
||||||
|
setModalQuestionParentContentId(item.id());
|
||||||
|
setOpenedModalQuestions(true);
|
||||||
|
});
|
||||||
|
layoutsContainer.current?.appendChild(layoutElement);
|
||||||
|
|
||||||
|
return layoutElement;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const plusesPopper = node.popper({
|
||||||
|
popper: {
|
||||||
|
placement: "right",
|
||||||
|
modifiers: [{ name: "flip", options: { boundary: node } }],
|
||||||
|
},
|
||||||
|
content: ([item]) => {
|
||||||
|
const itemId = item.id();
|
||||||
|
const itemElement = plusesContainer.current?.querySelector(
|
||||||
|
`.popper-plus[data-id='${itemId}']`
|
||||||
|
);
|
||||||
|
if (itemElement) {
|
||||||
|
return itemElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plusElement = document.createElement("div");
|
||||||
|
plusElement.classList.add("popper-plus");
|
||||||
|
plusElement.setAttribute("data-id", item.id());
|
||||||
|
plusElement.style.zIndex = "1";
|
||||||
|
plusElement.addEventListener("mouseup", () => {
|
||||||
|
setStartCreate(node.id());
|
||||||
|
});
|
||||||
|
|
||||||
|
plusesContainer.current?.appendChild(plusElement);
|
||||||
|
|
||||||
|
return plusElement;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const crossesPopper = node.popper({
|
||||||
|
popper: {
|
||||||
|
placement: "top-end",
|
||||||
|
modifiers: [{ name: "flip", options: { boundary: node } }],
|
||||||
|
},
|
||||||
|
content: ([item]) => {
|
||||||
|
const itemId = item.id();
|
||||||
|
const itemElement = crossesContainer.current?.querySelector(
|
||||||
|
`.popper-cross[data-id='${itemId}']`
|
||||||
|
);
|
||||||
|
if (itemElement) {
|
||||||
|
return itemElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const crossElement = document.createElement("div");
|
||||||
|
crossElement.classList.add("popper-cross");
|
||||||
|
crossElement.setAttribute("data-id", item.id());
|
||||||
|
crossElement.style.zIndex = "2";
|
||||||
|
crossesContainer.current?.appendChild(crossElement);
|
||||||
|
crossElement.addEventListener("mouseup", () => {
|
||||||
|
setStartRemove(node.id());
|
||||||
|
});
|
||||||
|
|
||||||
|
return crossElement;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let gearsPopper: Popper | null = null;
|
||||||
|
if (node.data().root !== true) {
|
||||||
|
gearsPopper = node.popper({
|
||||||
|
popper: {
|
||||||
|
placement: "left",
|
||||||
|
modifiers: [{ name: "flip", options: { boundary: node } }],
|
||||||
|
},
|
||||||
|
content: ([item]) => {
|
||||||
|
const itemId = item.id();
|
||||||
|
|
||||||
|
const itemElement = gearsContainer.current?.querySelector(
|
||||||
|
`.popper-gear[data-id='${itemId}']`
|
||||||
|
);
|
||||||
|
if (itemElement) {
|
||||||
|
return itemElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gearElement = document.createElement("div");
|
||||||
|
gearElement.classList.add("popper-gear");
|
||||||
|
gearElement.setAttribute("data-id", item.id());
|
||||||
|
gearElement.style.zIndex = "1";
|
||||||
|
gearsContainer.current?.appendChild(gearElement);
|
||||||
|
gearElement.addEventListener("mouseup", () => {
|
||||||
|
console.log("up");
|
||||||
|
updateOpenedModalSettingsId(item.id());
|
||||||
|
});
|
||||||
|
|
||||||
|
return gearElement;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const update = async () => {
|
||||||
|
await plusesPopper.update();
|
||||||
|
await crossesPopper.update();
|
||||||
|
await gearsPopper?.update();
|
||||||
|
await layoutsPopper.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onZoom = (event: AbstractEventObject) => {
|
||||||
|
const zoom = event.cy.zoom();
|
||||||
|
|
||||||
|
//update();
|
||||||
|
|
||||||
|
crossesPopper.setOptions({
|
||||||
|
modifiers: [
|
||||||
|
{ name: "flip", options: { boundary: node } },
|
||||||
|
{ name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
layoutsPopper.setOptions({
|
||||||
|
modifiers: [
|
||||||
|
{ name: "flip", options: { boundary: node } },
|
||||||
|
{ name: "offset", options: { offset: [0, -130 * zoom] } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
plusesPopper.setOptions({
|
||||||
|
modifiers: [
|
||||||
|
{ name: "flip", options: { boundary: node } },
|
||||||
|
{ name: "offset", options: { offset: [0, 0 * zoom] } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
gearsPopper?.setOptions({
|
||||||
|
modifiers: [
|
||||||
|
{ name: "flip", options: { boundary: node } },
|
||||||
|
{ name: "offset", options: { offset: [0, 0] } },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
layoutsContainer.current
|
||||||
|
?.querySelectorAll("#popper-layouts > .popper-layout")
|
||||||
|
.forEach((item) => {
|
||||||
|
const element = item as HTMLDivElement;
|
||||||
|
element.style.width = `${130 * zoom}px`;
|
||||||
|
element.style.height = `${130 * zoom}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
plusesContainer.current
|
||||||
|
?.querySelectorAll("#popper-pluses > .popper-plus")
|
||||||
|
.forEach((item) => {
|
||||||
|
const element = item as HTMLDivElement;
|
||||||
|
element.style.width = `${40 * zoom}px`;
|
||||||
|
element.style.height = `${40 * zoom}px`;
|
||||||
|
element.style.fontSize = `${40 * zoom}px`;
|
||||||
|
element.style.borderRadius = `${6 * zoom}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
crossesContainer.current
|
||||||
|
?.querySelectorAll("#popper-crosses > .popper-cross")
|
||||||
|
.forEach((item) => {
|
||||||
|
const element = item as HTMLDivElement;
|
||||||
|
element.style.width = `${24 * zoom}px`;
|
||||||
|
element.style.height = `${24 * zoom}px`;
|
||||||
|
element.style.fontSize = `${24 * zoom}px`;
|
||||||
|
element.style.borderRadius = `${6 * zoom}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
gearsContainer?.current
|
||||||
|
?.querySelectorAll("#popper-gears > .popper-gear")
|
||||||
|
.forEach((item) => {
|
||||||
|
const element = item as HTMLDivElement;
|
||||||
|
element.style.width = `${60 * zoom}px`;
|
||||||
|
element.style.height = `${40 * zoom}px`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//node?.on("position", update);
|
||||||
|
let pressed = false;
|
||||||
|
let hide = false;
|
||||||
|
cy?.on("mousedown", () => {
|
||||||
|
pressed = true;
|
||||||
|
});
|
||||||
|
cy?.on("mouseup", () => {
|
||||||
|
pressed = false;
|
||||||
|
hide = false;
|
||||||
|
|
||||||
|
const gc = gearsContainer.current;
|
||||||
|
if (gc) gc.style.display = "block";
|
||||||
|
const pc = plusesContainer.current;
|
||||||
|
const xc = crossesContainer.current;
|
||||||
|
const lc = layoutsContainer.current;
|
||||||
|
if (pc) pc.style.display = "block";
|
||||||
|
if (xc) xc.style.display = "block";
|
||||||
|
if (lc) lc.style.display = "block";
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
|
cy?.on("mousemove", () => {
|
||||||
|
if (pressed && !hide) {
|
||||||
|
hide = true;
|
||||||
|
const gc = gearsContainer.current;
|
||||||
|
if (gc) gc.style.display = "none";
|
||||||
|
const pc = plusesContainer.current;
|
||||||
|
const xc = crossesContainer.current;
|
||||||
|
const lc = layoutsContainer.current;
|
||||||
|
if (pc) pc.style.display = "none";
|
||||||
|
if (xc) xc.style.display = "none";
|
||||||
|
if (lc) lc.style.display = "block";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy?.on("zoom render", onZoom);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const readyLO = (event: LayoutEventObject) => {
|
||||||
|
if (event.cy.data("firstNode") === "nonroot") {
|
||||||
|
event.cy.data("firstNode", "root");
|
||||||
|
event.cy
|
||||||
|
.nodes()
|
||||||
|
.sort((a, b) => (a.data("root") ? 1 : -1))
|
||||||
|
.layout(layoutOptions)
|
||||||
|
.run();
|
||||||
|
} else {
|
||||||
|
event.cy.data("changed", false);
|
||||||
|
event.cy.removeData("firstNode");
|
||||||
|
}
|
||||||
|
|
||||||
|
//удаляем иконки
|
||||||
|
event.cy.nodes().forEach((ele: any) => {
|
||||||
|
const data = ele.data();
|
||||||
|
data.id && removeButtons(data.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
initialPopperIcons(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const layoutOptions: PresetLayoutOptions = {
|
||||||
|
name: "preset",
|
||||||
|
|
||||||
|
positions: (node) => {
|
||||||
|
if (!node.cy().data("changed")) {
|
||||||
|
return node.data("oldPos");
|
||||||
|
}
|
||||||
|
const id = node.id();
|
||||||
|
const incomming = node.cy().edges(`[target="${id}"]`);
|
||||||
|
const layer = 0;
|
||||||
|
node.removeData("lastChild");
|
||||||
|
|
||||||
|
if (incomming.length === 0) {
|
||||||
|
if (node.cy().data("firstNode") === undefined)
|
||||||
|
node.cy().data("firstNode", "root");
|
||||||
|
node.data("root", true);
|
||||||
|
const children = node.cy().edges(`[source="${id}"]`).targets();
|
||||||
|
node.data("layer", layer);
|
||||||
|
node.data("children", children.length);
|
||||||
|
const queue = [];
|
||||||
|
children.forEach((n) => {
|
||||||
|
queue.push({ task: n, layer: layer + 1 });
|
||||||
|
});
|
||||||
|
while (queue.length) {
|
||||||
|
const task = queue.pop();
|
||||||
|
task.task.data("layer", task.layer);
|
||||||
|
task.task.removeData("subtreeWidth");
|
||||||
|
const children = node
|
||||||
|
.cy()
|
||||||
|
.edges(`[source="${task.task.id()}"]`)
|
||||||
|
.targets();
|
||||||
|
task.task.data("children", children.length);
|
||||||
|
if (children.length !== 0) {
|
||||||
|
children.forEach((n) =>
|
||||||
|
queue.push({ task: n, layer: task.layer + 1 })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queue.push({ parent: node, children: children });
|
||||||
|
while (queue.length) {
|
||||||
|
const task = queue.pop();
|
||||||
|
if (task.children.length === 0) {
|
||||||
|
task.parent.data("subtreeWidth", task.parent.height() + 50);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const unprocessed = task?.children.filter((node) => {
|
||||||
|
return node.data("subtreeWidth") === undefined;
|
||||||
|
});
|
||||||
|
if (unprocessed.length !== 0) {
|
||||||
|
queue.push(task);
|
||||||
|
unprocessed.forEach((t) => {
|
||||||
|
queue.push({
|
||||||
|
parent: t,
|
||||||
|
children: t.cy().edges(`[source="${t.id()}"]`).targets(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
task?.parent.data(
|
||||||
|
"subtreeWidth",
|
||||||
|
task.children.reduce((p, n) => p + n.data("subtreeWidth"), 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pos = { x: 0, y: 0 };
|
||||||
|
node.data("oldPos", pos);
|
||||||
|
|
||||||
|
queue.push({ task: children, parent: node });
|
||||||
|
while (queue.length) {
|
||||||
|
const task = queue.pop();
|
||||||
|
const oldPos = task.parent.data("oldPos");
|
||||||
|
let yoffset = oldPos.y - task.parent.data("subtreeWidth") / 2;
|
||||||
|
task.task.forEach((n) => {
|
||||||
|
const width = n.data("subtreeWidth");
|
||||||
|
|
||||||
|
n.data("oldPos", {
|
||||||
|
x: 250 * n.data("layer"),
|
||||||
|
y: yoffset + width / 2,
|
||||||
|
});
|
||||||
|
yoffset += width;
|
||||||
|
queue.push({
|
||||||
|
task: n.cy().edges(`[source="${n.id()}"]`).targets(),
|
||||||
|
parent: n,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
node.cy().data("changed", false);
|
||||||
|
return pos;
|
||||||
|
} else {
|
||||||
|
const opos = node.data("oldPos");
|
||||||
|
if (opos) {
|
||||||
|
return opos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, // map of (node id) => (position obj); or function(node){ return somPos; }
|
||||||
|
zoom: undefined, // the zoom level to set (prob want fit = false if set)
|
||||||
|
pan: 1, // the pan level to set (prob want fit = false if set)
|
||||||
|
fit: false, // whether to fit to viewport
|
||||||
|
padding: 30, // padding on fit
|
||||||
|
animate: false, // whether to transition the node positions
|
||||||
|
animationDuration: 500, // duration of animation in ms if enabled
|
||||||
|
animationEasing: undefined, // easing of animation if enabled
|
||||||
|
animateFilter: function (node, i) {
|
||||||
|
return false;
|
||||||
|
}, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
|
||||||
|
ready: readyLO, // callback on layoutready
|
||||||
|
transform: function (node, position) {
|
||||||
|
return position;
|
||||||
|
}, // transform a given node position. Useful for changing flow direction in discrete layouts
|
||||||
|
};
|
||||||
|
|
||||||
|
return { layoutOptions };
|
||||||
|
};
|
205
src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts
Normal file
205
src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import {
|
||||||
|
deleteQuestion,
|
||||||
|
updateQuestion,
|
||||||
|
getQuestionByContentId,
|
||||||
|
clearRuleForAll,
|
||||||
|
} from "@root/questions/actions";
|
||||||
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import { updateRootContentId } from "@root/quizes/actions";
|
||||||
|
|
||||||
|
import type { MutableRefObject } from "react";
|
||||||
|
import type {
|
||||||
|
Core,
|
||||||
|
CollectionReturnValue,
|
||||||
|
PresetLayoutOptions,
|
||||||
|
} from "cytoscape";
|
||||||
|
import type {
|
||||||
|
QuestionBranchingRule,
|
||||||
|
QuestionBranchingRuleMain,
|
||||||
|
} from "../../../../model/questionTypes/shared";
|
||||||
|
|
||||||
|
type UseRemoveNodeArgs = {
|
||||||
|
cyRef: MutableRefObject<Core | null>;
|
||||||
|
layoutOptions: PresetLayoutOptions;
|
||||||
|
layoutsContainer: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
plusesContainer: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
crossesContainer: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
gearsContainer: MutableRefObject<HTMLDivElement | null>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRemoveNode = ({
|
||||||
|
cyRef,
|
||||||
|
layoutOptions,
|
||||||
|
layoutsContainer,
|
||||||
|
plusesContainer,
|
||||||
|
crossesContainer,
|
||||||
|
gearsContainer,
|
||||||
|
}: UseRemoveNodeArgs) => {
|
||||||
|
const { questions: trashQuestions } = useQuestionsStore();
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
|
const removeButtons = (id: string) => {
|
||||||
|
layoutsContainer.current
|
||||||
|
?.querySelector(`.popper-layout[data-id='${id}']`)
|
||||||
|
?.remove();
|
||||||
|
plusesContainer.current
|
||||||
|
?.querySelector(`.popper-plus[data-id='${id}']`)
|
||||||
|
?.remove();
|
||||||
|
crossesContainer.current
|
||||||
|
?.querySelector(`.popper-cross[data-id='${id}']`)
|
||||||
|
?.remove();
|
||||||
|
gearsContainer.current
|
||||||
|
?.querySelector(`.popper-gear[data-id='${id}']`)
|
||||||
|
?.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearDataAfterRemoveNode = ({
|
||||||
|
targetQuestionContentId,
|
||||||
|
parentQuestionContentId,
|
||||||
|
}: {
|
||||||
|
targetQuestionContentId: string;
|
||||||
|
parentQuestionContentId: string;
|
||||||
|
}) => {
|
||||||
|
updateQuestion(targetQuestionContentId, (question) => {
|
||||||
|
question.content.rule.parentId = "";
|
||||||
|
question.content.rule.children = [];
|
||||||
|
question.content.rule.main = [];
|
||||||
|
question.content.rule.default = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
//чистим rule родителя
|
||||||
|
const parentQuestion = getQuestionByContentId(parentQuestionContentId);
|
||||||
|
|
||||||
|
if (!parentQuestion?.type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newChildren = [...parentQuestion.content.rule.children];
|
||||||
|
newChildren.splice(
|
||||||
|
parentQuestion.content.rule.children.indexOf(targetQuestionContentId),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
const newRule: QuestionBranchingRule = {
|
||||||
|
children: newChildren,
|
||||||
|
default:
|
||||||
|
parentQuestion.content.rule.default === targetQuestionContentId
|
||||||
|
? ""
|
||||||
|
: parentQuestion.content.rule.default,
|
||||||
|
//удаляем условия перехода от родителя к этому вопросу,
|
||||||
|
main: parentQuestion.content.rule.main.filter(
|
||||||
|
(data: QuestionBranchingRuleMain) =>
|
||||||
|
data.next !== targetQuestionContentId
|
||||||
|
),
|
||||||
|
parentId: parentQuestion.content.rule.parentId,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateQuestion(parentQuestionContentId, (PQ) => {
|
||||||
|
PQ.content.rule = newRule;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeNode = (targetNodeContentId: string) => {
|
||||||
|
const deleteNodes: string[] = [];
|
||||||
|
const deleteEdges: any = [];
|
||||||
|
const cy = cyRef?.current;
|
||||||
|
|
||||||
|
const findChildrenToDelete = (node: CollectionReturnValue) => {
|
||||||
|
//Узнаём грани, идущие от этой ноды
|
||||||
|
cy?.$('edge[source = "' + node.id() + '"]')
|
||||||
|
?.toArray()
|
||||||
|
.forEach((edge) => {
|
||||||
|
const edgeData = edge.data();
|
||||||
|
|
||||||
|
//записываем id грани для дальнейшего удаления
|
||||||
|
deleteEdges.push(edge);
|
||||||
|
//ищем ноду на конце грани, записываем её ID для дальнейшего удаления
|
||||||
|
const targetNode = cy?.$("#" + edgeData.target);
|
||||||
|
deleteNodes.push(targetNode.data().id);
|
||||||
|
//вызываем функцию для анализа потомков уже у этой ноды
|
||||||
|
findChildrenToDelete(targetNode);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const elementToDelete = cy?.getElementById(targetNodeContentId);
|
||||||
|
|
||||||
|
if (elementToDelete) {
|
||||||
|
findChildrenToDelete(elementToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetQuestion = getQuestionByContentId(targetNodeContentId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
targetQuestion?.type &&
|
||||||
|
targetQuestion.content.rule.parentId === "root" &&
|
||||||
|
quiz
|
||||||
|
) {
|
||||||
|
updateRootContentId(quiz?.id, "");
|
||||||
|
updateQuestion(targetNodeContentId, (question) => {
|
||||||
|
question.content.rule.parentId = "";
|
||||||
|
question.content.rule.main = [];
|
||||||
|
question.content.rule.children = [];
|
||||||
|
question.content.rule.default = "";
|
||||||
|
});
|
||||||
|
clearRuleForAll();
|
||||||
|
} else {
|
||||||
|
const parentQuestionContentId = cy
|
||||||
|
?.$('edge[target = "' + targetNodeContentId + '"]')
|
||||||
|
?.toArray()?.[0]
|
||||||
|
?.data()?.source;
|
||||||
|
if (targetNodeContentId && parentQuestionContentId) {
|
||||||
|
if (
|
||||||
|
quiz &&
|
||||||
|
cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0
|
||||||
|
) {
|
||||||
|
//createFrontResult(quiz.backendId, parentQuestionContentId);
|
||||||
|
}
|
||||||
|
clearDataAfterRemoveNode({
|
||||||
|
targetQuestionContentId: targetNodeContentId,
|
||||||
|
parentQuestionContentId,
|
||||||
|
});
|
||||||
|
cy?.remove(cy?.$("#" + targetNodeContentId))
|
||||||
|
.layout(layoutOptions)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке
|
||||||
|
|
||||||
|
deleteNodes.forEach((nodeId) => {
|
||||||
|
//Ноды
|
||||||
|
cy?.remove(cy?.$("#" + nodeId));
|
||||||
|
removeButtons(nodeId);
|
||||||
|
updateQuestion(nodeId, (question) => {
|
||||||
|
question.content.rule.parentId = "";
|
||||||
|
question.content.rule.main = [];
|
||||||
|
question.content.rule.default = "";
|
||||||
|
question.content.rule.children = [];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteEdges.forEach((edge: any) => {
|
||||||
|
//Грани
|
||||||
|
cy?.remove(edge);
|
||||||
|
});
|
||||||
|
|
||||||
|
removeButtons(targetNodeContentId);
|
||||||
|
cy?.data("changed", true);
|
||||||
|
cy?.layout(layoutOptions).run();
|
||||||
|
|
||||||
|
//удаляем result всех потомков
|
||||||
|
trashQuestions.forEach((qr) => {
|
||||||
|
if (
|
||||||
|
qr.type === "result" &&
|
||||||
|
(deleteNodes.includes(qr.content.rule.parentId || "") ||
|
||||||
|
(targetQuestion?.type &&
|
||||||
|
qr.content.rule.parentId === targetQuestion.content.id))
|
||||||
|
) {
|
||||||
|
deleteQuestion(qr.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { removeNode };
|
||||||
|
};
|
@ -2,42 +2,53 @@ import { Box } from "@mui/material";
|
|||||||
import { FirstNodeField } from "./FirstNodeField";
|
import { FirstNodeField } from "./FirstNodeField";
|
||||||
import CsComponent from "./CsComponent";
|
import CsComponent from "./CsComponent";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {BranchingQuestionsModal} from "../BranchingQuestionsModal"
|
import { BranchingQuestionsModal } from "../BranchingQuestionsModal";
|
||||||
import { useUiTools } from "@root/uiTools/store";
|
import { useUiTools } from "@root/uiTools/store";
|
||||||
|
|
||||||
|
|
||||||
export const BranchingMap = () => {
|
export const BranchingMap = () => {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const { dragQuestionContentId } = useUiTools()
|
const { dragQuestionContentId } = useUiTools();
|
||||||
const [modalQuestionParentContentId, setModalQuestionParentContentId] = useState<string>("")
|
const [modalQuestionParentContentId, setModalQuestionParentContentId] =
|
||||||
const [modalQuestionTargetContentId, setModalQuestionTargetContentId] = useState<string>("")
|
useState<string>("");
|
||||||
const [openedModalQuestions, setOpenedModalQuestions] = useState<boolean>(false)
|
const [modalQuestionTargetContentId, setModalQuestionTargetContentId] =
|
||||||
|
useState<string>("");
|
||||||
|
const [openedModalQuestions, setOpenedModalQuestions] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
id="cytoscape-container"
|
id="cytoscape-container"
|
||||||
sx={{
|
sx={{
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
padding: "20px",
|
padding: "20px",
|
||||||
background: "#FFFFFF",
|
background: "#FFFFFF",
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||||
marginBottom: "40px",
|
marginBottom: "40px",
|
||||||
height: "568px",
|
height: "568px",
|
||||||
border: dragQuestionContentId === null ? "none" : "#7e2aea 2px dashed"
|
border: dragQuestionContentId === null ? "none" : "#7e2aea 2px dashed",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{quiz?.config.haveRoot ? (
|
||||||
{
|
<CsComponent
|
||||||
quiz?.config.haveRoot ?
|
modalQuestionParentContentId={modalQuestionParentContentId}
|
||||||
<CsComponent modalQuestionParentContentId={modalQuestionParentContentId} modalQuestionTargetContentId={modalQuestionTargetContentId} setOpenedModalQuestions={setOpenedModalQuestions} setModalQuestionParentContentId={setModalQuestionParentContentId} setModalQuestionTargetContentId={setModalQuestionTargetContentId}/>
|
modalQuestionTargetContentId={modalQuestionTargetContentId}
|
||||||
:
|
setOpenedModalQuestions={setOpenedModalQuestions}
|
||||||
<FirstNodeField setOpenedModalQuestions={setOpenedModalQuestions} modalQuestionTargetContentId={modalQuestionTargetContentId}/>
|
setModalQuestionParentContentId={setModalQuestionParentContentId}
|
||||||
}
|
setModalQuestionTargetContentId={setModalQuestionTargetContentId}
|
||||||
<BranchingQuestionsModal openedModalQuestions={openedModalQuestions} setOpenedModalQuestions={setOpenedModalQuestions} setModalQuestionTargetContentId={setModalQuestionTargetContentId} />
|
/>
|
||||||
</Box>
|
) : (
|
||||||
|
<FirstNodeField
|
||||||
|
setOpenedModalQuestions={setOpenedModalQuestions}
|
||||||
|
modalQuestionTargetContentId={modalQuestionTargetContentId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<BranchingQuestionsModal
|
||||||
|
openedModalQuestions={openedModalQuestions}
|
||||||
|
setOpenedModalQuestions={setOpenedModalQuestions}
|
||||||
|
setModalQuestionTargetContentId={setModalQuestionTargetContentId}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background-image: url("../../../assets/icons/ArrowGear.svg");
|
background-image: url("../../../../assets/icons/ArrowGear.svg");
|
||||||
font-size: 0px;
|
font-size: 0px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
53
src/pages/Questions/BranchingMap/style/stylesheet.ts
Normal file
53
src/pages/Questions/BranchingMap/style/stylesheet.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import type { Stylesheet } from "cytoscape";
|
||||||
|
|
||||||
|
export const stylesheet: Stylesheet[] = [
|
||||||
|
{
|
||||||
|
selector: "node",
|
||||||
|
style: {
|
||||||
|
shape: "round-rectangle",
|
||||||
|
width: 130,
|
||||||
|
height: 130,
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
label: "data(label)",
|
||||||
|
"font-size": "16",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
"text-halign": "center",
|
||||||
|
"text-valign": "center",
|
||||||
|
"text-wrap": "wrap",
|
||||||
|
"text-max-width": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "[?eroticeyeblink]",
|
||||||
|
style: {
|
||||||
|
"border-width": "4px",
|
||||||
|
"border-style": "solid",
|
||||||
|
"border-color": "#7e2aea",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: ".multiline-auto",
|
||||||
|
style: {
|
||||||
|
"text-wrap": "wrap",
|
||||||
|
"text-max-width": "80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "edge",
|
||||||
|
style: {
|
||||||
|
width: 30,
|
||||||
|
"line-color": "#DEDFE7",
|
||||||
|
"curve-style": "taxi",
|
||||||
|
"taxi-direction": "horizontal",
|
||||||
|
"taxi-turn": 60,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: ":selected",
|
||||||
|
style: {
|
||||||
|
"border-style": "solid",
|
||||||
|
"border-width": 1.5,
|
||||||
|
"border-color": "#9A9AAF",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
@ -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>
|
||||||
|
@ -6,14 +6,12 @@ interface Props {
|
|||||||
openedModalQuestions: boolean;
|
openedModalQuestions: boolean;
|
||||||
setModalQuestionTargetContentId: (contentId: string) => void;
|
setModalQuestionTargetContentId: (contentId: string) => void;
|
||||||
setOpenedModalQuestions: (open: boolean) => void;
|
setOpenedModalQuestions: (open: boolean) => void;
|
||||||
setModalQuestionParentContentId: (open: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BranchingQuestionsModal = ({
|
export const BranchingQuestionsModal = ({
|
||||||
openedModalQuestions,
|
openedModalQuestions,
|
||||||
setOpenedModalQuestions,
|
setOpenedModalQuestions,
|
||||||
setModalQuestionTargetContentId,
|
setModalQuestionTargetContentId,
|
||||||
setModalQuestionParentContentId,
|
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const trashQuestions = useQuestionsStore().questions;
|
const trashQuestions = useQuestionsStore().questions;
|
||||||
const questions = trashQuestions.filter(
|
const questions = trashQuestions.filter(
|
||||||
@ -25,10 +23,14 @@ export const BranchingQuestionsModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const typedQuestions: AnyTypedQuizQuestion[] = questions.filter(
|
const typedQuestions: AnyTypedQuizQuestion[] = questions.filter(
|
||||||
(question) => question.type && !question.content.rule.parentId && question.type !== "result"
|
(question) =>
|
||||||
|
question.type &&
|
||||||
|
!question.content.rule.parentId &&
|
||||||
|
question.type !== "result"
|
||||||
) as AnyTypedQuizQuestion[];
|
) as AnyTypedQuizQuestion[];
|
||||||
|
|
||||||
if (typedQuestions.length === 0) return <></>
|
if (typedQuestions.length === 0) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={openedModalQuestions} onClose={handleClose}>
|
<Modal open={openedModalQuestions} onClose={handleClose}>
|
||||||
<Box
|
<Box
|
||||||
@ -44,7 +46,7 @@ export const BranchingQuestionsModal = ({
|
|||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
padding: "30px 0",
|
padding: "30px 0",
|
||||||
height: "80vh"
|
height: "80vh",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ margin: "0 auto", maxWidth: "350px" }}>
|
<Box sx={{ margin: "0 auto", maxWidth: "350px" }}>
|
||||||
|
@ -57,11 +57,6 @@ export default function ButtonsOptions({
|
|||||||
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
|
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
|
||||||
updateRootContentId(quiz.id, "");
|
updateRootContentId(quiz.id, "");
|
||||||
clearRuleForAll();
|
clearRuleForAll();
|
||||||
questions.forEach(q => {
|
|
||||||
if (q.type === "result") {
|
|
||||||
deleteQuestion(q.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
deleteQuestion(question.id);
|
deleteQuestion(question.id);
|
||||||
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
|
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
|
||||||
const clearQuestions = [] as string[];
|
const clearQuestions = [] as string[];
|
||||||
@ -69,10 +64,8 @@ export default function ButtonsOptions({
|
|||||||
//записываем потомков , а их результаты удаляем
|
//записываем потомков , а их результаты удаляем
|
||||||
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
||||||
questions.forEach((targetQuestion) => {
|
questions.forEach((targetQuestion) => {
|
||||||
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
|
if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
|
||||||
if (targetQuestion.type === "result") {
|
if (targetQuestion.type !== "result" && targetQuestion.type !== null) {
|
||||||
deleteQuestion(targetQuestion.id);
|
|
||||||
} else {
|
|
||||||
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
|
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
|
||||||
getChildren(targetQuestion); //и ищем его потомков
|
getChildren(targetQuestion); //и ищем его потомков
|
||||||
}
|
}
|
||||||
|
@ -58,11 +58,6 @@ export default function ButtonsOptionsAndPict({
|
|||||||
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
|
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
|
||||||
updateRootContentId(quiz.id, "");
|
updateRootContentId(quiz.id, "");
|
||||||
clearRuleForAll();
|
clearRuleForAll();
|
||||||
questions.forEach(q => {
|
|
||||||
if (q.type === "result") {
|
|
||||||
deleteQuestion(q.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
deleteQuestion(question.id);
|
deleteQuestion(question.id);
|
||||||
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
|
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
|
||||||
const clearQuestions = [] as string[];
|
const clearQuestions = [] as string[];
|
||||||
@ -71,9 +66,7 @@ export default function ButtonsOptionsAndPict({
|
|||||||
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
||||||
questions.forEach((targetQuestion) => {
|
questions.forEach((targetQuestion) => {
|
||||||
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
|
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
|
||||||
if (targetQuestion.type === "result") {
|
if (targetQuestion.type !== null && targetQuestion.type !== "result") {
|
||||||
deleteQuestion(targetQuestion.id);
|
|
||||||
} else {
|
|
||||||
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
|
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
|
||||||
getChildren(targetQuestion); //и ищем его потомков
|
getChildren(targetQuestion); //и ищем его потомков
|
||||||
}
|
}
|
||||||
|
122
src/pages/Questions/DeleteNodeModal/index.tsx
Normal file
122
src/pages/Questions/DeleteNodeModal/index.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { useState, useRef, useEffect, useLayoutEffect } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
Link,
|
||||||
|
Modal,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
Checkbox,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import {
|
||||||
|
AnyTypedQuizQuestion,
|
||||||
|
createBranchingRuleMain,
|
||||||
|
} from "../../../model/questionTypes/shared";
|
||||||
|
import { Select } from "../Select";
|
||||||
|
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
import InfoIcon from "@icons/Info";
|
||||||
|
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getQuestionById,
|
||||||
|
getQuestionByContentId,
|
||||||
|
updateQuestion,
|
||||||
|
} from "@root/questions/actions";
|
||||||
|
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
||||||
|
import { useUiTools } from "@root/uiTools/store";
|
||||||
|
|
||||||
|
export const DeleteNodeModal = () => {
|
||||||
|
const { deleteNodeId } = useUiTools();
|
||||||
|
const targetQuestion = getQuestionById(deleteNodeId);
|
||||||
|
|
||||||
|
const saveData = () => {
|
||||||
|
// if (parentQuestion !== null) {
|
||||||
|
// updateQuestion(
|
||||||
|
// parentQuestion.content.id,
|
||||||
|
// (question) => (question.content = parentQuestion.content)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
updateOpenedModalSettingsId();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal open={!!deleteNodeId} onClose={handleClose}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
overflow: "hidden",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
maxWidth: "620px",
|
||||||
|
width: "100%",
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
borderRadius: "12px",
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
boxSizing: "border-box",
|
||||||
|
background: "#F2F3F7",
|
||||||
|
height: "70px",
|
||||||
|
padding: "0 25px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ color: "#4d4d4d" }}>
|
||||||
|
<Typography component="span">{targetQuestion?.title}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Tooltip
|
||||||
|
title="Настройте условия, при которых данный вопрос будет отображаться в квизе."
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<InfoIcon />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "end",
|
||||||
|
gap: "10px",
|
||||||
|
margin: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleClose}
|
||||||
|
sx={{ width: "100%", maxWidth: "130px" }}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ width: "100%", maxWidth: "130px" }}
|
||||||
|
onClick={saveData}
|
||||||
|
>
|
||||||
|
Готово
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -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";
|
||||||
@ -51,9 +64,14 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) {
|
export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) {
|
||||||
|
const maxLengthTextField = 225;
|
||||||
|
|
||||||
const { questions } = useQuestionsStore();
|
const { questions } = useQuestionsStore();
|
||||||
const [plusVisible, setPlusVisible] = useState<boolean>(false);
|
const [plusVisible, setPlusVisible] = useState<boolean>(false);
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [isTextFieldtActive, setIsTextFieldtActive] = useState(false);
|
||||||
|
|
||||||
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
@ -75,21 +93,14 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
updateRootContentId(quiz.id, "");
|
updateRootContentId(quiz.id, "");
|
||||||
clearRuleForAll();
|
clearRuleForAll();
|
||||||
deleteQuestion(question.id);
|
deleteQuestion(question.id);
|
||||||
questions.forEach(q => {
|
|
||||||
if (q.type === "result") {
|
|
||||||
deleteQuestion(q.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
|
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
|
||||||
const clearQuestions = [] as string[];
|
const clearQuestions = [] as string[];
|
||||||
|
|
||||||
//записываем потомков , а их результаты удаляем
|
//записываем потомков , а их результаты удаляем
|
||||||
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
||||||
questions.forEach((targetQuestion) => {
|
questions.forEach((targetQuestion) => {
|
||||||
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
|
if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
|
||||||
if (targetQuestion.type === "result") {
|
if (targetQuestion.type !== null && targetQuestion.type !== "result") {
|
||||||
deleteQuestion(targetQuestion.id);
|
|
||||||
} else {
|
|
||||||
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
|
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id);
|
||||||
getChildren(targetQuestion); //и ищем его потомков
|
getChildren(targetQuestion); //и ищем его потомков
|
||||||
}
|
}
|
||||||
@ -127,328 +138,332 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Paper
|
|
||||||
id={question.id}
|
|
||||||
data-cy="quiz-question-card"
|
|
||||||
sx={{
|
|
||||||
maxWidth: "796px",
|
|
||||||
width: "100%",
|
|
||||||
borderRadius: "12px",
|
|
||||||
backgroundColor: question.expanded ? "white" : "#EEE4FC",
|
|
||||||
border: question.expanded ? "none" : "1px solid #9A9AAF",
|
|
||||||
boxShadow: "0px 10px 30px #e7e7e7",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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 || " ")}
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<Box>
|
|
||||||
<InputAdornment
|
|
||||||
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.type === null) deleteQuestion(question.id)
|
|
||||||
if(question?.content?.rule.parentId.length !== 0) {
|
|
||||||
setOpenDelete(true)
|
|
||||||
} else {
|
|
||||||
deleteQuestionWithTimeout(question.id, deleteFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
}}
|
const handleInputFocus = () => {
|
||||||
data-cy="delete-question"
|
setIsTextFieldtActive(true);
|
||||||
>
|
};
|
||||||
<DeleteIcon
|
|
||||||
style={{ color: theme.palette.brightPurple.main }}
|
const handleInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {
|
||||||
/>
|
setIsTextFieldtActive(false);
|
||||||
</IconButton>
|
};
|
||||||
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
return (
|
||||||
position: "absolute",
|
<>
|
||||||
top: "50%",
|
<Paper
|
||||||
left: "50%",
|
id={question.id}
|
||||||
transform: "translate(-50%, -50%)",
|
data-cy="quiz-question-card"
|
||||||
padding: "30px",
|
sx={{
|
||||||
borderRadius: "10px",
|
maxWidth: "796px",
|
||||||
background: "#FFFFFF",
|
width: "100%",
|
||||||
}}
|
borderRadius: "12px",
|
||||||
>
|
backgroundColor: question.expanded ? "white" : "#EEE4FC",
|
||||||
<Typography variant="h6" sx={{textAlign: "center"}}>
|
border: question.expanded ? "none" : "1px solid #9A9AAF",
|
||||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы уверены, что хотите удалить вопрос?
|
boxShadow: "0px 10px 30px #e7e7e7",
|
||||||
</Typography>
|
}}
|
||||||
<Box
|
>
|
||||||
sx={{
|
<Box
|
||||||
marginTop: "30px",
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
alignItems: "center",
|
||||||
gap: "15px",
|
padding: isMobile ? "10px" : "20px 10px 20px 20px",
|
||||||
}}
|
flexDirection: isMobile ? "column" : null,
|
||||||
>
|
}}
|
||||||
<Button
|
>
|
||||||
variant="contained"
|
<FormControl
|
||||||
sx={{ minWidth: "150px" }}
|
variant="standard"
|
||||||
onClick={() => setOpenDelete(false)}
|
sx={{
|
||||||
>
|
p: 0,
|
||||||
Отмена
|
maxWidth: isTablet ? "549px" : "640px",
|
||||||
</Button>
|
width: "100%",
|
||||||
<Button
|
marginRight: isMobile ? "0px" : "16.1px",
|
||||||
variant="contained"
|
}}
|
||||||
sx={{ minWidth: "150px" }}
|
>
|
||||||
onClick={() => {
|
<TextField
|
||||||
deleteQuestionWithTimeout(question.id, deleteFn);
|
defaultValue={question.title}
|
||||||
}}
|
placeholder={"Заголовок вопроса"}
|
||||||
>
|
onChange={({ target }: { target: HTMLInputElement }) => setTitle(target.value || " ")}
|
||||||
Подтвердить
|
onFocus={handleInputFocus}
|
||||||
</Button>
|
onBlur={handleInputBlur}
|
||||||
</Box>
|
inputProps={{
|
||||||
</Box>
|
maxLength: maxLengthTextField,
|
||||||
</Modal>
|
}}
|
||||||
</Box>
|
InputProps={{
|
||||||
)}
|
startAdornment: (
|
||||||
{question.type !== null &&
|
<Box>
|
||||||
<Box
|
<InputAdornment
|
||||||
style={{
|
ref={anchorRef}
|
||||||
display: "flex",
|
position="start"
|
||||||
alignItems: "center",
|
sx={{ cursor: "pointer" }}
|
||||||
justifyContent: "center",
|
onClick={() => setOpen((isOpened) => !isOpened)}
|
||||||
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 ? (
|
{IconAndrom(question.expanded, question.type)}
|
||||||
<TypeQuestions question={question} />
|
</InputAdornment>
|
||||||
) : (
|
<ChooseAnswerModal
|
||||||
<SwitchQuestionsPage question={question} />
|
open={open}
|
||||||
)}
|
onClose={() => setOpen(false)}
|
||||||
</Box>
|
anchorRef={anchorRef}
|
||||||
)}
|
question={question}
|
||||||
</Paper>
|
questionType={question.type}
|
||||||
<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) => {
|
||||||
|
@ -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} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,23 @@ import OptionsPict from "@icons/questionsPage/options_pict";
|
|||||||
import Page from "@icons/questionsPage/page";
|
import Page from "@icons/questionsPage/page";
|
||||||
import RatingIcon from "@icons/questionsPage/rating";
|
import RatingIcon from "@icons/questionsPage/rating";
|
||||||
import Slider from "@icons/questionsPage/slider";
|
import Slider from "@icons/questionsPage/slider";
|
||||||
import { Box, FormControlLabel, IconButton, InputAdornment, Paper, useMediaQuery, useTheme } from "@mui/material";
|
import {
|
||||||
import { toggleExpandQuestion, updateQuestion, updateUntypedQuestion } from "@root/questions/actions";
|
Box, Checkbox,
|
||||||
|
FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
Paper, TextField,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
copyQuestion,
|
||||||
|
deleteQuestion, deleteQuestionWithTimeout,
|
||||||
|
toggleExpandQuestion,
|
||||||
|
updateQuestion,
|
||||||
|
updateUntypedQuestion
|
||||||
|
} from "@root/questions/actions";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||||
@ -52,150 +67,246 @@ export default function QuestionsPageCard({ question, questionIndex, draggablePr
|
|||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
console.log(question)
|
console.log(question)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Paper
|
<Paper
|
||||||
sx={{
|
sx={{
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
maxWidth: "796px",
|
maxWidth: "796px",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
backgroundColor: "white",
|
backgroundColor: question.expanded ? "white" : "#EEE4FC",
|
||||||
border: "none",
|
border: question.expanded ? "none" : "1px solid #9A9AAF",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
paddingBottom: "20px",
|
paddingBottom: "20px",
|
||||||
borderRadius: "0",
|
borderRadius: "0",
|
||||||
borderTopLeftRadius: "12px",
|
borderTopLeftRadius: "12px",
|
||||||
borderTopRightRadius: "12px",
|
borderTopRightRadius: "12px",
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
p: 0,
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
margin: "20px",
|
|
||||||
gap: "18px",
|
|
||||||
flexDirection: isMobile ? "column-reverse" : null,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CustomTextField
|
|
||||||
placeholder={`Заголовок ${questionIndex + 1} вопроса`}
|
|
||||||
value={question.title}
|
|
||||||
onChange={({ target }) => setTitle(target.value)}
|
|
||||||
sx={{ width: "100%" }}
|
|
||||||
InputProps={{
|
|
||||||
startAdornment: (
|
|
||||||
<Box>
|
|
||||||
<InputAdornment
|
|
||||||
ref={anchorRef}
|
|
||||||
position="start"
|
|
||||||
sx={{ cursor: "pointer" }}
|
|
||||||
onClick={() => setOpen((isOpened) => !isOpened)}
|
|
||||||
>
|
|
||||||
{IconAndrom(question.type)}
|
|
||||||
</InputAdornment>
|
|
||||||
<ChooseAnswerModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => setOpen(false)}
|
|
||||||
anchorRef={anchorRef}
|
|
||||||
question={question}
|
|
||||||
questionType={question.type}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
width: isMobile ? "100%" : "auto",
|
|
||||||
position: "relative",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
flexDirection: isMobile ? "row-reverse" : null,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
style={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
p: 0,
|
||||||
justifyContent: "center",
|
flexDirection: "column",
|
||||||
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,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{questionIndex + 1}
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
margin: "20px",
|
||||||
|
gap: "18px",
|
||||||
|
flexDirection: isMobile ? "column-reverse" : null,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl
|
||||||
|
variant="standard"
|
||||||
|
sx={{
|
||||||
|
p: 0,
|
||||||
|
maxWidth: isTablet ? "549px" : "640px",
|
||||||
|
width: "100%",
|
||||||
|
marginRight: isMobile ? "0px" : "16.1px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
placeholder={`Заголовок ${questionIndex + 1} вопроса`}
|
||||||
|
value={question.title}
|
||||||
|
onChange={({target}) => setTitle(target.value)}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
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={{
|
||||||
|
startAdornment: (
|
||||||
|
<Box>
|
||||||
|
<InputAdornment
|
||||||
|
ref={anchorRef}
|
||||||
|
position="start"
|
||||||
|
sx={{cursor: "pointer"}}
|
||||||
|
onClick={() => setOpen((isOpened) => !isOpened)}
|
||||||
|
>
|
||||||
|
{IconAndrom(question.type)}
|
||||||
|
</InputAdornment>
|
||||||
|
<ChooseAnswerModal
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
anchorRef={anchorRef}
|
||||||
|
question={question}
|
||||||
|
questionType={question.type}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: isMobile ? "100%" : "auto",
|
||||||
|
position: "relative",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexDirection: isMobile ? "row-reverse" : null,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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={() => {
|
||||||
|
deleteQuestionWithTimeout(question.id, deleteQuestion(question.id));
|
||||||
|
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon
|
||||||
|
style={{ color: theme.palette.brightPurple.main }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<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,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{questionIndex + 1}
|
||||||
|
</Box>
|
||||||
|
</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 && (
|
||||||
|
<>
|
||||||
|
{question.type === null ? (
|
||||||
|
<FormTypeQuestions question={question}/>
|
||||||
|
) : (
|
||||||
|
<SwitchQuestionsPage question={question}/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Paper>
|
||||||
|
</>
|
||||||
<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.type === null ? (
|
|
||||||
<FormTypeQuestions question={question} />
|
|
||||||
) : (
|
|
||||||
<SwitchQuestionsPage question={question} />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const IconAndrom = (questionType: QuestionType | null) => {
|
const IconAndrom = (questionType: QuestionType | null) => {
|
||||||
@ -225,4 +336,4 @@ const IconAndrom = (questionType: QuestionType | null) => {
|
|||||||
default:
|
default:
|
||||||
return <AnswerGroup color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
return <AnswerGroup color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import ButtonsOptions from "../ButtonsOptions";
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
import CustomNumberField from "@ui_kit/CustomNumberField";
|
import CustomNumberField from "@ui_kit/CustomNumberField";
|
||||||
@ -16,6 +16,38 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const [stepError, setStepError] = useState("");
|
const [stepError, setStepError] = useState("");
|
||||||
|
const [startError, setStartError] = useState<boolean>(false);
|
||||||
|
const [minError, setMinError] = useState<boolean>(false);
|
||||||
|
const [maxError, setMaxError] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const min = Number(question.content.range.split("—")[0]);
|
||||||
|
const max = Number(question.content.range.split("—")[1]);
|
||||||
|
const start = Number(question.content.start);
|
||||||
|
|
||||||
|
if (start < min || start > max) {
|
||||||
|
setStartError(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start >= min && start <= max) {
|
||||||
|
setStartError(false);
|
||||||
|
}
|
||||||
|
}, [question.content.range, question.content.start]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const min = Number(question.content.range.split("—")[0]);
|
||||||
|
const max = Number(question.content.range.split("—")[1]);
|
||||||
|
const step = Number(question.content.step);
|
||||||
|
const range = max - min;
|
||||||
|
|
||||||
|
if (range % step) {
|
||||||
|
setStepError(
|
||||||
|
`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setStepError("");
|
||||||
|
}
|
||||||
|
}, [question]);
|
||||||
|
|
||||||
const SSHC = (data: string) => {
|
const SSHC = (data: string) => {
|
||||||
setSwitchState(data);
|
setSwitchState(data);
|
||||||
@ -44,42 +76,43 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
marginRight: isMobile ? "10px" : "0px",
|
marginRight: isMobile ? "10px" : "0px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D" }}>
|
<Typography
|
||||||
|
sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D" }}
|
||||||
|
>
|
||||||
Выбор значения из диапазона
|
Выбор значения из диапазона
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ width: "100%", display: "flex", alignItems: "center", gap: isMobile ? "9px" : "20px" }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: isMobile ? "9px" : "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CustomNumberField
|
<CustomNumberField
|
||||||
sx={{ maxWidth: "310px", width: "100%" }}
|
sx={{ maxWidth: "310px", width: "100%" }}
|
||||||
placeholder={"0"}
|
placeholder={"0"}
|
||||||
min={0}
|
min={0}
|
||||||
max={99999999999}
|
max={99999999999}
|
||||||
value={question.content.range.split("—")[0]}
|
value={question.content.range.split("—")[0]}
|
||||||
|
emptyError={minError}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestion(question.id, (question) => {
|
|
||||||
if (question.type !== "number") return;
|
|
||||||
|
|
||||||
question.content.range = `${target.value}—${question.content.range.split("—")[1]}`;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onBlur={({ target }) => {
|
|
||||||
const start = question.content.start;
|
|
||||||
const min = Number(target.value);
|
const min = Number(target.value);
|
||||||
const max = Number(question.content.range.split("—")[1]);
|
const max = Number(question.content.range.split("—")[1]);
|
||||||
|
|
||||||
|
updateQuestion(question.id, (question) => {
|
||||||
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.content.range = `${target.value}—${
|
||||||
|
question.content.range.split("—")[1]
|
||||||
|
}`;
|
||||||
|
});
|
||||||
|
|
||||||
if (min >= max) {
|
if (min >= max) {
|
||||||
updateQuestion(question.id, (question) => {
|
setMinError(true);
|
||||||
if (question.type !== "number") return;
|
} else {
|
||||||
|
setMinError(false);
|
||||||
question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${question.content.range.split("—")[1]}`;
|
setMaxError(false);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start < min) {
|
|
||||||
updateQuestion(question.id, (question) => {
|
|
||||||
if (question.type !== "number") return;
|
|
||||||
|
|
||||||
question.content.start = min;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -90,35 +123,30 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
min={0}
|
min={0}
|
||||||
max={100000000000}
|
max={100000000000}
|
||||||
value={question.content.range.split("—")[1]}
|
value={question.content.range.split("—")[1]}
|
||||||
|
emptyError={maxError}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
|
const min = Number(question.content.range.split("—")[0]);
|
||||||
|
const max = Number(target.value);
|
||||||
|
|
||||||
updateQuestion(question.id, (question) => {
|
updateQuestion(question.id, (question) => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.range = `${question.content.range.split("—")[0]}—${target.value}`;
|
question.content.range = `${
|
||||||
|
question.content.range.split("—")[0]
|
||||||
|
}—${target.value}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (max <= min) {
|
||||||
|
setMaxError(true);
|
||||||
|
} else {
|
||||||
|
setMaxError(false);
|
||||||
|
setMinError(false);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onBlur={({ target }) => {
|
onBlur={({ target }) => {
|
||||||
const start = question.content.start;
|
|
||||||
const step = question.content.step;
|
const step = question.content.step;
|
||||||
const min = Number(question.content.range.split("—")[0]);
|
const min = Number(question.content.range.split("—")[0]);
|
||||||
const max = Number(target.value);
|
const max = Number(target.value);
|
||||||
const range = max - min;
|
|
||||||
|
|
||||||
if (max <= min) {
|
|
||||||
updateQuestion(question.id, (question) => {
|
|
||||||
if (question.type !== "number") return;
|
|
||||||
|
|
||||||
question.content.range = `${min}—${min + 1 >= 100 ? 100 : min + 1}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start > max) {
|
|
||||||
updateQuestion(question.id, (question) => {
|
|
||||||
if (question.type !== "number") return;
|
|
||||||
|
|
||||||
question.content.start = max;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step > max) {
|
if (step > max) {
|
||||||
updateQuestion(question.id, (question) => {
|
updateQuestion(question.id, (question) => {
|
||||||
@ -126,12 +154,6 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
|
|
||||||
question.content.step = min;
|
question.content.step = min;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (range % step) {
|
|
||||||
setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`);
|
|
||||||
} else {
|
|
||||||
setStepError("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -147,15 +169,21 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ width: "100%" }}>
|
<Box sx={{ width: "100%" }}>
|
||||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D", mb: isMobile ? "10px" : "14px" }}>
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontWeight: "500",
|
||||||
|
fontSize: "18px",
|
||||||
|
color: "#4D4D4D",
|
||||||
|
mb: isMobile ? "10px" : "14px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
Начальное значение
|
Начальное значение
|
||||||
</Typography>
|
</Typography>
|
||||||
<CustomNumberField
|
<CustomNumberField
|
||||||
sx={{ maxWidth: "310px", width: "100%" }}
|
sx={{ maxWidth: "310px", width: "100%" }}
|
||||||
placeholder={"50"}
|
placeholder={"50"}
|
||||||
min={Number(question.content.range.split("—")[0])}
|
|
||||||
max={Number(question.content.range.split("—")[1])}
|
|
||||||
value={String(question.content.start)}
|
value={String(question.content.start)}
|
||||||
|
emptyError={startError}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestion(question.id, (question) => {
|
updateQuestion(question.id, (question) => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
@ -178,8 +206,8 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<CustomNumberField
|
<CustomNumberField
|
||||||
sx={{ maxWidth: "310px", width: "100%" }}
|
sx={{ maxWidth: "310px", width: "100%" }}
|
||||||
min={0}
|
min={Number(question.content.range.split("—")[0])}
|
||||||
max={100}
|
max={Number(question.content.range.split("—")[1])}
|
||||||
placeholder={"1"}
|
placeholder={"1"}
|
||||||
error={stepError}
|
error={stepError}
|
||||||
value={String(question.content.step)}
|
value={String(question.content.step)}
|
||||||
@ -191,9 +219,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onBlur={({ target }) => {
|
onBlur={({ target }) => {
|
||||||
const min = Number(question.content.range.split("—")[0]);
|
|
||||||
const max = Number(question.content.range.split("—")[1]);
|
const max = Number(question.content.range.split("—")[1]);
|
||||||
const range = max - min;
|
|
||||||
const step = Number(target.value);
|
const step = Number(target.value);
|
||||||
|
|
||||||
if (step > max) {
|
if (step > max) {
|
||||||
@ -203,18 +229,16 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
question.content.step = max;
|
question.content.step = max;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (range % step) {
|
|
||||||
setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`);
|
|
||||||
} else {
|
|
||||||
setStepError("");
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
<ButtonsOptions
|
||||||
|
switchState={switchState}
|
||||||
|
SSHC={SSHC}
|
||||||
|
question={question}
|
||||||
|
/>
|
||||||
<SwitchSlider switchState={switchState} question={question} />
|
<SwitchSlider 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} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,10 @@
|
|||||||
import { ResultSettings } from "./ResultSettings";
|
|
||||||
import { createFrontResult } from "@root/questions/actions";
|
|
||||||
import { useQuestionsStore } from "@root/questions/store";
|
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
|
||||||
import { Box, Typography, useTheme, useMediaQuery, Button } from "@mui/material";
|
import { Box, Typography, useTheme, useMediaQuery, Button } from "@mui/material";
|
||||||
import image from "../../assets/Rectangle 110.png";
|
import image from "../../assets/Rectangle 110.png";
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
|
||||||
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
|
||||||
import { decrementCurrentStep } from "@root/quizes/actions";
|
|
||||||
|
|
||||||
export const FirstEntry = () => {
|
export const FirstEntry = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const quiz = useCurrentQuiz();
|
|
||||||
const { questions } = useQuestionsStore();
|
|
||||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1250));
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1250));
|
||||||
|
|
||||||
const create = () => {
|
|
||||||
if (quiz?.config.haveRoot) {
|
|
||||||
questions
|
|
||||||
.filter((question: AnyTypedQuizQuestion) => {
|
|
||||||
return (
|
|
||||||
question.type !== null &&
|
|
||||||
question.content.rule.parentId.length !== 0 &&
|
|
||||||
question.content.rule.children.length === 0
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.forEach((question) => {
|
|
||||||
createFrontResult(quiz.backendId, question.content.id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
createFrontResult(quiz.backendId, "line");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -99,35 +71,6 @@ export const FirstEntry = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: "flex", justifyContent: "flex-start", alignItems: "center", gap: "8px", mt: "30px" }}>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
sx={{
|
|
||||||
padding: "10px 20px",
|
|
||||||
borderRadius: "8px",
|
|
||||||
height: "44px",
|
|
||||||
}}
|
|
||||||
onClick={decrementCurrentStep}
|
|
||||||
>
|
|
||||||
<ArrowLeft />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={create}
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
|
||||||
backgroundColor: "#7E2AEA",
|
|
||||||
fontSize: "18px",
|
|
||||||
lineHeight: "18px",
|
|
||||||
width: "216px",
|
|
||||||
height: "44px",
|
|
||||||
|
|
||||||
p: "10px 20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Создать результаты
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,43 @@
|
|||||||
import { useQuestionsStore } from "@root/questions/store";
|
|
||||||
import { FirstEntry } from "./FirstEntry"
|
import { FirstEntry } from "./FirstEntry"
|
||||||
import { ResultSettings } from "./ResultSettings"
|
import { ResultSettings } from "./ResultSettings"
|
||||||
|
import { decrementCurrentStep, incrementCurrentStep } from "@root/quizes/actions";
|
||||||
|
import { Box, Button } from "@mui/material";
|
||||||
|
import ArrowLeft from "@icons/questionsPage/arrowLeft"
|
||||||
|
|
||||||
export const ResultPage = () => {
|
export const ResultPage = () => {
|
||||||
const { questions } = useQuestionsStore();
|
|
||||||
//ищём хотя бы один result
|
|
||||||
const haveResult = questions.some((question) => question.type === "result")
|
|
||||||
return (
|
return (
|
||||||
haveResult ?
|
<>
|
||||||
<ResultSettings />
|
|
||||||
:
|
|
||||||
<FirstEntry />
|
<FirstEntry />
|
||||||
);
|
<ResultSettings />
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "flex-start", alignItems: "center", gap: "8px", mt: "30px" }}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
padding: "10px 20px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
height: "44px",
|
||||||
|
}}
|
||||||
|
onClick={decrementCurrentStep}
|
||||||
|
>
|
||||||
|
<ArrowLeft />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={incrementCurrentStep}
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#7E2AEA",
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "18px",
|
||||||
|
width: "216px",
|
||||||
|
height: "44px",
|
||||||
|
|
||||||
|
p: "10px 20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Настроить форму
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
@ -12,45 +12,50 @@ 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 { 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(){
|
console.log(quiz)
|
||||||
let isReadyToLeave = true;
|
console.log(results)
|
||||||
results.forEach((result) => {
|
useEffect(
|
||||||
if (checkEmptyData({ resultData: result })) {
|
function calcIsReadyToLeave() {
|
||||||
isReadyToLeave = false;
|
let isReadyToLeave = true;
|
||||||
}
|
results.forEach((result) => {
|
||||||
});
|
if (checkEmptyData({ resultData: result })) {
|
||||||
isReadyToLeaveRef.current = isReadyToLeave;
|
isReadyToLeave = false;
|
||||||
}, [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 +78,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 +106,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>
|
||||||
|
@ -6,6 +6,7 @@ import TextIcon from "@icons/ContactFormIcon/TextIcon";
|
|||||||
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
|
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
|
||||||
|
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import { NameplateLogo } from "@icons/NameplateLogo";
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useQuestionsStore } from "@root/questions/store";
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
@ -71,19 +72,19 @@ export const ContactForm = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{quiz?.config.formContact.title || "Заполните форму, чтобы получить результаты теста"}
|
{quiz?.config.formContact.title || "Заполните форму, чтобы получить результаты теста"}
|
||||||
|
|
||||||
</Typography>
|
</Typography>
|
||||||
{
|
{
|
||||||
quiz?.config.formContact.desc &&
|
quiz?.config.formContact.desc &&
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
m: "20px 0",
|
m: "20px 0",
|
||||||
fontSize: "18px"
|
fontSize: "18px"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{quiz?.config.formContact.desc}
|
{quiz?.config.formContact.desc}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -138,6 +139,16 @@ export const ContactForm = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
mt: "20px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NameplateLogo style={{ fontSize: "34px" }} />
|
||||||
|
<Typography sx={{ fontSize: "20px", color: "#4D4D4D", whiteSpace: "nowrap" }}>Сделано на PenaQuiz</Typography>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box >
|
</Box >
|
||||||
</Box >
|
</Box >
|
||||||
@ -154,14 +165,14 @@ const Inputs = () => {
|
|||||||
if (FC.used) someUsed.push(<CustomInput title={FC.innerText || data.defaultText} desc={FC.text || data.defaultTitle} Icon={data.icon} />)
|
if (FC.used) someUsed.push(<CustomInput title={FC.innerText || data.defaultText} desc={FC.text || data.defaultTitle} Icon={data.icon} />)
|
||||||
return <CustomInput title={FC.innerText || data.defaultText} desc={FC.text || data.defaultTitle} Icon={data.icon} />
|
return <CustomInput title={FC.innerText || data.defaultText} desc={FC.text || data.defaultTitle} Icon={data.icon} />
|
||||||
})
|
})
|
||||||
|
|
||||||
if (someUsed.length) {
|
if (someUsed.length) {
|
||||||
return <>{someUsed}</>
|
return <>{someUsed}</>
|
||||||
} else {
|
} else {
|
||||||
return <>
|
return <>
|
||||||
{Icons[0]}
|
{Icons[0]}
|
||||||
{Icons[1]}
|
{Icons[1]}
|
||||||
{Icons[2]}
|
{Icons[2]}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import { useQuestionsStore } from "@root/questions/store";
|
|||||||
import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared";
|
import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared";
|
||||||
import { getQuestionByContentId } from "@root/questions/actions";
|
import { getQuestionByContentId } from "@root/questions/actions";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { NameplateLogo } from "@icons/NameplateLogo";
|
import { NameplateLogoFQ } from "@icons/NameplateLogoFQ";
|
||||||
|
|
||||||
type FooterProps = {
|
type FooterProps = {
|
||||||
setCurrentQuestion: (step: AnyTypedQuizQuestion) => void;
|
setCurrentQuestion: (step: AnyTypedQuizQuestion) => void;
|
||||||
@ -189,22 +189,11 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
padding: "15px 0",
|
padding: "15px 0",
|
||||||
borderTop: `1px solid ${theme.palette.grey[400]}`,
|
borderTop: `1px solid ${theme.palette.grey[400]}`,
|
||||||
|
height: '75px',
|
||||||
|
|
||||||
|
display: "flex"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
position: "absolute",
|
|
||||||
top: "-45px",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
gap: "8px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<NameplateLogo style={{ fontSize: "34px" }} />
|
|
||||||
<Typography sx={{ fontSize: "20px", color: "#4D4D4D", whiteSpace: "nowrap" }}>Сделано на PenaQuiz</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@ -216,6 +205,7 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh
|
|||||||
gap: "10px",
|
gap: "10px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<NameplateLogoFQ style={{ fontSize: "34px", width:"200px", height:"auto" }} />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -66,11 +66,14 @@ export const Question = ({ questions }: QuestionProps) => {
|
|||||||
QUESTIONS_MAP[currentQuestion.type as Exclude<QuestionType, "nonselected">];
|
QUESTIONS_MAP[currentQuestion.type as Exclude<QuestionType, "nonselected">];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box
|
||||||
|
|
||||||
|
height="100vh"
|
||||||
|
>
|
||||||
{!showContactForm && !showResultForm && (
|
{!showContactForm && !showResultForm && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: "calc(100vh - 75px)",
|
height: "calc(100vh - 75px)",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: "1440px",
|
maxWidth: "1440px",
|
||||||
padding: "40px 25px 20px",
|
padding: "40px 25px 20px",
|
||||||
|
@ -6,6 +6,7 @@ import { useQuestionsStore } from "@root/questions/store";
|
|||||||
|
|
||||||
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
|
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||||
import YoutubeEmbedIframe from "../../ui_kit/StartPagePreview/YoutubeEmbedIframe.tsx"
|
import YoutubeEmbedIframe from "../../ui_kit/StartPagePreview/YoutubeEmbedIframe.tsx"
|
||||||
|
import { NameplateLogo } from "@icons/NameplateLogo";
|
||||||
|
|
||||||
type ResultFormProps = {
|
type ResultFormProps = {
|
||||||
currentQuestion: AnyTypedQuizQuestion;
|
currentQuestion: AnyTypedQuizQuestion;
|
||||||
@ -29,7 +30,7 @@ export const ResultForm = ({
|
|||||||
(question.content.rule.parentId === "line" || currentQuestion.content.id)
|
(question.content.rule.parentId === "line" || currentQuestion.content.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const followNextForm = () => {
|
const followNextForm = () => {
|
||||||
@ -100,31 +101,59 @@ export const ResultForm = ({
|
|||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{
|
<Box width="100%">
|
||||||
quiz?.config.resultInfo.when === "before" &&
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "100px",
|
|
||||||
boxShadow: "0 0 15px 0 rgba(0,0,0,.08)",
|
|
||||||
width: "100%",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
width: "100%",
|
||||||
alignItems: "center"
|
justifyContent: "end",
|
||||||
|
px: "20px"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Box
|
||||||
onClick={followNextForm}
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
sx={{
|
||||||
p: "10px 20px",
|
display: "flex",
|
||||||
width: "210px",
|
alignItems: "center",
|
||||||
height: "50px"
|
mt: "15px"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{resultQuestion.content.hint.text || "Узнать подробнее"}
|
<NameplateLogo style={{ fontSize: "34px" }} />
|
||||||
</Button>
|
<Typography sx={{ fontSize: "20px", color: "#4D4D4D", whiteSpace: "nowrap" }}>Сделано на PenaQuiz</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
|
||||||
|
{
|
||||||
|
quiz?.config.resultInfo.when === "before" &&
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
boxShadow: "0 0 15px 0 rgba(0,0,0,.08)",
|
||||||
|
width: "100%",
|
||||||
|
flexDirection: "column",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
p: "20px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={followNextForm}
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
p: "10px 20px",
|
||||||
|
width: "210px",
|
||||||
|
height: "50px"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{resultQuestion.content.hint.text || "Узнать подробнее"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,21 +18,29 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
const [maxRange, setMaxRange] = useState<string>("100000000000");
|
const [maxRange, setMaxRange] = useState<string>("100000000000");
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
const updateMinRangeDebounced = useDebouncedCallback((value, crowded = false) => {
|
const updateMinRangeDebounced = useDebouncedCallback(
|
||||||
if (crowded) {
|
(value, crowded = false) => {
|
||||||
setMinRange(maxRange);
|
if (crowded) {
|
||||||
}
|
setMinRange(maxRange);
|
||||||
|
}
|
||||||
|
|
||||||
updateAnswer(currentQuestion.content.id, value);
|
updateAnswer(currentQuestion.content.id, value);
|
||||||
}, 1000);
|
},
|
||||||
const updateMaxRangeDebounced = useDebouncedCallback((value, crowded = false) => {
|
1000
|
||||||
if (crowded) {
|
);
|
||||||
setMaxRange(minRange);
|
const updateMaxRangeDebounced = useDebouncedCallback(
|
||||||
}
|
(value, crowded = false) => {
|
||||||
|
if (crowded) {
|
||||||
|
setMaxRange(minRange);
|
||||||
|
}
|
||||||
|
|
||||||
updateAnswer(currentQuestion.content.id, value);
|
updateAnswer(currentQuestion.content.id, value);
|
||||||
}, 1000);
|
},
|
||||||
const answer = answers.find(({ questionId }) => questionId === currentQuestion.content.id)?.answer as string;
|
1000
|
||||||
|
);
|
||||||
|
const answer = answers.find(
|
||||||
|
({ questionId }) => questionId === currentQuestion.content.id
|
||||||
|
)?.answer as string;
|
||||||
|
|
||||||
const min = window.Number(currentQuestion.content.range.split("—")[0]);
|
const min = window.Number(currentQuestion.content.range.split("—")[0]);
|
||||||
const max = window.Number(currentQuestion.content.range.split("—")[1]);
|
const max = window.Number(currentQuestion.content.range.split("—")[1]);
|
||||||
|
@ -12,171 +12,171 @@ import { Link as RouterLink, useNavigate, useLocation } from "react-router-dom";
|
|||||||
import { object, string } from "yup";
|
import { object, string } from "yup";
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialValues: Values = {
|
const initialValues: Values = {
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
email: string().required("Поле обязательно").email("Введите корректный email"),
|
email: string().required("Поле обязательно").email("Введите корректный email"),
|
||||||
password: string().required("Поле обязательно").min(8, "Минимум 8 символов"),
|
password: string().required("Поле обязательно").min(8, "Минимум 8 символов"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function SigninDialog() {
|
export default function SigninDialog() {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const formik = useFormik<Values>({
|
const formik = useFormik<Values>({
|
||||||
initialValues,
|
initialValues,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
onSubmit: async (values, formikHelpers) => {
|
onSubmit: async (values, formikHelpers) => {
|
||||||
const [loginResponse, loginError] = await login(values.email.trim(), values.password.trim());
|
const [loginResponse, loginError] = await login(values.email.trim(), values.password.trim());
|
||||||
|
|
||||||
formikHelpers.setSubmitting(false);
|
formikHelpers.setSubmitting(false);
|
||||||
|
|
||||||
if (loginError) {
|
if (loginError) {
|
||||||
return enqueueSnackbar(loginError);
|
return enqueueSnackbar(loginError);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginResponse) {
|
if (loginResponse) {
|
||||||
setUserId(loginResponse._id);
|
setUserId(loginResponse._id);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function redirectIfSignedIn() {
|
|
||||||
if (user) navigate("/list", { replace: true });
|
|
||||||
},
|
|
||||||
[navigate, user]
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
setIsDialogOpen(false);
|
|
||||||
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
open={isDialogOpen}
|
|
||||||
onClose={handleClose}
|
|
||||||
PaperProps={{
|
|
||||||
sx: {
|
|
||||||
width: "600px",
|
|
||||||
maxWidth: "600px",
|
|
||||||
},
|
},
|
||||||
}}
|
});
|
||||||
slotProps={{
|
|
||||||
backdrop: {
|
useEffect(
|
||||||
style: {
|
function redirectIfSignedIn() {
|
||||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
if (user) navigate("/list", { replace: true });
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}}
|
[navigate, user]
|
||||||
>
|
);
|
||||||
<Box
|
|
||||||
component="form"
|
function handleClose() {
|
||||||
onSubmit={formik.handleSubmit}
|
setIsDialogOpen(false);
|
||||||
noValidate
|
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
||||||
sx={{
|
}
|
||||||
position: "relative",
|
|
||||||
backgroundColor: "white",
|
return (
|
||||||
display: "flex",
|
<Dialog
|
||||||
alignItems: "center",
|
open={isDialogOpen}
|
||||||
flexDirection: "column",
|
onClose={handleClose}
|
||||||
p: upMd ? "50px" : "18px",
|
PaperProps={{
|
||||||
pb: upMd ? "40px" : "30px",
|
sx: {
|
||||||
gap: "15px",
|
width: "600px",
|
||||||
borderRadius: "12px",
|
maxWidth: "600px",
|
||||||
boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)",
|
},
|
||||||
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": {
|
}}
|
||||||
position: "absolute",
|
slotProps={{
|
||||||
top: "46px",
|
backdrop: {
|
||||||
margin: "0",
|
style: {
|
||||||
},
|
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||||
}}
|
},
|
||||||
>
|
},
|
||||||
<IconButton
|
}}
|
||||||
onClick={handleClose}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
right: "7px",
|
|
||||||
top: "7px",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
<Box
|
||||||
</IconButton>
|
component="form"
|
||||||
<Box>
|
onSubmit={formik.handleSubmit}
|
||||||
<Logotip width={upMd ? 233 : 196} />
|
noValidate
|
||||||
</Box>
|
sx={{
|
||||||
<Typography
|
position: "relative",
|
||||||
sx={{
|
backgroundColor: "white",
|
||||||
color: "#4D4D4D",
|
display: "flex",
|
||||||
mt: "5px",
|
alignItems: "center",
|
||||||
mb: upMd ? "10px" : "33px",
|
flexDirection: "column",
|
||||||
}}
|
p: upMd ? "50px" : "18px",
|
||||||
>
|
pb: upMd ? "40px" : "30px",
|
||||||
Вход в личный кабинет
|
gap: "15px",
|
||||||
</Typography>
|
borderRadius: "12px",
|
||||||
<InputTextfield
|
boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)",
|
||||||
TextfieldProps={{
|
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": {
|
||||||
value: formik.values.email,
|
position: "absolute",
|
||||||
placeholder: "username",
|
top: "46px",
|
||||||
onBlur: formik.handleBlur,
|
margin: "0",
|
||||||
error: formik.touched.email && Boolean(formik.errors.email),
|
},
|
||||||
helperText: formik.touched.email && formik.errors.email,
|
}}
|
||||||
"data-cy": "username",
|
>
|
||||||
}}
|
<IconButton
|
||||||
onChange={formik.handleChange}
|
onClick={handleClose}
|
||||||
color="#F2F3F7"
|
sx={{
|
||||||
id="email"
|
position: "absolute",
|
||||||
label="Email"
|
right: "7px",
|
||||||
gap={upMd ? "10px" : "10px"}
|
top: "7px",
|
||||||
/>
|
}}
|
||||||
<PasswordInput
|
>
|
||||||
TextfieldProps={{
|
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||||
value: formik.values.password,
|
</IconButton>
|
||||||
placeholder: "Не менее 8 символов",
|
<Box>
|
||||||
onBlur: formik.handleBlur,
|
<Logotip width={upMd ? 233 : 196} />
|
||||||
error: formik.touched.password && Boolean(formik.errors.password),
|
</Box>
|
||||||
helperText: formik.touched.password && formik.errors.password,
|
<Typography
|
||||||
type: "password",
|
sx={{
|
||||||
"data-cy": "password",
|
color: "#4D4D4D",
|
||||||
}}
|
mt: "5px",
|
||||||
onChange={formik.handleChange}
|
mb: upMd ? "10px" : "33px",
|
||||||
color="#F2F3F7"
|
}}
|
||||||
id="password"
|
>
|
||||||
label="Пароль"
|
Вход в личный кабинет
|
||||||
gap={upMd ? "10px" : "10px"}
|
</Typography>
|
||||||
/>
|
<InputTextfield
|
||||||
<Button
|
TextfieldProps={{
|
||||||
variant="contained"
|
value: formik.values.email,
|
||||||
fullWidth
|
placeholder: "username",
|
||||||
type="submit"
|
onBlur: formik.handleBlur,
|
||||||
disabled={formik.isSubmitting}
|
error: formik.touched.email && Boolean(formik.errors.email),
|
||||||
sx={{
|
helperText: formik.touched.email && formik.errors.email,
|
||||||
py: "12px",
|
"data-cy": "username",
|
||||||
"&:hover": {
|
}}
|
||||||
backgroundColor: "#581CA7",
|
onChange={formik.handleChange}
|
||||||
},
|
color="#F2F3F7"
|
||||||
"&:active": {
|
id="email"
|
||||||
color: "white",
|
label="Email"
|
||||||
backgroundColor: "black",
|
gap={upMd ? "10px" : "10px"}
|
||||||
},
|
/>
|
||||||
}}
|
<PasswordInput
|
||||||
data-cy="signin"
|
TextfieldProps={{
|
||||||
>
|
value: formik.values.password,
|
||||||
Войти
|
placeholder: "Не менее 8 символов",
|
||||||
</Button>
|
onBlur: formik.handleBlur,
|
||||||
{/* <Link
|
error: formik.touched.password && Boolean(formik.errors.password),
|
||||||
|
helperText: formik.touched.password && formik.errors.password,
|
||||||
|
type: "password",
|
||||||
|
"data-cy": "password",
|
||||||
|
}}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
color="#F2F3F7"
|
||||||
|
id="password"
|
||||||
|
label="Пароль"
|
||||||
|
gap={upMd ? "10px" : "10px"}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
type="submit"
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
sx={{
|
||||||
|
py: "12px",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#581CA7",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
color: "white",
|
||||||
|
backgroundColor: "black",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
data-cy="signin"
|
||||||
|
>
|
||||||
|
Войти
|
||||||
|
</Button>
|
||||||
|
{/* <Link
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
to="/"
|
to="/"
|
||||||
href="#"
|
href="#"
|
||||||
@ -188,26 +188,38 @@ export default function SigninDialog() {
|
|||||||
>
|
>
|
||||||
Забыли пароль?
|
Забыли пароль?
|
||||||
</Link> */}
|
</Link> */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "10px",
|
gap: "10px",
|
||||||
mt: "auto",
|
mt: "auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{ color: "#7E2AEA", textAlign: "center" }}>Вы еще не присоединились?</Typography>
|
<Typography sx={{ color: "#7E2AEA", textAlign: "center" }}>Вы еще не присоединились?</Typography>
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "15px" }}>
|
||||||
<Link component={RouterLink} to="/signup" sx={{ color: "#7E2AEA" }}>
|
<Link
|
||||||
Регистрация
|
component={RouterLink}
|
||||||
</Link>
|
to="/signup"
|
||||||
<Link component={RouterLink} to="/restore" sx={{ color: "#7E2AEA" }}>
|
state={{ backgroundLocation: location.state.backgroundLocation }}
|
||||||
Забыли пароль
|
sx={{
|
||||||
</Link>
|
color: "#7E2AEA"
|
||||||
</Box>
|
}}>
|
||||||
</Box>
|
Регистрация
|
||||||
</Box>
|
</Link>
|
||||||
</Dialog>
|
<Link
|
||||||
);
|
component={RouterLink}
|
||||||
|
to="/restore"
|
||||||
|
state={{ backgroundLocation: location.state.backgroundLocation }}
|
||||||
|
sx={{
|
||||||
|
color: "#7E2AEA"
|
||||||
|
}}>
|
||||||
|
Забыли пароль
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,210 +12,215 @@ import { Link as RouterLink, useLocation, useNavigate } from "react-router-dom";
|
|||||||
import { object, ref, string } from "yup";
|
import { object, ref, string } from "yup";
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
repeatPassword: string;
|
repeatPassword: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialValues: Values = {
|
const initialValues: Values = {
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
repeatPassword: "",
|
repeatPassword: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
email: string().required("Поле обязательно").email("Введите корректный email"),
|
email: string().required("Поле обязательно").email("Введите корректный email"),
|
||||||
password: string()
|
password: string()
|
||||||
.min(8, "Минимум 8 символов")
|
.min(8, "Минимум 8 символов")
|
||||||
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
||||||
.required("Поле обязательно"),
|
.required("Поле обязательно"),
|
||||||
repeatPassword: string()
|
repeatPassword: string()
|
||||||
.oneOf([ref("password"), undefined], "Пароли не совпадают")
|
.oneOf([ref("password"), undefined], "Пароли не совпадают")
|
||||||
.required("Повторите пароль"),
|
.required("Повторите пароль"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function SignupDialog() {
|
export default function SignupDialog() {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
||||||
const user = useUserStore((state) => state.user);
|
const user = useUserStore((state) => state.user);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const formik = useFormik<Values>({
|
const formik = useFormik<Values>({
|
||||||
initialValues,
|
initialValues,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
onSubmit: async (values, formikHelpers) => {
|
onSubmit: async (values, formikHelpers) => {
|
||||||
const [registerResponse, registerError] = await register(values.email.trim(), values.password.trim(), "+7");
|
const [registerResponse, registerError] = await register(values.email.trim(), values.password.trim(), "+7");
|
||||||
|
|
||||||
formikHelpers.setSubmitting(false);
|
formikHelpers.setSubmitting(false);
|
||||||
|
|
||||||
if (registerError) {
|
if (registerError) {
|
||||||
return enqueueSnackbar(registerError);
|
return enqueueSnackbar(registerError);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registerResponse) {
|
if (registerResponse) {
|
||||||
setUserId(registerResponse._id);
|
setUserId(registerResponse._id);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
function redirectIfSignedIn() {
|
|
||||||
if (user) navigate("/list", { replace: true });
|
|
||||||
},
|
|
||||||
[navigate, user]
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
setIsDialogOpen(false);
|
|
||||||
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
open={isDialogOpen}
|
|
||||||
onClose={handleClose}
|
|
||||||
PaperProps={{
|
|
||||||
sx: {
|
|
||||||
width: "600px",
|
|
||||||
maxWidth: "600px",
|
|
||||||
},
|
},
|
||||||
}}
|
});
|
||||||
slotProps={{
|
|
||||||
backdrop: {
|
|
||||||
style: {
|
|
||||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
component="form"
|
|
||||||
onSubmit={formik.handleSubmit}
|
|
||||||
noValidate
|
|
||||||
sx={{
|
|
||||||
position: "relative",
|
|
||||||
backgroundColor: "white",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
p: upMd ? "50px" : "18px",
|
|
||||||
pb: upMd ? "40px" : "30px",
|
|
||||||
gap: "15px",
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)",
|
|
||||||
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": {
|
|
||||||
position: "absolute",
|
|
||||||
top: "46px",
|
|
||||||
margin: "0",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
onClick={handleClose}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
right: "7px",
|
|
||||||
top: "7px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
|
||||||
</IconButton>
|
|
||||||
<Box sx={{ mt: upMd ? undefined : "62px" }}>
|
|
||||||
<Logotip width={upMd ? 233 : 196} />
|
|
||||||
</Box>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: "#4D4D4D",
|
|
||||||
mt: "5px",
|
|
||||||
mb: upMd ? "35px" : "33px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Регистрация
|
|
||||||
</Typography>
|
|
||||||
<InputTextfield
|
|
||||||
TextfieldProps={{
|
|
||||||
value: formik.values.email,
|
|
||||||
placeholder: "username",
|
|
||||||
onBlur: formik.handleBlur,
|
|
||||||
error: formik.touched.email && Boolean(formik.errors.email),
|
|
||||||
helperText: formik.touched.email && formik.errors.email,
|
|
||||||
"data-cy": "username",
|
|
||||||
}}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
color="#F2F3F7"
|
|
||||||
id="email"
|
|
||||||
label="Email"
|
|
||||||
gap={upMd ? "10px" : "10px"}
|
|
||||||
/>
|
|
||||||
<PasswordInput
|
|
||||||
TextfieldProps={{
|
|
||||||
value: formik.values.password,
|
|
||||||
placeholder: "Не менее 8 символов",
|
|
||||||
onBlur: formik.handleBlur,
|
|
||||||
error: formik.touched.password && Boolean(formik.errors.password),
|
|
||||||
helperText: formik.touched.password && formik.errors.password,
|
|
||||||
autoComplete: "new-password",
|
|
||||||
"data-cy": "password",
|
|
||||||
}}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
color="#F2F3F7"
|
|
||||||
id="password"
|
|
||||||
label="Пароль"
|
|
||||||
gap={upMd ? "10px" : "10px"}
|
|
||||||
/>
|
|
||||||
<PasswordInput
|
|
||||||
TextfieldProps={{
|
|
||||||
value: formik.values.repeatPassword,
|
|
||||||
placeholder: "Не менее 8 символов",
|
|
||||||
onBlur: formik.handleBlur,
|
|
||||||
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
|
||||||
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
|
||||||
autoComplete: "new-password",
|
|
||||||
"data-cy": "repeat-password",
|
|
||||||
}}
|
|
||||||
onChange={formik.handleChange}
|
|
||||||
color="#F2F3F7"
|
|
||||||
id="repeatPassword"
|
|
||||||
label="Повторить пароль"
|
|
||||||
gap={upMd ? "10px" : "10px"}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
type="submit"
|
|
||||||
disabled={formik.isSubmitting}
|
|
||||||
sx={{
|
|
||||||
py: "12px",
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "#581CA7",
|
|
||||||
},
|
|
||||||
"&:active": {
|
|
||||||
color: "white",
|
|
||||||
backgroundColor: "black",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
data-cy="signup"
|
|
||||||
>
|
|
||||||
Зарегистрироваться
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Link
|
useEffect(
|
||||||
component={RouterLink}
|
function redirectIfSignedIn() {
|
||||||
to="/signin"
|
if (user) navigate("/list", { replace: true });
|
||||||
state={{ backgroundLocation: location.state.backgroundLocation }}
|
},
|
||||||
sx={{
|
[navigate, user]
|
||||||
color: "#7E2AEA",
|
);
|
||||||
mt: "auto",
|
|
||||||
}}
|
function handleClose() {
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={isDialogOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
width: "600px",
|
||||||
|
maxWidth: "600px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
backdrop: {
|
||||||
|
style: {
|
||||||
|
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Вход в личный кабинет
|
<Box
|
||||||
</Link>
|
component="form"
|
||||||
<Link component={RouterLink} to="/restore" sx={{ color: "#7E2AEA" }}>
|
onSubmit={formik.handleSubmit}
|
||||||
Забыли пароль
|
noValidate
|
||||||
</Link>
|
sx={{
|
||||||
</Box>
|
position: "relative",
|
||||||
</Dialog>
|
backgroundColor: "white",
|
||||||
);
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
p: upMd ? "50px" : "18px",
|
||||||
|
pb: upMd ? "40px" : "30px",
|
||||||
|
gap: "15px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)",
|
||||||
|
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": {
|
||||||
|
position: "absolute",
|
||||||
|
top: "46px",
|
||||||
|
margin: "0",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
onClick={handleClose}
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "7px",
|
||||||
|
top: "7px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||||
|
</IconButton>
|
||||||
|
<Box sx={{ mt: upMd ? undefined : "62px" }}>
|
||||||
|
<Logotip width={upMd ? 233 : 196} />
|
||||||
|
</Box>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
color: "#4D4D4D",
|
||||||
|
mt: "5px",
|
||||||
|
mb: upMd ? "35px" : "33px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Регистрация
|
||||||
|
</Typography>
|
||||||
|
<InputTextfield
|
||||||
|
TextfieldProps={{
|
||||||
|
value: formik.values.email,
|
||||||
|
placeholder: "username",
|
||||||
|
onBlur: formik.handleBlur,
|
||||||
|
error: formik.touched.email && Boolean(formik.errors.email),
|
||||||
|
helperText: formik.touched.email && formik.errors.email,
|
||||||
|
"data-cy": "username",
|
||||||
|
}}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
color="#F2F3F7"
|
||||||
|
id="email"
|
||||||
|
label="Email"
|
||||||
|
gap={upMd ? "10px" : "10px"}
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
TextfieldProps={{
|
||||||
|
value: formik.values.password,
|
||||||
|
placeholder: "Не менее 8 символов",
|
||||||
|
onBlur: formik.handleBlur,
|
||||||
|
error: formik.touched.password && Boolean(formik.errors.password),
|
||||||
|
helperText: formik.touched.password && formik.errors.password,
|
||||||
|
autoComplete: "new-password",
|
||||||
|
"data-cy": "password",
|
||||||
|
}}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
color="#F2F3F7"
|
||||||
|
id="password"
|
||||||
|
label="Пароль"
|
||||||
|
gap={upMd ? "10px" : "10px"}
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
TextfieldProps={{
|
||||||
|
value: formik.values.repeatPassword,
|
||||||
|
placeholder: "Не менее 8 символов",
|
||||||
|
onBlur: formik.handleBlur,
|
||||||
|
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
||||||
|
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
||||||
|
autoComplete: "new-password",
|
||||||
|
"data-cy": "repeat-password",
|
||||||
|
}}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
color="#F2F3F7"
|
||||||
|
id="repeatPassword"
|
||||||
|
label="Повторить пароль"
|
||||||
|
gap={upMd ? "10px" : "10px"}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
type="submit"
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
sx={{
|
||||||
|
py: "12px",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#581CA7",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
color: "white",
|
||||||
|
backgroundColor: "black",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
data-cy="signup"
|
||||||
|
>
|
||||||
|
Зарегистрироваться
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
to="/signin"
|
||||||
|
state={{ backgroundLocation: location.state.backgroundLocation }}
|
||||||
|
sx={{
|
||||||
|
color: "#7E2AEA",
|
||||||
|
mt: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Вход в личный кабинет
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
to="/restore"
|
||||||
|
state={{ backgroundLocation: location.state.backgroundLocation }}
|
||||||
|
sx={{ color: "#7E2AEA" }}
|
||||||
|
>
|
||||||
|
Забыли пароль
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -32,7 +32,7 @@ import { Link, useNavigate } from "react-router-dom";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { SidebarMobile } from "./Sidebar/SidebarMobile";
|
import { SidebarMobile } from "./Sidebar/SidebarMobile";
|
||||||
import { cleanQuestions, setQuestions } from "@root/questions/actions";
|
import { cleanQuestions, createResult, setQuestions } from "@root/questions/actions";
|
||||||
import { updateOpenBranchingPanel, updateCanCreatePublic, updateModalInfoWhyCantCreate } from "@root/uiTools/actions";
|
import { updateOpenBranchingPanel, updateCanCreatePublic, updateModalInfoWhyCantCreate } from "@root/uiTools/actions";
|
||||||
import { BranchingPanel } from "../Questions/BranchingPanel";
|
import { BranchingPanel } from "../Questions/BranchingPanel";
|
||||||
import { useQuestionsStore } from "@root/questions/store";
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
@ -59,10 +59,14 @@ export default function EditPage() {
|
|||||||
|
|
||||||
const questions = await questionApi.getList({ quiz_id: editQuizId });
|
const questions = await questionApi.getList({ quiz_id: editQuizId });
|
||||||
setQuestions(questions);
|
setQuestions(questions);
|
||||||
|
if (questions === null || !questions.find(q => q.type !== null && q.content?.rule.parentId === "line")) createResult(quiz?.backendId, "line")
|
||||||
};
|
};
|
||||||
getData();
|
getData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
console.log(quiz)
|
||||||
|
console.log(questions)
|
||||||
|
|
||||||
const { openBranchingPanel, whyCantCreatePublic, canCreatePublic } = useUiTools();
|
const { openBranchingPanel, whyCantCreatePublic, canCreatePublic } = useUiTools();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -78,10 +82,12 @@ export default function EditPage() {
|
|||||||
}, [navigate, editQuizId]);
|
}, [navigate, editQuizId]);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => {
|
||||||
resetEditConfig();
|
return () => {
|
||||||
cleanQuestions();
|
resetEditConfig();
|
||||||
updateModalInfoWhyCantCreate(false)
|
cleanQuestions();
|
||||||
|
updateModalInfoWhyCantCreate(false)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@ -123,7 +129,7 @@ export default function EditPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateQuestionHint(questions)
|
updateQuestionHint(questions)
|
||||||
}, [questions]);
|
}, [questions]);
|
||||||
|
|
||||||
|
|
||||||
async function handleLogoutClick() {
|
async function handleLogoutClick() {
|
||||||
const [, logoutError] = await logout();
|
const [, logoutError] = await logout();
|
||||||
@ -376,11 +382,11 @@ export default function EditPage() {
|
|||||||
height: "34px",
|
height: "34px",
|
||||||
minWidth: "130px",
|
minWidth: "130px",
|
||||||
}}
|
}}
|
||||||
onClick={() => Object.keys(whyCantCreatePublic).length === 0 ? () => {} : updateModalInfoWhyCantCreate(true)}
|
onClick={() => Object.keys(whyCantCreatePublic).length === 0 ? () => { } : updateModalInfoWhyCantCreate(true)}
|
||||||
>
|
>
|
||||||
Тестовый просмотр
|
Тестовый просмотр
|
||||||
</Button>
|
</Button>
|
||||||
:
|
:
|
||||||
<a href={`/view`} target="_blank" rel="noreferrer" style={{ textDecoration: "none" }}>
|
<a href={`/view`} target="_blank" rel="noreferrer" style={{ textDecoration: "none" }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@ -411,22 +417,22 @@ export default function EditPage() {
|
|||||||
() => updateQuiz(quiz?.id, (state) => {
|
() => updateQuiz(quiz?.id, (state) => {
|
||||||
state.status = quiz?.status === "start" ? "stop" : "start";
|
state.status = quiz?.status === "start" ? "stop" : "start";
|
||||||
})
|
})
|
||||||
:
|
:
|
||||||
() => updateModalInfoWhyCantCreate(true)
|
() => updateModalInfoWhyCantCreate(true)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{quiz?.status === "start" ? "Стоп" : "Старт"}
|
{quiz?.status === "start" ? "Стоп" : "Старт"}
|
||||||
</Button>
|
</Button>
|
||||||
{quiz?.status === "start" && <Box
|
{quiz?.status === "start" && <Box
|
||||||
component={Link}
|
component={Link}
|
||||||
sx={{
|
sx={{
|
||||||
color: "#7e2aea",
|
color: "#7e2aea",
|
||||||
fontSize: "14px"
|
fontSize: "14px"
|
||||||
}}
|
}}
|
||||||
target="_blank" to={"https://hbpn.link/" + quiz.qid}>https://hbpn.link/{quiz.qid}
|
target="_blank" to={"https://hbpn.link/" + quiz.qid}>https://hbpn.link/{quiz.qid}
|
||||||
</Box>}
|
</Box>}
|
||||||
</Box>
|
</Box>
|
||||||
</Box >
|
</Box >
|
||||||
<ModalInfoWhyCantCreate />
|
<ModalInfoWhyCantCreate />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,183 +1,195 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { Box, Button, Dialog, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Button, Dialog, IconButton, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import InputTextfield from "@ui_kit/InputTextfield";
|
import InputTextfield from "@ui_kit/InputTextfield";
|
||||||
import PasswordInput from "@ui_kit/passwordInput";
|
import PasswordInput from "@ui_kit/passwordInput";
|
||||||
import { useFormik } from "formik";
|
import { useFormik } from "formik";
|
||||||
import { object, ref, string } from "yup";
|
import { object, ref, string } from "yup";
|
||||||
import Logotip from "../Landing/images/icons/QuizLogo";
|
import Logotip from "../Landing/images/icons/QuizLogo";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate, Link as RouterLink, useLocation } from "react-router-dom";
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
repeatPassword: string;
|
repeatPassword: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialValues: Values = {
|
const initialValues: Values = {
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
repeatPassword: "",
|
repeatPassword: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
email: string().required("Поле обязательно").email("Введите корректный email"),
|
email: string().required("Поле обязательно").email("Введите корректный email"),
|
||||||
password: string()
|
password: string()
|
||||||
.min(8, "Минимум 8 символов")
|
.min(8, "Минимум 8 символов")
|
||||||
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
||||||
.required("Поле обязательно"),
|
.required("Поле обязательно"),
|
||||||
repeatPassword: string()
|
repeatPassword: string()
|
||||||
.oneOf([ref("password"), undefined], "Пароли не совпадают")
|
.oneOf([ref("password"), undefined], "Пароли не совпадают")
|
||||||
.required("Повторите пароль"),
|
.required("Повторите пароль"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Restore: FC = () => {
|
export const Restore: FC = () => {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(true);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const formik = useFormik<Values>({
|
const formik = useFormik<Values>({
|
||||||
initialValues,
|
initialValues,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
setIsDialogOpen(false);
|
|
||||||
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
open={isDialogOpen}
|
|
||||||
onClose={handleClose}
|
|
||||||
PaperProps={{
|
|
||||||
sx: {
|
|
||||||
width: "600px",
|
|
||||||
maxWidth: "600px",
|
|
||||||
},
|
},
|
||||||
}}
|
});
|
||||||
slotProps={{
|
|
||||||
backdrop: {
|
function handleClose() {
|
||||||
style: {
|
setIsDialogOpen(false);
|
||||||
backgroundColor: "rgb(0 0 0 / 0.7)",
|
setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen);
|
||||||
},
|
}
|
||||||
},
|
|
||||||
}}
|
return (
|
||||||
>
|
<Dialog
|
||||||
<Box
|
open={isDialogOpen}
|
||||||
component="form"
|
onClose={handleClose}
|
||||||
onSubmit={formik.handleSubmit}
|
PaperProps={{
|
||||||
noValidate
|
sx: {
|
||||||
sx={{
|
width: "600px",
|
||||||
position: "relative",
|
maxWidth: "600px",
|
||||||
backgroundColor: "white",
|
},
|
||||||
display: "flex",
|
}}
|
||||||
alignItems: "center",
|
slotProps={{
|
||||||
flexDirection: "column",
|
backdrop: {
|
||||||
p: upMd ? "50px" : "18px",
|
style: {
|
||||||
pb: upMd ? "40px" : "30px",
|
backgroundColor: "rgb(0 0 0 / 0.7)",
|
||||||
gap: "15px",
|
},
|
||||||
borderRadius: "12px",
|
},
|
||||||
boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)",
|
}}
|
||||||
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": {
|
|
||||||
position: "absolute",
|
|
||||||
top: "46px",
|
|
||||||
margin: "0",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
onClick={handleClose}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
right: "7px",
|
|
||||||
top: "7px",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
<Box
|
||||||
</IconButton>
|
component="form"
|
||||||
<Box sx={{ mt: upMd ? undefined : "62px" }}>
|
onSubmit={formik.handleSubmit}
|
||||||
<Logotip width={upMd ? 233 : 196} />
|
noValidate
|
||||||
</Box>
|
sx={{
|
||||||
<Typography
|
position: "relative",
|
||||||
sx={{
|
backgroundColor: "white",
|
||||||
color: "#4D4D4D",
|
display: "flex",
|
||||||
mt: "5px",
|
alignItems: "center",
|
||||||
mb: upMd ? "30px" : "33px",
|
flexDirection: "column",
|
||||||
}}
|
p: upMd ? "50px" : "18px",
|
||||||
>
|
pb: upMd ? "40px" : "30px",
|
||||||
Восстановление пароля
|
gap: "15px",
|
||||||
</Typography>
|
borderRadius: "12px",
|
||||||
<InputTextfield
|
boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)",
|
||||||
TextfieldProps={{
|
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled": {
|
||||||
value: formik.values.email,
|
position: "absolute",
|
||||||
placeholder: "username",
|
top: "46px",
|
||||||
onBlur: formik.handleBlur,
|
margin: "0",
|
||||||
error: formik.touched.email && Boolean(formik.errors.email),
|
},
|
||||||
helperText: formik.touched.email && formik.errors.email,
|
}}
|
||||||
"data-cy": "username",
|
>
|
||||||
}}
|
<IconButton
|
||||||
onChange={formik.handleChange}
|
onClick={handleClose}
|
||||||
color="#F2F3F7"
|
sx={{
|
||||||
id="email"
|
position: "absolute",
|
||||||
label="Email"
|
right: "7px",
|
||||||
gap={upMd ? "10px" : "10px"}
|
top: "7px",
|
||||||
/>
|
}}
|
||||||
<PasswordInput
|
>
|
||||||
TextfieldProps={{
|
<CloseIcon sx={{ transform: "scale(1.5)" }} />
|
||||||
value: formik.values.password,
|
</IconButton>
|
||||||
placeholder: "Не менее 8 символов",
|
<Box sx={{ mt: upMd ? undefined : "62px" }}>
|
||||||
onBlur: formik.handleBlur,
|
<Logotip width={upMd ? 233 : 196} />
|
||||||
error: formik.touched.password && Boolean(formik.errors.password),
|
</Box>
|
||||||
helperText: formik.touched.password && formik.errors.password,
|
<Typography
|
||||||
autoComplete: "new-password",
|
sx={{
|
||||||
"data-cy": "password",
|
color: "#4D4D4D",
|
||||||
}}
|
mt: "5px",
|
||||||
onChange={formik.handleChange}
|
mb: upMd ? "30px" : "33px",
|
||||||
color="#F2F3F7"
|
}}
|
||||||
id="password"
|
>
|
||||||
label="Пароль"
|
Восстановление пароля
|
||||||
gap={upMd ? "10px" : "10px"}
|
</Typography>
|
||||||
/>
|
<InputTextfield
|
||||||
<PasswordInput
|
TextfieldProps={{
|
||||||
TextfieldProps={{
|
value: formik.values.email,
|
||||||
value: formik.values.repeatPassword,
|
placeholder: "username",
|
||||||
placeholder: "Не менее 8 символов",
|
onBlur: formik.handleBlur,
|
||||||
onBlur: formik.handleBlur,
|
error: formik.touched.email && Boolean(formik.errors.email),
|
||||||
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
helperText: formik.touched.email && formik.errors.email,
|
||||||
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
"data-cy": "username",
|
||||||
autoComplete: "new-password",
|
}}
|
||||||
"data-cy": "repeat-password",
|
onChange={formik.handleChange}
|
||||||
}}
|
color="#F2F3F7"
|
||||||
onChange={formik.handleChange}
|
id="email"
|
||||||
color="#F2F3F7"
|
label="Email"
|
||||||
id="repeatPassword"
|
gap={upMd ? "10px" : "10px"}
|
||||||
label="Повторить пароль"
|
/>
|
||||||
gap={upMd ? "10px" : "10px"}
|
<PasswordInput
|
||||||
/>
|
TextfieldProps={{
|
||||||
<Button
|
value: formik.values.password,
|
||||||
variant="contained"
|
placeholder: "Не менее 8 символов",
|
||||||
fullWidth
|
onBlur: formik.handleBlur,
|
||||||
type="submit"
|
error: formik.touched.password && Boolean(formik.errors.password),
|
||||||
disabled={formik.isSubmitting}
|
helperText: formik.touched.password && formik.errors.password,
|
||||||
sx={{
|
autoComplete: "new-password",
|
||||||
py: "12px",
|
"data-cy": "password",
|
||||||
"&:hover": {
|
}}
|
||||||
backgroundColor: "#581CA7",
|
onChange={formik.handleChange}
|
||||||
},
|
color="#F2F3F7"
|
||||||
"&:active": {
|
id="password"
|
||||||
color: "white",
|
label="Пароль"
|
||||||
backgroundColor: "black",
|
gap={upMd ? "10px" : "10px"}
|
||||||
},
|
/>
|
||||||
}}
|
<PasswordInput
|
||||||
data-cy="signup"
|
TextfieldProps={{
|
||||||
>
|
value: formik.values.repeatPassword,
|
||||||
Восстановить
|
placeholder: "Не менее 8 символов",
|
||||||
</Button>
|
onBlur: formik.handleBlur,
|
||||||
</Box>
|
error: formik.touched.repeatPassword && Boolean(formik.errors.repeatPassword),
|
||||||
</Dialog>
|
helperText: formik.touched.repeatPassword && formik.errors.repeatPassword,
|
||||||
);
|
autoComplete: "new-password",
|
||||||
|
"data-cy": "repeat-password",
|
||||||
|
}}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
color="#F2F3F7"
|
||||||
|
id="repeatPassword"
|
||||||
|
label="Повторить пароль"
|
||||||
|
gap={upMd ? "10px" : "10px"}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
type="submit"
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
sx={{
|
||||||
|
py: "12px",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "#581CA7",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
color: "white",
|
||||||
|
backgroundColor: "black",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
data-cy="signup"
|
||||||
|
>
|
||||||
|
Восстановить
|
||||||
|
</Button>
|
||||||
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
to="/signin"
|
||||||
|
state={{ backgroundLocation: location.state.backgroundLocation }}
|
||||||
|
sx={{
|
||||||
|
color: "#7E2AEA",
|
||||||
|
mt: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
У меня уже есть аккаунт
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
|||||||
import { QuestionsStore, useQuestionsStore } from "./store";
|
import { QuestionsStore, useQuestionsStore } from "./store";
|
||||||
import { useUiTools } from "../uiTools/store";
|
import { useUiTools } from "../uiTools/store";
|
||||||
import { withErrorBoundary } from "react-error-boundary";
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
|
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||||
|
|
||||||
|
|
||||||
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
||||||
@ -481,7 +482,12 @@ export const getQuestionByContentId = (questionContentId: string | null) => {
|
|||||||
export const clearRuleForAll = () => {
|
export const clearRuleForAll = () => {
|
||||||
const { questions } = useQuestionsStore.getState();
|
const { questions } = useQuestionsStore.getState();
|
||||||
questions.forEach(question => {
|
questions.forEach(question => {
|
||||||
if (question.type !== null && (question.content.rule.main.length > 0 || question.content.rule.default.length > 0 || question.content.rule.parentId.length > 0)) {
|
if (question.type !== null &&
|
||||||
|
(question.content.rule.main.length > 0
|
||||||
|
|| question.content.rule.default.length > 0
|
||||||
|
|| question.content.rule.parentId.length > 0)
|
||||||
|
&& question.type !== "result") {
|
||||||
|
|
||||||
updateQuestion(question.content.id, question => {
|
updateQuestion(question.content.id, question => {
|
||||||
question.content.rule.parentId = "";
|
question.content.rule.parentId = "";
|
||||||
question.content.rule.main = [];
|
question.content.rule.main = [];
|
||||||
@ -491,63 +497,48 @@ export const clearRuleForAll = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createResult = async (
|
||||||
export const createFrontResult = (quizId: number, parentContentId?: string) => setProducedState(state => {
|
quizId: number,
|
||||||
const frontId = nanoid();
|
parentContentId?: string
|
||||||
const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content));
|
|
||||||
content.id = frontId;
|
|
||||||
if (parentContentId) content.rule.parentId = parentContentId;
|
|
||||||
state.questions.push({
|
|
||||||
id: frontId,
|
|
||||||
quizId,
|
|
||||||
type: "result",
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
deleted: false,
|
|
||||||
expanded: true,
|
|
||||||
page: 101,
|
|
||||||
required: true,
|
|
||||||
content
|
|
||||||
});
|
|
||||||
}, {
|
|
||||||
type: "createFrontResult",
|
|
||||||
quizId,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export const createBackResult = async (
|
|
||||||
questionId: string,
|
|
||||||
type: QuestionType,
|
|
||||||
) => requestQueue.enqueue(async () => {
|
) => requestQueue.enqueue(async () => {
|
||||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
if (!quizId || !parentContentId) {
|
||||||
if (!question) return;
|
console.error("Нет данных для создания результата. quizId: ", quizId, ", quizId: ", parentContentId)
|
||||||
if (question.type !== "result") throw new Error("Cannot upgrade already typed question");
|
|
||||||
|
|
||||||
try {
|
|
||||||
const createdQuestion = await questionApi.create({
|
|
||||||
quiz_id: question.quizId,
|
|
||||||
type,
|
|
||||||
title: question.title,
|
|
||||||
description: question.description,
|
|
||||||
page: 0,
|
|
||||||
required: true,
|
|
||||||
content: JSON.stringify(defaultQuestionByType[type].content),
|
|
||||||
});
|
|
||||||
|
|
||||||
setProducedState(state => {
|
|
||||||
const questionIndex = state.questions.findIndex(q => q.id === questionId);
|
|
||||||
if (questionIndex !== -1) state.questions.splice(
|
|
||||||
questionIndex,
|
|
||||||
1,
|
|
||||||
rawQuestionToQuestion(createdQuestion)
|
|
||||||
);
|
|
||||||
}, {
|
|
||||||
type: "createBackResult",
|
|
||||||
question,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
devlog("Error creating question", error);
|
|
||||||
enqueueSnackbar("Не удалось создать вопрос");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Мы получили запрос на создание резулта. Анализируем существует ли такой. Если да - просто делаем его активным
|
||||||
|
const question = useQuestionsStore.getState().questions.find(q=> q.type !== null && q?.content.rule.parentContentId === parentContentId)
|
||||||
|
|
||||||
|
if (question) {//существует, делаем активным
|
||||||
|
|
||||||
|
updateQuestion(question.id, (q) => {
|
||||||
|
q.content.usage = true
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {//не существует, создаём
|
||||||
|
const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content));
|
||||||
|
content.rule.parentId = parentContentId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdQuestion:RawQuestion = await questionApi.create({
|
||||||
|
quiz_id: quizId,
|
||||||
|
type: "result",
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
page: 101,
|
||||||
|
required: true,
|
||||||
|
content: JSON.stringify(content),
|
||||||
|
});
|
||||||
|
|
||||||
|
setProducedState(state => {
|
||||||
|
state.questions.push(rawQuestionToQuestion(createdQuestion))
|
||||||
|
}, {
|
||||||
|
type: "createBackResult",
|
||||||
|
createdQuestion,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
devlog("Error creating question", error);
|
||||||
|
enqueueSnackbar("Не удалось создать вопрос");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,8 +9,9 @@ import { NavigateFunction } from "react-router-dom";
|
|||||||
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
||||||
import { RequestQueue } from "../../utils/requestQueue";
|
import { RequestQueue } from "../../utils/requestQueue";
|
||||||
import { QuizStore, useQuizStore } from "./store";
|
import { QuizStore, useQuizStore } from "./store";
|
||||||
import { createUntypedQuestion } from "@root/questions/actions";
|
import { createUntypedQuestion, updateQuestion } from "@root/questions/actions";
|
||||||
import { useCurrentQuiz } from "./hooks"
|
import { useCurrentQuiz } from "./hooks"
|
||||||
|
import { useQuestionsStore } from "@root/questions/store";
|
||||||
|
|
||||||
|
|
||||||
export const setEditQuizId = (quizId: number | null) => setProducedState(state => {
|
export const setEditQuizId = (quizId: number | null) => setProducedState(state => {
|
||||||
@ -176,12 +177,40 @@ export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async (
|
|||||||
enqueueSnackbar(`Не удалось удалить квиз. ${message}`);
|
enqueueSnackbar(`Не удалось удалить квиз. ${message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export const updateRootContentId = (quizId: string, id:string) => updateQuiz(
|
export const updateRootContentId = (quizId: string, id: string) => {
|
||||||
quizId,
|
|
||||||
quiz => {
|
if (id.length === 0) {//дерева больше не существует, все результаты неактивны кроме результата линейности
|
||||||
quiz.config.haveRoot = id
|
useQuestionsStore.getState().questions.forEach((q) => {
|
||||||
},
|
if (q.type !== null && q.type === "result") {
|
||||||
);
|
if (q.content.rule.parentId === "line" && q.content.usage === false) {
|
||||||
|
updateQuestion(q.id, (q) => {
|
||||||
|
q.content.usage === true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
updateQuestion(q.id, (q) => {
|
||||||
|
q.content.usage === false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else { //было создано дерево, результат линейности неактивен
|
||||||
|
useQuestionsStore.getState().questions.forEach((q) => {
|
||||||
|
if (q.type !== null && q.content.rule.parentId === "line") {
|
||||||
|
updateQuestion(q.id, (q) => {
|
||||||
|
q.content.usage === false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateQuiz(
|
||||||
|
quizId,
|
||||||
|
quiz => {
|
||||||
|
quiz.config.haveRoot = id
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO copy quiz
|
// TODO copy quiz
|
||||||
|
@ -36,4 +36,5 @@ export const updateOpenedModalSettingsId = (id?: string) => useUiTools.setState(
|
|||||||
|
|
||||||
export const updateCanCreatePublic = (can: boolean) => useUiTools.setState({ canCreatePublic: can });
|
export const updateCanCreatePublic = (can: boolean) => useUiTools.setState({ canCreatePublic: can });
|
||||||
|
|
||||||
export const updateModalInfoWhyCantCreate = (can: boolean) => useUiTools.setState({ openModalInfoWhyCantCreate: can });
|
export const updateModalInfoWhyCantCreate = (can: boolean) => useUiTools.setState({ openModalInfoWhyCantCreate: can });
|
||||||
|
export const updateDeleteId = (deleteNodeId: string | null = null) => useUiTools.setState({ deleteNodeId });
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { devtools } from "zustand/middleware";
|
import { devtools } from "zustand/middleware";
|
||||||
|
|
||||||
|
|
||||||
export type UiTools = {
|
export type UiTools = {
|
||||||
openedModalSettingsId: string | null;
|
openedModalSettingsId: string | null;
|
||||||
dragQuestionContentId: string | null;
|
dragQuestionContentId: string | null;
|
||||||
@ -11,6 +10,8 @@ export type UiTools = {
|
|||||||
canCreatePublic: boolean;
|
canCreatePublic: boolean;
|
||||||
whyCantCreatePublic: Record<string, WhyCantCreatePublic>//ид вопроса и список претензий к нему
|
whyCantCreatePublic: Record<string, WhyCantCreatePublic>//ид вопроса и список претензий к нему
|
||||||
openModalInfoWhyCantCreate: boolean;
|
openModalInfoWhyCantCreate: boolean;
|
||||||
|
lastDeletionNodeTime: number | null;
|
||||||
|
deleteNodeId: string | null;
|
||||||
};
|
};
|
||||||
export type WhyCantCreatePublic = {
|
export type WhyCantCreatePublic = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -26,16 +27,15 @@ const initialState: UiTools = {
|
|||||||
editSomeQuestion: null as null,
|
editSomeQuestion: null as null,
|
||||||
canCreatePublic: false,
|
canCreatePublic: false,
|
||||||
whyCantCreatePublic: {},
|
whyCantCreatePublic: {},
|
||||||
openModalInfoWhyCantCreate: false
|
openModalInfoWhyCantCreate: false,
|
||||||
|
lastDeletionNodeTime: null,
|
||||||
|
deleteNodeId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUiTools = create<UiTools>()(
|
export const useUiTools = create<UiTools>()(
|
||||||
devtools(
|
devtools(() => initialState, {
|
||||||
() => initialState,
|
name: "UiTools",
|
||||||
{
|
enabled: process.env.NODE_ENV === "development",
|
||||||
name: "UiTools",
|
trace: process.env.NODE_ENV === "development",
|
||||||
enabled: process.env.NODE_ENV === "development",
|
})
|
||||||
trace: process.env.NODE_ENV === "development",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
@ -9,6 +9,7 @@ interface CustomNumberFieldProps {
|
|||||||
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||||||
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
emptyError?: boolean;
|
||||||
value: string;
|
value: string;
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
min?: number;
|
min?: number;
|
||||||
@ -20,6 +21,7 @@ export default function CustomNumberField({
|
|||||||
value,
|
value,
|
||||||
sx,
|
sx,
|
||||||
error,
|
error,
|
||||||
|
emptyError,
|
||||||
onChange,
|
onChange,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
onBlur,
|
onBlur,
|
||||||
@ -57,6 +59,7 @@ export default function CustomNumberField({
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
sx={sx}
|
sx={sx}
|
||||||
error={error}
|
error={error}
|
||||||
|
emptyError={emptyError}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
onBlur={onInputBlur}
|
onBlur={onInputBlur}
|
||||||
|
@ -7,6 +7,7 @@ interface CustomTextFieldProps {
|
|||||||
placeholder: string;
|
placeholder: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
emptyError?: boolean;
|
||||||
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
||||||
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
||||||
@ -25,6 +26,7 @@ export default function CustomTextField({
|
|||||||
text,
|
text,
|
||||||
sx,
|
sx,
|
||||||
error,
|
error,
|
||||||
|
emptyError,
|
||||||
InputProps,
|
InputProps,
|
||||||
maxLength = 200,
|
maxLength = 200,
|
||||||
}: CustomTextFieldProps) {
|
}: CustomTextFieldProps) {
|
||||||
@ -62,7 +64,7 @@ export default function CustomTextField({
|
|||||||
value={inputValue}
|
value={inputValue}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
error={!!error}
|
error={!!error || emptyError}
|
||||||
label={error}
|
label={error}
|
||||||
onFocus={handleInputFocus}
|
onFocus={handleInputFocus}
|
||||||
onBlur={handleInputBlur}
|
onBlur={handleInputBlur}
|
||||||
|
17
src/utils/hooks/useAddAnswer.ts
Normal file
17
src/utils/hooks/useAddAnswer.ts
Normal file
@ -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;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user