fix conflicts

This commit is contained in:
nflnkr 2023-11-17 18:42:49 +03:00
parent f463270a9b
commit bf8a41a3bc
18 changed files with 415 additions and 396 deletions

@ -1,10 +1,11 @@
import { makeRequest } from "@frontend/kitui"; import { makeRequest } from "@frontend/kitui";
import { CreateQuestionRequest } from "model/question/create"; import { CreateQuestionRequest } from "model/question/create";
import { RawQuestion } from "model/question/question"; import { RawQuestion } from "model/question/question";
import { GetQuestionListRequest, GetQuestionListResponse } from "model/question/getList"; import { GetQuestionListRequest, GetQuestionListResponse } from "@model/question/getList";
import { EditQuestionRequest, EditQuestionResponse } from "model/question/edit"; import { EditQuestionRequest, EditQuestionResponse } from "@model/question/edit";
import { DeleteQuestionRequest, DeleteQuestionResponse } from "model/question/delete"; import { DeleteQuestionRequest, DeleteQuestionResponse } from "@model/question/delete";
import { CopyQuestionRequest, CopyQuestionResponse } from "model/question/copy"; import { CopyQuestionRequest, CopyQuestionResponse } from "@model/question/copy";
import { QUIZ_QUESTION_VARIANT } from "../constants/variant";
const baseUrl = process.env.NODE_ENV === "production" ? "/squiz" : "https://squiz.pena.digital/squiz"; const baseUrl = process.env.NODE_ENV === "production" ? "/squiz" : "https://squiz.pena.digital/squiz";
@ -70,16 +71,11 @@ const defaultCreateQuestionBody: CreateQuestionRequest = {
"type": "variant", "type": "variant",
"required": true, "required": true,
"page": 0, "page": 0,
"content": "string", "content": JSON.stringify(QUIZ_QUESTION_VARIANT.content),
}; };
const defaultGetQuestionListBody: GetQuestionListRequest = { const defaultGetQuestionListBody: GetQuestionListRequest = {
"limit": 0, "limit": 0,
"offset": 0, "offset": 0,
"from": 0, "type": "",
"to": 0, };
"search": "string",
"type": "string",
"deleted": true,
"required": true,
};

