feat: create form questions page

This commit is contained in:
IlyaDoronin 2023-10-10 16:33:21 +03:00
parent 0573316cfc
commit 1bb899dcd7
14 changed files with 1128 additions and 121 deletions

@ -46,7 +46,7 @@ root.render(
{routeslink.map((e, i) => (
<Route key={i} path={e.path} element={<Main page={e.page} header={e.header} sidebar={e.sidebar} />} />
))}
<Route path="quize-setting/:quizId" element={<StartPage />} />
<Route path="setting/:quizId" element={<StartPage />} />
<Route path="crop" element={<ImageCrop />} />
</Routes>
</BrowserRouter>

@ -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 }) => (
<Box key={value}>
{value === "branching" ? (
<Tooltip
arrow
placement="right"
title={
<Box>
<Typography
sx={{
color: "#4D4D4D",
fontWeight: "bold",
fontSize: "14px",
marginBottom: "10px",
}}
>
Будет показан при условии
</Typography>
<Typography sx={{ fontWeight: "bold", fontSize: "12px" }}>
Название
</Typography>
<Typography
sx={{
fontWeight: "bold",
fontSize: "12px",
marginBottom: "10px",
}}
>
Условие 1, Условие 2
</Typography>
<Typography sx={{ color: "#7E2AEA", fontSize: "12px" }}>
Все условия обязательны
</Typography>
</Box>
}
>
<MiniButtonSetting
key={title}
onClick={() => {
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 && (
<Tooltip
arrow
placement="right"
title={
<Box>
<Typography
sx={{
color: "#4D4D4D",
fontWeight: "bold",
fontSize: "14px",
marginBottom: "10px",
}}
>
Будет показан при условии
</Typography>
<Typography sx={{ fontWeight: "bold", fontSize: "12px" }}>
Название
</Typography>
<Typography
sx={{
fontWeight: "bold",
fontSize: "12px",
marginBottom: "10px",
}}
>
Условие 1, Условие 2
</Typography>
<Typography sx={{ color: "#7E2AEA", fontSize: "12px" }}>
Все условия обязательны
</Typography>
</Box>
}
>
{icon}
{isWrappMiniButtonSetting ? null : title}
</MiniButtonSetting>
</Tooltip>
<MiniButtonSetting
key={title}
onClick={() => {
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}
</MiniButtonSetting>
</Tooltip>
)
) : (
<MiniButtonSetting
key={title}
onClick={() => {
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}
</MiniButtonSetting>
<>
{(value !== "setting" ||
(value === "setting" && !disableSettings)) && (
<MiniButtonSetting
key={title}
onClick={() => {
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}
</MiniButtonSetting>
)}
</>
)}
</Box>
))}
<MiniButtonSetting
onClick={() => setOpenedReallyChangingModal(true)}
sx={{
minWidth: "30px",
height: "30px",
backgroundColor: "#FEDFD0",
}}
>
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
</MiniButtonSetting>
<MiniButtonSetting
onClick={() => setOpenedReallyChangingModal(true)}
sx={{
minWidth: "30px",
height: "30px",
backgroundColor: "#FEDFD0",
}}
>
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
</MiniButtonSetting>
<MiniButtonSetting
onClick={() => setOpenedReallyChangingModal(true)}
sx={{
minWidth: "30px",
height: "30px",
backgroundColor: "#FEDFD0",
}}
>
<VectorQuestions style={{ color: "#FC712F", fontSize: "9px" }} />
</MiniButtonSetting>
{!disableMiniButtons && (
<>
<MiniButtonSetting
onClick={() => setOpenedReallyChangingModal(true)}
sx={{
minWidth: "30px",
height: "30px",
backgroundColor: "#FEDFD0",
}}
>
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
</MiniButtonSetting>
<MiniButtonSetting
onClick={() => setOpenedReallyChangingModal(true)}
sx={{
minWidth: "30px",
height: "30px",
backgroundColor: "#FEDFD0",
}}
>
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
</MiniButtonSetting>
<MiniButtonSetting
onClick={() => setOpenedReallyChangingModal(true)}
sx={{
minWidth: "30px",
height: "30px",
backgroundColor: "#FEDFD0",
}}
>
<VectorQuestions style={{ color: "#FC712F", fontSize: "9px" }} />
</MiniButtonSetting>
</>
)}
</Box>
<Box
sx={{

@ -172,9 +172,9 @@ export default function QuestionsPageCard({
maxWidth: "796px",
width: "100%",
borderRadius: "12px",
boxShadow: "none",
backgroundColor: question.expanded ? "white" : "#EEE4FC",
border: question.expanded ? "none" : "1px solid #9A9AAF",
boxShadow: "0px 10px 30px #e7e7e7",
}}
>
<Box

@ -0,0 +1,148 @@
import { useState } from "react";
import { useParams } from "react-router-dom";
import {
Box,
Typography,
Popper,
Grow,
Paper,
MenuList,
MenuItem,
ClickAwayListener,
Modal,
Button,
useTheme,
} from "@mui/material";
import {
questionStore,
updateQuestionsList,
removeQuestionForce,
createQuestion,
} from "@root/questions";
import { BUTTON_TYPE_QUESTIONS } from "../../TypeQuestions";
import type { RefObject } from "react";
import type {
QuizQuestionType,
QuizQuestionBase,
} from "../../../../model/questionTypes/shared";
type ChooseAnswerModalProps = {
open: boolean;
onClose: () => void;
anchorRef: RefObject<HTMLDivElement>;
totalIndex: number;
switchState: string;
};
export const ChooseAnswerModal = ({
open,
onClose,
anchorRef,
totalIndex,
switchState,
}: ChooseAnswerModalProps) => {
const [openModal, setOpenModal] = useState<boolean>(false);
const [selectedValue, setSelectedValue] = useState<QuizQuestionType>("text");
const quizId = Number(useParams().quizId);
const { listQuestions } = questionStore();
const theme = useTheme();
return (
<>
<Popper
placement="right-start"
open={open}
anchorEl={anchorRef.current}
transition
>
{({ TransitionProps }) => (
<Grow {...TransitionProps}>
<Paper>
<ClickAwayListener onClickAway={onClose}>
<MenuList autoFocusItem={open}>
{BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => (
<MenuItem
key={value}
sx={{ display: "flex", gap: "10px" }}
{...(value !== switchState && {
onClick: () => {
onClose();
setOpenModal(true);
setSelectedValue(value);
},
})}
>
<Box>{icon}</Box>
<Typography
sx={{
color:
value === switchState
? theme.palette.brightPurple.main
: theme.palette.grey2.main,
}}
>
{title}
</Typography>
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
<Modal open={openModal} onClose={() => setOpenModal(false)}>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
padding: "30px",
borderRadius: "10px",
background: "#FFFFFF",
}}
>
<Typography variant="h6">
Все настройки, кроме заголовка вопроса будут сброшены
</Typography>
<Box
sx={{
marginTop: "30px",
display: "flex",
justifyContent: "center",
gap: "15px",
}}
>
<Button
variant="contained"
sx={{ minWidth: "150px" }}
onClick={() => setOpenModal(false)}
>
Отмена
</Button>
<Button
variant="contained"
sx={{ minWidth: "150px" }}
onClick={() => {
setOpenModal(false);
const question = { ...listQuestions[quizId][totalIndex] };
removeQuestionForce(quizId, question.id);
createQuestion(quizId, selectedValue, totalIndex);
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
expanded: question.expanded,
});
}}
>
Подтвердить
</Button>
</Box>
</Box>
</Modal>
</>
);
};

@ -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 (
<Draggable draggableId={String(index)} index={index}>
{(provided) => (
<ListItem
ref={provided.innerRef}
{...provided.draggableProps}
sx={{ userSelect: "none", padding: 0 }}
>
{questionData.deleted ? (
<Box
{...provided.dragHandleProps}
sx={{
width: "100%",
maxWidth: "800px",
display: "flex",
justifyContent: "center",
marginBottom: "40px",
gap: "5px",
}}
>
<Typography
sx={{
fontSize: "16px",
color: theme.palette.grey2.main,
}}
>
Вопрос удалён.
</Typography>
<Typography
onClick={() => {
updateQuestionsList<QuizQuestionBase>(quizId, index, {
...questionData,
deleted: false,
});
}}
sx={{
cursor: "pointer",
fontSize: "16px",
textDecoration: "underline",
color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main,
}}
>
Восстановить?
</Typography>
</Box>
) : (
<Box sx={{ width: "100%", position: "relative" }}>
<QuestionsPageCard
key={index}
totalIndex={index}
draggableProps={provided.dragHandleProps}
isDragging={isDragging}
/>
</Box>
)}
</ListItem>
)}
</Draggable>
);
}
);

@ -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 (
<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 <></>;
}
};
export default function QuestionsPageCard({
totalIndex,
draggableProps,
isDragging,
}: Props) {
const [plusVisible, setPlusVisible] = useState<boolean>(false);
const [open, setOpen] = useState<boolean>(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<QuizQuestionBase>(quizId, totalIndex, { title });
}, 1000);
useEffect(() => {
if (question.deleteTimeoutId) {
clearTimeout(question.deleteTimeoutId);
}
}, [question]);
return (
<>
<Paper
id={String(totalIndex)}
sx={{
overflow: "hidden",
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 }) => debounced(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}
totalIndex={totalIndex}
switchState={question.type}
/>
</Box>
),
}}
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",
},
}}
/>
</FormControl>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
width: isMobile ? "100%" : "auto",
position: "relative",
}}
>
<IconButton
sx={{ padding: "0", margin: "5px" }}
disableRipple
onClick={() =>
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
expanded: !question.expanded,
})
}
>
{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(quizId, totalIndex)}
>
<CopyIcon
style={{ color: theme.palette.brightPurple.main }}
/>
</IconButton>
<IconButton
sx={{
cursor: "pointer",
borderRadius: "6px",
padding: "0",
margin: "0 5px 0 10px",
}}
onClick={() => {
const removedId = question.id;
if (question.deleteTimeoutId) {
clearTimeout(question.deleteTimeoutId);
}
removeQuestion(quizId, totalIndex);
const newTimeoutId = window.setTimeout(() => {
removeQuestionForce(quizId, removedId);
}, 5000);
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
...question,
deleteTimeoutId: newTimeoutId,
});
}}
>
<DeleteIcon
style={{ color: theme.palette.brightPurple.main }}
/>
</IconButton>
</Box>
)}
<OneIcon
style={{
fontSize: "30px",
marginLeft: "3px",
color: !question.expanded ? "#FFF" : "",
fill: question.expanded
? "#EEE4FC"
: theme.palette.brightPurple.main,
}}
/>
<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.length === 0 ? (
<FormTypeQuestions totalIndex={totalIndex} />
) : (
<SwitchQuestionsPage totalIndex={totalIndex} />
)}
</Box>
)}
</Paper>
<Box
onMouseEnter={() => setPlusVisible(true)}
onMouseLeave={() => setPlusVisible(false)}
sx={{
maxWidth: "825px",
display: "flex",
alignItems: "center",
height: "40px",
cursor: "pointer",
}}
>
<Box
onClick={() => createQuestion(quizId, "", totalIndex + 1)}
sx={{
display: plusVisible && !isDragging ? "flex" : "none",
width: "100%",
alignItems: "center",
columnGap: "10px",
}}
>
<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>
</>
);
}

