WIP use new store & resolve type conflicts
This commit is contained in:
parent
2103fe8977
commit
f463270a9b
@ -1,4 +1,4 @@
|
||||
import { DefiniteQuestionType } from "@model/questionTypes/shared";
|
||||
import { QuestionType } from "./question";
|
||||
|
||||
|
||||
export interface CreateQuestionRequest {
|
||||
@ -9,7 +9,7 @@ export interface CreateQuestionRequest {
|
||||
/** description of question. html/text */
|
||||
description?: string;
|
||||
/** type of question. allow only text, select, file, variant, images, varimg, emoji, date, number, page, rating */
|
||||
type?: DefiniteQuestionType;
|
||||
type?: QuestionType;
|
||||
/** set true if user MUST answer this question */
|
||||
required?: boolean;
|
||||
/** page of question */
|
||||
|
@ -86,6 +86,13 @@ export type AnyQuizQuestion =
|
||||
| QuizQuestionRating;
|
||||
// | QuizQuestionInitial;
|
||||
|
||||
type FilterQuestionsWithVariants<T> = T extends {
|
||||
content: { variants: QuestionVariant[] | ImageQuestionVariant[]; };
|
||||
} ? T : never;
|
||||
|
||||
export type QuizQuestionsWithVariants = FilterQuestionsWithVariants<AnyQuizQuestion>;
|
||||
|
||||
|
||||
export const createQuestionVariant: () => QuestionVariant = () => ({
|
||||
id: nanoid(),
|
||||
answer: "",
|
||||
|
@ -1,21 +1,19 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import { AnswerItem } from "./AnswerItem";
|
||||
import { reorderQuestionVariants } from "@root/questions/actions";
|
||||
import { type ReactNode } from "react";
|
||||
import type { DropResult } from "react-beautiful-dnd";
|
||||
import type { AnyQuizQuestion, ImageQuestionVariant, QuestionVariant } from "../../../model/questionTypes/shared";
|
||||
import { reorderQuestionVariants } from "@root/questions/actions";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import type { ImageQuestionVariant, QuestionVariant, QuizQuestionsWithVariants } from "../../../model/questionTypes/shared";
|
||||
import { AnswerItem } from "./AnswerItem";
|
||||
|
||||
|
||||
type AnswerDraggableListProps = {
|
||||
variants: QuestionVariant[];
|
||||
question: AnyQuizQuestion;
|
||||
question: QuizQuestionsWithVariants;
|
||||
additionalContent?: (variant: QuestionVariant | ImageQuestionVariant, index: number) => ReactNode;
|
||||
additionalMobile?: (variant: QuestionVariant | ImageQuestionVariant, index: number) => ReactNode;
|
||||
};
|
||||
|
||||
export const AnswerDraggableList = ({
|
||||
variants,
|
||||
question,
|
||||
additionalContent,
|
||||
additionalMobile,
|
||||
@ -31,7 +29,7 @@ export const AnswerDraggableList = ({
|
||||
<Droppable droppableId="droppable-answer-list">
|
||||
{(provided) => (
|
||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{variants.map((variant, index) => (
|
||||
{question.content.variants.map((variant, index) => (
|
||||
<AnswerItem
|
||||
key={variant.id}
|
||||
index={index}
|
||||
|
@ -1,311 +1,288 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import { useParams } from "react-router-dom";
|
||||
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||
import Clue from "../../assets/icons/questionsPage/clue";
|
||||
import Branching from "../../assets/icons/questionsPage/branching";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Tooltip,
|
||||
IconButton,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
|
||||
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
|
||||
|
||||
import {
|
||||
questionStore,
|
||||
copyQuestion,
|
||||
removeQuestionForce,
|
||||
updateQuestionsList,
|
||||
removeQuestion,
|
||||
} from "@root/questions";
|
||||
import { quizStore } from "@root/quizes";
|
||||
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
|
||||
import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight";
|
||||
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
|
||||
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";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { copyQuestion, deleteQuestion, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "../../assets/icons/questionsPage/branching";
|
||||
import Clue from "../../assets/icons/questionsPage/clue";
|
||||
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
|
||||
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||
import type { AnyQuizQuestion } from "../../model/questionTypes/shared";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
SSHC: (data: string) => void;
|
||||
totalIndex: number;
|
||||
sx?: SxProps;
|
||||
switchState: string;
|
||||
SSHC: (data: string) => void;
|
||||
question: AnyQuizQuestion;
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
export default function ButtonsOptions({
|
||||
SSHC,
|
||||
switchState,
|
||||
totalIndex,
|
||||
SSHC,
|
||||
switchState,
|
||||
question,
|
||||
}: Props) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const { listQuizes } = quizStore();
|
||||
const [openedReallyChangingModal, setOpenedReallyChangingModal] =
|
||||
useState<boolean>(false);
|
||||
const quize = listQuizes[quizId];
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionBase;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||
|
||||
useEffect(() => {
|
||||
if (question.deleteTimeoutId) {
|
||||
clearTimeout(question.deleteTimeoutId);
|
||||
}
|
||||
}, [listQuestions]);
|
||||
const openedModal = () => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.openedModalSettings = true;
|
||||
});
|
||||
};
|
||||
|
||||
const openedModal = () => {
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
||||
openedModalSettings: true,
|
||||
});
|
||||
};
|
||||
const buttonSetting: {
|
||||
icon: JSX.Element;
|
||||
title: string;
|
||||
value: string;
|
||||
myFunc?: any;
|
||||
}[] = [
|
||||
{
|
||||
icon: (
|
||||
<SettingIcon
|
||||
color={
|
||||
switchState === "setting" ? "#ffffff" : theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
),
|
||||
title: "Настройки",
|
||||
value: "setting",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Clue
|
||||
color={switchState === "help" ? "#ffffff" : theme.palette.grey3.main}
|
||||
/>
|
||||
),
|
||||
title: "Подсказка",
|
||||
value: "help",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Branching
|
||||
color={
|
||||
switchState === "branching" ? "#ffffff" : theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
),
|
||||
title: "Ветвление",
|
||||
value: "branching",
|
||||
myFunc: openedModal,
|
||||
},
|
||||
];
|
||||
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||
|
||||
const buttonSetting: {
|
||||
icon: JSX.Element;
|
||||
title: string;
|
||||
value: string;
|
||||
myFunc?: any;
|
||||
}[] = [
|
||||
{
|
||||
icon: (
|
||||
<SettingIcon
|
||||
color={
|
||||
switchState === "setting" ? "#ffffff" : theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
),
|
||||
title: "Настройки",
|
||||
value: "setting",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Clue
|
||||
color={switchState === "help" ? "#ffffff" : theme.palette.grey3.main}
|
||||
/>
|
||||
),
|
||||
title: "Подсказка",
|
||||
value: "help",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Branching
|
||||
color={
|
||||
switchState === "branching" ? "#ffffff" : theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
),
|
||||
title: "Ветвление",
|
||||
value: "branching",
|
||||
myFunc: openedModal,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
background: "#f2f3f7",
|
||||
height: isMobile ? "92px" : "70px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
padding: isMobile ? " 3px 12px 11px" : "20px",
|
||||
display: "flex",
|
||||
flexWrap: isMobile ? "wrap" : "nowrap",
|
||||
gap: "6px",
|
||||
}}
|
||||
>
|
||||
{buttonSetting.map(({ icon, title, value, myFunc }) => (
|
||||
<Box key={value}>
|
||||
{value === "branching" ? (
|
||||
<Tooltip
|
||||
arrow
|
||||
placement="right"
|
||||
componentsProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
background: "#fff",
|
||||
borderRadius: "6px",
|
||||
color: "#9A9AAF",
|
||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||
"& .MuiTooltip-arrow": {
|
||||
color: "#FFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
background: "#f2f3f7",
|
||||
height: isMobile ? "92px" : "70px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
padding: isMobile ? " 3px 12px 11px" : "20px",
|
||||
display: "flex",
|
||||
flexWrap: isMobile ? "wrap" : "nowrap",
|
||||
gap: "6px",
|
||||
}}
|
||||
title={
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
fontWeight: "bold",
|
||||
fontSize: "14px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
{buttonSetting.map(({ icon, title, value, myFunc }) => (
|
||||
<Box key={value}>
|
||||
{value === "branching" ? (
|
||||
<Tooltip
|
||||
arrow
|
||||
placement="right"
|
||||
componentsProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
background: "#fff",
|
||||
borderRadius: "6px",
|
||||
color: "#9A9AAF",
|
||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||
"& .MuiTooltip-arrow": {
|
||||
color: "#FFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
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 },
|
||||
},
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
<>
|
||||
<MiniButtonSetting
|
||||
onClick={undefined} // TODO
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
Будет показан при условии
|
||||
</Typography>
|
||||
<Typography sx={{ fontWeight: "bold", fontSize: "12px" }}>
|
||||
Название
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "12px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={undefined} // TODO
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
Условие 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 },
|
||||
},
|
||||
}}
|
||||
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={undefined} // TODO
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<VectorQuestions style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
</>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "20px",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
||||
<HideIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => copyQuestion(question.id, question.quizId)}
|
||||
>
|
||||
{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 },
|
||||
},
|
||||
}}
|
||||
<CopyIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => { // TODO
|
||||
// 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,
|
||||
// });
|
||||
|
||||
deleteQuestion(question.id);
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
</>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "20px",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
||||
<HideIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => copyQuestion(quizId, totalIndex)}
|
||||
>
|
||||
<CopyIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
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 color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
<DeleteIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight";
|
||||
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
|
||||
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
|
||||
import { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
@ -20,13 +21,13 @@ import { DeleteIcon } from "../../assets/icons/questionsPage/deleteIcon";
|
||||
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
|
||||
import ImgIcon from "../../assets/icons/questionsPage/imgIcon";
|
||||
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||
import type { AnyQuizQuestion } from "../../model/questionTypes/shared";
|
||||
import { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
SSHC: (data: string) => void;
|
||||
question: AnyQuizQuestion;
|
||||
question: QuizQuestionVariant | QuizQuestionVarImg;
|
||||
}
|
||||
|
||||
export default function ButtonsOptionsAndPict({
|
||||
|
@ -3,13 +3,14 @@ import { useState } from "react";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchData from "./switchData";
|
||||
import { QuizQuestionDate } from "@model/questionTypes/date";
|
||||
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionDate;
|
||||
}
|
||||
|
||||
export default function DataOptions({ totalIndex }: Props) {
|
||||
export default function DataOptions({ question }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
@ -49,8 +50,8 @@ export default function DataOptions({ totalIndex }: Props) {
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchData switchState={switchState} totalIndex={totalIndex} />
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||
<SwitchData switchState={switchState} question={question} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,139 +1,134 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import type { QuizQuestionDate } from "../../../model/questionTypes/date";
|
||||
|
||||
|
||||
type SettingsDataProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionDate;
|
||||
};
|
||||
|
||||
export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionDate;
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
export default function SettingsData({ question }: SettingsDataProps) {
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
const setInnerName = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки календаря
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор диапазона дат"}
|
||||
checked={question.content.dateRange}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
content: { ...question.content, dateRange: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор времени"}
|
||||
checked={question.content.time}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
content: { ...question.content, time: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
required: !target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "93%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки календаря
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор диапазона дат"}
|
||||
checked={question.content.dateRange}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "date") return;
|
||||
|
||||
question.content.dateRange = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор времени"}
|
||||
checked={question.content.time}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "date") return;
|
||||
|
||||
question.content.time = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.required = !target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "93%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => setInnerName(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
@ -1,27 +1,25 @@
|
||||
import * as React from "react";
|
||||
import { QuizQuestionDate } from "@model/questionTypes/date";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingData from "./settingData";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
question: QuizQuestionDate;
|
||||
}
|
||||
|
||||
export default function SwitchData({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingData totalIndex={totalIndex} />;
|
||||
break;
|
||||
return <SettingData question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
|
@ -1,31 +1,22 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Popper,
|
||||
Grow,
|
||||
Paper,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
ClickAwayListener,
|
||||
Modal,
|
||||
Button,
|
||||
ClickAwayListener,
|
||||
Grow,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Modal,
|
||||
Paper,
|
||||
Popper,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
updateQuestionsList,
|
||||
removeQuestionForce,
|
||||
createQuestion,
|
||||
} from "@root/questions";
|
||||
import { useState } from "react";
|
||||
import { BUTTON_TYPE_QUESTIONS } from "../TypeQuestions";
|
||||
|
||||
import type { RefObject } from "react";
|
||||
import type {
|
||||
QuizQuestionType,
|
||||
QuizQuestionBase,
|
||||
AnyQuizQuestion,
|
||||
} from "../../../model/questionTypes/shared";
|
||||
import type { AnyQuizQuestion } from "../../../model/questionTypes/shared";
|
||||
import { QuestionType } from "@model/question/question";
|
||||
|
||||
|
||||
type ChooseAnswerModalProps = {
|
||||
open: boolean;
|
||||
@ -43,7 +34,7 @@ export const ChooseAnswerModal = ({
|
||||
switchState,
|
||||
}: ChooseAnswerModalProps) => {
|
||||
const [openModal, setOpenModal] = useState<boolean>(false);
|
||||
const [selectedValue, setSelectedValue] = useState<QuizQuestionType>("text");
|
||||
const [selectedValue, setSelectedValue] = useState<QuestionType>("text");
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
|
@ -31,7 +31,7 @@ import Page from "@icons/questionsPage/page";
|
||||
import RatingIcon from "@icons/questionsPage/rating";
|
||||
import Slider from "@icons/questionsPage/slider";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
import { copyQuestion, deleteQuestion, toggleExpandQuestion } from "@root/questions/actions";
|
||||
import { copyQuestion, createQuestion, deleteQuestion, toggleExpandQuestion } from "@root/questions/actions";
|
||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||
import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg";
|
||||
import type { AnyQuizQuestion } from "../../../model/questionTypes/shared";
|
||||
@ -307,7 +307,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
onClick={() => createQuestion(quizId, "nonselected", totalIndex + 1)}
|
||||
onClick={() => createQuestion(question.quizId)}
|
||||
sx={{
|
||||
display: plusVisible && !isDragging ? "flex" : "none",
|
||||
width: "100%",
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import DraggableListItem from "./DraggableListItem";
|
||||
import type { DropResult } from "react-beautiful-dnd";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useQuestionArray } from "@root/questions/hooks";
|
||||
import useSWR from "swr";
|
||||
import { questionApi } from "@api/question";
|
||||
import { setQuestions } from "@root/questions/actions";
|
||||
import { isAxiosError } from "axios";
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { Box } from "@mui/material";
|
||||
import { reorderQuestions, setQuestions } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { isAxiosError } from "axios";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import type { DropResult } from "react-beautiful-dnd";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import useSWR from "swr";
|
||||
import DraggableListItem from "./DraggableListItem";
|
||||
|
||||
|
||||
export const DraggableList = () => {
|
||||
@ -23,18 +23,10 @@ export const DraggableList = () => {
|
||||
enqueueSnackbar(`Не удалось получить вопросы. ${message}`);
|
||||
}
|
||||
});
|
||||
const questions = useQuestionArray();
|
||||
const questions = useQuestionsStore(state => state.questions);
|
||||
|
||||
const onDragEnd = ({ destination, source }: DropResult) => { // TODO
|
||||
// if (destination) {
|
||||
// const newItems = reorder(
|
||||
// listQuestions[quizId],
|
||||
// source.index,
|
||||
// destination.index
|
||||
// );
|
||||
|
||||
// updateQuestionsListDragAndDrop(quizId, newItems);
|
||||
// }
|
||||
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||
if (destination) reorderQuestions(source.index, destination.index);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,40 +1,25 @@
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, Typography, Link, useTheme, useMediaQuery } from "@mui/material";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import SwitchDropDown from "./switchDropDown";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
|
||||
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
||||
import { addQuestionVariant } from "@root/questions/actions";
|
||||
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionSelect;
|
||||
}
|
||||
|
||||
export default function DropDown({ totalIndex }: Props) {
|
||||
export default function DropDown({ question }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionSelect;
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
const addNewAnswer = () => {
|
||||
const answerNew = question.content.variants.slice();
|
||||
answerNew.push({ answer: "", extendedText: "", hints: "" });
|
||||
|
||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: answerNew },
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -56,10 +41,7 @@ export default function DropDown({ totalIndex }: Props) {
|
||||
Добавьте ответ
|
||||
</Typography>
|
||||
) : (
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
question={totalIndex}
|
||||
/>
|
||||
<AnswerDraggableList question={question} />
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
@ -78,7 +60,7 @@ export default function DropDown({ totalIndex }: Props) {
|
||||
mr: "4px",
|
||||
height: "19px",
|
||||
}}
|
||||
onClick={addNewAnswer}
|
||||
onClick={() => addQuestionVariant(question.id)}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
@ -108,9 +90,9 @@ export default function DropDown({ totalIndex }: Props) {
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
question={question}
|
||||
/>
|
||||
<SwitchDropDown switchState={switchState} totalIndex={totalIndex} />
|
||||
<SwitchDropDown switchState={switchState} question={question} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,202 +1,194 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
||||
|
||||
|
||||
type SettingDropDownProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionSelect;
|
||||
};
|
||||
|
||||
export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
export default function SettingDropDown({ question }: SettingDropDownProps) {
|
||||
const theme = useTheme();
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionSelect;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
const debounceAnswer = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
content: { ...question.content, default: value },
|
||||
});
|
||||
}, 1000);
|
||||
const debounceAnswer = useDebouncedCallback((value) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "select") return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isMobile ? "column" : null,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
maxWidth: isFigmaTablte ? "297px" : "360px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
label={"Можно несколько"}
|
||||
checked={question.content.multi}
|
||||
dataCy="multiple-answers-checkbox"
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
content: { ...question.content, multi: target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: isMobile ? "none" : "block",
|
||||
mt: isMobile ? "11px" : "6px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "14px",
|
||||
}}
|
||||
question.content.default = value;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
width: "100%",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isMobile ? "column" : null,
|
||||
}}
|
||||
>
|
||||
Текст в выпадающем списке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
placeholder={"Выберите вариант"}
|
||||
text={question.content.default}
|
||||
onChange={({ target }) => debounceAnswer(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isMobile ? "20px" : "30px") : "0px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ position: "relative", display: "flex", alignItems: "flex-start" }}>
|
||||
<CustomCheckbox
|
||||
sx={{ height: isMobile ? "100%" : "26px", alignItems: isMobile ? "flex-start" : "center" }}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
pt: "20px",
|
||||
display: isMobile ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
Текст в выпадающем списке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
placeholder={"Выберите вариант"}
|
||||
text={question.content.default}
|
||||
onChange={({ target }) => debounceAnswer(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
maxWidth: isFigmaTablte ? "297px" : "360px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
label={"Можно несколько"}
|
||||
checked={question.content.multi}
|
||||
dataCy="multiple-answers-checkbox"
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "select") return;
|
||||
|
||||
question.content.multi = target.checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: isMobile ? "none" : "block",
|
||||
mt: isMobile ? "11px" : "6px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Текст в выпадающем списке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
placeholder={"Выберите вариант"}
|
||||
text={question.content.default}
|
||||
onChange={({ target }) => debounceAnswer(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isMobile ? "20px" : "30px") : "0px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ position: "relative", display: "flex", alignItems: "flex-start" }}>
|
||||
<CustomCheckbox
|
||||
sx={{ height: isMobile ? "100%" : "26px", alignItems: isMobile ? "flex-start" : "center" }}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
pt: "20px",
|
||||
display: isMobile ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
Текст в выпадающем списке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
placeholder={"Выберите вариант"}
|
||||
text={question.content.default}
|
||||
onChange={({ target }) => debounceAnswer(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,28 +1,26 @@
|
||||
import * as React from "react";
|
||||
import { QuizQuestionSelect } from "@model/questionTypes/select";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingDropDown from "./settingDropDown";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionSelect;
|
||||
}
|
||||
|
||||
export default function SwitchDropDown({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingDropDown totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingDropDown question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -1,257 +1,242 @@
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Link,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Popover,
|
||||
} from "@mui/material";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchEmoji from "./switchEmoji";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import { EmojiPicker } from "@ui_kit/EmojiPicker";
|
||||
import { EmojiIcons } from "@icons/EmojiIocns";
|
||||
import AddEmoji from "@icons/questionsPage/addEmoji";
|
||||
import PlusImage from "@icons/questionsPage/plus";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Link,
|
||||
Popover,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { addQuestionVariant, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import { EmojiPicker } from "@ui_kit/EmojiPicker";
|
||||
import { useState } from "react";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchEmoji from "./switchEmoji";
|
||||
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionEmoji;
|
||||
}
|
||||
|
||||
export default function Emoji({ totalIndex }: Props) {
|
||||
const [switchState, setSwitchState] = useState<string>("setting");
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(
|
||||
null
|
||||
);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||
const { listQuestions } = questionStore();
|
||||
const quizId = Number(useParams().quizId);
|
||||
const theme = useTheme();
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionEmoji;
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
export default function Emoji({ question }: Props) {
|
||||
const [switchState, setSwitchState] = useState<string>("setting");
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(
|
||||
null
|
||||
);
|
||||
const [selectedVariant, setSelectedVariant] = useState<string | null>(null);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
question={totalIndex}
|
||||
additionalContent={(variant, index) => (
|
||||
<>
|
||||
{!isTablet && (
|
||||
<Box sx={{ cursor: "pointer" }}>
|
||||
<Box
|
||||
data-cy="choose-emoji-button"
|
||||
onClick={({ currentTarget }) => {
|
||||
setAnchorElement(currentTarget);
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
question={question}
|
||||
additionalContent={(variant) => (
|
||||
<>
|
||||
{!isTablet && (
|
||||
<Box sx={{ cursor: "pointer" }}>
|
||||
<Box
|
||||
data-cy="choose-emoji-button"
|
||||
onClick={({ currentTarget }) => {
|
||||
setAnchorElement(currentTarget);
|
||||
setSelectedVariant(variant.id);
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
background: "#EEE4FC",
|
||||
borderRadius: "3px",
|
||||
marginRight: "15px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText}
|
||||
</Box>
|
||||
<Box>
|
||||
<PlusImage />
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<AddEmoji />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
additionalMobile={(variant) => (
|
||||
<>
|
||||
{isTablet && (
|
||||
<Box
|
||||
onClick={({ currentTarget }) => {
|
||||
setAnchorElement(currentTarget);
|
||||
setSelectedVariant(variant.id);
|
||||
setOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
}}
|
||||
/>
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText}
|
||||
</Box>
|
||||
) : (
|
||||
<EmojiIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={anchorElement}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onClose={() => setOpen(false)}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
}}
|
||||
sx={{
|
||||
".MuiPaper-root.MuiPaper-rounded": {
|
||||
borderRadius: "10px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
background: "#EEE4FC",
|
||||
borderRadius: "3px",
|
||||
marginRight: "15px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText}
|
||||
</Box>
|
||||
<Box>
|
||||
<PlusImage />
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<AddEmoji />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
additionalMobile={(variant, index) => (
|
||||
<>
|
||||
{isTablet && (
|
||||
<Box
|
||||
onClick={({ currentTarget }) => {
|
||||
setAnchorElement(currentTarget);
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
}}
|
||||
/>
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText}
|
||||
</Box>
|
||||
) : (
|
||||
<EmojiIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
<EmojiPicker
|
||||
onEmojiSelect={({ native }) => {
|
||||
setOpen(false);
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "emoji") return;
|
||||
|
||||
const variant = question.content.variants.find(v => v.id === selectedVariant);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = native;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
</Popover>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
marginBottom: isMobile ? "17px" : "20px",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.brightPurple.main }}
|
||||
onClick={() => addQuestionVariant(question.id)}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
{!isTablet && (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "21.33px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
или нажмите Enter
|
||||
</Typography>
|
||||
<EnterIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "24px",
|
||||
marginLeft: "6px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={anchorElement}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onClose={() => setOpen(false)}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
}}
|
||||
sx={{
|
||||
".MuiPaper-root.MuiPaper-rounded": {
|
||||
borderRadius: "10px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<EmojiPicker
|
||||
onEmojiSelect={({ native }) => {
|
||||
setOpen(false);
|
||||
const cloneVariants = [...question.content.variants];
|
||||
|
||||
cloneVariants[currentIndex] = {
|
||||
...cloneVariants[currentIndex],
|
||||
extendedText: native,
|
||||
};
|
||||
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: cloneVariants },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
marginBottom: isMobile ? "17px" : "20px",
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.brightPurple.main }}
|
||||
onClick={() => {
|
||||
const answerNew = question.content.variants.slice();
|
||||
answerNew.push({ answer: "", extendedText: "", hints: "" });
|
||||
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: answerNew },
|
||||
});
|
||||
}}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
{!isTablet && (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "21.33px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
или нажмите Enter
|
||||
</Typography>
|
||||
<EnterIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "24px",
|
||||
marginLeft: "6px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchEmoji switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
question={question}
|
||||
/>
|
||||
<SwitchEmoji switchState={switchState} question={question} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,142 +1,131 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
||||
|
||||
|
||||
type SettingEmojiProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionEmoji;
|
||||
};
|
||||
|
||||
export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
export default function SettingEmoji({ question }: SettingEmojiProps) {
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionEmoji;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
const setInnerName = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : "none",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: isFigmaTablte ? (isWrappColumn ? "20px" : "0px") : "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={question.content.multi}
|
||||
dataCy="multiple-answers-checkbox"
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, multi: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, own: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isTablet ? "20px" : "",
|
||||
pr: isFigmaTablte ? "30px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : "none",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: isFigmaTablte ? (isWrappColumn ? "20px" : "0px") : "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={question.content.multi}
|
||||
dataCy="multiple-answers-checkbox"
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "emoji") return;
|
||||
|
||||
question.content.multi = target.checked;
|
||||
})}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "emoji") return;
|
||||
|
||||
question.content.own = target.checked;
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isTablet ? "20px" : "",
|
||||
pr: isFigmaTablte ? "30px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "emoji") return;
|
||||
|
||||
question.content.required = !e.target.checked;
|
||||
})}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
})}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => setInnerName(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
@ -1,28 +1,26 @@
|
||||
import * as React from "react";
|
||||
import { QuizQuestionEmoji } from "@model/questionTypes/emoji";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingEmoji from "./settingEmoji";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionEmoji;
|
||||
}
|
||||
|
||||
export default function SwitchEmoji({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingEmoji totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingEmoji question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -76,9 +76,8 @@ export default memo(
|
||||
>
|
||||
<QuestionsPageCard
|
||||
key={index}
|
||||
totalIndex={index}
|
||||
question={question}
|
||||
draggableProps={provided.dragHandleProps}
|
||||
isDragging={isDragging}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
@ -1,170 +1,157 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, InputAdornment, Paper } from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { ChooseAnswerModal } from "./ChooseAnswerModal";
|
||||
import FormTypeQuestions from "../FormTypeQuestions";
|
||||
import SwitchQuestionsPage from "../../SwitchQuestionsPage";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
|
||||
import Answer from "@icons/questionsPage/answer";
|
||||
import OptionsPict from "@icons/questionsPage/options_pict";
|
||||
import OptionsAndPict from "@icons/questionsPage/options_and_pict";
|
||||
import AnswerGroup from "@icons/questionsPage/answerGroup";
|
||||
import Date from "@icons/questionsPage/date";
|
||||
import Download from "@icons/questionsPage/download";
|
||||
import DropDown from "@icons/questionsPage/drop_down";
|
||||
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 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";
|
||||
import AnswerGroup from "@icons/questionsPage/answerGroup";
|
||||
|
||||
import Slider from "@icons/questionsPage/slider";
|
||||
import { Box, InputAdornment, Paper } from "@mui/material";
|
||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useRef, useState } from "react";
|
||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||
import type { QuizQuestionBase } from "../../../../model/questionTypes/shared";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import type { AnyQuizQuestion } from "../../../../model/questionTypes/shared";
|
||||
import SwitchQuestionsPage from "../../SwitchQuestionsPage";
|
||||
import { ChooseAnswerModal } from "./ChooseAnswerModal";
|
||||
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
draggableProps: DraggableProvidedDragHandleProps | null | undefined;
|
||||
isDragging: boolean;
|
||||
question: AnyQuizQuestion;
|
||||
draggableProps: DraggableProvidedDragHandleProps | null | undefined;
|
||||
}
|
||||
|
||||
export default function QuestionsPageCard({
|
||||
question,
|
||||
draggableProps,
|
||||
}: Props) {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const anchorRef = useRef(null);
|
||||
|
||||
const setTitle = useDebouncedCallback((title) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.title = title;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Paper
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
maxWidth: "796px",
|
||||
width: "100%",
|
||||
backgroundColor: "white",
|
||||
border: "none",
|
||||
boxShadow: "none",
|
||||
paddingBottom: "20px",
|
||||
borderRadius: "0",
|
||||
borderTopLeftRadius: "12px",
|
||||
borderTopRightRadius: "12px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<CustomTextField
|
||||
placeholder={`Заголовок ${totalIndex + 1} вопроса`}
|
||||
text={question.title}
|
||||
onChange={({ target }) => setTitle(target.value)}
|
||||
sx={{ margin: "20px", width: "auto" }}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<Box>
|
||||
<InputAdornment
|
||||
ref={anchorRef}
|
||||
position="start"
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => setOpen((isOpened) => !isOpened)}
|
||||
>
|
||||
{IconAndrom(question.type)}
|
||||
</InputAdornment>
|
||||
<ChooseAnswerModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
anchorRef={anchorRef}
|
||||
question={question}
|
||||
switchState={question.type}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
endAdornment: (
|
||||
<Box {...draggableProps}>
|
||||
{totalIndex !== 0 && (
|
||||
<InputAdornment position="start">
|
||||
<PointsIcon
|
||||
style={{ color: "#9A9AAF", fontSize: "30px" }}
|
||||
/>
|
||||
</InputAdornment>
|
||||
)}
|
||||
</Box>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{/* {question.type === "" ? (
|
||||
<FormTypeQuestions totalIndex={totalIndex} />
|
||||
) : ( */}
|
||||
<SwitchQuestionsPage question={question} />
|
||||
{/* )} */}
|
||||
</Box>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const IconAndrom = (switchState: string) => {
|
||||
switch (switchState) {
|
||||
case "variant":
|
||||
return <Answer color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "images":
|
||||
return (
|
||||
<OptionsPict color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "varimg":
|
||||
return (
|
||||
<OptionsAndPict
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "emoji":
|
||||
return <Emoji color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "text":
|
||||
return <Input color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "select":
|
||||
return (
|
||||
<DropDown color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "date":
|
||||
return <Date color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "number":
|
||||
return <Slider color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "file":
|
||||
return (
|
||||
<Download color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "page":
|
||||
return <Page color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "rating":
|
||||
return (
|
||||
<RatingIcon color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<AnswerGroup color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
}
|
||||
};
|
||||
export default function QuestionsPageCard({
|
||||
totalIndex,
|
||||
draggableProps,
|
||||
isDragging,
|
||||
}: Props) {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const question = listQuestions[quizId][totalIndex];
|
||||
const anchorRef = useRef(null);
|
||||
const debounced = useDebouncedCallback((title) => {
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, { title });
|
||||
}, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
if (question.deleteTimeoutId) {
|
||||
clearTimeout(question.deleteTimeoutId);
|
||||
switch (switchState) {
|
||||
case "variant":
|
||||
return <Answer color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "images":
|
||||
return (
|
||||
<OptionsPict color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "varimg":
|
||||
return (
|
||||
<OptionsAndPict
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "emoji":
|
||||
return <Emoji color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "text":
|
||||
return <Input color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "select":
|
||||
return (
|
||||
<DropDown color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "date":
|
||||
return <Date color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "number":
|
||||
return <Slider color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "file":
|
||||
return (
|
||||
<Download color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "page":
|
||||
return <Page color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "rating":
|
||||
return (
|
||||
<RatingIcon color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<AnswerGroup color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
}
|
||||
}, [question]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Paper
|
||||
id={String(totalIndex)}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
maxWidth: "796px",
|
||||
width: "100%",
|
||||
backgroundColor: "white",
|
||||
border: "none",
|
||||
boxShadow: "none",
|
||||
paddingBottom: "20px",
|
||||
borderRadius: "0",
|
||||
borderTopLeftRadius: "12px",
|
||||
borderTopRightRadius: "12px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<CustomTextField
|
||||
placeholder={`Заголовок ${totalIndex + 1} вопроса`}
|
||||
text={question.title}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
sx={{ margin: "20px", width: "auto" }}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<Box>
|
||||
<InputAdornment
|
||||
ref={anchorRef}
|
||||
position="start"
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => setOpen((isOpened) => !isOpened)}
|
||||
>
|
||||
{IconAndrom(question.type)}
|
||||
</InputAdornment>
|
||||
<ChooseAnswerModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
anchorRef={anchorRef}
|
||||
totalIndex={totalIndex}
|
||||
switchState={question.type}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
endAdornment: (
|
||||
<Box {...draggableProps}>
|
||||
{totalIndex !== 0 && (
|
||||
<InputAdornment position="start">
|
||||
<PointsIcon
|
||||
style={{ color: "#9A9AAF", fontSize: "30px" }}
|
||||
/>
|
||||
</InputAdornment>
|
||||
)}
|
||||
</Box>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{/* {question.type === "" ? (
|
||||
<FormTypeQuestions totalIndex={totalIndex} />
|
||||
) : ( */}
|
||||
<SwitchQuestionsPage totalIndex={totalIndex} />
|
||||
{/* )} */}
|
||||
</Box>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,11 +0,0 @@
|
||||
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;
|
||||
};
|
@ -1,52 +1,35 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box } from "@mui/material";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
|
||||
import FormDraggableListItem from "./FormDraggableListItem";
|
||||
|
||||
import { questionStore, updateQuestionsListDragAndDrop } from "@root/questions";
|
||||
|
||||
import { reorder } from "./helper";
|
||||
|
||||
import type { DropResult } from "react-beautiful-dnd";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import FormDraggableListItem from "./FormDraggableListItem";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { reorderQuestions } from "@root/questions/actions";
|
||||
|
||||
|
||||
export const FormDraggableList = () => {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const questions = useQuestionsStore(state => state.questions);
|
||||
|
||||
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||
if (destination?.index === 0) {
|
||||
return;
|
||||
}
|
||||
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||
if (destination) reorderQuestions(source.index, destination.index);
|
||||
};
|
||||
|
||||
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) => (
|
||||
<FormDraggableListItem
|
||||
key={index}
|
||||
index={index}
|
||||
isDragging={snapshot.isDraggingOver}
|
||||
questionData={question}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Box>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="droppable-list">
|
||||
{(provided, snapshot) => (
|
||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{questions.map((question, index) => (
|
||||
<FormDraggableListItem
|
||||
key={index}
|
||||
index={index}
|
||||
isDragging={snapshot.isDraggingOver}
|
||||
questionData={question}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Box>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
|
@ -1,135 +1,111 @@
|
||||
import { Box, Button, Typography, useTheme } from "@mui/material";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { FormDraggableList } from "./FormDraggableList";
|
||||
|
||||
import {
|
||||
questionStore,
|
||||
createQuestion,
|
||||
updateQuestionsList,
|
||||
} from "@root/questions";
|
||||
import { quizStore } from "@root/quizes";
|
||||
|
||||
import ArrowLeft from "../../../assets/icons/questionsPage/arrowLeft";
|
||||
import AddAnswer from "../../../assets/icons/questionsPage/addAnswer";
|
||||
|
||||
import type {
|
||||
AnyQuizQuestion,
|
||||
QuizQuestionBase,
|
||||
} from "../../../model/questionTypes/shared";
|
||||
import { incrementCurrentStep } from "@root/quizes/actions";
|
||||
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||
import { createPortal } from "react-dom";
|
||||
import AddAnswer from "../../../assets/icons/questionsPage/addAnswer";
|
||||
import ArrowLeft from "../../../assets/icons/questionsPage/arrowLeft";
|
||||
import { FormDraggableList } from "./FormDraggableList";
|
||||
import { collapseAllQuestions, createQuestion } from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
|
||||
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 theme = useTheme();
|
||||
const { quiz } = useCurrentQuiz();
|
||||
|
||||
const collapseEverything = () => {
|
||||
listQuestions[quizId].forEach((item, index) => {
|
||||
updateQuestionsList<AnyQuizQuestion>(quizId, index, {
|
||||
...item,
|
||||
expanded: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
if (!quiz) return null;
|
||||
|
||||
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>
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
borderRadius: "12px",
|
||||
marginBottom: "40px",
|
||||
borderTop: "1px solid transparent",
|
||||
borderBottom: "1px solid transparent",
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<FormDraggableList />
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
padding: "4px",
|
||||
margin: "15px",
|
||||
border: "1px solid transparent",
|
||||
borderRadius: "8px",
|
||||
"&:hover": {
|
||||
border: "1px solid #9A9AAF",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
createQuestion(quizId);
|
||||
}}
|
||||
>
|
||||
<AddAnswer color="#EEE4FC" />
|
||||
<Typography sx={{ color: "#9A9AAF" }}>
|
||||
Добавить еще один вопрос
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
gap: "8px",
|
||||
maxWidth: "796px",
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
{createPortal(<QuizPreview />, document.body)}
|
||||
</>
|
||||
);
|
||||
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={collapseAllQuestions}
|
||||
>
|
||||
Свернуть всё
|
||||
</Button>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
borderRadius: "12px",
|
||||
marginBottom: "40px",
|
||||
borderTop: "1px solid transparent",
|
||||
borderBottom: "1px solid transparent",
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<FormDraggableList />
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
padding: "4px",
|
||||
margin: "15px",
|
||||
border: "1px solid transparent",
|
||||
borderRadius: "8px",
|
||||
"&:hover": {
|
||||
border: "1px solid #9A9AAF",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
createQuestion(quiz.id);
|
||||
}}
|
||||
>
|
||||
<AddAnswer color="#EEE4FC" />
|
||||
<Typography sx={{ color: "#9A9AAF" }}>
|
||||
Добавить еще один вопрос
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
gap: "8px",
|
||||
maxWidth: "796px",
|
||||
}}
|
||||
>
|
||||
<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={incrementCurrentStep}
|
||||
>
|
||||
Следующий шаг
|
||||
</Button>
|
||||
</Box>
|
||||
{createPortal(<QuizPreview />, document.body)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
import QuestionsMiniButton from "@ui_kit/QuestionsMiniButton";
|
||||
@ -12,23 +11,14 @@ 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 {
|
||||
questionStore,
|
||||
updateQuestionsList,
|
||||
createQuestion,
|
||||
removeQuestionForce,
|
||||
} from "@root/questions";
|
||||
import Download from "../../../assets/icons/questionsPage/download";
|
||||
|
||||
import type {
|
||||
QuizQuestionBase,
|
||||
AnyQuizQuestion,
|
||||
} from "../../../model/questionTypes/shared";
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
}
|
||||
|
||||
type ButtonTypeQuestion = {
|
||||
icon: JSX.Element;
|
||||
@ -69,11 +59,12 @@ const BUTTON_TYPE_SHORT_QUESTIONS: ButtonTypeQuestion[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export default function FormTypeQuestions({ totalIndex }: Props) {
|
||||
interface Props {
|
||||
question: AnyQuizQuestion;
|
||||
}
|
||||
|
||||
export default function FormTypeQuestions({ question }: Props) {
|
||||
const [switchState, setSwitchState] = useState("");
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionBase;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@ -85,21 +76,16 @@ export default function FormTypeQuestions({ totalIndex }: Props) {
|
||||
margin: "20px",
|
||||
}}
|
||||
>
|
||||
{(totalIndex === 0
|
||||
{(true /* TODO какое-то непонятное условие */
|
||||
? BUTTON_TYPE_QUESTIONS
|
||||
: BUTTON_TYPE_SHORT_QUESTIONS
|
||||
).map(({ icon, title, value }) => (
|
||||
).map(({ icon, title, value: questionType }) => (
|
||||
<QuestionsMiniButton
|
||||
key={title}
|
||||
onClick={() => {
|
||||
const clonedQuestion = { ...question };
|
||||
|
||||
removeQuestionForce(quizId, clonedQuestion.id);
|
||||
createQuestion(quizId, value, totalIndex);
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
||||
expanded: clonedQuestion.expanded,
|
||||
type: value,
|
||||
});
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.type = questionType;
|
||||
})
|
||||
}}
|
||||
icon={icon}
|
||||
text={title}
|
||||
@ -109,9 +95,10 @@ export default function FormTypeQuestions({ totalIndex }: Props) {
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
totalIndex={totalIndex}
|
||||
question={question}
|
||||
/>
|
||||
<SwitchAnswerOptions switchState={switchState} totalIndex={totalIndex} />
|
||||
{/* TODO конфликт типов */}
|
||||
{/* <SwitchAnswerOptions switchState={switchState} question={question} /> */}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,371 +1,352 @@
|
||||
import {
|
||||
Box,
|
||||
Link,
|
||||
Typography,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
InputAdornment,
|
||||
IconButton,
|
||||
Button,
|
||||
Popover,
|
||||
TextareaAutosize,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import { UploadImageModal } from "../UploadImage/UploadImageModal";
|
||||
|
||||
import { ImageAddIcons } from "@icons/ImageAddIcons";
|
||||
import { MessageIcon } from "@icons/messagIcon";
|
||||
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import { questionStore, setVariantImageUrl, setVariantOriginalImageUrl, updateQuestionsList } from "@root/questions";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
Link,
|
||||
Popover,
|
||||
TextField,
|
||||
TextareaAutosize,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
import { openCropModal } from "@root/cropModal";
|
||||
import { closeImageUploadModal, openImageUploadModal } from "@root/imageUploadModal";
|
||||
import { addQuestionVariant, setVariantImageUrl, setVariantOriginalImageUrl } from "@root/questions/actions";
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||
import { useState } from "react";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
|
||||
import { UploadImageModal } from "../UploadImage/UploadImageModal";
|
||||
import SwitchOptionsAndPict from "./switchOptionsAndPict";
|
||||
|
||||
import { openCropModal } from "@root/cropModal";
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionVarImg;
|
||||
}
|
||||
|
||||
export default function OptionsAndPicture({ totalIndex }: Props) {
|
||||
const [isUploadImageModalOpen, setIsUploadImageModalOpen] = useState(false);
|
||||
export default function OptionsAndPicture({ question }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionVarImg;
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
const handleImageUpload = (files: FileList | null) => {
|
||||
if (!files?.length) return;
|
||||
if (!files?.length || !selectedVariantId) return;
|
||||
|
||||
const [file] = Array.from(files);
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
setVariantImageUrl(quizId, totalIndex, currentIndex, url);
|
||||
setVariantOriginalImageUrl(quizId, totalIndex, currentIndex, url);
|
||||
setIsUploadImageModalOpen(false);
|
||||
setVariantImageUrl(question.id, selectedVariantId, url);
|
||||
setVariantOriginalImageUrl(question.id, selectedVariantId, url);
|
||||
closeImageUploadModal();
|
||||
openCropModal(url, url);
|
||||
};
|
||||
|
||||
function handleCropModalSaveClick(url: string) {
|
||||
setVariantImageUrl(quizId, totalIndex, currentIndex, url);
|
||||
if (!selectedVariantId) return;
|
||||
|
||||
setVariantImageUrl(question.id, selectedVariantId, url);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ pl: "20px", pr: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
question={totalIndex}
|
||||
additionalContent={(variant, index) => (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<AddOrEditImageButton
|
||||
imageSrc={variant.extendedText}
|
||||
onImageClick={() => {
|
||||
if (!("originalImageUrl" in variant)) return;
|
||||
<>
|
||||
<Box sx={{ pl: "20px", pr: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
question={question}
|
||||
additionalContent={(variant) => (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<AddOrEditImageButton
|
||||
imageSrc={variant.extendedText}
|
||||
onImageClick={() => {
|
||||
if (!("originalImageUrl" in variant)) return;
|
||||
|
||||
setCurrentIndex(index);
|
||||
if (variant.extendedText) return openCropModal(
|
||||
variant.extendedText,
|
||||
variant.originalImageUrl
|
||||
);
|
||||
setSelectedVariantId(variant.id);
|
||||
if (variant.extendedText) return openCropModal(
|
||||
variant.extendedText,
|
||||
variant.originalImageUrl
|
||||
);
|
||||
|
||||
setIsUploadImageModalOpen(true);
|
||||
}}
|
||||
onPlusClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setIsUploadImageModalOpen(true);
|
||||
}}
|
||||
sx={{ mx: "10px" }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
additionalMobile={(variant, index) => (
|
||||
<>
|
||||
{isMobile && (
|
||||
<AddOrEditImageButton
|
||||
imageSrc={variant.extendedText}
|
||||
onImageClick={() => {
|
||||
if (!("originalImageUrl" in variant)) return;
|
||||
openImageUploadModal();
|
||||
}}
|
||||
onPlusClick={() => {
|
||||
setSelectedVariantId(variant.id);
|
||||
openImageUploadModal();
|
||||
}}
|
||||
sx={{ mx: "10px" }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
additionalMobile={(variant) => (
|
||||
<>
|
||||
{isMobile && (
|
||||
<AddOrEditImageButton
|
||||
imageSrc={variant.extendedText}
|
||||
onImageClick={() => {
|
||||
if (!("originalImageUrl" in variant)) return;
|
||||
|
||||
setCurrentIndex(index);
|
||||
if (variant.extendedText) return openCropModal(
|
||||
variant.extendedText,
|
||||
variant.originalImageUrl
|
||||
);
|
||||
setSelectedVariantId(variant.id);
|
||||
if (variant.extendedText) return openCropModal(
|
||||
variant.extendedText,
|
||||
variant.originalImageUrl
|
||||
);
|
||||
|
||||
setIsUploadImageModalOpen(true);
|
||||
}}
|
||||
onPlusClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setIsUploadImageModalOpen(true);
|
||||
}}
|
||||
sx={{ m: "8px", width: "auto" }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<UploadImageModal
|
||||
open={isUploadImageModalOpen}
|
||||
onClose={() => setIsUploadImageModalOpen(false)}
|
||||
imgHC={handleImageUpload}
|
||||
/>
|
||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
border: "1px solid #9A9AAF",
|
||||
borderRadius: "8px",
|
||||
display: isTablet ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
fullWidth
|
||||
focused={false}
|
||||
placeholder={"Добавьте ответ"}
|
||||
multiline={question.content.largeCheck}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<>
|
||||
<InputAdornment position="start">
|
||||
<PointsIcon
|
||||
style={{ color: "#9A9AAF", fontSize: "30px" }}
|
||||
/>
|
||||
</InputAdornment>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "60px",
|
||||
height: "40px",
|
||||
background: "#EEE4FC",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginRight: "20px",
|
||||
marginLeft: "12px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<ImageAddIcons fontSize="22px" color="#7E2AEA" />
|
||||
</Box>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#7E2AEA",
|
||||
height: "100%",
|
||||
width: "25px",
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
sx={{ padding: "0" }}
|
||||
aria-describedby="my-popover-id"
|
||||
>
|
||||
<MessageIcon
|
||||
style={{
|
||||
color: "#9A9AAF",
|
||||
fontSize: "30px",
|
||||
marginRight: "6.5px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
<Popover
|
||||
id="my-popover-id"
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
open={false}
|
||||
>
|
||||
<TextareaAutosize
|
||||
style={{ margin: "10px" }}
|
||||
placeholder="Подсказка для этого ответа"
|
||||
/>
|
||||
</Popover>
|
||||
<IconButton sx={{ padding: "0" }}>
|
||||
<DeleteIcon
|
||||
style={{
|
||||
color: theme.palette.grey2.main,
|
||||
marginRight: "-1px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
padding: "13.5px",
|
||||
borderRadius: "10px",
|
||||
background: "#ffffff",
|
||||
height: "48px",
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: "none",
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: { fontSize: "18px", lineHeight: "21px", py: 0 },
|
||||
}}
|
||||
/>
|
||||
openImageUploadModal();
|
||||
}}
|
||||
onPlusClick={() => {
|
||||
setSelectedVariantId(variant.id);
|
||||
openImageUploadModal();
|
||||
}}
|
||||
sx={{ m: "8px", width: "auto" }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<UploadImageModal imgHC={handleImageUpload} />
|
||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
border: "1px solid #9A9AAF",
|
||||
borderRadius: "8px",
|
||||
display: isTablet ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
fullWidth
|
||||
focused={false}
|
||||
placeholder={"Добавьте ответ"}
|
||||
multiline={question.content.largeCheck}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<>
|
||||
<InputAdornment position="start">
|
||||
<PointsIcon
|
||||
style={{ color: "#9A9AAF", fontSize: "30px" }}
|
||||
/>
|
||||
</InputAdornment>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "60px",
|
||||
height: "40px",
|
||||
background: "#EEE4FC",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginRight: "20px",
|
||||
marginLeft: "12px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<ImageAddIcons fontSize="22px" color="#7E2AEA" />
|
||||
</Box>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#7E2AEA",
|
||||
height: "100%",
|
||||
width: "25px",
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
sx={{ padding: "0" }}
|
||||
aria-describedby="my-popover-id"
|
||||
>
|
||||
<MessageIcon
|
||||
style={{
|
||||
color: "#9A9AAF",
|
||||
fontSize: "30px",
|
||||
marginRight: "6.5px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
<Popover
|
||||
id="my-popover-id"
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
open={false}
|
||||
>
|
||||
<TextareaAutosize
|
||||
style={{ margin: "10px" }}
|
||||
placeholder="Подсказка для этого ответа"
|
||||
/>
|
||||
</Popover>
|
||||
<IconButton sx={{ padding: "0" }}>
|
||||
<DeleteIcon
|
||||
style={{
|
||||
color: theme.palette.grey2.main,
|
||||
marginRight: "-1px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
padding: "13.5px",
|
||||
borderRadius: "10px",
|
||||
background: "#ffffff",
|
||||
height: "48px",
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: "none",
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: { fontSize: "18px", lineHeight: "21px", py: 0 },
|
||||
}}
|
||||
/>
|
||||
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%", background: "#EEE4FC", height: "40px" }}
|
||||
/>
|
||||
<ImageAddIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%", background: "#EEE4FC", height: "40px" }}
|
||||
/>
|
||||
<ImageAddIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "17px",
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: theme.palette.brightPurple.main,
|
||||
fontWeight: "400",
|
||||
fontSize: "16px",
|
||||
mr: "4px",
|
||||
height: "19px",
|
||||
}}
|
||||
onClick={() => {
|
||||
const clonedContent = { ...question.content };
|
||||
clonedContent.variants.push({
|
||||
answer: "",
|
||||
hints: "",
|
||||
extendedText: "",
|
||||
originalImageUrl: "",
|
||||
});
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: clonedContent,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
{isMobile ? null : (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "21.33px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
или нажмите Enter
|
||||
</Typography>
|
||||
<EnterIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "24px",
|
||||
marginLeft: "6px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptionsAndPict
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchOptionsAndPict switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
{isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%", background: "#EEE4FC", height: "40px" }}
|
||||
/>
|
||||
<ImageAddIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%", background: "#EEE4FC", height: "40px" }}
|
||||
/>
|
||||
<ImageAddIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "17px",
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: theme.palette.brightPurple.main,
|
||||
fontWeight: "400",
|
||||
fontSize: "16px",
|
||||
mr: "4px",
|
||||
height: "19px",
|
||||
}}
|
||||
onClick={() => {
|
||||
addQuestionVariant(question.id)
|
||||
}}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
{isMobile ? null : (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "21.33px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
или нажмите Enter
|
||||
</Typography>
|
||||
<EnterIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "24px",
|
||||
marginLeft: "6px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptionsAndPict
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
question={question}
|
||||
/>
|
||||
<SwitchOptionsAndPict switchState={switchState} question={question} />
|
||||
</>
|
||||
|
||||
);
|
||||
);
|
||||
}
|
||||
|
@ -1,175 +1,166 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||
|
||||
|
||||
type SettingOptionsAndPictProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionVarImg;
|
||||
};
|
||||
|
||||
export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndPictProps) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
export default function SettingOptionsAndPict({ question }: SettingOptionsAndPictProps) {
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(680));
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(680));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionVarImg;
|
||||
const debounced = useDebouncedCallback((replText) => {
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: { ...question.content, replText },
|
||||
});
|
||||
}, 1000);
|
||||
const debounceDescription = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
const setReplText = useDebouncedCallback((replText) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "varimg") return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : "none",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
maxWidth: isFigmaTablte ? "297px" : "360px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: { ...question.content, own: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{!isWrappColumn && (
|
||||
<Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
||||
<Typography
|
||||
question.content.replText = replText;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const setDescription = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "14px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : "none",
|
||||
}}
|
||||
>
|
||||
Текст-заглушка на картинке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
sx={{
|
||||
maxWidth: "330px",
|
||||
width: "100%",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
placeholder={"Пример текста"}
|
||||
text={question.content.replText}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
maxWidth: isFigmaTablte ? "297px" : "360px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "varimg") return;
|
||||
|
||||
question.content.own = target.checked;
|
||||
})}
|
||||
/>
|
||||
{!isWrappColumn && (
|
||||
<Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Текст-заглушка на картинке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
sx={{
|
||||
maxWidth: "330px",
|
||||
width: "100%",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
placeholder={"Пример текста"}
|
||||
text={question.content.replText}
|
||||
onChange={({ target }) => setReplText(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "31px") : "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={question.content.required}
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "varimg") return;
|
||||
|
||||
question.content.required = target.checked;
|
||||
})}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = "";
|
||||
})}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => setDescription(target.value)}
|
||||
/>
|
||||
)}
|
||||
{isWrappColumn && (
|
||||
<>
|
||||
<Typography
|
||||
sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Текст-заглушка на картинке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
sx={{ maxWidth: "360px", width: "100%" }}
|
||||
placeholder={"Пример текста"}
|
||||
text={question.content.replText}
|
||||
onChange={({ target }) => setReplText(target.value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "31px") : "20px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={question.content.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: { ...question.content, required: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounceDescription(target.value)}
|
||||
/>
|
||||
)}
|
||||
{isWrappColumn && (
|
||||
<>
|
||||
<Typography
|
||||
sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Текст-заглушка на картинке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
sx={{ maxWidth: "360px", width: "100%" }}
|
||||
placeholder={"Пример текста"}
|
||||
text={question.content.replText}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,32 +1,29 @@
|
||||
import * as React from "react";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import SettingOptionsAndPict from "./SettingOptionsAndPict";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||
import UploadImage from "../UploadImage";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingOptionsAndPict from "./SettingOptionsAndPict";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionVarImg;
|
||||
}
|
||||
|
||||
export default function SwitchOptionsAndPict({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingOptionsAndPict totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "image":
|
||||
return <UploadImage totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingOptionsAndPict question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
case "image":
|
||||
return <UploadImage question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ import {
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
import { openCropModal } from "@root/cropModal";
|
||||
import { setVariantImageUrl, setVariantOriginalImageUrl, updateQuestionsList } from "@root/questions";
|
||||
import { closeImageUploadModal, openImageUploadModal } from "@root/imageUploadModal";
|
||||
import { addQuestionVariant, setVariantImageUrl, setVariantOriginalImageUrl } from "@root/questions/actions";
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
@ -24,49 +24,39 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function OptionsPicture({ question }: Props) {
|
||||
const [isUploadImageModalOpen, setIsUploadImageModalOpen] = useState(false);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const quizId = Number(useParams().quizId);
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
const handleImageUpload = (files: FileList | null) => {
|
||||
if (!files?.length) return;
|
||||
if (!files?.length || !selectedVariantId) return;
|
||||
|
||||
const [file] = Array.from(files);
|
||||
const url = URL.createObjectURL(file);
|
||||
|
||||
setVariantImageUrl(quizId, totalIndex, currentIndex, url);
|
||||
setVariantOriginalImageUrl(quizId, totalIndex, currentIndex, url);
|
||||
setIsUploadImageModalOpen(false);
|
||||
setVariantImageUrl(question.id, selectedVariantId, url);
|
||||
setVariantOriginalImageUrl(question.id, selectedVariantId, url);
|
||||
closeImageUploadModal();
|
||||
openCropModal(url, url);
|
||||
};
|
||||
|
||||
const addNewAnswer = () => {
|
||||
const answerNew = question.content.variants.slice();
|
||||
answerNew.push({ answer: "", hints: "", extendedText: "", originalImageUrl: "" });
|
||||
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: answerNew },
|
||||
});
|
||||
};
|
||||
|
||||
function handleCropModalSaveClick(url: string) {
|
||||
setVariantImageUrl(quizId, totalIndex, currentIndex, url);
|
||||
if (!selectedVariantId) return;
|
||||
|
||||
setVariantImageUrl(question.id, selectedVariantId, url);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
question={totalIndex}
|
||||
additionalContent={(variant, index) => (
|
||||
question={question}
|
||||
additionalContent={(variant) => (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<AddOrEditImageButton
|
||||
@ -74,7 +64,7 @@ export default function OptionsPicture({ question }: Props) {
|
||||
onImageClick={() => {
|
||||
if (!("originalImageUrl" in variant)) return;
|
||||
|
||||
setCurrentIndex(index);
|
||||
setSelectedVariantId(variant.id);
|
||||
if (variant.extendedText) {
|
||||
return openCropModal(
|
||||
variant.extendedText,
|
||||
@ -82,18 +72,18 @@ export default function OptionsPicture({ question }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
setIsUploadImageModalOpen(true);
|
||||
openImageUploadModal();
|
||||
}}
|
||||
onPlusClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setIsUploadImageModalOpen(true);
|
||||
setSelectedVariantId(variant.id);
|
||||
openImageUploadModal();
|
||||
}}
|
||||
sx={{ mx: "10px" }}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
additionalMobile={(variant, index) => (
|
||||
additionalMobile={(variant) => (
|
||||
<>
|
||||
{isMobile && (
|
||||
<AddOrEditImageButton
|
||||
@ -101,7 +91,7 @@ export default function OptionsPicture({ question }: Props) {
|
||||
onImageClick={() => {
|
||||
if (!("originalImageUrl" in variant)) return;
|
||||
|
||||
setCurrentIndex(index);
|
||||
setSelectedVariantId(variant.id);
|
||||
if (variant.extendedText) {
|
||||
return openCropModal(
|
||||
variant.extendedText,
|
||||
@ -109,11 +99,11 @@ export default function OptionsPicture({ question }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
setIsUploadImageModalOpen(true);
|
||||
openImageUploadModal();
|
||||
}}
|
||||
onPlusClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setIsUploadImageModalOpen(true);
|
||||
setSelectedVariantId(variant.id);
|
||||
openImageUploadModal();
|
||||
}}
|
||||
sx={{ m: "8px", width: "auto" }}
|
||||
/>
|
||||
@ -121,18 +111,14 @@ export default function OptionsPicture({ question }: Props) {
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<UploadImageModal
|
||||
open={isUploadImageModalOpen}
|
||||
onClose={() => setIsUploadImageModalOpen(false)}
|
||||
imgHC={handleImageUpload}
|
||||
/>
|
||||
<UploadImageModal imgHC={handleImageUpload} />
|
||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.brightPurple.main }}
|
||||
onClick={addNewAnswer}
|
||||
onClick={() => addQuestionVariant(question.id)}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
@ -159,8 +145,8 @@ export default function OptionsPicture({ question }: Props) {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchAnswerOptionsPict switchState={switchState} totalIndex={totalIndex} />
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||
<SwitchAnswerOptionsPict switchState={switchState} question={question} />
|
||||
</>
|
||||
|
||||
);
|
||||
|
@ -1,327 +1,314 @@
|
||||
import { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
Button,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2";
|
||||
import FormatIcon1 from "../../../assets/icons/questionsPage/FormatIcon1";
|
||||
import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2";
|
||||
import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11";
|
||||
import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21";
|
||||
import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIcon12";
|
||||
|
||||
import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21";
|
||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
||||
|
||||
interface Props {
|
||||
Icon: (props: { color: string }) => JSX.Element;
|
||||
// Icon: React.ElementType;
|
||||
isActive?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
type SettingOpytionsPictProps = {
|
||||
totalIndex: number;
|
||||
};
|
||||
|
||||
type Proportion = "1:1" | "2:1" | "1:2";
|
||||
|
||||
type ProportionItem = {
|
||||
value: Proportion;
|
||||
icon: (props: { color: string }) => JSX.Element;
|
||||
value: Proportion;
|
||||
icon: (props: { color: string; }) => JSX.Element;
|
||||
};
|
||||
|
||||
const PROPORTIONS: ProportionItem[] = [
|
||||
{ value: "1:1", icon: ProportionsIcon11 },
|
||||
{ value: "2:1", icon: ProportionsIcon21 },
|
||||
{ value: "1:2", icon: ProportionsIcon12 },
|
||||
{ value: "1:1", icon: ProportionsIcon11 },
|
||||
{ value: "2:1", icon: ProportionsIcon21 },
|
||||
{ value: "1:2", icon: ProportionsIcon12 },
|
||||
];
|
||||
|
||||
export function SelectIconButton({ Icon, isActive = false, onClick }: Props) {
|
||||
const theme = useTheme();
|
||||
type SettingOpytionsPictProps = {
|
||||
question: QuizQuestionImages;
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="outlined"
|
||||
startIcon={
|
||||
<Icon
|
||||
color={
|
||||
isActive
|
||||
? theme.palette.navbarbg.main
|
||||
: theme.palette.brightPurple.main
|
||||
}
|
||||
/>
|
||||
}
|
||||
sx={{
|
||||
backgroundColor: isActive ? theme.palette.brightPurple.main : "#eee4fc",
|
||||
export default function SettingOpytionsPict({ question }: SettingOpytionsPictProps) {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
borderRadius: 0,
|
||||
border: "none",
|
||||
color: isActive
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey2.main,
|
||||
p: "7px",
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
minWidth: 0,
|
||||
"& .MuiButton-startIcon": {
|
||||
mr: 0,
|
||||
ml: 0,
|
||||
},
|
||||
"&:hover": {
|
||||
border: "none",
|
||||
borderColor: isActive
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey2.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
export default function SettingOpytionsPict({
|
||||
totalIndex,
|
||||
}: SettingOpytionsPictProps) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionImages;
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const updateProportions = (proportions: Proportion) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
question.content.xy = proportions;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!question.content.xy) {
|
||||
updateProportions("1:1");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const updateProportions = (proportions: Proportion) => {
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, xy: proportions },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isTablet ? "column" : null,
|
||||
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: isFigmaTablte ? (isMobile ? "20px" : "0px") : "28px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ pb: isMobile ? "11px" : "6px" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: isMobile ? "10px" : "14px",
|
||||
}}
|
||||
>
|
||||
Пропорции
|
||||
</Typography>
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isTablet ? "column" : null,
|
||||
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
|
||||
}}
|
||||
>
|
||||
{PROPORTIONS.map(({ value, icon }, index) => (
|
||||
<SelectIconButton
|
||||
key={index}
|
||||
onClick={() => updateProportions(value)}
|
||||
isActive={question.content.xy === value}
|
||||
Icon={icon}
|
||||
/>
|
||||
))}
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: isFigmaTablte ? (isMobile ? "20px" : "0px") : "28px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ pb: isMobile ? "11px" : "6px" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: isMobile ? "10px" : "14px",
|
||||
}}
|
||||
>
|
||||
Пропорции
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
{PROPORTIONS.map(({ value, icon }, index) => (
|
||||
<SelectIconButton
|
||||
key={index}
|
||||
onClick={() => updateProportions(value)}
|
||||
isActive={question.content.xy === value}
|
||||
Icon={icon}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={question.content.multi}
|
||||
dataCy="multiple-answers-checkbox"
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.multi = target.checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
{question.content.xy !== "1:1" &&
|
||||
question.content.format !== "masonry" && (
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Большие картинки"}
|
||||
checked={question.content.largeCheck}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.largeCheck = target.checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.own = target.checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: isFigmaTablte ? (isMobile ? "20px" : "0px") : "28px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginBottom: "5px",
|
||||
opacity: question.content.xy !== "1:1" ? 1 : 0,
|
||||
display: isTablet
|
||||
? question.content.xy === "1:1"
|
||||
? "none"
|
||||
: "block"
|
||||
: "block",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
marginBottom: "15px",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Формат
|
||||
</Typography>
|
||||
<SelectIconButton
|
||||
onClick={() => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.format = "carousel";
|
||||
})
|
||||
}
|
||||
isActive={question.content.format === "carousel"}
|
||||
Icon={FormatIcon2}
|
||||
/>
|
||||
<SelectIconButton
|
||||
onClick={() => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.format = "masonry";
|
||||
})
|
||||
}
|
||||
isActive={question.content.format === "masonry"}
|
||||
Icon={FormatIcon1}
|
||||
/>
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={question.content.required}
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.required = target.checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = "";
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Внутреннее описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={question.content.multi}
|
||||
dataCy="multiple-answers-checkbox"
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, multi: target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
{question.content.xy !== "1:1" &&
|
||||
question.content.format !== "masonry" && (
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Большие картинки"}
|
||||
checked={question.content.largeCheck}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
largeCheck: target.checked,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, own: target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: isFigmaTablte ? (isMobile ? "20px" : "0px") : "28px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
marginBottom: "5px",
|
||||
opacity: question.content.xy !== "1:1" ? 1 : 0,
|
||||
display: isTablet
|
||||
? question.content.xy === "1:1"
|
||||
? "none"
|
||||
: "block"
|
||||
: "block",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
marginBottom: "15px",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Формат
|
||||
</Typography>
|
||||
<SelectIconButton
|
||||
onClick={() =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, format: "carousel" },
|
||||
})
|
||||
}
|
||||
isActive={question.content.format === "carousel"}
|
||||
Icon={FormatIcon2}
|
||||
/>
|
||||
<SelectIconButton
|
||||
onClick={() =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, format: "masonry" },
|
||||
})
|
||||
}
|
||||
isActive={question.content.format === "masonry"}
|
||||
Icon={FormatIcon1}
|
||||
/>
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={question.content.required}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, required: target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Внутреннее описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
Icon: (props: { color: string; }) => JSX.Element;
|
||||
// Icon: React.ElementType;
|
||||
isActive?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function SelectIconButton({ Icon, isActive = false, onClick }: Props) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="outlined"
|
||||
startIcon={
|
||||
<Icon
|
||||
color={
|
||||
isActive
|
||||
? theme.palette.navbarbg.main
|
||||
: theme.palette.brightPurple.main
|
||||
}
|
||||
/>
|
||||
}
|
||||
sx={{
|
||||
backgroundColor: isActive ? theme.palette.brightPurple.main : "#eee4fc",
|
||||
|
||||
borderRadius: 0,
|
||||
border: "none",
|
||||
color: isActive
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey2.main,
|
||||
p: "7px",
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
minWidth: 0,
|
||||
"& .MuiButton-startIcon": {
|
||||
mr: 0,
|
||||
ml: 0,
|
||||
},
|
||||
"&:hover": {
|
||||
border: "none",
|
||||
borderColor: isActive
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey2.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -2,27 +2,25 @@ import * as React from "react";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import SettingOpytionsPict from "./settingOpytionsPict";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import { QuizQuestionImages } from "@model/questionTypes/images";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionImages;
|
||||
}
|
||||
|
||||
export default function SwitchAnswerOptionsPict({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingOpytionsPict totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingOpytionsPict question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -1,79 +1,76 @@
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, Typography, Tooltip, useTheme, useMediaQuery } from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchTextField from "./switchTextField";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionText;
|
||||
}
|
||||
export default function OwnTextField({ totalIndex }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionText;
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: { ...question.content, placeholder: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
export default function OwnTextField({ question }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: "auto",
|
||||
maxWidth: "745px",
|
||||
display: "flex",
|
||||
pb: "20px",
|
||||
pl: "20px",
|
||||
pr: "20px",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "15px" : "20px",
|
||||
}}
|
||||
>
|
||||
<CustomTextField
|
||||
placeholder={"Пример ответа"}
|
||||
text={question.content.placeholder}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
sx={{ maxWidth: isFigmaTablte ? "549px" : "640px", width: "100%", mt: isMobile ? "15px" : "0px" }}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: isMobile ? "flex-start" : "center", gap: "12px" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "18.96px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
Пользователю будет дано поле для ввода значения
|
||||
</Typography>
|
||||
<Tooltip title="Будет использоваться для ввода значения." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
const setPlaceholder = useDebouncedCallback((value) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "text") return;
|
||||
|
||||
question.content.placeholder = value;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: "auto",
|
||||
maxWidth: "745px",
|
||||
display: "flex",
|
||||
pb: "20px",
|
||||
pl: "20px",
|
||||
pr: "20px",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "15px" : "20px",
|
||||
}}
|
||||
>
|
||||
<CustomTextField
|
||||
placeholder={"Пример ответа"}
|
||||
text={question.content.placeholder}
|
||||
onChange={({ target }) => setPlaceholder(target.value)}
|
||||
sx={{ maxWidth: isFigmaTablte ? "549px" : "640px", width: "100%", mt: isMobile ? "15px" : "0px" }}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: isMobile ? "flex-start" : "center", gap: "12px" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "18.96px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
Пользователю будет дано поле для ввода значения
|
||||
</Typography>
|
||||
<Tooltip title="Будет использоваться для ввода значения." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchTextField switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||
<SwitchTextField switchState={switchState} question={question} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,231 +1,223 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Typography,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
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 { useDebouncedCallback } from "use-debounce";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||
|
||||
|
||||
type SettingTextFieldProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionText;
|
||||
};
|
||||
|
||||
type Answer = {
|
||||
name: string;
|
||||
value: "single" | "multi";
|
||||
name: string;
|
||||
value: "single" | "multi";
|
||||
};
|
||||
|
||||
const ANSWER_TYPES: Answer[] = [
|
||||
{ name: "Однострочное", value: "single" },
|
||||
{ name: "Многострочное", value: "multi" },
|
||||
{ name: "Однострочное", value: "single" },
|
||||
{ name: "Многострочное", value: "multi" },
|
||||
];
|
||||
|
||||
export default function SettingTextField({
|
||||
totalIndex,
|
||||
question,
|
||||
}: SettingTextFieldProps) {
|
||||
const { listQuestions } = questionStore();
|
||||
const quizId = Number(useParams().quizId);
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionText;
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
marginRight: isFigmaTablte ? "0px" : "32px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
marginBottom: "14px",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
aria-labelledby="demo-controlled-radio-buttons-group"
|
||||
name="controlled-radio-buttons-group"
|
||||
value={ANSWER_TYPES.findIndex(
|
||||
({ value }) => question.content.answerType === value
|
||||
)}
|
||||
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
answerType: ANSWER_TYPES[Number(target.value)].value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ANSWER_TYPES.map(({ name }, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
sx={{
|
||||
height: "26px",
|
||||
color: theme.palette.grey2.main,
|
||||
"& .MuiRadio-root": { padding: "8px 9px" },
|
||||
}}
|
||||
value={index}
|
||||
control={
|
||||
<Radio icon={<CheckIcon />} checkedIcon={<CheckedIcon />} />
|
||||
}
|
||||
label={name}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
marginTop: "15px",
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Только числа"}
|
||||
checked={question.content.onlyNumbers}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: { ...question.content, onlyNumbers: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
||||
pr: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Автозаполнение адреса"}
|
||||
checked={question.content.autofill}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: { ...question.content, autofill: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
marginRight: isFigmaTablte ? "0px" : "32px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
marginBottom: "14px",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
aria-labelledby="demo-controlled-radio-buttons-group"
|
||||
name="controlled-radio-buttons-group"
|
||||
value={ANSWER_TYPES.findIndex(
|
||||
({ value }) => question.content.answerType === value
|
||||
)}
|
||||
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "text") return;
|
||||
|
||||
question.content.answerType = ANSWER_TYPES[Number(target.value)].value;
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ANSWER_TYPES.map(({ name }, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
sx={{
|
||||
height: "26px",
|
||||
color: theme.palette.grey2.main,
|
||||
"& .MuiRadio-root": { padding: "8px 9px" },
|
||||
}}
|
||||
value={index}
|
||||
control={
|
||||
<Radio icon={<CheckIcon />} checkedIcon={<CheckedIcon />} />
|
||||
}
|
||||
label={name}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
marginTop: "15px",
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Только числа"}
|
||||
checked={question.content.onlyNumbers}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "text") return;
|
||||
|
||||
question.content.onlyNumbers = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
||||
pr: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Автозаполнение адреса"}
|
||||
checked={question.content.autofill}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.autofill = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked
|
||||
? question.content.innerName
|
||||
: "";
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
@ -1,28 +1,26 @@
|
||||
import * as React from "react";
|
||||
import { QuizQuestionText } from "@model/questionTypes/text";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingTextField from "./settingTextField";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionText;
|
||||
}
|
||||
|
||||
export default function SwitchTextField({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingTextField totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingTextField question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +1,38 @@
|
||||
import { VideofileIcon } from "@icons/questionsPage/VideofileIcon";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { questionStore, setPageQuestionOriginalPicture, setPageQuestionPicture, updateQuestionsList } from "@root/questions";
|
||||
import { openCropModal } from "@root/cropModal";
|
||||
import { closeImageUploadModal, openImageUploadModal } from "@root/imageUploadModal";
|
||||
import { setPageQuestionOriginalPicture, setPageQuestionPicture, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import { UploadImageModal } from "../UploadImage/UploadImageModal";
|
||||
import { UploadVideoModal } from "../UploadVideoModal";
|
||||
import SwitchPageOptions from "./switchPageOptions";
|
||||
|
||||
import { openCropModal } from "@root/cropModal";
|
||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||
|
||||
type Props = {
|
||||
disableInput?: boolean;
|
||||
totalIndex: number;
|
||||
question: QuizQuestionPage;
|
||||
};
|
||||
|
||||
export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
const [openImageModal, setOpenImageModal] = useState<boolean>(false);
|
||||
export default function PageOptions({ disableInput, question }: Props) {
|
||||
const [openVideoModal, setOpenVideoModal] = useState<boolean>(false);
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(780));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionPage;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: { ...question.content, text: value },
|
||||
|
||||
const setText = useDebouncedCallback((value) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "page") return;
|
||||
|
||||
question.content.text = value;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
@ -46,14 +45,14 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
|
||||
const url = URL.createObjectURL(fileList[0]);
|
||||
|
||||
setPageQuestionPicture(quizId, totalIndex, url);
|
||||
setPageQuestionOriginalPicture(quizId, totalIndex, url);
|
||||
setOpenImageModal(false);
|
||||
setPageQuestionPicture(question.id, url);
|
||||
setPageQuestionOriginalPicture(question.id, url);
|
||||
closeImageUploadModal();
|
||||
openCropModal(url, url);
|
||||
}
|
||||
|
||||
function handleCropModalSaveClick(url: string) {
|
||||
setPageQuestionPicture(quizId, totalIndex, url);
|
||||
setPageQuestionPicture(question.id, url);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -72,7 +71,7 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
<CustomTextField
|
||||
placeholder={"Можно добавить текст"}
|
||||
text={question.content.text}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
onChange={({ target }) => setText(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@ -104,10 +103,10 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
setOpenImageModal(true);
|
||||
openImageUploadModal();
|
||||
}}
|
||||
onPlusClick={() => {
|
||||
setOpenImageModal(true);
|
||||
openImageUploadModal();
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -123,11 +122,7 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
Изображение
|
||||
</Typography>
|
||||
</Box>
|
||||
<UploadImageModal
|
||||
open={openImageModal}
|
||||
onClose={() => setOpenImageModal(false)}
|
||||
imgHC={handleImageUpload}
|
||||
/>
|
||||
<UploadImageModal imgHC={handleImageUpload} />
|
||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||
<Typography> или</Typography>
|
||||
<Box
|
||||
@ -230,15 +225,17 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
onClose={() => setOpenVideoModal(false)}
|
||||
video={question.content.video}
|
||||
onUpload={(url) => {
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: { ...question.content, video: url },
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "page") return;
|
||||
|
||||
question.content.video = url;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchPageOptions switchState={switchState} totalIndex={totalIndex} />
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||
<SwitchPageOptions switchState={switchState} question={question} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,97 +1,87 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
|
||||
|
||||
type SettingPageOptionsProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionPage;
|
||||
};
|
||||
|
||||
export default function SettingPageOptions({
|
||||
totalIndex,
|
||||
question,
|
||||
}: SettingPageOptionsProps) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionPage;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопроса
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", alignItems: "flex-start" }}>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
const setInnerName = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Внутреннее описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопроса
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", alignItems: "flex-start" }}>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = "";
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Внутреннее описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => setInnerName(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,27 +1,26 @@
|
||||
import { QuizQuestionPage } from "@model/questionTypes/page";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingPageOptions from "./SettingPageOptions";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionPage;
|
||||
}
|
||||
|
||||
export default function SwitchPageOptions({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingPageOptions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingPageOptions question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { createQuestion } from "@root/questions/actions";
|
||||
import { collapseAllQuestions, createQuestion } from "@root/questions/actions";
|
||||
import { incrementCurrentStep } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||
@ -21,15 +21,6 @@ export default function QuestionsPage() {
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||
const { quiz } = useCurrentQuiz();
|
||||
|
||||
const collapseEverything = () => { // TODO
|
||||
// listQuestions[quizId].forEach((item, index) => {
|
||||
// updateQuestionsList<AnyQuizQuestion>(quizId, index, {
|
||||
// ...item,
|
||||
// expanded: false,
|
||||
// });
|
||||
// });
|
||||
};
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
return (
|
||||
@ -53,7 +44,7 @@ export default function QuestionsPage() {
|
||||
color: theme.palette.brightPurple.main,
|
||||
textDecorationColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
onClick={collapseEverything}
|
||||
onClick={collapseAllQuestions}
|
||||
>
|
||||
Свернуть всё
|
||||
</Button>
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
Typography,
|
||||
TextField,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchRating from "./switchRating";
|
||||
|
||||
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
|
||||
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
|
||||
import HeartIcon from "../../../assets/icons/questionsPage/heartIcon";
|
||||
@ -19,260 +17,253 @@ import LikeIcon from "../../../assets/icons/questionsPage/likeIcon";
|
||||
import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon";
|
||||
import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon";
|
||||
import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini";
|
||||
|
||||
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionRating;
|
||||
}
|
||||
|
||||
export type ButtonRatingFrom = {
|
||||
name: "star" | "trophie" | "flag" | "heart" | "like" | "bubble" | "hashtag";
|
||||
icon: JSX.Element;
|
||||
name: "star" | "trophie" | "flag" | "heart" | "like" | "bubble" | "hashtag";
|
||||
icon: JSX.Element;
|
||||
};
|
||||
|
||||
export default function RatingOptions({ totalIndex }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [negativeText, setNegativeText] = useState<string>("");
|
||||
const [positiveText, setPositiveText] = useState<string>("");
|
||||
const [negativeTextWidth, setNegativeTextWidth] = useState<number>(0);
|
||||
const [positiveTextWidth, setPositiveTextWidth] = useState<number>(0);
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionRating;
|
||||
const negativeRef = useRef<HTMLDivElement>(null);
|
||||
const positiveRef = useRef<HTMLDivElement>(null);
|
||||
const debounceNegativeDescription = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionRating>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
ratingNegativeDescription: value.substring(0, 15),
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
const debouncePositiveDescription = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionRating>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
ratingPositiveDescription: value.substring(0, 15),
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
export default function RatingOptions({ question }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [negativeText, setNegativeText] = useState<string>("");
|
||||
const [positiveText, setPositiveText] = useState<string>("");
|
||||
const [negativeTextWidth, setNegativeTextWidth] = useState<number>(0);
|
||||
const [positiveTextWidth, setPositiveTextWidth] = useState<number>(0);
|
||||
const quizId = Number(useParams().quizId);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const negativeRef = useRef<HTMLDivElement>(null);
|
||||
const positiveRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setNegativeText(question.content.ratingNegativeDescription);
|
||||
setPositiveText(question.content.ratingPositiveDescription);
|
||||
}, []);
|
||||
const debounceNegativeDescription = useDebouncedCallback((value) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "rating") return;
|
||||
|
||||
useEffect(() => {
|
||||
setNegativeTextWidth(negativeRef.current?.offsetWidth || 0);
|
||||
}, [negativeText]);
|
||||
question.content.ratingNegativeDescription = value.substring(0, 15);
|
||||
});
|
||||
}, 500);
|
||||
const debouncePositiveDescription = useDebouncedCallback((value) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "rating") return;
|
||||
|
||||
useEffect(() => {
|
||||
setPositiveTextWidth(positiveRef.current?.offsetWidth || 0);
|
||||
}, [positiveText]);
|
||||
question.content.ratingPositiveDescription = value.substring(0, 15);
|
||||
});
|
||||
}, 500);
|
||||
|
||||
const buttonRatingForm: ButtonRatingFrom[] = [
|
||||
{
|
||||
name: "star",
|
||||
icon: <StarIconMini width={"50px"} color={theme.palette.grey2.main} />,
|
||||
},
|
||||
{ name: "trophie", icon: <TropfyIcon color={theme.palette.grey2.main} /> },
|
||||
{ name: "flag", icon: <FlagIcon color={theme.palette.grey2.main} /> },
|
||||
{ name: "heart", icon: <HeartIcon color={theme.palette.grey2.main} /> },
|
||||
{ name: "like", icon: <LikeIcon color={theme.palette.grey2.main} /> },
|
||||
{
|
||||
name: "bubble",
|
||||
icon: <LightbulbIcon color={theme.palette.grey2.main} />,
|
||||
},
|
||||
{ name: "hashtag", icon: <HashtagIcon color={theme.palette.grey2.main} /> },
|
||||
];
|
||||
useEffect(() => {
|
||||
setNegativeText(question.content.ratingNegativeDescription);
|
||||
setPositiveText(question.content.ratingPositiveDescription);
|
||||
}, []);
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
useEffect(() => {
|
||||
setNegativeTextWidth(negativeRef.current?.offsetWidth || 0);
|
||||
}, [negativeText]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
px: "20px",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
marginTop: isMobile ? "20px" : 0,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "auto" : `${question.content.steps * 44}px`,
|
||||
maxWidth: "440px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
padding: "0 10px",
|
||||
gap: isMobile ? "10px" : "15px",
|
||||
}}
|
||||
>
|
||||
{Array.from(
|
||||
{ length: question.content.steps },
|
||||
(_, index) => index
|
||||
).map((itemNumber) => (
|
||||
useEffect(() => {
|
||||
setPositiveTextWidth(positiveRef.current?.offsetWidth || 0);
|
||||
}, [positiveText]);
|
||||
|
||||
const buttonRatingForm: ButtonRatingFrom[] = [
|
||||
{
|
||||
name: "star",
|
||||
icon: <StarIconMini width={"50px"} color={theme.palette.grey2.main} />,
|
||||
},
|
||||
{ name: "trophie", icon: <TropfyIcon color={theme.palette.grey2.main} /> },
|
||||
{ name: "flag", icon: <FlagIcon color={theme.palette.grey2.main} /> },
|
||||
{ name: "heart", icon: <HeartIcon color={theme.palette.grey2.main} /> },
|
||||
{ name: "like", icon: <LikeIcon color={theme.palette.grey2.main} /> },
|
||||
{
|
||||
name: "bubble",
|
||||
icon: <LightbulbIcon color={theme.palette.grey2.main} />,
|
||||
},
|
||||
{ name: "hashtag", icon: <HashtagIcon color={theme.palette.grey2.main} /> },
|
||||
];
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
key={itemNumber}
|
||||
{...(itemNumber === 0 || itemNumber === question.content.steps - 1
|
||||
? {
|
||||
onClick: () => {
|
||||
updateQuestionsList<QuizQuestionRating>(
|
||||
quizId,
|
||||
totalIndex,
|
||||
{
|
||||
content: {
|
||||
...question.content,
|
||||
ratingExpanded: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
sx: {
|
||||
cursor: "pointer",
|
||||
transform: "scale(1.5)",
|
||||
":hover": {
|
||||
transform: "scale(1.7)",
|
||||
transition: "0.2s",
|
||||
},
|
||||
},
|
||||
}
|
||||
: { sx: { transform: "scale(1.5)" } })}
|
||||
sx={{
|
||||
display: "flex",
|
||||
px: "20px",
|
||||
flexDirection: "column",
|
||||
gap: "20px",
|
||||
marginTop: isMobile ? "20px" : 0,
|
||||
}}
|
||||
>
|
||||
{
|
||||
buttonRatingForm.find(
|
||||
({ name }) => question.content.form === name
|
||||
)?.icon
|
||||
}
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "auto" : `${question.content.steps * 44}px`,
|
||||
maxWidth: "440px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
padding: "0 10px",
|
||||
gap: isMobile ? "10px" : "15px",
|
||||
}}
|
||||
>
|
||||
{Array.from(
|
||||
{ length: question.content.steps },
|
||||
(_, index) => index
|
||||
).map((itemNumber) => (
|
||||
<Box
|
||||
key={itemNumber}
|
||||
{...(itemNumber === 0 || itemNumber === question.content.steps - 1
|
||||
? {
|
||||
onClick: () => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "rating") return;
|
||||
|
||||
question.content.ratingExpanded = true;
|
||||
});
|
||||
},
|
||||
sx: {
|
||||
cursor: "pointer",
|
||||
transform: "scale(1.5)",
|
||||
":hover": {
|
||||
transform: "scale(1.7)",
|
||||
transition: "0.2s",
|
||||
},
|
||||
},
|
||||
}
|
||||
: { sx: { transform: "scale(1.5)" } })}
|
||||
>
|
||||
{
|
||||
buttonRatingForm.find(
|
||||
({ name }) => question.content.form === name
|
||||
)?.icon
|
||||
}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mb: "20px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
maxWidth: "410px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ minWidth: isMobile ? "140px" : "205px" }}>
|
||||
<Typography
|
||||
ref={negativeRef}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: "-100",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{negativeText}
|
||||
</Typography>
|
||||
<TextField
|
||||
defaultValue={question.content.ratingNegativeDescription}
|
||||
value={negativeText}
|
||||
placeholder="Негативно"
|
||||
onChange={({ target }) => {
|
||||
if (target.value.length <= 15) {
|
||||
setNegativeText(target.value);
|
||||
debounceNegativeDescription(target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={({ target }) => debounceNegativeDescription(target.value)}
|
||||
sx={{
|
||||
width: negativeTextWidth + 10 + "px",
|
||||
maxWidth: isMobile ? "140px" : "230px",
|
||||
background: "transparent",
|
||||
fontSize: "18px",
|
||||
minWidth: "95px",
|
||||
transition: "0.2s",
|
||||
"& .MuiInputBase-root": {
|
||||
"& .MuiInputBase-input": {
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
padding: "0 3px",
|
||||
borderRadius: "3px",
|
||||
border: "1px solid",
|
||||
borderColor: "transparent",
|
||||
"&:hover, &:focus": {
|
||||
borderColor: theme.palette.grey2.main,
|
||||
},
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
outline: "none",
|
||||
border: "none",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ minWidth: isMobile ? "140px" : "205px" }}>
|
||||
<Typography
|
||||
ref={positiveRef}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: "-100",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{positiveText}
|
||||
</Typography>
|
||||
<TextField
|
||||
value={positiveText}
|
||||
placeholder="Позитивно"
|
||||
onChange={({ target }) => {
|
||||
if (target.value.length <= 15) {
|
||||
setPositiveText(target.value);
|
||||
debouncePositiveDescription(target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={({ target }) => debouncePositiveDescription(target.value)}
|
||||
sx={{
|
||||
width: positiveTextWidth + 10 + "px",
|
||||
maxWidth: isMobile ? "140px" : "230px",
|
||||
background: "transparent",
|
||||
fontSize: "18px",
|
||||
minWidth: "95px",
|
||||
transition: "0.2s",
|
||||
"& .MuiInputBase-root": {
|
||||
"& .MuiInputBase-input": {
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
padding: "0 3px",
|
||||
borderRadius: "3px",
|
||||
border: "1px solid",
|
||||
borderColor: "transparent",
|
||||
"&:hover, &:focus": {
|
||||
borderColor: theme.palette.grey2.main,
|
||||
},
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
outline: "none",
|
||||
border: "none",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mb: "20px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
maxWidth: "410px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ minWidth: isMobile ? "140px" : "205px" }}>
|
||||
<Typography
|
||||
ref={negativeRef}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: "-100",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{negativeText}
|
||||
</Typography>
|
||||
<TextField
|
||||
defaultValue={question.content.ratingNegativeDescription}
|
||||
value={negativeText}
|
||||
placeholder="Негативно"
|
||||
onChange={({ target }) => {
|
||||
if (target.value.length <= 15) {
|
||||
setNegativeText(target.value);
|
||||
debounceNegativeDescription(target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={({ target }) => debounceNegativeDescription(target.value)}
|
||||
sx={{
|
||||
width: negativeTextWidth + 10 + "px",
|
||||
maxWidth: isMobile ? "140px" : "230px",
|
||||
background: "transparent",
|
||||
fontSize: "18px",
|
||||
minWidth: "95px",
|
||||
transition: "0.2s",
|
||||
"& .MuiInputBase-root": {
|
||||
"& .MuiInputBase-input": {
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
padding: "0 3px",
|
||||
borderRadius: "3px",
|
||||
border: "1px solid",
|
||||
borderColor: "transparent",
|
||||
"&:hover, &:focus": {
|
||||
borderColor: theme.palette.grey2.main,
|
||||
},
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
outline: "none",
|
||||
border: "none",
|
||||
},
|
||||
},
|
||||
}}
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
question={question}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ minWidth: isMobile ? "140px" : "205px" }}>
|
||||
<Typography
|
||||
ref={positiveRef}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: "-100",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{positiveText}
|
||||
</Typography>
|
||||
<TextField
|
||||
value={positiveText}
|
||||
placeholder="Позитивно"
|
||||
onChange={({ target }) => {
|
||||
if (target.value.length <= 15) {
|
||||
setPositiveText(target.value);
|
||||
debouncePositiveDescription(target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={({ target }) => debouncePositiveDescription(target.value)}
|
||||
sx={{
|
||||
width: positiveTextWidth + 10 + "px",
|
||||
maxWidth: isMobile ? "140px" : "230px",
|
||||
background: "transparent",
|
||||
fontSize: "18px",
|
||||
minWidth: "95px",
|
||||
transition: "0.2s",
|
||||
"& .MuiInputBase-root": {
|
||||
"& .MuiInputBase-input": {
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
padding: "0 3px",
|
||||
borderRadius: "3px",
|
||||
border: "1px solid",
|
||||
borderColor: "transparent",
|
||||
"&:hover, &:focus": {
|
||||
borderColor: theme.palette.grey2.main,
|
||||
},
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
outline: "none",
|
||||
border: "none",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchRating switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
<SwitchRating switchState={switchState} question={question} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,198 +1,196 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, ButtonBase, Slider, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { QuizQuestionRating } from "@model/questionTypes/rating";
|
||||
import { Box, ButtonBase, Slider, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
|
||||
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
|
||||
import HeartIcon from "../../../assets/icons/questionsPage/heartIcon";
|
||||
import LikeIcon from "../../../assets/icons/questionsPage/likeIcon";
|
||||
import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon";
|
||||
import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon";
|
||||
import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini";
|
||||
|
||||
import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon";
|
||||
import HeartIcon from "../../../assets/icons/questionsPage/heartIcon";
|
||||
import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon";
|
||||
import LikeIcon from "../../../assets/icons/questionsPage/likeIcon";
|
||||
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
|
||||
import type { ButtonRatingFrom } from "./RatingOptions";
|
||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||
|
||||
|
||||
type SettingSliderProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionRating;
|
||||
};
|
||||
|
||||
export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const theme = useTheme();
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const { listQuestions } = questionStore();
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionNumber;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
export default function SettingSlider({ question }: SettingSliderProps) {
|
||||
const theme = useTheme();
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
|
||||
const buttonRatingForm: ButtonRatingFrom[] = [
|
||||
{ name: "star", icon: <StarIconMini color={theme.palette.grey3.main} /> },
|
||||
{ name: "trophie", icon: <TropfyIcon color={theme.palette.grey3.main} /> },
|
||||
{ name: "flag", icon: <FlagIcon color={theme.palette.grey3.main} /> },
|
||||
{ name: "heart", icon: <HeartIcon color={theme.palette.grey3.main} /> },
|
||||
{ name: "like", icon: <LikeIcon color={theme.palette.grey3.main} /> },
|
||||
{
|
||||
name: "bubble",
|
||||
icon: <LightbulbIcon color={theme.palette.grey3.main} />,
|
||||
},
|
||||
{ name: "hashtag", icon: <HashtagIcon color={theme.palette.grey3.main} /> },
|
||||
];
|
||||
const setInnerName = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
maxWidth: "340px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки рейтинга
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Форма
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", marginBottom: "15px" }}>
|
||||
{buttonRatingForm.map(({ name, icon }, index) => (
|
||||
<ButtonBase
|
||||
key={index}
|
||||
onClick={() => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, form: name },
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
question.content.form === name
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
question.content.form === name
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main,
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</ButtonBase>
|
||||
))}
|
||||
</Box>
|
||||
const buttonRatingForm: ButtonRatingFrom[] = [
|
||||
{ name: "star", icon: <StarIconMini color={theme.palette.grey3.main} /> },
|
||||
{ name: "trophie", icon: <TropfyIcon color={theme.palette.grey3.main} /> },
|
||||
{ name: "flag", icon: <FlagIcon color={theme.palette.grey3.main} /> },
|
||||
{ name: "heart", icon: <HeartIcon color={theme.palette.grey3.main} /> },
|
||||
{ name: "like", icon: <LikeIcon color={theme.palette.grey3.main} /> },
|
||||
{
|
||||
name: "bubble",
|
||||
icon: <LightbulbIcon color={theme.palette.grey3.main} />,
|
||||
},
|
||||
{ name: "hashtag", icon: <HashtagIcon color={theme.palette.grey3.main} /> },
|
||||
];
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Количество
|
||||
</Typography>
|
||||
<Slider
|
||||
value={question.content.steps}
|
||||
min={2}
|
||||
max={10}
|
||||
aria-label="Default"
|
||||
valueLabelDisplay="auto"
|
||||
sx={{ color: theme.palette.brightPurple.main, padding: "0" }}
|
||||
onChange={(_, value) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, steps: Number(value) || 1 },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isMobile ? "20px" : "34px") : "40px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
maxWidth: "340px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки рейтинга
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Форма
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", marginBottom: "15px" }}>
|
||||
{buttonRatingForm.map(({ name, icon }, index) => (
|
||||
<ButtonBase
|
||||
key={index}
|
||||
onClick={() => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "rating") return;
|
||||
|
||||
question.content.form = name;
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
question.content.form === name
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
question.content.form === name
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main,
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</ButtonBase>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Количество
|
||||
</Typography>
|
||||
<Slider
|
||||
value={question.content.steps}
|
||||
min={2}
|
||||
max={10}
|
||||
aria-label="Default"
|
||||
valueLabelDisplay="auto"
|
||||
sx={{ color: theme.palette.brightPurple.main, padding: "0" }}
|
||||
onChange={(_, value) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "rating") return;
|
||||
|
||||
question.content.steps = Number(value) || 1;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isMobile ? "20px" : "34px") : "40px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "rating") return;
|
||||
|
||||
question.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "rating") return;
|
||||
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => setInnerName(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
@ -1,27 +1,26 @@
|
||||
import { QuizQuestionRating } from "@model/questionTypes/rating";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingRating from "./settingRating";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionRating;
|
||||
}
|
||||
|
||||
export default function SwitchRating({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingRating totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingRating question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -1,226 +1,222 @@
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import CustomNumberField from "@ui_kit/CustomNumberField";
|
||||
import SwitchSlider from "./switchSlider";
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionNumber;
|
||||
}
|
||||
|
||||
export default function SliderOptions({ totalIndex }: Props) {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [stepError, setStepError] = useState("");
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionNumber;
|
||||
export default function SliderOptions({ question }: Props) {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [stepError, setStepError] = useState("");
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: isTablet ? "auto" : "100%",
|
||||
maxWidth: "673.8px",
|
||||
display: "flex",
|
||||
pl: "20px",
|
||||
pr: isMobile ? "13px" : "20px",
|
||||
pb: isMobile ? "30px" : "20px",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "25px" : "20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
gap: isMobile ? "10px" : "14px",
|
||||
mt: isMobile ? "25px" : "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginRight: isMobile ? "10px" : "0px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D" }}>
|
||||
Выбор значения из диапазона
|
||||
</Typography>
|
||||
<Box sx={{ width: "100%", display: "flex", alignItems: "center", gap: isMobile ? "9px" : "20px" }}>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
placeholder={"0"}
|
||||
min={0}
|
||||
max={99}
|
||||
value={question.content.range.split("—")[0]}
|
||||
onChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
range: `${target.value}—${
|
||||
question.content.range.split("—")[1]
|
||||
}`,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
const start = question.content.start;
|
||||
const min = Number(target.value);
|
||||
const max = Number(question.content.range.split("—")[1]);
|
||||
|
||||
if (min >= max) {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
range: `${max - 1 >= 0 ? max - 1 : 0}—${
|
||||
question.content.range.split("—")[1]
|
||||
}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (start < min) {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, start: min },
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Typography>—</Typography>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
placeholder={"100"}
|
||||
min={0}
|
||||
max={100}
|
||||
value={question.content.range.split("—")[1]}
|
||||
onChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
range: `${question.content.range.split("—")[0]}—${
|
||||
target.value
|
||||
}`,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
const start = question.content.start;
|
||||
const step = question.content.step;
|
||||
const min = Number(question.content.range.split("—")[0]);
|
||||
const max = Number(target.value);
|
||||
const range = max - min;
|
||||
|
||||
if (max <= min) {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
range: `${question.content.range.split("—")[0]}—${
|
||||
min + 1 >= 100 ? 100 : min + 1
|
||||
}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (start > max) {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, start: max },
|
||||
});
|
||||
}
|
||||
|
||||
if (step > max) {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, step: max },
|
||||
});
|
||||
|
||||
if (range % step) {
|
||||
setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`);
|
||||
} else {
|
||||
setStepError("");
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isMobile ? "column-reverse" : "",
|
||||
gap: isMobile ? "15px" : "50px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D", mb: isMobile ? "10px" : "14px" }}>
|
||||
Начальное значение
|
||||
</Typography>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
placeholder={"50"}
|
||||
min={Number(question.content.range.split("—")[0])}
|
||||
max={Number(question.content.range.split("—")[1])}
|
||||
value={String(question.content.start)}
|
||||
onChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, start: Number(target.value) },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: "#4D4D4D",
|
||||
mb: "10px",
|
||||
}}
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: isTablet ? "auto" : "100%",
|
||||
maxWidth: "673.8px",
|
||||
display: "flex",
|
||||
pl: "20px",
|
||||
pr: isMobile ? "13px" : "20px",
|
||||
pb: isMobile ? "30px" : "20px",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "25px" : "20px",
|
||||
}}
|
||||
>
|
||||
Шаг
|
||||
</Typography>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
min={0}
|
||||
max={100}
|
||||
placeholder={"1"}
|
||||
error={stepError}
|
||||
value={String(question.content.step)}
|
||||
onChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, step: Number(target.value) },
|
||||
});
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
const min = Number(question.content.range.split("—")[0]);
|
||||
const max = Number(question.content.range.split("—")[1]);
|
||||
const range = max - min;
|
||||
const step = Number(target.value);
|
||||
<Box
|
||||
sx={{
|
||||
gap: isMobile ? "10px" : "14px",
|
||||
mt: isMobile ? "25px" : "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginRight: isMobile ? "10px" : "0px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D" }}>
|
||||
Выбор значения из диапазона
|
||||
</Typography>
|
||||
<Box sx={{ width: "100%", display: "flex", alignItems: "center", gap: isMobile ? "9px" : "20px" }}>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
placeholder={"0"}
|
||||
min={0}
|
||||
max={99}
|
||||
value={question.content.range.split("—")[0]}
|
||||
onChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
if (step > max) {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, step: max },
|
||||
});
|
||||
}
|
||||
question.content.range = `${target.value}—${question.content.range.split("—")[1]}`;
|
||||
});
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
const start = question.content.start;
|
||||
const min = Number(target.value);
|
||||
const max = Number(question.content.range.split("—")[1]);
|
||||
|
||||
if (range % step) {
|
||||
setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`);
|
||||
} else {
|
||||
setStepError("");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchSlider switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
if (min >= max) {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${question.content.range.split("—")[1]}`;
|
||||
});
|
||||
}
|
||||
|
||||
if (start < min) {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.start = min;
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Typography>—</Typography>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
placeholder={"100"}
|
||||
min={0}
|
||||
max={100}
|
||||
value={question.content.range.split("—")[1]}
|
||||
onChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.range = `${question.content.range.split("—")[0]}—${target.value}`;
|
||||
});
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
const start = question.content.start;
|
||||
const step = question.content.step;
|
||||
const min = Number(question.content.range.split("—")[0]);
|
||||
const max = Number(target.value);
|
||||
const range = max - min;
|
||||
|
||||
if (max <= min) {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.range = `${min}—${min + 1 >= 100 ? 100 : min + 1}`;
|
||||
});
|
||||
}
|
||||
|
||||
if (start > max) {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.start = max;
|
||||
});
|
||||
}
|
||||
|
||||
if (step > max) {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.step = min;
|
||||
});
|
||||
|
||||
if (range % step) {
|
||||
setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`);
|
||||
} else {
|
||||
setStepError("");
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isMobile ? "column-reverse" : "",
|
||||
gap: isMobile ? "15px" : "50px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D", mb: isMobile ? "10px" : "14px" }}>
|
||||
Начальное значение
|
||||
</Typography>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
placeholder={"50"}
|
||||
min={Number(question.content.range.split("—")[0])}
|
||||
max={Number(question.content.range.split("—")[1])}
|
||||
value={String(question.content.start)}
|
||||
onChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.start = Number(target.value);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: "#4D4D4D",
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
Шаг
|
||||
</Typography>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
min={0}
|
||||
max={100}
|
||||
placeholder={"1"}
|
||||
error={stepError}
|
||||
value={String(question.content.step)}
|
||||
onChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.step = Number(target.value);
|
||||
});
|
||||
}}
|
||||
onBlur={({ target }) => {
|
||||
const min = Number(question.content.range.split("—")[0]);
|
||||
const max = Number(question.content.range.split("—")[1]);
|
||||
const range = max - min;
|
||||
const step = Number(target.value);
|
||||
|
||||
if (step > max) {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.step = max;
|
||||
});
|
||||
}
|
||||
|
||||
if (range % step) {
|
||||
setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`);
|
||||
} else {
|
||||
setStepError("");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||
<SwitchSlider switchState={switchState} question={question} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,132 +1,130 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||
|
||||
|
||||
type SettingSliderProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionNumber;
|
||||
};
|
||||
|
||||
export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionNumber;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
export default function SettingSlider({ question }: SettingSliderProps) {
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "30px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки ползунка
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "auto",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Выбор диапозона (два ползунка)"}
|
||||
checked={question.content.chooseRange}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, chooseRange: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "0px") : "0px",
|
||||
pr: isFigmaTablte ? (isMobile ? "20px" : "12px") : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px", alignItems: isMobile ? "flex-end" : "center" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
const setInnerName = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "auto",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "30px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки ползунка
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "auto",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Выбор диапозона (два ползунка)"}
|
||||
checked={question.content.chooseRange}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.chooseRange = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "0px") : "0px",
|
||||
pr: isFigmaTablte ? (isMobile ? "20px" : "12px") : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px", alignItems: isMobile ? "flex-end" : "center" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "auto",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => setInnerName(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
@ -1,28 +1,26 @@
|
||||
import * as React from "react";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import { QuizQuestionNumber } from "@model/questionTypes/number";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingSlider from "./settingSlider";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionNumber;
|
||||
}
|
||||
|
||||
export default function SwitchSlider({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingSlider totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingSlider question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -1,202 +1,199 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Typography,
|
||||
Tooltip,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
FormControl,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import { useEffect, useState } from "react";
|
||||
import ArrowDown from "../../../assets/icons/ArrowDownIcon";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import type {
|
||||
QuizQuestionFile,
|
||||
UploadFileType,
|
||||
} from "../../../model/questionTypes/file";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchUpload from "./switchUpload";
|
||||
|
||||
import type { AnyQuizQuestion } from "../../../model/questionTypes/shared";
|
||||
import type {
|
||||
QuizQuestionFile,
|
||||
UploadFileType,
|
||||
} from "../../../model/questionTypes/file";
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
}
|
||||
|
||||
type DesignItem = {
|
||||
name: string;
|
||||
value: UploadFileType;
|
||||
name: string;
|
||||
value: UploadFileType;
|
||||
};
|
||||
|
||||
const DESIGN_TYPES: DesignItem[] = [
|
||||
{ name: "Все типы файлов", value: "all" },
|
||||
{ name: "Изображения", value: "picture" },
|
||||
{ name: "Видео", value: "video" },
|
||||
{ name: "Аудио", value: "audio" },
|
||||
{ name: "Документ", value: "document" },
|
||||
{ name: "Все типы файлов", value: "all" },
|
||||
{ name: "Изображения", value: "picture" },
|
||||
{ name: "Видео", value: "video" },
|
||||
{ name: "Аудио", value: "audio" },
|
||||
{ name: "Документ", value: "document" },
|
||||
];
|
||||
|
||||
export default function UploadFile({ totalIndex }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionFile;
|
||||
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
const handleChange = ({ target }: SelectChangeEvent) => {
|
||||
updateQuestionsList<AnyQuizQuestion>(quizId, totalIndex, {
|
||||
content: { ...question.content, type: target.value as UploadFileType },
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const isTypeSetted = DESIGN_TYPES.find(
|
||||
({ value }) => value === question.content.type
|
||||
);
|
||||
|
||||
if (!isTypeSetted) {
|
||||
updateQuestionsList<AnyQuizQuestion>(quizId, totalIndex, {
|
||||
content: { ...question.content, type: DESIGN_TYPES[0].value },
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: isTablet ? "auto" : "100%",
|
||||
maxWidth: "640px",
|
||||
display: "flex",
|
||||
px: "20px",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ gap: "10px", display: "flex", mt: isMobile ? "15px" : "0px" }}>
|
||||
<FormControl
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{
|
||||
width: "100%",
|
||||
minWidth: "200px",
|
||||
height: "48px",
|
||||
maxWidth: isFigmaTablet ? "551px" : "640px",
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
id="category-select"
|
||||
variant="outlined"
|
||||
value={question.content.type}
|
||||
displayEmpty
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
height: "48px",
|
||||
borderRadius: "8px",
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: `1px solid ${theme.palette.brightPurple.main} !important`,
|
||||
},
|
||||
}}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
mt: "8px",
|
||||
p: "4px",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #EEE4FC",
|
||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||
},
|
||||
},
|
||||
MenuListProps: {
|
||||
sx: {
|
||||
py: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
"& .Mui-selected": {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.brightPurple.main,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
height: "48px",
|
||||
color: theme.palette.brightPurple.main,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
px: "9px",
|
||||
gap: "20px",
|
||||
},
|
||||
}}
|
||||
IconComponent={(props) => <ArrowDown {...props} />}
|
||||
>
|
||||
{DESIGN_TYPES.map(({ name, value }) => (
|
||||
<MenuItem
|
||||
key={value}
|
||||
value={value}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
p: "4px",
|
||||
borderRadius: "5px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
marginBottom: "20px",
|
||||
marginTop: "15px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "18.96px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
Пользователь может загрузить любой собственный файл
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title="Можно загрузить файл в желаемом формате."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchUpload switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
interface Props {
|
||||
question: QuizQuestionFile;
|
||||
}
|
||||
|
||||
export default function UploadFile({ question }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
const handleChange = ({ target }: SelectChangeEvent) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "file") return;
|
||||
|
||||
question.content.type = target.value as UploadFileType;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const isTypeSetted = DESIGN_TYPES.find(
|
||||
({ value }) => value === question.content.type
|
||||
);
|
||||
|
||||
if (!isTypeSetted) {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
if (question.type !== "file") return;
|
||||
|
||||
question.content.type = DESIGN_TYPES[0].value;
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: isTablet ? "auto" : "100%",
|
||||
maxWidth: "640px",
|
||||
display: "flex",
|
||||
px: "20px",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ gap: "10px", display: "flex", mt: isMobile ? "15px" : "0px" }}>
|
||||
<FormControl
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{
|
||||
width: "100%",
|
||||
minWidth: "200px",
|
||||
height: "48px",
|
||||
maxWidth: isFigmaTablet ? "551px" : "640px",
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
id="category-select"
|
||||
variant="outlined"
|
||||
value={question.content.type}
|
||||
displayEmpty
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
height: "48px",
|
||||
borderRadius: "8px",
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: `1px solid ${theme.palette.brightPurple.main} !important`,
|
||||
},
|
||||
}}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
mt: "8px",
|
||||
p: "4px",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #EEE4FC",
|
||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||
},
|
||||
},
|
||||
MenuListProps: {
|
||||
sx: {
|
||||
py: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
"& .Mui-selected": {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.brightPurple.main,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
height: "48px",
|
||||
color: theme.palette.brightPurple.main,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
px: "9px",
|
||||
gap: "20px",
|
||||
},
|
||||
}}
|
||||
IconComponent={(props) => <ArrowDown {...props} />}
|
||||
>
|
||||
{DESIGN_TYPES.map(({ name, value }) => (
|
||||
<MenuItem
|
||||
key={value}
|
||||
value={value}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
p: "4px",
|
||||
borderRadius: "5px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
marginBottom: "20px",
|
||||
marginTop: "15px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
fontSize: "16px",
|
||||
lineHeight: "18.96px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
Пользователь может загрузить любой собственный файл
|
||||
</Typography>
|
||||
<Tooltip
|
||||
title="Можно загрузить файл в желаемом формате."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
question={question}
|
||||
/>
|
||||
<SwitchUpload switchState={switchState} question={question} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,120 +1,110 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Tooltip,
|
||||
Box,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
||||
|
||||
|
||||
type SettingsUploadProps = {
|
||||
totalIndex: number;
|
||||
question: QuizQuestionFile;
|
||||
};
|
||||
|
||||
export default function SettingsUpload({ totalIndex }: SettingsUploadProps) {
|
||||
const theme = useTheme();
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionFile;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
export default function SettingsUpload({ question }: SettingsUploadProps) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: "20px",
|
||||
pl: "20px",
|
||||
pr: isMobile ? "20px" : "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography>Настройки вопроса</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Автозаполнение адреса"}
|
||||
checked={question.content.autofill}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
||||
content: { ...question.content, autofill: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
const setInnerName = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: "20px",
|
||||
pl: "20px",
|
||||
pr: isMobile ? "20px" : "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
sx={{ paddingRight: "20px" }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
<Typography>Настройки вопроса</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Автозаполнение адреса"}
|
||||
checked={question.content.autofill}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.autofill = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => setInnerName(target.value)}
|
||||
sx={{ paddingRight: "20px" }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -1,28 +1,26 @@
|
||||
import * as React from "react";
|
||||
import { QuizQuestionFile } from "@model/questionTypes/file";
|
||||
import BranchingQuestions from "../branchingQuestions";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingsUpload from "./settingUpload";
|
||||
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
question: QuizQuestionFile;
|
||||
}
|
||||
|
||||
export default function SwitchUpload({
|
||||
switchState = "setting",
|
||||
totalIndex,
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingsUpload totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "help":
|
||||
return <HelpQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
break;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingsUpload question={question} />;
|
||||
case "help":
|
||||
return <HelpQuestions question={question} />;
|
||||
case "branching":
|
||||
return <BranchingQuestions question={question} />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -14,19 +14,17 @@ import * as React from "react";
|
||||
import UnsplashIcon from "../../../assets/icons/Unsplash.svg";
|
||||
|
||||
import type { DragEvent } from "react";
|
||||
import { closeImageUploadModal, useImageUploadModalStore } from "@root/imageUploadModal";
|
||||
|
||||
interface ModalkaProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
imgHC: (imgInp: FileList | null) => void;
|
||||
}
|
||||
|
||||
export const UploadImageModal: React.FC<ModalkaProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
imgHC,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isOpen = useImageUploadModalStore(state => state.isOpen)
|
||||
|
||||
const dropZone = React.useRef<HTMLDivElement>(null);
|
||||
const [ready, setReady] = React.useState(false);
|
||||
@ -45,8 +43,8 @@ export const UploadImageModal: React.FC<ModalkaProps> = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
open={isOpen}
|
||||
onClose={closeImageUploadModal}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
|
@ -1,21 +1,21 @@
|
||||
import { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
import { AnyQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { Box, ButtonBase, Typography, useTheme } from "@mui/material";
|
||||
import { openCropModal } from "@root/cropModal";
|
||||
import { closeImageUploadModal, openImageUploadModal } from "@root/imageUploadModal";
|
||||
import { setQuestionBackgroundImage, setQuestionOriginalBackgroundImage } from "@root/questions/actions";
|
||||
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||
import UploadBox from "@ui_kit/UploadBox";
|
||||
import { useState, type DragEvent } from "react";
|
||||
import { type DragEvent } from "react";
|
||||
import UploadIcon from "../../../assets/icons/UploadIcon";
|
||||
import { UploadImageModal } from "./UploadImageModal";
|
||||
|
||||
|
||||
type UploadImageProps = {
|
||||
question: QuizQuestionVariant;
|
||||
question: AnyQuizQuestion;
|
||||
};
|
||||
|
||||
export default function UploadImage({ question }: UploadImageProps) {
|
||||
const theme = useTheme();
|
||||
const [isUploadImageModalOpen, setIsUploadImageModalOpen] = useState(false);
|
||||
|
||||
const handleImageUpload = (files: FileList | null) => {
|
||||
if (!files?.length) return;
|
||||
@ -26,7 +26,7 @@ export default function UploadImage({ question }: UploadImageProps) {
|
||||
|
||||
setQuestionBackgroundImage(question.id, url);
|
||||
setQuestionOriginalBackgroundImage(question.id, url);
|
||||
setIsUploadImageModalOpen(false);
|
||||
closeImageUploadModal();
|
||||
openCropModal(url, url);
|
||||
};
|
||||
|
||||
@ -54,7 +54,7 @@ export default function UploadImage({ question }: UploadImageProps) {
|
||||
Загрузить изображение
|
||||
</Typography>
|
||||
<ButtonBase
|
||||
onClick={() => setIsUploadImageModalOpen(true)}
|
||||
onClick={openImageUploadModal}
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: "260px",
|
||||
@ -81,11 +81,7 @@ export default function UploadImage({ question }: UploadImageProps) {
|
||||
/>
|
||||
}
|
||||
</ButtonBase>
|
||||
<UploadImageModal
|
||||
open={isUploadImageModalOpen}
|
||||
onClose={() => setIsUploadImageModalOpen(false)}
|
||||
imgHC={handleImageUpload}
|
||||
/>
|
||||
<UploadImageModal imgHC={handleImageUpload} />
|
||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||
</Box>
|
||||
);
|
||||
|
@ -37,10 +37,7 @@ export default function AnswerOptions({ question }: Props) {
|
||||
Добавьте ответ
|
||||
</Typography>
|
||||
) : (
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
question={question}
|
||||
/>
|
||||
<AnswerDraggableList question={question} />
|
||||
)}
|
||||
|
||||
<Box
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
@ -25,9 +25,7 @@ export default function ResponseSettings({ question }: Props) {
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const updateQuestionInnerName = useDebouncedCallback((value) => {
|
||||
updateQuestionWithFnOptimistic(question.id, question => {
|
||||
question.content.innerName = value;
|
||||
});
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import InfoIcon from "@icons/Info";
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
import { AnyQuizQuestion } from "@model/questionTypes/shared";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -24,7 +24,7 @@ import { Select } from "./Select";
|
||||
|
||||
|
||||
type BranchingQuestionsProps = {
|
||||
question: QuizQuestionVariant;
|
||||
question: AnyQuizQuestion;
|
||||
};
|
||||
|
||||
const ACTIONS = ["Показать", "Скрыть"];
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
import { AnyQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { Box, ButtonBase, Typography } from "@mui/material";
|
||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
@ -13,7 +13,7 @@ import { UploadVideoModal } from "./UploadVideoModal";
|
||||
type BackgroundType = "text" | "video";
|
||||
|
||||
type HelpQuestionsProps = {
|
||||
question: QuizQuestionVariant;
|
||||
question: AnyQuizQuestion;
|
||||
};
|
||||
|
||||
export default function HelpQuestions({ question }: HelpQuestionsProps) {
|
||||
|
@ -46,7 +46,8 @@ export default function SwitchResult({
|
||||
return <ResponseSettings />;
|
||||
break;
|
||||
case "branching":
|
||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
||||
// return <BranchingQuestions question={question} />;
|
||||
return null
|
||||
break;
|
||||
case "points":
|
||||
return <PointsQuestions />;
|
||||
|
29
src/stores/imageUploadModal.ts
Normal file
29
src/stores/imageUploadModal.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { create } from "zustand";
|
||||
import { devtools, persist } from "zustand/middleware";
|
||||
|
||||
|
||||
type ImageUploadModalStore = {
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
const initialState: ImageUploadModalStore = {
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
export const useImageUploadModalStore = create<ImageUploadModalStore>()(
|
||||
persist(
|
||||
devtools(
|
||||
() => initialState,
|
||||
{
|
||||
name: "ImageUploadModalStore",
|
||||
}
|
||||
),
|
||||
{
|
||||
name: "ImageUploadModalStore",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const openImageUploadModal = () => useImageUploadModalStore.setState({ isOpen: true });
|
||||
|
||||
export const closeImageUploadModal = () => useImageUploadModalStore.setState({ isOpen: false });
|
@ -1,7 +1,7 @@
|
||||
import { questionApi } from "@api/question";
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { questionToEditQuestionRequest } from "@model/question/edit";
|
||||
import { RawQuestion, rawQuestionToQuestion } from "@model/question/question";
|
||||
import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/question/question";
|
||||
import { AnyQuizQuestion, ImageQuestionVariant, QuestionVariant, createQuestionImageVariant, createQuestionVariant } from "@model/questionTypes/shared";
|
||||
import { produce } from "immer";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
@ -11,44 +11,37 @@ import { QuestionsStore, useQuestionsStore } from "./store";
|
||||
|
||||
|
||||
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
||||
state.questionsById = {};
|
||||
if (questions === null) return;
|
||||
|
||||
questions.forEach(question => state.questionsById[question.id] = rawQuestionToQuestion(question));
|
||||
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
|
||||
}, {
|
||||
type: "setQuestions",
|
||||
questions,
|
||||
});
|
||||
|
||||
export const setQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
|
||||
state.questionsById[question.id] = question;
|
||||
const setQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
|
||||
const index = state.questions.findIndex(q => q.id === question.id);
|
||||
state.questions.splice(index, 1, question);
|
||||
}, {
|
||||
type: "setQuestion",
|
||||
question,
|
||||
});
|
||||
|
||||
export const removeQuestion = (questionId: number) => setProducedState(state => {
|
||||
delete state.questionsById[questionId];
|
||||
const removeQuestion = (questionId: number) => setProducedState(state => {
|
||||
const index = state.questions.findIndex(q => q.id === questionId);
|
||||
state.questions.splice(index, 1);
|
||||
}, {
|
||||
type: "removeQuestion",
|
||||
questionId,
|
||||
});
|
||||
|
||||
export const setQuestionField = <T extends keyof AnyQuizQuestion>(
|
||||
const setQuestionField = <T extends keyof AnyQuizQuestion>(
|
||||
questionId: number,
|
||||
field: T,
|
||||
value: AnyQuizQuestion[T],
|
||||
) => setProducedState(state => {
|
||||
const question = state.questionsById[questionId];
|
||||
const question = state.questions.find(q => q.id === questionId);
|
||||
if (!question) return;
|
||||
|
||||
const oldId = question.id;
|
||||
question[field] = value;
|
||||
|
||||
if (field === "id") {
|
||||
delete state.questionsById[oldId];
|
||||
state.questionsById[value as number] = question;
|
||||
}
|
||||
}, {
|
||||
type: "setQuestionField",
|
||||
questionId,
|
||||
@ -56,15 +49,31 @@ export const setQuestionField = <T extends keyof AnyQuizQuestion>(
|
||||
value,
|
||||
});
|
||||
|
||||
export const reorderQuestions = (
|
||||
sourceIndex: number,
|
||||
destinationIndex: number,
|
||||
) => {
|
||||
if (sourceIndex === destinationIndex) return;
|
||||
|
||||
setProducedState(state => {
|
||||
const [removed] = state.questions.splice(sourceIndex, 1);
|
||||
state.questions.splice(destinationIndex, 0, removed);
|
||||
});
|
||||
};
|
||||
|
||||
export const toggleExpandQuestion = (questionId: number) => setProducedState(state => {
|
||||
const question = state.questionsById[questionId];
|
||||
const question = state.questions.find(q => q.id === questionId);
|
||||
if (!question) return;
|
||||
|
||||
question.expanded = !question.expanded;
|
||||
});
|
||||
|
||||
export const collapseAllQuestions = () => setProducedState(state => {
|
||||
state.questions.forEach(question => question.expanded = false);
|
||||
});
|
||||
|
||||
export const toggleOpenQuestionModal = (questionId: number) => setProducedState(state => {
|
||||
const question = state.questionsById[questionId];
|
||||
const question = state.questions.find(q => q.id === questionId);
|
||||
if (!question) return;
|
||||
|
||||
question.openedModalSettings = !question.openedModalSettings;
|
||||
@ -183,6 +192,84 @@ export const setQuestionOriginalBackgroundImage = (
|
||||
});
|
||||
};
|
||||
|
||||
export const setVariantImageUrl = (
|
||||
questionId: number,
|
||||
variantId: string,
|
||||
url: string,
|
||||
) => {
|
||||
updateQuestionWithFnOptimistic(questionId, question => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find(variant => variant.id === variantId);
|
||||
if (!variant || !("originalImageUrl" in variant)) return;
|
||||
|
||||
if (variant.extendedText === url) return;
|
||||
|
||||
if (variant.extendedText !== variant.originalImageUrl) URL.revokeObjectURL(variant.extendedText);
|
||||
variant.extendedText = url;
|
||||
});
|
||||
};
|
||||
|
||||
export const setVariantOriginalImageUrl = (
|
||||
questionId: number,
|
||||
variantId: string,
|
||||
url: string,
|
||||
) => {
|
||||
updateQuestionWithFnOptimistic(questionId, question => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find(
|
||||
variant => variant.id === variantId
|
||||
) as ImageQuestionVariant | undefined;
|
||||
if (!variant || !("originalImageUrl" in variant)) return;
|
||||
|
||||
if (variant.originalImageUrl === url) return;
|
||||
|
||||
URL.revokeObjectURL(variant.originalImageUrl);
|
||||
variant.originalImageUrl = url;
|
||||
});
|
||||
};
|
||||
|
||||
export const setPageQuestionPicture = (
|
||||
questionId: number,
|
||||
url: string,
|
||||
) => {
|
||||
updateQuestionWithFnOptimistic(questionId, question => {
|
||||
if (question.type !== "page") return;
|
||||
|
||||
if (question.content.picture === url) return;
|
||||
|
||||
if (
|
||||
question.content.picture !== question.content.originalPicture
|
||||
) URL.revokeObjectURL(question.content.picture);
|
||||
question.content.picture = url;
|
||||
});
|
||||
};
|
||||
|
||||
export const setPageQuestionOriginalPicture = (
|
||||
questionId: number,
|
||||
url: string,
|
||||
) => {
|
||||
updateQuestionWithFnOptimistic(questionId, question => {
|
||||
if (question.type !== "page") return;
|
||||
|
||||
if (question.content.originalPicture === url) return;
|
||||
|
||||
URL.revokeObjectURL(question.content.originalPicture);
|
||||
question.content.originalPicture = url;
|
||||
});
|
||||
};
|
||||
|
||||
export const setQuestionInnerName = (
|
||||
questionId: number,
|
||||
name: string,
|
||||
) => {
|
||||
updateQuestionWithFnOptimistic(questionId, question => {
|
||||
question.content.innerName = name;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
let savedOriginalQuestion: AnyQuizQuestion | null = null;
|
||||
let controller: AbortController | null = null;
|
||||
@ -191,7 +278,7 @@ export const updateQuestionWithFnOptimistic = async (
|
||||
questionId: number,
|
||||
updateFn: (question: AnyQuizQuestion) => void,
|
||||
) => {
|
||||
const question = useQuestionsStore.getState().questionsById[questionId] ?? null;
|
||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||
if (!question) return;
|
||||
|
||||
const currentUpdatedQuestion = produce(question, updateFn);
|
||||
@ -228,10 +315,11 @@ export const updateQuestionWithFnOptimistic = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const createQuestion = async (quizId: number) => {
|
||||
export const createQuestion = async (quizId: number, type: QuestionType = "variant") => {
|
||||
try {
|
||||
const question = await questionApi.create({
|
||||
quiz_id: quizId,
|
||||
type,
|
||||
});
|
||||
|
||||
setQuestion(rawQuestionToQuestion(question));
|
||||
@ -257,10 +345,12 @@ export const copyQuestion = async (questionId: number, quizId: number) => {
|
||||
const { updated: newQuestionId } = await questionApi.copy(questionId, quizId);
|
||||
|
||||
setProducedState(state => {
|
||||
const question = state.questionsById[questionId];
|
||||
const question = state.questions.find(q => q.id === questionId);
|
||||
if (!question) return;
|
||||
|
||||
state.questionsById[newQuestionId] = question;
|
||||
const copiedQuestion = structuredClone(question);
|
||||
copiedQuestion.id = newQuestionId;
|
||||
state.questions.push(copiedQuestion);
|
||||
}, {
|
||||
type: "copyQuestion",
|
||||
questionId,
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { useQuestionsStore } from "./store";
|
||||
|
||||
|
||||
export function useQuestionArray() {
|
||||
const questions = useQuestionsStore(state => state.questionsById);
|
||||
|
||||
return Object.values(questions).flatMap(question => question ? [question] : []);
|
||||
}
|
@ -4,11 +4,11 @@ import { devtools } from "zustand/middleware";
|
||||
|
||||
|
||||
export type QuestionsStore = {
|
||||
questionsById: Record<number, AnyQuizQuestion | undefined>;
|
||||
questions: AnyQuizQuestion[];
|
||||
};
|
||||
|
||||
const initialState: QuestionsStore = {
|
||||
questionsById: {},
|
||||
questions: [],
|
||||
};
|
||||
|
||||
export const useQuestionsStore = create<QuestionsStore>()(
|
||||
|
Loading…
Reference in New Issue
Block a user