From bf8a41a3bccb084050856b067c658ec4ceef4fe0 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Fri, 17 Nov 2023 18:42:49 +0300 Subject: [PATCH] fix conflicts --- src/api/question.ts | 20 +- src/constants/emoji.ts | 2 + src/constants/images.ts | 2 + src/constants/select.ts | 3 +- src/constants/variant.ts | 3 +- src/constants/varimg.ts | 3 +- src/model/question/edit.ts | 2 + src/model/question/getList.ts | 4 +- .../AnswerDraggableList/AnswerItem.tsx | 280 +++++++++--------- .../DraggableList/DraggableListItem.tsx | 4 +- .../DraggableList/QuestionPageCard.tsx | 41 +-- .../FormDraggableList/ChooseAnswerModal.tsx | 251 ++++++++-------- .../FormDraggableListItem.tsx | 149 +++++----- .../FormDraggableList/QuestionPageCard.tsx | 6 +- .../Form/FormDraggableList/index.tsx | 26 +- src/pages/startPage/StartPageSettings.tsx | 1 - src/stores/questions/actions.ts | 11 +- src/stores/quizes/actions.ts | 3 + 18 files changed, 415 insertions(+), 396 deletions(-) diff --git a/src/api/question.ts b/src/api/question.ts index 0afed6ed..49ba1259 100644 --- a/src/api/question.ts +++ b/src/api/question.ts @@ -1,10 +1,11 @@ import { makeRequest } from "@frontend/kitui"; import { CreateQuestionRequest } from "model/question/create"; import { RawQuestion } from "model/question/question"; -import { GetQuestionListRequest, GetQuestionListResponse } from "model/question/getList"; -import { EditQuestionRequest, EditQuestionResponse } from "model/question/edit"; -import { DeleteQuestionRequest, DeleteQuestionResponse } from "model/question/delete"; -import { CopyQuestionRequest, CopyQuestionResponse } from "model/question/copy"; +import { GetQuestionListRequest, GetQuestionListResponse } from "@model/question/getList"; +import { EditQuestionRequest, EditQuestionResponse } from "@model/question/edit"; +import { DeleteQuestionRequest, DeleteQuestionResponse } from "@model/question/delete"; +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"; @@ -70,16 +71,11 @@ const defaultCreateQuestionBody: CreateQuestionRequest = { "type": "variant", "required": true, "page": 0, - "content": "string", + "content": JSON.stringify(QUIZ_QUESTION_VARIANT.content), }; const defaultGetQuestionListBody: GetQuestionListRequest = { "limit": 0, "offset": 0, - "from": 0, - "to": 0, - "search": "string", - "type": "string", - "deleted": true, - "required": true, -}; + "type": "", +}; diff --git a/src/constants/emoji.ts b/src/constants/emoji.ts index 937d0a51..bca84035 100644 --- a/src/constants/emoji.ts +++ b/src/constants/emoji.ts @@ -1,6 +1,7 @@ import { QUIZ_QUESTION_BASE } from "./base"; import type { QuizQuestionEmoji } from "../model/questionTypes/emoji"; +import { nanoid } from "nanoid"; export const QUIZ_QUESTION_EMOJI: Omit = { ...QUIZ_QUESTION_BASE, @@ -14,6 +15,7 @@ export const QUIZ_QUESTION_EMOJI: Omit = { required: false, variants: [ { + id: nanoid(), answer: "", extendedText: "", hints: "" diff --git a/src/constants/images.ts b/src/constants/images.ts index f27419b4..2e89ac3b 100644 --- a/src/constants/images.ts +++ b/src/constants/images.ts @@ -1,6 +1,7 @@ import { QUIZ_QUESTION_BASE } from "./base"; import type { QuizQuestionImages } from "../model/questionTypes/images"; +import { nanoid } from "nanoid"; export const QUIZ_QUESTION_IMAGES: Omit = { ...QUIZ_QUESTION_BASE, @@ -17,6 +18,7 @@ export const QUIZ_QUESTION_IMAGES: Omit = { required: false, variants: [ { + id: nanoid(), answer: "", extendedText: "", originalImageUrl: "", diff --git a/src/constants/select.ts b/src/constants/select.ts index 88e9b0f2..6f3a0eba 100644 --- a/src/constants/select.ts +++ b/src/constants/select.ts @@ -1,6 +1,7 @@ import { QUIZ_QUESTION_BASE } from "./base"; import type { QuizQuestionSelect } from "../model/questionTypes/select"; +import { nanoid } from "nanoid"; export const QUIZ_QUESTION_SELECT: Omit = { ...QUIZ_QUESTION_BASE, @@ -12,6 +13,6 @@ export const QUIZ_QUESTION_SELECT: Omit = { innerNameCheck: false, innerName: "", default: "", - variants: [{ answer: "", extendedText: "", hints: "" }], + variants: [{ id: nanoid(), answer: "", extendedText: "", hints: "" }], }, }; diff --git a/src/constants/variant.ts b/src/constants/variant.ts index ed0d6b79..b2147833 100644 --- a/src/constants/variant.ts +++ b/src/constants/variant.ts @@ -1,6 +1,7 @@ import { QUIZ_QUESTION_BASE } from "./base"; import type { QuizQuestionVariant } from "../model/questionTypes/variant"; +import { nanoid } from "nanoid"; export const QUIZ_QUESTION_VARIANT: Omit = { ...QUIZ_QUESTION_BASE, @@ -13,6 +14,6 @@ export const QUIZ_QUESTION_VARIANT: Omit = { innerNameCheck: false, required: false, innerName: "", - variants: [{ answer: "", extendedText: "", hints: "" }], + variants: [{ id: nanoid(), answer: "", extendedText: "", hints: "" }], }, }; diff --git a/src/constants/varimg.ts b/src/constants/varimg.ts index e13080db..07c38a11 100644 --- a/src/constants/varimg.ts +++ b/src/constants/varimg.ts @@ -1,6 +1,7 @@ import { QUIZ_QUESTION_BASE } from "./base"; import type { QuizQuestionVarImg } from "../model/questionTypes/varimg"; +import { nanoid } from "nanoid"; export const QUIZ_QUESTION_VARIMG: Omit = { ...QUIZ_QUESTION_BASE, @@ -11,7 +12,7 @@ export const QUIZ_QUESTION_VARIMG: Omit = { innerNameCheck: false, innerName: "", required: false, - variants: [{ answer: "", hints: "", extendedText: "", originalImageUrl: "" }], + variants: [{ id: nanoid(), answer: "", hints: "", extendedText: "", originalImageUrl: "" }], largeCheck: false, replText: "", }, diff --git a/src/model/question/edit.ts b/src/model/question/edit.ts index ff451bba..7edb9cd8 100644 --- a/src/model/question/edit.ts +++ b/src/model/question/edit.ts @@ -9,6 +9,7 @@ export interface EditQuestionRequest { type?: QuestionType; required?: boolean; page?: number; + content: AnyQuizQuestion["content"]; } export interface EditQuestionResponse { @@ -23,5 +24,6 @@ export function questionToEditQuestionRequest(question: AnyQuizQuestion): EditQu type: question.type, required: question.required, page: question.page, + content: question.content, }; } diff --git a/src/model/question/getList.ts b/src/model/question/getList.ts index 235cc626..738957dc 100644 --- a/src/model/question/getList.ts +++ b/src/model/question/getList.ts @@ -1,4 +1,4 @@ -import { RawQuestion } from "./question"; +import { QuestionType, RawQuestion } from "./question"; export interface GetQuestionListRequest { @@ -13,7 +13,7 @@ export interface GetQuestionListRequest { /** string for fulltext search in titles of questions */ search?: string; /** allow only - text, select, file, variant, images, varimg, emoji, date, number, page, rating or empty string */ - type?: string; + type: "" | QuestionType; /** get deleted quizes */ deleted?: boolean; /** get only require questions */ diff --git a/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx b/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx index 94e284ce..466a2fb7 100644 --- a/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx +++ b/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx @@ -21,153 +21,153 @@ import type { ImageQuestionVariant, QuestionVariant } from "../../../model/quest type AnswerItemProps = { - index: number; - questionId: number; - variant: QuestionVariant | ImageQuestionVariant; - largeCheck: boolean; - additionalContent?: ReactNode; - additionalMobile?: ReactNode; + index: number; + questionId: number; + variant: QuestionVariant | ImageQuestionVariant; + largeCheck: boolean; + additionalContent?: ReactNode; + additionalMobile?: ReactNode; }; export const AnswerItem = ({ - index, - variant, - questionId, - largeCheck, - additionalContent, - additionalMobile, + index, + variant, + questionId, + largeCheck, + additionalContent, + additionalMobile, }: AnswerItemProps) => { - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(790)); - const [isOpen, setIsOpen] = useState(false); - const [anchorEl, setAnchorEl] = useState(null); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(790)); + const [isOpen, setIsOpen] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); - const setQuestionVariantAnswer = useDebouncedCallback((value) => { - setQuestionVariantField(questionId, variant.id,"answer", value); - }, 1000); + const setQuestionVariantAnswer = useDebouncedCallback((value) => { + setQuestionVariantField(questionId, variant.id, "answer", value); + }, 1000); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - setIsOpen(true); - }; + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + setIsOpen(true); + }; - const handleClose = () => { - setIsOpen(false); - }; + const handleClose = () => { + setIsOpen(false); + }; - return ( - - {(provided) => ( - - - setQuestionVariantAnswer(target.value)} - onKeyDown={(event: KeyboardEvent) => { - if (event.code === "Enter" && !largeCheck) { - addQuestionVariant(questionId); - } - }} - InputProps={{ - startAdornment: ( - <> - - - - {additionalContent} - - ), - endAdornment: ( - - - - - - setQuestionVariantField(questionId, variant.id, "hints", e.target.value)} - onKeyDown={( - event: KeyboardEvent - ) => event.stopPropagation()} - /> - - deleteQuestionVariant(questionId, variant.id)} - > - - - - ), - }} - sx={{ - "& .MuiInputBase-root": { - padding: additionalContent ? "5px 13px" : "13px", - borderRadius: "10px", - background: "#ffffff", - "& input.MuiInputBase-input": { - height: "22px", - }, - "& textarea.MuiInputBase-input": { - marginTop: "1px", - }, - "& .MuiOutlinedInput-notchedOutline": { - border: "none", - }, - }, - }} - inputProps={{ - sx: { fontSize: "18px", lineHeight: "21px", py: 0, ml: "13px" }, - "data-cy": "quiz-variant-question-answer", - }} - /> - {additionalMobile} - - - )} - + return ( + + {(provided) => ( + + + setQuestionVariantAnswer(target.value)} + onKeyDown={(event: KeyboardEvent) => { + if (event.code === "Enter" && !largeCheck) { + addQuestionVariant(questionId); + } + }} + InputProps={{ + startAdornment: ( + <> + + + + {additionalContent} + + ), + endAdornment: ( + + + + + + setQuestionVariantField(questionId, variant.id, "hints", e.target.value)} + onKeyDown={( + event: KeyboardEvent + ) => event.stopPropagation()} + /> + + deleteQuestionVariant(questionId, variant.id)} + > + + + + ), + }} + sx={{ + "& .MuiInputBase-root": { + padding: additionalContent ? "5px 13px" : "13px", + borderRadius: "10px", + background: "#ffffff", + "& input.MuiInputBase-input": { + height: "22px", + }, + "& textarea.MuiInputBase-input": { + marginTop: "1px", + }, + "& .MuiOutlinedInput-notchedOutline": { + border: "none", + }, + }, + }} + inputProps={{ + sx: { fontSize: "18px", lineHeight: "21px", py: 0, ml: "13px" }, + "data-cy": "quiz-variant-question-answer", + }} + /> + {additionalMobile} + + + )} + - ); + ); }; diff --git a/src/pages/Questions/DraggableList/DraggableListItem.tsx b/src/pages/Questions/DraggableList/DraggableListItem.tsx index 08d88c7d..19dcaa36 100644 --- a/src/pages/Questions/DraggableList/DraggableListItem.tsx +++ b/src/pages/Questions/DraggableList/DraggableListItem.tsx @@ -1,8 +1,8 @@ +import { AnyQuizQuestion } from "@model/questionTypes/shared"; import { Box, ListItem, Typography, useTheme } from "@mui/material"; import { memo } from "react"; import { Draggable } from "react-beautiful-dnd"; import QuestionsPageCard from "./QuestionPageCard"; -import { AnyQuizQuestion } from "@model/questionTypes/shared"; type Props = { @@ -22,7 +22,7 @@ function DraggableListItem({ question, isDragging, index }: Props) { {...provided.draggableProps} sx={{ userSelect: "none", padding: 0 }} > - {/* questionData.deleted TODO */ true ? ( + {question.deleted ? ( { // TODO update title - // updateQuestionsList(quizId, totalIndex, { title }); + + const setTitle = useDebouncedCallback((title) => { + updateQuestionWithFnOptimistic(question.id, question => { + question.title = title; + }); }, 200); return ( @@ -88,8 +91,8 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging > debounced(target.value)} + placeholder={"Заголовок вопроса2"} + onChange={({ target }) => setTitle(target.value)} InputProps={{ startAdornment: ( diff --git a/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx b/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx index aa956efe..2fb6383b 100644 --- a/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx +++ b/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx @@ -1,148 +1,133 @@ -import { useState } from "react"; -import { useParams } from "react-router-dom"; import { - Box, - Typography, - Popper, - Grow, - Paper, - MenuList, - MenuItem, - ClickAwayListener, - Modal, - Button, - useTheme, + Box, + Button, + ClickAwayListener, + Grow, + MenuItem, + MenuList, + Modal, + Paper, + Popper, + Typography, + useTheme, } from "@mui/material"; - -import { - questionStore, - updateQuestionsList, - removeQuestionForce, - createQuestion, -} from "@root/questions"; +import { useState } from "react"; 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 { updateQuestionWithFnOptimistic } from "@root/questions/actions"; +import type { RefObject } from "react"; +import type { AnyQuizQuestion } from "../../../../model/questionTypes/shared"; + type ChooseAnswerModalProps = { - open: boolean; - onClose: () => void; - anchorRef: RefObject; - totalIndex: number; - switchState: string; + open: boolean; + onClose: () => void; + anchorRef: RefObject; + question: AnyQuizQuestion; + switchState: string; }; export const ChooseAnswerModal = ({ - open, - onClose, - anchorRef, - totalIndex, - switchState, + open, + onClose, + anchorRef, + question, + switchState, }: ChooseAnswerModalProps) => { - const [openModal, setOpenModal] = useState(false); - const [selectedValue, setSelectedValue] = useState("text"); - const quizId = Number(useParams().quizId); - const { listQuestions } = questionStore(); - const theme = useTheme(); + const [openModal, setOpenModal] = useState(false); + const [selectedValue, setSelectedValue] = useState("text"); + const theme = useTheme(); - return ( - <> - - {({ TransitionProps }) => ( - - - - - {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( - { - onClose(); - setOpenModal(true); - setSelectedValue(value); - }, - })} - > - {icon} - + + {({ TransitionProps }) => ( + + + + + {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( + { + onClose(); + setOpenModal(true); + setSelectedValue(value); + }, + })} + > + {icon} + + {title} + + + ))} + + + + + )} + + setOpenModal(false)}> + + + Все настройки, кроме заголовка вопроса будут сброшены + + - {title} - - - ))} - - - - - )} - - setOpenModal(false)}> - - - Все настройки, кроме заголовка вопроса будут сброшены - - - - + - - - - - ); + updateQuestionWithFnOptimistic(question.id, question => { + question.type = selectedValue; + }); + }} + > + Подтвердить + + + + + + ); }; diff --git a/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx b/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx index 348cce0c..8b83777e 100644 --- a/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx +++ b/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx @@ -2,88 +2,87 @@ import { memo } from "react"; import { useParams } from "react-router-dom"; import { Draggable } from "react-beautiful-dnd"; import { Box, ListItem, Typography, useTheme } from "@mui/material"; - import QuestionsPageCard from "./QuestionPageCard"; - import { updateQuestionsList } from "@root/questions"; +import { AnyQuizQuestion, QuizQuestionBase } from "../../../../model/questionTypes/shared"; -import { QuizQuestionBase } from "../../../../model/questionTypes/shared"; type FormDraggableListItemProps = { - index: number; - isDragging: boolean; - questionData: QuizQuestionBase; + question: AnyQuizQuestion; + questionIndex: number; + questionData: QuizQuestionBase; }; export default memo( - ({ index, isDragging, questionData }: FormDraggableListItemProps) => { - const quizId = Number(useParams().quizId); - const theme = useTheme(); + ({ question, questionIndex, questionData }: FormDraggableListItemProps) => { + const quizId = Number(useParams().quizId); + const theme = useTheme(); - return ( - - {(provided) => ( - - {questionData.deleted ? ( - - - Вопрос удалён. - - { - updateQuestionsList(quizId, index, { - ...questionData, - deleted: false, - }); - }} - sx={{ - cursor: "pointer", - fontSize: "16px", - textDecoration: "underline", - color: theme.palette.brightPurple.main, - textDecorationColor: theme.palette.brightPurple.main, - }} - > - Восстановить? - - - ) : ( - - - - )} - - )} - - ); - } + return ( + + {(provided) => ( + + {questionData.deleted ? ( + + + Вопрос удалён. + + { + updateQuestionsList(quizId, questionIndex, { + ...questionData, + deleted: false, + }); + }} + sx={{ + cursor: "pointer", + fontSize: "16px", + textDecoration: "underline", + color: theme.palette.brightPurple.main, + textDecorationColor: theme.palette.brightPurple.main, + }} + > + Восстановить? + + + ) : ( + + + + )} + + )} + + ); + } ); diff --git a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx index 789306d2..e0881171 100644 --- a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx @@ -24,11 +24,13 @@ import { ChooseAnswerModal } from "./ChooseAnswerModal"; interface Props { question: AnyQuizQuestion; + questionIndex: number; draggableProps: DraggableProvidedDragHandleProps | null | undefined; } export default function QuestionsPageCard({ question, + questionIndex, draggableProps, }: Props) { const [open, setOpen] = useState(false); @@ -64,7 +66,7 @@ export default function QuestionsPageCard({ }} > setTitle(target.value)} sx={{ margin: "20px", width: "auto" }} @@ -90,7 +92,7 @@ export default function QuestionsPageCard({ ), endAdornment: ( - {totalIndex !== 0 && ( + {questionIndex !== 0 && ( { + const { quiz } = useCurrentQuiz(); + useSWR(["questions", quiz?.id], ([, id]) => questionApi.getList({ quiz_id: id }), { + onSuccess: setQuestions, + onError: error => { + const message = isAxiosError(error) ? (error.response?.data ?? "") : ""; + + devlog("Error getting question list", error); + enqueueSnackbar(`Не удалось получить вопросы. ${message}`); + } + }); const questions = useQuestionsStore(state => state.questions); const onDragEnd = ({ destination, source }: DropResult) => { @@ -16,13 +32,13 @@ export const FormDraggableList = () => { return ( - {(provided, snapshot) => ( + {(provided) => ( {questions.map((question, index) => ( ))} diff --git a/src/pages/startPage/StartPageSettings.tsx b/src/pages/startPage/StartPageSettings.tsx index 618d234d..a0182a5e 100755 --- a/src/pages/startPage/StartPageSettings.tsx +++ b/src/pages/startPage/StartPageSettings.tsx @@ -839,7 +839,6 @@ export default function StartPageSettings() { quiz.config.startpage.background.video = "https://youtu.be/dbaPkCiLPKQ"; }); incrementCurrentStep(); - // TODO create new question }} > Настроить вопросы diff --git a/src/stores/questions/actions.ts b/src/stores/questions/actions.ts index 152420cc..7b5587dd 100644 --- a/src/stores/questions/actions.ts +++ b/src/stores/questions/actions.ts @@ -5,7 +5,7 @@ import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/questio import { AnyQuizQuestion, ImageQuestionVariant, QuestionVariant, createQuestionImageVariant, createQuestionVariant } from "@model/questionTypes/shared"; import { produce } from "immer"; import { enqueueSnackbar } from "notistack"; -import { notReachable } from "utils/notReachable"; +import { notReachable } from "../../utils/notReachable"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { QuestionsStore, useQuestionsStore } from "./store"; @@ -25,6 +25,13 @@ const setQuestion = (question: AnyQuizQuestion) => setProducedState(state => { question, }); +const addQuestion = (question: AnyQuizQuestion) => setProducedState(state => { + state.questions.push(question); +}, { + type: "addQuestion", + question, +}); + const removeQuestion = (questionId: number) => setProducedState(state => { const index = state.questions.findIndex(q => q.id === questionId); state.questions.splice(index, 1); @@ -322,7 +329,7 @@ export const createQuestion = async (quizId: number, type: QuestionType = "varia type, }); - setQuestion(rawQuestionToQuestion(question)); + addQuestion(rawQuestionToQuestion(question)); } catch (error) { devlog("Error creating question", error); enqueueSnackbar("Не удалось создать вопрос"); diff --git a/src/stores/quizes/actions.ts b/src/stores/quizes/actions.ts index 387dcefd..b908f936 100644 --- a/src/stores/quizes/actions.ts +++ b/src/stores/quizes/actions.ts @@ -13,6 +13,9 @@ import { createQuestion } from "@root/questions/actions"; export const setEditQuizId = (quizId: number | null) => setProducedState(state => { state.editQuizId = quizId; +}, { + type: "setEditQuizId", + quizId, }); export const resetEditConfig = () => setProducedState(state => {