@ -0,0 +1,11 @@
export const reorder = <T>(
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;
};

@ -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 (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable-list">
{(provided, snapshot) => (
<Box ref={provided.innerRef} {...provided.droppableProps}>
{listQuestions[quizId]?.map((question, index) => (
<DraggableListItem
key={index}
index={index}
isDragging={snapshot.isDraggingOver}
questionData={question}
/>
))}
{provided.placeholder}
</Box>
)}
</Droppable>
</DragDropContext>
);
};

@ -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<AnyQuizQuestion>(quizId, index, {
...item,
expanded: false,
});
});
};
const theme = useTheme();
return (
<>
<Box
sx={{
maxWidth: "796px",
width: "100%",
display: "flex",
justifyContent: "space-between",
margin: "60px 0 40px 0",
}}
>
<Typography variant={"h5"}>Заголовок анкеты</Typography>
<Button
sx={{
fontSize: "16px",
lineHeight: "19px",
padding: 0,
textDecoration: "underline",
color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main,
}}
onClick={collapseEverything}
>
Свернуть всё
</Button>
</Box>
<FormDraggableList />
<Box
sx={{
display: "flex",
justifyContent: "space-between",
maxWidth: "796px",
}}
>
<IconButton
onClick={() => {
createQuestion(quizId);
}}
>
<AddPlus />
</IconButton>
<Box sx={{ display: "flex", gap: "8px" }}>
<Button
variant="outlined"
sx={{ padding: "10px 20px", borderRadius: "8px", height: "44px" }}
>
<ArrowLeft />
</Button>
<Button
variant="contained"
sx={{
height: "44px",
padding: "10px 20px",
borderRadius: "8px",
background: theme.palette.brightPurple.main,
fontSize: "18px",
}}
onClick={handleNext}
>
Следующий шаг
</Button>
</Box>
</Box>
</>
);
}

