fix edit requests logic & refactor
This commit is contained in:
parent
0ba8472220
commit
cbfa4a13d8
@ -71,7 +71,7 @@ function addQuizImages(quizId: number, image: Blob) {
|
|||||||
return makeRequest<FormData, never>({
|
return makeRequest<FormData, never>({
|
||||||
url: `${baseUrl}/quiz/putImages`,
|
url: `${baseUrl}/quiz/putImages`,
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST",
|
method: "PUT",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +100,6 @@ const defaultCreateQuizBody: CreateQuizRequest = {
|
|||||||
"due_to": 0,
|
"due_to": 0,
|
||||||
"time_of_passing": 0,
|
"time_of_passing": 0,
|
||||||
"pausable": false,
|
"pausable": false,
|
||||||
"question_cnt": 0,
|
|
||||||
"super": false,
|
"super": false,
|
||||||
"group_id": 0,
|
"group_id": 0,
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { QuizQuestionBase } from "../model/questionTypes/shared";
|
import type { QuizQuestionBase } from "../model/questionTypes/shared";
|
||||||
|
|
||||||
|
|
||||||
export const QUIZ_QUESTION_BASE: Omit<QuizQuestionBase, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_BASE: Omit<QuizQuestionBase, "id" | "backendId"> = {
|
||||||
quizId: 0,
|
quizId: 0,
|
||||||
description: "",
|
description: "",
|
||||||
page: 0,
|
page: 0,
|
||||||
|
@ -2,7 +2,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
|
|
||||||
import type { QuizQuestionDate } from "../model/questionTypes/date";
|
import type { QuizQuestionDate } from "../model/questionTypes/date";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_DATE: Omit<QuizQuestionDate, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_DATE: Omit<QuizQuestionDate, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "date",
|
type: "date",
|
||||||
content: {
|
content: {
|
||||||
|
@ -13,7 +13,7 @@ import { QUIZ_QUESTION_VARIANT } from "./variant";
|
|||||||
import { QUIZ_QUESTION_VARIMG } from "./varimg";
|
import { QUIZ_QUESTION_VARIMG } from "./varimg";
|
||||||
|
|
||||||
|
|
||||||
export const defaultQuestionByType: Record<QuestionType, Omit<AnyQuizQuestion, "id" | "fixedId">> = {
|
export const defaultQuestionByType: Record<QuestionType, Omit<AnyQuizQuestion, "id" | "backendId">> = {
|
||||||
"date": QUIZ_QUESTION_DATE,
|
"date": QUIZ_QUESTION_DATE,
|
||||||
"emoji": QUIZ_QUESTION_EMOJI,
|
"emoji": QUIZ_QUESTION_EMOJI,
|
||||||
"file": QUIZ_QUESTION_FILE,
|
"file": QUIZ_QUESTION_FILE,
|
||||||
|
@ -3,7 +3,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
import type { QuizQuestionEmoji } from "../model/questionTypes/emoji";
|
import type { QuizQuestionEmoji } from "../model/questionTypes/emoji";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_EMOJI: Omit<QuizQuestionEmoji, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_EMOJI: Omit<QuizQuestionEmoji, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "emoji",
|
type: "emoji",
|
||||||
content: {
|
content: {
|
||||||
|
@ -2,7 +2,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
|
|
||||||
import type { QuizQuestionFile } from "../model/questionTypes/file";
|
import type { QuizQuestionFile } from "../model/questionTypes/file";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_FILE: Omit<QuizQuestionFile, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_FILE: Omit<QuizQuestionFile, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "file",
|
type: "file",
|
||||||
content: {
|
content: {
|
||||||
|
@ -3,7 +3,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
import type { QuizQuestionImages } from "../model/questionTypes/images";
|
import type { QuizQuestionImages } from "../model/questionTypes/images";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_IMAGES: Omit<QuizQuestionImages, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_IMAGES: Omit<QuizQuestionImages, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "images",
|
type: "images",
|
||||||
content: {
|
content: {
|
||||||
|
@ -2,7 +2,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
|
|
||||||
import type { QuizQuestionNumber } from "../model/questionTypes/number";
|
import type { QuizQuestionNumber } from "../model/questionTypes/number";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_NUMBER: Omit<QuizQuestionNumber, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_NUMBER: Omit<QuizQuestionNumber, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "number",
|
type: "number",
|
||||||
content: {
|
content: {
|
||||||
|
@ -2,7 +2,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
|
|
||||||
import type { QuizQuestionPage } from "../model/questionTypes/page";
|
import type { QuizQuestionPage } from "../model/questionTypes/page";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_PAGE: Omit<QuizQuestionPage, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_PAGE: Omit<QuizQuestionPage, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "page",
|
type: "page",
|
||||||
content: {
|
content: {
|
||||||
|
@ -2,7 +2,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
|
|
||||||
import type { QuizQuestionRating } from "../model/questionTypes/rating";
|
import type { QuizQuestionRating } from "../model/questionTypes/rating";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_RATING: Omit<QuizQuestionRating, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_RATING: Omit<QuizQuestionRating, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "rating",
|
type: "rating",
|
||||||
content: {
|
content: {
|
||||||
|
@ -3,7 +3,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
import type { QuizQuestionSelect } from "../model/questionTypes/select";
|
import type { QuizQuestionSelect } from "../model/questionTypes/select";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_SELECT: Omit<QuizQuestionSelect, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_SELECT: Omit<QuizQuestionSelect, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "select",
|
type: "select",
|
||||||
content: {
|
content: {
|
||||||
|
@ -2,7 +2,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
|
|
||||||
import type { QuizQuestionText } from "../model/questionTypes/text";
|
import type { QuizQuestionText } from "../model/questionTypes/text";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_TEXT: Omit<QuizQuestionText, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_TEXT: Omit<QuizQuestionText, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "text",
|
type: "text",
|
||||||
content: {
|
content: {
|
||||||
|
@ -3,7 +3,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
import type { QuizQuestionVariant } from "../model/questionTypes/variant";
|
import type { QuizQuestionVariant } from "../model/questionTypes/variant";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_VARIANT: Omit<QuizQuestionVariant, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_VARIANT: Omit<QuizQuestionVariant, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "variant",
|
type: "variant",
|
||||||
content: {
|
content: {
|
||||||
|
@ -3,7 +3,7 @@ import { QUIZ_QUESTION_BASE } from "./base";
|
|||||||
import type { QuizQuestionVarImg } from "../model/questionTypes/varimg";
|
import type { QuizQuestionVarImg } from "../model/questionTypes/varimg";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
export const QUIZ_QUESTION_VARIMG: Omit<QuizQuestionVarImg, "id" | "fixedId"> = {
|
export const QUIZ_QUESTION_VARIMG: Omit<QuizQuestionVarImg, "id" | "backendId"> = {
|
||||||
...QUIZ_QUESTION_BASE,
|
...QUIZ_QUESTION_BASE,
|
||||||
type: "varimg",
|
type: "varimg",
|
||||||
content: {
|
content: {
|
||||||
|
@ -16,9 +16,9 @@ export interface EditQuestionResponse {
|
|||||||
updated: number;
|
updated: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function questionToEditQuestionRequest(question: AnyQuizQuestion, newId?: number): EditQuestionRequest {
|
export function questionToEditQuestionRequest(question: AnyQuizQuestion): EditQuestionRequest {
|
||||||
return {
|
return {
|
||||||
id: newId ?? question.id,
|
id: question.backendId,
|
||||||
title: question.title,
|
title: question.title,
|
||||||
desc: question.description,
|
desc: question.description,
|
||||||
type: question.type,
|
type: question.type,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AnyQuizQuestion } from "@model/questionTypes/shared";
|
import { AnyQuizQuestion } from "@model/questionTypes/shared";
|
||||||
import { defaultQuestionByType } from "../../constants/default";
|
import { defaultQuestionByType } from "../../constants/default";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
|
||||||
export type QuestionType =
|
export type QuestionType =
|
||||||
@ -53,8 +54,8 @@ export function rawQuestionToQuestion(rawQuestion: RawQuestion): AnyQuizQuestion
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: rawQuestion.id,
|
backendId: rawQuestion.id,
|
||||||
fixedId: rawQuestion.id,
|
id: nanoid(),
|
||||||
description: rawQuestion.description,
|
description: rawQuestion.description,
|
||||||
page: rawQuestion.page,
|
page: rawQuestion.page,
|
||||||
quizId: rawQuestion.quiz_id,
|
quizId: rawQuestion.quiz_id,
|
||||||
|
@ -48,9 +48,9 @@ export interface ImageQuestionVariant extends QuestionVariant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface QuizQuestionBase {
|
export interface QuizQuestionBase {
|
||||||
id: number;
|
backendId: number;
|
||||||
/** fixed id for using it as a key prop */
|
/** Stable id, generated on client */
|
||||||
fixedId: number;
|
id: string;
|
||||||
quizId: number;
|
quizId: number;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -26,7 +26,7 @@ export interface CreateQuizRequest {
|
|||||||
/** true if it is allowed for pause quiz */
|
/** true if it is allowed for pause quiz */
|
||||||
pausable: boolean;
|
pausable: boolean;
|
||||||
/** count of questions */
|
/** count of questions */
|
||||||
question_cnt: number;
|
question_cnt?: number;
|
||||||
/** set true if squiz realize group functionality */
|
/** set true if squiz realize group functionality */
|
||||||
super: boolean;
|
super: boolean;
|
||||||
/** group of new quiz */
|
/** group of new quiz */
|
||||||
|
@ -43,9 +43,9 @@ export interface EditQuizResponse {
|
|||||||
updated: number;
|
updated: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function quizToEditQuizRequest(quiz: Quiz, newId?: number): EditQuizRequest {
|
export function quizToEditQuizRequest(quiz: Quiz): EditQuizRequest {
|
||||||
return {
|
return {
|
||||||
id: newId ?? quiz.id,
|
id: quiz.backendId,
|
||||||
fp: quiz.fingerprinting,
|
fp: quiz.fingerprinting,
|
||||||
rep: quiz.repeatable,
|
rep: quiz.repeatable,
|
||||||
note_prevented: quiz.note_prevented,
|
note_prevented: quiz.note_prevented,
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { QuizConfig, defaultQuizConfig } from "@model/quizSettings";
|
import { QuizConfig, defaultQuizConfig } from "@model/quizSettings";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
|
||||||
export interface Quiz {
|
export interface Quiz {
|
||||||
|
/** Stable id, generated on client */
|
||||||
|
id: string;
|
||||||
/** Id of created quiz */
|
/** Id of created quiz */
|
||||||
id: number;
|
backendId: number;
|
||||||
/** string id for customers */
|
/** string id for customers */
|
||||||
qid: string;
|
qid: string;
|
||||||
/** true if quiz deleted */
|
/** true if quiz deleted */
|
||||||
@ -112,13 +115,6 @@ export interface RawQuiz {
|
|||||||
group_id: number;
|
group_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function quizToRawQuiz(quiz: Quiz): RawQuiz {
|
|
||||||
return {
|
|
||||||
...quiz,
|
|
||||||
config: JSON.stringify(quiz.config),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rawQuizToQuiz(rawQuiz: RawQuiz): Quiz {
|
export function rawQuizToQuiz(rawQuiz: RawQuiz): Quiz {
|
||||||
let config = defaultQuizConfig;
|
let config = defaultQuizConfig;
|
||||||
|
|
||||||
@ -131,5 +127,7 @@ export function rawQuizToQuiz(rawQuiz: RawQuiz): Quiz {
|
|||||||
return {
|
return {
|
||||||
...rawQuiz,
|
...rawQuiz,
|
||||||
config,
|
config,
|
||||||
|
backendId: rawQuiz.id,
|
||||||
|
id: nanoid(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,38 @@
|
|||||||
export const quizSetupSteps = {
|
import ChartPieIcon from "@icons/ChartPieIcon";
|
||||||
1: { displayStep: 1, text: "Настройка стартовой страницы" },
|
import ContactBookIcon from "@icons/ContactBookIcon";
|
||||||
2: { displayStep: 1, text: "Настройка стартовой страницы" },
|
import FlowArrowIcon from "@icons/FlowArrowIcon";
|
||||||
3: { displayStep: 1, text: "Настройка стартовой страницы" },
|
import LayoutIcon from "@icons/LayoutIcon";
|
||||||
4: { displayStep: 2, text: "Задайте вопросы" },
|
import MegaphoneIcon from "@icons/MegaphoneIcon";
|
||||||
5: { displayStep: 3, text: "Настройте авторезультаты" },
|
import QuestionIcon from "@icons/QuestionIcon";
|
||||||
6: { displayStep: 3, text: "Настройте авторезультаты" },
|
import QuestionsMapIcon from "@icons/QuestionsMapIcon";
|
||||||
7: { displayStep: 4, text: "Оценка графа карты вопросов" },
|
|
||||||
8: { displayStep: 5, text: "Настройте форму контактов" },
|
|
||||||
9: { displayStep: 6, text: "Установите квиз" },
|
|
||||||
10: { displayStep: 7, text: "Запустите рекламу" },
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const maxQuizSetupSteps = Math.max(...Object.keys(quizSetupSteps).map(n => parseInt(n)));
|
|
||||||
|
|
||||||
export const maxDisplayQuizSetupSteps = Math.max(...Object.values(quizSetupSteps).map(v => v.displayStep));
|
export const quizSetupSteps = [
|
||||||
|
{ stepperText: "Настройка стартовой страницы", sidebarText: "Стартовая страница", sidebarIcon: LayoutIcon },
|
||||||
|
{ stepperText: "Задайте вопросы", sidebarText: "Вопросы", sidebarIcon: QuestionIcon },
|
||||||
|
{ stepperText: "Настройте авторезультаты", sidebarText: "Результаты", sidebarIcon: ChartPieIcon },
|
||||||
|
{ stepperText: "Оценка графа карты вопросов", sidebarText: "Карта вопросов", sidebarIcon: QuestionsMapIcon },
|
||||||
|
{ stepperText: "Настройте форму контактов", sidebarText: "Форма контактов", sidebarIcon: ContactBookIcon },
|
||||||
|
{ stepperText: "Установите квиз", sidebarText: "Установка квиза", sidebarIcon: FlowArrowIcon },
|
||||||
|
{ stepperText: "Запустите рекламу", sidebarText: "Запуск рекламы", sidebarIcon: MegaphoneIcon },
|
||||||
|
] as const;
|
||||||
|
|
||||||
export type QuizSetupStep = keyof typeof quizSetupSteps;
|
export const maxQuizSetupSteps = quizSetupSteps.length;
|
||||||
|
|
||||||
export type QuizStartpageType = "standard" | "expanded" | "centered";
|
export type QuizStartpageType = "standard" | "expanded" | "centered" | null;
|
||||||
|
|
||||||
export type QuizStartpageAlignType = "left" | "right" | "center";
|
export type QuizStartpageAlignType = "left" | "right" | "center";
|
||||||
|
|
||||||
|
export type QuizType = "quiz" | "form" | null;
|
||||||
|
|
||||||
|
export type QuizResultsType = true | null;
|
||||||
|
|
||||||
export interface QuizConfig {
|
export interface QuizConfig {
|
||||||
type: "quiz" | "form";
|
type: QuizType;
|
||||||
logo: string;
|
logo: string;
|
||||||
noStartPage: boolean;
|
noStartPage: boolean;
|
||||||
startpageType: QuizStartpageType;
|
startpageType: QuizStartpageType;
|
||||||
|
results: QuizResultsType;
|
||||||
startpage: {
|
startpage: {
|
||||||
description: string;
|
description: string;
|
||||||
button: string;
|
button: string;
|
||||||
@ -49,19 +56,20 @@ export interface QuizConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defaultQuizConfig: QuizConfig = {
|
export const defaultQuizConfig: QuizConfig = {
|
||||||
type: "quiz",
|
type: null,
|
||||||
logo: "",
|
logo: "",
|
||||||
noStartPage: false,
|
noStartPage: false,
|
||||||
startpageType: "standard",
|
startpageType: null,
|
||||||
|
results: null,
|
||||||
startpage: {
|
startpage: {
|
||||||
description: "",
|
description: "",
|
||||||
button: "",
|
button: "",
|
||||||
position: "left",
|
position: "left",
|
||||||
background: {
|
background: {
|
||||||
type: null,
|
type: null,
|
||||||
desktop: "",
|
desktop: "https://happypik.ru/wp-content/uploads/2019/09/njashnye-kotiki8.jpg",
|
||||||
mobile: "",
|
mobile: "https://krot.info/uploads/posts/2022-03/1646156155_3-krot-info-p-smeshnie-tolstie-koti-smeshnie-foto-3.png",
|
||||||
video: "",
|
video: "https://youtu.be/dbaPkCiLPKQ",
|
||||||
cycle: false,
|
cycle: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,7 @@ import type { ImageQuestionVariant, QuestionVariant } from "../../../model/quest
|
|||||||
|
|
||||||
type AnswerItemProps = {
|
type AnswerItemProps = {
|
||||||
index: number;
|
index: number;
|
||||||
questionId: number;
|
questionId: string;
|
||||||
variant: QuestionVariant | ImageQuestionVariant;
|
variant: QuestionVariant | ImageQuestionVariant;
|
||||||
largeCheck: boolean;
|
largeCheck: boolean;
|
||||||
additionalContent?: ReactNode;
|
additionalContent?: ReactNode;
|
||||||
@ -40,11 +40,7 @@ export const AnswerItem = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(790));
|
const isTablet = useMediaQuery(theme.breakpoints.down(790));
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
const setQuestionVariantAnswer = useDebouncedCallback((value) => {
|
|
||||||
setQuestionVariantField(questionId, variant.id, "answer", value);
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
@ -76,7 +72,9 @@ export const AnswerItem = ({
|
|||||||
focused={false}
|
focused={false}
|
||||||
placeholder={"Добавьте ответ"}
|
placeholder={"Добавьте ответ"}
|
||||||
multiline={largeCheck}
|
multiline={largeCheck}
|
||||||
onChange={({ target }) => setQuestionVariantAnswer(target.value)}
|
onChange={({ target }) => {
|
||||||
|
setQuestionVariantField(questionId, variant.id, "answer", target.value)
|
||||||
|
}}
|
||||||
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (event.code === "Enter" && !largeCheck) {
|
if (event.code === "Enter" && !largeCheck) {
|
||||||
addQuestionVariant(questionId);
|
addQuestionVariant(questionId);
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { copyQuestion, deleteQuestion, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { copyQuestion, deleteQuestion, updateQuestion } from "@root/questions/actions";
|
||||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||||
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
|
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
|
||||||
import Branching from "../../assets/icons/questionsPage/branching";
|
import Branching from "../../assets/icons/questionsPage/branching";
|
||||||
@ -38,7 +38,7 @@ export default function ButtonsOptions({
|
|||||||
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||||
|
|
||||||
const openedModal = () => {
|
const openedModal = () => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.openedModalSettings = true;
|
question.openedModalSettings = true;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -279,6 +279,7 @@ export default function ButtonsOptions({
|
|||||||
|
|
||||||
deleteQuestion(question.id);
|
deleteQuestion(question.id);
|
||||||
}}
|
}}
|
||||||
|
data-cy="delete-question"
|
||||||
>
|
>
|
||||||
<DeleteIcon color={"#4D4D4D"} />
|
<DeleteIcon color={"#4D4D4D"} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { copyQuestion, deleteQuestion, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { copyQuestion, deleteQuestion, updateQuestion } from "@root/questions/actions";
|
||||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||||
import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal";
|
import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@ -184,7 +184,7 @@ export default function ButtonsOptionsAndPict({
|
|||||||
onMouseLeave={() => setButtonHover("")}
|
onMouseLeave={() => setButtonHover("")}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
SSHC("branching");
|
SSHC("branching");
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.openedModalSettings = true;
|
question.openedModalSettings = true;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -320,6 +320,7 @@ export default function ButtonsOptionsAndPict({
|
|||||||
|
|
||||||
deleteQuestion(question.id);
|
deleteQuestion(question.id);
|
||||||
}}
|
}}
|
||||||
|
data-cy="delete-question"
|
||||||
>
|
>
|
||||||
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -19,7 +19,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
|||||||
|
|
||||||
const setInnerName = useDebouncedCallback((value) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -48,7 +48,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
|||||||
label={"Выбор диапазона дат"}
|
label={"Выбор диапазона дат"}
|
||||||
checked={question.content.dateRange}
|
checked={question.content.dateRange}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "date") return;
|
if (question.type !== "date") return;
|
||||||
|
|
||||||
question.content.dateRange = target.checked;
|
question.content.dateRange = target.checked;
|
||||||
@ -60,7 +60,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
|||||||
label={"Выбор времени"}
|
label={"Выбор времени"}
|
||||||
checked={question.content.time}
|
checked={question.content.time}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "date") return;
|
if (question.type !== "date") return;
|
||||||
|
|
||||||
question.content.time = target.checked;
|
question.content.time = target.checked;
|
||||||
@ -88,7 +88,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.required = !target.checked;
|
question.required = !target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -109,7 +109,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
});
|
});
|
||||||
|
@ -29,7 +29,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { copyQuestion, createQuestion, deleteQuestion, toggleExpandQuestion, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { copyQuestion, createQuestion, deleteQuestion, toggleExpandQuestion, updateQuestion } from "@root/questions/actions";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
@ -54,7 +54,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
const anchorRef = useRef(null);
|
const anchorRef = useRef(null);
|
||||||
|
|
||||||
const setTitle = useDebouncedCallback((title) => {
|
const setTitle = useDebouncedCallback((title) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.title = title;
|
question.title = title;
|
||||||
});
|
});
|
||||||
}, 200);
|
}, 200);
|
||||||
@ -250,6 +250,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
|
|
||||||
deleteQuestion(question.id);
|
deleteQuestion(question.id);
|
||||||
}}
|
}}
|
||||||
|
data-cy="delete-question"
|
||||||
>
|
>
|
||||||
<DeleteIcon
|
<DeleteIcon
|
||||||
style={{ color: theme.palette.brightPurple.main }}
|
style={{ color: theme.palette.brightPurple.main }}
|
||||||
@ -317,6 +318,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
columnGap: "10px",
|
columnGap: "10px",
|
||||||
}}
|
}}
|
||||||
|
data-cy="create-question"
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -13,8 +13,8 @@ import DraggableListItem from "./DraggableListItem";
|
|||||||
|
|
||||||
|
|
||||||
export const DraggableList = () => {
|
export const DraggableList = () => {
|
||||||
const { quiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
useSWR(["questions", quiz?.id], ([, id]) => questionApi.getList({ quiz_id: id }), {
|
const { isLoading } = useSWR(["questions", quiz?.backendId], ([, id]) => questionApi.getList({ quiz_id: id }), {
|
||||||
onSuccess: setQuestions,
|
onSuccess: setQuestions,
|
||||||
onError: error => {
|
onError: error => {
|
||||||
const message = isAxiosError<string>(error) ? (error.response?.data ?? "") : "";
|
const message = isAxiosError<string>(error) ? (error.response?.data ?? "") : "";
|
||||||
@ -29,6 +29,8 @@ export const DraggableList = () => {
|
|||||||
if (destination) reorderQuestions(source.index, destination.index);
|
if (destination) reorderQuestions(source.index, destination.index);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<Droppable droppableId="droppable-list">
|
<Droppable droppableId="droppable-list">
|
||||||
@ -36,7 +38,7 @@ export const DraggableList = () => {
|
|||||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
{questions.map((question, index) => (
|
{questions.map((question, index) => (
|
||||||
<DraggableListItem
|
<DraggableListItem
|
||||||
key={question.fixedId}
|
key={question.id}
|
||||||
question={question}
|
question={question}
|
||||||
isDragging={snapshot.isDraggingOver}
|
isDragging={snapshot.isDraggingOver}
|
||||||
index={index}
|
index={index}
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -24,15 +24,15 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
|
|||||||
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const debounced = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
const debounceAnswer = useDebouncedCallback((value) => {
|
const debounceAnswer = useDebouncedCallback((value) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "select") return;
|
if (question.type !== "select") return;
|
||||||
|
|
||||||
question.content.default = value;
|
question.content.default = value;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -73,7 +73,7 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
|
|||||||
checked={question.content.multi}
|
checked={question.content.multi}
|
||||||
dataCy="multiple-answers-checkbox"
|
dataCy="multiple-answers-checkbox"
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) =>
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "select") return;
|
if (question.type !== "select") return;
|
||||||
|
|
||||||
question.content.multi = target.checked;
|
question.content.multi = target.checked;
|
||||||
@ -130,7 +130,7 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.required = !e.target.checked;
|
question.required = !e.target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -141,7 +141,7 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { addQuestionVariant, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { addQuestionVariant, updateQuestion } from "@root/questions/actions";
|
||||||
import { EmojiPicker } from "@ui_kit/EmojiPicker";
|
import { EmojiPicker } from "@ui_kit/EmojiPicker";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||||
@ -181,7 +181,7 @@ export default function Emoji({ question }: Props) {
|
|||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
onEmojiSelect={({ native }) => {
|
onEmojiSelect={({ native }) => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "emoji") return;
|
if (question.type !== "emoji") return;
|
||||||
|
|
||||||
const variant = question.content.variants.find(v => v.id === selectedVariant);
|
const variant = question.content.variants.find(v => v.id === selectedVariant);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -20,7 +20,7 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
|||||||
|
|
||||||
const setInnerName = useDebouncedCallback((value) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -50,7 +50,7 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
|||||||
label={"Можно несколько"}
|
label={"Можно несколько"}
|
||||||
checked={question.content.multi}
|
checked={question.content.multi}
|
||||||
dataCy="multiple-answers-checkbox"
|
dataCy="multiple-answers-checkbox"
|
||||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "emoji") return;
|
if (question.type !== "emoji") return;
|
||||||
|
|
||||||
question.content.multi = target.checked;
|
question.content.multi = target.checked;
|
||||||
@ -60,7 +60,7 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
|||||||
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 }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "emoji") return;
|
if (question.type !== "emoji") return;
|
||||||
|
|
||||||
question.content.own = target.checked;
|
question.content.own = target.checked;
|
||||||
@ -86,7 +86,7 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
|||||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={(e) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "emoji") return;
|
if (question.type !== "emoji") return;
|
||||||
|
|
||||||
question.content.required = !e.target.checked;
|
question.content.required = !e.target.checked;
|
||||||
@ -107,7 +107,7 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
|||||||
}}
|
}}
|
||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
})}
|
})}
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { BUTTON_TYPE_QUESTIONS } from "../../TypeQuestions";
|
import { BUTTON_TYPE_QUESTIONS } from "../../TypeQuestions";
|
||||||
import { QuestionType } from "@model/question/question";
|
import { QuestionType } from "@model/question/question";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
import type { RefObject } from "react";
|
import type { RefObject } from "react";
|
||||||
import type { AnyQuizQuestion } from "../../../../model/questionTypes/shared";
|
import type { AnyQuizQuestion } from "../../../../model/questionTypes/shared";
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ export const ChooseAnswerModal = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
|
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.type = selectedValue;
|
question.type = selectedValue;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, ListItem, Typography, useTheme } from "@mui/material";
|
import { Box, ListItem, Typography, useTheme } from "@mui/material";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Draggable } from "react-beautiful-dnd";
|
import { Draggable } from "react-beautiful-dnd";
|
||||||
import { AnyQuizQuestion, QuizQuestionBase } from "../../../../model/questionTypes/shared";
|
import { AnyQuizQuestion, QuizQuestionBase } from "../../../../model/questionTypes/shared";
|
||||||
@ -46,7 +46,7 @@ export default memo(
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.deleted = false;
|
question.deleted = false;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -12,7 +12,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 { Box, InputAdornment, Paper } from "@mui/material";
|
import { Box, InputAdornment, Paper } from "@mui/material";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||||
@ -37,10 +37,10 @@ export default function QuestionsPageCard({
|
|||||||
const anchorRef = useRef(null);
|
const anchorRef = useRef(null);
|
||||||
|
|
||||||
const setTitle = useDebouncedCallback((title) => {
|
const setTitle = useDebouncedCallback((title) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.title = title;
|
question.title = title;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -13,8 +13,8 @@ import { enqueueSnackbar } from "notistack";
|
|||||||
|
|
||||||
|
|
||||||
export const FormDraggableList = () => {
|
export const FormDraggableList = () => {
|
||||||
const { quiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
useSWR(["questions", quiz?.id], ([, id]) => questionApi.getList({ quiz_id: id }), {
|
useSWR(["questions", quiz?.backendId], ([, id]) => questionApi.getList({ quiz_id: id }), {
|
||||||
onSuccess: setQuestions,
|
onSuccess: setQuestions,
|
||||||
onError: error => {
|
onError: error => {
|
||||||
const message = isAxiosError<string>(error) ? (error.response?.data ?? "") : "";
|
const message = isAxiosError<string>(error) ? (error.response?.data ?? "") : "";
|
||||||
@ -36,7 +36,7 @@ export const FormDraggableList = () => {
|
|||||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
{questions.map((question, index) => (
|
{questions.map((question, index) => (
|
||||||
<FormDraggableListItem
|
<FormDraggableListItem
|
||||||
key={question.fixedId}
|
key={question.id}
|
||||||
question={question}
|
question={question}
|
||||||
questionIndex={index}
|
questionIndex={index}
|
||||||
questionData={question}
|
questionData={question}
|
||||||
|
@ -11,7 +11,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
|||||||
|
|
||||||
export default function FormQuestionsPage() {
|
export default function FormQuestionsPage() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { quiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
if (!quiz) return null;
|
if (!quiz) return null;
|
||||||
|
|
||||||
@ -68,8 +68,9 @@ export default function FormQuestionsPage() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
createQuestion(quiz.id);
|
createQuestion(quiz.backendId);
|
||||||
}}
|
}}
|
||||||
|
data-cy="create-question"
|
||||||
>
|
>
|
||||||
<AddAnswer color="#EEE4FC" />
|
<AddAnswer color="#EEE4FC" />
|
||||||
<Typography sx={{ color: "#9A9AAF" }}>
|
<Typography sx={{ color: "#9A9AAF" }}>
|
||||||
|
@ -17,7 +17,7 @@ import type {
|
|||||||
AnyQuizQuestion,
|
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";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
|
|
||||||
|
|
||||||
type ButtonTypeQuestion = {
|
type ButtonTypeQuestion = {
|
||||||
@ -83,7 +83,7 @@ export default function FormTypeQuestions({ question }: Props) {
|
|||||||
<QuestionsMiniButton
|
<QuestionsMiniButton
|
||||||
key={title}
|
key={title}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.type = questionType;
|
question.type = questionType;
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -18,16 +18,16 @@ export default function SettingOptionsAndPict({ question }: SettingOptionsAndPic
|
|||||||
const isMobile = useMediaQuery(theme.breakpoints.down(680));
|
const isMobile = useMediaQuery(theme.breakpoints.down(680));
|
||||||
|
|
||||||
const setReplText = useDebouncedCallback((replText) => {
|
const setReplText = useDebouncedCallback((replText) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "varimg") return;
|
if (question.type !== "varimg") return;
|
||||||
|
|
||||||
question.content.replText = replText;
|
question.content.replText = replText;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
const setDescription = useDebouncedCallback((value) => {
|
const setDescription = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -59,7 +59,7 @@ export default function SettingOptionsAndPict({ question }: SettingOptionsAndPic
|
|||||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||||
label={'Вариант "свой ответ"'}
|
label={'Вариант "свой ответ"'}
|
||||||
checked={question.content.own}
|
checked={question.content.own}
|
||||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "varimg") return;
|
if (question.type !== "varimg") return;
|
||||||
|
|
||||||
question.content.own = target.checked;
|
question.content.own = target.checked;
|
||||||
@ -112,7 +112,7 @@ export default function SettingOptionsAndPict({ question }: SettingOptionsAndPic
|
|||||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={question.content.required}
|
checked={question.content.required}
|
||||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "varimg") return;
|
if (question.type !== "varimg") return;
|
||||||
|
|
||||||
question.content.required = target.checked;
|
question.content.required = target.checked;
|
||||||
@ -126,7 +126,7 @@ export default function SettingOptionsAndPict({ question }: SettingOptionsAndPic
|
|||||||
}}
|
}}
|
||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
question.content.innerName = "";
|
question.content.innerName = "";
|
||||||
})}
|
})}
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -44,10 +44,10 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
|||||||
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const debounced = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
const updateProportions = (proportions: Proportion) => {
|
const updateProportions = (proportions: Proportion) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "images") return;
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
question.content.xy = proportions;
|
question.content.xy = proportions;
|
||||||
@ -115,7 +115,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
|||||||
label={"Можно несколько"}
|
label={"Можно несколько"}
|
||||||
checked={question.content.multi}
|
checked={question.content.multi}
|
||||||
dataCy="multiple-answers-checkbox"
|
dataCy="multiple-answers-checkbox"
|
||||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "images") return;
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
question.content.multi = target.checked;
|
question.content.multi = target.checked;
|
||||||
@ -129,7 +129,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
|||||||
label={"Большие картинки"}
|
label={"Большие картинки"}
|
||||||
checked={question.content.largeCheck}
|
checked={question.content.largeCheck}
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) =>
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "images") return;
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
question.content.largeCheck = target.checked;
|
question.content.largeCheck = target.checked;
|
||||||
@ -141,7 +141,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
|||||||
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 }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "images") return;
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
question.content.own = target.checked;
|
question.content.own = target.checked;
|
||||||
@ -183,7 +183,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
|||||||
Формат
|
Формат
|
||||||
</Typography>
|
</Typography>
|
||||||
<SelectIconButton
|
<SelectIconButton
|
||||||
onClick={() => updateQuestionWithFnOptimistic(question.id, question => {
|
onClick={() => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "images") return;
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
question.content.format = "carousel";
|
question.content.format = "carousel";
|
||||||
@ -193,7 +193,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
|||||||
Icon={FormatIcon2}
|
Icon={FormatIcon2}
|
||||||
/>
|
/>
|
||||||
<SelectIconButton
|
<SelectIconButton
|
||||||
onClick={() => updateQuestionWithFnOptimistic(question.id, question => {
|
onClick={() => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "images") return;
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
question.content.format = "masonry";
|
question.content.format = "masonry";
|
||||||
@ -212,7 +212,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
|||||||
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={question.content.required}
|
checked={question.content.required}
|
||||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "images") return;
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
question.content.required = target.checked;
|
question.content.required = target.checked;
|
||||||
@ -233,7 +233,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
|||||||
}}
|
}}
|
||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => updateQuestionWithFnOptimistic(question.id, question => {
|
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||||
if (question.type !== "images") return;
|
if (question.type !== "images") return;
|
||||||
|
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
@ -20,12 +20,12 @@ export default function OwnTextField({ question }: Props) {
|
|||||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||||
|
|
||||||
const setPlaceholder = useDebouncedCallback((value) => {
|
const setPlaceholder = useDebouncedCallback((value) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "text") return;
|
if (question.type !== "text") return;
|
||||||
|
|
||||||
question.content.placeholder = value;
|
question.content.placeholder = value;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
const SSHC = (data: string) => {
|
const SSHC = (data: string) => {
|
||||||
setSwitchState(data);
|
setSwitchState(data);
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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 CheckedIcon from "@ui_kit/RadioCheck";
|
import CheckedIcon from "@ui_kit/RadioCheck";
|
||||||
@ -43,7 +43,7 @@ export default function SettingTextField({
|
|||||||
|
|
||||||
const debounced = useDebouncedCallback((value) => {
|
const debounced = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -85,7 +85,7 @@ export default function SettingTextField({
|
|||||||
({ value }) => question.content.answerType === value
|
({ value }) => question.content.answerType === value
|
||||||
)}
|
)}
|
||||||
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "text") return;
|
if (question.type !== "text") return;
|
||||||
|
|
||||||
question.content.answerType = ANSWER_TYPES[Number(target.value)].value;
|
question.content.answerType = ANSWER_TYPES[Number(target.value)].value;
|
||||||
@ -119,7 +119,7 @@ export default function SettingTextField({
|
|||||||
label={"Только числа"}
|
label={"Только числа"}
|
||||||
checked={question.content.onlyNumbers}
|
checked={question.content.onlyNumbers}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "text") return;
|
if (question.type !== "text") return;
|
||||||
|
|
||||||
question.content.onlyNumbers = target.checked;
|
question.content.onlyNumbers = target.checked;
|
||||||
@ -157,7 +157,7 @@ export default function SettingTextField({
|
|||||||
label={"Автозаполнение адреса"}
|
label={"Автозаполнение адреса"}
|
||||||
checked={question.content.autofill}
|
checked={question.content.autofill}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.autofill = target.checked;
|
question.content.autofill = target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -171,7 +171,7 @@ export default function SettingTextField({
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.required = !e.target.checked;
|
question.required = !e.target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -193,7 +193,7 @@ export default function SettingTextField({
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
question.content.innerName = target.checked
|
question.content.innerName = target.checked
|
||||||
? question.content.innerName
|
? question.content.innerName
|
||||||
|
@ -2,7 +2,7 @@ import { VideofileIcon } from "@icons/questionsPage/VideofileIcon";
|
|||||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { openCropModal } from "@root/cropModal";
|
import { openCropModal } from "@root/cropModal";
|
||||||
import { closeImageUploadModal, openImageUploadModal } from "@root/imageUploadModal";
|
import { closeImageUploadModal, openImageUploadModal } from "@root/imageUploadModal";
|
||||||
import { setPageQuestionOriginalPicture, setPageQuestionPicture, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setPageQuestionOriginalPicture, setPageQuestionPicture, updateQuestion } from "@root/questions/actions";
|
||||||
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
|
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 { CropModal } from "@ui_kit/Modal/CropModal";
|
||||||
@ -29,12 +29,12 @@ export default function PageOptions({ disableInput, question }: Props) {
|
|||||||
const isMobile = useMediaQuery(theme.breakpoints.down(780));
|
const isMobile = useMediaQuery(theme.breakpoints.down(780));
|
||||||
|
|
||||||
const setText = useDebouncedCallback((value) => {
|
const setText = useDebouncedCallback((value) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "page") return;
|
if (question.type !== "page") return;
|
||||||
|
|
||||||
question.content.text = value;
|
question.content.text = value;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
const SSHC = (data: string) => {
|
const SSHC = (data: string) => {
|
||||||
setSwitchState(data);
|
setSwitchState(data);
|
||||||
@ -225,7 +225,7 @@ export default function PageOptions({ disableInput, question }: Props) {
|
|||||||
onClose={() => setOpenVideoModal(false)}
|
onClose={() => setOpenVideoModal(false)}
|
||||||
video={question.content.video}
|
video={question.content.video}
|
||||||
onUpload={(url) => {
|
onUpload={(url) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "page") return;
|
if (question.type !== "page") return;
|
||||||
|
|
||||||
question.content.video = url;
|
question.content.video = url;
|
||||||
|
@ -10,7 +10,7 @@ import CustomTextField from "@ui_kit/CustomTextField";
|
|||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
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";
|
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
|
||||||
|
|
||||||
|
|
||||||
type SettingPageOptionsProps = {
|
type SettingPageOptionsProps = {
|
||||||
@ -25,7 +25,7 @@ export default function SettingPageOptions({
|
|||||||
|
|
||||||
const setInnerName = useDebouncedCallback((value) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -60,7 +60,7 @@ export default function SettingPageOptions({
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) =>
|
handleChange={({ target }) =>
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
question.content.innerName = "";
|
question.content.innerName = "";
|
||||||
})
|
})
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { collapseAllQuestions, createQuestion } from "@root/questions/actions";
|
import { collapseAllQuestions, createQuestion } from "@root/questions/actions";
|
||||||
import { incrementCurrentStep } from "@root/quizes/actions";
|
import { decrementCurrentStep, 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";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
@ -19,7 +19,7 @@ import { DraggableList } from "./DraggableList";
|
|||||||
export default function QuestionsPage() {
|
export default function QuestionsPage() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||||
const { quiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
if (!quiz) return null;
|
if (!quiz) return null;
|
||||||
|
|
||||||
@ -59,13 +59,14 @@ export default function QuestionsPage() {
|
|||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
createQuestion(quiz.id);
|
createQuestion(quiz.backendId);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
left: isMobile ? "20px" : "250px",
|
left: isMobile ? "20px" : "250px",
|
||||||
bottom: "20px",
|
bottom: "20px",
|
||||||
}}
|
}}
|
||||||
|
data-cy="create-question"
|
||||||
>
|
>
|
||||||
<AddPlus />
|
<AddPlus />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -73,6 +74,8 @@ export default function QuestionsPage() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{ padding: "10px 20px", borderRadius: "8px", height: "44px" }}
|
sx={{ padding: "10px 20px", borderRadius: "8px", height: "44px" }}
|
||||||
|
data-cy="back-button"
|
||||||
|
onClick={decrementCurrentStep}
|
||||||
>
|
>
|
||||||
<ArrowLeft />
|
<ArrowLeft />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -18,7 +18,7 @@ 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";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -43,19 +43,19 @@ export default function RatingOptions({ question }: Props) {
|
|||||||
const positiveRef = useRef<HTMLDivElement>(null);
|
const positiveRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const debounceNegativeDescription = useDebouncedCallback((value) => {
|
const debounceNegativeDescription = useDebouncedCallback((value) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "rating") return;
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
question.content.ratingNegativeDescription = value.substring(0, 15);
|
question.content.ratingNegativeDescription = value.substring(0, 15);
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 200);
|
||||||
const debouncePositiveDescription = useDebouncedCallback((value) => {
|
const debouncePositiveDescription = useDebouncedCallback((value) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "rating") return;
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
question.content.ratingPositiveDescription = value.substring(0, 15);
|
question.content.ratingPositiveDescription = value.substring(0, 15);
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 200);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNegativeText(question.content.ratingNegativeDescription);
|
setNegativeText(question.content.ratingNegativeDescription);
|
||||||
@ -120,7 +120,7 @@ export default function RatingOptions({ question }: Props) {
|
|||||||
{...(itemNumber === 0 || itemNumber === question.content.steps - 1
|
{...(itemNumber === 0 || itemNumber === question.content.steps - 1
|
||||||
? {
|
? {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "rating") return;
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
question.content.ratingExpanded = true;
|
question.content.ratingExpanded = true;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { QuizQuestionRating } from "@model/questionTypes/rating";
|
import { QuizQuestionRating } from "@model/questionTypes/rating";
|
||||||
import { Box, ButtonBase, Slider, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, ButtonBase, Slider, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -27,7 +27,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
|||||||
|
|
||||||
const setInnerName = useDebouncedCallback((value) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
const buttonRatingForm: ButtonRatingFrom[] = [
|
const buttonRatingForm: ButtonRatingFrom[] = [
|
||||||
{ name: "star", icon: <StarIconMini color={theme.palette.grey3.main} /> },
|
{ name: "star", icon: <StarIconMini color={theme.palette.grey3.main} /> },
|
||||||
@ -79,7 +79,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
|||||||
<ButtonBase
|
<ButtonBase
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "rating") return;
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
question.content.form = name;
|
question.content.form = name;
|
||||||
@ -121,7 +121,7 @@ export default function SettingSlider({ question }: 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) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "rating") return;
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
question.content.steps = Number(value) || 1;
|
question.content.steps = Number(value) || 1;
|
||||||
@ -150,7 +150,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "rating") return;
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
question.required = !e.target.checked;
|
question.required = !e.target.checked;
|
||||||
@ -169,7 +169,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "rating") return;
|
if (question.type !== "rating") return;
|
||||||
|
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
|
@ -4,7 +4,7 @@ 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 type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -56,7 +56,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
max={99}
|
max={99}
|
||||||
value={question.content.range.split("—")[0]}
|
value={question.content.range.split("—")[0]}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.range = `${target.value}—${question.content.range.split("—")[1]}`;
|
question.content.range = `${target.value}—${question.content.range.split("—")[1]}`;
|
||||||
@ -68,7 +68,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
const max = Number(question.content.range.split("—")[1]);
|
const max = Number(question.content.range.split("—")[1]);
|
||||||
|
|
||||||
if (min >= max) {
|
if (min >= max) {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${question.content.range.split("—")[1]}`;
|
question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${question.content.range.split("—")[1]}`;
|
||||||
@ -76,7 +76,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (start < min) {
|
if (start < min) {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.start = min;
|
question.content.start = min;
|
||||||
@ -92,7 +92,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
max={100}
|
max={100}
|
||||||
value={question.content.range.split("—")[1]}
|
value={question.content.range.split("—")[1]}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.range = `${question.content.range.split("—")[0]}—${target.value}`;
|
question.content.range = `${question.content.range.split("—")[0]}—${target.value}`;
|
||||||
@ -106,7 +106,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
const range = max - min;
|
const range = max - min;
|
||||||
|
|
||||||
if (max <= min) {
|
if (max <= min) {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.range = `${min}—${min + 1 >= 100 ? 100 : min + 1}`;
|
question.content.range = `${min}—${min + 1 >= 100 ? 100 : min + 1}`;
|
||||||
@ -114,7 +114,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (start > max) {
|
if (start > max) {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.start = max;
|
question.content.start = max;
|
||||||
@ -122,7 +122,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (step > max) {
|
if (step > max) {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.step = min;
|
question.content.step = min;
|
||||||
@ -158,7 +158,7 @@ export default function SliderOptions({ question }: 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 }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.start = Number(target.value);
|
question.content.start = Number(target.value);
|
||||||
@ -185,7 +185,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
error={stepError}
|
error={stepError}
|
||||||
value={String(question.content.step)}
|
value={String(question.content.step)}
|
||||||
onChange={({ target }) => {
|
onChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.step = Number(target.value);
|
question.content.step = Number(target.value);
|
||||||
@ -198,7 +198,7 @@ export default function SliderOptions({ question }: Props) {
|
|||||||
const step = Number(target.value);
|
const step = Number(target.value);
|
||||||
|
|
||||||
if (step > max) {
|
if (step > max) {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.step = max;
|
question.content.step = max;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -19,7 +19,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
|||||||
|
|
||||||
const setInnerName = useDebouncedCallback((value) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -52,7 +52,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
|||||||
label={"Выбор диапозона (два ползунка)"}
|
label={"Выбор диапозона (два ползунка)"}
|
||||||
checked={question.content.chooseRange}
|
checked={question.content.chooseRange}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.chooseRange = target.checked;
|
question.content.chooseRange = target.checked;
|
||||||
@ -80,7 +80,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.required = !e.target.checked;
|
question.required = !e.target.checked;
|
||||||
@ -103,7 +103,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "number") return;
|
if (question.type !== "number") return;
|
||||||
|
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
|
@ -11,7 +11,7 @@ import OptionsPict from "../../assets/icons/questionsPage/options_pict";
|
|||||||
import Page from "../../assets/icons/questionsPage/page";
|
import Page from "../../assets/icons/questionsPage/page";
|
||||||
import RatingIcon from "../../assets/icons/questionsPage/rating";
|
import RatingIcon from "../../assets/icons/questionsPage/rating";
|
||||||
import Slider from "../../assets/icons/questionsPage/slider";
|
import Slider from "../../assets/icons/questionsPage/slider";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
import type {
|
import type {
|
||||||
AnyQuizQuestion,
|
AnyQuizQuestion,
|
||||||
} from "../../model/questionTypes/shared";
|
} from "../../model/questionTypes/shared";
|
||||||
@ -42,7 +42,7 @@ export default function TypeQuestions({ question }: Props) {
|
|||||||
<QuestionsMiniButton
|
<QuestionsMiniButton
|
||||||
key={title}
|
key={title}
|
||||||
dataCy={`select-questiontype-${value}`}
|
dataCy={`select-questiontype-${value}`}
|
||||||
onClick={() => updateQuestionWithFnOptimistic(question.id, question => {
|
onClick={() => updateQuestion(question.id, question => {
|
||||||
question.type = value;
|
question.type = value;
|
||||||
})}
|
})}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ArrowDown from "../../../assets/icons/ArrowDownIcon";
|
import ArrowDown from "../../../assets/icons/ArrowDownIcon";
|
||||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||||
@ -50,7 +50,7 @@ export default function UploadFile({ question }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = ({ target }: SelectChangeEvent) => {
|
const handleChange = ({ target }: SelectChangeEvent) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "file") return;
|
if (question.type !== "file") return;
|
||||||
|
|
||||||
question.content.type = target.value as UploadFileType;
|
question.content.type = target.value as UploadFileType;
|
||||||
@ -63,7 +63,7 @@ export default function UploadFile({ question }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!isTypeSetted) {
|
if (!isTypeSetted) {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (question.type !== "file") return;
|
if (question.type !== "file") return;
|
||||||
|
|
||||||
question.content.type = DESIGN_TYPES[0].value;
|
question.content.type = DESIGN_TYPES[0].value;
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -23,7 +23,7 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
|
|||||||
|
|
||||||
const setInnerName = useDebouncedCallback((value) => {
|
const setInnerName = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -48,7 +48,7 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
|
|||||||
label={"Автозаполнение адреса"}
|
label={"Автозаполнение адреса"}
|
||||||
checked={question.content.autofill}
|
checked={question.content.autofill}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.autofill = target.checked;
|
question.content.autofill = target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -61,7 +61,7 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={(e) => {
|
handleChange={(e) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.required = !e.target.checked;
|
question.required = !e.target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -82,7 +82,7 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { setQuestionInnerName, updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { setQuestionInnerName, updateQuestion } 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";
|
||||||
@ -26,7 +26,7 @@ export default function ResponseSettings({ question }: Props) {
|
|||||||
|
|
||||||
const updateQuestionInnerName = useDebouncedCallback((value) => {
|
const updateQuestionInnerName = useDebouncedCallback((value) => {
|
||||||
setQuestionInnerName(question.id, value);
|
setQuestionInnerName(question.id, value);
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -63,7 +63,7 @@ export default function ResponseSettings({ question }: Props) {
|
|||||||
label={"Длинный текстовый ответ"}
|
label={"Длинный текстовый ответ"}
|
||||||
checked={question.content.largeCheck}
|
checked={question.content.largeCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (!("largeCheck" in question.content)) return;
|
if (!("largeCheck" in question.content)) return;
|
||||||
|
|
||||||
question.content.largeCheck = target.checked;
|
question.content.largeCheck = target.checked;
|
||||||
@ -76,7 +76,7 @@ export default function ResponseSettings({ question }: Props) {
|
|||||||
checked={question.content.multi}
|
checked={question.content.multi}
|
||||||
dataCy="multiple-answers-checkbox"
|
dataCy="multiple-answers-checkbox"
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (!("multi" in question.content)) return;
|
if (!("multi" in question.content)) return;
|
||||||
|
|
||||||
question.content.multi = target.checked;
|
question.content.multi = target.checked;
|
||||||
@ -88,7 +88,7 @@ export default function ResponseSettings({ question }: Props) {
|
|||||||
label={'Вариант "свой ответ"'}
|
label={'Вариант "свой ответ"'}
|
||||||
checked={question.content.own}
|
checked={question.content.own}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
if (!("own" in question.content)) return;
|
if (!("own" in question.content)) return;
|
||||||
|
|
||||||
question.content.own = target.checked;
|
question.content.own = target.checked;
|
||||||
@ -124,7 +124,7 @@ export default function ResponseSettings({ question }: Props) {
|
|||||||
label={"Необязательный вопрос"}
|
label={"Необязательный вопрос"}
|
||||||
checked={!question.required}
|
checked={!question.required}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.required = !target.checked;
|
question.required = !target.checked;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -145,7 +145,7 @@ export default function ResponseSettings({ question }: Props) {
|
|||||||
label={"Внутреннее название вопроса"}
|
label={"Внутреннее название вопроса"}
|
||||||
checked={question.content.innerNameCheck}
|
checked={question.content.innerNameCheck}
|
||||||
handleChange={({ target }) => {
|
handleChange={({ target }) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.innerNameCheck = target.checked;
|
question.content.innerNameCheck = target.checked;
|
||||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||||
});
|
});
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
|
import { updateQuestion } from "@root/questions/actions";
|
||||||
import RadioCheck from "@ui_kit/RadioCheck";
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
import RadioIcon from "@ui_kit/RadioIcon";
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
@ -48,7 +48,7 @@ export default function BranchingQuestions({
|
|||||||
}, [title]);
|
}, [title]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.openedModalSettings = false;
|
question.openedModalSettings = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -144,7 +144,7 @@ export default function BranchingQuestions({
|
|||||||
activeItemIndex={question.content.rule.show ? 0 : 1}
|
activeItemIndex={question.content.rule.show ? 0 : 1}
|
||||||
sx={{ maxWidth: "140px" }}
|
sx={{ maxWidth: "140px" }}
|
||||||
onChange={(action) => {
|
onChange={(action) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.rule.show = action === ACTIONS[0];
|
question.content.rule.show = action === ACTIONS[0];
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -177,7 +177,7 @@ export default function BranchingQuestions({
|
|||||||
<IconButton
|
<IconButton
|
||||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.rule.reqs.splice(index, 1);
|
question.content.rule.reqs.splice(index, 1);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -190,7 +190,7 @@ export default function BranchingQuestions({
|
|||||||
activeItemIndex={request.id ? Number(request.id) : -1}
|
activeItemIndex={request.id ? Number(request.id) : -1}
|
||||||
items={STIPULATIONS}
|
items={STIPULATIONS}
|
||||||
onChange={(stipulation) => {
|
onChange={(stipulation) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.rule.reqs[index].id = String(
|
question.content.rule.reqs[index].id = String(
|
||||||
STIPULATIONS.findIndex((item) => item.includes(stipulation))
|
STIPULATIONS.findIndex((item) => item.includes(stipulation))
|
||||||
);
|
);
|
||||||
@ -222,7 +222,7 @@ export default function BranchingQuestions({
|
|||||||
const answerItemIndex = ANSWERS.findIndex(
|
const answerItemIndex = ANSWERS.findIndex(
|
||||||
(answerItem) => answerItem === answer
|
(answerItem) => answerItem === answer
|
||||||
);
|
);
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
const vars = question.content.rule.reqs[index].vars;
|
const vars = question.content.rule.reqs[index].vars;
|
||||||
if (vars.includes(answerItemIndex)) {
|
if (vars.includes(answerItemIndex)) {
|
||||||
vars.push(answerItemIndex);
|
vars.push(answerItemIndex);
|
||||||
@ -249,7 +249,7 @@ export default function BranchingQuestions({
|
|||||||
label={ANSWERS[item]}
|
label={ANSWERS[item]}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onDelete={() => {
|
onDelete={() => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
const vars = question.content.rule.reqs[index].vars;
|
const vars = question.content.rule.reqs[index].vars;
|
||||||
|
|
||||||
const removedItemIndex = vars.findIndex((varItem) => varItem === item);
|
const removedItemIndex = vars.findIndex((varItem) => varItem === item);
|
||||||
@ -280,7 +280,7 @@ export default function BranchingQuestions({
|
|||||||
marginBottom: "10px",
|
marginBottom: "10px",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.rule.reqs.push({ id: "", vars: [] });
|
question.content.rule.reqs.push({ id: "", vars: [] });
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -292,7 +292,7 @@ export default function BranchingQuestions({
|
|||||||
aria-labelledby="demo-controlled-radio-buttons-group"
|
aria-labelledby="demo-controlled-radio-buttons-group"
|
||||||
value={question.content.rule.or ? 1 : 0}
|
value={question.content.rule.or ? 1 : 0}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.rule.or = Boolean(Number(value));
|
question.content.rule.or = Boolean(Number(value));
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { AnyQuizQuestion } from "@model/questionTypes/shared";
|
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 { updateQuestion } from "@root/questions/actions";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import SelectableButton from "@ui_kit/SelectableButton";
|
import SelectableButton from "@ui_kit/SelectableButton";
|
||||||
import UploadBox from "@ui_kit/UploadBox";
|
import UploadBox from "@ui_kit/UploadBox";
|
||||||
@ -21,10 +21,10 @@ export default function HelpQuestions({ question }: HelpQuestionsProps) {
|
|||||||
const [backgroundType, setBackgroundType] = useState<BackgroundType>("text");
|
const [backgroundType, setBackgroundType] = useState<BackgroundType>("text");
|
||||||
|
|
||||||
const updateQuestionHint = useDebouncedCallback((value) => {
|
const updateQuestionHint = useDebouncedCallback((value) => {
|
||||||
updateQuestionWithFnOptimistic(question.id, question => {
|
updateQuestion(question.id, question => {
|
||||||
question.content.hint.text = value;
|
question.content.hint.text = value;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -92,7 +92,7 @@ export default function HelpQuestions({ question }: HelpQuestionsProps) {
|
|||||||
open={open}
|
open={open}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
video={question.content.hint.video}
|
video={question.content.hint.video}
|
||||||
onUpload={url => updateQuestionWithFnOptimistic(question.id, question => {
|
onUpload={url => updateQuestion(question.id, question => {
|
||||||
question.content.hint.video = url;
|
question.content.hint.video = url;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
@ -1,34 +1,39 @@
|
|||||||
import { Box, Button, Tooltip } from "@mui/material";
|
import { Box, Button, Tooltip } from "@mui/material";
|
||||||
|
import { updateQuiz } from "@root/quizes/actions";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import image from "../../assets/Rectangle 110.png";
|
||||||
|
import Info from "../../assets/icons/Info";
|
||||||
import CreationFullCard from "./CreationFullCard";
|
import CreationFullCard from "./CreationFullCard";
|
||||||
|
|
||||||
import Info from "../../assets/icons/Info";
|
|
||||||
|
|
||||||
import image from "../../assets/Rectangle 110.png";
|
|
||||||
import { incrementCurrentStep } from "@root/quizes/actions";
|
|
||||||
|
|
||||||
export const Result = () => {
|
export const Result = () => {
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
return (
|
if (!quiz) return null;
|
||||||
<Box component="section">
|
|
||||||
<CreationFullCard
|
return (
|
||||||
text="Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке."
|
<Box component="section">
|
||||||
text2="Этот шаг - необязательный, квиз будет работать и без автоматических результатов."
|
<CreationFullCard
|
||||||
image={image}
|
text="Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке."
|
||||||
/>
|
text2="Этот шаг - необязательный, квиз будет работать и без автоматических результатов."
|
||||||
<Box sx={{ display: "flex", mt: "30px", alignItems: "center" }}>
|
image={image}
|
||||||
<Button
|
/>
|
||||||
variant="contained"
|
<Box sx={{ display: "flex", mt: "30px", alignItems: "center" }}>
|
||||||
sx={{ mr: "15px", minWidth: "258px" }}
|
<Button
|
||||||
onClick={incrementCurrentStep}
|
variant="contained"
|
||||||
>
|
sx={{ mr: "15px", minWidth: "258px" }}
|
||||||
Создать результаты
|
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||||
</Button>
|
quiz.config.results = true;
|
||||||
<Tooltip title="Посмотреть справку" placement="top">
|
})}
|
||||||
<Box>
|
>
|
||||||
<Info />
|
Создать результаты
|
||||||
</Box>
|
</Button>
|
||||||
</Tooltip>
|
<Tooltip title="Посмотреть справку" placement="top">
|
||||||
</Box>
|
<Box>
|
||||||
</Box>
|
<Info />
|
||||||
);
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -146,13 +146,14 @@ export default function SigninDialog() {
|
|||||||
onBlur: formik.handleBlur,
|
onBlur: formik.handleBlur,
|
||||||
error: formik.touched.email && Boolean(formik.errors.email),
|
error: formik.touched.email && Boolean(formik.errors.email),
|
||||||
helperText: formik.touched.email && formik.errors.email,
|
helperText: formik.touched.email && formik.errors.email,
|
||||||
|
"data-cy": "username",
|
||||||
}}
|
}}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
color="#F2F3F7"
|
color="#F2F3F7"
|
||||||
id="email"
|
id="email"
|
||||||
label="Email"
|
label="Email"
|
||||||
gap={upMd ? "10px" : "10px"}
|
gap={upMd ? "10px" : "10px"}
|
||||||
/>
|
/>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
TextfieldProps={{
|
TextfieldProps={{
|
||||||
value: formik.values.password,
|
value: formik.values.password,
|
||||||
@ -161,6 +162,7 @@ export default function SigninDialog() {
|
|||||||
error: formik.touched.password && Boolean(formik.errors.password),
|
error: formik.touched.password && Boolean(formik.errors.password),
|
||||||
helperText: formik.touched.password && formik.errors.password,
|
helperText: formik.touched.password && formik.errors.password,
|
||||||
type: "password",
|
type: "password",
|
||||||
|
"data-cy": "password",
|
||||||
}}
|
}}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
color="#F2F3F7"
|
color="#F2F3F7"
|
||||||
@ -183,6 +185,7 @@ export default function SigninDialog() {
|
|||||||
backgroundColor: "black",
|
backgroundColor: "black",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
data-cy="signin"
|
||||||
>
|
>
|
||||||
Войти
|
Войти
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -155,6 +155,7 @@ export default function SignupDialog() {
|
|||||||
onBlur: formik.handleBlur,
|
onBlur: formik.handleBlur,
|
||||||
error: formik.touched.email && Boolean(formik.errors.email),
|
error: formik.touched.email && Boolean(formik.errors.email),
|
||||||
helperText: formik.touched.email && formik.errors.email,
|
helperText: formik.touched.email && formik.errors.email,
|
||||||
|
"data-cy": "username",
|
||||||
}}
|
}}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
color="#F2F3F7"
|
color="#F2F3F7"
|
||||||
@ -170,6 +171,7 @@ export default function SignupDialog() {
|
|||||||
error: formik.touched.password && Boolean(formik.errors.password),
|
error: formik.touched.password && Boolean(formik.errors.password),
|
||||||
helperText: formik.touched.password && formik.errors.password,
|
helperText: formik.touched.password && formik.errors.password,
|
||||||
autoComplete: "new-password",
|
autoComplete: "new-password",
|
||||||
|
"data-cy": "password",
|
||||||
}}
|
}}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
color="#F2F3F7"
|
color="#F2F3F7"
|
||||||
@ -188,6 +190,7 @@ export default function SignupDialog() {
|
|||||||
helperText:
|
helperText:
|
||||||
formik.touched.repeatPassword && formik.errors.repeatPassword,
|
formik.touched.repeatPassword && formik.errors.repeatPassword,
|
||||||
autoComplete: "new-password",
|
autoComplete: "new-password",
|
||||||
|
"data-cy": "repeat-password",
|
||||||
}}
|
}}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
color="#F2F3F7"
|
color="#F2F3F7"
|
||||||
@ -210,6 +213,7 @@ export default function SignupDialog() {
|
|||||||
backgroundColor: "black",
|
backgroundColor: "black",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
data-cy="signup"
|
||||||
>
|
>
|
||||||
Зарегистрироваться
|
Зарегистрироваться
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -19,7 +19,7 @@ import ComplexNavText from "./ComplexNavText";
|
|||||||
import FirstQuiz from "./FirstQuiz";
|
import FirstQuiz from "./FirstQuiz";
|
||||||
import QuizCard from "./QuizCard";
|
import QuizCard from "./QuizCard";
|
||||||
import { setQuizes, createQuiz } from "@root/quizes/actions";
|
import { setQuizes, createQuiz } from "@root/quizes/actions";
|
||||||
import { useQuizArray } from "@root/quizes/hooks";
|
import { useQuizStore } from "@root/quizes/store";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -40,7 +40,7 @@ export default function MyQuizzesFull({
|
|||||||
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const quizArray = useQuizArray();
|
const quizArray = useQuizStore(state => state.quizes);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(500));
|
const isMobile = useMediaQuery(theme.breakpoints.down(500));
|
||||||
@ -69,6 +69,7 @@ export default function MyQuizzesFull({
|
|||||||
minWidth: "44px",
|
minWidth: "44px",
|
||||||
}}
|
}}
|
||||||
onClick={() => createQuiz(navigate)}
|
onClick={() => createQuiz(navigate)}
|
||||||
|
data-cy="create-quiz"
|
||||||
>
|
>
|
||||||
{isMobile ? "+" : "Создать +"}
|
{isMobile ? "+" : "Создать +"}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -33,7 +33,7 @@ export default function QuizCard({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
function handleEditClick() {
|
function handleEditClick() {
|
||||||
setEditQuizId(quiz.id);
|
setEditQuizId(quiz.backendId);
|
||||||
navigate("/edit");
|
navigate("/edit");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +139,7 @@ export default function QuizCard({
|
|||||||
ml: "auto",
|
ml: "auto",
|
||||||
}}
|
}}
|
||||||
onClick={() => deleteQuiz(quiz.id)}
|
onClick={() => deleteQuiz(quiz.id)}
|
||||||
|
data-cy="delete-quiz"
|
||||||
>
|
>
|
||||||
<MoreHorizIcon sx={{ transform: "scale(1.75)" }} />
|
<MoreHorizIcon sx={{ transform: "scale(1.75)" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -43,8 +43,8 @@ export default function StartPage() {
|
|||||||
});
|
});
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const quizId = useQuizStore(state => state.editQuizId);
|
const editQuizId = useQuizStore(state => state.editQuizId);
|
||||||
const { quiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const currentStep = useQuizStore(state => state.currentStep);
|
const currentStep = useQuizStore(state => state.currentStep);
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||||
@ -53,8 +53,8 @@ export default function StartPage() {
|
|||||||
const quizConfig = quiz?.config;
|
const quizConfig = quiz?.config;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quizId === null) navigate("/list");
|
if (editQuizId === null) navigate("/list");
|
||||||
}, [navigate, quizId]);
|
}, [navigate, editQuizId]);
|
||||||
|
|
||||||
useEffect(() => () => resetEditConfig(), []);
|
useEffect(() => () => resetEditConfig(), []);
|
||||||
|
|
||||||
@ -227,6 +227,8 @@ export default function StartPage() {
|
|||||||
<SwitchStepPages
|
<SwitchStepPages
|
||||||
activeStep={currentStep}
|
activeStep={currentStep}
|
||||||
quizType={quizConfig.type}
|
quizType={quizConfig.type}
|
||||||
|
quizResults={quizConfig.results}
|
||||||
|
quizStartPageType={quizConfig.startpageType}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { incrementCurrentStep } from "@root/quizes/actions";
|
import { incrementCurrentStep, updateQuiz } from "@root/quizes/actions";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
@ -63,7 +63,7 @@ export default function StartPageSettings() {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1500));
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1500));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(950));
|
const isTablet = useMediaQuery(theme.breakpoints.down(950));
|
||||||
const { quiz, updateQuiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const [formState, setFormState] = useState<"design" | "content">("design");
|
const [formState, setFormState] = useState<"design" | "content">("design");
|
||||||
|
|
||||||
const designType = quiz?.config?.startpageType;
|
const designType = quiz?.config?.startpageType;
|
||||||
@ -179,7 +179,7 @@ export default function StartPageSettings() {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={designType}
|
value={designType}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpageType = e.target.value as QuizStartpageType;
|
quiz.config.startpageType = e.target.value as QuizStartpageType;
|
||||||
})}
|
})}
|
||||||
sx={{
|
sx={{
|
||||||
@ -264,7 +264,7 @@ export default function StartPageSettings() {
|
|||||||
>
|
>
|
||||||
<SelectableButton
|
<SelectableButton
|
||||||
isSelected={quiz.config.startpage.background.type === "image"}
|
isSelected={quiz.config.startpage.background.type === "image"}
|
||||||
onClick={() => updateQuiz(quiz => {
|
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.background.type = "image";
|
quiz.config.startpage.background.type = "image";
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@ -272,7 +272,7 @@ export default function StartPageSettings() {
|
|||||||
</SelectableButton>
|
</SelectableButton>
|
||||||
<SelectableButton
|
<SelectableButton
|
||||||
isSelected={quiz.config.startpage.background.type === "video"}
|
isSelected={quiz.config.startpage.background.type === "video"}
|
||||||
onClick={() => updateQuiz(quiz => {
|
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.background.type = "video";
|
quiz.config.startpage.background.type = "video";
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@ -418,7 +418,7 @@ export default function StartPageSettings() {
|
|||||||
<CustomCheckbox
|
<CustomCheckbox
|
||||||
label="Зацикливать видео"
|
label="Зацикливать видео"
|
||||||
checked={quiz.config.startpage.background.cycle}
|
checked={quiz.config.startpage.background.cycle}
|
||||||
handleChange={e => updateQuiz(quiz => {
|
handleChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.background.cycle = e.target.checked;
|
quiz.config.startpage.background.cycle = e.target.checked;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -452,14 +452,14 @@ export default function StartPageSettings() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectableIconButton
|
<SelectableIconButton
|
||||||
onClick={() => updateQuiz(quiz => {
|
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.position = "left";
|
quiz.config.startpage.position = "left";
|
||||||
})}
|
})}
|
||||||
isActive={quiz.config.startpage.position === "left"}
|
isActive={quiz.config.startpage.position === "left"}
|
||||||
Icon={AlignLeftIcon}
|
Icon={AlignLeftIcon}
|
||||||
/>
|
/>
|
||||||
<SelectableIconButton
|
<SelectableIconButton
|
||||||
onClick={() => updateQuiz(quiz => {
|
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.position = "center";
|
quiz.config.startpage.position = "center";
|
||||||
})}
|
})}
|
||||||
isActive={quiz.config.startpage.position === "center"}
|
isActive={quiz.config.startpage.position === "center"}
|
||||||
@ -467,7 +467,7 @@ export default function StartPageSettings() {
|
|||||||
sx={{ display: designType === "centered" ? "flex" : "none" }}
|
sx={{ display: designType === "centered" ? "flex" : "none" }}
|
||||||
/>
|
/>
|
||||||
<SelectableIconButton
|
<SelectableIconButton
|
||||||
onClick={() => updateQuiz(quiz => {
|
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.position = "right";
|
quiz.config.startpage.position = "right";
|
||||||
})}
|
})}
|
||||||
isActive={quiz.config.startpage.position === "right"}
|
isActive={quiz.config.startpage.position === "right"}
|
||||||
@ -619,7 +619,7 @@ export default function StartPageSettings() {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="Текст-заполнитель — это текст, который"
|
placeholder="Текст-заполнитель — это текст, который"
|
||||||
text={quiz.name}
|
text={quiz.name}
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.name = e.target.value;
|
quiz.name = e.target.value;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -636,7 +636,7 @@ export default function StartPageSettings() {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="Текст-заполнитель — это текст, который "
|
placeholder="Текст-заполнитель — это текст, который "
|
||||||
text={quiz.config.startpage.description}
|
text={quiz.config.startpage.description}
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.description = e.target.value;
|
quiz.config.startpage.description = e.target.value;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -653,7 +653,7 @@ export default function StartPageSettings() {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="Начать"
|
placeholder="Начать"
|
||||||
text={quiz.config.startpage.button}
|
text={quiz.config.startpage.button}
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.button = e.target.value;
|
quiz.config.startpage.button = e.target.value;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -670,14 +670,14 @@ export default function StartPageSettings() {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="+7 900 000 00 00"
|
placeholder="+7 900 000 00 00"
|
||||||
text={quiz.config.info.phonenumber}
|
text={quiz.config.info.phonenumber}
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.info.phonenumber = e.target.value;
|
quiz.config.info.phonenumber = e.target.value;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<CustomCheckbox
|
<CustomCheckbox
|
||||||
label="Кликабельный"
|
label="Кликабельный"
|
||||||
checked={quiz.config.info.clickable}
|
checked={quiz.config.info.clickable}
|
||||||
handleChange={e => updateQuiz(quiz => {
|
handleChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.info.clickable = e.target.checked;
|
quiz.config.info.clickable = e.target.checked;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -694,7 +694,7 @@ export default function StartPageSettings() {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="Текст-заполнитель — это текст, который "
|
placeholder="Текст-заполнитель — это текст, который "
|
||||||
text={quiz.config.info.orgname}
|
text={quiz.config.info.orgname}
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.info.orgname = e.target.value;
|
quiz.config.info.orgname = e.target.value;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -711,7 +711,7 @@ export default function StartPageSettings() {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="Текст-заполнитель — это текст, который "
|
placeholder="Текст-заполнитель — это текст, который "
|
||||||
text={quiz.config.info.site}
|
text={quiz.config.info.site}
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.info.site = e.target.value;
|
quiz.config.info.site = e.target.value;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -728,7 +728,7 @@ export default function StartPageSettings() {
|
|||||||
<CustomTextField
|
<CustomTextField
|
||||||
placeholder="Текст-заполнитель — это текст, который "
|
placeholder="Текст-заполнитель — это текст, который "
|
||||||
text={quiz.config.info.law}
|
text={quiz.config.info.law}
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.info.law = e.target.value;
|
quiz.config.info.law = e.target.value;
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -815,7 +815,7 @@ export default function StartPageSettings() {
|
|||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
}}
|
}}
|
||||||
onChange={e => updateQuiz(quiz => {
|
onChange={e => updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.noStartPage = e.target.checked;
|
quiz.config.noStartPage = e.target.checked;
|
||||||
})}
|
})}
|
||||||
checked={quiz.config.noStartPage}
|
checked={quiz.config.noStartPage}
|
||||||
@ -832,14 +832,7 @@ export default function StartPageSettings() {
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
data-cy="setup-questions"
|
data-cy="setup-questions"
|
||||||
onClick={() => {
|
onClick={incrementCurrentStep}
|
||||||
updateQuiz(quiz => {
|
|
||||||
quiz.config.startpage.background.desktop = "https://happypik.ru/wp-content/uploads/2019/09/njashnye-kotiki8.jpg";
|
|
||||||
quiz.config.startpage.background.mobile = "https://krot.info/uploads/posts/2022-03/1646156155_3-krot-info-p-smeshnie-tolstie-koti-smeshnie-foto-3.png";
|
|
||||||
quiz.config.startpage.background.video = "https://youtu.be/dbaPkCiLPKQ";
|
|
||||||
});
|
|
||||||
incrementCurrentStep();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Настроить вопросы
|
Настроить вопросы
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { updateQuiz } from "@root/quizes/actions";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@ -22,7 +23,7 @@ interface Props {
|
|||||||
//Научи функцию принимать данные для валидации
|
//Научи функцию принимать данные для валидации
|
||||||
export const DropZone = ({ text, sx, heightImg, widthImg }: Props) => {
|
export const DropZone = ({ text, sx, heightImg, widthImg }: Props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { quiz, updateQuiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
|
|
||||||
if (!quiz) return null; // TODO throw and catch with error boundary
|
if (!quiz) return null; // TODO throw and catch with error boundary
|
||||||
@ -31,7 +32,7 @@ export const DropZone = ({ text, sx, heightImg, widthImg }: Props) => {
|
|||||||
const file = imgInp.files?.[0];
|
const file = imgInp.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
if (file.size < 5242880) {
|
if (file.size < 5242880) {
|
||||||
updateQuiz(quiz => {
|
updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.background.desktop = URL.createObjectURL(file);
|
quiz.config.startpage.background.desktop = URL.createObjectURL(file);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -54,7 +55,7 @@ export const DropZone = ({ text, sx, heightImg, widthImg }: Props) => {
|
|||||||
|
|
||||||
const file = event.dataTransfer.files[0];
|
const file = event.dataTransfer.files[0];
|
||||||
if (file.size < 5242880) {
|
if (file.size < 5242880) {
|
||||||
updateQuiz(quiz => {
|
updateQuiz(quiz.id, quiz => {
|
||||||
quiz.config.startpage.background.desktop = URL.createObjectURL(file);
|
quiz.config.startpage.background.desktop = URL.createObjectURL(file);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Box, Link, Typography, useTheme } from "@mui/material";
|
import { Box, Link, Typography, useTheme } from "@mui/material";
|
||||||
|
import { updateQuiz } from "@root/quizes/actions";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
import { ChangeEvent, useState } from "react";
|
import { ChangeEvent, useState } from "react";
|
||||||
@ -6,14 +7,14 @@ import { ChangeEvent, useState } from "react";
|
|||||||
export default function Extra() {
|
export default function Extra() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const { quiz, updateQuiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
const expandedHC = (bool: boolean) => {
|
const expandedHC = (bool: boolean) => {
|
||||||
setIsExpanded(bool);
|
setIsExpanded(bool);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mutationOrgMetaHC = (event: ChangeEvent<HTMLInputElement>) => {
|
const mutationOrgMetaHC = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
updateQuiz(quiz => {
|
updateQuiz(quiz?.id, quiz => {
|
||||||
quiz.config.meta = event.target.value;
|
quiz.config.meta = event.target.value;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
|||||||
|
|
||||||
|
|
||||||
export default function StepOne() {
|
export default function StepOne() {
|
||||||
const { quiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const config = quiz?.config;
|
const config = quiz?.config;
|
||||||
|
|
||||||
if (!config) return null; // TODO throw and catch with error boundary
|
if (!config) return null; // TODO throw and catch with error boundary
|
||||||
|
@ -16,7 +16,7 @@ import CardWithImage from "./CardWithImage";
|
|||||||
export default function Steptwo() {
|
export default function Steptwo() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1300));
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1300));
|
||||||
const { quiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
|
|
||||||
const config = quiz?.config;
|
const config = quiz?.config;
|
||||||
|
|
||||||
|
@ -49,10 +49,10 @@ export const questionStore = create<QuestionStore>()(
|
|||||||
isFirstPartialize = false;
|
isFirstPartialize = false;
|
||||||
|
|
||||||
Object.keys(state.listQuestions).forEach((quizId) => {
|
Object.keys(state.listQuestions).forEach((quizId) => {
|
||||||
[...state.listQuestions[quizId]].forEach(({ id, deleted }) => {
|
[...state.listQuestions[quizId]].forEach(({ backendId: id, deleted }) => {
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
const removedItemIndex = state.listQuestions[quizId].findIndex(
|
const removedItemIndex = state.listQuestions[quizId].findIndex(
|
||||||
(item) => item.id === id
|
(item) => item.backendId === id
|
||||||
);
|
);
|
||||||
|
|
||||||
state.listQuestions[quizId].splice(removedItemIndex, 1);
|
state.listQuestions[quizId].splice(removedItemIndex, 1);
|
||||||
@ -319,7 +319,7 @@ export const createQuestion = (
|
|||||||
newData[quizId].splice(
|
newData[quizId].splice(
|
||||||
placeIndex < 0 ? newData[quizId].length : placeIndex,
|
placeIndex < 0 ? newData[quizId].length : placeIndex,
|
||||||
0,
|
0,
|
||||||
{ ...JSON.parse(JSON.stringify(defaultObject)), id }
|
{ ...JSON.parse(JSON.stringify(defaultObject)), backendId: id }
|
||||||
);
|
);
|
||||||
|
|
||||||
questionStore.setState({ listQuestions: newData });
|
questionStore.setState({ listQuestions: newData });
|
||||||
@ -333,7 +333,7 @@ export const copyQuestion = (quizId: number, copiedQuestionIndex: number) => {
|
|||||||
const copiedQuiz = { ...listQuestions[quizId][copiedQuestionIndex] };
|
const copiedQuiz = { ...listQuestions[quizId][copiedQuestionIndex] };
|
||||||
listQuestions[quizId].splice(copiedQuestionIndex, 0, {
|
listQuestions[quizId].splice(copiedQuestionIndex, 0, {
|
||||||
...copiedQuiz,
|
...copiedQuiz,
|
||||||
id: getRandom(),
|
backendId: getRandom(),
|
||||||
});
|
});
|
||||||
|
|
||||||
questionStore.setState({ listQuestions });
|
questionStore.setState({ listQuestions });
|
||||||
@ -343,7 +343,7 @@ export const copyQuestion = (quizId: number, copiedQuestionIndex: number) => {
|
|||||||
export const removeQuestionForce = (quizId: number, removedId: number) => {
|
export const removeQuestionForce = (quizId: number, removedId: number) => {
|
||||||
const questionListClone = { ...questionStore.getState()["listQuestions"] };
|
const questionListClone = { ...questionStore.getState()["listQuestions"] };
|
||||||
const removedItemIndex = questionListClone[quizId].findIndex(
|
const removedItemIndex = questionListClone[quizId].findIndex(
|
||||||
({ id }) => id === removedId
|
({ backendId: id }) => id === removedId
|
||||||
);
|
);
|
||||||
questionListClone[quizId].splice(removedItemIndex, 1);
|
questionListClone[quizId].splice(removedItemIndex, 1);
|
||||||
questionStore.setState({ listQuestions: questionListClone });
|
questionStore.setState({ listQuestions: questionListClone });
|
||||||
@ -362,7 +362,7 @@ export const findQuestionById = (quizId: number) => {
|
|||||||
questionStore
|
questionStore
|
||||||
.getState()
|
.getState()
|
||||||
["listQuestions"][quizId].some((quiz: AnyQuizQuestion, index: number) => {
|
["listQuestions"][quizId].some((quiz: AnyQuizQuestion, index: number) => {
|
||||||
if (quiz.id === quizId) {
|
if (quiz.backendId === quizId) {
|
||||||
found = { quiz, index };
|
found = { quiz, index };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { questionApi } from "@api/question";
|
import { questionApi } from "@api/question";
|
||||||
import { devlog } from "@frontend/kitui";
|
import { devlog } from "@frontend/kitui";
|
||||||
import { EditQuestionResponse, questionToEditQuestionRequest } from "@model/question/edit";
|
import { questionToEditQuestionRequest } from "@model/question/edit";
|
||||||
import { QuestionType, 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";
|
||||||
import { notReachable } from "../../utils/notReachable";
|
|
||||||
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
||||||
import { QuestionsStore, useQuestionsStore } from "./store";
|
import { notReachable } from "../../utils/notReachable";
|
||||||
import { RequestQueue } from "../../utils/requestQueue";
|
import { RequestQueue } from "../../utils/requestQueue";
|
||||||
|
import { QuestionsStore, useQuestionsStore } from "./store";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
|
||||||
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
||||||
@ -18,14 +19,6 @@ export const setQuestions = (questions: RawQuestion[] | null) => setProducedStat
|
|||||||
questions,
|
questions,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
|
|
||||||
const index = state.questions.findIndex(q => q.id === question.id);
|
|
||||||
state.questions.splice(index, 1, question);
|
|
||||||
}, {
|
|
||||||
type: "setQuestion",
|
|
||||||
question,
|
|
||||||
});
|
|
||||||
|
|
||||||
const addQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
|
const addQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
|
||||||
state.questions.push(question);
|
state.questions.push(question);
|
||||||
}, {
|
}, {
|
||||||
@ -33,28 +26,25 @@ const addQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
|
|||||||
question,
|
question,
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeQuestion = (questionId: number) => setProducedState(state => {
|
const removeQuestion = (questionId: string) => setProducedState(state => {
|
||||||
const index = state.questions.findIndex(q => q.id === questionId);
|
const index = state.questions.findIndex(q => q.id === questionId);
|
||||||
|
if (index === -1) return;
|
||||||
|
|
||||||
state.questions.splice(index, 1);
|
state.questions.splice(index, 1);
|
||||||
}, {
|
}, {
|
||||||
type: "removeQuestion",
|
type: "removeQuestion",
|
||||||
questionId,
|
questionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setQuestionField = <T extends keyof AnyQuizQuestion>(
|
const setQuestionBackendId = (questionId: string, backendId: number) => setProducedState(state => {
|
||||||
questionId: number,
|
|
||||||
field: T,
|
|
||||||
value: AnyQuizQuestion[T],
|
|
||||||
) => setProducedState(state => {
|
|
||||||
const question = state.questions.find(q => q.id === questionId);
|
const question = state.questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
question[field] = value;
|
question.backendId = backendId;
|
||||||
}, {
|
}, {
|
||||||
type: "setQuestionField",
|
type: "setQuestionBackendId",
|
||||||
questionId,
|
questionId: questionId,
|
||||||
field,
|
backendId,
|
||||||
value,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const reorderQuestions = (
|
export const reorderQuestions = (
|
||||||
@ -69,7 +59,7 @@ export const reorderQuestions = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toggleExpandQuestion = (questionId: number) => setProducedState(state => {
|
export const toggleExpandQuestion = (questionId: string) => setProducedState(state => {
|
||||||
const question = state.questions.find(q => q.id === questionId);
|
const question = state.questions.find(q => q.id === questionId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
|
|
||||||
@ -80,15 +70,8 @@ export const collapseAllQuestions = () => setProducedState(state => {
|
|||||||
state.questions.forEach(question => question.expanded = false);
|
state.questions.forEach(question => question.expanded = false);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const toggleOpenQuestionModal = (questionId: number) => setProducedState(state => {
|
export const addQuestionVariant = (questionId: string) => {
|
||||||
const question = state.questions.find(q => q.id === questionId);
|
updateQuestion(questionId, question => {
|
||||||
if (!question) return;
|
|
||||||
|
|
||||||
question.openedModalSettings = !question.openedModalSettings;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addQuestionVariant = (questionId: number) => {
|
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
|
||||||
switch (question.type) {
|
switch (question.type) {
|
||||||
case "variant":
|
case "variant":
|
||||||
case "emoji":
|
case "emoji":
|
||||||
@ -111,8 +94,8 @@ export const addQuestionVariant = (questionId: number) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteQuestionVariant = (questionId: number, variantId: string) => {
|
export const deleteQuestionVariant = (questionId: string, variantId: string) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
||||||
@ -123,12 +106,12 @@ export const deleteQuestionVariant = (questionId: number, variantId: string) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionVariantField = (
|
export const setQuestionVariantField = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
variantId: string,
|
variantId: string,
|
||||||
field: keyof QuestionVariant,
|
field: keyof QuestionVariant,
|
||||||
value: QuestionVariant[keyof QuestionVariant],
|
value: QuestionVariant[keyof QuestionVariant],
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
||||||
@ -140,12 +123,12 @@ export const setQuestionVariantField = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionImageVariantField = (
|
export const setQuestionImageVariantField = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
variantId: string,
|
variantId: string,
|
||||||
field: keyof ImageQuestionVariant,
|
field: keyof ImageQuestionVariant,
|
||||||
value: ImageQuestionVariant[keyof ImageQuestionVariant],
|
value: ImageQuestionVariant[keyof ImageQuestionVariant],
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
const variantIndex = question.content.variants.findIndex(variant => variant.id === variantId);
|
||||||
@ -159,13 +142,13 @@ export const setQuestionImageVariantField = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const reorderQuestionVariants = (
|
export const reorderQuestionVariants = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
sourceIndex: number,
|
sourceIndex: number,
|
||||||
destinationIndex: number,
|
destinationIndex: number,
|
||||||
) => {
|
) => {
|
||||||
if (sourceIndex === destinationIndex) return;
|
if (sourceIndex === destinationIndex) return;
|
||||||
|
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const [removed] = question.content.variants.splice(sourceIndex, 1);
|
const [removed] = question.content.variants.splice(sourceIndex, 1);
|
||||||
@ -175,10 +158,10 @@ export const reorderQuestionVariants = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionBackgroundImage = (
|
export const setQuestionBackgroundImage = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
url: string,
|
url: string,
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (question.content.back === url) return;
|
if (question.content.back === url) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -189,10 +172,10 @@ export const setQuestionBackgroundImage = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionOriginalBackgroundImage = (
|
export const setQuestionOriginalBackgroundImage = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
url: string,
|
url: string,
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (question.content.originalBack === url) return;
|
if (question.content.originalBack === url) return;
|
||||||
|
|
||||||
URL.revokeObjectURL(question.content.originalBack);
|
URL.revokeObjectURL(question.content.originalBack);
|
||||||
@ -201,11 +184,11 @@ export const setQuestionOriginalBackgroundImage = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setVariantImageUrl = (
|
export const setVariantImageUrl = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
variantId: string,
|
variantId: string,
|
||||||
url: string,
|
url: string,
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variant = question.content.variants.find(variant => variant.id === variantId);
|
const variant = question.content.variants.find(variant => variant.id === variantId);
|
||||||
@ -219,11 +202,11 @@ export const setVariantImageUrl = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setVariantOriginalImageUrl = (
|
export const setVariantOriginalImageUrl = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
variantId: string,
|
variantId: string,
|
||||||
url: string,
|
url: string,
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (!("variants" in question.content)) return;
|
if (!("variants" in question.content)) return;
|
||||||
|
|
||||||
const variant = question.content.variants.find(
|
const variant = question.content.variants.find(
|
||||||
@ -239,10 +222,10 @@ export const setVariantOriginalImageUrl = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setPageQuestionPicture = (
|
export const setPageQuestionPicture = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
url: string,
|
url: string,
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (question.type !== "page") return;
|
if (question.type !== "page") return;
|
||||||
|
|
||||||
if (question.content.picture === url) return;
|
if (question.content.picture === url) return;
|
||||||
@ -255,10 +238,10 @@ export const setPageQuestionPicture = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setPageQuestionOriginalPicture = (
|
export const setPageQuestionOriginalPicture = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
url: string,
|
url: string,
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
if (question.type !== "page") return;
|
if (question.type !== "page") return;
|
||||||
|
|
||||||
if (question.content.originalPicture === url) return;
|
if (question.content.originalPicture === url) return;
|
||||||
@ -269,47 +252,52 @@ export const setPageQuestionOriginalPicture = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setQuestionInnerName = (
|
export const setQuestionInnerName = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
name: string,
|
name: string,
|
||||||
) => {
|
) => {
|
||||||
updateQuestionWithFnOptimistic(questionId, question => {
|
updateQuestion(questionId, question => {
|
||||||
question.content.innerName = name;
|
question.content.innerName = name;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const REQUEST_DEBOUNCE = 1000;
|
const REQUEST_DEBOUNCE = 200;
|
||||||
const requestQueue = new RequestQueue<EditQuestionResponse>();
|
const requestQueue = new RequestQueue();
|
||||||
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
export const updateQuestionWithFnOptimistic = async (
|
export const updateQuestion = (
|
||||||
questionId: number,
|
questionId: string,
|
||||||
updateFn: (question: AnyQuizQuestion) => void,
|
updateFn: (question: AnyQuizQuestion) => void,
|
||||||
) => {
|
) => {
|
||||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
setProducedState(state => {
|
||||||
if (!question) return;
|
const question = state.questions.find(q => q.id === questionId);
|
||||||
|
if (!question) return;
|
||||||
|
|
||||||
const updatedQuestion = produce(question, updateFn);
|
updateFn(question);
|
||||||
setQuestion(updatedQuestion);
|
}, {
|
||||||
|
type: "updateQuestion",
|
||||||
|
questionId,
|
||||||
|
updateFn: updateFn.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
clearTimeout(requestTimeoutId);
|
clearTimeout(requestTimeoutId);
|
||||||
requestTimeoutId = setTimeout(async () => {
|
requestTimeoutId = setTimeout(() => {
|
||||||
requestQueue.enqueue(async (prevResponse) => {
|
requestQueue.enqueue(async () => {
|
||||||
const questionId = prevResponse?.updated ?? updatedQuestion.id;
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||||
const response = await questionApi.edit(questionToEditQuestionRequest(updatedQuestion, questionId));
|
if (!question) return;
|
||||||
|
|
||||||
setQuestionField(questionId, "id", response.updated);
|
const response = await questionApi.edit(questionToEditQuestionRequest(question));
|
||||||
|
|
||||||
return response;
|
setQuestionBackendId(questionId, response.updated);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (isAxiosCanceledError(error)) return;
|
if (isAxiosCanceledError(error)) return;
|
||||||
|
|
||||||
devlog("Error editing question", { error, question, updatedQuestion });
|
devlog("Error editing question", { error, questionId });
|
||||||
enqueueSnackbar("Не удалось сохранить вопрос");
|
enqueueSnackbar("Не удалось сохранить вопрос");
|
||||||
});
|
});
|
||||||
}, REQUEST_DEBOUNCE);
|
}, REQUEST_DEBOUNCE);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createQuestion = async (quizId: number, type: QuestionType = "variant") => {
|
export const createQuestion = async (quizId: number, type: QuestionType = "variant") => requestQueue.enqueue(async () => {
|
||||||
try {
|
try {
|
||||||
const question = await questionApi.create({
|
const question = await questionApi.create({
|
||||||
quiz_id: quizId,
|
quiz_id: quizId,
|
||||||
@ -321,41 +309,45 @@ export const createQuestion = async (quizId: number, type: QuestionType = "varia
|
|||||||
devlog("Error creating question", error);
|
devlog("Error creating question", error);
|
||||||
enqueueSnackbar("Не удалось создать вопрос");
|
enqueueSnackbar("Не удалось создать вопрос");
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => {
|
||||||
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||||
|
if (!question) return;
|
||||||
|
|
||||||
export const deleteQuestion = async (questionId: number) => {
|
|
||||||
try {
|
try {
|
||||||
await questionApi.delete(questionId);
|
await questionApi.delete(question.backendId);
|
||||||
|
|
||||||
removeQuestion(questionId);
|
removeQuestion(questionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
devlog("Error deleting question", error);
|
devlog("Error deleting question", error);
|
||||||
enqueueSnackbar("Не удалось удалить вопрос");
|
enqueueSnackbar("Не удалось удалить вопрос");
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
export const copyQuestion = async (questionId: string, quizId: number) => requestQueue.enqueue(async () => {
|
||||||
|
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||||
|
if (!question) return;
|
||||||
|
|
||||||
export const copyQuestion = async (questionId: number, quizId: number) => {
|
|
||||||
try {
|
try {
|
||||||
const { updated: newQuestionId } = await questionApi.copy(questionId, quizId);
|
const { updated: newQuestionId } = await questionApi.copy(question.backendId, quizId);
|
||||||
|
|
||||||
|
const copiedQuestion = structuredClone(question);
|
||||||
|
copiedQuestion.backendId = newQuestionId;
|
||||||
|
copiedQuestion.id = nanoid();
|
||||||
|
|
||||||
setProducedState(state => {
|
setProducedState(state => {
|
||||||
const question = state.questions.find(q => q.id === questionId);
|
|
||||||
if (!question) return;
|
|
||||||
|
|
||||||
console.log(question);
|
|
||||||
const copiedQuestion = structuredClone(question);
|
|
||||||
copiedQuestion.id = newQuestionId;
|
|
||||||
state.questions.push(copiedQuestion);
|
state.questions.push(copiedQuestion);
|
||||||
}, {
|
}, {
|
||||||
type: "copyQuestion",
|
type: "copyQuestion",
|
||||||
questionId,
|
questionId: questionId,
|
||||||
quizId,
|
quizId,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
devlog("Error copying question", error);
|
devlog("Error copying question", error);
|
||||||
enqueueSnackbar("Не удалось скопировать вопрос");
|
enqueueSnackbar("Не удалось скопировать вопрос");
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
function setProducedState<A extends string | { type: unknown; }>(
|
function setProducedState<A extends string | { type: unknown; }>(
|
||||||
recipe: (state: QuestionsStore) => void,
|
recipe: (state: QuestionsStore) => void,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { quizApi } from "@api/quiz";
|
import { quizApi } from "@api/quiz";
|
||||||
import { devlog, getMessageFromFetchError } from "@frontend/kitui";
|
import { devlog, getMessageFromFetchError } from "@frontend/kitui";
|
||||||
import { EditQuizResponse, quizToEditQuizRequest } from "@model/quiz/edit";
|
import { quizToEditQuizRequest } from "@model/quiz/edit";
|
||||||
import { Quiz, RawQuiz, rawQuizToQuiz } from "@model/quiz/quiz";
|
import { Quiz, RawQuiz, rawQuizToQuiz } from "@model/quiz/quiz";
|
||||||
import { QuizConfig, QuizSetupStep, maxQuizSetupSteps } from "@model/quizSettings";
|
import { QuizConfig, maxQuizSetupSteps } from "@model/quizSettings";
|
||||||
import { createQuestion } from "@root/questions/actions";
|
import { createQuestion } from "@root/questions/actions";
|
||||||
import { produce } from "immer";
|
import { produce } from "immer";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
@ -21,156 +21,148 @@ export const setEditQuizId = (quizId: number | null) => setProducedState(state =
|
|||||||
|
|
||||||
export const resetEditConfig = () => setProducedState(state => {
|
export const resetEditConfig = () => setProducedState(state => {
|
||||||
state.editQuizId = null;
|
state.editQuizId = null;
|
||||||
state.currentStep = 1;
|
state.currentStep = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setQuizes = (quizes: RawQuiz[] | null) => setProducedState(state => {
|
export const setQuizes = (quizes: RawQuiz[] | null) => setProducedState(state => {
|
||||||
state.quizById = {};
|
state.quizes = quizes?.map(rawQuizToQuiz) ?? [];
|
||||||
if (quizes === null) return;
|
|
||||||
|
|
||||||
quizes.forEach(quiz => state.quizById[quiz.id] = rawQuizToQuiz(quiz));
|
|
||||||
}, {
|
}, {
|
||||||
type: "setQuizes",
|
type: "setQuizes",
|
||||||
quizes,
|
quizes,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setQuiz = (quiz: Quiz) => setProducedState(state => {
|
const addQuiz = (quiz: Quiz) => setProducedState(state => {
|
||||||
state.quizById[quiz.id] = quiz;
|
state.quizes.push(quiz);
|
||||||
}, {
|
}, {
|
||||||
type: "setQuiz",
|
type: "addQuiz",
|
||||||
quiz,
|
quiz,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const removeQuiz = (quizId: number) => setProducedState(state => {
|
const removeQuiz = (quizId: string) => setProducedState(state => {
|
||||||
delete state.quizById[quizId];
|
const index = state.quizes.findIndex(q => q.id === quizId);
|
||||||
|
if (index === -1) return;
|
||||||
|
|
||||||
|
state.quizes.splice(index, 1);
|
||||||
}, {
|
}, {
|
||||||
type: "removeQuiz",
|
type: "removeQuiz",
|
||||||
quizId,
|
quizId,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setQuizField = <T extends keyof Quiz>(
|
const setQuizBackendId = (quizId: string, backendId: number) => setProducedState(state => {
|
||||||
quizId: number,
|
const quiz = state.quizes.find(q => q.id === quizId);
|
||||||
field: T,
|
|
||||||
value: Quiz[T],
|
|
||||||
) => setProducedState(state => {
|
|
||||||
const quiz = state.quizById[quizId];
|
|
||||||
if (!quiz) return;
|
if (!quiz) return;
|
||||||
|
|
||||||
const oldId = quiz.id;
|
quiz.backendId = backendId;
|
||||||
quiz[field] = value;
|
|
||||||
|
|
||||||
if (field === "id") {
|
|
||||||
delete state.quizById[oldId];
|
|
||||||
state.quizById[value as number] = quiz;
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
type: "setQuizField",
|
type: "setQuizBackendId",
|
||||||
quizId,
|
quizId,
|
||||||
field,
|
backendId,
|
||||||
value,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const incrementCurrentStep = () => setProducedState(state => {
|
export const incrementCurrentStep = () => setProducedState(state => {
|
||||||
state.currentStep = Math.min(
|
state.currentStep = Math.min(maxQuizSetupSteps - 1, state.currentStep + 1);
|
||||||
maxQuizSetupSteps, state.currentStep + 1
|
|
||||||
) as QuizSetupStep;
|
|
||||||
}, {
|
}, {
|
||||||
type: "incrementCurrentStep",
|
type: "incrementCurrentStep",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const decrementCurrentStep = () => setProducedState(state => {
|
export const decrementCurrentStep = () => setProducedState(state => {
|
||||||
state.currentStep = Math.max(
|
state.currentStep = Math.max(0, state.currentStep - 1);
|
||||||
1, state.currentStep - 1
|
|
||||||
) as QuizSetupStep;
|
|
||||||
}, {
|
}, {
|
||||||
type: "decrementCurrentStep",
|
type: "decrementCurrentStep",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setCurrentStep = (step: number) => setProducedState(state => {
|
export const setCurrentStep = (step: number) => setProducedState(state => {
|
||||||
state.currentStep = Math.max(0, Math.min(maxQuizSetupSteps, step)) as QuizSetupStep;
|
state.currentStep = Math.max(0, Math.min(maxQuizSetupSteps - 1, step));
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setQuizType = (
|
export const setQuizType = (
|
||||||
quizId: number,
|
quizId: string,
|
||||||
quizType: QuizConfig["type"],
|
quizType: QuizConfig["type"],
|
||||||
) => {
|
) => {
|
||||||
updateQuizWithFnOptimistic(
|
updateQuiz(
|
||||||
quizId,
|
quizId,
|
||||||
quiz => {
|
quiz => {
|
||||||
quiz.config.type = quizType;
|
quiz.config.type = quizType;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
incrementCurrentStep();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setQuizStartpageType = (
|
export const setQuizStartpageType = (
|
||||||
quizId: number | null,
|
quizId: string,
|
||||||
startpageType: QuizConfig["startpageType"],
|
startpageType: QuizConfig["startpageType"],
|
||||||
) => {
|
) => {
|
||||||
updateQuizWithFnOptimistic(
|
updateQuiz(
|
||||||
quizId,
|
quizId,
|
||||||
quiz => {
|
quiz => {
|
||||||
quiz.config.startpageType = startpageType;
|
quiz.config.startpageType = startpageType;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
incrementCurrentStep();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const REQUEST_DEBOUNCE = 1000;
|
const REQUEST_DEBOUNCE = 200;
|
||||||
const requestQueue = new RequestQueue<EditQuizResponse>();
|
const requestQueue = new RequestQueue();
|
||||||
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
export const updateQuizWithFnOptimistic = async (
|
export const updateQuiz = async (
|
||||||
quizId: number | null,
|
quizId: string | null | undefined,
|
||||||
updateFn: (quiz: Quiz) => void,
|
updateFn: (quiz: Quiz) => void,
|
||||||
) => {
|
) => {
|
||||||
if (!quizId) return;
|
if (!quizId) return;
|
||||||
|
|
||||||
const quiz = useQuizStore.getState().quizById[quizId];
|
setProducedState(state => {
|
||||||
if (!quiz) return;
|
const quiz = state.quizes.find(q => q.id === quizId);
|
||||||
|
if (!quiz) return;
|
||||||
|
|
||||||
const updatedQuiz = produce(quiz, updateFn);
|
updateFn(quiz);
|
||||||
setQuiz(updatedQuiz);
|
}, {
|
||||||
|
type: "updateQuiz",
|
||||||
|
quizId,
|
||||||
|
updateFn: updateFn.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
clearTimeout(requestTimeoutId);
|
clearTimeout(requestTimeoutId);
|
||||||
requestTimeoutId = setTimeout(async () => {
|
requestTimeoutId = setTimeout(async () => {
|
||||||
requestQueue.enqueue(async (prevResponse) => {
|
requestQueue.enqueue(async () => {
|
||||||
const quizId = prevResponse?.updated ?? updatedQuiz.id;
|
const quiz = useQuizStore.getState().quizes.find(q => q.id === quizId);
|
||||||
const response = await quizApi.edit(quizToEditQuizRequest(updatedQuiz, quizId));
|
if (!quiz) return;
|
||||||
|
|
||||||
setQuizField(quizId, "id", response.updated);
|
const response = await quizApi.edit(quizToEditQuizRequest(quiz));
|
||||||
|
|
||||||
|
setQuizBackendId(quizId, response.updated);
|
||||||
setEditQuizId(response.updated);
|
setEditQuizId(response.updated);
|
||||||
|
|
||||||
return response;
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (isAxiosCanceledError(error)) return;
|
if (isAxiosCanceledError(error)) return;
|
||||||
|
|
||||||
devlog("Error editing quiz", { error, quiz, updatedQuiz });
|
devlog("Error editing quiz", error, quizId);
|
||||||
enqueueSnackbar("Не удалось сохранить настройки квиза");
|
enqueueSnackbar("Не удалось сохранить настройки квиза");
|
||||||
});
|
});
|
||||||
}, REQUEST_DEBOUNCE);
|
}, REQUEST_DEBOUNCE);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createQuiz = async (navigate: NavigateFunction) => {
|
export const createQuiz = async (navigate: NavigateFunction) => requestQueue.enqueue(async () => {
|
||||||
try {
|
try {
|
||||||
const quiz = await quizApi.create();
|
const rawQuiz = await quizApi.create();
|
||||||
|
const quiz = rawQuizToQuiz(rawQuiz);
|
||||||
|
|
||||||
setQuiz(rawQuizToQuiz(quiz));
|
addQuiz(quiz);
|
||||||
setEditQuizId(quiz.id);
|
setEditQuizId(quiz.backendId);
|
||||||
navigate("/edit");
|
navigate("/edit");
|
||||||
|
|
||||||
await createQuestion(quiz.id);
|
await createQuestion(rawQuiz.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
devlog("Error creating quiz", error);
|
devlog("Error creating quiz", error);
|
||||||
|
|
||||||
const message = getMessageFromFetchError(error) ?? "";
|
const message = getMessageFromFetchError(error) ?? "";
|
||||||
enqueueSnackbar(`Не удалось создать квиз. ${message}`);
|
enqueueSnackbar(`Не удалось создать квиз. ${message}`);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async () => {
|
||||||
|
const quiz = useQuizStore.getState().quizes.find(q => q.id === quizId);
|
||||||
|
if (!quiz) return;
|
||||||
|
|
||||||
export const deleteQuiz = async (quizId: number) => {
|
|
||||||
try {
|
try {
|
||||||
await quizApi.delete(quizId);
|
await quizApi.delete(quiz.backendId);
|
||||||
|
|
||||||
removeQuiz(quizId);
|
removeQuiz(quizId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -179,7 +171,9 @@ export const deleteQuiz = async (quizId: number) => {
|
|||||||
const message = getMessageFromFetchError(error) ?? "";
|
const message = getMessageFromFetchError(error) ?? "";
|
||||||
enqueueSnackbar(`Не удалось удалить квиз. ${message}`);
|
enqueueSnackbar(`Не удалось удалить квиз. ${message}`);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
// TODO copy quiz
|
||||||
|
|
||||||
function setProducedState<A extends string | { type: unknown; }>(
|
function setProducedState<A extends string | { type: unknown; }>(
|
||||||
recipe: (state: QuizStore) => void,
|
recipe: (state: QuizStore) => void,
|
||||||
|
@ -1,22 +1,11 @@
|
|||||||
import { Quiz } from "@model/quiz/quiz";
|
|
||||||
import { useCallback } from "react";
|
|
||||||
import { updateQuizWithFnOptimistic } from "./actions";
|
|
||||||
import { useQuizStore } from "./store";
|
import { useQuizStore } from "./store";
|
||||||
|
|
||||||
|
|
||||||
export function useQuizArray(): Quiz[] {
|
|
||||||
const quizes = useQuizStore(state => state.quizById);
|
|
||||||
|
|
||||||
return Object.values(quizes).flatMap(quiz => quiz ? [quiz] : []);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useCurrentQuiz() {
|
export function useCurrentQuiz() {
|
||||||
const quizId = useQuizStore(state => state.editQuizId);
|
const quizId = useQuizStore(state => state.editQuizId);
|
||||||
const quiz = useQuizStore(state => state.quizById[quizId ?? -1]);
|
const quizes = useQuizStore(state => state.quizes);
|
||||||
|
|
||||||
const updateQuiz = useCallback((updateFn: (quiz: Quiz) => void) => {
|
const quiz = quizes.find(q => q.backendId === quizId);
|
||||||
updateQuizWithFnOptimistic(quizId, updateFn);
|
|
||||||
}, [quizId]);
|
|
||||||
|
|
||||||
return { quiz, updateQuiz };
|
return quiz;
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import { Quiz } from "@model/quiz/quiz";
|
import { Quiz } from "@model/quiz/quiz";
|
||||||
import { QuizSetupStep } from "@model/quizSettings";
|
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { devtools, persist } from "zustand/middleware";
|
import { devtools, persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
|
||||||
export type QuizStore = {
|
export type QuizStore = {
|
||||||
quizById: Record<number, Quiz | undefined>;
|
quizes: Quiz[];
|
||||||
editQuizId: number | null;
|
editQuizId: number | null;
|
||||||
currentStep: QuizSetupStep;
|
currentStep: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: QuizStore = {
|
const initialState: QuizStore = {
|
||||||
quizById: {},
|
quizes: [],
|
||||||
editQuizId: null,
|
editQuizId: null,
|
||||||
currentStep: 1,
|
currentStep: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useQuizStore = create<QuizStore>()(
|
export const useQuizStore = create<QuizStore>()(
|
||||||
@ -31,6 +30,5 @@ export const useQuizStore = create<QuizStore>()(
|
|||||||
editQuizId: state.editQuizId,
|
editQuizId: state.editQuizId,
|
||||||
currentStep: state.currentStep,
|
currentStep: state.currentStep,
|
||||||
}),
|
}),
|
||||||
}
|
})
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
import ChartPieIcon from "@icons/ChartPieIcon";
|
|
||||||
import CollapseMenuIcon from "@icons/CollapseMenuIcon";
|
import CollapseMenuIcon from "@icons/CollapseMenuIcon";
|
||||||
import ContactBookIcon from "@icons/ContactBookIcon";
|
|
||||||
import FlowArrowIcon from "@icons/FlowArrowIcon";
|
|
||||||
import GearIcon from "@icons/GearIcon";
|
import GearIcon from "@icons/GearIcon";
|
||||||
import LayoutIcon from "@icons/LayoutIcon";
|
|
||||||
import MegaphoneIcon from "@icons/MegaphoneIcon";
|
|
||||||
import PencilCircleIcon from "@icons/PencilCircleIcon";
|
import PencilCircleIcon from "@icons/PencilCircleIcon";
|
||||||
import PuzzlePieceIcon from "@icons/PuzzlePieceIcon";
|
import PuzzlePieceIcon from "@icons/PuzzlePieceIcon";
|
||||||
import QuestionIcon from "@icons/QuestionIcon";
|
|
||||||
import QuestionsMapIcon from "@icons/QuestionsMapIcon";
|
|
||||||
import TagIcon from "@icons/TagIcon";
|
import TagIcon from "@icons/TagIcon";
|
||||||
import { quizSetupSteps } from "@model/quizSettings";
|
import { quizSetupSteps } from "@model/quizSettings";
|
||||||
import {
|
import {
|
||||||
@ -23,15 +16,6 @@ import { useQuizStore } from "@root/quizes/store";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import MenuItem from "./MenuItem";
|
import MenuItem from "./MenuItem";
|
||||||
|
|
||||||
const createQuizMenuItems = [
|
|
||||||
[LayoutIcon, "Стартовая страница"],
|
|
||||||
[QuestionIcon, "Вопросы"],
|
|
||||||
[ChartPieIcon, "Результаты"],
|
|
||||||
[QuestionsMapIcon, "Карта вопросов"],
|
|
||||||
[ContactBookIcon, "Форма контактов"],
|
|
||||||
[FlowArrowIcon, "Установка квиза"],
|
|
||||||
[MegaphoneIcon, "Запуск рекламы"],
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const quizSettingsMenuItems = [
|
const quizSettingsMenuItems = [
|
||||||
[TagIcon, "Дополнения"],
|
[TagIcon, "Дополнения"],
|
||||||
@ -43,7 +27,6 @@ const quizSettingsMenuItems = [
|
|||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [isMenuCollapsed, setIsMenuCollapsed] = useState(false);
|
const [isMenuCollapsed, setIsMenuCollapsed] = useState(false);
|
||||||
const [progress, setProgress] = useState<number>(1 / 7);
|
|
||||||
const currentStep = useQuizStore(state => state.currentStep);
|
const currentStep = useQuizStore(state => state.currentStep);
|
||||||
|
|
||||||
const handleMenuCollapseToggle = () => setIsMenuCollapsed((prev) => !prev);
|
const handleMenuCollapseToggle = () => setIsMenuCollapsed((prev) => !prev);
|
||||||
@ -100,19 +83,20 @@ export default function Sidebar() {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
<List disablePadding>
|
<List disablePadding>
|
||||||
{createQuizMenuItems.map((menuItem, index) => {
|
{quizSetupSteps.map((menuItem, index) => {
|
||||||
const Icon = menuItem[0];
|
const Icon = menuItem.sidebarIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => setCurrentStep(index + 1)}
|
onClick={() => setCurrentStep(index)}
|
||||||
key={menuItem[1]}
|
key={index}
|
||||||
text={menuItem[1]}
|
text={menuItem.sidebarText}
|
||||||
isCollapsed={isMenuCollapsed}
|
isCollapsed={isMenuCollapsed}
|
||||||
isActive={quizSetupSteps[currentStep].displayStep === index + 1}
|
isActive={currentStep === index}
|
||||||
icon={
|
icon={
|
||||||
<Icon
|
<Icon
|
||||||
color={
|
color={
|
||||||
quizSetupSteps[currentStep].displayStep === index + 1
|
currentStep === index
|
||||||
? theme.palette.brightPurple.main
|
? theme.palette.brightPurple.main
|
||||||
: isMenuCollapsed
|
: isMenuCollapsed
|
||||||
? "white"
|
? "white"
|
||||||
@ -141,14 +125,15 @@ export default function Sidebar() {
|
|||||||
Настройки квиза
|
Настройки квиза
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{/* <List disablePadding> // TODO
|
<List disablePadding>
|
||||||
{quizSettingsMenuItems.map((menuItem, index) => {
|
{quizSettingsMenuItems.map((menuItem, index) => {
|
||||||
const Icon = menuItem[0];
|
const Icon = menuItem[0];
|
||||||
const totalIndex = index + createQuizMenuItems.length;
|
const totalIndex = index + quizSetupSteps.length;
|
||||||
const isActive = listQuizes[quizId].step === totalIndex + 1;
|
const isActive = currentStep === totalIndex + 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => updateQuizesList(quizId, { step: totalIndex + 1 })}
|
onClick={() => null}
|
||||||
key={menuItem[1]}
|
key={menuItem[1]}
|
||||||
text={menuItem[1]}
|
text={menuItem[1]}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
@ -169,7 +154,7 @@ export default function Sidebar() {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List> */}
|
</List>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
|||||||
|
|
||||||
export default function QuizPreviewLayout() {
|
export default function QuizPreviewLayout() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { quiz } = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(630));
|
const isTablet = useMediaQuery(theme.breakpoints.down(630));
|
||||||
|
|
||||||
if (!quiz) return null;
|
if (!quiz) return null;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import MobileStepper from "@mui/material/MobileStepper";
|
import MobileStepper from "@mui/material/MobileStepper";
|
||||||
import { QuizSetupStep, maxDisplayQuizSetupSteps, quizSetupSteps } from "@model/quizSettings";
|
import { maxQuizSetupSteps, quizSetupSteps } from "@model/quizSettings";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
activeStep: QuizSetupStep;
|
activeStep: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProgressMobileStepper({
|
export default function ProgressMobileStepper({
|
||||||
@ -28,9 +28,9 @@ export default function ProgressMobileStepper({
|
|||||||
>
|
>
|
||||||
<MobileStepper
|
<MobileStepper
|
||||||
variant="progress"
|
variant="progress"
|
||||||
steps={maxDisplayQuizSetupSteps}
|
steps={maxQuizSetupSteps}
|
||||||
position="static"
|
position="static"
|
||||||
activeStep={quizSetupSteps[activeStep].displayStep - 1}
|
activeStep={activeStep}
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@ -52,9 +52,9 @@ export default function ProgressMobileStepper({
|
|||||||
sx={{ fontWeight: 400, fontSize: "12px", lineHeight: "14.22px" }}
|
sx={{ fontWeight: 400, fontSize: "12px", lineHeight: "14.22px" }}
|
||||||
>
|
>
|
||||||
{" "}
|
{" "}
|
||||||
Шаг {quizSetupSteps[activeStep].displayStep} из {maxDisplayQuizSetupSteps}
|
Шаг {activeStep + 1 } из {maxQuizSetupSteps}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>{quizSetupSteps[activeStep].text}</Typography>
|
<Typography>{quizSetupSteps[activeStep].stepperText}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { QuizSetupStep } from "@model/quizSettings";
|
import { QuizResultsType, QuizStartpageType, QuizType } from "@model/quizSettings";
|
||||||
import { notReachable } from "../utils/notReachable";
|
|
||||||
import ContactFormPage from "../pages/ContactFormPage/ContactFormPage";
|
import ContactFormPage from "../pages/ContactFormPage/ContactFormPage";
|
||||||
import InstallQuiz from "../pages/InstallQuiz/InstallQuiz";
|
import InstallQuiz from "../pages/InstallQuiz/InstallQuiz";
|
||||||
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
|
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
|
||||||
@ -13,25 +12,33 @@ import Steptwo from "../pages/startPage/steptwo";
|
|||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
activeStep: QuizSetupStep;
|
activeStep: number;
|
||||||
quizType: string;
|
quizType: QuizType;
|
||||||
|
quizStartPageType: QuizStartpageType;
|
||||||
|
quizResults: QuizResultsType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SwitchStepPages({
|
export default function SwitchStepPages({
|
||||||
activeStep = 1,
|
activeStep = 1,
|
||||||
quizType,
|
quizType,
|
||||||
|
quizStartPageType,
|
||||||
|
quizResults,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (activeStep) {
|
switch (activeStep) {
|
||||||
case 1: return <StepOne />;
|
case 0: {
|
||||||
case 2: return <Steptwo />;
|
if (!quizType) return <StepOne />;
|
||||||
case 3: return <StartPageSettings />;
|
if (!quizStartPageType) return <Steptwo />;
|
||||||
case 4: return quizType === "form" ? <FormQuestionsPage /> : <QuestionsPage />;
|
return <StartPageSettings />;
|
||||||
case 5: return <Result />;
|
}
|
||||||
case 6: return <Setting />;
|
case 1: return quizType === "form" ? <FormQuestionsPage /> : <QuestionsPage />;
|
||||||
case 7: return <QuestionsMap />;
|
case 2: {
|
||||||
case 8: return <ContactFormPage />;
|
if (!quizResults) return <Result />;
|
||||||
case 9: return <InstallQuiz />;
|
return <Setting />;
|
||||||
case 10: return <>Реклама</>;
|
}
|
||||||
default: return notReachable(activeStep);
|
case 3: return <QuestionsMap />;
|
||||||
|
case 4: return <ContactFormPage />;
|
||||||
|
case 5: return <InstallQuiz />;
|
||||||
|
case 6: return <>Реклама</>;
|
||||||
|
default: throw new Error(`Invalid quiz setup step: ${activeStep}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
export class RequestQueue<T> {
|
export class RequestQueue<T = unknown> {
|
||||||
private pendingPromise = false;
|
private pendingPromise = false;
|
||||||
private items: Array<{
|
private items: Array<{
|
||||||
action: (prevPayload?: T | null) => Promise<T>;
|
action: () => Promise<T>;
|
||||||
resolve: (value: T) => void;
|
resolve: (value: T) => void;
|
||||||
reject: (reason?: any) => void;
|
reject: (reason?: any) => void;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
enqueue(action: (prevPayload?: T | null) => Promise<T>) {
|
enqueue(action: () => Promise<T>) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.items.length === 2) {
|
if (this.items.length === 2) {
|
||||||
this.items[1] = { action, resolve, reject };
|
this.items[1] = { action, resolve, reject };
|
||||||
@ -17,17 +17,15 @@ export class RequestQueue<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async dequeue(prevPayload?: T | null) {
|
async dequeue() {
|
||||||
if (this.pendingPromise) return;
|
if (this.pendingPromise) return;
|
||||||
|
|
||||||
const item = this.items.shift();
|
const item = this.items.shift();
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
let payload: T | null = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.pendingPromise = true;
|
this.pendingPromise = true;
|
||||||
payload = await item.action(prevPayload);
|
const payload = await item.action();
|
||||||
this.pendingPromise = false;
|
this.pendingPromise = false;
|
||||||
|
|
||||||
item.resolve(payload);
|
item.resolve(payload);
|
||||||
@ -35,7 +33,7 @@ export class RequestQueue<T> {
|
|||||||
this.pendingPromise = false;
|
this.pendingPromise = false;
|
||||||
item.reject(e);
|
item.reject(e);
|
||||||
} finally {
|
} finally {
|
||||||
this.dequeue(payload);
|
this.dequeue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user