diff --git a/src/index.tsx b/src/index.tsx index eb2ac964..cd7404b0 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -46,7 +46,7 @@ root.render( {routeslink.map((e, i) => ( } /> ))} - } /> + } /> } /> diff --git a/src/pages/Questions/ButtonsOptions.tsx b/src/pages/Questions/ButtonsOptions.tsx index ad5505de..782f3761 100644 --- a/src/pages/Questions/ButtonsOptions.tsx +++ b/src/pages/Questions/ButtonsOptions.tsx @@ -28,18 +28,26 @@ import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight"; import { VectorQuestions } from "@icons/questionsPage/VectorQuestions"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; +import type { SxProps } from "@mui/material"; import type { QuizQuestionBase } from "../../model/questionTypes/shared"; interface Props { switchState: string; SSHC: (data: string) => void; totalIndex: number; + sx?: SxProps; + disableSettings?: boolean; + disableBranching?: boolean; + disableMiniButtons?: boolean; } export default function ButtonsOptions({ SSHC, switchState, totalIndex, + disableSettings = false, + disableBranching = false, + disableMiniButtons = false, }: Props) { const quizId = Number(useParams().quizId); const { openedModalSettings, listQuestions } = questionStore(); @@ -124,126 +132,137 @@ export default function ButtonsOptions({ {buttonSetting.map(({ icon, title, value, myFunc }) => ( {value === "branching" ? ( - - - Будет показан при условии - - - Название - - - Условие 1, Условие 2 - - - Все условия обязательны - - - } - > - { - SSHC(value); - myFunc(); - }} - sx={{ - backgroundColor: - switchState === value - ? theme.palette.brightPurple.main - : "transparent", - color: - switchState === value - ? "#ffffff" - : theme.palette.grey3.main, - minWidth: isWrappMiniButtonSetting ? "30px" : "64px", - height: "30px", - "&:hover": { - color: theme.palette.grey3.main, - "& path": { stroke: theme.palette.grey3.main }, - }, - }} + !disableBranching && ( + + + Будет показан при условии + + + Название + + + Условие 1, Условие 2 + + + Все условия обязательны + + + } > - {icon} - {isWrappMiniButtonSetting ? null : title} - - + { + SSHC(value); + myFunc(); + }} + sx={{ + backgroundColor: + switchState === value + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === value + ? "#ffffff" + : theme.palette.grey3.main, + minWidth: isWrappMiniButtonSetting ? "30px" : "64px", + height: "30px", + "&:hover": { + color: theme.palette.grey3.main, + "& path": { stroke: theme.palette.grey3.main }, + }, + }} + > + {icon} + {isWrappMiniButtonSetting ? null : title} + + + ) ) : ( - { - SSHC(value); - myFunc(); - }} - sx={{ - backgroundColor: - switchState === value - ? theme.palette.brightPurple.main - : "transparent", - color: - switchState === value - ? "#ffffff" - : theme.palette.grey3.main, - minWidth: isWrappMiniButtonSetting ? "30px" : "64px", - height: "30px", - "&:hover": { - color: theme.palette.grey3.main, - "& path": { stroke: theme.palette.grey3.main }, - }, - }} - > - {icon} - {isWrappMiniButtonSetting ? null : title} - + <> + {(value !== "setting" || + (value === "setting" && !disableSettings)) && ( + { + SSHC(value); + myFunc(); + }} + sx={{ + backgroundColor: + switchState === value + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === value + ? "#ffffff" + : theme.palette.grey3.main, + minWidth: isWrappMiniButtonSetting ? "30px" : "64px", + height: "30px", + "&:hover": { + color: theme.palette.grey3.main, + "& path": { stroke: theme.palette.grey3.main }, + }, + }} + > + {icon} + {isWrappMiniButtonSetting ? null : title} + + )} + )} ))} - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - + {!disableMiniButtons && ( + <> + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + + )} void; + anchorRef: RefObject; + totalIndex: number; + switchState: string; +}; + +export const ChooseAnswerModal = ({ + open, + onClose, + anchorRef, + totalIndex, + switchState, +}: ChooseAnswerModalProps) => { + const [openModal, setOpenModal] = useState(false); + const [selectedValue, setSelectedValue] = useState("text"); + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + const theme = useTheme(); + + return ( + <> + + {({ TransitionProps }) => ( + + + + + {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( + { + onClose(); + setOpenModal(true); + setSelectedValue(value); + }, + })} + > + {icon} + + {title} + + + ))} + + + + + )} + + setOpenModal(false)}> + + + Все настройки, кроме заголовка вопроса будут сброшены + + + + + + + + + ); +}; diff --git a/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx b/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx new file mode 100644 index 00000000..0b85ae13 --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx @@ -0,0 +1,86 @@ +import { memo } from "react"; +import { useParams } from "react-router-dom"; +import { Draggable } from "react-beautiful-dnd"; +import { Box, ListItem, Typography, useTheme } from "@mui/material"; + +import QuestionsPageCard from "./QuestionPageCard"; + +import { updateQuestionsList } from "@root/questions"; + +import { QuizQuestionBase } from "../../../../model/questionTypes/shared"; + +type FormDraggableListItemProps = { + index: number; + isDragging: boolean; + questionData: QuizQuestionBase; +}; + +export default memo( + ({ index, isDragging, questionData }: FormDraggableListItemProps) => { + const quizId = Number(useParams().quizId); + const theme = useTheme(); + console.log("Мой индекс " + index); + console.log(questionData); + + return ( + + {(provided) => ( + + {questionData.deleted ? ( + + + Вопрос удалён. + + { + updateQuestionsList(quizId, index, { + ...questionData, + deleted: false, + }); + }} + sx={{ + cursor: "pointer", + fontSize: "16px", + textDecoration: "underline", + color: theme.palette.brightPurple.main, + textDecorationColor: theme.palette.brightPurple.main, + }} + > + Восстановить? + + + ) : ( + + + + )} + + )} + + ); + } +); diff --git a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx new file mode 100644 index 00000000..e6b6445f --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx @@ -0,0 +1,444 @@ +import { useState, useRef, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import { + Box, + Checkbox, + FormControl, + FormControlLabel, + IconButton, + InputAdornment, + Paper, + TextField, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { useDebouncedCallback } from "use-debounce"; + +import { ChooseAnswerModal } from "./ChooseAnswerModal"; +import FormTypeQuestions from "../FormTypeQuestions"; +import SwitchQuestionsPage from "../../SwitchQuestionsPage"; + +import { + questionStore, + updateQuestionsList, + createQuestion, + copyQuestion, + removeQuestion, + removeQuestionForce, +} from "@root/questions"; + +import ExpandLessIcon from "@mui/icons-material/ExpandLess"; +import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; +import { OneIcon } from "@icons/questionsPage/OneIcon"; +import { PointsIcon } from "@icons/questionsPage/PointsIcon"; +import { CopyIcon } from "@icons/questionsPage/CopyIcon"; +import { CrossedEyeIcon } from "@icons/CrossedEyeIcon"; +import { HideIcon } from "@icons/questionsPage/hideIcon"; +import Answer from "@icons/questionsPage/answer"; +import OptionsPict from "@icons/questionsPage/options_pict"; +import OptionsAndPict from "@icons/questionsPage/options_and_pict"; +import Emoji from "@icons/questionsPage/emoji"; +import Input from "@icons/questionsPage/input"; +import DropDown from "@icons/questionsPage/drop_down"; +import Date from "@icons/questionsPage/date"; +import Slider from "@icons/questionsPage/slider"; +import Download from "@icons/questionsPage/download"; +import Page from "@icons/questionsPage/page"; +import RatingIcon from "@icons/questionsPage/rating"; +import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon"; +import { ReactComponent as PlusIcon } from "../../../../assets/icons/plus.svg"; + +import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; +import type { QuizQuestionBase } from "../../../../model/questionTypes/shared"; + +interface Props { + totalIndex: number; + draggableProps: DraggableProvidedDragHandleProps | null | undefined; + isDragging: boolean; +} + +const IconAndrom = (isExpanded: boolean, switchState: string) => { + switch (switchState) { + case "variant": + return ( + + ); + case "images": + return ( + + ); + case "varimg": + return ( + + ); + case "emoji": + return ( + + ); + case "text": + return ( + + ); + case "select": + return ( + + ); + case "date": + return ( + + ); + case "number": + return ( + + ); + case "file": + return ( + + ); + case "page": + return ( + + ); + case "rating": + return ( + + ); + default: + return <>; + } +}; +export default function QuestionsPageCard({ + totalIndex, + draggableProps, + isDragging, +}: Props) { + const [plusVisible, setPlusVisible] = useState(false); + const [open, setOpen] = useState(false); + const quizId = Number(useParams().quizId); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; + const anchorRef = useRef(null); + const debounced = useDebouncedCallback((title) => { + updateQuestionsList(quizId, totalIndex, { title }); + }, 1000); + + useEffect(() => { + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); + } + }, [question]); + + return ( + <> + + + + debounced(target.value)} + InputProps={{ + startAdornment: ( + + setOpen((isOpened) => !isOpened)} + > + {IconAndrom(question.expanded, question.type)} + + setOpen(false)} + anchorRef={anchorRef} + totalIndex={totalIndex} + switchState={question.type} + /> + + ), + }} + sx={{ + margin: isMobile ? "10px 0" : 0, + "& .MuiInputBase-root": { + color: question.expanded ? "#9A9AAF" : "#4D4D4D", + 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.length === 0 ? 0 : "18px", + }, + }} + /> + + + + updateQuestionsList(quizId, totalIndex, { + expanded: !question.expanded, + }) + } + > + {question.expanded ? ( + + ) : ( + + )} + + {question.expanded ? ( + <> + ) : ( + + + } + checkedIcon={} + /> + } + label={""} + sx={{ + color: theme.palette.grey2.main, + ml: "-9px", + mr: 0, + userSelect: "none", + }} + /> + copyQuestion(quizId, totalIndex)} + > + + + { + const removedId = question.id; + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); + } + + removeQuestion(quizId, totalIndex); + + const newTimeoutId = window.setTimeout(() => { + removeQuestionForce(quizId, removedId); + }, 5000); + + updateQuestionsList(quizId, totalIndex, { + ...question, + deleteTimeoutId: newTimeoutId, + }); + }} + > + + + + )} + + + + + + + + {question.expanded && ( + + {question.type.length === 0 ? ( + + ) : ( + + )} + + )} + + setPlusVisible(true)} + onMouseLeave={() => setPlusVisible(false)} + sx={{ + maxWidth: "825px", + display: "flex", + alignItems: "center", + height: "40px", + cursor: "pointer", + }} + > + createQuestion(quizId, "", totalIndex + 1)} + sx={{ + display: plusVisible && !isDragging ? "flex" : "none", + width: "100%", + alignItems: "center", + columnGap: "10px", + }} + > + + + + + + ); +} diff --git a/src/pages/Questions/Form/FormDraggableList/helper.ts b/src/pages/Questions/Form/FormDraggableList/helper.ts new file mode 100644 index 00000000..203ee13b --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/helper.ts @@ -0,0 +1,11 @@ +export const reorder = ( + list: T[], + startIndex: number, + endIndex: number +): T[] => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + + return result; +}; diff --git a/src/pages/Questions/Form/FormDraggableList/index.tsx b/src/pages/Questions/Form/FormDraggableList/index.tsx new file mode 100644 index 00000000..ea8cf384 --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/index.tsx @@ -0,0 +1,48 @@ +import { useParams } from "react-router-dom"; +import { Box } from "@mui/material"; +import { DragDropContext, Droppable } from "react-beautiful-dnd"; + +import DraggableListItem from "./FormDraggableListItem"; + +import { questionStore, updateQuestionsListDragAndDrop } from "@root/questions"; + +import { reorder } from "./helper"; + +import type { DropResult } from "react-beautiful-dnd"; + +export const FormDraggableList = () => { + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + + const onDragEnd = ({ destination, source }: DropResult) => { + if (destination) { + const newItems = reorder( + listQuestions[quizId], + source.index, + destination.index + ); + + updateQuestionsListDragAndDrop(quizId, newItems); + } + }; + + return ( + + + {(provided, snapshot) => ( + + {listQuestions[quizId]?.map((question, index) => ( + + ))} + {provided.placeholder} + + )} + + + ); +}; diff --git a/src/pages/Questions/Form/FormQuestionsPage.tsx b/src/pages/Questions/Form/FormQuestionsPage.tsx new file mode 100644 index 00000000..8f2730cc --- /dev/null +++ b/src/pages/Questions/Form/FormQuestionsPage.tsx @@ -0,0 +1,103 @@ +import { Box, Button, IconButton, Typography, useTheme } from "@mui/material"; +import { useParams } from "react-router-dom"; + +import { FormDraggableList } from "./FormDraggableList"; +import ButtonsOptions from "../ButtonsOptions"; + +import { + questionStore, + createQuestion, + updateQuestionsList, +} from "@root/questions"; +import { quizStore } from "@root/quizes"; + +import AddPlus from "../../../assets/icons/questionsPage/addPlus"; +import ArrowLeft from "../../../assets/icons/questionsPage/arrowLeft"; + +import type { AnyQuizQuestion } from "../../../model/questionTypes/shared"; + +export default function FormQuestionsPage() { + const { listQuizes, updateQuizesList } = quizStore(); + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + const handleNext = () => { + updateQuizesList(quizId, { step: listQuizes[quizId].step + 1 }); + }; + + const collapseEverything = () => { + listQuestions[quizId].forEach((item, index) => { + updateQuestionsList(quizId, index, { + ...item, + expanded: false, + }); + }); + }; + + const theme = useTheme(); + + return ( + <> + + Заголовок анкеты + + + + + { + createQuestion(quizId); + }} + > + + + + + + + + + ); +} diff --git a/src/pages/Questions/Form/FormTypeQuestions.tsx b/src/pages/Questions/Form/FormTypeQuestions.tsx new file mode 100644 index 00000000..f79ee6a3 --- /dev/null +++ b/src/pages/Questions/Form/FormTypeQuestions.tsx @@ -0,0 +1,146 @@ +import { useState } from "react"; +import QuestionsMiniButton from "@ui_kit/QuestionsMiniButton"; +import Answer from "../../../assets/icons/questionsPage/answer"; +import OptionsPict from "../../../assets/icons/questionsPage/options_pict"; +import OptionsAndPict from "../../../assets/icons/questionsPage/options_and_pict"; +import Emoji from "../../../assets/icons/questionsPage/emoji"; +import Input from "../../../assets/icons/questionsPage/input"; +import DropDown from "../../../assets/icons/questionsPage/drop_down"; +import Date from "../../../assets/icons/questionsPage/date"; +import Slider from "../../../assets/icons/questionsPage/slider"; +import Download from "../../../assets/icons/questionsPage/download"; +import Page from "../../../assets/icons/questionsPage/page"; +import RatingIcon from "../../../assets/icons/questionsPage/rating"; +import { Box } from "@mui/material"; +import React from "react"; +import { useParams } from "react-router-dom"; +import ButtonsOptions from "../ButtonsOptions"; + +import { + questionStore, + updateQuestionsList, + createQuestion, + removeQuestionForce, +} from "@root/questions"; + +import type { + QuizQuestionType, + QuizQuestionBase, +} from "../../../model/questionTypes/shared"; + +interface Props { + totalIndex: number; +} + +type ButtonTypeQuestion = { + icon: JSX.Element; + title: string; + value: QuizQuestionType; +}; + +export const BUTTON_TYPE_QUESTIONS: ButtonTypeQuestion[] = [ + { + icon: , + title: "Варианты ответов", + value: "variant", + }, + { + icon: , + title: "Варианты с картинками", + value: "images", + }, + { + icon: , + title: "Варианты и картинка", + value: "varimg", + }, + { + icon: , + title: "Эмоджи", + value: "emoji", + }, + { + icon: , + title: "Своё поле для ввода", + value: "text", + }, + { + icon: , + title: "Выпадающий список", + value: "select", + }, + { + icon: , + title: "Дата", + value: "date", + }, + { + icon: , + title: "Ползунок", + value: "number", + }, + { + icon: , + title: "Загрузка файла", + value: "file", + }, + { + icon: , + title: "Страница", + value: "page", + }, + { + icon: , + title: "Рейтинг", + value: "rating", + }, +]; + +export default function FormTypeQuestions({ totalIndex }: Props) { + const [switchState, setSwitchState] = useState("setting"); + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + + return ( + + + {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( + { + const question = { ...listQuestions[quizId][totalIndex] }; + + removeQuestionForce(quizId, question.id); + createQuestion(quizId, value, totalIndex); + updateQuestionsList(quizId, totalIndex, { + expanded: question.expanded, + type: value, + }); + }} + icon={icon} + text={title} + /> + ))} + + + + ); +} diff --git a/src/pages/Questions/TypeQuestions.tsx b/src/pages/Questions/TypeQuestions.tsx index 2b5e8007..7c29886f 100755 --- a/src/pages/Questions/TypeQuestions.tsx +++ b/src/pages/Questions/TypeQuestions.tsx @@ -13,6 +13,7 @@ import RatingIcon from "../../assets/icons/questionsPage/rating"; import { Box } from "@mui/material"; import React from "react"; import { useParams } from "react-router-dom"; + import { questionStore, updateQuestionsList, diff --git a/src/pages/createQuize/FirstQuiz.tsx b/src/pages/createQuize/FirstQuiz.tsx index 85b67121..4dbcc5e8 100755 --- a/src/pages/createQuize/FirstQuiz.tsx +++ b/src/pages/createQuize/FirstQuiz.tsx @@ -35,7 +35,7 @@ export default function FirstQuiz() { @@ -62,7 +62,7 @@ export default function MyQuizzesFull({outerContainerSx: sx, children}: Props) { removeQuiz(e.id) }} onClickEdit={() => - navigate(`/quize-setting/${e.id}`) + navigate(`/setting/${e.id}`) } /> ) diff --git a/src/ui_kit/switchStepPages.tsx b/src/ui_kit/switchStepPages.tsx index 50fa0f2b..1248cd89 100755 --- a/src/ui_kit/switchStepPages.tsx +++ b/src/ui_kit/switchStepPages.tsx @@ -3,6 +3,7 @@ import StepOne from "../pages/startPage/stepOne"; import Steptwo from "../pages/startPage/steptwo"; import StartPageSettings from "../pages/startPage/StartPageSettings"; import QuestionsPage from "../pages/Questions/QuestionsPage"; +import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage"; import ContactFormPage from "../pages/ContactFormPage/ContactFormPage"; import InstallQuiz from "../pages/InstallQuiz/InstallQuiz"; import { Result } from "../pages/Result/Result"; @@ -27,7 +28,7 @@ export default function SwitchStepPages({ if (!startpage) return ; return ; case 2: - if (quizType === "form") return ; + if (quizType === "form") return ; return ; case 3: if (!createResult) return ;