@ -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: <Answer color="#9A9AAF" />,
title: "Варианты ответов",
value: "variant",
},
{
icon: <OptionsPict color="#9A9AAF" />,
title: "Варианты с картинками",
value: "images",
},
{
icon: <OptionsAndPict color="#9A9AAF" />,
title: "Варианты и картинка",
value: "varimg",
},
{
icon: <Emoji color="#9A9AAF" />,
title: "Эмоджи",
value: "emoji",
},
{
icon: <Input color="#9A9AAF" />,
title: "Своё поле для ввода",
value: "text",
},
{
icon: <DropDown color="#9A9AAF" />,
title: "Выпадающий список",
value: "select",
},
{
icon: <Date color="#9A9AAF" />,
title: "Дата",
value: "date",
},
{
icon: <Slider color="#9A9AAF" />,
title: "Ползунок",
value: "number",
},
{
icon: <Download color="#9A9AAF" />,
title: "Загрузка файла",
value: "file",
},
{
icon: <Page color="#9A9AAF" />,
title: "Страница",
value: "page",
},
{
icon: <RatingIcon color="#9A9AAF" />,
title: "Рейтинг",
value: "rating",
},
];
export default function FormTypeQuestions({ totalIndex }: Props) {
const [switchState, setSwitchState] = useState("setting");
const quizId = Number(useParams().quizId);
const { listQuestions } = questionStore();
return (
<Box
sx={{
paddingTop: "8px",
}}
>
<Box
sx={{
display: "flex",
flexWrap: "wrap",
gap: "20px",
margin: "0 20px 20px",
}}
>
{BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => (
<QuestionsMiniButton
key={title}
onClick={() => {
const question = { ...listQuestions[quizId][totalIndex] };
removeQuestionForce(quizId, question.id);
createQuestion(quizId, value, totalIndex);
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
expanded: question.expanded,
type: value,
});
}}
icon={icon}
text={title}
/>
))}
</Box>
<ButtonsOptions
switchState={switchState}
SSHC={setSwitchState}
totalIndex={totalIndex}
disableSettings
disableBranching
disableMiniButtons
/>
</Box>
);
}

@ -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,

@ -35,7 +35,7 @@ export default function FirstQuiz() {
<Button
variant="contained"
onClick={() => {
navigate(`/quize-setting/${createBlank()}`);
navigate(`/setting/${createBlank()}`);
}}
>
Создать +

@ -40,7 +40,7 @@ export default function MyQuizzesFull({outerContainerSx: sx, children}: Props) {
variant="contained"
sx={{padding: "10px 47px"}}
onClick={() => {
navigate(`/quize-setting/${createBlank()}`);
navigate(`/setting/${createBlank()}`);
}}
>Создать +</Button>
</Box>
@ -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}`)
}
/>
)

@ -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 <Steptwo />;
return <StartPageSettings />;
case 2:
if (quizType === "form") return <QuestionsPage />;
if (quizType === "form") return <FormQuestionsPage />;
return <QuestionsPage />;
case 3:
if (!createResult) return <Result />;