@ -1,6 +1,7 @@
import { QUIZ_QUESTION_BASE } from "./base"; import { QUIZ_QUESTION_BASE } from "./base";
import type { QuizQuestionEmoji } from "../model/questionTypes/emoji"; import type { QuizQuestionEmoji } from "../model/questionTypes/emoji";
import { nanoid } from "nanoid";
export const QUIZ_QUESTION_EMOJI: Omit<QuizQuestionEmoji, "id"> = { export const QUIZ_QUESTION_EMOJI: Omit<QuizQuestionEmoji, "id"> = {
...QUIZ_QUESTION_BASE, ...QUIZ_QUESTION_BASE,
@ -14,6 +15,7 @@ export const QUIZ_QUESTION_EMOJI: Omit<QuizQuestionEmoji, "id"> = {
required: false, required: false,
variants: [ variants: [
{ {
id: nanoid(),
answer: "", answer: "",
extendedText: "", extendedText: "",
hints: "" hints: ""

@ -1,6 +1,7 @@
import { QUIZ_QUESTION_BASE } from "./base"; import { QUIZ_QUESTION_BASE } from "./base";
import type { QuizQuestionImages } from "../model/questionTypes/images"; import type { QuizQuestionImages } from "../model/questionTypes/images";
import { nanoid } from "nanoid";
export const QUIZ_QUESTION_IMAGES: Omit<QuizQuestionImages, "id"> = { export const QUIZ_QUESTION_IMAGES: Omit<QuizQuestionImages, "id"> = {
...QUIZ_QUESTION_BASE, ...QUIZ_QUESTION_BASE,
@ -17,6 +18,7 @@ export const QUIZ_QUESTION_IMAGES: Omit<QuizQuestionImages, "id"> = {
required: false, required: false,
variants: [ variants: [
{ {
id: nanoid(),
answer: "", answer: "",
extendedText: "", extendedText: "",
originalImageUrl: "", originalImageUrl: "",

@ -1,6 +1,7 @@
import { QUIZ_QUESTION_BASE } from "./base"; import { QUIZ_QUESTION_BASE } from "./base";
import type { QuizQuestionSelect } from "../model/questionTypes/select"; import type { QuizQuestionSelect } from "../model/questionTypes/select";
import { nanoid } from "nanoid";
export const QUIZ_QUESTION_SELECT: Omit<QuizQuestionSelect, "id"> = { export const QUIZ_QUESTION_SELECT: Omit<QuizQuestionSelect, "id"> = {
...QUIZ_QUESTION_BASE, ...QUIZ_QUESTION_BASE,
@ -12,6 +13,6 @@ export const QUIZ_QUESTION_SELECT: Omit<QuizQuestionSelect, "id"> = {
innerNameCheck: false, innerNameCheck: false,
innerName: "", innerName: "",
default: "", default: "",
variants: [{ answer: "", extendedText: "", hints: "" }], variants: [{ id: nanoid(), answer: "", extendedText: "", hints: "" }],
}, },
}; };

@ -1,6 +1,7 @@
import { QUIZ_QUESTION_BASE } from "./base"; import { QUIZ_QUESTION_BASE } from "./base";
import type { QuizQuestionVariant } from "../model/questionTypes/variant"; import type { QuizQuestionVariant } from "../model/questionTypes/variant";
import { nanoid } from "nanoid";
export const QUIZ_QUESTION_VARIANT: Omit<QuizQuestionVariant, "id"> = { export const QUIZ_QUESTION_VARIANT: Omit<QuizQuestionVariant, "id"> = {
...QUIZ_QUESTION_BASE, ...QUIZ_QUESTION_BASE,
@ -13,6 +14,6 @@ export const QUIZ_QUESTION_VARIANT: Omit<QuizQuestionVariant, "id"> = {
innerNameCheck: false, innerNameCheck: false,
required: false, required: false,
innerName: "", innerName: "",
variants: [{ answer: "", extendedText: "", hints: "" }], variants: [{ id: nanoid(), answer: "", extendedText: "", hints: "" }],
}, },
}; };

@ -1,6 +1,7 @@
import { QUIZ_QUESTION_BASE } from "./base"; import { QUIZ_QUESTION_BASE } from "./base";
import type { QuizQuestionVarImg } from "../model/questionTypes/varimg"; import type { QuizQuestionVarImg } from "../model/questionTypes/varimg";
import { nanoid } from "nanoid";
export const QUIZ_QUESTION_VARIMG: Omit<QuizQuestionVarImg, "id"> = { export const QUIZ_QUESTION_VARIMG: Omit<QuizQuestionVarImg, "id"> = {
...QUIZ_QUESTION_BASE, ...QUIZ_QUESTION_BASE,
@ -11,7 +12,7 @@ export const QUIZ_QUESTION_VARIMG: Omit<QuizQuestionVarImg, "id"> = {
innerNameCheck: false, innerNameCheck: false,
innerName: "", innerName: "",
required: false, required: false,
variants: [{ answer: "", hints: "", extendedText: "", originalImageUrl: "" }], variants: [{ id: nanoid(), answer: "", hints: "", extendedText: "", originalImageUrl: "" }],
largeCheck: false, largeCheck: false,
replText: "", replText: "",
}, },

@ -9,6 +9,7 @@ export interface EditQuestionRequest {
type?: QuestionType; type?: QuestionType;
required?: boolean; required?: boolean;
page?: number; page?: number;
content: AnyQuizQuestion["content"];
} }
export interface EditQuestionResponse { export interface EditQuestionResponse {
@ -23,5 +24,6 @@ export function questionToEditQuestionRequest(question: AnyQuizQuestion): EditQu
type: question.type, type: question.type,
required: question.required, required: question.required,
page: question.page, page: question.page,
content: question.content,
}; };
} }

@ -1,4 +1,4 @@
import { RawQuestion } from "./question"; import { QuestionType, RawQuestion } from "./question";
export interface GetQuestionListRequest { export interface GetQuestionListRequest {
@ -13,7 +13,7 @@ export interface GetQuestionListRequest {
/** string for fulltext search in titles of questions */ /** string for fulltext search in titles of questions */
search?: string; search?: string;
/** allow only - text, select, file, variant, images, varimg, emoji, date, number, page, rating or empty string */ /** allow only - text, select, file, variant, images, varimg, emoji, date, number, page, rating or empty string */
type?: string; type: "" | QuestionType;
/** get deleted quizes */ /** get deleted quizes */
deleted?: boolean; deleted?: boolean;
/** get only require questions */ /** get only require questions */

@ -21,153 +21,153 @@ import type { ImageQuestionVariant, QuestionVariant } from "../../../model/quest
type AnswerItemProps = { type AnswerItemProps = {
index: number; index: number;
questionId: number; questionId: number;
variant: QuestionVariant | ImageQuestionVariant; variant: QuestionVariant | ImageQuestionVariant;
largeCheck: boolean; largeCheck: boolean;
additionalContent?: ReactNode; additionalContent?: ReactNode;
additionalMobile?: ReactNode; additionalMobile?: ReactNode;
}; };
export const AnswerItem = ({ export const AnswerItem = ({
index, index,
variant, variant,
questionId, questionId,
largeCheck, largeCheck,
additionalContent, additionalContent,
additionalMobile, additionalMobile,
}: AnswerItemProps) => { }: AnswerItemProps) => {
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) => { const setQuestionVariantAnswer = useDebouncedCallback((value) => {
setQuestionVariantField(questionId, variant.id,"answer", value); setQuestionVariantField(questionId, variant.id, "answer", value);
}, 1000); }, 1000);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
setIsOpen(true); setIsOpen(true);
}; };
const handleClose = () => { const handleClose = () => {
setIsOpen(false); setIsOpen(false);
}; };
return ( return (
<Draggable draggableId={String(index)} index={index}> <Draggable draggableId={String(index)} index={index}>
{(provided) => ( {(provided) => (
<Box ref={provided.innerRef} {...provided.draggableProps}> <Box ref={provided.innerRef} {...provided.draggableProps}>
<FormControl <FormControl
key={index} key={index}
fullWidth fullWidth
variant="standard" variant="standard"
sx={{ sx={{
margin: isTablet ? " 15px 0 20px 0" : "0 0 20px 0", margin: isTablet ? " 15px 0 20px 0" : "0 0 20px 0",
borderRadius: "10px", borderRadius: "10px",
border: "1px solid rgba(0, 0, 0, 0.23)", border: "1px solid rgba(0, 0, 0, 0.23)",
background: "white", background: "white",
}} }}
> >
<TextField <TextField
defaultValue={variant.answer} defaultValue={variant.answer}
fullWidth fullWidth
focused={false} focused={false}
placeholder={"Добавьте ответ"} placeholder={"Добавьте ответ"}
multiline={largeCheck} multiline={largeCheck}
onChange={({ target }) => setQuestionVariantAnswer(target.value)} onChange={({ target }) => setQuestionVariantAnswer(target.value)}
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => { onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
if (event.code === "Enter" && !largeCheck) { if (event.code === "Enter" && !largeCheck) {
addQuestionVariant(questionId); addQuestionVariant(questionId);
} }
}} }}
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<> <>
<InputAdornment <InputAdornment
{...provided.dragHandleProps} {...provided.dragHandleProps}
position="start" position="start"
> >
<PointsIcon <PointsIcon
style={{ color: "#9A9AAF", fontSize: "30px" }} style={{ color: "#9A9AAF", fontSize: "30px" }}
/> />
</InputAdornment> </InputAdornment>
{additionalContent} {additionalContent}
</> </>
), ),
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton <IconButton
sx={{ padding: "0" }} sx={{ padding: "0" }}
aria-describedby="my-popover-id" aria-describedby="my-popover-id"
onClick={handleClick} onClick={handleClick}
> >
<MessageIcon <MessageIcon
style={{ style={{
color: "#9A9AAF", color: "#9A9AAF",
fontSize: "30px", fontSize: "30px",
marginRight: "6.5px", marginRight: "6.5px",
}} }}
/> />
</IconButton> </IconButton>
<Popover <Popover
id="my-popover-id" id="my-popover-id"
open={isOpen} open={isOpen}
anchorEl={anchorEl} anchorEl={anchorEl}
onClose={handleClose} onClose={handleClose}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }} anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
> >
<TextareaAutosize <TextareaAutosize
style={{ margin: "10px" }} style={{ margin: "10px" }}
placeholder="Подсказка для этого ответа" placeholder="Подсказка для этого ответа"
value={variant.hints} value={variant.hints}
onChange={e => setQuestionVariantField(questionId, variant.id, "hints", e.target.value)} onChange={e => setQuestionVariantField(questionId, variant.id, "hints", e.target.value)}
onKeyDown={( onKeyDown={(
event: KeyboardEvent<HTMLTextAreaElement> event: KeyboardEvent<HTMLTextAreaElement>
) => event.stopPropagation()} ) => event.stopPropagation()}
/> />
</Popover> </Popover>
<IconButton <IconButton
sx={{ padding: "0" }} sx={{ padding: "0" }}
onClick={() => deleteQuestionVariant(questionId, variant.id)} onClick={() => deleteQuestionVariant(questionId, variant.id)}
> >
<DeleteIcon <DeleteIcon
style={{ style={{
color: theme.palette.grey2.main, color: theme.palette.grey2.main,
marginRight: "-1px", marginRight: "-1px",
}} }}
/> />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
), ),
}} }}
sx={{ sx={{
"& .MuiInputBase-root": { "& .MuiInputBase-root": {
padding: additionalContent ? "5px 13px" : "13px", padding: additionalContent ? "5px 13px" : "13px",
borderRadius: "10px", borderRadius: "10px",
background: "#ffffff", background: "#ffffff",
"& input.MuiInputBase-input": { "& input.MuiInputBase-input": {
height: "22px", height: "22px",
}, },
"& textarea.MuiInputBase-input": { "& textarea.MuiInputBase-input": {
marginTop: "1px", marginTop: "1px",
}, },
"& .MuiOutlinedInput-notchedOutline": { "& .MuiOutlinedInput-notchedOutline": {
border: "none", border: "none",
}, },
}, },
}} }}
inputProps={{ inputProps={{
sx: { fontSize: "18px", lineHeight: "21px", py: 0, ml: "13px" }, sx: { fontSize: "18px", lineHeight: "21px", py: 0, ml: "13px" },
"data-cy": "quiz-variant-question-answer", "data-cy": "quiz-variant-question-answer",
}} }}
/> />
{additionalMobile} {additionalMobile}
</FormControl> </FormControl>
</Box> </Box>
)} )}
</Draggable> </Draggable>
); );
}; };

