feat: new store for view

This commit is contained in:
IlyaDoronin 2023-12-01 16:48:25 +03:00
parent 305b8707ce
commit eed127a236
14 changed files with 161 additions and 139 deletions

@ -25,8 +25,6 @@ export const Footer = ({
useEffect(() => { useEffect(() => {
clearDisabledQuestions(); clearDisabledQuestions();
const nextStepId = questions[stepNumber + 1].id;
const disabledIds = [] as string[]; const disabledIds = [] as string[];
const newDisabledIds = new Set([...disabledQuestionsId, ...disabledIds]); const newDisabledIds = new Set([...disabledQuestionsId, ...disabledIds]);
@ -37,7 +35,7 @@ export const Footer = ({
const cleanDisabledQuestions = new Set<string>(); const cleanDisabledQuestions = new Set<string>();
answers.forEach(({ step, answer }) => { answers.forEach(({ step, answer }) => {
questions[step].content.rule.main.forEach(({ next, rules }) => { questions[step - 1].content.rule.main.forEach(({ next, rules }) => {
rules.forEach(({ answers }) => { rules.forEach(({ answers }) => {
if (answer !== answers[0]) { if (answer !== answers[0]) {
cleanDisabledQuestions.add(next); cleanDisabledQuestions.add(next);

@ -1,3 +1,4 @@
import { useParams } from "react-router-dom";
import { import {
Box, Box,
Button, Button,
@ -12,7 +13,8 @@ import { devlog } from "@frontend/kitui";
import { quizApi } from "@api/quiz"; import { quizApi } from "@api/quiz";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useQuizStore } from "@root/quizes/store";
import { useQuestions } from "@root/questions/hooks";
import { setQuizes } from "@root/quizes/actions"; import { setQuizes } from "@root/quizes/actions";
type StartPageViewPublicationProps = { type StartPageViewPublicationProps = {
@ -24,9 +26,12 @@ export const StartPageViewPublication = ({
setStepNumber, setStepNumber,
showNextButton, showNextButton,
}: StartPageViewPublicationProps) => { }: StartPageViewPublicationProps) => {
const quizId = Number(useParams().quizId);
const { quizes } = useQuizStore();
const { questions } = useQuestions();
const theme = useTheme(); const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(630)); const isTablet = useMediaQuery(theme.breakpoints.down(630));
const quiz = useCurrentQuiz(); const quiz = quizes.find(({ backendId }) => quizId === backendId);
const isMediaFileExist = const isMediaFileExist =
quiz?.config.startpage.background.desktop || quiz?.config.startpage.background.desktop ||
quiz?.config.startpage.background.video; quiz?.config.startpage.background.video;
@ -103,10 +108,8 @@ export const StartPageViewPublication = ({
<Box> <Box>
<Button <Button
variant="contained" variant="contained"
sx={{ sx={{ fontSize: "16px", padding: "10px 15px" }}
fontSize: "16px", disabled={!questions.length}
padding: "10px 15px",
}}
onClick={() => setStepNumber(1)} onClick={() => setStepNumber(1)}
> >
{quiz?.config.startpage.button {quiz?.config.startpage.button

@ -1,25 +1,22 @@
import { useState } from "react"; import { useState } from "react";
import { useParams } from "react-router-dom";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
import type { QuizQuestionDate } from "../../../model/questionTypes/date"; import type { QuizQuestionDate } from "../../../model/questionTypes/date";
type DateProps = { type DateProps = {
stepNumber: number;
question: QuizQuestionDate; question: QuizQuestionDate;
}; };
export const Date = ({ question }: DateProps) => { export const Date = ({ stepNumber, question }: DateProps) => {
const [startDate, setStartDate] = useState<Date | null>(new window.Date()); const [startDate, setStartDate] = useState<Date | null>(new window.Date());
const quizId = Number(useParams().quizId); const { answers } = useQuizViewStore();
const { listQuestions } = questionStore(); const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
const totalIndex = listQuestions[quizId].findIndex(
({ id }) => question.id === id
);
return ( return (
<Box> <Box>

@ -1,5 +1,4 @@
import { useState } from "react"; import { useEffect } from "react";
import { useParams } from "react-router-dom";
import { import {
Box, Box,
Typography, Typography,
@ -9,7 +8,7 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -17,25 +16,33 @@ import RadioIcon from "@ui_kit/RadioIcon";
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
type EmojiProps = { type EmojiProps = {
stepNumber: number;
question: QuizQuestionEmoji; question: QuizQuestionEmoji;
}; };
export const Emoji = ({ question }: EmojiProps) => { export const Emoji = ({ stepNumber, question }: EmojiProps) => {
const [valueIndex, setValueIndex] = useState<number>(0); const { answers } = useQuizViewStore();
const quizId = Number(useParams().quizId);
const { listQuestions } = questionStore();
const theme = useTheme(); const theme = useTheme();
const totalIndex = listQuestions[quizId].findIndex( const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
({ id }) => question.id === id
); useEffect(() => {
if (!answer) {
updateAnswer(stepNumber, question.content.variants[0].id);
}
}, []);
return ( return (
<Box> <Box>
<Typography variant="h5">{question.title}</Typography> <Typography variant="h5">{question.title}</Typography>
<RadioGroup <RadioGroup
name={question.id} name={question.id}
value={valueIndex} value={question.content.variants.findIndex(({ id }) => answer === id)}
onChange={({ target }) => setValueIndex(Number(target.value))} onChange={({ target }) =>
updateAnswer(
stepNumber,
question.content.variants[Number(target.value)].id
)
}
sx={{ sx={{
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap",

@ -1,10 +1,8 @@
import { useState } from "react";
import { useParams } from "react-router-dom";
import { Box, Typography, ButtonBase } from "@mui/material"; import { Box, Typography, ButtonBase } from "@mui/material";
import UploadBox from "@ui_kit/UploadBox"; import UploadBox from "@ui_kit/UploadBox";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import { UPLOAD_FILE_TYPES_MAP } from "@ui_kit/QuizPreview/QuizPreviewQuestionTypes/File"; import { UPLOAD_FILE_TYPES_MAP } from "@ui_kit/QuizPreview/QuizPreviewQuestionTypes/File";
import UploadIcon from "@icons/UploadIcon"; import UploadIcon from "@icons/UploadIcon";
@ -13,24 +11,19 @@ import type { ChangeEvent } from "react";
import type { QuizQuestionFile } from "../../../model/questionTypes/file"; import type { QuizQuestionFile } from "../../../model/questionTypes/file";
type FileProps = { type FileProps = {
stepNumber: number;
question: QuizQuestionFile; question: QuizQuestionFile;
}; };
export const File = ({ question }: FileProps) => { export const File = ({ stepNumber, question }: FileProps) => {
const [fileName, setFileName] = useState<string>(""); const { answers } = useQuizViewStore();
const [file, setFile] = useState<string>(); const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
const quizId = Number(useParams().quizId);
const { listQuestions } = questionStore();
const totalIndex = listQuestions[quizId].findIndex(
({ id }) => question.id === id
);
const uploadFile = ({ target }: ChangeEvent<HTMLInputElement>) => { const uploadFile = ({ target }: ChangeEvent<HTMLInputElement>) => {
const file = target.files?.[0]; const file = target.files?.[0];
if (file) { if (file) {
setFileName(file.name); updateAnswer(stepNumber, `${file.name}|${URL.createObjectURL(file)}`);
setFile(URL.createObjectURL(file));
} }
}; };
@ -55,8 +48,10 @@ export const File = ({ question }: FileProps) => {
/> />
<UploadBox icon={<UploadIcon />} text="5 MB максимум" /> <UploadBox icon={<UploadIcon />} text="5 MB максимум" />
</ButtonBase> </ButtonBase>
{fileName && ( {answer?.split("|")[0] && (
<Typography sx={{ marginTop: "15px" }}>{fileName}</Typography> <Typography sx={{ marginTop: "15px" }}>
{answer?.split("|")[0]}
</Typography>
)} )}
</Box> </Box>
</Box> </Box>

@ -1,5 +1,4 @@
import { useState } from "react"; import { useEffect } from "react";
import { useParams } from "react-router-dom";
import { import {
Box, Box,
Typography, Typography,
@ -10,7 +9,7 @@ import {
useMediaQuery, useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -18,27 +17,35 @@ import RadioIcon from "@ui_kit/RadioIcon";
import type { QuizQuestionImages } from "../../../model/questionTypes/images"; import type { QuizQuestionImages } from "../../../model/questionTypes/images";
type ImagesProps = { type ImagesProps = {
stepNumber: number;
question: QuizQuestionImages; question: QuizQuestionImages;
}; };
export const Images = ({ question }: ImagesProps) => { export const Images = ({ stepNumber, question }: ImagesProps) => {
const [valueIndex, setValueIndex] = useState<number>(0); const { answers } = useQuizViewStore();
const quizId = Number(useParams().quizId);
const { listQuestions } = questionStore();
const theme = useTheme(); const theme = useTheme();
const totalIndex = listQuestions[quizId].findIndex( const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
({ id }) => question.id === id
);
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(500)); const isMobile = useMediaQuery(theme.breakpoints.down(500));
useEffect(() => {
if (!answer) {
updateAnswer(stepNumber, question.content.variants[0].id);
}
}, []);
return ( return (
<Box> <Box>
<Typography variant="h5">{question.title}</Typography> <Typography variant="h5">{question.title}</Typography>
<RadioGroup <RadioGroup
name={question.id} name={question.id}
value={valueIndex} value={question.content.variants.findIndex(({ id }) => answer === id)}
onChange={({ target }) => setValueIndex(Number(target.value))} onChange={({ target }) =>
updateAnswer(
stepNumber,
question.content.variants[Number(target.value)].id
)
}
sx={{ sx={{
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap",

@ -1,25 +1,30 @@
import { useState } from "react"; import { useEffect } from "react";
import { useParams } from "react-router-dom";
import { Box, Typography, Slider, useTheme } from "@mui/material"; import { Box, Typography, Slider, useTheme } from "@mui/material";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
type NumberProps = { type NumberProps = {
stepNumber: number;
question: QuizQuestionNumber; question: QuizQuestionNumber;
}; };
export const Number = ({ question }: NumberProps) => { export const Number = ({ stepNumber, question }: NumberProps) => {
const [value, setValue] = useState<number>(0);
const quizId = window.Number(useParams().quizId);
const { listQuestions } = questionStore();
const theme = useTheme(); const theme = useTheme();
const totalIndex = listQuestions[quizId].findIndex( const { answers } = useQuizViewStore();
({ id }) => question.id === id const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
);
const min = window.Number(question.content.range.split("—")[0]);
const max = window.Number(question.content.range.split("—")[1]);
useEffect(() => {
if (!answer) {
updateAnswer(stepNumber, "1");
}
}, []);
return ( return (
<Box> <Box>
@ -34,25 +39,34 @@ export const Number = ({ question }: NumberProps) => {
> >
<CustomTextField <CustomTextField
placeholder="0" placeholder="0"
value={String(value)} value={answer || ""}
onChange={({ target }) => {
updateAnswer(
stepNumber,
window.Number(target.value) > max
? String(max)
: window.Number(target.value) < min
? String(min)
: target.value
);
}}
sx={{ sx={{
maxWidth: "80px", maxWidth: "80px",
"& .MuiInputBase-input": { "& .MuiInputBase-input": { textAlign: "center" },
textAlign: "center",
},
}} }}
/> />
<Slider <Slider
value={value} value={window.Number(answer || 1)}
min={window.Number(question.content.range.split("—")[0])} min={min}
max={window.Number(question.content.range.split("—")[1])} max={max}
step={question.content.step || 1}
sx={{ sx={{
color: theme.palette.brightPurple.main, color: theme.palette.brightPurple.main,
padding: "0", padding: "0",
marginTop: "25px", marginTop: "25px",
}} }}
onChange={(_, value) => { onChange={(_, value) => {
setValue(value as number); updateAnswer(stepNumber, String(value));
}} }}
/> />
</Box> </Box>

@ -1,19 +1,17 @@
import { useParams } from "react-router-dom";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import type { QuizQuestionPage } from "../../../model/questionTypes/page"; import type { QuizQuestionPage } from "../../../model/questionTypes/page";
type PageProps = { type PageProps = {
stepNumber: number;
question: QuizQuestionPage; question: QuizQuestionPage;
}; };
export const Page = ({ question }: PageProps) => { export const Page = ({ stepNumber, question }: PageProps) => {
const quizId = Number(useParams().quizId); const { answers } = useQuizViewStore();
const { listQuestions } = questionStore(); const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
const totalIndex = listQuestions[quizId].findIndex(
({ id }) => question.id === id
);
return ( return (
<Box> <Box>

@ -1,4 +1,3 @@
import { useParams } from "react-router-dom";
import { import {
Box, Box,
Typography, Typography,
@ -6,23 +5,21 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import StarIconMini from "@icons/questionsPage/StarIconMini"; import StarIconMini from "@icons/questionsPage/StarIconMini";
import type { QuizQuestionRating } from "../../../model/questionTypes/rating"; import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
type RatingProps = { type RatingProps = {
stepNumber: number;
question: QuizQuestionRating; question: QuizQuestionRating;
}; };
export const Rating = ({ question }: RatingProps) => { export const Rating = ({ stepNumber, question }: RatingProps) => {
const quizId = Number(useParams().quizId); const { answers } = useQuizViewStore();
const { listQuestions } = questionStore();
const totalIndex = listQuestions[quizId].findIndex(
({ id }) => question.id === id
);
const theme = useTheme(); const theme = useTheme();
const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
return ( return (
<Box> <Box>
@ -36,6 +33,8 @@ export const Rating = ({ question }: RatingProps) => {
}} }}
> >
<RatingComponent <RatingComponent
value={Number(answer || 0)}
onChange={(_, value) => updateAnswer(stepNumber, String(value))}
sx={{ height: "50px" }} sx={{ height: "50px" }}
max={question.content.steps} max={question.content.steps}
icon={ icon={

@ -1,22 +1,19 @@
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
import { useParams } from "react-router-dom";
import { Select as SelectComponent } from "../../../pages/Questions/Select"; import { Select as SelectComponent } from "../../../pages/Questions/Select";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
type SelectProps = { type SelectProps = {
stepNumber: number;
question: QuizQuestionSelect; question: QuizQuestionSelect;
}; };
export const Select = ({ question }: SelectProps) => { export const Select = ({ stepNumber, question }: SelectProps) => {
const quizId = Number(useParams().quizId); const { answers } = useQuizViewStore();
const { listQuestions } = questionStore(); const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
const totalIndex = listQuestions[quizId].findIndex(
({ id }) => question.id === id
);
return ( return (
<Box> <Box>
@ -30,8 +27,10 @@ export const Select = ({ question }: SelectProps) => {
}} }}
> >
<SelectComponent <SelectComponent
activeItemIndex={Number(answer) || 0}
items={question.content.variants.map(({ answer }) => answer)} items={question.content.variants.map(({ answer }) => answer)}
onChange={(action, num) => { onChange={(_, value) => {
updateAnswer(stepNumber, String(value));
}} }}
/> />
</Box> </Box>

@ -1,22 +1,19 @@
import { useParams } from "react-router-dom";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { questionStore, updateQuestionsList } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import type { QuizQuestionText } from "../../../model/questionTypes/text"; import type { QuizQuestionText } from "../../../model/questionTypes/text";
type TextProps = { type TextProps = {
stepNumber: number;
question: QuizQuestionText; question: QuizQuestionText;
}; };
export const Text = ({ question }: TextProps) => { export const Text = ({ stepNumber, question }: TextProps) => {
const quizId = Number(useParams().quizId); const { answers } = useQuizViewStore();
const { listQuestions } = questionStore(); const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
const totalIndex = listQuestions[quizId].findIndex(
({ id }) => question.id === id
);
return ( return (
<Box> <Box>
@ -29,7 +26,11 @@ export const Text = ({ question }: TextProps) => {
marginTop: "20px", marginTop: "20px",
}} }}
> >
<CustomTextField placeholder={question.content.placeholder} /> <CustomTextField
placeholder={question.content.placeholder}
value={answer || ""}
onChange={({ target }) => updateAnswer(stepNumber, target.value)}
/>
</Box> </Box>
</Box> </Box>
); );

@ -23,8 +23,7 @@ type VariantProps = {
export const Variant = ({ stepNumber, question }: VariantProps) => { export const Variant = ({ stepNumber, question }: VariantProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const answerIndex = answers.findIndex(({ step }) => step === stepNumber); const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
const answer = answers[answerIndex]?.answer;
useEffect(() => { useEffect(() => {
if (!answer) { if (!answer) {

@ -1,5 +1,4 @@
import { useState } from "react"; import { useEffect } from "react";
import { useParams } from "react-router-dom";
import { import {
Box, Box,
Typography, Typography,
@ -9,7 +8,7 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { questionStore } from "@root/questions"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -17,17 +16,21 @@ import RadioIcon from "@ui_kit/RadioIcon";
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg"; import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
type VarimgProps = { type VarimgProps = {
stepNumber: number;
question: QuizQuestionVarImg; question: QuizQuestionVarImg;
}; };
export const Varimg = ({ question }: VarimgProps) => { export const Varimg = ({ stepNumber, question }: VarimgProps) => {
const [valueIndex, setValueIndex] = useState<number>(-1); const { answers } = useQuizViewStore();
const quizId = Number(useParams().quizId);
const { listQuestions } = questionStore();
const theme = useTheme(); const theme = useTheme();
const totalIndex = listQuestions[quizId].findIndex( const { answer } = answers.find(({ step }) => step === stepNumber) ?? {};
({ id }) => question.id === id const variant = question.content.variants.find(({ id }) => answer === id);
);
useEffect(() => {
if (!answer) {
updateAnswer(stepNumber, question.content.variants[0].id);
}
}, []);
return ( return (
<Box> <Box>
@ -35,8 +38,13 @@ export const Varimg = ({ question }: VarimgProps) => {
<Box sx={{ display: "flex" }}> <Box sx={{ display: "flex" }}>
<RadioGroup <RadioGroup
name={question.id} name={question.id}
value={valueIndex} value={question.content.variants.findIndex(({ id }) => answer === id)}
onChange={({ target }) => setValueIndex(Number(target.value))} onChange={({ target }) =>
updateAnswer(
stepNumber,
question.content.variants[Number(target.value)].id
)
}
sx={{ sx={{
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap",
@ -67,15 +75,10 @@ export const Varimg = ({ question }: VarimgProps) => {
))} ))}
</Box> </Box>
</RadioGroup> </RadioGroup>
{(question.content.variants[valueIndex]?.extendedText || {(variant?.extendedText || question.content.back) && (
question.content.back) && (
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}> <Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
<img <img
src={ src={answer ? variant?.extendedText : question.content.back}
valueIndex >= 0
? question.content.variants[valueIndex].extendedText
: question.content.back
}
style={{ width: "100%", height: "100%", objectFit: "cover" }} style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt="" alt=""
/> />

@ -186,16 +186,18 @@ export default function StartPage() {
> >
Предпросмотр Предпросмотр
</Button> </Button>
<Button <a href={`/view/${quiz?.backendId}`} target="_blank" rel="noreferrer" style={{ textDecoration: "none" }}>
variant="contained" <Button
sx={{ variant="contained"
fontSize: "14px", sx={{
lineHeight: "18px", fontSize: "14px",
height: "34px", lineHeight: "18px",
}} height: "34px",
> }}
Опубликовать >
</Button> Опубликовать
</Button>
</a>
<CustomAvatar <CustomAvatar
sx={{ sx={{
ml: "11px", ml: "11px",