frontPanel/src/pages/Questions/DraggableList/QuestionPageCard.tsx

502 lines
17 KiB
TypeScript
Raw Normal View History

2023-11-14 20:15:52 +00:00
import { CrossedEyeIcon } from "@icons/CrossedEyeIcon";
import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon";
import { CopyIcon } from "@icons/questionsPage/CopyIcon";
2023-09-25 13:43:15 +00:00
import { OneIcon } from "@icons/questionsPage/OneIcon";
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
import Answer from "@icons/questionsPage/answer";
import Date from "@icons/questionsPage/date";
2023-11-14 20:15:52 +00:00
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import Download from "@icons/questionsPage/download";
2023-11-14 20:15:52 +00:00
import DropDown from "@icons/questionsPage/drop_down";
import Emoji from "@icons/questionsPage/emoji";
import { HideIcon } from "@icons/questionsPage/hideIcon";
import Input from "@icons/questionsPage/input";
import OptionsAndPict from "@icons/questionsPage/options_and_pict";
import OptionsPict from "@icons/questionsPage/options_pict";
import Page from "@icons/questionsPage/page";
import RatingIcon from "@icons/questionsPage/rating";
2023-11-14 20:15:52 +00:00
import Slider from "@icons/questionsPage/slider";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
2023-11-17 15:42:49 +00:00
import {
2023-12-19 23:08:33 +00:00
Box,
Button,
Checkbox,
FormControl,
FormControlLabel,
IconButton,
InputAdornment,
Modal,
Paper,
TextField,
Typography,
useMediaQuery,
useTheme,
2023-11-17 15:42:49 +00:00
} from "@mui/material";
2023-12-19 23:08:33 +00:00
import {
copyQuestion,
createUntypedQuestion,
deleteQuestion,
clearRuleForAll,
toggleExpandQuestion,
updateQuestion,
updateUntypedQuestion,
getQuestionByContentId,
deleteQuestionWithTimeout,
} from "@root/questions/actions";
import { updateRootContentId } from "@root/quizes/actions";
2023-11-17 15:42:49 +00:00
import { useRef, useState } from "react";
2023-11-14 20:15:52 +00:00
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
2023-11-17 15:42:49 +00:00
import { useDebouncedCallback } from "use-debounce";
2023-09-13 10:58:07 +00:00
import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg";
2023-11-29 13:49:52 +00:00
import type { AnyTypedQuizQuestion, UntypedQuizQuestion } from "../../../model/questionTypes/shared";
2023-11-15 18:38:02 +00:00
import SwitchQuestionsPage from "../SwitchQuestionsPage";
import { ChooseAnswerModal } from "./ChooseAnswerModal";
2023-11-29 13:49:52 +00:00
import TypeQuestions from "../TypeQuestions";
import { QuestionType } from "@model/question/question";
2023-12-08 13:36:00 +00:00
import { useCurrentQuiz } from "@root/quizes/hooks";
import { useQuestionsStore } from "@root/questions/store";
import { updateSomeWorkBackend } from "@root/uiTools/actions";
import { DeleteFunction } from "@utils/deleteFunc";
2023-08-08 11:01:37 +00:00
interface Props {
2023-11-29 13:49:52 +00:00
question: AnyTypedQuizQuestion | UntypedQuizQuestion;
2023-11-14 20:15:52 +00:00
draggableProps: DraggableProvidedDragHandleProps | null | undefined;
isDragging: boolean;
index: number;
}
export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) {
const maxLengthTextField = 225;
2023-12-19 23:08:33 +00:00
2023-12-12 19:05:30 +00:00
const { questions } = useQuestionsStore();
2023-11-14 20:15:52 +00:00
const [plusVisible, setPlusVisible] = useState<boolean>(false);
const [open, setOpen] = useState<boolean>(false);
2023-12-19 23:08:33 +00:00
const [isTextFieldtActive, setIsTextFieldtActive] = useState(false);
2023-11-17 15:42:49 +00:00
const [openDelete, setOpenDelete] = useState<boolean>(false);
2023-11-14 20:15:52 +00:00
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(790));
const anchorRef = useRef(null);
const quiz = useCurrentQuiz();
2023-11-17 15:42:49 +00:00
const setTitle = useDebouncedCallback((title) => {
2023-11-29 13:49:52 +00:00
const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion;
updateQuestionFn(question.id, question => {
2023-11-17 15:42:49 +00:00
question.title = title;
});
2023-11-14 20:15:52 +00:00
}, 200);
2023-09-27 14:14:48 +00:00
2023-12-19 23:08:33 +00:00
const handleInputFocus = () => {
setIsTextFieldtActive(true);
};
2023-12-19 23:08:33 +00:00
const handleInputBlur = (event: React.FocusEvent<HTMLInputElement>) => {
setIsTextFieldtActive(false);
};
2023-11-14 20:15:52 +00:00
return (
2023-12-19 23:08:33 +00:00
<>
<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 || " ")}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
inputProps={{
maxLength: maxLengthTextField,
}}
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>
),
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
2023-10-09 14:39:40 +00:00
sx={{
2023-12-19 23:08:33 +00:00
display: "flex",
height: "30px",
borderRight: "solid 1px #4D4D4D",
2023-10-09 14:39:40 +00:00
}}
2023-12-19 23:08:33 +00:00
>
<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, () => DeleteFunction(questions, question, quiz));
2023-12-19 23:08:33 +00:00
}
}}
data-cy="delete-question"
>
<DeleteIcon style={{ color: theme.palette.brightPurple.main }} />
</IconButton>
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
<Box
2023-11-14 20:15:52 +00:00
sx={{
2023-12-19 23:08:33 +00:00
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
padding: "30px",
borderRadius: "10px",
background: "#FFFFFF",
2023-11-14 20:15:52 +00:00
}}
2023-12-19 23:08:33 +00:00
>
<Typography variant="h6" sx={{ textAlign: "center" }}>
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы
уверены, что хотите удалить вопрос?
</Typography>
2023-11-14 20:15:52 +00:00
<Box
2023-12-19 23:08:33 +00:00
sx={{
marginTop: "30px",
display: "flex",
justifyContent: "center",
gap: "15px",
}}
2023-11-14 20:15:52 +00:00
>
2023-12-19 23:08:33 +00:00
<Button variant="contained" sx={{ minWidth: "150px" }} onClick={() => setOpenDelete(false)}>
Отмена
</Button>
<Button
variant="contained"
sx={{ minWidth: "150px" }}
onClick={() => {
deleteQuestionWithTimeout(question.id, () => DeleteFunction(questions, question, quiz));
2023-11-14 20:15:52 +00:00
}}
2023-12-19 23:08:33 +00:00
>
Подтвердить
</Button>
2023-11-14 20:15:52 +00:00
</Box>
2023-12-19 23:08:33 +00:00
</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,
2023-11-14 20:15:52 +00:00
}}
2023-12-19 23:08:33 +00:00
>
{question.page + 1}
</Box>
)}
<IconButton
disableRipple
sx={{
padding: isMobile ? "0" : "0 5px",
right: isMobile ? "0" : null,
bottom: isMobile ? "0" : null,
}}
{...draggableProps}
2023-09-15 12:37:12 +00:00
>
2023-12-19 23:08:33 +00:00
<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>
</>
);
2023-08-08 11:01:37 +00:00
}
2023-11-14 20:15:52 +00:00
2023-11-29 13:49:52 +00:00
const IconAndrom = (isExpanded: boolean, questionType: QuestionType | null) => {
switch (questionType) {
2023-11-14 20:15:52 +00:00
case "variant":
return (
<Answer
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "images":
return (
<OptionsPict
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "varimg":
return (
<OptionsAndPict
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "emoji":
return (
<Emoji
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "text":
return (
<Input
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "select":
return (
<DropDown
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "date":
return (
<Date
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "number":
return (
<Slider
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "file":
return (
<Download
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "page":
return (
<Page
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
case "rating":
return (
<RatingIcon
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
sx={{ height: "22px", width: "20px" }}
/>
);
default:
return <></>;
}
};