@ -1,8 +1,8 @@
import { AnyQuizQuestion } from "@model/questionTypes/shared";
import { Box, ListItem, Typography, useTheme } from "@mui/material"; import { Box, ListItem, Typography, useTheme } from "@mui/material";
import { memo } from "react"; import { memo } from "react";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import QuestionsPageCard from "./QuestionPageCard"; import QuestionsPageCard from "./QuestionPageCard";
import { AnyQuizQuestion } from "@model/questionTypes/shared";
type Props = { type Props = {
@ -22,7 +22,7 @@ function DraggableListItem({ question, isDragging, index }: Props) {
{...provided.draggableProps} {...provided.draggableProps}
sx={{ userSelect: "none", padding: 0 }} sx={{ userSelect: "none", padding: 0 }}
> >
{/* questionData.deleted TODO */ true ? ( {question.deleted ? (
<Box <Box
{...provided.dragHandleProps} {...provided.dragHandleProps}
sx={{ sx={{

@ -1,17 +1,3 @@
import {
Box,
Checkbox,
FormControl,
FormControlLabel,
IconButton,
InputAdornment,
Paper,
TextField,
useMediaQuery,
useTheme,
} from "@mui/material";
import { useRef, useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import { CrossedEyeIcon } from "@icons/CrossedEyeIcon"; import { CrossedEyeIcon } from "@icons/CrossedEyeIcon";
import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon"; import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon";
import { CopyIcon } from "@icons/questionsPage/CopyIcon"; import { CopyIcon } from "@icons/questionsPage/CopyIcon";
@ -31,8 +17,22 @@ import Page from "@icons/questionsPage/page";
import RatingIcon from "@icons/questionsPage/rating"; import RatingIcon from "@icons/questionsPage/rating";
import Slider from "@icons/questionsPage/slider"; import Slider from "@icons/questionsPage/slider";
import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import { copyQuestion, createQuestion, deleteQuestion, toggleExpandQuestion } from "@root/questions/actions"; import {
Box,
Checkbox,
FormControl,
FormControlLabel,
IconButton,
InputAdornment,
Paper,
TextField,
useMediaQuery,
useTheme,
} from "@mui/material";
import { copyQuestion, createQuestion, deleteQuestion, toggleExpandQuestion, updateQuestionWithFnOptimistic } from "@root/questions/actions";
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 { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg"; import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg";
import type { AnyQuizQuestion } from "../../../model/questionTypes/shared"; import type { AnyQuizQuestion } from "../../../model/questionTypes/shared";
import SwitchQuestionsPage from "../SwitchQuestionsPage"; import SwitchQuestionsPage from "../SwitchQuestionsPage";
@ -52,8 +52,11 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const anchorRef = useRef(null); const anchorRef = useRef(null);
const debounced = useDebouncedCallback((title) => { // TODO update title
// updateQuestionsList<QuizQuestionInitial>(quizId, totalIndex, { title }); const setTitle = useDebouncedCallback((title) => {
updateQuestionWithFnOptimistic(question.id, question => {
question.title = title;
});
}, 200); }, 200);
return ( return (
@ -88,8 +91,8 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
> >
<TextField <TextField
defaultValue={question.title} defaultValue={question.title}
placeholder={"Заголовок вопроса"} placeholder={"Заголовок вопроса2"}
onChange={({ target }) => debounced(target.value)} onChange={({ target }) => setTitle(target.value)}
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<Box> <Box>

@ -1,148 +1,133 @@
import { useState } from "react";
import { useParams } from "react-router-dom";
import { import {
Box, Box,
Typography, Button,
Popper, ClickAwayListener,
Grow, Grow,
Paper, MenuItem,
MenuList, MenuList,
MenuItem, Modal,
ClickAwayListener, Paper,
Modal, Popper,
Button, Typography,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { useState } from "react";
import {
questionStore,
updateQuestionsList,
removeQuestionForce,
createQuestion,
} from "@root/questions";
import { BUTTON_TYPE_QUESTIONS } from "../../TypeQuestions"; import { BUTTON_TYPE_QUESTIONS } from "../../TypeQuestions";
import type { RefObject } from "react";
import type {
QuizQuestionBase,
} from "../../../../model/questionTypes/shared";
import { QuestionType } from "@model/question/question"; import { QuestionType } from "@model/question/question";
import { updateQuestionWithFnOptimistic } from "@root/questions/actions";
import type { RefObject } from "react";
import type { AnyQuizQuestion } from "../../../../model/questionTypes/shared";
type ChooseAnswerModalProps = { type ChooseAnswerModalProps = {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
anchorRef: RefObject<HTMLDivElement>; anchorRef: RefObject<HTMLDivElement>;
totalIndex: number; question: AnyQuizQuestion;
switchState: string; switchState: string;
}; };
export const ChooseAnswerModal = ({ export const ChooseAnswerModal = ({
open, open,
onClose, onClose,
anchorRef, anchorRef,
totalIndex, question,
switchState, switchState,
}: ChooseAnswerModalProps) => { }: ChooseAnswerModalProps) => {
const [openModal, setOpenModal] = useState<boolean>(false); const [openModal, setOpenModal] = useState<boolean>(false);
const [selectedValue, setSelectedValue] = useState<QuestionType>("text"); const [selectedValue, setSelectedValue] = useState<QuestionType>("text");
const quizId = Number(useParams().quizId); const theme = useTheme();
const { listQuestions } = questionStore();
const theme = useTheme();
return ( return (
<> <>
<Popper <Popper
placement="right-start" placement="right-start"
open={open} open={open}
anchorEl={anchorRef.current} anchorEl={anchorRef.current}
transition transition
> >
{({ TransitionProps }) => ( {({ TransitionProps }) => (
<Grow {...TransitionProps}> <Grow {...TransitionProps}>
<Paper> <Paper>
<ClickAwayListener onClickAway={onClose}> <ClickAwayListener onClickAway={onClose}>
<MenuList autoFocusItem={open}> <MenuList autoFocusItem={open}>
{BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => (
<MenuItem <MenuItem
key={value} key={value}
sx={{ display: "flex", gap: "10px" }} sx={{ display: "flex", gap: "10px" }}
{...(value !== switchState && { {...(value !== switchState && {
onClick: () => { onClick: () => {
onClose(); onClose();
setOpenModal(true); setOpenModal(true);
setSelectedValue(value); setSelectedValue(value);
}, },
})} })}
> >
<Box>{icon}</Box> <Box>{icon}</Box>
<Typography <Typography
sx={{
color:
value === switchState
? theme.palette.brightPurple.main
: theme.palette.grey2.main,
}}
>
{title}
</Typography>
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
<Modal open={openModal} onClose={() => setOpenModal(false)}>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
padding: "30px",
borderRadius: "10px",
background: "#FFFFFF",
}}
>
<Typography variant="h6">
Все настройки, кроме заголовка вопроса будут сброшены
</Typography>
<Box
sx={{ sx={{
color: marginTop: "30px",
value === switchState display: "flex",
? theme.palette.brightPurple.main justifyContent: "center",
: theme.palette.grey2.main, gap: "15px",
}} }}
> >
{title} <Button
</Typography> variant="contained"
</MenuItem> sx={{ minWidth: "150px" }}
))} onClick={() => setOpenModal(false)}
</MenuList> >
</ClickAwayListener> Отмена
</Paper> </Button>
</Grow> <Button
)} variant="contained"
</Popper> sx={{ minWidth: "150px" }}
<Modal open={openModal} onClose={() => setOpenModal(false)}> onClick={() => {
<Box setOpenModal(false);
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
padding: "30px",
borderRadius: "10px",
background: "#FFFFFF",
}}
>
<Typography variant="h6">
Все настройки, кроме заголовка вопроса будут сброшены
</Typography>
<Box
sx={{
marginTop: "30px",
display: "flex",
justifyContent: "center",
gap: "15px",
}}
>
<Button
variant="contained"
sx={{ minWidth: "150px" }}
onClick={() => setOpenModal(false)}
>
Отмена
</Button>
<Button
variant="contained"
sx={{ minWidth: "150px" }}
onClick={() => {
setOpenModal(false);
const question = { ...listQuestions[quizId][totalIndex] }; updateQuestionWithFnOptimistic(question.id, question => {
question.type = selectedValue;
removeQuestionForce(quizId, question.id); });
createQuestion(quizId, selectedValue, totalIndex); }}
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, { >
expanded: question.expanded, Подтвердить
}); </Button>
}} </Box>
> </Box>
Подтвердить </Modal>
</Button> </>
</Box> );
</Box>
</Modal>
</>
);
}; };

@ -2,88 +2,87 @@ import { memo } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import { Box, ListItem, Typography, useTheme } from "@mui/material"; import { Box, ListItem, Typography, useTheme } from "@mui/material";
import QuestionsPageCard from "./QuestionPageCard"; import QuestionsPageCard from "./QuestionPageCard";
import { updateQuestionsList } from "@root/questions"; import { updateQuestionsList } from "@root/questions";
import { AnyQuizQuestion, QuizQuestionBase } from "../../../../model/questionTypes/shared";
import { QuizQuestionBase } from "../../../../model/questionTypes/shared";
type FormDraggableListItemProps = { type FormDraggableListItemProps = {
index: number; question: AnyQuizQuestion;
isDragging: boolean; questionIndex: number;
questionData: QuizQuestionBase; questionData: QuizQuestionBase;
}; };
export default memo( export default memo(
({ index, isDragging, questionData }: FormDraggableListItemProps) => { ({ question, questionIndex, questionData }: FormDraggableListItemProps) => {
const quizId = Number(useParams().quizId); const quizId = Number(useParams().quizId);
const theme = useTheme(); const theme = useTheme();
return ( return (
<Draggable draggableId={String(index)} index={index}> <Draggable draggableId={String(questionIndex)} index={questionIndex}>
{(provided) => ( {(provided) => (
<ListItem <ListItem
ref={provided.innerRef} ref={provided.innerRef}
{...(index !== 0 ? provided.draggableProps : {})} {...(questionIndex !== 0 ? provided.draggableProps : {})}
sx={{ userSelect: "none", padding: 0 }} sx={{ userSelect: "none", padding: 0 }}
> >
{questionData.deleted ? ( {questionData.deleted ? (
<Box <Box
{...provided.dragHandleProps} {...provided.dragHandleProps}
sx={{ sx={{
width: "100%", width: "100%",
maxWidth: "800px", maxWidth: "800px",
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
marginTop: "30px", marginTop: "30px",
gap: "5px", gap: "5px",
}} }}
> >
<Typography <Typography
sx={{ sx={{
fontSize: "16px", fontSize: "16px",
color: theme.palette.grey2.main, color: theme.palette.grey2.main,
}} }}
> >
Вопрос удалён. Вопрос удалён.
</Typography> </Typography>
<Typography <Typography
onClick={() => { onClick={() => {
updateQuestionsList<QuizQuestionBase>(quizId, index, { updateQuestionsList<QuizQuestionBase>(quizId, questionIndex, {
...questionData, ...questionData,
deleted: false, deleted: false,
}); });
}} }}
sx={{ sx={{
cursor: "pointer", cursor: "pointer",
fontSize: "16px", fontSize: "16px",
textDecoration: "underline", textDecoration: "underline",
color: theme.palette.brightPurple.main, color: theme.palette.brightPurple.main,
textDecorationColor: theme.palette.brightPurple.main, textDecorationColor: theme.palette.brightPurple.main,
}} }}
> >
Восстановить? Восстановить?
</Typography> </Typography>
</Box> </Box>
) : ( ) : (
<Box <Box
sx={{ sx={{
width: "100%", width: "100%",
position: "relative", position: "relative",
borderBottom: "1px solid rgba(0, 0, 0, 0.23)", borderBottom: "1px solid rgba(0, 0, 0, 0.23)",
}} }}
> >
<QuestionsPageCard <QuestionsPageCard
key={index} key={questionIndex}
question={question} question={question}
draggableProps={provided.dragHandleProps} questionIndex={questionIndex}
/> draggableProps={provided.dragHandleProps}
</Box> />
)} </Box>
</ListItem> )}
)} </ListItem>
</Draggable> )}
); </Draggable>
} );
}
); );

@ -24,11 +24,13 @@ import { ChooseAnswerModal } from "./ChooseAnswerModal";
interface Props { interface Props {
question: AnyQuizQuestion; question: AnyQuizQuestion;
questionIndex: number;
draggableProps: DraggableProvidedDragHandleProps | null | undefined; draggableProps: DraggableProvidedDragHandleProps | null | undefined;
} }
export default function QuestionsPageCard({ export default function QuestionsPageCard({
question, question,
questionIndex,
draggableProps, draggableProps,
}: Props) { }: Props) {
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
@ -64,7 +66,7 @@ export default function QuestionsPageCard({
}} }}
> >
<CustomTextField <CustomTextField
placeholder={`Заголовок ${totalIndex + 1} вопроса`} placeholder={`Заголовок ${questionIndex + 1} вопроса`}
text={question.title} text={question.title}
onChange={({ target }) => setTitle(target.value)} onChange={({ target }) => setTitle(target.value)}
sx={{ margin: "20px", width: "auto" }} sx={{ margin: "20px", width: "auto" }}
@ -90,7 +92,7 @@ export default function QuestionsPageCard({
), ),
endAdornment: ( endAdornment: (
<Box {...draggableProps}> <Box {...draggableProps}>
{totalIndex !== 0 && ( {questionIndex !== 0 && (
<InputAdornment position="start"> <InputAdornment position="start">
<PointsIcon <PointsIcon
style={{ color: "#9A9AAF", fontSize: "30px" }} style={{ color: "#9A9AAF", fontSize: "30px" }}

@ -3,10 +3,26 @@ import type { DropResult } from "react-beautiful-dnd";
import { DragDropContext, Droppable } from "react-beautiful-dnd"; import { DragDropContext, Droppable } from "react-beautiful-dnd";
import FormDraggableListItem from "./FormDraggableListItem"; import FormDraggableListItem from "./FormDraggableListItem";
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { reorderQuestions } from "@root/questions/actions"; import { reorderQuestions, setQuestions } from "@root/questions/actions";
import { useCurrentQuiz } from "@root/quizes/hooks";
import useSWR from "swr";
import { questionApi } from "@api/question";
import { devlog } from "@frontend/kitui";
import { isAxiosError } from "axios";
import { enqueueSnackbar } from "notistack";
export const FormDraggableList = () => { export const FormDraggableList = () => {
const { quiz } = useCurrentQuiz();
useSWR(["questions", quiz?.id], ([, id]) => questionApi.getList({ quiz_id: id }), {
onSuccess: setQuestions,
onError: error => {
const message = isAxiosError<string>(error) ? (error.response?.data ?? "") : "";
devlog("Error getting question list", error);
enqueueSnackbar(`Не удалось получить вопросы. ${message}`);
}
});
const questions = useQuestionsStore(state => state.questions); const questions = useQuestionsStore(state => state.questions);
const onDragEnd = ({ destination, source }: DropResult) => { const onDragEnd = ({ destination, source }: DropResult) => {
@ -16,13 +32,13 @@ export const FormDraggableList = () => {
return ( return (
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable-list"> <Droppable droppableId="droppable-list">
{(provided, snapshot) => ( {(provided) => (
<Box ref={provided.innerRef} {...provided.droppableProps}> <Box ref={provided.innerRef} {...provided.droppableProps}>
{questions.map((question, index) => ( {questions.map((question, index) => (
<FormDraggableListItem <FormDraggableListItem
key={index} key={question.id}
index={index} question={question}
isDragging={snapshot.isDraggingOver} questionIndex={index}
questionData={question} questionData={question}
/> />
))} ))}

@ -839,7 +839,6 @@ export default function StartPageSettings() {
quiz.config.startpage.background.video = "https://youtu.be/dbaPkCiLPKQ"; quiz.config.startpage.background.video = "https://youtu.be/dbaPkCiLPKQ";
}); });
incrementCurrentStep(); incrementCurrentStep();
// TODO create new question
}} }}
> >
Настроить вопросы Настроить вопросы

@ -5,7 +5,7 @@ import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/questio
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 { notReachable } from "../../utils/notReachable";
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
import { QuestionsStore, useQuestionsStore } from "./store"; import { QuestionsStore, useQuestionsStore } from "./store";
@ -25,6 +25,13 @@ const setQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
question, question,
}); });
const addQuestion = (question: AnyQuizQuestion) => setProducedState(state => {
state.questions.push(question);
}, {
type: "addQuestion",
question,
});
const removeQuestion = (questionId: number) => setProducedState(state => { const removeQuestion = (questionId: number) => setProducedState(state => {
const index = state.questions.findIndex(q => q.id === questionId); const index = state.questions.findIndex(q => q.id === questionId);
state.questions.splice(index, 1); state.questions.splice(index, 1);
@ -322,7 +329,7 @@ export const createQuestion = async (quizId: number, type: QuestionType = "varia
type, type,
}); });
setQuestion(rawQuestionToQuestion(question)); addQuestion(rawQuestionToQuestion(question));
} catch (error) { } catch (error) {
devlog("Error creating question", error); devlog("Error creating question", error);
enqueueSnackbar("Не удалось создать вопрос"); enqueueSnackbar("Не удалось создать вопрос");

@ -13,6 +13,9 @@ import { createQuestion } from "@root/questions/actions";
export const setEditQuizId = (quizId: number | null) => setProducedState(state => { export const setEditQuizId = (quizId: number | null) => setProducedState(state => {
state.editQuizId = quizId; state.editQuizId = quizId;
}, {
type: "setEditQuizId",
quizId,
}); });
export const resetEditConfig = () => setProducedState(state => { export const resetEditConfig = () => setProducedState(state => {