Merge branch 'fix-questions-store' into 'dev'

fix: questions store

See merge request frontend/squiz!9
This commit is contained in:
Mikhail 2023-09-12 17:35:12 +00:00
commit 354e97cb8f
21 changed files with 909 additions and 191 deletions

@ -1,29 +1,63 @@
import { Box } from "@mui/material";
interface Props {
color: string;
color: string;
}
export default function DeleteIcon({color}: Props) {
return (
<Box
sx={{
height: "30px",
width: "30px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.25 5.25H3.75" stroke="#4D4D4D" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M9.75 9.75V15.75" stroke="#4D4D4D" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M14.25 9.75V15.75" stroke="#4D4D4D" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M18.75 5.25V19.5C18.75 19.6989 18.671 19.8897 18.5303 20.0303C18.3897 20.171 18.1989 20.25 18 20.25H6C5.80109 20.25 5.61032 20.171 5.46967 20.0303C5.32902 19.8897 5.25 19.6989 5.25 19.5V5.25" stroke="#4D4D4D" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M15.75 5.25V3.75C15.75 3.35218 15.592 2.97064 15.3107 2.68934C15.0294 2.40804 14.6478 2.25 14.25 2.25H9.75C9.35218 2.25 8.97064 2.40804 8.68934 2.68934C8.40804 2.97064 8.25 3.35218 8.25 3.75V5.25" stroke="#4D4D4D" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</Box>
);
export default function DeleteIcon({ color = "#4D4D4D" }: Props) {
return (
<Box
sx={{
height: "30px",
width: "30px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M20.25 5.25H3.75"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.75 9.75V15.75"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14.25 9.75V15.75"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M18.75 5.25V19.5C18.75 19.6989 18.671 19.8897 18.5303 20.0303C18.3897 20.171 18.1989 20.25 18 20.25H6C5.80109 20.25 5.61032 20.171 5.46967 20.0303C5.32902 19.8897 5.25 19.6989 5.25 19.5V5.25"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.75 5.25V3.75C15.75 3.35218 15.592 2.97064 15.3107 2.68934C15.0294 2.40804 14.6478 2.25 14.25 2.25H9.75C9.35218 2.25 8.97064 2.40804 8.68934 2.68934C8.40804 2.97064 8.25 3.35218 8.25 3.75V5.25"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</Box>
);
}

@ -1,25 +1,37 @@
import { Box } from "@mui/material";
import type { SxProps } from "@mui/material";
interface Props {
color: string;
sx?: SxProps;
onClick?: () => void;
}
export default function RatingStar({color}: Props) {
return (
<Box
sx={{
height: "38px",
width: "45px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<svg width="50" height="48" viewBox="0 0 50 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.0576 40.0068L38.1721 47.6836C39.7345 48.6672 41.6574 47.2038 41.2007 45.4045L37.6914 31.6342C37.5965 31.2525 37.6116 30.8518 37.7349 30.4783C37.8582 30.1048 38.0848 29.7736 38.3884 29.523L49.253 20.4787C50.6712 19.3032 49.9501 16.9282 48.0992 16.8082L33.9176 15.8966C33.5307 15.8741 33.1586 15.7396 32.847 15.5096C32.5354 15.2795 32.2977 14.9638 32.1629 14.6011L26.8749 1.31053C26.7349 0.926449 26.4799 0.594665 26.1445 0.360213C25.8091 0.125762 25.4095 0 25 0C24.5905 0 24.1909 0.125762 23.8555 0.360213C23.5201 0.594665 23.2651 0.926449 23.1251 1.31053L17.8371 14.6011C17.7023 14.9638 17.4646 15.2795 17.153 15.5096C16.8414 15.7396 16.4693 15.8741 16.0824 15.8966L1.90076 16.8082C0.0499407 16.9282 -0.671159 19.3032 0.747004 20.4787L11.6116 29.523C11.9152 29.7736 12.1418 30.1048 12.2651 30.4783C12.3884 30.8518 12.4035 31.2525 12.3086 31.6342L9.06369 44.397C8.51085 46.5561 10.8184 48.3074 12.6692 47.1318L23.9424 40.0068C24.2585 39.8061 24.6254 39.6996 25 39.6996C25.3746 39.6996 25.7415 39.8061 26.0576 40.0068Z" fill={color}/>
</svg>
</Box>
);
export default function RatingStar({ sx, onClick }: Props) {
return (
<Box
sx={{
height: "38px",
width: "45px",
display: "flex",
alignItems: "center",
justifyContent: "center",
...sx,
}}
onClick={onClick}
>
<svg
width="50"
height="48"
viewBox="0 0 50 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M26.0576 40.0068L38.1721 47.6836C39.7345 48.6672 41.6574 47.2038 41.2007 45.4045L37.6914 31.6342C37.5965 31.2525 37.6116 30.8518 37.7349 30.4783C37.8582 30.1048 38.0848 29.7736 38.3884 29.523L49.253 20.4787C50.6712 19.3032 49.9501 16.9282 48.0992 16.8082L33.9176 15.8966C33.5307 15.8741 33.1586 15.7396 32.847 15.5096C32.5354 15.2795 32.2977 14.9638 32.1629 14.6011L26.8749 1.31053C26.7349 0.926449 26.4799 0.594665 26.1445 0.360213C25.8091 0.125762 25.4095 0 25 0C24.5905 0 24.1909 0.125762 23.8555 0.360213C23.5201 0.594665 23.2651 0.926449 23.1251 1.31053L17.8371 14.6011C17.7023 14.9638 17.4646 15.2795 17.153 15.5096C16.8414 15.7396 16.4693 15.8741 16.0824 15.8966L1.90076 16.8082C0.0499407 16.9282 -0.671159 19.3032 0.747004 20.4787L11.6116 29.523C11.9152 29.7736 12.1418 30.1048 12.2651 30.4783C12.3884 30.8518 12.4035 31.2525 12.3086 31.6342L9.06369 44.397C8.51085 46.5561 10.8184 48.3074 12.6692 47.1318L23.9424 40.0068C24.2585 39.8061 24.6254 39.6996 25 39.6996C25.3746 39.6996 25.7415 39.8061 26.0576 40.0068Z"
fill="#9A9AAF"
/>
</svg>
</Box>
);
}

@ -109,7 +109,7 @@ export const AnswerItem = ({
key={index}
fullWidth
variant="standard"
sx={{ p: "0 0 20px 0" }}
sx={{ padding: "0 0 20px 0" }}
>
<TextField
value={variant.answer}
@ -171,7 +171,7 @@ export const AnswerItem = ({
}}
sx={{
"& .MuiInputBase-root": {
padding: "13.5px",
padding: icon ? "5px 13.5px" : "13.5px",
borderRadius: "10px",
background: "#ffffff",
},

@ -1,45 +1,75 @@
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
import { useParams } from "react-router-dom";
import React from "react";
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
import Clue from "../../assets/icons/questionsPage/clue";
import Branching from "../../assets/icons/questionsPage/branching";
import { Box, IconButton, useTheme } from "@mui/material";
import { Box, Typography, Tooltip, IconButton, useTheme } from "@mui/material";
import HideIcon from "../../assets/icons/questionsPage/hideIcon";
import CopyIcon from "../../assets/icons/questionsPage/CopyIcon";
import DeleteIcon from "../../assets/icons/questionsPage/deleteIcon";
import {useParams} from "react-router-dom";
import { questionStore, resetSomeField, removeQuestion } from "@root/questions";
import {
questionStore,
resetSomeField,
copyQuestion,
removeQuestion,
} from "@root/questions";
interface Props {
switchState: string;
SSHC: (data: string) => void;
totalIndex: number
totalIndex: number;
}
export default function ButtonsOptions({ SSHC, switchState, totalIndex }: Props) {
export default function ButtonsOptions({
SSHC,
switchState,
totalIndex,
}: Props) {
const quizId = Number(useParams().quizId);
const {openedModalSettings} = questionStore()
const { openedModalSettings } = questionStore();
const openedModal = () => {
resetSomeField({openedModalSettings: "open"})
console.log(openedModalSettings)
}
resetSomeField({ openedModalSettings: "open" });
console.log(openedModalSettings);
};
const theme = useTheme();
const buttonSetting: { icon: JSX.Element; title: string; value: string; myFunc?: any }[] = [
const buttonSetting: {
icon: JSX.Element;
title: string;
value: string;
myFunc?: any;
}[] = [
{
icon: <SettingIcon color={switchState === "setting" ? "#ffffff" : theme.palette.grey3.main} />,
icon: (
<SettingIcon
color={
switchState === "setting" ? "#ffffff" : theme.palette.grey3.main
}
/>
),
title: "Настройки",
value: "setting",
},
{
icon: <Clue color={switchState === "help" ? "#ffffff" : theme.palette.grey3.main} />,
icon: (
<Clue
color={switchState === "help" ? "#ffffff" : theme.palette.grey3.main}
/>
),
title: "Подсказка",
value: "help",
},
{
icon: <Branching color={switchState === "branching" ? "#ffffff" : theme.palette.grey3.main} />,
icon: (
<Branching
color={
switchState === "branching" ? "#ffffff" : theme.palette.grey3.main
}
/>
),
title: "Ветвление",
value: "branching",
myFunc: openedModal
myFunc: openedModal,
},
];
@ -59,21 +89,86 @@ export default function ButtonsOptions({ SSHC, switchState, totalIndex }: Props)
gap: "10px",
}}
>
{buttonSetting.map(({ icon, title, value, myFunc}) => (
<MiniButtonSetting
key={title}
onClick={() => {
SSHC(value);
myFunc()
}}
sx={{
backgroundColor: switchState === value ? theme.palette.brightPurple.main : "transparent",
color: switchState === value ? "#ffffff" : theme.palette.grey3.main,
}}
>
{icon}
{title}
</MiniButtonSetting>
{buttonSetting.map(({ icon, title, value, myFunc }) => (
<>
{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,
}}
>
{icon}
{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,
}}
>
{icon}
{title}
</MiniButtonSetting>
)}
</>
))}
</Box>
<Box
@ -82,13 +177,19 @@ export default function ButtonsOptions({ SSHC, switchState, totalIndex }: Props)
}}
>
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
<HideIcon color={"#4D4D4D"}/>
<HideIcon color={"#4D4D4D"} />
</IconButton>
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
<CopyIcon color={"#4D4D4D"}/>
<IconButton
sx={{ borderRadius: "6px", padding: "2px" }}
onClick={() => copyQuestion(quizId, totalIndex)}
>
<CopyIcon color={"#4D4D4D"} />
</IconButton>
<IconButton sx={{ borderRadius: "6px", padding: "2px" }} onClick={() => removeQuestion(quizId, totalIndex)}>
<DeleteIcon color={"#4D4D4D"}/>
<IconButton
sx={{ borderRadius: "6px", padding: "2px" }}
onClick={() => removeQuestion(quizId, totalIndex)}
>
<DeleteIcon color={"#4D4D4D"} />
</IconButton>
</Box>
</Box>

@ -9,7 +9,12 @@ import CopyIcon from "../../assets/icons/questionsPage/CopyIcon";
import DeleteIcon from "../../assets/icons/questionsPage/deleteIcon";
import ImgIcon from "../../assets/icons/questionsPage/imgIcon";
import { useParams } from "react-router-dom";
import { questionStore, removeQuestion, resetSomeField } from "@root/questions";
import {
questionStore,
copyQuestion,
removeQuestion,
resetSomeField,
} from "@root/questions";
import "./ButtonsOptionsAndPict.css";
@ -176,7 +181,10 @@ export default function ButtonsOptionsAndPict({
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
<HideIcon color={"#4D4D4D"} />
</IconButton>
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
<IconButton
sx={{ borderRadius: "6px", padding: "2px" }}
onClick={() => copyQuestion(quizId, totalIndex)}
>
<CopyIcon color={"#4D4D4D"} />
</IconButton>
<IconButton

@ -1,6 +1,7 @@
import { useParams } from "react-router-dom";
import { Box, Typography } from "@mui/material";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import InfoIcon from "../../../assets/icons/InfoIcon";
import { questionStore, updateQuestionsList } from "@root/questions";
@ -17,7 +18,7 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
<Box sx={{ padding: "20px" }}>
<Typography>Настройки календаря</Typography>
<CustomCheckbox
label={"Выбор диапозона дат"}
label={"Выбор диапазона дат"}
checked={listQuestions[quizId][totalIndex].content.dateRange}
handleChange={({ target }) => {
const clonContent = listQuestions[quizId][totalIndex].content;
@ -35,10 +36,45 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
}}
/>
</Box>
<Box sx={{ padding: "20px" }}>
<Box sx={{ padding: "20px", minWidth: "350px", marginRight: "30px" }}>
<Typography>Настройки вопросов</Typography>
<CustomCheckbox label={"Необязательный вопрос"} />
<CustomCheckbox label={"Внутреннее название вопроса"} /> <InfoIcon />
<CustomCheckbox
label={"Необязательный вопрос"}
checked={!listQuestions[quizId][totalIndex].required}
handleChange={(e) => {
updateQuestionsList(quizId, totalIndex, {
required: !e.target.checked,
});
}}
/>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
handleChange={(e) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerNameCheck = e.target.checked;
if (!e.target.checked) {
clonContent.innerName = "";
}
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>{" "}
<InfoIcon />
</Box>
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
<CustomTextField
placeholder={"Развёрнутое описание вопроса"}
text={listQuestions[quizId][totalIndex].content.innerName}
onChange={({ target }) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerName = target.value;
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
)}
</Box>
</Box>
);

@ -24,7 +24,18 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
<Typography sx={{ marginBottom: "15px" }}>
Настройки ответов
</Typography>
<CustomCheckbox label={"Можно несколько"} />
<CustomCheckbox
label={"Можно несколько"}
checked={listQuestions[quizId][totalIndex].content.multi}
handleChange={({ target }) =>
updateQuestionsList(quizId, totalIndex, {
content: {
...listQuestions[quizId][totalIndex].content,
multi: target.checked,
},
})
}
/>
<Typography sx={{ marginBottom: "15px" }}>
Текст в выпадающем списке
</Typography>
@ -38,12 +49,51 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
}}
/>
</Box>
<Box sx={{ padding: "20px" }}>
<Box sx={{ padding: "20px", width: "100%" }}>
<Typography sx={{ marginBottom: "15px" }}>
Настройки вопросов
</Typography>
<CustomCheckbox label={"Необязательный вопрос"} />
<CustomCheckbox label={"Внутреннее название вопроса"} /> <InfoIcon />
<CustomCheckbox
label={"Необязательный вопрос"}
checked={!listQuestions[quizId][totalIndex].required}
handleChange={(e) => {
updateQuestionsList(quizId, totalIndex, {
required: !e.target.checked,
});
}}
/>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
handleChange={(e) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerNameCheck = e.target.checked;
if (!e.target.checked) {
clonContent.innerName = "";
}
updateQuestionsList(quizId, totalIndex, {
content: clonContent,
});
}}
/>{" "}
<InfoIcon />
</Box>
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
<CustomTextField
placeholder={"Развёрнутое описание вопроса"}
text={listQuestions[quizId][totalIndex].content.innerName}
onChange={({ target }) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerName = target.value;
updateQuestionsList(quizId, totalIndex, {
content: clonContent,
});
}}
/>
)}
</Box>
</Box>
</>

@ -1,6 +1,7 @@
import { useParams } from "react-router-dom";
import { Box, Typography } from "@mui/material";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import InfoIcon from "../../../assets/icons/InfoIcon";
import { questionStore, updateQuestionsList } from "@root/questions";
@ -13,7 +14,7 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
const { listQuestions } = questionStore();
return (
<Box sx={{ display: "flex" }}>
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Box sx={{ padding: "20px" }}>
<Typography>Настройки ответов</Typography>
<CustomCheckbox
@ -35,10 +36,45 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
}}
/>
</Box>
<Box sx={{ padding: "20px" }}>
<Box sx={{ padding: "20px", minWidth: "350px", marginRight: "30px" }}>
<Typography>Настройки вопросов</Typography>
<CustomCheckbox label={"Необязательный вопрос"} />
<CustomCheckbox label={"Внутреннее название вопроса"} /> <InfoIcon />
<CustomCheckbox
label={"Необязательный вопрос"}
checked={!listQuestions[quizId][totalIndex].required}
handleChange={(e) => {
updateQuestionsList(quizId, totalIndex, {
required: !e.target.checked,
});
}}
/>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
handleChange={(e) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerNameCheck = e.target.checked;
if (!e.target.checked) {
clonContent.innerName = "";
}
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>{" "}
<InfoIcon />
</Box>
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
<CustomTextField
placeholder={"Развёрнутое описание вопроса"}
text={listQuestions[quizId][totalIndex].content.innerName}
onChange={({ target }) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerName = target.value;
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
)}
</Box>
</Box>
);

@ -89,7 +89,13 @@ export default function SettingOpytionsPict({
return (
<>
<Box sx={{ display: "flex" }}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
marginRight: "30px",
}}
>
<Box sx={{ padding: "20px" }}>
<Typography sx={{ marginBottom: "15px" }}>Пропорции</Typography>
<Box
@ -152,7 +158,7 @@ export default function SettingOpytionsPict({
}
/>
</Box>
<Box sx={{ padding: "20px" }}>
<Box sx={{ padding: "20px", minWidth: "350px" }}>
<Typography sx={{ marginBottom: "15px" }}>Форма</Typography>
<Box
sx={{
@ -205,7 +211,7 @@ export default function SettingOpytionsPict({
})
}
/>
<Box sx={{ display: "flex" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
sx={{ width: "100%" }}
label={"Внутреннее название вопроса"}

@ -9,9 +9,12 @@ import {
useTheme,
} from "@mui/material";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { questionStore, updateQuestionsList } from "@root/questions";
import CheckedIcon from "@ui_kit/RadioCheck";
import CheckIcon from "@ui_kit/RadioIcon";
import InfoIcon from "../../../assets/icons/InfoIcon";
type SettingTextFieldProps = {
@ -37,7 +40,13 @@ export default function SettingTextField({
const theme = useTheme();
return (
<Box sx={{ display: "flex" }}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
marginRight: "50px",
}}
>
<Box sx={{ padding: "20px" }}>
<Typography>Настройки ответов</Typography>
<FormControl>
@ -62,20 +71,68 @@ export default function SettingTextField({
{ANSWER_TYPES.map(({ name }, index) => (
<FormControlLabel
key={index}
sx={{ color: theme.palette.grey2.main }}
sx={{
color: theme.palette.grey2.main,
"& .MuiRadio-root": { padding: "8px 9px" },
}}
value={index}
control={<Radio />}
control={
<Radio icon={<CheckIcon />} checkedIcon={<CheckedIcon />} />
}
label={name}
/>
))}
</RadioGroup>
</FormControl>
</Box>
<Box sx={{ padding: "20px" }}>
<Box sx={{ padding: "20px", display: "flex", flexDirection: "column" }}>
<Typography>Настройки вопросов</Typography>
<CustomCheckbox label={"Автозаполнение адреса"} />
<CustomCheckbox label={"Необязательный вопрос"} />
<CustomCheckbox label={"Внутреннее название вопроса"} /> <InfoIcon />
<CustomCheckbox
label={"Автозаполнение адреса"}
checked={listQuestions[quizId][totalIndex].content.autofill}
handleChange={({ target }) => {
const clonContent = listQuestions[quizId][totalIndex].content;
clonContent.autofill = target.checked;
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
<CustomCheckbox
label={"Необязательный вопрос"}
checked={!listQuestions[quizId][totalIndex].required}
handleChange={(e) => {
updateQuestionsList(quizId, totalIndex, {
required: !e.target.checked,
});
}}
/>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
handleChange={(e) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerNameCheck = e.target.checked;
if (!e.target.checked) {
clonContent.innerName = "";
}
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>{" "}
<InfoIcon />
</Box>
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
<CustomTextField
placeholder={"Развёрнутое описание вопроса"}
text={listQuestions[quizId][totalIndex].content.innerName}
onChange={({ target }) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerName = target.value;
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
)}
</Box>
</Box>
);

@ -1,15 +1,54 @@
import { useParams } from "react-router-dom";
import { Box, Typography } from "@mui/material";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { questionStore, updateQuestionsList } from "@root/questions";
import InfoIcon from "../../../assets/icons/InfoIcon";
export default function SettingPageOptions() {
type SettingPageOptionsProps = {
totalIndex: number;
};
export default function SettingPageOptions({
totalIndex,
}: SettingPageOptionsProps) {
const quizId = Number(useParams().quizId);
const { listQuestions } = questionStore();
return (
<Box sx={{ display: "flex", flexDirection: "column", padding: "20px" }}>
<Typography>Настройки вопроса</Typography>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox label={"Внутреннее название вопроса"} />
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
handleChange={({ target }) =>
updateQuestionsList(quizId, totalIndex, {
content: {
...listQuestions[quizId][totalIndex].content,
innerNameCheck: target.checked,
innerName: "",
},
})
}
/>
<InfoIcon />
</Box>
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
<CustomTextField
placeholder={"Внутреннее описание вопроса"}
text={listQuestions[quizId][totalIndex].content.innerName}
onChange={({ target }) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerName = target.value;
updateQuestionsList(quizId, totalIndex, {
content: clonContent,
});
}}
/>
)}
</Box>
);
}

@ -13,13 +13,13 @@ export default function SwitchPageOptions({
}: Props) {
switch (switchState) {
case "setting":
return <SettingPageOptions />;
return <SettingPageOptions totalIndex={totalIndex} />;
break;
case "help":
return <HelpQuestions totalIndex={totalIndex} />;
break;
case "branching":
return <BranchingQuestions totalIndex={totalIndex}/>;
return <BranchingQuestions totalIndex={totalIndex} />;
break;
default:
return <></>;

@ -2,10 +2,10 @@ import { useState } from "react";
import { useParams } from "react-router-dom";
import { Box, Typography, useTheme } from "@mui/material";
import ButtonsOptions from "../ButtonsOptions";
import Rating from "@mui/material/Rating";
import RatingStar from "../../../assets/icons/questionsPage/ratingStar";
import SwitchRating from "./switchRating";
import { questionStore, updateQuestionsList } from "@root/questions";
import CustomTextField from "@ui_kit/CustomTextField";
interface Props {
totalIndex: number;
@ -33,22 +33,45 @@ export default function RatingOptions({ totalIndex }: Props) {
gap: "20px",
}}
>
<Rating
name="customized-color"
defaultValue={listQuestions[quizId][totalIndex].content.starts}
getLabelText={(value: number) =>
`${value} Heart${value !== 1 ? "s" : ""}`
}
precision={1}
icon={<RatingStar color={theme.palette.brightPurple.main} />}
emptyIcon={<RatingStar color={"#9A9AAF"} />}
sx={{ display: "flex", gap: "15px" }}
onChange={(_, value) => {
const clonContent = listQuestions[quizId][totalIndex].content;
clonContent.starts = value || 0;
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
<Box sx={{ display: "flex", gap: "15px" }}>
<RatingStar
onClick={() => {
updateQuestionsList(quizId, totalIndex, {
content: {
...listQuestions[quizId][totalIndex].content,
ratingExpanded: true,
},
});
}}
sx={{
cursor: "pointer",
":hover": {
transform: "scale(1.1)",
transition: "0.2s",
},
}}
/>
<RatingStar />
<RatingStar />
<RatingStar />
<RatingStar
onClick={() => {
updateQuestionsList(quizId, totalIndex, {
content: {
...listQuestions[quizId][totalIndex].content,
ratingExpanded: true,
},
});
}}
sx={{
cursor: "pointer",
":hover": {
transform: "scale(1.1)",
transition: "0.2s",
},
}}
/>
</Box>
<Box
sx={{
display: "flex",
@ -75,6 +98,37 @@ export default function RatingOptions({ totalIndex }: Props) {
Позитивно
</Typography>
</Box>
{listQuestions[quizId][totalIndex].content.ratingExpanded &&
(listQuestions[quizId][totalIndex].content.ratingDescription ? (
<Typography>
{listQuestions[quizId][totalIndex].content.ratingDescription}
</Typography>
) : (
<CustomTextField
placeholder={"Описание"}
text={listQuestions[quizId][totalIndex].content.ratingDescription}
onKeyDown={({ target, key }) => {
if (key === "Enter") {
const currentTarget = target as HTMLInputElement;
updateQuestionsList(quizId, totalIndex, {
content: {
...listQuestions[quizId][totalIndex].content,
ratingDescription: currentTarget.value.substring(0, 20),
},
});
}
}}
onBlur={({ target }) => {
updateQuestionsList(quizId, totalIndex, {
content: {
...listQuestions[quizId][totalIndex].content,
ratingDescription: target.value.substring(0, 20),
},
});
}}
/>
))}
</Box>
<ButtonsOptions
switchState={switchState}

@ -2,6 +2,7 @@ import { useParams } from "react-router-dom";
import { Box, ButtonBase, Slider, Typography, useTheme } from "@mui/material";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { questionStore, updateQuestionsList } from "@root/questions";
import InfoIcon from "../../../assets/icons/InfoIcon";
@ -109,8 +110,43 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
</Box>
<Box sx={{ padding: "20px" }}>
<Typography>Настройки вопросов</Typography>
<CustomCheckbox label={"Необязательный вопрос"} />
<CustomCheckbox label={"Внутреннее название вопроса"} /> <InfoIcon />
<CustomCheckbox
label={"Необязательный вопрос"}
checked={!listQuestions[quizId][totalIndex].required}
handleChange={(e) => {
updateQuestionsList(quizId, totalIndex, {
required: !e.target.checked,
});
}}
/>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
handleChange={(e) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerNameCheck = e.target.checked;
if (!e.target.checked) {
clonContent.innerName = "";
}
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>{" "}
<InfoIcon />
</Box>
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
<CustomTextField
placeholder={"Развёрнутое описание вопроса"}
text={listQuestions[quizId][totalIndex].content.innerName}
onChange={({ target }) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerName = target.value;
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
)}
</Box>
</Box>
);

@ -1,8 +1,8 @@
import { useState } from "react";
import { useParams } from "react-router-dom";
import { Box, Typography } from "@mui/material";
import ButtonsOptions from "../ButtonsOptions";
import React from "react";
import CustomTextField from "@ui_kit/CustomTextField";
import CustomNumberField from "@ui_kit/CustomNumberField";
import SwitchSlider from "./switchSlider";
import { questionStore, updateQuestionsList } from "@root/questions";
@ -11,7 +11,8 @@ interface Props {
}
export default function SliderOptions({ totalIndex }: Props) {
const [switchState, setSwitchState] = React.useState("setting");
const [switchState, setSwitchState] = useState("setting");
const [stepError, setStepError] = useState("");
const quizId = Number(useParams().quizId);
const { listQuestions } = questionStore();
@ -34,9 +35,11 @@ export default function SliderOptions({ totalIndex }: Props) {
<Box sx={{ gap: "10px", display: "flex", flexDirection: "column" }}>
<Typography>Выбор значения из диапазона</Typography>
<Box sx={{ display: "flex", alignItems: "center", gap: "20px" }}>
<CustomTextField
<CustomNumberField
placeholder={"0"}
text={
min={0}
max={99}
value={
listQuestions[quizId][totalIndex].content.range.split("—")[0]
}
onChange={({ target }) => {
@ -48,11 +51,31 @@ export default function SliderOptions({ totalIndex }: Props) {
content: clonContent,
});
}}
onBlur={({ target }) => {
const min = Number(target.value);
const max = Number(
listQuestions[quizId][totalIndex].content.range.split("—")[1]
);
if (min >= max) {
const clonContent = listQuestions[quizId][totalIndex].content;
clonContent.range = `${max - 1 >= 0 ? max - 1 : 0}${
listQuestions[quizId][totalIndex].content.range.split(
"—"
)[1]
}`;
updateQuestionsList(quizId, totalIndex, {
content: clonContent,
});
}
}}
/>
<Typography></Typography>
<CustomTextField
<CustomNumberField
placeholder={"100"}
text={
min={0}
max={100}
value={
listQuestions[quizId][totalIndex].content.range.split("—")[1]
}
onChange={({ target }) => {
@ -64,6 +87,24 @@ export default function SliderOptions({ totalIndex }: Props) {
content: clonContent,
});
}}
onBlur={({ target }) => {
const min = Number(
listQuestions[quizId][totalIndex].content.range.split("—")[0]
);
const max = Number(target.value);
if (max <= min) {
const clonContent = listQuestions[quizId][totalIndex].content;
clonContent.range = `${
listQuestions[quizId][totalIndex].content.range.split(
"—"
)[0]
}${min + 1 >= 100 ? 100 : min + 1}`;
updateQuestionsList(quizId, totalIndex, {
content: clonContent,
});
}
}}
/>
</Box>
</Box>
@ -72,13 +113,15 @@ export default function SliderOptions({ totalIndex }: Props) {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "50px",
}}
>
<Box>
<Box sx={{ width: "100%" }}>
<Typography>Начальное значение</Typography>
<CustomTextField
<CustomNumberField
placeholder={"50"}
text={String(listQuestions[quizId][totalIndex].content.start)}
min={0}
value={String(listQuestions[quizId][totalIndex].content.start)}
onChange={({ target }) => {
const clonContent = listQuestions[quizId][totalIndex].content;
clonContent.start = Number(target.value);
@ -86,13 +129,31 @@ export default function SliderOptions({ totalIndex }: Props) {
content: clonContent,
});
}}
onBlur={({ target }) => {
const start = Number(target.value);
const min = Number(
listQuestions[quizId][totalIndex].content.range.split("—")[0]
);
if (start < min) {
updateQuestionsList(quizId, totalIndex, {
content: {
...listQuestions[quizId][totalIndex].content,
start: min,
},
});
}
}}
/>
</Box>
<Box>
<Box sx={{ width: "100%" }}>
<Typography>Шаг</Typography>
<CustomTextField
<CustomNumberField
min={0}
max={100}
placeholder={"1"}
text={String(listQuestions[quizId][totalIndex].content.step)}
error={stepError}
value={String(listQuestions[quizId][totalIndex].content.step)}
onChange={({ target }) => {
const clonContent = listQuestions[quizId][totalIndex].content;
clonContent.step = Number(target.value);
@ -100,6 +161,35 @@ export default function SliderOptions({ totalIndex }: Props) {
content: clonContent,
});
}}
onBlur={({ target }) => {
const min = Number(
listQuestions[quizId][totalIndex].content.range.split("—")[0]
);
const max = Number(
listQuestions[quizId][totalIndex].content.range.split("—")[1]
);
const range = max - min;
const step = Number(target.value);
if (step > range) {
updateQuestionsList(quizId, totalIndex, {
content: {
...listQuestions[quizId][totalIndex].content,
step: range,
},
});
}
if (range % step) {
setStepError(
`Шаг должен делить без остатка диапазон ${max} - ${min} = ${
max - min
}`
);
} else {
setStepError("");
}
}}
/>
</Box>
</Box>

@ -1,6 +1,7 @@
import { useParams } from "react-router-dom";
import { Box, Typography } from "@mui/material";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import InfoIcon from "../../../assets/icons/InfoIcon";
import { questionStore, updateQuestionsList } from "@root/questions";
@ -28,8 +29,43 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
</Box>
<Box sx={{ padding: "20px" }}>
<Typography>Настройки вопросов</Typography>
<CustomCheckbox label={"Необязательный вопрос"} />
<CustomCheckbox label={"Внутреннее название вопроса"} /> <InfoIcon />
<CustomCheckbox
label={"Необязательный вопрос"}
checked={!listQuestions[quizId][totalIndex].required}
handleChange={(e) => {
updateQuestionsList(quizId, totalIndex, {
required: !e.target.checked,
});
}}
/>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
handleChange={(e) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerNameCheck = e.target.checked;
if (!e.target.checked) {
clonContent.innerName = "";
}
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>{" "}
<InfoIcon />
</Box>
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
<CustomTextField
placeholder={"Развёрнутое описание вопроса"}
text={listQuestions[quizId][totalIndex].content.innerName}
onChange={({ target }) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerName = target.value;
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
)}
</Box>
</Box>
);

@ -1,6 +1,7 @@
import { useParams } from "react-router-dom";
import { Box, Typography } from "@mui/material";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { questionStore, updateQuestionsList } from "@root/questions";
@ -26,8 +27,43 @@ export default function SettingsUpload({ totalIndex }: SettingsUploadProps) {
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
<CustomCheckbox label={"Необязательный вопрос"} />
<CustomCheckbox label={"Внутреннее название вопроса"} /> <InfoIcon />
<CustomCheckbox
label={"Необязательный вопрос"}
checked={!listQuestions[quizId][totalIndex].required}
handleChange={(e) => {
updateQuestionsList(quizId, totalIndex, {
required: !e.target.checked,
});
}}
/>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
handleChange={(e) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerNameCheck = e.target.checked;
if (!e.target.checked) {
clonContent.innerName = "";
}
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>{" "}
<InfoIcon />
</Box>
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
<CustomTextField
placeholder={"Развёрнутое описание вопроса"}
text={listQuestions[quizId][totalIndex].content.innerName}
onChange={({ target }) => {
let clonContent = listQuestions[quizId][totalIndex].content;
clonContent.innerName = target.value;
updateQuestionsList(quizId, totalIndex, { content: clonContent });
}}
/>
)}
</Box>
);
}

@ -14,7 +14,13 @@ export default function ResponseSettings({ totalIndex }: Props) {
const { listQuestions } = questionStore();
return (
<Box sx={{ display: "flex" }}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
marginRight: "30px",
}}
>
<Box sx={{ padding: "20px", display: "flex", flexDirection: "column" }}>
<Typography>Настройки ответов</Typography>
<CustomCheckbox
@ -61,7 +67,7 @@ export default function ResponseSettings({ totalIndex }: Props) {
});
}}
/>
<Box sx={{ display: "flex" }}>
<Box sx={{ display: "flex", alignItems: "center" }}>
<CustomCheckbox
label={"Внутреннее название вопроса"}
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}

@ -55,7 +55,6 @@ export interface Question {
video: string;
dateRange: boolean;
time: boolean;
starts: number;
form: string;
steps: number;
range: string;
@ -64,6 +63,8 @@ export interface Question {
chooseRange: boolean;
required: boolean;
replText: string;
ratingExpanded: boolean;
ratingDescription: string;
};
version: number;
parent_ids: number[];
@ -161,7 +162,6 @@ export const createQuestion = (quizId: number) => {
video: "",
dateRange: false,
time: false,
starts: 0,
form: "star",
steps: 5,
range: "0—100",
@ -170,6 +170,8 @@ export const createQuestion = (quizId: number) => {
chooseRange: false,
required: false,
replText: "",
ratingExpanded: false,
ratingDescription: "",
variants: [
{
answer: "",
@ -204,9 +206,11 @@ export const createQuestion = (quizId: number) => {
export const copyQuestion = (quizId: number, copiedQuestionIndex: number) => {
const listQuestions = { ...questionStore.getState()["listQuestions"] };
listQuestions[quizId].push({
...listQuestions[quizId][copiedQuestionIndex],
});
listQuestions[quizId].splice(
copiedQuestionIndex,
0,
listQuestions[quizId][copiedQuestionIndex]
);
questionStore.setState({ listQuestions });
};

@ -0,0 +1,54 @@
import CustomTextField from "./CustomTextField";
import type { ChangeEvent, KeyboardEvent, FocusEvent } from "react";
import type { SxProps, Theme } from "@mui/material";
interface CustomNumberFieldProps {
placeholder: string;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
error?: string;
value: string;
sx?: SxProps<Theme>;
min?: number;
max?: number;
}
export default function CustomNumberField({
placeholder,
value,
sx,
error,
onChange,
onKeyDown,
onBlur,
min = -999999999,
max = 999999999,
}: CustomNumberFieldProps) {
const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
const inputValue = event.target.value;
if (
Number(inputValue) >= min &&
Number(inputValue) <= max &&
(inputValue === "" ||
inputValue.match(/^\d*$/) ||
(inputValue[0] === "-" && inputValue.slice(1).match(/^\d*$/)))
) {
onChange?.({ ...event, target: { ...event.target, value: inputValue } });
}
};
return (
<CustomTextField
placeholder={placeholder}
sx={sx}
error={error}
onChange={onInputChange}
onKeyDown={onKeyDown}
onBlur={onBlur}
value={value}
/>
);
}

@ -1,46 +1,69 @@
import {FormControl, TextField, useTheme, SxProps, Theme} from "@mui/material";
import {ChangeEvent} from "react";
import {
FormControl,
TextField,
useTheme,
SxProps,
Theme,
} from "@mui/material";
import type { ChangeEvent, KeyboardEvent, FocusEvent } from "react";
interface CustomTextFieldProps {
placeholder: string;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
text?: string;
sx?: SxProps<Theme>;
placeholder: string;
value?: string;
error?: string;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
text?: string;
sx?: SxProps<Theme>;
}
export default function CustomTextField({ placeholder, text, sx, onChange}: CustomTextFieldProps) {
const theme = useTheme();
export default function CustomTextField({
placeholder,
value,
text,
sx,
error,
onChange,
onKeyDown,
onBlur,
}: CustomTextFieldProps) {
const theme = useTheme();
return (
<FormControl
fullWidth
variant="standard"
sx={{ p: 0 }}
>
<TextField
defaultValue={text}
fullWidth
placeholder={placeholder}
onChange={onChange}
sx={{
"& .MuiInputBase-root": {
backgroundColor: theme.palette.background.default,
height: "48px",
borderRadius: "10px",
},
...sx
}}
inputProps={{
sx: {
borderRadius: "10px",
fontSize: "18px",
lineHeight: "21px",
py: 0,
}
}}
/>
</FormControl>
);
return (
<FormControl fullWidth variant="standard" sx={{ p: 0 }}>
<TextField
defaultValue={text}
fullWidth
value={value}
placeholder={placeholder}
error={!!error}
label={error}
onChange={onChange}
onKeyDown={onKeyDown}
onBlur={onBlur}
sx={{
"& .MuiInputBase-root": {
backgroundColor: theme.palette.background.default,
height: "48px",
borderRadius: "10px",
},
"& .MuiInputLabel-root": {
fontSize: "13.5px",
marginTop: "3px",
},
...sx,
}}
inputProps={{
sx: {
borderRadius: "10px",
fontSize: "18px",
lineHeight: "21px",
py: 0,
},
}}
/>
</FormControl>
);
}