feat: view
This commit is contained in:
parent
960ee026bd
commit
305b8707ce
@ -33,6 +33,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-cytoscapejs": "^2.0.0",
|
"react-cytoscapejs": "^2.0.0",
|
||||||
|
"react-datepicker": "^4.24.0",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@ -79,6 +80,7 @@
|
|||||||
"@emoji-mart/react": "^1.1.1",
|
"@emoji-mart/react": "^1.1.1",
|
||||||
"@types/react-beautiful-dnd": "^13.1.4",
|
"@types/react-beautiful-dnd": "^13.1.4",
|
||||||
"@types/react-cytoscapejs": "^1.2.4",
|
"@types/react-cytoscapejs": "^1.2.4",
|
||||||
|
"@types/react-datepicker": "^4.19.3",
|
||||||
"craco-alias": "^3.0.1",
|
"craco-alias": "^3.0.1",
|
||||||
"cypress": "^13.4.0"
|
"cypress": "^13.4.0"
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import dayjs from "dayjs";
|
|||||||
import "dayjs/locale/ru";
|
import "dayjs/locale/ru";
|
||||||
import SigninDialog from "./pages/auth/Signin";
|
import SigninDialog from "./pages/auth/Signin";
|
||||||
import SignupDialog from "./pages/auth/Signup";
|
import SignupDialog from "./pages/auth/Signup";
|
||||||
|
import { ViewPage } from "./pages/ViewPublicationPage";
|
||||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
|
import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
|
||||||
@ -61,6 +62,7 @@ export default function App() {
|
|||||||
<Route path="/" element={<Landing />} />
|
<Route path="/" element={<Landing />} />
|
||||||
<Route path="/signin" element={<SigninDialog />} />
|
<Route path="/signin" element={<SigninDialog />} />
|
||||||
<Route path="/signup" element={<SignupDialog />} />
|
<Route path="/signup" element={<SignupDialog />} />
|
||||||
|
<Route path="view/:quizId" element={<ViewPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</>
|
</>
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
import type { SxProps } from "@mui/material";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
color: string;
|
color: string;
|
||||||
width?: string;
|
width?: number;
|
||||||
|
sx?: SxProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StarIconMini({ color, width = "30px" }: Props) {
|
export default function StarIconMini({ color, width = 30, sx }: Props) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "30px",
|
height: "30px",
|
||||||
width: width,
|
width: width + "px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
...sx,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<svg width="28" height="27" viewBox="0 0 28 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="28" height="27" viewBox="0 0 28 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@ -17,15 +17,8 @@ export const QUIZ_QUESTION_BASE: Omit<QuizQuestionBase, "id" | "backendId"> = {
|
|||||||
video: "",
|
video: "",
|
||||||
},
|
},
|
||||||
rule: {
|
rule: {
|
||||||
or: true,
|
default: "",
|
||||||
show: true,
|
main: [],
|
||||||
title: "",
|
|
||||||
reqs: [
|
|
||||||
{
|
|
||||||
id: "",
|
|
||||||
vars: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
back: "",
|
back: "",
|
||||||
originalBack: "",
|
originalBack: "",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionDate extends QuizQuestionBase {
|
export interface QuizQuestionDate extends QuizQuestionBase {
|
||||||
@ -16,7 +16,7 @@ export interface QuizQuestionDate extends QuizQuestionBase {
|
|||||||
dateRange: boolean;
|
dateRange: boolean;
|
||||||
time: boolean;
|
time: boolean;
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
@ -2,7 +2,7 @@ import type {
|
|||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionVariant,
|
QuestionVariant,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionEmoji extends QuizQuestionBase {
|
export interface QuizQuestionEmoji extends QuizQuestionBase {
|
||||||
@ -20,7 +20,7 @@ export interface QuizQuestionEmoji extends QuizQuestionBase {
|
|||||||
required: boolean;
|
required: boolean;
|
||||||
variants: QuestionVariant[];
|
variants: QuestionVariant[];
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export const UPLOAD_FILE_TYPES_MAP = {
|
export const UPLOAD_FILE_TYPES_MAP = {
|
||||||
@ -27,7 +27,7 @@ export interface QuizQuestionFile extends QuizQuestionBase {
|
|||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
type: UploadFileType;
|
type: UploadFileType;
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import type {
|
import type {
|
||||||
QuestionBranchingRule,
|
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionVariant,
|
QuestionVariant,
|
||||||
QuizQuestionBase
|
QuizQuestionBase,
|
||||||
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionImages extends QuizQuestionBase {
|
export interface QuizQuestionImages extends QuizQuestionBase {
|
||||||
@ -27,7 +27,7 @@ export interface QuizQuestionImages extends QuizQuestionBase {
|
|||||||
/** Варианты (картинки) */
|
/** Варианты (картинки) */
|
||||||
variants: QuestionVariant[];
|
variants: QuestionVariant[];
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionNumber extends QuizQuestionBase {
|
export interface QuizQuestionNumber extends QuizQuestionBase {
|
||||||
@ -25,7 +25,7 @@ export interface QuizQuestionNumber extends QuizQuestionBase {
|
|||||||
/** Чекбокс "Выбор диапазона (два ползунка)" */
|
/** Чекбокс "Выбор диапазона (два ползунка)" */
|
||||||
chooseRange: boolean;
|
chooseRange: boolean;
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionPage extends QuizQuestionBase {
|
export interface QuizQuestionPage extends QuizQuestionBase {
|
||||||
@ -16,7 +16,7 @@ export interface QuizQuestionPage extends QuizQuestionBase {
|
|||||||
originalPicture: string;
|
originalPicture: string;
|
||||||
video: string;
|
video: string;
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionRating extends QuizQuestionBase {
|
export interface QuizQuestionRating extends QuizQuestionBase {
|
||||||
@ -18,7 +18,7 @@ export interface QuizQuestionRating extends QuizQuestionBase {
|
|||||||
/** Форма иконки */
|
/** Форма иконки */
|
||||||
form: string;
|
form: string;
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
@ -2,7 +2,7 @@ import type {
|
|||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionVariant,
|
QuestionVariant,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionSelect extends QuizQuestionBase {
|
export interface QuizQuestionSelect extends QuizQuestionBase {
|
||||||
@ -19,7 +19,7 @@ export interface QuizQuestionSelect extends QuizQuestionBase {
|
|||||||
/** Поле "Текст в выпадающем списке" */
|
/** Поле "Текст в выпадающем списке" */
|
||||||
default: string;
|
default: string;
|
||||||
variants: QuestionVariant[];
|
variants: QuestionVariant[];
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
|
@ -12,17 +12,24 @@ import type { QuizQuestionVariant } from "./variant";
|
|||||||
import type { QuizQuestionVarImg } from "./varimg";
|
import type { QuizQuestionVarImg } from "./varimg";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
export type Rule = {
|
||||||
|
/* question id */
|
||||||
|
question: string;
|
||||||
|
/* Ответы на вопросы. Для вариантов выбора - конкретные айдишники ответов, для полей ввода текста - текст по полному совпадению, для ввода файла - просто факт того что файл ввели, т.е. boolean */
|
||||||
|
answers: (number | string | boolean)[];
|
||||||
|
};
|
||||||
|
|
||||||
export interface QuestionBranchingRule {
|
export type PreviewRuleInfo = {
|
||||||
/** Радиокнопка "Все условия обязательны" */
|
/* Id следующего вопроса */
|
||||||
|
next: string;
|
||||||
|
/* Радиокнопка "Все условия обязательны" */
|
||||||
or: boolean;
|
or: boolean;
|
||||||
show: boolean;
|
rules: Rule[];
|
||||||
title: string;
|
};
|
||||||
reqs: {
|
|
||||||
id: string;
|
export interface PreviewRule {
|
||||||
/** Список выбранных вариантов */
|
default: string;
|
||||||
vars: number[];
|
main: PreviewRuleInfo[];
|
||||||
}[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QuestionHint {
|
export interface QuestionHint {
|
||||||
@ -60,7 +67,7 @@ export interface QuizQuestionBase {
|
|||||||
deleteTimeoutId: number;
|
deleteTimeoutId: number;
|
||||||
content: {
|
content: {
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
@ -91,11 +98,13 @@ export type AnyTypedQuizQuestion =
|
|||||||
| QuizQuestionRating;
|
| QuizQuestionRating;
|
||||||
|
|
||||||
type FilterQuestionsWithVariants<T> = T extends {
|
type FilterQuestionsWithVariants<T> = T extends {
|
||||||
content: { variants: QuestionVariant[]; };
|
content: { variants: QuestionVariant[] };
|
||||||
} ? T : never;
|
}
|
||||||
|
? T
|
||||||
export type QuizQuestionsWithVariants = FilterQuestionsWithVariants<AnyTypedQuizQuestion>;
|
: never;
|
||||||
|
|
||||||
|
export type QuizQuestionsWithVariants =
|
||||||
|
FilterQuestionsWithVariants<AnyTypedQuizQuestion>;
|
||||||
|
|
||||||
export const createQuestionVariant: () => QuestionVariant = () => ({
|
export const createQuestionVariant: () => QuestionVariant = () => ({
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionText extends QuizQuestionBase {
|
export interface QuizQuestionText extends QuizQuestionBase {
|
||||||
@ -18,7 +18,7 @@ export interface QuizQuestionText extends QuizQuestionBase {
|
|||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
answerType: "single" | "multi";
|
answerType: "single" | "multi";
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
onlyNumbers: boolean;
|
onlyNumbers: boolean;
|
||||||
|
@ -2,7 +2,7 @@ import type {
|
|||||||
QuizQuestionBase,
|
QuizQuestionBase,
|
||||||
QuestionVariant,
|
QuestionVariant,
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionBranchingRule,
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionVariant extends QuizQuestionBase {
|
export interface QuizQuestionVariant extends QuizQuestionBase {
|
||||||
@ -23,7 +23,7 @@ export interface QuizQuestionVariant extends QuizQuestionBase {
|
|||||||
/** Варианты ответов */
|
/** Варианты ответов */
|
||||||
variants: QuestionVariant[];
|
variants: QuestionVariant[];
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import type {
|
import type {
|
||||||
QuestionBranchingRule,
|
|
||||||
QuestionHint,
|
QuestionHint,
|
||||||
QuestionVariant,
|
QuestionVariant,
|
||||||
QuizQuestionBase
|
QuizQuestionBase,
|
||||||
|
PreviewRule,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
export interface QuizQuestionVarImg extends QuizQuestionBase {
|
export interface QuizQuestionVarImg extends QuizQuestionBase {
|
||||||
@ -18,7 +18,7 @@ export interface QuizQuestionVarImg extends QuizQuestionBase {
|
|||||||
required: boolean;
|
required: boolean;
|
||||||
variants: QuestionVariant[];
|
variants: QuestionVariant[];
|
||||||
hint: QuestionHint;
|
hint: QuestionHint;
|
||||||
rule: QuestionBranchingRule;
|
rule: PreviewRule;
|
||||||
back: string;
|
back: string;
|
||||||
originalBack: string;
|
originalBack: string;
|
||||||
autofill: boolean;
|
autofill: boolean;
|
||||||
|
@ -73,7 +73,7 @@ export default function RatingOptions({ question }: Props) {
|
|||||||
const buttonRatingForm: ButtonRatingFrom[] = [
|
const buttonRatingForm: ButtonRatingFrom[] = [
|
||||||
{
|
{
|
||||||
name: "star",
|
name: "star",
|
||||||
icon: <StarIconMini width={"50px"} color={theme.palette.grey2.main} />,
|
icon: <StarIconMini width={50} color={theme.palette.grey2.main} />,
|
||||||
},
|
},
|
||||||
{ name: "trophie", icon: <TropfyIcon color={theme.palette.grey2.main} /> },
|
{ name: "trophie", icon: <TropfyIcon color={theme.palette.grey2.main} /> },
|
||||||
{ name: "flag", icon: <FlagIcon color={theme.palette.grey2.main} /> },
|
{ name: "flag", icon: <FlagIcon color={theme.palette.grey2.main} /> },
|
||||||
|
@ -71,7 +71,7 @@ export default function BranchingQuestions({
|
|||||||
p: 0,
|
p: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
{/* <Box
|
||||||
sx={{
|
sx={{
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
background: "#F2F3F7",
|
background: "#F2F3F7",
|
||||||
@ -330,7 +330,7 @@ export default function BranchingQuestions({
|
|||||||
Готово
|
Готово
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box> */}
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
127
src/pages/ViewPublicationPage/Footer.tsx
Normal file
127
src/pages/ViewPublicationPage/Footer.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { Box, Typography, Button, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import { useQuizViewStore } from "@root/quizView";
|
||||||
|
|
||||||
|
import type { QuizQuestionBase } from "../../model/questionTypes/shared";
|
||||||
|
|
||||||
|
type FooterProps = {
|
||||||
|
stepNumber: number;
|
||||||
|
setStepNumber: (step: number) => void;
|
||||||
|
questions: QuizQuestionBase[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Footer = ({
|
||||||
|
stepNumber,
|
||||||
|
setStepNumber,
|
||||||
|
questions,
|
||||||
|
}: FooterProps) => {
|
||||||
|
const [disabledQuestionsId, setDisabledQuestionsId] = useState<Set<string>>(
|
||||||
|
new Set()
|
||||||
|
);
|
||||||
|
const { answers } = useQuizViewStore();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
clearDisabledQuestions();
|
||||||
|
|
||||||
|
const nextStepId = questions[stepNumber + 1].id;
|
||||||
|
|
||||||
|
const disabledIds = [] as string[];
|
||||||
|
|
||||||
|
const newDisabledIds = new Set([...disabledQuestionsId, ...disabledIds]);
|
||||||
|
setDisabledQuestionsId(newDisabledIds);
|
||||||
|
}, [answers]);
|
||||||
|
|
||||||
|
const clearDisabledQuestions = () => {
|
||||||
|
const cleanDisabledQuestions = new Set<string>();
|
||||||
|
|
||||||
|
answers.forEach(({ step, answer }) => {
|
||||||
|
questions[step].content.rule.main.forEach(({ next, rules }) => {
|
||||||
|
rules.forEach(({ answers }) => {
|
||||||
|
if (answer !== answers[0]) {
|
||||||
|
cleanDisabledQuestions.add(next);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setDisabledQuestionsId(cleanDisabledQuestions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const followPreviousStep = () => {
|
||||||
|
setStepNumber(stepNumber - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const followNextStep = () => {
|
||||||
|
setStepNumber(stepNumber + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: "15px 0",
|
||||||
|
borderTop: `1px solid ${theme.palette.grey[400]}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "1000px",
|
||||||
|
padding: "0 10px",
|
||||||
|
margin: "0 auto",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "10px",
|
||||||
|
marginRight: "auto",
|
||||||
|
color: theme.palette.grey1.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography>Шаг</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
fontWeight: "bold",
|
||||||
|
borderRadius: "50%",
|
||||||
|
width: "30px",
|
||||||
|
height: "30px",
|
||||||
|
color: "#FFF",
|
||||||
|
background: theme.palette.brightPurple.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stepNumber}
|
||||||
|
</Typography>
|
||||||
|
<Typography>Из</Typography>
|
||||||
|
<Typography sx={{ fontWeight: "bold" }}>
|
||||||
|
{questions.length}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={stepNumber <= 1}
|
||||||
|
sx={{ fontSize: "16px", padding: "10px 15px" }}
|
||||||
|
onClick={followPreviousStep}
|
||||||
|
>
|
||||||
|
← Назад
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={questions.length <= stepNumber}
|
||||||
|
sx={{ fontSize: "16px", padding: "10px 15px" }}
|
||||||
|
onClick={followNextStep}
|
||||||
|
>
|
||||||
|
Далее →
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
72
src/pages/ViewPublicationPage/Question.tsx
Normal file
72
src/pages/ViewPublicationPage/Question.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
import { Variant } from "./questions/Variant";
|
||||||
|
import { Images } from "./questions/Images";
|
||||||
|
import { Varimg } from "./questions/Varimg";
|
||||||
|
import { Emoji } from "./questions/Emoji";
|
||||||
|
import { Text } from "./questions/Text";
|
||||||
|
import { Select } from "./questions/Select";
|
||||||
|
import { Date } from "./questions/Date";
|
||||||
|
import { Number } from "./questions/Number";
|
||||||
|
import { File } from "./questions/File";
|
||||||
|
import { Page } from "./questions/Page";
|
||||||
|
import { Rating } from "./questions/Rating";
|
||||||
|
import { Footer } from "./Footer";
|
||||||
|
|
||||||
|
import type { FC } from "react";
|
||||||
|
import type { QuestionType } from "../../model/question/question";
|
||||||
|
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||||
|
|
||||||
|
type QuestionProps = {
|
||||||
|
stepNumber: number;
|
||||||
|
setStepNumber: (step: number) => void;
|
||||||
|
questions: AnyTypedQuizQuestion[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const QUESTIONS_MAP: Record<
|
||||||
|
Exclude<QuestionType, "nonselected">,
|
||||||
|
FC<{ stepNumber: number; question: any }>
|
||||||
|
> = {
|
||||||
|
variant: Variant,
|
||||||
|
images: Images,
|
||||||
|
varimg: Varimg,
|
||||||
|
emoji: Emoji,
|
||||||
|
text: Text,
|
||||||
|
select: Select,
|
||||||
|
date: Date,
|
||||||
|
number: Number,
|
||||||
|
file: File,
|
||||||
|
page: Page,
|
||||||
|
rating: Rating,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Question = ({
|
||||||
|
stepNumber,
|
||||||
|
setStepNumber,
|
||||||
|
questions,
|
||||||
|
}: QuestionProps) => {
|
||||||
|
const question = questions[stepNumber - 1] as AnyTypedQuizQuestion;
|
||||||
|
const QuestionComponent =
|
||||||
|
QUESTIONS_MAP[question.type as Exclude<QuestionType, "nonselected">];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: "calc(100vh - 75px)",
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "1000px",
|
||||||
|
padding: "20px 10px 0",
|
||||||
|
margin: "0 auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<QuestionComponent question={question} stepNumber={stepNumber} />
|
||||||
|
</Box>
|
||||||
|
<Footer
|
||||||
|
stepNumber={stepNumber}
|
||||||
|
setStepNumber={setStepNumber}
|
||||||
|
questions={questions}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
159
src/pages/ViewPublicationPage/StartPageViewPublication.tsx
Normal file
159
src/pages/ViewPublicationPage/StartPageViewPublication.tsx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
} from "@mui/material";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { isAxiosError } from "axios";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { devlog } from "@frontend/kitui";
|
||||||
|
|
||||||
|
import { quizApi } from "@api/quiz";
|
||||||
|
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
import { setQuizes } from "@root/quizes/actions";
|
||||||
|
|
||||||
|
type StartPageViewPublicationProps = {
|
||||||
|
setStepNumber: (step: number) => void;
|
||||||
|
showNextButton: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StartPageViewPublication = ({
|
||||||
|
setStepNumber,
|
||||||
|
showNextButton,
|
||||||
|
}: StartPageViewPublicationProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isTablet = useMediaQuery(theme.breakpoints.down(630));
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
const isMediaFileExist =
|
||||||
|
quiz?.config.startpage.background.desktop ||
|
||||||
|
quiz?.config.startpage.background.video;
|
||||||
|
|
||||||
|
useSWR("quizes", () => quizApi.getList(), {
|
||||||
|
onSuccess: setQuizes,
|
||||||
|
onError: (error: unknown) => {
|
||||||
|
const message = isAxiosError<string>(error)
|
||||||
|
? error.response?.data ?? ""
|
||||||
|
: "";
|
||||||
|
|
||||||
|
devlog("Error getting quiz list", error);
|
||||||
|
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection:
|
||||||
|
quiz?.config.startpage.position === "left" ? "row" : "row-reverse",
|
||||||
|
flexGrow: 1,
|
||||||
|
"&::-webkit-scrollbar": { width: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: isMediaFileExist && !isTablet ? "40%" : "100%",
|
||||||
|
padding: "16px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: isMediaFileExist && !isTablet ? "flex-start" : "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{quiz?.config.startpage.background.mobile && (
|
||||||
|
<img
|
||||||
|
src={quiz.config.startpage.background.mobile}
|
||||||
|
style={{
|
||||||
|
height: "50px",
|
||||||
|
maxWidth: "100px",
|
||||||
|
objectFit: "cover",
|
||||||
|
}}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Typography sx={{ fontSize: "18px" }}>
|
||||||
|
{quiz?.config.info.orgname}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{ fontWeight: "bold", fontSize: "20px" }}>
|
||||||
|
{quiz?.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ fontSize: "16px" }}>
|
||||||
|
{quiz?.config.startpage.description}
|
||||||
|
</Typography>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
fontSize: "16px",
|
||||||
|
padding: "10px 15px",
|
||||||
|
}}
|
||||||
|
onClick={() => setStepNumber(1)}
|
||||||
|
>
|
||||||
|
{quiz?.config.startpage.button
|
||||||
|
? quiz?.config.startpage.button
|
||||||
|
: "Пройти тест"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}
|
||||||
|
>
|
||||||
|
{quiz?.config.info.phonenumber}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ fontSize: "12px" }}>
|
||||||
|
{quiz?.config.info.law}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{!isTablet && isMediaFileExist && (
|
||||||
|
<Box sx={{ width: "60%" }}>
|
||||||
|
{quiz?.config.startpage.background.mobile && (
|
||||||
|
<img
|
||||||
|
src={quiz.config.startpage.background.mobile}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{quiz.config.startpage.background.type === "video" &&
|
||||||
|
quiz.config.startpage.background.video && (
|
||||||
|
<video
|
||||||
|
src={quiz.config.startpage.background.video}
|
||||||
|
controls
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
42
src/pages/ViewPublicationPage/index.tsx
Normal file
42
src/pages/ViewPublicationPage/index.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { useLayoutEffect, useState } from "react";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
import { StartPageViewPublication } from "./StartPageViewPublication";
|
||||||
|
import { Question } from "./Question";
|
||||||
|
import { useQuestions } from "@root/questions/hooks";
|
||||||
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
|
|
||||||
|
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||||
|
|
||||||
|
export const ViewPage = () => {
|
||||||
|
const [stepNumber, setStepNumber] = useState<number>(0);
|
||||||
|
const quiz = useCurrentQuiz();
|
||||||
|
const { questions } = useQuestions();
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (stepNumber === 0 && quiz?.config.noStartPage) {
|
||||||
|
setStepNumber(1);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const filteredQuestions = questions.filter(
|
||||||
|
({ type }) => type
|
||||||
|
) as AnyTypedQuizQuestion[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{stepNumber ? (
|
||||||
|
<Question
|
||||||
|
stepNumber={stepNumber}
|
||||||
|
setStepNumber={setStepNumber}
|
||||||
|
questions={filteredQuestions}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<StartPageViewPublication
|
||||||
|
setStepNumber={setStepNumber}
|
||||||
|
showNextButton={!!filteredQuestions.length}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
42
src/pages/ViewPublicationPage/questions/Date.tsx
Normal file
42
src/pages/ViewPublicationPage/questions/Date.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import DatePicker from "react-datepicker";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
|
||||||
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
|
|
||||||
|
import type { QuizQuestionDate } from "../../../model/questionTypes/date";
|
||||||
|
|
||||||
|
type DateProps = {
|
||||||
|
question: QuizQuestionDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Date = ({ question }: DateProps) => {
|
||||||
|
const [startDate, setStartDate] = useState<Date | null>(new window.Date());
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
selected={startDate}
|
||||||
|
onChange={(date) => setStartDate(date)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
78
src/pages/ViewPublicationPage/questions/Emoji.tsx
Normal file
78
src/pages/ViewPublicationPage/questions/Emoji.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
RadioGroup,
|
||||||
|
FormControlLabel,
|
||||||
|
Radio,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
|
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
||||||
|
|
||||||
|
type EmojiProps = {
|
||||||
|
question: QuizQuestionEmoji;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Emoji = ({ question }: EmojiProps) => {
|
||||||
|
const [valueIndex, setValueIndex] = useState<number>(0);
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const theme = useTheme();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<RadioGroup
|
||||||
|
name={question.id}
|
||||||
|
value={valueIndex}
|
||||||
|
onChange={({ target }) => setValueIndex(Number(target.value))}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||||
|
{question.content.variants.map(
|
||||||
|
({ id, answer, extendedText }, index) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={id}
|
||||||
|
sx={{
|
||||||
|
marginBottom: "15px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
padding: "15px",
|
||||||
|
color: theme.palette.grey2.main,
|
||||||
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
value={index}
|
||||||
|
control={
|
||||||
|
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Box sx={{ display: "flex", gap: "10px" }}>
|
||||||
|
<Typography>{extendedText}</Typography>
|
||||||
|
<Typography>{answer}</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
64
src/pages/ViewPublicationPage/questions/File.tsx
Normal file
64
src/pages/ViewPublicationPage/questions/File.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { Box, Typography, ButtonBase } from "@mui/material";
|
||||||
|
|
||||||
|
import UploadBox from "@ui_kit/UploadBox";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
import { UPLOAD_FILE_TYPES_MAP } from "@ui_kit/QuizPreview/QuizPreviewQuestionTypes/File";
|
||||||
|
|
||||||
|
import UploadIcon from "@icons/UploadIcon";
|
||||||
|
|
||||||
|
import type { ChangeEvent } from "react";
|
||||||
|
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
||||||
|
|
||||||
|
type FileProps = {
|
||||||
|
question: QuizQuestionFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const File = ({ question }: FileProps) => {
|
||||||
|
const [fileName, setFileName] = useState<string>("");
|
||||||
|
const [file, setFile] = useState<string>();
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
const uploadFile = ({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = target.files?.[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
setFileName(file.name);
|
||||||
|
setFile(URL.createObjectURL(file));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
|
||||||
|
<input
|
||||||
|
onChange={uploadFile}
|
||||||
|
hidden
|
||||||
|
accept={UPLOAD_FILE_TYPES_MAP[question.content.type]}
|
||||||
|
multiple
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<UploadBox icon={<UploadIcon />} text="5 MB максимум" />
|
||||||
|
</ButtonBase>
|
||||||
|
{fileName && (
|
||||||
|
<Typography sx={{ marginTop: "15px" }}>{fileName}</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
110
src/pages/ViewPublicationPage/questions/Images.tsx
Normal file
110
src/pages/ViewPublicationPage/questions/Images.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
RadioGroup,
|
||||||
|
FormControlLabel,
|
||||||
|
Radio,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
|
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
||||||
|
|
||||||
|
type ImagesProps = {
|
||||||
|
question: QuizQuestionImages;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Images = ({ question }: ImagesProps) => {
|
||||||
|
const [valueIndex, setValueIndex] = useState<number>(0);
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const theme = useTheme();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(500));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<RadioGroup
|
||||||
|
name={question.id}
|
||||||
|
value={valueIndex}
|
||||||
|
onChange={({ target }) => setValueIndex(Number(target.value))}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "grid",
|
||||||
|
gap: "15px",
|
||||||
|
gridTemplateColumns: isTablet
|
||||||
|
? isMobile
|
||||||
|
? "repeat(1, 1fr)"
|
||||||
|
: "repeat(2, 1fr)"
|
||||||
|
: "repeat(3, 1fr)",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{question.content.variants.map(
|
||||||
|
({ id, answer, extendedText }, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
borderRadius: "5px",
|
||||||
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{ display: "flex", alignItems: "center", gap: "10px" }}
|
||||||
|
>
|
||||||
|
<Box sx={{ width: "100%", height: "300px" }}>
|
||||||
|
{extendedText && (
|
||||||
|
<img
|
||||||
|
src={extendedText}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<FormControlLabel
|
||||||
|
key={id}
|
||||||
|
sx={{
|
||||||
|
display: "block",
|
||||||
|
textAlign: "center",
|
||||||
|
color: theme.palette.grey2.main,
|
||||||
|
marginTop: "10px",
|
||||||
|
}}
|
||||||
|
value={index}
|
||||||
|
control={
|
||||||
|
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||||
|
}
|
||||||
|
label={answer}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
61
src/pages/ViewPublicationPage/questions/Number.tsx
Normal file
61
src/pages/ViewPublicationPage/questions/Number.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { Box, Typography, Slider, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
|
||||||
|
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||||
|
|
||||||
|
type NumberProps = {
|
||||||
|
question: QuizQuestionNumber;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Number = ({ question }: NumberProps) => {
|
||||||
|
const [value, setValue] = useState<number>(0);
|
||||||
|
const quizId = window.Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const theme = useTheme();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomTextField
|
||||||
|
placeholder="0"
|
||||||
|
value={String(value)}
|
||||||
|
sx={{
|
||||||
|
maxWidth: "80px",
|
||||||
|
"& .MuiInputBase-input": {
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Slider
|
||||||
|
value={value}
|
||||||
|
min={window.Number(question.content.range.split("—")[0])}
|
||||||
|
max={window.Number(question.content.range.split("—")[1])}
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.brightPurple.main,
|
||||||
|
padding: "0",
|
||||||
|
marginTop: "25px",
|
||||||
|
}}
|
||||||
|
onChange={(_, value) => {
|
||||||
|
setValue(value as number);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
57
src/pages/ViewPublicationPage/questions/Page.tsx
Normal file
57
src/pages/ViewPublicationPage/questions/Page.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||||
|
|
||||||
|
type PageProps = {
|
||||||
|
question: QuizQuestionPage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Page = ({ question }: PageProps) => {
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{question.content.picture && (
|
||||||
|
<img
|
||||||
|
src={question.content.picture}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
maxHeight: "80vh",
|
||||||
|
objectFit: "contain",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{question.content.video && (
|
||||||
|
<video
|
||||||
|
src={question.content.video}
|
||||||
|
controls
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
maxHeight: "80vh",
|
||||||
|
objectFit: "contain",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
70
src/pages/ViewPublicationPage/questions/Rating.tsx
Normal file
70
src/pages/ViewPublicationPage/questions/Rating.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Rating as RatingComponent,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
|
||||||
|
import StarIconMini from "@icons/questionsPage/StarIconMini";
|
||||||
|
|
||||||
|
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
||||||
|
|
||||||
|
type RatingProps = {
|
||||||
|
question: QuizQuestionRating;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Rating = ({ question }: RatingProps) => {
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RatingComponent
|
||||||
|
sx={{ height: "50px" }}
|
||||||
|
max={question.content.steps}
|
||||||
|
icon={
|
||||||
|
<StarIconMini
|
||||||
|
color={theme.palette.brightPurple.main}
|
||||||
|
width={50}
|
||||||
|
sx={{ transform: "scale(0.8)" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
emptyIcon={
|
||||||
|
<StarIconMini
|
||||||
|
color={theme.palette.grey2.main}
|
||||||
|
width={50}
|
||||||
|
sx={{ transform: "scale(0.8)" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
maxWidth: `${question.content.steps * 50}px`,
|
||||||
|
color: theme.palette.grey2.main,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography>{question.content.ratingNegativeDescription}</Typography>
|
||||||
|
<Typography>{question.content.ratingPositiveDescription}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
40
src/pages/ViewPublicationPage/questions/Select.tsx
Normal file
40
src/pages/ViewPublicationPage/questions/Select.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import { Select as SelectComponent } from "../../../pages/Questions/Select";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
|
||||||
|
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
||||||
|
|
||||||
|
type SelectProps = {
|
||||||
|
question: QuizQuestionSelect;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Select = ({ question }: SelectProps) => {
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectComponent
|
||||||
|
items={question.content.variants.map(({ answer }) => answer)}
|
||||||
|
onChange={(action, num) => {
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
36
src/pages/ViewPublicationPage/questions/Text.tsx
Normal file
36
src/pages/ViewPublicationPage/questions/Text.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
|
||||||
|
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||||
|
|
||||||
|
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||||
|
|
||||||
|
type TextProps = {
|
||||||
|
question: QuizQuestionText;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Text = ({ question }: TextProps) => {
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomTextField placeholder={question.content.placeholder} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
91
src/pages/ViewPublicationPage/questions/Variant.tsx
Normal file
91
src/pages/ViewPublicationPage/questions/Variant.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
RadioGroup,
|
||||||
|
FormControlLabel,
|
||||||
|
Radio,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
||||||
|
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
|
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
|
||||||
|
|
||||||
|
type VariantProps = {
|
||||||
|
stepNumber: number;
|
||||||
|
question: QuizQuestionVariant;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Variant = ({ stepNumber, question }: VariantProps) => {
|
||||||
|
const { answers } = useQuizViewStore();
|
||||||
|
const theme = useTheme();
|
||||||
|
const answerIndex = answers.findIndex(({ step }) => step === stepNumber);
|
||||||
|
const answer = answers[answerIndex]?.answer;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!answer) {
|
||||||
|
updateAnswer(stepNumber, question.content.variants[0].id);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<RadioGroup
|
||||||
|
name={question.id}
|
||||||
|
value={question.content.variants.findIndex(({ id }) => answer === id)}
|
||||||
|
onChange={({ target }) =>
|
||||||
|
updateAnswer(
|
||||||
|
stepNumber,
|
||||||
|
question.content.variants[Number(target.value)].id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexBasis: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||||
|
{question.content.variants.map(({ id, answer }, index) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={id}
|
||||||
|
sx={{
|
||||||
|
marginBottom: "15px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
padding: "15px",
|
||||||
|
color: theme.palette.grey2.main,
|
||||||
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
value={index}
|
||||||
|
control={
|
||||||
|
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||||
|
}
|
||||||
|
label={answer}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup>
|
||||||
|
{question.content.back && (
|
||||||
|
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
|
||||||
|
<img
|
||||||
|
src={question.content.back}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
87
src/pages/ViewPublicationPage/questions/Varimg.tsx
Normal file
87
src/pages/ViewPublicationPage/questions/Varimg.tsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
RadioGroup,
|
||||||
|
FormControlLabel,
|
||||||
|
Radio,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { questionStore } from "@root/questions";
|
||||||
|
|
||||||
|
import RadioCheck from "@ui_kit/RadioCheck";
|
||||||
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
|
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||||
|
|
||||||
|
type VarimgProps = {
|
||||||
|
question: QuizQuestionVarImg;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Varimg = ({ question }: VarimgProps) => {
|
||||||
|
const [valueIndex, setValueIndex] = useState<number>(-1);
|
||||||
|
const quizId = Number(useParams().quizId);
|
||||||
|
const { listQuestions } = questionStore();
|
||||||
|
const theme = useTheme();
|
||||||
|
const totalIndex = listQuestions[quizId].findIndex(
|
||||||
|
({ id }) => question.id === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5">{question.title}</Typography>
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<RadioGroup
|
||||||
|
name={question.id}
|
||||||
|
value={valueIndex}
|
||||||
|
onChange={({ target }) => setValueIndex(Number(target.value))}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexBasis: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||||
|
{question.content.variants.map(({ id, answer }, index) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={id}
|
||||||
|
sx={{
|
||||||
|
marginBottom: "15px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
padding: "15px",
|
||||||
|
color: theme.palette.grey2.main,
|
||||||
|
border: `1px solid ${theme.palette.grey2.main}`,
|
||||||
|
display: "flex",
|
||||||
|
}}
|
||||||
|
value={index}
|
||||||
|
control={
|
||||||
|
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||||
|
}
|
||||||
|
label={answer}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</RadioGroup>
|
||||||
|
{(question.content.variants[valueIndex]?.extendedText ||
|
||||||
|
question.content.back) && (
|
||||||
|
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
valueIndex >= 0
|
||||||
|
? question.content.variants[valueIndex].extendedText
|
||||||
|
: question.content.back
|
||||||
|
}
|
||||||
|
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
35
src/stores/quizView.ts
Normal file
35
src/stores/quizView.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { devtools } from "zustand/middleware";
|
||||||
|
|
||||||
|
type Answer = {
|
||||||
|
step: number;
|
||||||
|
answer: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface QuizViewStore {
|
||||||
|
answers: Answer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useQuizViewStore = create<QuizViewStore>()(
|
||||||
|
devtools(
|
||||||
|
(set, get) => ({
|
||||||
|
answers: [],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "quizView",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateAnswer = (step: number, answer: string) => {
|
||||||
|
const answers = [...useQuizViewStore.getState().answers];
|
||||||
|
const answerIndex = answers.findIndex((answer) => step === answer.step);
|
||||||
|
|
||||||
|
if (answerIndex < 0) {
|
||||||
|
answers.push({ step, answer });
|
||||||
|
} else {
|
||||||
|
answers[answerIndex] = { step, answer };
|
||||||
|
}
|
||||||
|
|
||||||
|
useQuizViewStore.setState({ answers });
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.extend.json",
|
"extends": "./tsconfig.extend.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es2015",
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
"dom.iterable",
|
"dom.iterable",
|
||||||
|
70
yarn.lock
70
yarn.lock
@ -1041,6 +1041,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.14.0"
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.21.0":
|
||||||
|
version "7.23.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.5.tgz#11edb98f8aeec529b82b211028177679144242db"
|
||||||
|
integrity sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3":
|
"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3":
|
||||||
version "7.20.7"
|
version "7.20.7"
|
||||||
resolved "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz"
|
resolved "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz"
|
||||||
@ -1883,7 +1890,7 @@
|
|||||||
schema-utils "^3.0.0"
|
schema-utils "^3.0.0"
|
||||||
source-map "^0.7.3"
|
source-map "^0.7.3"
|
||||||
|
|
||||||
"@popperjs/core@^2.11.6", "@popperjs/core@^2.11.8":
|
"@popperjs/core@^2.11.6", "@popperjs/core@^2.11.8", "@popperjs/core@^2.9.2":
|
||||||
version "2.11.8"
|
version "2.11.8"
|
||||||
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz"
|
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz"
|
||||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||||
@ -2419,6 +2426,16 @@
|
|||||||
"@types/cytoscape" "*"
|
"@types/cytoscape" "*"
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-datepicker@^4.19.3":
|
||||||
|
version "4.19.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.19.3.tgz#0a58e42d820adf12337617bd72289766643775db"
|
||||||
|
integrity sha512-85F9eKWu9fGiD9r4KVVMPYAdkJJswR3Wci9PvqplmB6T+D+VbUqPeKtifg96NZ4nEhufjehW+SX4JLrEWVplWw==
|
||||||
|
dependencies:
|
||||||
|
"@popperjs/core" "^2.9.2"
|
||||||
|
"@types/react" "*"
|
||||||
|
date-fns "^2.0.1"
|
||||||
|
react-popper "^2.2.5"
|
||||||
|
|
||||||
"@types/react-dnd@^3.0.2":
|
"@types/react-dnd@^3.0.2":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.npmjs.org/@types/react-dnd/-/react-dnd-3.0.2.tgz"
|
resolved "https://registry.npmjs.org/@types/react-dnd/-/react-dnd-3.0.2.tgz"
|
||||||
@ -3607,6 +3624,11 @@ cjs-module-lexer@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz"
|
resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz"
|
||||||
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
|
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
|
||||||
|
|
||||||
|
classnames@^2.2.6:
|
||||||
|
version "2.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
|
||||||
|
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||||
|
|
||||||
clean-css@^5.2.2:
|
clean-css@^5.2.2:
|
||||||
version "5.3.1"
|
version "5.3.1"
|
||||||
resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz"
|
resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz"
|
||||||
@ -4199,6 +4221,13 @@ data-urls@^2.0.0:
|
|||||||
whatwg-mimetype "^2.3.0"
|
whatwg-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.0"
|
whatwg-url "^8.0.0"
|
||||||
|
|
||||||
|
date-fns@^2.0.1, date-fns@^2.30.0:
|
||||||
|
version "2.30.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
|
||||||
|
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.21.0"
|
||||||
|
|
||||||
dayjs@^1.10.4, dayjs@^1.11.10:
|
dayjs@^1.10.4, dayjs@^1.11.10:
|
||||||
version "1.11.10"
|
version "1.11.10"
|
||||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz"
|
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz"
|
||||||
@ -7093,7 +7122,7 @@ log-update@^4.0.0:
|
|||||||
slice-ansi "^4.0.0"
|
slice-ansi "^4.0.0"
|
||||||
wrap-ansi "^6.2.0"
|
wrap-ansi "^6.2.0"
|
||||||
|
|
||||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||||
@ -8522,6 +8551,18 @@ react-cytoscapejs@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
|
|
||||||
|
react-datepicker@^4.24.0:
|
||||||
|
version "4.24.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.24.0.tgz#dfb12e277993f1ae2d350b7ba4dd6bba7d21bfb1"
|
||||||
|
integrity sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==
|
||||||
|
dependencies:
|
||||||
|
"@popperjs/core" "^2.11.8"
|
||||||
|
classnames "^2.2.6"
|
||||||
|
date-fns "^2.30.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-onclickoutside "^6.13.0"
|
||||||
|
react-popper "^2.3.0"
|
||||||
|
|
||||||
react-dev-utils@^12.0.1:
|
react-dev-utils@^12.0.1:
|
||||||
version "12.0.1"
|
version "12.0.1"
|
||||||
resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz"
|
resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz"
|
||||||
@ -8604,6 +8645,11 @@ react-fast-compare@^2.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||||
|
|
||||||
|
react-fast-compare@^3.0.1:
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
|
||||||
|
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
|
||||||
|
|
||||||
react-image-crop@^10.1.5:
|
react-image-crop@^10.1.5:
|
||||||
version "10.1.8"
|
version "10.1.8"
|
||||||
resolved "https://registry.npmjs.org/react-image-crop/-/react-image-crop-10.1.8.tgz"
|
resolved "https://registry.npmjs.org/react-image-crop/-/react-image-crop-10.1.8.tgz"
|
||||||
@ -8629,6 +8675,19 @@ react-is@^18.0.0, react-is@^18.2.0:
|
|||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz"
|
||||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||||
|
|
||||||
|
react-onclickoutside@^6.13.0:
|
||||||
|
version "6.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc"
|
||||||
|
integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==
|
||||||
|
|
||||||
|
react-popper@^2.2.5, react-popper@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
|
||||||
|
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
|
||||||
|
dependencies:
|
||||||
|
react-fast-compare "^3.0.1"
|
||||||
|
warning "^4.0.2"
|
||||||
|
|
||||||
react-redux@^7.2.0:
|
react-redux@^7.2.0:
|
||||||
version "7.2.9"
|
version "7.2.9"
|
||||||
resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz"
|
resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz"
|
||||||
@ -10201,6 +10260,13 @@ walker@^1.0.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
makeerror "1.0.12"
|
makeerror "1.0.12"
|
||||||
|
|
||||||
|
warning@^4.0.2:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||||
|
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
watchpack@^2.4.0:
|
watchpack@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz"
|
resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user