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 {
|
export interface CreateQuestionRequest {
|
||||||
@ -9,7 +9,7 @@ export interface CreateQuestionRequest {
|
|||||||
/** description of question. html/text */
|
/** description of question. html/text */
|
||||||
description?: string;
|
description?: string;
|
||||||
/** type of question. allow only text, select, file, variant, images, varimg, emoji, date, number, page, rating */
|
/** 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 */
|
/** set true if user MUST answer this question */
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
/** page of question */
|
/** page of question */
|
||||||
|
|||||||
@ -86,6 +86,13 @@ export type AnyQuizQuestion =
|
|||||||
| QuizQuestionRating;
|
| QuizQuestionRating;
|
||||||
// | QuizQuestionInitial;
|
// | QuizQuestionInitial;
|
||||||
|
|
||||||
|
type FilterQuestionsWithVariants<T> = T extends {
|
||||||
|
content: { variants: QuestionVariant[] | ImageQuestionVariant[]; };
|
||||||
|
} ? T : never;
|
||||||
|
|
||||||
|
export type QuizQuestionsWithVariants = FilterQuestionsWithVariants<AnyQuizQuestion>;
|
||||||
|
|
||||||
|
|
||||||
export const createQuestionVariant: () => QuestionVariant = () => ({
|
export const createQuestionVariant: () => QuestionVariant = () => ({
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
answer: "",
|
answer: "",
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
import { reorderQuestionVariants } from "@root/questions/actions";
|
||||||
import { AnswerItem } from "./AnswerItem";
|
|
||||||
import { type ReactNode } from "react";
|
import { type ReactNode } from "react";
|
||||||
import type { DropResult } from "react-beautiful-dnd";
|
import type { DropResult } from "react-beautiful-dnd";
|
||||||
import type { AnyQuizQuestion, ImageQuestionVariant, QuestionVariant } from "../../../model/questionTypes/shared";
|
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||||
import { reorderQuestionVariants } from "@root/questions/actions";
|
import type { ImageQuestionVariant, QuestionVariant, QuizQuestionsWithVariants } from "../../../model/questionTypes/shared";
|
||||||
|
import { AnswerItem } from "./AnswerItem";
|
||||||
|
|
||||||
|
|
||||||
type AnswerDraggableListProps = {
|
type AnswerDraggableListProps = {
|
||||||
variants: QuestionVariant[];
|
question: QuizQuestionsWithVariants;
|
||||||
question: AnyQuizQuestion;
|
|
||||||
additionalContent?: (variant: QuestionVariant | ImageQuestionVariant, index: number) => ReactNode;
|
additionalContent?: (variant: QuestionVariant | ImageQuestionVariant, index: number) => ReactNode;
|
||||||
additionalMobile?: (variant: QuestionVariant | ImageQuestionVariant, index: number) => ReactNode;
|
additionalMobile?: (variant: QuestionVariant | ImageQuestionVariant, index: number) => ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AnswerDraggableList = ({
|
export const AnswerDraggableList = ({
|
||||||
variants,
|
|
||||||
question,
|
question,
|
||||||
additionalContent,
|
additionalContent,
|
||||||
additionalMobile,
|
additionalMobile,
|
||||||
@ -31,7 +29,7 @@ export const AnswerDraggableList = ({
|
|||||||
<Droppable droppableId="droppable-answer-list">
|
<Droppable droppableId="droppable-answer-list">
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
{variants.map((variant, index) => (
|
{question.content.variants.map((variant, index) => (
|
||||||
<AnswerItem
|
<AnswerItem
|
||||||
key={variant.id}
|
key={variant.id}
|
||||||
index={index}
|
index={index}
|
||||||
|
|||||||
@ -1,73 +1,48 @@
|
|||||||
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 { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight";
|
||||||
|
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
|
||||||
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
|
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
|
||||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||||
|
|
||||||
import type { SxProps } from "@mui/material";
|
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 {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
SSHC: (data: string) => void;
|
SSHC: (data: string) => void;
|
||||||
totalIndex: number;
|
question: AnyQuizQuestion;
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ButtonsOptions({
|
export default function ButtonsOptions({
|
||||||
SSHC,
|
SSHC,
|
||||||
switchState,
|
switchState,
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: 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;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (question.deleteTimeoutId) {
|
|
||||||
clearTimeout(question.deleteTimeoutId);
|
|
||||||
}
|
|
||||||
}, [listQuestions]);
|
|
||||||
|
|
||||||
const openedModal = () => {
|
|
||||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
|
||||||
openedModalSettings: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||||
|
|
||||||
|
const openedModal = () => {
|
||||||
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
|
question.openedModalSettings = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const buttonSetting: {
|
const buttonSetting: {
|
||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
title: string;
|
title: string;
|
||||||
@ -237,7 +212,7 @@ export default function ButtonsOptions({
|
|||||||
))}
|
))}
|
||||||
<>
|
<>
|
||||||
<MiniButtonSetting
|
<MiniButtonSetting
|
||||||
onClick={() => setOpenedReallyChangingModal(true)}
|
onClick={undefined} // TODO
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: "30px",
|
minWidth: "30px",
|
||||||
height: "30px",
|
height: "30px",
|
||||||
@ -247,7 +222,7 @@ export default function ButtonsOptions({
|
|||||||
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
|
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||||
</MiniButtonSetting>
|
</MiniButtonSetting>
|
||||||
<MiniButtonSetting
|
<MiniButtonSetting
|
||||||
onClick={() => setOpenedReallyChangingModal(true)}
|
onClick={undefined} // TODO
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: "30px",
|
minWidth: "30px",
|
||||||
height: "30px",
|
height: "30px",
|
||||||
@ -257,7 +232,7 @@ export default function ButtonsOptions({
|
|||||||
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
|
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||||
</MiniButtonSetting>
|
</MiniButtonSetting>
|
||||||
<MiniButtonSetting
|
<MiniButtonSetting
|
||||||
onClick={() => setOpenedReallyChangingModal(true)}
|
onClick={undefined} // TODO
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: "30px",
|
minWidth: "30px",
|
||||||
height: "30px",
|
height: "30px",
|
||||||
@ -279,28 +254,30 @@ export default function ButtonsOptions({
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||||
onClick={() => copyQuestion(quizId, totalIndex)}
|
onClick={() => copyQuestion(question.id, question.quizId)}
|
||||||
>
|
>
|
||||||
<CopyIcon color={"#4D4D4D"} />
|
<CopyIcon color={"#4D4D4D"} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||||
onClick={() => {
|
onClick={() => { // TODO
|
||||||
const removedId = question.id;
|
// const removedId = question.id;
|
||||||
if (question.deleteTimeoutId) {
|
// if (question.deleteTimeoutId) {
|
||||||
clearTimeout(question.deleteTimeoutId);
|
// clearTimeout(question.deleteTimeoutId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
removeQuestion(quizId, totalIndex);
|
// removeQuestion(quizId, totalIndex);
|
||||||
|
|
||||||
const newTimeoutId = window.setTimeout(() => {
|
// const newTimeoutId = window.setTimeout(() => {
|
||||||
removeQuestionForce(quizId, removedId);
|
// removeQuestionForce(quizId, removedId);
|
||||||
}, 5000);
|
// }, 5000);
|
||||||
|
|
||||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
// updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
||||||
...question,
|
// ...question,
|
||||||
deleteTimeoutId: newTimeoutId,
|
// deleteTimeoutId: newTimeoutId,
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
deleteQuestion(question.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon color={"#4D4D4D"} />
|
<DeleteIcon color={"#4D4D4D"} />
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight";
|
import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight";
|
||||||
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
|
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
|
||||||
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
|
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
|
||||||
|
import { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -20,13 +21,13 @@ import { DeleteIcon } from "../../assets/icons/questionsPage/deleteIcon";
|
|||||||
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
|
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
|
||||||
import ImgIcon from "../../assets/icons/questionsPage/imgIcon";
|
import ImgIcon from "../../assets/icons/questionsPage/imgIcon";
|
||||||
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||||
import type { AnyQuizQuestion } from "../../model/questionTypes/shared";
|
import { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
SSHC: (data: string) => void;
|
SSHC: (data: string) => void;
|
||||||
question: AnyQuizQuestion;
|
question: QuizQuestionVariant | QuizQuestionVarImg;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ButtonsOptionsAndPict({
|
export default function ButtonsOptionsAndPict({
|
||||||
|
|||||||
@ -3,13 +3,14 @@ import { useState } from "react";
|
|||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
import ButtonsOptions from "../ButtonsOptions";
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
import SwitchData from "./switchData";
|
import SwitchData from "./switchData";
|
||||||
|
import { QuizQuestionDate } from "@model/questionTypes/date";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
totalIndex: number;
|
question: QuizQuestionDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DataOptions({ totalIndex }: Props) {
|
export default function DataOptions({ question }: Props) {
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
@ -49,8 +50,8 @@ export default function DataOptions({ totalIndex }: Props) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||||
<SwitchData switchState={switchState} totalIndex={totalIndex} />
|
<SwitchData switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,24 @@
|
|||||||
import { useParams } from "react-router-dom";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import type { QuizQuestionDate } from "../../../model/questionTypes/date";
|
import type { QuizQuestionDate } from "../../../model/questionTypes/date";
|
||||||
|
|
||||||
|
|
||||||
type SettingsDataProps = {
|
type SettingsDataProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
export default function SettingsData({ question }: SettingsDataProps) {
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionDate;
|
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||||
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
setQuestionInnerName(question.id, value);
|
||||||
content: { ...question.content, innerName: value },
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -54,8 +48,10 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
|||||||
label={"Выбор диапазона дат"}
|
label={"Выбор диапазона дат"}
|
||||||
checked={question.content.dateRange}
|
checked={question.content.dateRange}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, dateRange: target.checked },
|
if (question.type !== "date") return;
|
||||||
|
|
||||||
|
question.content.dateRange = target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -64,8 +60,10 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
|||||||
label={"Выбор времени"}
|
label={"Выбор времени"}
|
||||||
checked={question.content.time}
|
checked={question.content.time}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, time: target.checked },
|
if (question.type !== "date") return;
|
||||||
|
|
||||||
|
question.content.time = target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -90,8 +88,8 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
required: !target.checked,
|
question.required = !target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -111,12 +109,9 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
question.content.innerNameCheck = target.checked;
|
||||||
...question.content,
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
innerNameCheck: target.checked,
|
|
||||||
innerName: target.checked ? question.content.innerName : "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -130,7 +125,7 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Развёрнутое описание вопроса"}
|
placeholder={"Развёрнутое описание вопроса"}
|
||||||
text={question.content.innerName}
|
text={question.content.innerName}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setInnerName(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 HelpQuestions from "../helpQuestions";
|
||||||
import SettingData from "./settingData";
|
import SettingData from "./settingData";
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchData({
|
export default function SwitchData({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingData totalIndex={totalIndex} />;
|
return <SettingData question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +1,22 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
|
||||||
Popper,
|
|
||||||
Grow,
|
|
||||||
Paper,
|
|
||||||
MenuList,
|
|
||||||
MenuItem,
|
|
||||||
ClickAwayListener,
|
|
||||||
Modal,
|
|
||||||
Button,
|
Button,
|
||||||
|
ClickAwayListener,
|
||||||
|
Grow,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
Modal,
|
||||||
|
Paper,
|
||||||
|
Popper,
|
||||||
|
Typography,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
import {
|
|
||||||
updateQuestionsList,
|
|
||||||
removeQuestionForce,
|
|
||||||
createQuestion,
|
|
||||||
} from "@root/questions";
|
|
||||||
import { BUTTON_TYPE_QUESTIONS } from "../TypeQuestions";
|
import { BUTTON_TYPE_QUESTIONS } from "../TypeQuestions";
|
||||||
|
|
||||||
import type { RefObject } from "react";
|
import type { RefObject } from "react";
|
||||||
import type {
|
import type { AnyQuizQuestion } from "../../../model/questionTypes/shared";
|
||||||
QuizQuestionType,
|
import { QuestionType } from "@model/question/question";
|
||||||
QuizQuestionBase,
|
|
||||||
AnyQuizQuestion,
|
|
||||||
} from "../../../model/questionTypes/shared";
|
|
||||||
|
|
||||||
type ChooseAnswerModalProps = {
|
type ChooseAnswerModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -43,7 +34,7 @@ export const ChooseAnswerModal = ({
|
|||||||
switchState,
|
switchState,
|
||||||
}: ChooseAnswerModalProps) => {
|
}: ChooseAnswerModalProps) => {
|
||||||
const [openModal, setOpenModal] = useState<boolean>(false);
|
const [openModal, setOpenModal] = useState<boolean>(false);
|
||||||
const [selectedValue, setSelectedValue] = useState<QuizQuestionType>("text");
|
const [selectedValue, setSelectedValue] = useState<QuestionType>("text");
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import Page from "@icons/questionsPage/page";
|
|||||||
import RatingIcon from "@icons/questionsPage/rating";
|
import RatingIcon from "@icons/questionsPage/rating";
|
||||||
import Slider from "@icons/questionsPage/slider";
|
import Slider from "@icons/questionsPage/slider";
|
||||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
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 type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||||
import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg";
|
import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg";
|
||||||
import type { AnyQuizQuestion } from "../../../model/questionTypes/shared";
|
import type { AnyQuizQuestion } from "../../../model/questionTypes/shared";
|
||||||
@ -307,7 +307,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
onClick={() => createQuestion(quizId, "nonselected", totalIndex + 1)}
|
onClick={() => createQuestion(question.quizId)}
|
||||||
sx={{
|
sx={{
|
||||||
display: plusVisible && !isDragging ? "flex" : "none",
|
display: plusVisible && !isDragging ? "flex" : "none",
|
||||||
width: "100%",
|
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 { questionApi } from "@api/question";
|
||||||
import { setQuestions } from "@root/questions/actions";
|
|
||||||
import { isAxiosError } from "axios";
|
|
||||||
import { devlog } from "@frontend/kitui";
|
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 { 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 = () => {
|
export const DraggableList = () => {
|
||||||
@ -23,18 +23,10 @@ export const DraggableList = () => {
|
|||||||
enqueueSnackbar(`Не удалось получить вопросы. ${message}`);
|
enqueueSnackbar(`Не удалось получить вопросы. ${message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const questions = useQuestionArray();
|
const questions = useQuestionsStore(state => state.questions);
|
||||||
|
|
||||||
const onDragEnd = ({ destination, source }: DropResult) => { // TODO
|
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||||
// if (destination) {
|
if (destination) reorderQuestions(source.index, destination.index);
|
||||||
// const newItems = reorder(
|
|
||||||
// listQuestions[quizId],
|
|
||||||
// source.index,
|
|
||||||
// destination.index
|
|
||||||
// );
|
|
||||||
|
|
||||||
// updateQuestionsListDragAndDrop(quizId, newItems);
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,41 +1,26 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { Box, Typography, Link, useTheme, useMediaQuery } from "@mui/material";
|
import { Box, Typography, Link, useTheme, useMediaQuery } from "@mui/material";
|
||||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||||
|
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||||
import SwitchDropDown from "./switchDropDown";
|
import SwitchDropDown from "./switchDropDown";
|
||||||
import ButtonsOptions from "../ButtonsOptions";
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
|
|
||||||
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
||||||
|
import { addQuestionVariant } from "@root/questions/actions";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
totalIndex: number;
|
question: QuizQuestionSelect;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DropDown({ totalIndex }: Props) {
|
export default function DropDown({ question }: Props) {
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionSelect;
|
|
||||||
|
|
||||||
const SSHC = (data: string) => {
|
const SSHC = (data: string) => {
|
||||||
setSwitchState(data);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -56,10 +41,7 @@ export default function DropDown({ totalIndex }: Props) {
|
|||||||
Добавьте ответ
|
Добавьте ответ
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<AnswerDraggableList
|
<AnswerDraggableList question={question} />
|
||||||
variants={question.content.variants}
|
|
||||||
question={totalIndex}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -78,7 +60,7 @@ export default function DropDown({ totalIndex }: Props) {
|
|||||||
mr: "4px",
|
mr: "4px",
|
||||||
height: "19px",
|
height: "19px",
|
||||||
}}
|
}}
|
||||||
onClick={addNewAnswer}
|
onClick={() => addQuestionVariant(question.id)}
|
||||||
>
|
>
|
||||||
Добавьте ответ
|
Добавьте ответ
|
||||||
</Link>
|
</Link>
|
||||||
@ -108,9 +90,9 @@ export default function DropDown({ totalIndex }: Props) {
|
|||||||
<ButtonsOptions
|
<ButtonsOptions
|
||||||
switchState={switchState}
|
switchState={switchState}
|
||||||
SSHC={SSHC}
|
SSHC={SSHC}
|
||||||
totalIndex={totalIndex}
|
question={question}
|
||||||
/>
|
/>
|
||||||
<SwitchDropDown switchState={switchState} totalIndex={totalIndex} />
|
<SwitchDropDown switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,43 +1,36 @@
|
|||||||
import { useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
|
|
||||||
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
||||||
|
|
||||||
|
|
||||||
type SettingDropDownProps = {
|
type SettingDropDownProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionSelect;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
|
export default function SettingDropDown({ question }: SettingDropDownProps) {
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
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 QuizQuestionSelect;
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const debounced = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
setQuestionInnerName(question.id, value);
|
||||||
content: { ...question.content, innerName: value },
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const debounceAnswer = useDebouncedCallback((value) => {
|
const debounceAnswer = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, default: value },
|
if (question.type !== "select") return;
|
||||||
|
|
||||||
|
question.content.default = value;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
@ -80,8 +73,10 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
|
|||||||
checked={question.content.multi}
|
checked={question.content.multi}
|
||||||
dataCy="multiple-answers-checkbox"
|
dataCy="multiple-answers-checkbox"
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) =>
|
||||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, multi: target.checked },
|
if (question.type !== "select") return;
|
||||||
|
|
||||||
|
question.content.multi = target.checked;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -135,8 +130,8 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
required: !e.target.checked,
|
question.required = !e.target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -146,12 +141,9 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
question.content.innerNameCheck = target.checked;
|
||||||
...question.content,
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
innerNameCheck: target.checked,
|
|
||||||
innerName: target.checked ? question.content.innerName : "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,27 +1,25 @@
|
|||||||
import * as React from "react";
|
import { QuizQuestionSelect } from "@model/questionTypes/select";
|
||||||
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
import HelpQuestions from "../helpQuestions";
|
import HelpQuestions from "../helpQuestions";
|
||||||
import SettingDropDown from "./settingDropDown";
|
import SettingDropDown from "./settingDropDown";
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionSelect;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchDropDown({
|
export default function SwitchDropDown({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingDropDown totalIndex={totalIndex} />;
|
return <SettingDropDown question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,41 +1,36 @@
|
|||||||
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 { EmojiIcons } from "@icons/EmojiIocns";
|
||||||
import AddEmoji from "@icons/questionsPage/addEmoji";
|
import AddEmoji from "@icons/questionsPage/addEmoji";
|
||||||
import PlusImage from "@icons/questionsPage/plus";
|
import PlusImage from "@icons/questionsPage/plus";
|
||||||
|
import {
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
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 type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
||||||
|
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||||
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
|
import SwitchEmoji from "./switchEmoji";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
totalIndex: number;
|
question: QuizQuestionEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Emoji({ totalIndex }: Props) {
|
export default function Emoji({ question }: Props) {
|
||||||
const [switchState, setSwitchState] = useState<string>("setting");
|
const [switchState, setSwitchState] = useState<string>("setting");
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(
|
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
const [selectedVariant, setSelectedVariant] = useState<string | null>(null);
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionEmoji;
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
|
|
||||||
@ -47,9 +42,8 @@ export default function Emoji({ totalIndex }: Props) {
|
|||||||
<>
|
<>
|
||||||
<Box sx={{ padding: "20px" }}>
|
<Box sx={{ padding: "20px" }}>
|
||||||
<AnswerDraggableList
|
<AnswerDraggableList
|
||||||
variants={question.content.variants}
|
question={question}
|
||||||
question={totalIndex}
|
additionalContent={(variant) => (
|
||||||
additionalContent={(variant, index) => (
|
|
||||||
<>
|
<>
|
||||||
{!isTablet && (
|
{!isTablet && (
|
||||||
<Box sx={{ cursor: "pointer" }}>
|
<Box sx={{ cursor: "pointer" }}>
|
||||||
@ -57,7 +51,7 @@ export default function Emoji({ totalIndex }: Props) {
|
|||||||
data-cy="choose-emoji-button"
|
data-cy="choose-emoji-button"
|
||||||
onClick={({ currentTarget }) => {
|
onClick={({ currentTarget }) => {
|
||||||
setAnchorElement(currentTarget);
|
setAnchorElement(currentTarget);
|
||||||
setCurrentIndex(index);
|
setSelectedVariant(variant.id);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -104,13 +98,13 @@ export default function Emoji({ totalIndex }: Props) {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
additionalMobile={(variant, index) => (
|
additionalMobile={(variant) => (
|
||||||
<>
|
<>
|
||||||
{isTablet && (
|
{isTablet && (
|
||||||
<Box
|
<Box
|
||||||
onClick={({ currentTarget }) => {
|
onClick={({ currentTarget }) => {
|
||||||
setAnchorElement(currentTarget);
|
setAnchorElement(currentTarget);
|
||||||
setCurrentIndex(index);
|
setSelectedVariant(variant.id);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
@ -187,15 +181,13 @@ export default function Emoji({ totalIndex }: Props) {
|
|||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
onEmojiSelect={({ native }) => {
|
onEmojiSelect={({ native }) => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
const cloneVariants = [...question.content.variants];
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
|
if (question.type !== "emoji") return;
|
||||||
|
|
||||||
cloneVariants[currentIndex] = {
|
const variant = question.content.variants.find(v => v.id === selectedVariant);
|
||||||
...cloneVariants[currentIndex],
|
if (!variant) return;
|
||||||
extendedText: native,
|
|
||||||
};
|
|
||||||
|
|
||||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
variant.extendedText = native;
|
||||||
content: { ...question.content, variants: cloneVariants },
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -212,14 +204,7 @@ export default function Emoji({ totalIndex }: Props) {
|
|||||||
component="button"
|
component="button"
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ color: theme.palette.brightPurple.main }}
|
sx={{ color: theme.palette.brightPurple.main }}
|
||||||
onClick={() => {
|
onClick={() => addQuestionVariant(question.id)}
|
||||||
const answerNew = question.content.variants.slice();
|
|
||||||
answerNew.push({ answer: "", extendedText: "", hints: "" });
|
|
||||||
|
|
||||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
|
||||||
content: { ...question.content, variants: answerNew },
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Добавьте ответ
|
Добавьте ответ
|
||||||
</Link>
|
</Link>
|
||||||
@ -249,9 +234,9 @@ export default function Emoji({ totalIndex }: Props) {
|
|||||||
<ButtonsOptions
|
<ButtonsOptions
|
||||||
switchState={switchState}
|
switchState={switchState}
|
||||||
SSHC={SSHC}
|
SSHC={SSHC}
|
||||||
totalIndex={totalIndex}
|
question={question}
|
||||||
/>
|
/>
|
||||||
<SwitchEmoji switchState={switchState} totalIndex={totalIndex} />
|
<SwitchEmoji switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +1,25 @@
|
|||||||
import { useParams } from "react-router-dom";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
||||||
|
|
||||||
|
|
||||||
type SettingEmojiProps = {
|
type SettingEmojiProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionEmoji;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
export default function SettingEmoji({ question }: SettingEmojiProps) {
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
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) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
setQuestionInnerName(question.id, value);
|
||||||
content: { ...question.content, innerName: value },
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -56,21 +50,21 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
|||||||
label={"Можно несколько"}
|
label={"Можно несколько"}
|
||||||
checked={question.content.multi}
|
checked={question.content.multi}
|
||||||
dataCy="multiple-answers-checkbox"
|
dataCy="multiple-answers-checkbox"
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
if (question.type !== "emoji") return;
|
||||||
content: { ...question.content, multi: target.checked },
|
|
||||||
});
|
question.content.multi = target.checked;
|
||||||
}}
|
})}
|
||||||
/>
|
/>
|
||||||
<CustomCheckbox
|
<CustomCheckbox
|
||||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||||
label={'Вариант "свой ответ"'}
|
label={'Вариант "свой ответ"'}
|
||||||
checked={question.content.own}
|
checked={question.content.own}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
if (question.type !== "emoji") return;
|
||||||
content: { ...question.content, own: target.checked },
|
|
||||||
});
|
question.content.own = target.checked;
|
||||||
}}
|
})}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
@ -92,11 +86,11 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
|||||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
if (question.type !== "emoji") return;
|
||||||
required: !e.target.checked,
|
|
||||||
});
|
question.content.required = !e.target.checked;
|
||||||
}}
|
})}
|
||||||
/>
|
/>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -113,15 +107,10 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
|||||||
}}
|
}}
|
||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
question.content.innerNameCheck = target.checked;
|
||||||
content: {
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
...question.content,
|
})}
|
||||||
innerNameCheck: target.checked,
|
|
||||||
innerName: target.checked ? question.content.innerName : "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||||
<Box>
|
<Box>
|
||||||
@ -133,7 +122,7 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Развёрнутое описание вопроса"}
|
placeholder={"Развёрнутое описание вопроса"}
|
||||||
text={question.content.innerName}
|
text={question.content.innerName}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setInnerName(target.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,27 +1,25 @@
|
|||||||
import * as React from "react";
|
import { QuizQuestionEmoji } from "@model/questionTypes/emoji";
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
import HelpQuestions from "../helpQuestions";
|
import HelpQuestions from "../helpQuestions";
|
||||||
import SettingEmoji from "./settingEmoji";
|
import SettingEmoji from "./settingEmoji";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchEmoji({
|
export default function SwitchEmoji({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingEmoji totalIndex={totalIndex} />;
|
return <SettingEmoji question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,9 +76,8 @@ export default memo(
|
|||||||
>
|
>
|
||||||
<QuestionsPageCard
|
<QuestionsPageCard
|
||||||
key={index}
|
key={index}
|
||||||
totalIndex={index}
|
question={question}
|
||||||
draggableProps={provided.dragHandleProps}
|
draggableProps={provided.dragHandleProps}
|
||||||
isDragging={isDragging}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,36 +1,115 @@
|
|||||||
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 { PointsIcon } from "@icons/questionsPage/PointsIcon";
|
||||||
import Answer from "@icons/questionsPage/answer";
|
import Answer from "@icons/questionsPage/answer";
|
||||||
import OptionsPict from "@icons/questionsPage/options_pict";
|
import AnswerGroup from "@icons/questionsPage/answerGroup";
|
||||||
import OptionsAndPict from "@icons/questionsPage/options_and_pict";
|
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 Emoji from "@icons/questionsPage/emoji";
|
||||||
import Input from "@icons/questionsPage/input";
|
import Input from "@icons/questionsPage/input";
|
||||||
import DropDown from "@icons/questionsPage/drop_down";
|
import OptionsAndPict from "@icons/questionsPage/options_and_pict";
|
||||||
import Date from "@icons/questionsPage/date";
|
import OptionsPict from "@icons/questionsPage/options_pict";
|
||||||
import Slider from "@icons/questionsPage/slider";
|
|
||||||
import Download from "@icons/questionsPage/download";
|
|
||||||
import Page from "@icons/questionsPage/page";
|
import Page from "@icons/questionsPage/page";
|
||||||
import RatingIcon from "@icons/questionsPage/rating";
|
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 { 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 {
|
interface Props {
|
||||||
totalIndex: number;
|
question: AnyQuizQuestion;
|
||||||
draggableProps: DraggableProvidedDragHandleProps | null | undefined;
|
draggableProps: DraggableProvidedDragHandleProps | null | undefined;
|
||||||
isDragging: boolean;
|
}
|
||||||
|
|
||||||
|
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) => {
|
const IconAndrom = (switchState: string) => {
|
||||||
@ -76,95 +155,3 @@ const IconAndrom = (switchState: string) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
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);
|
|
||||||
}
|
|
||||||
}, [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,33 +1,16 @@
|
|||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { Box } from "@mui/material";
|
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 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 = () => {
|
export const FormDraggableList = () => {
|
||||||
const quizId = Number(useParams().quizId);
|
const questions = useQuestionsStore(state => state.questions);
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
|
|
||||||
const onDragEnd = ({ destination, source }: DropResult) => {
|
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||||
if (destination?.index === 0) {
|
if (destination) reorderQuestions(source.index, destination.index);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destination) {
|
|
||||||
const newItems = reorder(
|
|
||||||
listQuestions[quizId],
|
|
||||||
source.index,
|
|
||||||
destination.index
|
|
||||||
);
|
|
||||||
|
|
||||||
updateQuestionsListDragAndDrop(quizId, newItems);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -35,7 +18,7 @@ export const FormDraggableList = () => {
|
|||||||
<Droppable droppableId="droppable-list">
|
<Droppable droppableId="droppable-list">
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
{listQuestions[quizId]?.map((question, index) => (
|
{questions.map((question, index) => (
|
||||||
<FormDraggableListItem
|
<FormDraggableListItem
|
||||||
key={index}
|
key={index}
|
||||||
index={index}
|
index={index}
|
||||||
|
|||||||
@ -1,43 +1,19 @@
|
|||||||
import { Box, Button, Typography, useTheme } from "@mui/material";
|
import { Box, Button, Typography, useTheme } from "@mui/material";
|
||||||
import { useParams } from "react-router-dom";
|
import { incrementCurrentStep } from "@root/quizes/actions";
|
||||||
|
|
||||||
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 QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||||
import { createPortal } from "react-dom";
|
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() {
|
export default function FormQuestionsPage() {
|
||||||
const { listQuizes, updateQuizesList } = quizStore();
|
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const handleNext = () => {
|
|
||||||
updateQuizesList(quizId, { step: listQuizes[quizId].step + 1 });
|
|
||||||
};
|
|
||||||
|
|
||||||
const collapseEverything = () => {
|
|
||||||
listQuestions[quizId].forEach((item, index) => {
|
|
||||||
updateQuestionsList<AnyQuizQuestion>(quizId, index, {
|
|
||||||
...item,
|
|
||||||
expanded: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { quiz } = useCurrentQuiz();
|
||||||
|
|
||||||
|
if (!quiz) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -60,7 +36,7 @@ export default function FormQuestionsPage() {
|
|||||||
color: theme.palette.brightPurple.main,
|
color: theme.palette.brightPurple.main,
|
||||||
textDecorationColor: theme.palette.brightPurple.main,
|
textDecorationColor: theme.palette.brightPurple.main,
|
||||||
}}
|
}}
|
||||||
onClick={collapseEverything}
|
onClick={collapseAllQuestions}
|
||||||
>
|
>
|
||||||
Свернуть всё
|
Свернуть всё
|
||||||
</Button>
|
</Button>
|
||||||
@ -92,7 +68,7 @@ export default function FormQuestionsPage() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
createQuestion(quizId);
|
createQuestion(quiz.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AddAnswer color="#EEE4FC" />
|
<AddAnswer color="#EEE4FC" />
|
||||||
@ -124,7 +100,7 @@ export default function FormQuestionsPage() {
|
|||||||
background: theme.palette.brightPurple.main,
|
background: theme.palette.brightPurple.main,
|
||||||
fontSize: "18px",
|
fontSize: "18px",
|
||||||
}}
|
}}
|
||||||
onClick={handleNext}
|
onClick={incrementCurrentStep}
|
||||||
>
|
>
|
||||||
Следующий шаг
|
Следующий шаг
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
import QuestionsMiniButton from "@ui_kit/QuestionsMiniButton";
|
import QuestionsMiniButton from "@ui_kit/QuestionsMiniButton";
|
||||||
@ -14,21 +13,12 @@ import Date from "../../../assets/icons/questionsPage/date";
|
|||||||
import Slider from "../../../assets/icons/questionsPage/slider";
|
import Slider from "../../../assets/icons/questionsPage/slider";
|
||||||
import Download from "../../../assets/icons/questionsPage/download";
|
import Download from "../../../assets/icons/questionsPage/download";
|
||||||
|
|
||||||
import {
|
|
||||||
questionStore,
|
|
||||||
updateQuestionsList,
|
|
||||||
createQuestion,
|
|
||||||
removeQuestionForce,
|
|
||||||
} from "@root/questions";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
QuizQuestionBase,
|
AnyQuizQuestion,
|
||||||
} from "../../../model/questionTypes/shared";
|
} from "../../../model/questionTypes/shared";
|
||||||
import { QuestionType } from "@model/question/question";
|
import { QuestionType } from "@model/question/question";
|
||||||
|
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
|
|
||||||
interface Props {
|
|
||||||
totalIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ButtonTypeQuestion = {
|
type ButtonTypeQuestion = {
|
||||||
icon: JSX.Element;
|
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 [switchState, setSwitchState] = useState("");
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionBase;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@ -85,21 +76,16 @@ export default function FormTypeQuestions({ totalIndex }: Props) {
|
|||||||
margin: "20px",
|
margin: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(totalIndex === 0
|
{(true /* TODO какое-то непонятное условие */
|
||||||
? BUTTON_TYPE_QUESTIONS
|
? BUTTON_TYPE_QUESTIONS
|
||||||
: BUTTON_TYPE_SHORT_QUESTIONS
|
: BUTTON_TYPE_SHORT_QUESTIONS
|
||||||
).map(({ icon, title, value }) => (
|
).map(({ icon, title, value: questionType }) => (
|
||||||
<QuestionsMiniButton
|
<QuestionsMiniButton
|
||||||
key={title}
|
key={title}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const clonedQuestion = { ...question };
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
|
question.type = questionType;
|
||||||
removeQuestionForce(quizId, clonedQuestion.id);
|
})
|
||||||
createQuestion(quizId, value, totalIndex);
|
|
||||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
|
||||||
expanded: clonedQuestion.expanded,
|
|
||||||
type: value,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
text={title}
|
text={title}
|
||||||
@ -109,9 +95,10 @@ export default function FormTypeQuestions({ totalIndex }: Props) {
|
|||||||
<ButtonsOptions
|
<ButtonsOptions
|
||||||
switchState={switchState}
|
switchState={switchState}
|
||||||
SSHC={setSwitchState}
|
SSHC={setSwitchState}
|
||||||
totalIndex={totalIndex}
|
question={question}
|
||||||
/>
|
/>
|
||||||
<SwitchAnswerOptions switchState={switchState} totalIndex={totalIndex} />
|
{/* TODO конфликт типов */}
|
||||||
|
{/* <SwitchAnswerOptions switchState={switchState} question={question} /> */}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,78 +1,72 @@
|
|||||||
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 { ImageAddIcons } from "@icons/ImageAddIcons";
|
||||||
import { MessageIcon } from "@icons/messagIcon";
|
import { MessageIcon } from "@icons/messagIcon";
|
||||||
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
|
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
|
||||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
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 { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||||
|
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||||
|
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||||
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
|
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
|
||||||
|
import { UploadImageModal } from "../UploadImage/UploadImageModal";
|
||||||
import SwitchOptionsAndPict from "./switchOptionsAndPict";
|
import SwitchOptionsAndPict from "./switchOptionsAndPict";
|
||||||
|
|
||||||
import { openCropModal } from "@root/cropModal";
|
|
||||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
|
||||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
totalIndex: number;
|
question: QuizQuestionVarImg;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function OptionsAndPicture({ totalIndex }: Props) {
|
export default function OptionsAndPicture({ question }: Props) {
|
||||||
const [isUploadImageModalOpen, setIsUploadImageModalOpen] = useState(false);
|
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionVarImg;
|
|
||||||
|
|
||||||
const SSHC = (data: string) => {
|
const SSHC = (data: string) => {
|
||||||
setSwitchState(data);
|
setSwitchState(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImageUpload = (files: FileList | null) => {
|
const handleImageUpload = (files: FileList | null) => {
|
||||||
if (!files?.length) return;
|
if (!files?.length || !selectedVariantId) return;
|
||||||
|
|
||||||
const [file] = Array.from(files);
|
const [file] = Array.from(files);
|
||||||
const url = URL.createObjectURL(file);
|
const url = URL.createObjectURL(file);
|
||||||
|
|
||||||
setVariantImageUrl(quizId, totalIndex, currentIndex, url);
|
setVariantImageUrl(question.id, selectedVariantId, url);
|
||||||
setVariantOriginalImageUrl(quizId, totalIndex, currentIndex, url);
|
setVariantOriginalImageUrl(question.id, selectedVariantId, url);
|
||||||
setIsUploadImageModalOpen(false);
|
closeImageUploadModal();
|
||||||
openCropModal(url, url);
|
openCropModal(url, url);
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleCropModalSaveClick(url: string) {
|
function handleCropModalSaveClick(url: string) {
|
||||||
setVariantImageUrl(quizId, totalIndex, currentIndex, url);
|
if (!selectedVariantId) return;
|
||||||
|
|
||||||
|
setVariantImageUrl(question.id, selectedVariantId, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box sx={{ pl: "20px", pr: "20px" }}>
|
<Box sx={{ pl: "20px", pr: "20px" }}>
|
||||||
<AnswerDraggableList
|
<AnswerDraggableList
|
||||||
variants={question.content.variants}
|
question={question}
|
||||||
question={totalIndex}
|
additionalContent={(variant) => (
|
||||||
additionalContent={(variant, index) => (
|
|
||||||
<>
|
<>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<AddOrEditImageButton
|
<AddOrEditImageButton
|
||||||
@ -80,24 +74,24 @@ export default function OptionsAndPicture({ totalIndex }: Props) {
|
|||||||
onImageClick={() => {
|
onImageClick={() => {
|
||||||
if (!("originalImageUrl" in variant)) return;
|
if (!("originalImageUrl" in variant)) return;
|
||||||
|
|
||||||
setCurrentIndex(index);
|
setSelectedVariantId(variant.id);
|
||||||
if (variant.extendedText) return openCropModal(
|
if (variant.extendedText) return openCropModal(
|
||||||
variant.extendedText,
|
variant.extendedText,
|
||||||
variant.originalImageUrl
|
variant.originalImageUrl
|
||||||
);
|
);
|
||||||
|
|
||||||
setIsUploadImageModalOpen(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
onPlusClick={() => {
|
onPlusClick={() => {
|
||||||
setCurrentIndex(index);
|
setSelectedVariantId(variant.id);
|
||||||
setIsUploadImageModalOpen(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
sx={{ mx: "10px" }}
|
sx={{ mx: "10px" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
additionalMobile={(variant, index) => (
|
additionalMobile={(variant) => (
|
||||||
<>
|
<>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<AddOrEditImageButton
|
<AddOrEditImageButton
|
||||||
@ -105,17 +99,17 @@ export default function OptionsAndPicture({ totalIndex }: Props) {
|
|||||||
onImageClick={() => {
|
onImageClick={() => {
|
||||||
if (!("originalImageUrl" in variant)) return;
|
if (!("originalImageUrl" in variant)) return;
|
||||||
|
|
||||||
setCurrentIndex(index);
|
setSelectedVariantId(variant.id);
|
||||||
if (variant.extendedText) return openCropModal(
|
if (variant.extendedText) return openCropModal(
|
||||||
variant.extendedText,
|
variant.extendedText,
|
||||||
variant.originalImageUrl
|
variant.originalImageUrl
|
||||||
);
|
);
|
||||||
|
|
||||||
setIsUploadImageModalOpen(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
onPlusClick={() => {
|
onPlusClick={() => {
|
||||||
setCurrentIndex(index);
|
setSelectedVariantId(variant.id);
|
||||||
setIsUploadImageModalOpen(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
sx={{ m: "8px", width: "auto" }}
|
sx={{ m: "8px", width: "auto" }}
|
||||||
/>
|
/>
|
||||||
@ -123,11 +117,7 @@ export default function OptionsAndPicture({ totalIndex }: Props) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<UploadImageModal
|
<UploadImageModal imgHC={handleImageUpload} />
|
||||||
open={isUploadImageModalOpen}
|
|
||||||
onClose={() => setIsUploadImageModalOpen(false)}
|
|
||||||
imgHC={handleImageUpload}
|
|
||||||
/>
|
|
||||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -322,16 +312,7 @@ export default function OptionsAndPicture({ totalIndex }: Props) {
|
|||||||
height: "19px",
|
height: "19px",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const clonedContent = { ...question.content };
|
addQuestionVariant(question.id)
|
||||||
clonedContent.variants.push({
|
|
||||||
answer: "",
|
|
||||||
hints: "",
|
|
||||||
extendedText: "",
|
|
||||||
originalImageUrl: "",
|
|
||||||
});
|
|
||||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
|
||||||
content: clonedContent,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Добавьте ответ
|
Добавьте ответ
|
||||||
@ -362,9 +343,9 @@ export default function OptionsAndPicture({ totalIndex }: Props) {
|
|||||||
<ButtonsOptionsAndPict
|
<ButtonsOptionsAndPict
|
||||||
switchState={switchState}
|
switchState={switchState}
|
||||||
SSHC={SSHC}
|
SSHC={SSHC}
|
||||||
totalIndex={totalIndex}
|
question={question}
|
||||||
/>
|
/>
|
||||||
<SwitchOptionsAndPict switchState={switchState} totalIndex={totalIndex} />
|
<SwitchOptionsAndPict switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,36 +1,32 @@
|
|||||||
import { useParams } from "react-router-dom";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
|
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||||
|
|
||||||
|
|
||||||
type SettingOptionsAndPictProps = {
|
type SettingOptionsAndPictProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionVarImg;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndPictProps) {
|
export default function SettingOptionsAndPict({ question }: SettingOptionsAndPictProps) {
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
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) => {
|
const setReplText = useDebouncedCallback((replText) => {
|
||||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, replText },
|
if (question.type !== "varimg") return;
|
||||||
|
|
||||||
|
question.content.replText = replText;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
const debounceDescription = useDebouncedCallback((value) => {
|
|
||||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
const setDescription = useDebouncedCallback((value) => {
|
||||||
content: { ...question.content, innerName: value },
|
setQuestionInnerName(question.id, value);
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -63,11 +59,11 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
|||||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||||
label={'Вариант "свой ответ"'}
|
label={'Вариант "свой ответ"'}
|
||||||
checked={question.content.own}
|
checked={question.content.own}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
if (question.type !== "varimg") return;
|
||||||
content: { ...question.content, own: target.checked },
|
|
||||||
});
|
question.content.own = target.checked;
|
||||||
}}
|
})}
|
||||||
/>
|
/>
|
||||||
{!isWrappColumn && (
|
{!isWrappColumn && (
|
||||||
<Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
<Box sx={{ mt: isMobile ? "11px" : "6px", width: "100%" }}>
|
||||||
@ -90,7 +86,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
|||||||
}}
|
}}
|
||||||
placeholder={"Пример текста"}
|
placeholder={"Пример текста"}
|
||||||
text={question.content.replText}
|
text={question.content.replText}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setReplText(target.value)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -116,11 +112,11 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
|||||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={question.content.required}
|
checked={question.content.required}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
if (question.type !== "varimg") return;
|
||||||
content: { ...question.content, required: target.checked },
|
|
||||||
});
|
question.content.required = target.checked;
|
||||||
}}
|
})}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
<CustomCheckbox
|
<CustomCheckbox
|
||||||
@ -130,15 +126,10 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
|||||||
}}
|
}}
|
||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
question.content.innerNameCheck = target.checked;
|
||||||
content: {
|
question.content.innerName = "";
|
||||||
...question.content,
|
})}
|
||||||
innerNameCheck: target.checked,
|
|
||||||
innerName: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||||
<Box>
|
<Box>
|
||||||
@ -150,7 +141,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Развёрнутое описание вопроса"}
|
placeholder={"Развёрнутое описание вопроса"}
|
||||||
text={question.content.innerName}
|
text={question.content.innerName}
|
||||||
onChange={({ target }) => debounceDescription(target.value)}
|
onChange={({ target }) => setDescription(target.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isWrappColumn && (
|
{isWrappColumn && (
|
||||||
@ -164,7 +155,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
|||||||
sx={{ maxWidth: "360px", width: "100%" }}
|
sx={{ maxWidth: "360px", width: "100%" }}
|
||||||
placeholder={"Пример текста"}
|
placeholder={"Пример текста"}
|
||||||
text={question.content.replText}
|
text={question.content.replText}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setReplText(target.value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,31 +1,28 @@
|
|||||||
import * as React from "react";
|
import { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
|
||||||
import SettingOptionsAndPict from "./SettingOptionsAndPict";
|
|
||||||
import HelpQuestions from "../helpQuestions";
|
|
||||||
import UploadImage from "../UploadImage";
|
import UploadImage from "../UploadImage";
|
||||||
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
|
import HelpQuestions from "../helpQuestions";
|
||||||
|
import SettingOptionsAndPict from "./SettingOptionsAndPict";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionVarImg;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchOptionsAndPict({
|
export default function SwitchOptionsAndPict({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingOptionsAndPict totalIndex={totalIndex} />;
|
return <SettingOptionsAndPict question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "image":
|
case "image":
|
||||||
return <UploadImage totalIndex={totalIndex} />;
|
return <UploadImage question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import {
|
|||||||
useTheme
|
useTheme
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { openCropModal } from "@root/cropModal";
|
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 AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
||||||
import { CropModal } from "@ui_kit/Modal/CropModal";
|
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
||||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||||
@ -24,49 +24,39 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function OptionsPicture({ question }: Props) {
|
export default function OptionsPicture({ question }: Props) {
|
||||||
const [isUploadImageModalOpen, setIsUploadImageModalOpen] = useState(false);
|
|
||||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
|
|
||||||
const SSHC = (data: string) => {
|
const SSHC = (data: string) => {
|
||||||
setSwitchState(data);
|
setSwitchState(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImageUpload = (files: FileList | null) => {
|
const handleImageUpload = (files: FileList | null) => {
|
||||||
if (!files?.length) return;
|
if (!files?.length || !selectedVariantId) return;
|
||||||
|
|
||||||
const [file] = Array.from(files);
|
const [file] = Array.from(files);
|
||||||
const url = URL.createObjectURL(file);
|
const url = URL.createObjectURL(file);
|
||||||
|
|
||||||
setVariantImageUrl(quizId, totalIndex, currentIndex, url);
|
setVariantImageUrl(question.id, selectedVariantId, url);
|
||||||
setVariantOriginalImageUrl(quizId, totalIndex, currentIndex, url);
|
setVariantOriginalImageUrl(question.id, selectedVariantId, url);
|
||||||
setIsUploadImageModalOpen(false);
|
closeImageUploadModal();
|
||||||
openCropModal(url, url);
|
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) {
|
function handleCropModalSaveClick(url: string) {
|
||||||
setVariantImageUrl(quizId, totalIndex, currentIndex, url);
|
if (!selectedVariantId) return;
|
||||||
|
|
||||||
|
setVariantImageUrl(question.id, selectedVariantId, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box sx={{ padding: "20px" }}>
|
<Box sx={{ padding: "20px" }}>
|
||||||
<AnswerDraggableList
|
<AnswerDraggableList
|
||||||
variants={question.content.variants}
|
question={question}
|
||||||
question={totalIndex}
|
additionalContent={(variant) => (
|
||||||
additionalContent={(variant, index) => (
|
|
||||||
<>
|
<>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<AddOrEditImageButton
|
<AddOrEditImageButton
|
||||||
@ -74,7 +64,7 @@ export default function OptionsPicture({ question }: Props) {
|
|||||||
onImageClick={() => {
|
onImageClick={() => {
|
||||||
if (!("originalImageUrl" in variant)) return;
|
if (!("originalImageUrl" in variant)) return;
|
||||||
|
|
||||||
setCurrentIndex(index);
|
setSelectedVariantId(variant.id);
|
||||||
if (variant.extendedText) {
|
if (variant.extendedText) {
|
||||||
return openCropModal(
|
return openCropModal(
|
||||||
variant.extendedText,
|
variant.extendedText,
|
||||||
@ -82,18 +72,18 @@ export default function OptionsPicture({ question }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsUploadImageModalOpen(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
onPlusClick={() => {
|
onPlusClick={() => {
|
||||||
setCurrentIndex(index);
|
setSelectedVariantId(variant.id);
|
||||||
setIsUploadImageModalOpen(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
sx={{ mx: "10px" }}
|
sx={{ mx: "10px" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
additionalMobile={(variant, index) => (
|
additionalMobile={(variant) => (
|
||||||
<>
|
<>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<AddOrEditImageButton
|
<AddOrEditImageButton
|
||||||
@ -101,7 +91,7 @@ export default function OptionsPicture({ question }: Props) {
|
|||||||
onImageClick={() => {
|
onImageClick={() => {
|
||||||
if (!("originalImageUrl" in variant)) return;
|
if (!("originalImageUrl" in variant)) return;
|
||||||
|
|
||||||
setCurrentIndex(index);
|
setSelectedVariantId(variant.id);
|
||||||
if (variant.extendedText) {
|
if (variant.extendedText) {
|
||||||
return openCropModal(
|
return openCropModal(
|
||||||
variant.extendedText,
|
variant.extendedText,
|
||||||
@ -109,11 +99,11 @@ export default function OptionsPicture({ question }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsUploadImageModalOpen(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
onPlusClick={() => {
|
onPlusClick={() => {
|
||||||
setCurrentIndex(index);
|
setSelectedVariantId(variant.id);
|
||||||
setIsUploadImageModalOpen(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
sx={{ m: "8px", width: "auto" }}
|
sx={{ m: "8px", width: "auto" }}
|
||||||
/>
|
/>
|
||||||
@ -121,18 +111,14 @@ export default function OptionsPicture({ question }: Props) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<UploadImageModal
|
<UploadImageModal imgHC={handleImageUpload} />
|
||||||
open={isUploadImageModalOpen}
|
|
||||||
onClose={() => setIsUploadImageModalOpen(false)}
|
|
||||||
imgHC={handleImageUpload}
|
|
||||||
/>
|
|
||||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||||
<Link
|
<Link
|
||||||
component="button"
|
component="button"
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ color: theme.palette.brightPurple.main }}
|
sx={{ color: theme.palette.brightPurple.main }}
|
||||||
onClick={addNewAnswer}
|
onClick={() => addQuestionVariant(question.id)}
|
||||||
>
|
>
|
||||||
Добавьте ответ
|
Добавьте ответ
|
||||||
</Link>
|
</Link>
|
||||||
@ -159,8 +145,8 @@ export default function OptionsPicture({ question }: Props) {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||||
<SwitchAnswerOptionsPict switchState={switchState} totalIndex={totalIndex} />
|
<SwitchAnswerOptionsPict switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,44 +1,29 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Typography,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2";
|
|
||||||
import FormatIcon1 from "../../../assets/icons/questionsPage/FormatIcon1";
|
import FormatIcon1 from "../../../assets/icons/questionsPage/FormatIcon1";
|
||||||
|
import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2";
|
||||||
import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11";
|
import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11";
|
||||||
import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21";
|
|
||||||
import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIcon12";
|
import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIcon12";
|
||||||
|
import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21";
|
||||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
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 Proportion = "1:1" | "2:1" | "1:2";
|
||||||
|
|
||||||
type ProportionItem = {
|
type ProportionItem = {
|
||||||
value: Proportion;
|
value: Proportion;
|
||||||
icon: (props: { color: string }) => JSX.Element;
|
icon: (props: { color: string; }) => JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PROPORTIONS: ProportionItem[] = [
|
const PROPORTIONS: ProportionItem[] = [
|
||||||
@ -47,75 +32,25 @@ const PROPORTIONS: ProportionItem[] = [
|
|||||||
{ value: "1:2", icon: ProportionsIcon12 },
|
{ value: "1:2", icon: ProportionsIcon12 },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SelectIconButton({ Icon, isActive = false, onClick }: Props) {
|
type SettingOpytionsPictProps = {
|
||||||
const theme = useTheme();
|
question: QuizQuestionImages;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
export default function SettingOpytionsPict({ question }: SettingOpytionsPictProps) {
|
||||||
<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,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SettingOpytionsPict({
|
|
||||||
totalIndex,
|
|
||||||
}: SettingOpytionsPictProps) {
|
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionImages;
|
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||||
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const debounced = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
setQuestionInnerName(question.id, value);
|
||||||
content: { ...question.content, innerName: value },
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!question.content.xy) {
|
|
||||||
updateProportions("1:1");
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const updateProportions = (proportions: Proportion) => {
|
const updateProportions = (proportions: Proportion) => {
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, xy: proportions },
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
|
question.content.xy = proportions;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -180,9 +115,10 @@ export default function SettingOpytionsPict({
|
|||||||
label={"Можно несколько"}
|
label={"Можно несколько"}
|
||||||
checked={question.content.multi}
|
checked={question.content.multi}
|
||||||
dataCy="multiple-answers-checkbox"
|
dataCy="multiple-answers-checkbox"
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
if (question.type !== "images") return;
|
||||||
content: { ...question.content, multi: target.checked },
|
|
||||||
|
question.content.multi = target.checked;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -193,11 +129,10 @@ export default function SettingOpytionsPict({
|
|||||||
label={"Большие картинки"}
|
label={"Большие картинки"}
|
||||||
checked={question.content.largeCheck}
|
checked={question.content.largeCheck}
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) =>
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "images") return;
|
||||||
...question.content,
|
|
||||||
largeCheck: target.checked,
|
question.content.largeCheck = target.checked;
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -206,9 +141,10 @@ export default function SettingOpytionsPict({
|
|||||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||||
label={'Вариант "свой ответ"'}
|
label={'Вариант "свой ответ"'}
|
||||||
checked={question.content.own}
|
checked={question.content.own}
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
if (question.type !== "images") return;
|
||||||
content: { ...question.content, own: target.checked },
|
|
||||||
|
question.content.own = target.checked;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -247,18 +183,20 @@ export default function SettingOpytionsPict({
|
|||||||
Формат
|
Формат
|
||||||
</Typography>
|
</Typography>
|
||||||
<SelectIconButton
|
<SelectIconButton
|
||||||
onClick={() =>
|
onClick={() => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
if (question.type !== "images") return;
|
||||||
content: { ...question.content, format: "carousel" },
|
|
||||||
|
question.content.format = "carousel";
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
isActive={question.content.format === "carousel"}
|
isActive={question.content.format === "carousel"}
|
||||||
Icon={FormatIcon2}
|
Icon={FormatIcon2}
|
||||||
/>
|
/>
|
||||||
<SelectIconButton
|
<SelectIconButton
|
||||||
onClick={() =>
|
onClick={() => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
if (question.type !== "images") return;
|
||||||
content: { ...question.content, format: "masonry" },
|
|
||||||
|
question.content.format = "masonry";
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
isActive={question.content.format === "masonry"}
|
isActive={question.content.format === "masonry"}
|
||||||
@ -274,9 +212,10 @@ export default function SettingOpytionsPict({
|
|||||||
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={question.content.required}
|
checked={question.content.required}
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
if (question.type !== "images") return;
|
||||||
content: { ...question.content, required: target.checked },
|
|
||||||
|
question.content.required = target.checked;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -294,13 +233,11 @@ export default function SettingOpytionsPict({
|
|||||||
}}
|
}}
|
||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
if (question.type !== "images") return;
|
||||||
content: {
|
|
||||||
...question.content,
|
question.content.innerNameCheck = target.checked;
|
||||||
innerNameCheck: target.checked,
|
question.content.innerName = "";
|
||||||
innerName: "",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -325,3 +262,53 @@ export default function SettingOpytionsPict({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,26 +2,24 @@ import * as React from "react";
|
|||||||
import BranchingQuestions from "../branchingQuestions";
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
import SettingOpytionsPict from "./settingOpytionsPict";
|
import SettingOpytionsPict from "./settingOpytionsPict";
|
||||||
import HelpQuestions from "../helpQuestions";
|
import HelpQuestions from "../helpQuestions";
|
||||||
|
import { QuizQuestionImages } from "@model/questionTypes/images";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionImages;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchAnswerOptionsPict({
|
export default function SwitchAnswerOptionsPict({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingOpytionsPict totalIndex={totalIndex} />;
|
return <SettingOpytionsPict question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,29 @@
|
|||||||
import { useState } from "react";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { useParams } from "react-router-dom";
|
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import { Box, Typography, Tooltip, useTheme, useMediaQuery } from "@mui/material";
|
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
|
||||||
|
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
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 ButtonsOptions from "../ButtonsOptions";
|
||||||
import SwitchTextField from "./switchTextField";
|
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 {
|
interface Props {
|
||||||
totalIndex: number;
|
question: QuizQuestionText;
|
||||||
}
|
}
|
||||||
export default function OwnTextField({ totalIndex }: Props) {
|
|
||||||
|
export default function OwnTextField({ question }: Props) {
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionText;
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||||
const debounced = useDebouncedCallback((value) => {
|
|
||||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
const setPlaceholder = useDebouncedCallback((value) => {
|
||||||
content: { ...question.content, placeholder: value },
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
|
if (question.type !== "text") return;
|
||||||
|
|
||||||
|
question.content.placeholder = value;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
@ -51,7 +48,7 @@ export default function OwnTextField({ totalIndex }: Props) {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Пример ответа"}
|
placeholder={"Пример ответа"}
|
||||||
text={question.content.placeholder}
|
text={question.content.placeholder}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setPlaceholder(target.value)}
|
||||||
sx={{ maxWidth: isFigmaTablte ? "549px" : "640px", width: "100%", mt: isMobile ? "15px" : "0px" }}
|
sx={{ maxWidth: isFigmaTablte ? "549px" : "640px", width: "100%", mt: isMobile ? "15px" : "0px" }}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ display: "flex", alignItems: isMobile ? "flex-start" : "center", gap: "12px" }}>
|
<Box sx={{ display: "flex", alignItems: isMobile ? "flex-start" : "center", gap: "12px" }}>
|
||||||
@ -72,8 +69,8 @@ export default function OwnTextField({ totalIndex }: Props) {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||||
<SwitchTextField switchState={switchState} totalIndex={totalIndex} />
|
<SwitchTextField switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,26 @@
|
|||||||
import { useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Typography,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import CheckedIcon from "@ui_kit/RadioCheck";
|
import CheckedIcon from "@ui_kit/RadioCheck";
|
||||||
import CheckIcon from "@ui_kit/RadioIcon";
|
import CheckIcon from "@ui_kit/RadioIcon";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
|
|
||||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||||
|
|
||||||
|
|
||||||
type SettingTextFieldProps = {
|
type SettingTextFieldProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionText;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Answer = {
|
type Answer = {
|
||||||
@ -37,20 +34,15 @@ const ANSWER_TYPES: Answer[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function SettingTextField({
|
export default function SettingTextField({
|
||||||
totalIndex,
|
question,
|
||||||
}: SettingTextFieldProps) {
|
}: SettingTextFieldProps) {
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionText;
|
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||||
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const debounced = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
setQuestionInnerName(question.id, value);
|
||||||
content: { ...question.content, innerName: value },
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -93,11 +85,10 @@ export default function SettingTextField({
|
|||||||
({ value }) => question.content.answerType === value
|
({ value }) => question.content.answerType === value
|
||||||
)}
|
)}
|
||||||
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "text") return;
|
||||||
...question.content,
|
|
||||||
answerType: ANSWER_TYPES[Number(target.value)].value,
|
question.content.answerType = ANSWER_TYPES[Number(target.value)].value;
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -128,8 +119,10 @@ export default function SettingTextField({
|
|||||||
label={"Только числа"}
|
label={"Только числа"}
|
||||||
checked={question.content.onlyNumbers}
|
checked={question.content.onlyNumbers}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, onlyNumbers: target.checked },
|
if (question.type !== "text") return;
|
||||||
|
|
||||||
|
question.content.onlyNumbers = target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -164,8 +157,8 @@ export default function SettingTextField({
|
|||||||
label={"Автозаполнение адреса"}
|
label={"Автозаполнение адреса"}
|
||||||
checked={question.content.autofill}
|
checked={question.content.autofill}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, autofill: target.checked },
|
question.content.autofill = target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -178,8 +171,8 @@ export default function SettingTextField({
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
required: !e.target.checked,
|
question.required = !e.target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -200,12 +193,11 @@ export default function SettingTextField({
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
question.content.innerNameCheck = target.checked;
|
||||||
...question.content,
|
question.content.innerName = target.checked
|
||||||
innerNameCheck: target.checked,
|
? question.content.innerName
|
||||||
innerName: target.checked ? question.content.innerName : "",
|
: "";
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,27 +1,25 @@
|
|||||||
import * as React from "react";
|
import { QuizQuestionText } from "@model/questionTypes/text";
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
import HelpQuestions from "../helpQuestions";
|
import HelpQuestions from "../helpQuestions";
|
||||||
import SettingTextField from "./settingTextField";
|
import SettingTextField from "./settingTextField";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionText;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchTextField({
|
export default function SwitchTextField({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingTextField totalIndex={totalIndex} />;
|
return <SettingTextField question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,39 +1,38 @@
|
|||||||
import { VideofileIcon } from "@icons/questionsPage/VideofileIcon";
|
import { VideofileIcon } from "@icons/questionsPage/VideofileIcon";
|
||||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
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 CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||||
import ButtonsOptions from "../ButtonsOptions";
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
import { UploadImageModal } from "../UploadImage/UploadImageModal";
|
import { UploadImageModal } from "../UploadImage/UploadImageModal";
|
||||||
import { UploadVideoModal } from "../UploadVideoModal";
|
import { UploadVideoModal } from "../UploadVideoModal";
|
||||||
import SwitchPageOptions from "./switchPageOptions";
|
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 = {
|
type Props = {
|
||||||
disableInput?: boolean;
|
disableInput?: boolean;
|
||||||
totalIndex: number;
|
question: QuizQuestionPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PageOptions({ disableInput, totalIndex }: Props) {
|
export default function PageOptions({ disableInput, question }: Props) {
|
||||||
const [openImageModal, setOpenImageModal] = useState<boolean>(false);
|
|
||||||
const [openVideoModal, setOpenVideoModal] = useState<boolean>(false);
|
const [openVideoModal, setOpenVideoModal] = useState<boolean>(false);
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(780));
|
const isMobile = useMediaQuery(theme.breakpoints.down(780));
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionPage;
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const setText = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, text: value },
|
if (question.type !== "page") return;
|
||||||
|
|
||||||
|
question.content.text = value;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
@ -46,14 +45,14 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
|||||||
|
|
||||||
const url = URL.createObjectURL(fileList[0]);
|
const url = URL.createObjectURL(fileList[0]);
|
||||||
|
|
||||||
setPageQuestionPicture(quizId, totalIndex, url);
|
setPageQuestionPicture(question.id, url);
|
||||||
setPageQuestionOriginalPicture(quizId, totalIndex, url);
|
setPageQuestionOriginalPicture(question.id, url);
|
||||||
setOpenImageModal(false);
|
closeImageUploadModal();
|
||||||
openCropModal(url, url);
|
openCropModal(url, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCropModalSaveClick(url: string) {
|
function handleCropModalSaveClick(url: string) {
|
||||||
setPageQuestionPicture(quizId, totalIndex, url);
|
setPageQuestionPicture(question.id, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -72,7 +71,7 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Можно добавить текст"}
|
placeholder={"Можно добавить текст"}
|
||||||
text={question.content.text}
|
text={question.content.text}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setText(target.value)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -104,10 +103,10 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpenImageModal(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
onPlusClick={() => {
|
onPlusClick={() => {
|
||||||
setOpenImageModal(true);
|
openImageUploadModal();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -123,11 +122,7 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
|||||||
Изображение
|
Изображение
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<UploadImageModal
|
<UploadImageModal imgHC={handleImageUpload} />
|
||||||
open={openImageModal}
|
|
||||||
onClose={() => setOpenImageModal(false)}
|
|
||||||
imgHC={handleImageUpload}
|
|
||||||
/>
|
|
||||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||||
<Typography> или</Typography>
|
<Typography> или</Typography>
|
||||||
<Box
|
<Box
|
||||||
@ -230,15 +225,17 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
|||||||
onClose={() => setOpenVideoModal(false)}
|
onClose={() => setOpenVideoModal(false)}
|
||||||
video={question.content.video}
|
video={question.content.video}
|
||||||
onUpload={(url) => {
|
onUpload={(url) => {
|
||||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, video: url },
|
if (question.type !== "page") return;
|
||||||
|
|
||||||
|
question.content.video = url;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||||
<SwitchPageOptions switchState={switchState} totalIndex={totalIndex} />
|
<SwitchPageOptions switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +1,30 @@
|
|||||||
import { useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
|
|
||||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||||
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
|
|
||||||
|
|
||||||
type SettingPageOptionsProps = {
|
type SettingPageOptionsProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingPageOptions({
|
export default function SettingPageOptions({
|
||||||
totalIndex,
|
question,
|
||||||
}: SettingPageOptionsProps) {
|
}: SettingPageOptionsProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionPage;
|
setQuestionInnerName(question.id, value);
|
||||||
const debounced = useDebouncedCallback((value) => {
|
|
||||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
|
||||||
content: { ...question.content, innerName: value },
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -67,12 +60,9 @@ export default function SettingPageOptions({
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) =>
|
||||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
question.content.innerNameCheck = target.checked;
|
||||||
...question.content,
|
question.content.innerName = "";
|
||||||
innerNameCheck: target.checked,
|
|
||||||
innerName: "",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -89,7 +79,7 @@ export default function SettingPageOptions({
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Внутреннее описание вопроса"}
|
placeholder={"Внутреннее описание вопроса"}
|
||||||
text={question.content.innerName}
|
text={question.content.innerName}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setInnerName(target.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,26 +1,25 @@
|
|||||||
|
import { QuizQuestionPage } from "@model/questionTypes/page";
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
import HelpQuestions from "../helpQuestions";
|
import HelpQuestions from "../helpQuestions";
|
||||||
import SettingPageOptions from "./SettingPageOptions";
|
import SettingPageOptions from "./SettingPageOptions";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchPageOptions({
|
export default function SwitchPageOptions({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingPageOptions totalIndex={totalIndex} />;
|
return <SettingPageOptions question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { createQuestion } from "@root/questions/actions";
|
import { collapseAllQuestions, createQuestion } from "@root/questions/actions";
|
||||||
import { incrementCurrentStep } from "@root/quizes/actions";
|
import { incrementCurrentStep } from "@root/quizes/actions";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||||
@ -21,15 +21,6 @@ export default function QuestionsPage() {
|
|||||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||||
const { quiz } = useCurrentQuiz();
|
const { quiz } = useCurrentQuiz();
|
||||||
|
|
||||||
const collapseEverything = () => { // TODO
|
|
||||||
// listQuestions[quizId].forEach((item, index) => {
|
|
||||||
// updateQuestionsList<AnyQuizQuestion>(quizId, index, {
|
|
||||||
// ...item,
|
|
||||||
// expanded: false,
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!quiz) return null;
|
if (!quiz) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -53,7 +44,7 @@ export default function QuestionsPage() {
|
|||||||
color: theme.palette.brightPurple.main,
|
color: theme.palette.brightPurple.main,
|
||||||
textDecorationColor: theme.palette.brightPurple.main,
|
textDecorationColor: theme.palette.brightPurple.main,
|
||||||
}}
|
}}
|
||||||
onClick={collapseEverything}
|
onClick={collapseAllQuestions}
|
||||||
>
|
>
|
||||||
Свернуть всё
|
Свернуть всё
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -8,10 +8,8 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
import ButtonsOptions from "../ButtonsOptions";
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
import SwitchRating from "./switchRating";
|
import SwitchRating from "./switchRating";
|
||||||
|
|
||||||
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
|
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
|
||||||
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
|
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
|
||||||
import HeartIcon from "../../../assets/icons/questionsPage/heartIcon";
|
import HeartIcon from "../../../assets/icons/questionsPage/heartIcon";
|
||||||
@ -19,11 +17,12 @@ import LikeIcon from "../../../assets/icons/questionsPage/likeIcon";
|
|||||||
import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon";
|
import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon";
|
||||||
import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon";
|
import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon";
|
||||||
import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini";
|
import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini";
|
||||||
|
|
||||||
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
||||||
|
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
totalIndex: number;
|
question: QuizQuestionRating;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ButtonRatingFrom = {
|
export type ButtonRatingFrom = {
|
||||||
@ -31,33 +30,30 @@ export type ButtonRatingFrom = {
|
|||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RatingOptions({ totalIndex }: Props) {
|
export default function RatingOptions({ question }: Props) {
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const [negativeText, setNegativeText] = useState<string>("");
|
const [negativeText, setNegativeText] = useState<string>("");
|
||||||
const [positiveText, setPositiveText] = useState<string>("");
|
const [positiveText, setPositiveText] = useState<string>("");
|
||||||
const [negativeTextWidth, setNegativeTextWidth] = useState<number>(0);
|
const [negativeTextWidth, setNegativeTextWidth] = useState<number>(0);
|
||||||
const [positiveTextWidth, setPositiveTextWidth] = useState<number>(0);
|
const [positiveTextWidth, setPositiveTextWidth] = useState<number>(0);
|
||||||
const quizId = Number(useParams().quizId);
|
const quizId = Number(useParams().quizId);
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionRating;
|
|
||||||
const negativeRef = useRef<HTMLDivElement>(null);
|
const negativeRef = useRef<HTMLDivElement>(null);
|
||||||
const positiveRef = useRef<HTMLDivElement>(null);
|
const positiveRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const debounceNegativeDescription = useDebouncedCallback((value) => {
|
const debounceNegativeDescription = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionRating>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "rating") return;
|
||||||
...question.content,
|
|
||||||
ratingNegativeDescription: value.substring(0, 15),
|
question.content.ratingNegativeDescription = value.substring(0, 15);
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
const debouncePositiveDescription = useDebouncedCallback((value) => {
|
const debouncePositiveDescription = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionRating>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "rating") return;
|
||||||
...question.content,
|
|
||||||
ratingPositiveDescription: value.substring(0, 15),
|
question.content.ratingPositiveDescription = value.substring(0, 15);
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
@ -124,16 +120,11 @@ export default function RatingOptions({ totalIndex }: Props) {
|
|||||||
{...(itemNumber === 0 || itemNumber === question.content.steps - 1
|
{...(itemNumber === 0 || itemNumber === question.content.steps - 1
|
||||||
? {
|
? {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
updateQuestionsList<QuizQuestionRating>(
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
quizId,
|
if (question.type !== "rating") return;
|
||||||
totalIndex,
|
|
||||||
{
|
question.content.ratingExpanded = true;
|
||||||
content: {
|
});
|
||||||
...question.content,
|
|
||||||
ratingExpanded: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
sx: {
|
sx: {
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
@ -270,9 +261,9 @@ export default function RatingOptions({ totalIndex }: Props) {
|
|||||||
<ButtonsOptions
|
<ButtonsOptions
|
||||||
switchState={switchState}
|
switchState={switchState}
|
||||||
SSHC={SSHC}
|
SSHC={SSHC}
|
||||||
totalIndex={totalIndex}
|
question={question}
|
||||||
/>
|
/>
|
||||||
<SwitchRating switchState={switchState} totalIndex={totalIndex} />
|
<SwitchRating switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,39 +1,32 @@
|
|||||||
import { useParams } from "react-router-dom";
|
import { QuizQuestionRating } from "@model/questionTypes/rating";
|
||||||
import { Box, ButtonBase, Slider, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, ButtonBase, Slider, 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 CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
|
|
||||||
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
|
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 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 { ButtonRatingFrom } from "./RatingOptions";
|
||||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
|
||||||
|
|
||||||
type SettingSliderProps = {
|
type SettingSliderProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionRating;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
export default function SettingSlider({ question }: SettingSliderProps) {
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionNumber;
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
const debounced = useDebouncedCallback((value) => {
|
setQuestionInnerName(question.id, value);
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
|
||||||
content: { ...question.content, innerName: value },
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const buttonRatingForm: ButtonRatingFrom[] = [
|
const buttonRatingForm: ButtonRatingFrom[] = [
|
||||||
@ -86,8 +79,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
<ButtonBase
|
<ButtonBase
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, form: name },
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
|
question.content.form = name;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
@ -126,8 +121,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
valueLabelDisplay="auto"
|
valueLabelDisplay="auto"
|
||||||
sx={{ color: theme.palette.brightPurple.main, padding: "0" }}
|
sx={{ color: theme.palette.brightPurple.main, padding: "0" }}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, steps: Number(value) || 1 },
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
|
question.content.steps = Number(value) || 1;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -153,8 +150,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
required: !e.target.checked,
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
|
question.required = !e.target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -170,12 +169,11 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "rating") return;
|
||||||
...question.content,
|
|
||||||
innerNameCheck: target.checked,
|
question.content.innerNameCheck = target.checked;
|
||||||
innerName: target.checked ? question.content.innerName : "",
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -189,7 +187,7 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Развёрнутое описание вопроса"}
|
placeholder={"Развёрнутое описание вопроса"}
|
||||||
text={question.content.innerName}
|
text={question.content.innerName}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setInnerName(target.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,26 +1,25 @@
|
|||||||
|
import { QuizQuestionRating } from "@model/questionTypes/rating";
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
import HelpQuestions from "../helpQuestions";
|
import HelpQuestions from "../helpQuestions";
|
||||||
import SettingRating from "./settingRating";
|
import SettingRating from "./settingRating";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionRating;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchRating({
|
export default function SwitchRating({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingRating totalIndex={totalIndex} />;
|
return <SettingRating question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +1,22 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import ButtonsOptions from "../ButtonsOptions";
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
import CustomNumberField from "@ui_kit/CustomNumberField";
|
import CustomNumberField from "@ui_kit/CustomNumberField";
|
||||||
import SwitchSlider from "./switchSlider";
|
import SwitchSlider from "./switchSlider";
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||||
|
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
totalIndex: number;
|
question: QuizQuestionNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SliderOptions({ totalIndex }: Props) {
|
export default function SliderOptions({ question }: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const [stepError, setStepError] = useState("");
|
const [stepError, setStepError] = useState("");
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionNumber;
|
|
||||||
|
|
||||||
const SSHC = (data: string) => {
|
const SSHC = (data: string) => {
|
||||||
setSwitchState(data);
|
setSwitchState(data);
|
||||||
@ -60,13 +56,10 @@ export default function SliderOptions({ totalIndex }: Props) {
|
|||||||
max={99}
|
max={99}
|
||||||
value={question.content.range.split("—")[0]}
|
value={question.content.range.split("—")[0]}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "number") return;
|
||||||
...question.content,
|
|
||||||
range: `${target.value}—${
|
question.content.range = `${target.value}—${question.content.range.split("—")[1]}`;
|
||||||
question.content.range.split("—")[1]
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onBlur={({ target }) => {
|
onBlur={({ target }) => {
|
||||||
@ -75,19 +68,18 @@ export default function SliderOptions({ totalIndex }: Props) {
|
|||||||
const max = Number(question.content.range.split("—")[1]);
|
const max = Number(question.content.range.split("—")[1]);
|
||||||
|
|
||||||
if (min >= max) {
|
if (min >= max) {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "number") return;
|
||||||
...question.content,
|
|
||||||
range: `${max - 1 >= 0 ? max - 1 : 0}—${
|
question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${question.content.range.split("—")[1]}`;
|
||||||
question.content.range.split("—")[1]
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start < min) {
|
if (start < min) {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, start: min },
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.content.start = min;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -100,13 +92,10 @@ export default function SliderOptions({ totalIndex }: Props) {
|
|||||||
max={100}
|
max={100}
|
||||||
value={question.content.range.split("—")[1]}
|
value={question.content.range.split("—")[1]}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "number") return;
|
||||||
...question.content,
|
|
||||||
range: `${question.content.range.split("—")[0]}—${
|
question.content.range = `${question.content.range.split("—")[0]}—${target.value}`;
|
||||||
target.value
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onBlur={({ target }) => {
|
onBlur={({ target }) => {
|
||||||
@ -117,25 +106,26 @@ export default function SliderOptions({ totalIndex }: Props) {
|
|||||||
const range = max - min;
|
const range = max - min;
|
||||||
|
|
||||||
if (max <= min) {
|
if (max <= min) {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "number") return;
|
||||||
...question.content,
|
|
||||||
range: `${question.content.range.split("—")[0]}—${
|
question.content.range = `${min}—${min + 1 >= 100 ? 100 : min + 1}`;
|
||||||
min + 1 >= 100 ? 100 : min + 1
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start > max) {
|
if (start > max) {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, start: max },
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.content.start = max;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step > max) {
|
if (step > max) {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, step: max },
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.content.step = min;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (range % step) {
|
if (range % step) {
|
||||||
@ -168,8 +158,10 @@ export default function SliderOptions({ totalIndex }: Props) {
|
|||||||
max={Number(question.content.range.split("—")[1])}
|
max={Number(question.content.range.split("—")[1])}
|
||||||
value={String(question.content.start)}
|
value={String(question.content.start)}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, start: Number(target.value) },
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.content.start = Number(target.value);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -193,8 +185,10 @@ export default function SliderOptions({ totalIndex }: Props) {
|
|||||||
error={stepError}
|
error={stepError}
|
||||||
value={String(question.content.step)}
|
value={String(question.content.step)}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, step: Number(target.value) },
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.content.step = Number(target.value);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onBlur={({ target }) => {
|
onBlur={({ target }) => {
|
||||||
@ -204,8 +198,10 @@ export default function SliderOptions({ totalIndex }: Props) {
|
|||||||
const step = Number(target.value);
|
const step = Number(target.value);
|
||||||
|
|
||||||
if (step > max) {
|
if (step > max) {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, step: max },
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.content.step = max;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,8 +215,8 @@ export default function SliderOptions({ totalIndex }: Props) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} />
|
||||||
<SwitchSlider switchState={switchState} totalIndex={totalIndex} />
|
<SwitchSlider switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,24 @@
|
|||||||
import { useParams } from "react-router-dom";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material";
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||||
|
|
||||||
|
|
||||||
type SettingSliderProps = {
|
type SettingSliderProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionNumber;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
export default function SettingSlider({ question }: SettingSliderProps) {
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
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 QuizQuestionNumber;
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
setQuestionInnerName(question.id, value);
|
||||||
content: { ...question.content, innerName: value },
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -57,8 +52,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
label={"Выбор диапозона (два ползунка)"}
|
label={"Выбор диапозона (два ползунка)"}
|
||||||
checked={question.content.chooseRange}
|
checked={question.content.chooseRange}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, chooseRange: target.checked },
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.content.chooseRange = target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -83,8 +80,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
required: !e.target.checked,
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
|
question.required = !e.target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -104,12 +103,11 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
if (question.type !== "number") return;
|
||||||
...question.content,
|
|
||||||
innerNameCheck: target.checked,
|
question.content.innerNameCheck = target.checked;
|
||||||
innerName: target.checked ? question.content.innerName : "",
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -123,7 +121,7 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Развёрнутое описание вопроса"}
|
placeholder={"Развёрнутое описание вопроса"}
|
||||||
text={question.content.innerName}
|
text={question.content.innerName}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setInnerName(target.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,27 +1,25 @@
|
|||||||
import * as React from "react";
|
import { QuizQuestionNumber } from "@model/questionTypes/number";
|
||||||
import HelpQuestions from "../helpQuestions";
|
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
|
import HelpQuestions from "../helpQuestions";
|
||||||
import SettingSlider from "./settingSlider";
|
import SettingSlider from "./settingSlider";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchSlider({
|
export default function SwitchSlider({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingSlider totalIndex={totalIndex} />;
|
return <SettingSlider question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,25 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
FormControl,
|
FormControl,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
SelectChangeEvent,
|
SelectChangeEvent,
|
||||||
Typography,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ButtonsOptions from "../ButtonsOptions";
|
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
|
||||||
import ArrowDown from "../../../assets/icons/ArrowDownIcon";
|
import ArrowDown from "../../../assets/icons/ArrowDownIcon";
|
||||||
import SwitchUpload from "./switchUpload";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
|
|
||||||
import type { AnyQuizQuestion } from "../../../model/questionTypes/shared";
|
|
||||||
import type {
|
import type {
|
||||||
QuizQuestionFile,
|
QuizQuestionFile,
|
||||||
UploadFileType,
|
UploadFileType,
|
||||||
} from "../../../model/questionTypes/file";
|
} from "../../../model/questionTypes/file";
|
||||||
|
import ButtonsOptions from "../ButtonsOptions";
|
||||||
|
import SwitchUpload from "./switchUpload";
|
||||||
|
|
||||||
interface Props {
|
|
||||||
totalIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type DesignItem = {
|
type DesignItem = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -42,13 +34,14 @@ const DESIGN_TYPES: DesignItem[] = [
|
|||||||
{ name: "Документ", value: "document" },
|
{ name: "Документ", value: "document" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function UploadFile({ totalIndex }: Props) {
|
interface Props {
|
||||||
|
question: QuizQuestionFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UploadFile({ question }: Props) {
|
||||||
const [switchState, setSwitchState] = useState("setting");
|
const [switchState, setSwitchState] = useState("setting");
|
||||||
const quizId = Number(useParams().quizId);
|
|
||||||
const { listQuestions } = questionStore();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionFile;
|
|
||||||
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
|
|
||||||
@ -57,8 +50,10 @@ export default function UploadFile({ totalIndex }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = ({ target }: SelectChangeEvent) => {
|
const handleChange = ({ target }: SelectChangeEvent) => {
|
||||||
updateQuestionsList<AnyQuizQuestion>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, type: target.value as UploadFileType },
|
if (question.type !== "file") return;
|
||||||
|
|
||||||
|
question.content.type = target.value as UploadFileType;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,8 +63,10 @@ export default function UploadFile({ totalIndex }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!isTypeSetted) {
|
if (!isTypeSetted) {
|
||||||
updateQuestionsList<AnyQuizQuestion>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, type: DESIGN_TYPES[0].value },
|
if (question.type !== "file") return;
|
||||||
|
|
||||||
|
question.content.type = DESIGN_TYPES[0].value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@ -194,9 +191,9 @@ export default function UploadFile({ totalIndex }: Props) {
|
|||||||
<ButtonsOptions
|
<ButtonsOptions
|
||||||
switchState={switchState}
|
switchState={switchState}
|
||||||
SSHC={SSHC}
|
SSHC={SSHC}
|
||||||
totalIndex={totalIndex}
|
question={question}
|
||||||
/>
|
/>
|
||||||
<SwitchUpload switchState={switchState} totalIndex={totalIndex} />
|
<SwitchUpload switchState={switchState} question={question} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +1,30 @@
|
|||||||
import { useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
Tooltip,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
|
||||||
|
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
|
|
||||||
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
||||||
|
|
||||||
|
|
||||||
type SettingsUploadProps = {
|
type SettingsUploadProps = {
|
||||||
totalIndex: number;
|
question: QuizQuestionFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SettingsUpload({ totalIndex }: SettingsUploadProps) {
|
export default function SettingsUpload({ question }: SettingsUploadProps) {
|
||||||
const theme = useTheme();
|
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));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
|
|
||||||
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
|
setQuestionInnerName(question.id, value);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -55,8 +48,8 @@ export default function SettingsUpload({ totalIndex }: SettingsUploadProps) {
|
|||||||
label={"Автозаполнение адреса"}
|
label={"Автозаполнение адреса"}
|
||||||
checked={question.content.autofill}
|
checked={question.content.autofill}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: { ...question.content, autofill: target.checked },
|
question.content.autofill = target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -68,8 +61,8 @@ export default function SettingsUpload({ totalIndex }: SettingsUploadProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
required: !e.target.checked,
|
question.required = !e.target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -89,12 +82,9 @@ export default function SettingsUpload({ totalIndex }: SettingsUploadProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
updateQuestionWithFnOptimistic(question.id, question => {
|
||||||
content: {
|
question.content.innerNameCheck = target.checked;
|
||||||
...question.content,
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
innerNameCheck: target.checked,
|
|
||||||
innerName: target.checked ? question.content.innerName : "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -111,7 +101,7 @@ export default function SettingsUpload({ totalIndex }: SettingsUploadProps) {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder={"Развёрнутое описание вопроса"}
|
placeholder={"Развёрнутое описание вопроса"}
|
||||||
text={question.content.innerName}
|
text={question.content.innerName}
|
||||||
onChange={({ target }) => debounced(target.value)}
|
onChange={({ target }) => setInnerName(target.value)}
|
||||||
sx={{ paddingRight: "20px" }}
|
sx={{ paddingRight: "20px" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,27 +1,25 @@
|
|||||||
import * as React from "react";
|
import { QuizQuestionFile } from "@model/questionTypes/file";
|
||||||
import BranchingQuestions from "../branchingQuestions";
|
import BranchingQuestions from "../branchingQuestions";
|
||||||
import HelpQuestions from "../helpQuestions";
|
import HelpQuestions from "../helpQuestions";
|
||||||
import SettingsUpload from "./settingUpload";
|
import SettingsUpload from "./settingUpload";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
switchState: string;
|
switchState: string;
|
||||||
totalIndex: number;
|
question: QuizQuestionFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchUpload({
|
export default function SwitchUpload({
|
||||||
switchState = "setting",
|
switchState = "setting",
|
||||||
totalIndex,
|
question,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (switchState) {
|
switch (switchState) {
|
||||||
case "setting":
|
case "setting":
|
||||||
return <SettingsUpload totalIndex={totalIndex} />;
|
return <SettingsUpload question={question} />;
|
||||||
break;
|
|
||||||
case "help":
|
case "help":
|
||||||
return <HelpQuestions totalIndex={totalIndex} />;
|
return <HelpQuestions question={question} />;
|
||||||
break;
|
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
return <BranchingQuestions question={question} />;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,19 +14,17 @@ import * as React from "react";
|
|||||||
import UnsplashIcon from "../../../assets/icons/Unsplash.svg";
|
import UnsplashIcon from "../../../assets/icons/Unsplash.svg";
|
||||||
|
|
||||||
import type { DragEvent } from "react";
|
import type { DragEvent } from "react";
|
||||||
|
import { closeImageUploadModal, useImageUploadModalStore } from "@root/imageUploadModal";
|
||||||
|
|
||||||
interface ModalkaProps {
|
interface ModalkaProps {
|
||||||
open: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
imgHC: (imgInp: FileList | null) => void;
|
imgHC: (imgInp: FileList | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UploadImageModal: React.FC<ModalkaProps> = ({
|
export const UploadImageModal: React.FC<ModalkaProps> = ({
|
||||||
open,
|
|
||||||
onClose,
|
|
||||||
imgHC,
|
imgHC,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const isOpen = useImageUploadModalStore(state => state.isOpen)
|
||||||
|
|
||||||
const dropZone = React.useRef<HTMLDivElement>(null);
|
const dropZone = React.useRef<HTMLDivElement>(null);
|
||||||
const [ready, setReady] = React.useState(false);
|
const [ready, setReady] = React.useState(false);
|
||||||
@ -45,8 +43,8 @@ export const UploadImageModal: React.FC<ModalkaProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={isOpen}
|
||||||
onClose={onClose}
|
onClose={closeImageUploadModal}
|
||||||
aria-labelledby="modal-modal-title"
|
aria-labelledby="modal-modal-title"
|
||||||
aria-describedby="modal-modal-description"
|
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 { Box, ButtonBase, Typography, useTheme } from "@mui/material";
|
||||||
import { openCropModal } from "@root/cropModal";
|
import { openCropModal } from "@root/cropModal";
|
||||||
|
import { closeImageUploadModal, openImageUploadModal } from "@root/imageUploadModal";
|
||||||
import { setQuestionBackgroundImage, setQuestionOriginalBackgroundImage } from "@root/questions/actions";
|
import { setQuestionBackgroundImage, setQuestionOriginalBackgroundImage } from "@root/questions/actions";
|
||||||
import { CropModal } from "@ui_kit/Modal/CropModal";
|
import { CropModal } from "@ui_kit/Modal/CropModal";
|
||||||
import UploadBox from "@ui_kit/UploadBox";
|
import UploadBox from "@ui_kit/UploadBox";
|
||||||
import { useState, type DragEvent } from "react";
|
import { type DragEvent } from "react";
|
||||||
import UploadIcon from "../../../assets/icons/UploadIcon";
|
import UploadIcon from "../../../assets/icons/UploadIcon";
|
||||||
import { UploadImageModal } from "./UploadImageModal";
|
import { UploadImageModal } from "./UploadImageModal";
|
||||||
|
|
||||||
|
|
||||||
type UploadImageProps = {
|
type UploadImageProps = {
|
||||||
question: QuizQuestionVariant;
|
question: AnyQuizQuestion;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function UploadImage({ question }: UploadImageProps) {
|
export default function UploadImage({ question }: UploadImageProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [isUploadImageModalOpen, setIsUploadImageModalOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleImageUpload = (files: FileList | null) => {
|
const handleImageUpload = (files: FileList | null) => {
|
||||||
if (!files?.length) return;
|
if (!files?.length) return;
|
||||||
@ -26,7 +26,7 @@ export default function UploadImage({ question }: UploadImageProps) {
|
|||||||
|
|
||||||
setQuestionBackgroundImage(question.id, url);
|
setQuestionBackgroundImage(question.id, url);
|
||||||
setQuestionOriginalBackgroundImage(question.id, url);
|
setQuestionOriginalBackgroundImage(question.id, url);
|
||||||
setIsUploadImageModalOpen(false);
|
closeImageUploadModal();
|
||||||
openCropModal(url, url);
|
openCropModal(url, url);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ export default function UploadImage({ question }: UploadImageProps) {
|
|||||||
Загрузить изображение
|
Загрузить изображение
|
||||||
</Typography>
|
</Typography>
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
onClick={() => setIsUploadImageModalOpen(true)}
|
onClick={openImageUploadModal}
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: "260px",
|
maxWidth: "260px",
|
||||||
@ -81,11 +81,7 @@ export default function UploadImage({ question }: UploadImageProps) {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<UploadImageModal
|
<UploadImageModal imgHC={handleImageUpload} />
|
||||||
open={isUploadImageModalOpen}
|
|
||||||
onClose={() => setIsUploadImageModalOpen(false)}
|
|
||||||
imgHC={handleImageUpload}
|
|
||||||
/>
|
|
||||||
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
<CropModal onSaveImageClick={handleCropModalSaveClick} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -37,10 +37,7 @@ export default function AnswerOptions({ question }: Props) {
|
|||||||
Добавьте ответ
|
Добавьте ответ
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<AnswerDraggableList
|
<AnswerDraggableList question={question} />
|
||||||
variants={question.content.variants}
|
|
||||||
question={question}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
@ -25,9 +25,7 @@ export default function ResponseSettings({ question }: Props) {
|
|||||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||||
|
|
||||||
const updateQuestionInnerName = useDebouncedCallback((value) => {
|
const updateQuestionInnerName = useDebouncedCallback((value) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
setQuestionInnerName(question.id, value);
|
||||||
question.content.innerName = value;
|
|
||||||
});
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import InfoIcon from "@icons/Info";
|
import InfoIcon from "@icons/Info";
|
||||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||||
import { QuizQuestionVariant } from "@model/questionTypes/variant";
|
import { AnyQuizQuestion } from "@model/questionTypes/shared";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -24,7 +24,7 @@ import { Select } from "./Select";
|
|||||||
|
|
||||||
|
|
||||||
type BranchingQuestionsProps = {
|
type BranchingQuestionsProps = {
|
||||||
question: QuizQuestionVariant;
|
question: AnyQuizQuestion;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ACTIONS = ["Показать", "Скрыть"];
|
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 { Box, ButtonBase, Typography } from "@mui/material";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
@ -13,7 +13,7 @@ import { UploadVideoModal } from "./UploadVideoModal";
|
|||||||
type BackgroundType = "text" | "video";
|
type BackgroundType = "text" | "video";
|
||||||
|
|
||||||
type HelpQuestionsProps = {
|
type HelpQuestionsProps = {
|
||||||
question: QuizQuestionVariant;
|
question: AnyQuizQuestion;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HelpQuestions({ question }: HelpQuestionsProps) {
|
export default function HelpQuestions({ question }: HelpQuestionsProps) {
|
||||||
|
|||||||
@ -46,7 +46,8 @@ export default function SwitchResult({
|
|||||||
return <ResponseSettings />;
|
return <ResponseSettings />;
|
||||||
break;
|
break;
|
||||||
case "branching":
|
case "branching":
|
||||||
return <BranchingQuestions totalIndex={totalIndex} />;
|
// return <BranchingQuestions question={question} />;
|
||||||
|
return null
|
||||||
break;
|
break;
|
||||||
case "points":
|
case "points":
|
||||||
return <PointsQuestions />;
|
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 { questionApi } from "@api/question";
|
||||||
import { devlog } from "@frontend/kitui";
|
import { devlog } from "@frontend/kitui";
|
||||||
import { questionToEditQuestionRequest } from "@model/question/edit";
|
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 { AnyQuizQuestion, ImageQuestionVariant, QuestionVariant, createQuestionImageVariant, createQuestionVariant } from "@model/questionTypes/shared";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
@ -11,44 +11,37 @@ import { QuestionsStore, useQuestionsStore } from "./store";
|
|||||||
|
|
||||||
|
|
||||||
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
||||||
state.questionsById = {};
|
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
|
||||||
if (questions === null) return;
|
|
||||||
|
|
||||||
questions.forEach(question => state.questionsById[question.id] = rawQuestionToQuestion(question));
|
|
||||||
}, {
|
}, {
|
||||||
type: "setQuestions",
|
type: "setQuestions",
|
||||||
questions,
|
questions,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
|
const setQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
|
||||||
state.questionsById[question.id] = question;
|
const index = state.questions.findIndex(q => q.id === question.id);
|
||||||
|
state.questions.splice(index, 1, question);
|
||||||
}, {
|
}, {
|
||||||
type: "setQuestion",
|
type: "setQuestion",
|
||||||
question,
|
question,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const removeQuestion = (questionId: number) => setProducedState(state => {
|
const removeQuestion = (questionId: number) => setProducedState(state => {
|
||||||
delete state.questionsById[questionId];
|
const index = state.questions.findIndex(q => q.id === questionId);
|
||||||
|
state.questions.splice(index, 1);
|
||||||
}, {
|
}, {
|
||||||
type: "removeQuestion",
|
type: "removeQuestion",
|
||||||
questionId,
|
questionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setQuestionField = <T extends keyof AnyQuizQuestion>(
|
const setQuestionField = <T extends keyof AnyQuizQuestion>(
|
||||||
questionId: number,
|
questionId: number,
|
||||||
field: T,
|
field: T,
|
||||||
value: AnyQuizQuestion[T],
|
value: AnyQuizQuestion[T],
|
||||||
) => setProducedState(state => {
|
) => setProducedState(state => {
|
||||||
const question = state.questionsById[questionId];
|
const question = state.questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
const oldId = question.id;
|
|
||||||
question[field] = value;
|
question[field] = value;
|
||||||
|
|
||||||
if (field === "id") {
|
|
||||||
delete state.questionsById[oldId];
|
|
||||||
state.questionsById[value as number] = question;
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
type: "setQuestionField",
|
type: "setQuestionField",
|
||||||
questionId,
|
questionId,
|
||||||
@ -56,15 +49,31 @@ export const setQuestionField = <T extends keyof AnyQuizQuestion>(
|
|||||||
value,
|
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 => {
|
export const toggleExpandQuestion = (questionId: number) => setProducedState(state => {
|
||||||
const question = state.questionsById[questionId];
|
const question = state.questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
question.expanded = !question.expanded;
|
question.expanded = !question.expanded;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const collapseAllQuestions = () => setProducedState(state => {
|
||||||
|
state.questions.forEach(question => question.expanded = false);
|
||||||
|
});
|
||||||
|
|
||||||
export const toggleOpenQuestionModal = (questionId: number) => setProducedState(state => {
|
export const toggleOpenQuestionModal = (questionId: number) => setProducedState(state => {
|
||||||
const question = state.questionsById[questionId];
|
const question = state.questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
question.openedModalSettings = !question.openedModalSettings;
|
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 savedOriginalQuestion: AnyQuizQuestion | null = null;
|
||||||
let controller: AbortController | null = null;
|
let controller: AbortController | null = null;
|
||||||
@ -191,7 +278,7 @@ export const updateQuestionWithFnOptimistic = async (
|
|||||||
questionId: number,
|
questionId: number,
|
||||||
updateFn: (question: AnyQuizQuestion) => void,
|
updateFn: (question: AnyQuizQuestion) => void,
|
||||||
) => {
|
) => {
|
||||||
const question = useQuestionsStore.getState().questionsById[questionId] ?? null;
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
const currentUpdatedQuestion = produce(question, updateFn);
|
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 {
|
try {
|
||||||
const question = await questionApi.create({
|
const question = await questionApi.create({
|
||||||
quiz_id: quizId,
|
quiz_id: quizId,
|
||||||
|
type,
|
||||||
});
|
});
|
||||||
|
|
||||||
setQuestion(rawQuestionToQuestion(question));
|
setQuestion(rawQuestionToQuestion(question));
|
||||||
@ -257,10 +345,12 @@ export const copyQuestion = async (questionId: number, quizId: number) => {
|
|||||||
const { updated: newQuestionId } = await questionApi.copy(questionId, quizId);
|
const { updated: newQuestionId } = await questionApi.copy(questionId, quizId);
|
||||||
|
|
||||||
setProducedState(state => {
|
setProducedState(state => {
|
||||||
const question = state.questionsById[questionId];
|
const question = state.questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
state.questionsById[newQuestionId] = question;
|
const copiedQuestion = structuredClone(question);
|
||||||
|
copiedQuestion.id = newQuestionId;
|
||||||
|
state.questions.push(copiedQuestion);
|
||||||
}, {
|
}, {
|
||||||
type: "copyQuestion",
|
type: "copyQuestion",
|
||||||
questionId,
|
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 = {
|
export type QuestionsStore = {
|
||||||
questionsById: Record<number, AnyQuizQuestion | undefined>;
|
questions: AnyQuizQuestion[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: QuestionsStore = {
|
const initialState: QuestionsStore = {
|
||||||
questionsById: {},
|
questions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useQuestionsStore = create<QuestionsStore>()(
|
export const useQuestionsStore = create<QuestionsStore>()(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user