Merge branch 'dev' into backend-integration
This commit is contained in:
commit
2a9ad65093
@ -1,4 +1,4 @@
|
||||
FROM node:19.1-alpine as build
|
||||
FROM node:20.10-alpine3.18 as build
|
||||
|
||||
RUN apk update && rm -rf /var/cache/apk/*
|
||||
WORKDIR /usr/app
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-easy-crop": "^5.0.0",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-image-crop": "^10.1.5",
|
||||
"react-image-file-resizer": "^0.4.8",
|
||||
"react-rnd": "^10.4.1",
|
||||
|
||||
10
src/App.tsx
10
src/App.tsx
@ -11,11 +11,11 @@ import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
|
||||
import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
|
||||
import Landing from "./pages/Landing/Landing";
|
||||
import QuestionsPage from "./pages/Questions/QuestionsPage";
|
||||
import { Result } from "./pages/Result/Result";
|
||||
import { Setting } from "./pages/Result/Setting";
|
||||
import { Result } from "./pages/ResultPage/Result";
|
||||
import { ResultSettings } from "./pages/ResultPage/ResultSettings";
|
||||
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
|
||||
import Main from "./pages/main";
|
||||
import StartPage from "./pages/startPage/StartPage";
|
||||
import EditPage from "./pages/startPage/EditPage";
|
||||
import { clearAuthToken, getMessageFromFetchError, useUserFetcher } from "@frontend/kitui";
|
||||
import { clearUserData, setUser, useUserStore } from "@root/user";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
@ -28,7 +28,7 @@ const routeslink = [
|
||||
{ path: "/questions/:quizId", page: <QuestionsPage />, header: true, sidebar: true, },
|
||||
{ path: "/contacts", page: <ContactFormPage />, header: true, sidebar: true },
|
||||
{ path: "/result", page: <Result />, header: true, sidebar: true },
|
||||
{ path: "/settings", page: <Setting />, header: true, sidebar: true },
|
||||
{ path: "/settings", page: <ResultSettings />, header: true, sidebar: true },
|
||||
{ path: "/install", page: <InstallQuiz />, header: true, sidebar: true },
|
||||
] as const;
|
||||
|
||||
@ -57,7 +57,7 @@ export default function App() {
|
||||
{routeslink.map((e, i) => (
|
||||
<Route key={i} path={e.path} element={<Main page={e.page} header={e.header} sidebar={e.sidebar} />} />
|
||||
))}
|
||||
<Route path="edit" element={<StartPage />} />
|
||||
<Route path="edit" element={<EditPage />} />
|
||||
<Route path="crop" element={<ImageCrop />} />
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route path="/signin" element={<SigninDialog />} />
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 823 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
18
src/assets/icons/ExpandLessIconBG.tsx
Normal file
18
src/assets/icons/ExpandLessIconBG.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { useTheme, SxProps, Box } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
export default function ExpandIcon({ sx }: Props) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box sx={{ ...sx }}>
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="30" height="30" rx="6" fill="#EEE4FC" />
|
||||
<path d="M22.5 11.25L15 18.75L7.5 11.25" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 -2.62268e-07C21.3137 -1.17422e-07 24 2.68629 24 6L24 18C24 21.3137 21.3137 24 18 24L6 24C2.68629 24 -9.31652e-07 21.3137 -7.86805e-07 18L-5.24537e-07 12L-2.62268e-07 6C-1.17422e-07 2.68629 2.68629 -9.31652e-07 6 -7.86805e-07L18 -2.62268e-07Z" fill="#9A9AAF" fill-opacity="0.7"/>
|
||||
<path d="M7 11.5L11.2857 15.5L17 8" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 11.5L11.2857 15.5L17 8" stroke="white" stroke-linecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 495 B After Width: | Height: | Size: 494 B |
@ -11,6 +11,7 @@ import { QUIZ_QUESTION_SELECT } from "./select";
|
||||
import { QUIZ_QUESTION_TEXT } from "./text";
|
||||
import { QUIZ_QUESTION_VARIANT } from "./variant";
|
||||
import { QUIZ_QUESTION_VARIMG } from "./varimg";
|
||||
import { QUIZ_QUESTION_RESULT } from "./result";
|
||||
|
||||
|
||||
export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuestion, "id" | "backendId">> = {
|
||||
@ -25,4 +26,5 @@ export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuesti
|
||||
"text": QUIZ_QUESTION_TEXT,
|
||||
"variant": QUIZ_QUESTION_VARIANT,
|
||||
"varimg": QUIZ_QUESTION_VARIMG,
|
||||
"result": QUIZ_QUESTION_RESULT,
|
||||
} as const;
|
||||
|
||||
17
src/constants/result.ts
Normal file
17
src/constants/result.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionResult } from "../model/questionTypes/result";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const QUIZ_QUESTION_RESULT: Omit<QuizQuestionResult, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "result",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
video: "",
|
||||
innerName: "",
|
||||
text: "",
|
||||
price: [0],
|
||||
rangePrice: false
|
||||
},
|
||||
};
|
||||
@ -14,7 +14,8 @@ export type QuestionType =
|
||||
| "number"
|
||||
| "file"
|
||||
| "page"
|
||||
| "rating";
|
||||
| "rating"
|
||||
| "result";
|
||||
|
||||
/** Type that comes from server */
|
||||
export interface RawQuestion {
|
||||
|
||||
22
src/model/questionTypes/result.ts
Normal file
22
src/model/questionTypes/result.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type {
|
||||
QuizQuestionBase,
|
||||
QuestionBranchingRule,
|
||||
QuestionHint,
|
||||
} from "./shared";
|
||||
|
||||
export interface QuizQuestionResult extends QuizQuestionBase {
|
||||
type: "result";
|
||||
content: {
|
||||
id: string;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
video: string;
|
||||
innerName: string;
|
||||
text: string;
|
||||
price: [number] | [number, number];
|
||||
rangePrice: boolean;
|
||||
rule: QuestionBranchingRule,
|
||||
hint: QuestionHint;
|
||||
autofill: boolean;
|
||||
};
|
||||
}
|
||||
@ -10,6 +10,7 @@ import type { QuizQuestionSelect } from "./select";
|
||||
import type { QuizQuestionText } from "./text";
|
||||
import type { QuizQuestionVariant } from "./variant";
|
||||
import type { QuizQuestionVarImg } from "./varimg";
|
||||
import type { QuizQuestionResult } from "./result";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export interface QuestionBranchingRuleMain {
|
||||
@ -92,7 +93,8 @@ export type AnyTypedQuizQuestion =
|
||||
| QuizQuestionNumber
|
||||
| QuizQuestionFile
|
||||
| QuizQuestionPage
|
||||
| QuizQuestionRating;
|
||||
| QuizQuestionRating
|
||||
| QuizQuestionResult;
|
||||
|
||||
type FilterQuestionsWithVariants<T> = T extends {
|
||||
content: { variants: QuestionVariant[]; };
|
||||
|
||||
@ -33,6 +33,14 @@ export interface QuizConfig {
|
||||
startpageType: QuizStartpageType;
|
||||
results: QuizResultsType;
|
||||
haveRoot: string | null;
|
||||
resultInfo: {
|
||||
when: 'before' | 'after' | 'email',
|
||||
share: true | false,
|
||||
replay: true | false,
|
||||
theme: string,
|
||||
reply: string,
|
||||
replname: string,
|
||||
}
|
||||
startpage: {
|
||||
description: string;
|
||||
button: string;
|
||||
@ -66,6 +74,14 @@ export const defaultQuizConfig: QuizConfig = {
|
||||
startpageType: null,
|
||||
results: null,
|
||||
haveRoot: null,
|
||||
resultInfo: {
|
||||
when: 'after',
|
||||
share: false,
|
||||
replay: false,
|
||||
theme: "",
|
||||
reply: "",
|
||||
replname: "",
|
||||
},
|
||||
startpage: {
|
||||
description: "",
|
||||
button: "",
|
||||
|
||||
@ -6,7 +6,8 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { updateRootContentId } from "@root/quizes/actions"
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId } from "@root/questions/actions";
|
||||
import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId, clearRuleForAll } from "@root/questions/actions";
|
||||
import { withErrorBoundary } from "react-error-boundary";
|
||||
|
||||
import { storeToNodes } from "./helper";
|
||||
|
||||
@ -19,8 +20,9 @@ import type {
|
||||
AbstractEventObject,
|
||||
ElementDefinition,
|
||||
} from "cytoscape";
|
||||
import { QuestionsList } from "../BranchingPanel/QuestionsList";
|
||||
import { QuestionsList } from "../SwitchBranchingPanel/QuestionsList";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { Typography } from "@mui/material";
|
||||
|
||||
type PopperItem = {
|
||||
id: () => string;
|
||||
@ -65,6 +67,14 @@ const stylesheet: Stylesheet[] = [
|
||||
"text-max-width": "80",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: "[?eroticeyeblink]",
|
||||
style: {
|
||||
"border-width": "4px",
|
||||
"border-style": "solid",
|
||||
"border-color": "#7e2aea",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: ".multiline-auto",
|
||||
style: {
|
||||
@ -103,16 +113,18 @@ interface Props {
|
||||
}
|
||||
|
||||
|
||||
export const CsComponent = ({
|
||||
function CsComponent ({
|
||||
modalQuestionParentContentId,
|
||||
modalQuestionTargetContentId,
|
||||
setOpenedModalQuestions,
|
||||
setModalQuestionParentContentId,
|
||||
setModalQuestionTargetContentId
|
||||
}: Props) => {
|
||||
}: Props) {
|
||||
const quiz = useCurrentQuiz();
|
||||
|
||||
const { dragQuestionContentId, questions } = useQuestionsStore()
|
||||
const { dragQuestionContentId, desireToOpenABranchingModal } = useQuestionsStore()
|
||||
const trashQuestions = useQuestionsStore().questions
|
||||
const questions = trashQuestions.filter((question) => question.type !== "result")
|
||||
const [startCreate, setStartCreate] = useState("");
|
||||
const [startRemove, setStartRemove] = useState("");
|
||||
|
||||
@ -122,16 +134,27 @@ export const CsComponent = ({
|
||||
const crossesContainer = useRef<HTMLDivElement | null>(null);
|
||||
const gearsContainer = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
updateOpenedModalSettingsId()
|
||||
}, [])
|
||||
useLayoutEffect(() => {
|
||||
const cy = cyRef?.current
|
||||
if (desireToOpenABranchingModal) {
|
||||
setTimeout(() => {
|
||||
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true)
|
||||
}, 250)
|
||||
} else {
|
||||
cy?.elements().data("eroticeyeblink", false)
|
||||
}
|
||||
}, [desireToOpenABranchingModal])
|
||||
useLayoutEffect(() => {
|
||||
updateOpenedModalSettingsId()
|
||||
// updateRootContentId(quiz.id, "")
|
||||
// clearRuleForAll()
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
|
||||
addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId })
|
||||
}
|
||||
setModalQuestionParentContentId("")
|
||||
setModalQuestionTargetContentId("")
|
||||
|
||||
}, [modalQuestionTargetContentId])
|
||||
|
||||
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
|
||||
@ -211,6 +234,7 @@ export const CsComponent = ({
|
||||
question.content.rule.main = []
|
||||
question.content.rule.default = ""
|
||||
})
|
||||
clearRuleForAll()
|
||||
} else {
|
||||
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
|
||||
if (targetNodeContentId && parentQuestionContentId) {
|
||||
@ -248,11 +272,21 @@ export const CsComponent = ({
|
||||
question.content.rule.default = ""
|
||||
})
|
||||
|
||||
updateQuestion(parentQuestionContentId, question => {
|
||||
//Заменяем id удаляемого вопроса либо на id одного из оставшихся, либо на пустую строку
|
||||
if (question.content.rule.default === targetQuestionContentId) question.content.rule.default = ""
|
||||
})
|
||||
//чистим rule родителя
|
||||
const parentQuestion = getQuestionByContentId(parentQuestionContentId)
|
||||
const newRule = {}
|
||||
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу
|
||||
newRule.parentId = parentQuestion.content.rule.parentId
|
||||
newRule.default = questions.filter((q) => {
|
||||
return q.content.rule.parentId === parentQuestionContentId && q.content.id !== targetQuestionContentId
|
||||
})[0]?.content.id || ""
|
||||
//Если этот вопрос был дефолтным у родителя - чистим дефолт
|
||||
//Смотрим можем ли мы заменить id на один из main
|
||||
|
||||
console.log(newRule)
|
||||
updateQuestion(parentQuestionContentId, (PQ) => {
|
||||
PQ.content.rule = newRule
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -390,6 +424,7 @@ export const CsComponent = ({
|
||||
const cy = cyRef.current;
|
||||
const eles = cy?.add(storeToNodes(questions))
|
||||
cy.data('changed', true)
|
||||
// cy.data('changed', true)
|
||||
const elecs = eles.layout(lyopts).run()
|
||||
cy?.on('add', () => cy.data('changed', true))
|
||||
cy?.fit()
|
||||
@ -665,8 +700,9 @@ export const CsComponent = ({
|
||||
cy={(cy) => {
|
||||
cyRef.current = cy;
|
||||
}}
|
||||
// autolock
|
||||
/>
|
||||
{/* <button onClick={() => {
|
||||
<button onClick={() => {
|
||||
console.log("NODES____________________________")
|
||||
cyRef.current?.elements().forEach((ele: any) => {
|
||||
console.log(ele.data())
|
||||
@ -675,7 +711,23 @@ export const CsComponent = ({
|
||||
<button onClick={() => {
|
||||
console.log("ELEMENTS____________________________")
|
||||
console.log(questions)
|
||||
}}>elements</button> */}
|
||||
}}>elements</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function Clear () {
|
||||
const quiz = useCurrentQuiz();
|
||||
updateRootContentId(quiz.id, "")
|
||||
clearRuleForAll()
|
||||
return <></>
|
||||
}
|
||||
|
||||
export default withErrorBoundary(CsComponent, {
|
||||
fallback: <Clear/>,
|
||||
onError: (error, info) => {
|
||||
enqueueSnackbar("Дерево порвалось")
|
||||
console.log(info)
|
||||
console.log(error)
|
||||
},
|
||||
});
|
||||
@ -12,7 +12,7 @@ interface Props {
|
||||
}
|
||||
export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => {
|
||||
const quiz = useCurrentQuiz();
|
||||
const { dragQuestionContentId, questions } = useQuestionsStore()
|
||||
const { dragQuestionContentId } = useQuestionsStore()
|
||||
const Container = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const modalOpen = () => setOpenedModalQuestions(true)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { FirstNodeField } from "./FirstNodeField";
|
||||
import { CsComponent } from "./CsComponent";
|
||||
import CsComponent from "./CsComponent";
|
||||
import { useQuestionsStore } from "@root/questions/store"
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useState } from "react";
|
||||
|
||||
@ -290,7 +290,12 @@ const DateInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQu
|
||||
}
|
||||
<DatePicker
|
||||
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]}
|
||||
onChange={(e) => console.log(e)}
|
||||
onChange={(dateString) => {
|
||||
const date = dateString?.$d?.toLocaleDateString("ru-RU", { year: "numeric", month: "2-digit", day: "2-digit" });
|
||||
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion))
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [date]
|
||||
// setParentQuestion(newParentQuestion)
|
||||
}}
|
||||
slots={{
|
||||
openPickerIcon: () => <CalendarIcon />,
|
||||
}}
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import { Box, Typography, Switch, useTheme } from "@mui/material";
|
||||
import {Box, Typography, Switch, useTheme, Button, useMediaQuery, SxProps, Theme} from "@mui/material";
|
||||
|
||||
import { QuestionsList } from "./QuestionsList";
|
||||
import { updateOpenBranchingPanel } from "@root/questions/actions";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import {useRef} from "react";
|
||||
|
||||
|
||||
export const BranchingPanel = () => {
|
||||
export const BranchingPanel = (sx?: SxProps<Theme>) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
const ref = useRef()
|
||||
return (
|
||||
<Box sx={{ userSelect: "none", maxWidth: "350px", width: "100%" }}>
|
||||
<Box
|
||||
@ -19,12 +22,13 @@ export const BranchingPanel = () => {
|
||||
background: "#fff",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
...sx
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
value={openBranchingPanel}
|
||||
onChange={(_, value) => {
|
||||
updateOpenBranchingPanel(!value)
|
||||
updateOpenBranchingPanel(value)
|
||||
}}
|
||||
sx={{
|
||||
width: 50,
|
||||
@ -71,7 +75,7 @@ export const BranchingPanel = () => {
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
|
||||
<Typography ref={ref} sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
|
||||
Логика ветвления
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontSize: "12px" }}>
|
||||
@ -79,7 +83,7 @@ export const BranchingPanel = () => {
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
{ openBranchingPanel === true && <QuestionsList /> }
|
||||
{ openBranchingPanel && <QuestionsList /> }
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -15,7 +15,8 @@ export const BranchingQuestionsModal = ({
|
||||
setModalQuestionTargetContentId,
|
||||
setModalQuestionParentContentId
|
||||
}: Props) => {
|
||||
const { questions } = useQuestionsStore();
|
||||
const trashQuestions = useQuestionsStore().questions
|
||||
const questions = trashQuestions.filter((question) => question.type !== "result")
|
||||
|
||||
const handleClose = () => {
|
||||
setOpenedModalQuestions(false);
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import {copyQuestion, deleteQuestion, updateOpenBranchingPanel, updateQuestion} from "@root/questions/actions";
|
||||
import { copyQuestion, deleteQuestion, updateOpenBranchingPanel, updateDesireToOpenABranchingModal } from "@root/questions/actions";
|
||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "../../assets/icons/questionsPage/branching";
|
||||
@ -19,10 +19,9 @@ import Clue from "../../assets/icons/questionsPage/clue";
|
||||
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
|
||||
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||
import { updateOpenedModalSettingsId } from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import {enqueueSnackbar} from "notistack";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
|
||||
|
||||
interface Props {
|
||||
@ -42,25 +41,7 @@ export default function ButtonsOptions({
|
||||
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||
const quiz = useCurrentQuiz();
|
||||
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
const openedModal = () => {
|
||||
updateOpenedModalSettingsId(question.id)
|
||||
};
|
||||
|
||||
const handleClickBranching = (_, value) => {
|
||||
const parentId = question.content.rule.parentId
|
||||
if (parentId.length === 0 ){
|
||||
return enqueueSnackbar("Вопрос не учавствует в ветвлении")
|
||||
}
|
||||
if (parentId === "root") {
|
||||
return enqueueSnackbar("У корня нет условий ветвления")
|
||||
}
|
||||
if (parentId.length !== 0) {
|
||||
updateOpenBranchingPanel(!value)
|
||||
openedModal()
|
||||
}
|
||||
|
||||
}
|
||||
const { openBranchingPanel } = useQuestionsStore.getState()
|
||||
|
||||
const buttonSetting: {
|
||||
icon: JSX.Element;
|
||||
@ -98,7 +79,10 @@ const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
),
|
||||
title: "Ветвление",
|
||||
value: "branching",
|
||||
myFunc: () => handleClickBranching(question.id, openBranchingPanel),
|
||||
myFunc: (question) => {
|
||||
updateOpenBranchingPanel(true)
|
||||
updateDesireToOpenABranchingModal(question.content.id)
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
@ -174,7 +158,7 @@ const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
key={title}
|
||||
onClick={() => {
|
||||
SSHC(value);
|
||||
myFunc();
|
||||
myFunc(question);
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
|
||||
@ -23,8 +23,8 @@ import ImgIcon from "../../assets/icons/questionsPage/imgIcon";
|
||||
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||
import { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
import { updateOpenedModalSettingsId } from "@root/questions/actions";
|
||||
import { updateOpenBranchingPanel } from "@root/questions/actions";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import { updateOpenBranchingPanel, updateDesireToOpenABranchingModal } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
@ -46,7 +46,7 @@ export default function ButtonsOptionsAndPict({
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isIconMobile = useMediaQuery(theme.breakpoints.down(1050));
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
const { openBranchingPanel } = useQuestionsStore.getState()
|
||||
const quiz = useCurrentQuiz();
|
||||
|
||||
useEffect(() => {
|
||||
@ -55,22 +55,6 @@ export default function ButtonsOptionsAndPict({
|
||||
}
|
||||
}, [question]);
|
||||
|
||||
const handleClickBranching = (_, value) => {
|
||||
|
||||
const parentId = question.content.rule.parentId
|
||||
if (parentId.length === 0 ) {
|
||||
return enqueueSnackbar("Вопрос не учавствует в ветвлении")
|
||||
}
|
||||
if (parentId === "root") {
|
||||
return enqueueSnackbar("У корня нет условий ветвления")
|
||||
}
|
||||
if (parentId.length !== 0) {
|
||||
updateOpenBranchingPanel(!value)
|
||||
updateOpenedModalSettingsId(question.id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -206,7 +190,8 @@ export default function ButtonsOptionsAndPict({
|
||||
onMouseEnter={() => setButtonHover("branching")}
|
||||
onMouseLeave={() => setButtonHover("")}
|
||||
onClick={() => {
|
||||
handleClickBranching(question.id, openBranchingPanel)
|
||||
updateOpenBranchingPanel(true)
|
||||
updateDesireToOpenABranchingModal(question.content.id)
|
||||
}}
|
||||
sx={{
|
||||
height: "30px",
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { AnyTypedQuizQuestion, UntypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { Box, ListItem, Typography, useTheme } from "@mui/material";
|
||||
import { memo } from "react";
|
||||
import { memo, useEffect } from "react";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import QuestionsPageCard from "./QuestionPageCard";
|
||||
import { updateEditSomeQuestion } from "@root/questions/actions"
|
||||
import { useQuestionsStore } from "@root/questions/store"
|
||||
|
||||
|
||||
type Props = {
|
||||
@ -13,6 +15,23 @@ type Props = {
|
||||
|
||||
function DraggableListItem({ question, isDragging, index }: Props) {
|
||||
const theme = useTheme();
|
||||
const { editSomeQuestion } = useQuestionsStore()
|
||||
|
||||
useEffect(() => {
|
||||
if (editSomeQuestion !== null) {
|
||||
const setI = setInterval(() => {
|
||||
let comp = document.getElementById(editSomeQuestion)
|
||||
console.log(comp)
|
||||
if(comp !== null) {
|
||||
clearInterval(setI)
|
||||
comp.scrollIntoView({behavior: 'instant'})
|
||||
updateEditSomeQuestion()
|
||||
}
|
||||
}, 200)
|
||||
|
||||
}
|
||||
console.log(editSomeQuestion)
|
||||
}, [editSomeQuestion])
|
||||
|
||||
return (
|
||||
<Draggable draggableId={question.id.toString()} index={index}>
|
||||
|
||||
@ -68,6 +68,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
|
||||
return (
|
||||
<>
|
||||
<Paper
|
||||
id={question.id}
|
||||
data-cy="quiz-question-card"
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
|
||||
@ -1,26 +1,26 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { reorderQuestions } from "@root/questions/actions";
|
||||
import { useQuestions } from "@root/questions/hooks";
|
||||
import type { DropResult } from "react-beautiful-dnd";
|
||||
import { DragDropContext, Droppable } from "react-beautiful-dnd";
|
||||
import DraggableListItem from "./DraggableListItem";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
|
||||
|
||||
export const DraggableList = () => {
|
||||
const { questions, isLoading } = useQuestions();
|
||||
|
||||
const { questions } = useQuestionsStore()
|
||||
const filteredQuestions = questions.filter((question) => question.type !== "result")
|
||||
console.log(questions)
|
||||
console.log(filteredQuestions)
|
||||
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||
if (destination) reorderQuestions(source.index, destination.index);
|
||||
};
|
||||
|
||||
if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="droppable-list">
|
||||
{(provided, snapshot) => (
|
||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{questions.map((question, index) => (
|
||||
{filteredQuestions.map((question, index) => (
|
||||
<DraggableListItem
|
||||
key={question.id}
|
||||
question={question}
|
||||
|
||||
@ -3,21 +3,20 @@ import {
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { DraggableList } from "./DraggableList";
|
||||
import { BranchingPanel } from "./BranchingPanel";
|
||||
import { SwitchBranchingPanel } from "./SwitchBranchingPanel";
|
||||
import { BranchingMap } from "./BranchingMap";
|
||||
import { updateOpenBranchingPanel } from "@root/questions/actions";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
|
||||
|
||||
|
||||
export const QuestionSwitchWindowTool = () => {
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
console.log(openBranchingPanel)
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: "20px", flexWrap: "wrap" }}>
|
||||
<Box sx={{ flexBasis: "796px" }}>
|
||||
{openBranchingPanel? <BranchingMap /> : <DraggableList />}
|
||||
</Box>
|
||||
<BranchingPanel
|
||||
<SwitchBranchingPanel
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useLayoutEffect } from "react"
|
||||
import { useState, useEffect, useLayoutEffect, useRef } from "react"
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -17,25 +17,29 @@ import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
||||
import BranchingQuestions from "./BranchingModal/BranchingQuestionsModal"
|
||||
import { QuestionSwitchWindowTool } from "./QuestionSwitchWindowTool";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateOpenBranchingPanel } from "@root/questions/actions";
|
||||
import { updateOpenBranchingPanel, updateEditSomeQuestion } from "@root/questions/actions";
|
||||
|
||||
|
||||
export default function QuestionsPage() {
|
||||
const theme = useTheme();
|
||||
const { openedModalSettingsId } = useQuestionsStore();
|
||||
const { openedModalSettingsId, openBranchingPanel } = useQuestionsStore();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||
const quiz = useCurrentQuiz();
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
console.log(quiz)
|
||||
useLayoutEffect(() => {
|
||||
updateOpenBranchingPanel(true)
|
||||
updateOpenBranchingPanel(false)
|
||||
updateEditSomeQuestion()
|
||||
},[])
|
||||
|
||||
const ref = useRef()
|
||||
if (!quiz) return null;
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
ref={ref}
|
||||
id="QuestionsPage"
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
width: "100%",
|
||||
@ -44,7 +48,8 @@ export default function QuestionsPage() {
|
||||
margin: "60px 0 40px 0",
|
||||
}}
|
||||
>
|
||||
<Typography variant={"h5"}>Заголовок квиза</Typography>
|
||||
<Typography variant={"h5"}>{
|
||||
quiz.name ? quiz.name : "Заголовок квиза" }</Typography>
|
||||
<Button
|
||||
sx={{
|
||||
display: openBranchingPanel ? "none" : "flex",
|
||||
@ -81,6 +86,8 @@ export default function QuestionsPage() {
|
||||
>
|
||||
<AddPlus />
|
||||
</IconButton>
|
||||
|
||||
|
||||
<Box sx={{ display: "flex", gap: "8px", marginLeft: "auto" }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
@ -109,4 +116,4 @@ export default function QuestionsPage() {
|
||||
{openedModalSettingsId !== null && <BranchingQuestions/>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,15 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import { Box, Button, IconButton, Typography } from "@mui/material";
|
||||
import { ReactComponent as CheckedIcon } from "@icons/checked.svg";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateDragQuestionContentId } from "@root/questions/actions";
|
||||
import { useEffect } from "react";
|
||||
import { AnyTypedQuizQuestion, UntypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { Pencil } from "../../startPage/Sidebar/icons/Pencil";
|
||||
import {updateOpenBranchingPanel, updateEditSomeQuestion} from "@root/questions/actions"
|
||||
|
||||
|
||||
const getItemStyle = (isDragging:any, draggableStyle:any) => ({
|
||||
const getItemStyle = (isDragging: any, draggableStyle: any) => ({
|
||||
// some basic styles to make the items look a bit nicer
|
||||
userSelect: "none",
|
||||
padding: 5 * 2,
|
||||
@ -22,7 +24,9 @@ const getItemStyle = (isDragging:any, draggableStyle:any) => ({
|
||||
type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion
|
||||
|
||||
export const QuestionsList = () => {
|
||||
const { questions } = useQuestionsStore()
|
||||
const { desireToOpenABranchingModal } = useQuestionsStore()
|
||||
const trashQuestions = useQuestionsStore().questions
|
||||
const questions = trashQuestions.filter((question) => question.type !== "result")
|
||||
|
||||
return (
|
||||
<Box sx={{ padding: "15px" }}>
|
||||
@ -51,11 +55,11 @@ export const QuestionsList = () => {
|
||||
}}
|
||||
>
|
||||
{/* тут нужно будет фильтровать с проверкой, что вопрос имеет тип*/}
|
||||
{questions.filter((q:AnyQuestion) => q.type).map(({ title, id, content }, index) => (
|
||||
<Button
|
||||
onMouseDown={() => {//Разрешаем добавить этот вопрос если у него нет родителя (не добавляли ещё в дерево)
|
||||
if (!content.rule.parentId) updateDragQuestionContentId(content.id)
|
||||
}}
|
||||
{questions.filter((q: AnyQuestion) => q.type).map(({ title, content }, index) => (
|
||||
<Button
|
||||
onMouseDown={() => {//Разрешаем добавить этот вопрос если у него нет родителя (не добавляли ещё в дерево)
|
||||
if (!content.rule.parentId) updateDragQuestionContentId(content.id)
|
||||
}}
|
||||
key={index}
|
||||
sx={{
|
||||
width: "100%",
|
||||
@ -66,6 +70,7 @@ export const QuestionsList = () => {
|
||||
padding: "12px",
|
||||
background: "#FFFFFF",
|
||||
borderRadius: "8px",
|
||||
border: desireToOpenABranchingModal === content.id ? "4px solid #7e2aea" : "none",
|
||||
marginBottom: "20px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
backgroundImage: `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='rgb(154, 154, 175)' stroke-width='2' stroke-dasharray='8 8' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
|
||||
@ -73,13 +78,21 @@ export const QuestionsList = () => {
|
||||
"&:last-child": { marginBottom: 0 },
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ width: "100%", color: content.rule.parentId ? "#9A9AAF" : "#000" }}>
|
||||
<Typography sx={{ width: "100%", color: content.rule.parentId ? "#9A9AAF" : "#000" }}>
|
||||
{title || "нет заголовка"}
|
||||
</Typography>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
updateOpenBranchingPanel(false)
|
||||
updateEditSomeQuestion(content.id)
|
||||
}}
|
||||
>
|
||||
<Pencil style={{color: "#7e2aea"}}/>
|
||||
</IconButton>
|
||||
{content.rule.parentId && <CheckedIcon />}
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
91
src/pages/Questions/SwitchBranchingPanel/index.tsx
Normal file
91
src/pages/Questions/SwitchBranchingPanel/index.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import {Box, Typography, Switch, useTheme, Button, useMediaQuery} from "@mui/material";
|
||||
|
||||
import { QuestionsList } from "./QuestionsList";
|
||||
import { updateOpenBranchingPanel } from "@root/questions/actions";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import {useRef} from "react";
|
||||
|
||||
|
||||
export const SwitchBranchingPanel = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
console.log(openBranchingPanel)
|
||||
const ref = useRef()
|
||||
return (
|
||||
<Box sx={{ userSelect: "none", maxWidth: "350px", width: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
padding: "18px",
|
||||
background: "#fff",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
value={openBranchingPanel}
|
||||
onChange={(_, value) => {
|
||||
console.log("меняю на " + value)
|
||||
updateOpenBranchingPanel(value)
|
||||
}}
|
||||
sx={{
|
||||
width: 50,
|
||||
height: 30,
|
||||
padding: 0,
|
||||
"& .MuiSwitch-switchBase": {
|
||||
padding: 0,
|
||||
margin: "2px",
|
||||
transitionDuration: "300ms",
|
||||
"&.Mui-checked": {
|
||||
transform: "translateX(20px)",
|
||||
color: theme.palette.brightPurple.main,
|
||||
"& + .MuiSwitch-track": {
|
||||
backgroundColor: "#E8DCF9",
|
||||
opacity: 1,
|
||||
border: 0,
|
||||
},
|
||||
"&.Mui-disabled + .MuiSwitch-track": { opacity: 0.5 },
|
||||
},
|
||||
"&.Mui-disabled .MuiSwitch-thumb": {
|
||||
color:
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[600],
|
||||
},
|
||||
"&.Mui-disabled + .MuiSwitch-track": {
|
||||
opacity: theme.palette.mode === "light" ? 0.7 : 0.3,
|
||||
},
|
||||
},
|
||||
"& .MuiSwitch-thumb": {
|
||||
boxSizing: "border-box",
|
||||
width: 25,
|
||||
height: 25,
|
||||
},
|
||||
"& .MuiSwitch-track": {
|
||||
borderRadius: 13,
|
||||
backgroundColor:
|
||||
theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
|
||||
opacity: 1,
|
||||
transition: theme.transitions.create(["background-color"], {
|
||||
duration: 500,
|
||||
}),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Typography ref={ref} sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
|
||||
Логика ветвления
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontSize: "12px" }}>
|
||||
Настройте связи между вопросами
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
{ openBranchingPanel && <QuestionsList /> }
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -1,76 +0,0 @@
|
||||
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
|
||||
|
||||
type Props = {
|
||||
text: string;
|
||||
text2: string;
|
||||
image: string;
|
||||
};
|
||||
|
||||
export default function CreationFullCard({ text, text2, image }: Props) {
|
||||
const theme = useTheme();
|
||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1500));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
backgroundColor: "white",
|
||||
p: "20px",
|
||||
marginTop: "50px",
|
||||
borderRadius: "12px",
|
||||
display: isSmallMonitor ? "block" : "flex",
|
||||
flexDirection: isSmallMonitor ? "column" : "row",
|
||||
gap: "20px",
|
||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
|
||||
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
|
||||
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
|
||||
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
|
||||
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
mr: !isSmallMonitor ? "104px" : 0,
|
||||
marginBottom: "20px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5" sx={{ marginBottom: "20px" }}>
|
||||
Результаты квиза в зависимости от ответов
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
height: "100%",
|
||||
maxHeight: isSmallMonitor ? "none" : "220px",
|
||||
gap: "25px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", width: "95%" }}>
|
||||
{text}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#9A9AAF",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{text2}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<img
|
||||
src={image}
|
||||
alt="quiz creation"
|
||||
style={{
|
||||
display: "block",
|
||||
width: isSmallMonitor ? "100%" : "auto",
|
||||
maxHeight: isSmallMonitor ? "none" : "270px",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@ -39,24 +39,6 @@ const priceButtonsArray: { title: string; type: string; sx: SxProps<Theme> }[] =
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "ƒ",
|
||||
type: "ƒ",
|
||||
sx: {
|
||||
width: "38px",
|
||||
height: "48px",
|
||||
border: "1px solid #9A9AAF",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Скидка",
|
||||
type: "discount",
|
||||
sx: {
|
||||
width: "93px",
|
||||
height: "48px",
|
||||
border: "1px solid #9A9AAF",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
type Props = {
|
||||
@ -74,9 +56,6 @@ export default function PriceButtons({
|
||||
<Typography component={"h6"} sx={{ weight: "500", fontSize: "18px" }}>
|
||||
Стоимость
|
||||
</Typography>
|
||||
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
||||
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box
|
||||
component="div"
|
||||
112
src/pages/ResultPage/FirstEntry.tsx
Normal file
112
src/pages/ResultPage/FirstEntry.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
|
||||
import { ResultSettings } from "./ResultSettings"
|
||||
import { createFrontResult } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { Box, Typography, useTheme, useMediaQuery, Button } from "@mui/material";
|
||||
import image from "../../assets/Rectangle 110.png";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
export const FirstEntry = () => {
|
||||
const theme = useTheme();
|
||||
const quiz = useCurrentQuiz();
|
||||
const { questions } = useQuestionsStore();
|
||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1250));
|
||||
|
||||
const create = () => {
|
||||
if (quiz?.config.haveRoot) {
|
||||
if (questions.length === 0) {
|
||||
enqueueSnackbar("У вас не добавлено ни одного вопроса")
|
||||
return
|
||||
}
|
||||
questions
|
||||
.filter((question) => question.content.rule.parentId.length !== 0 && question.content.rule.default.length === 0)
|
||||
.forEach(question => {
|
||||
createFrontResult(quiz.id, question.content.id)
|
||||
})
|
||||
} else {
|
||||
createFrontResult(quiz.id)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
backgroundColor: "white",
|
||||
p: "20px",
|
||||
marginTop: "50px",
|
||||
borderRadius: "12px",
|
||||
display: isSmallMonitor ? "block" : "flex",
|
||||
flexDirection: isSmallMonitor ? "column" : "row",
|
||||
gap: "20px",
|
||||
boxShadow: `0px 100px 309px rgba(210, 208, 225, 0.24),
|
||||
0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525),
|
||||
0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066),
|
||||
0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12),
|
||||
0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343),
|
||||
0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
mr: !isSmallMonitor ? "104px" : 0,
|
||||
marginBottom: isSmallMonitor ? "20px" : 0,
|
||||
position: "relative",
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<Typography variant="h5" sx={{ marginBottom: "20px" }}>
|
||||
Результаты квиза в зависимости от ответов
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
height: "100%",
|
||||
gap: "25px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", width: "95%" }}>
|
||||
Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке.
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#9A9AAF",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
Этот шаг - необязательный, квиз будет работать и без автоматических результатов.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<img
|
||||
src={image}
|
||||
alt="quiz creation"
|
||||
style={{
|
||||
display: "block",
|
||||
width: isSmallMonitor ? "100%" : "auto",
|
||||
maxHeight: isSmallMonitor ? "none" : "270px",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button
|
||||
onClick={create}
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: "#7E2AEA",
|
||||
fontSize: "18px",
|
||||
lineHeight: "18px",
|
||||
width: "216px",
|
||||
height: "44px",
|
||||
mt: "30px",
|
||||
p: "10px 20px"
|
||||
}}
|
||||
>
|
||||
Создать результаты
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -3,7 +3,7 @@ import { updateQuiz } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import image from "../../assets/Rectangle 110.png";
|
||||
import Info from "../../assets/icons/Info";
|
||||
import CreationFullCard from "./CreationFullCard";
|
||||
// import CreationFullCard from "./FirstEntry";
|
||||
|
||||
|
||||
export const Result = () => {
|
||||
@ -13,11 +13,11 @@ export const Result = () => {
|
||||
|
||||
return (
|
||||
<Box component="section">
|
||||
<CreationFullCard
|
||||
{/* <CreationFullCard
|
||||
text="Вы можете показывать разные результаты квиза (добавьте описание, изображение, стоимость и т.п.) разным пользователям, нужно только их создать и проставить условия. Таким образом ваш квиз получится максимально индивидуальным для каждого клиента. Показывайте картинку/видео вместо результата или переадресовывайте пользователя по нужной ссылке."
|
||||
text2="Этот шаг - необязательный, квиз будет работать и без автоматических результатов."
|
||||
image={image}
|
||||
/>
|
||||
/> */}
|
||||
<Box sx={{ display: "flex", mt: "30px", alignItems: "center" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
15
src/pages/ResultPage/ResultPage.tsx
Normal file
15
src/pages/ResultPage/ResultPage.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { FirstEntry } from "./FirstEntry"
|
||||
import { ResultSettings } from "./ResultSettings"
|
||||
|
||||
export const ResultPage = () => {
|
||||
const { questions } = useQuestionsStore();
|
||||
//ищём хотя бы один result
|
||||
const haveResult = questions.some((question) => question.type === "result")
|
||||
return (
|
||||
haveResult ?
|
||||
<ResultSettings />
|
||||
:
|
||||
<FirstEntry />
|
||||
);
|
||||
}
|
||||
@ -2,19 +2,31 @@ import IconPlus from "@icons/IconPlus";
|
||||
import Info from "@icons/Info";
|
||||
import Plus from "@icons/Plus";
|
||||
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import { Box, Button, Typography, Paper, FormControl, TextField } from "@mui/material";
|
||||
import { incrementCurrentStep } from "@root/quizes/actions";
|
||||
import CustomWrapper from "@ui_kit/CustomWrapper";
|
||||
import { DescriptionForm } from "./DescriptionForm/DescriptionForm";
|
||||
import { ResultListForm } from "./ResultListForm";
|
||||
import { SettingForm } from "./SettingForm";
|
||||
import { useState } from "react";
|
||||
import { WhenCard } from "./cards/WhenCard";
|
||||
import { ResultCard } from "./cards/ResultCard";
|
||||
import { EmailSettingsCard } from "./cards/EmailSettingsCard";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||
|
||||
export const ResultSettings = () => {
|
||||
const quiz = useCurrentQuiz()
|
||||
const [quizExpand, setQuizExpand] = useState(true)
|
||||
const [resultContract, setResultContract] = useState(true)
|
||||
|
||||
export const Setting = () => {
|
||||
|
||||
return (
|
||||
<Box sx={{ maxWidth: "796px" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", mb: "40px" }}>
|
||||
<Typography sx={{ pr: "10px" }} variant="h5">
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
margin: "60px 0 40px 0",
|
||||
}}>
|
||||
<Typography variant="h5">
|
||||
Настройки результатов
|
||||
</Typography>
|
||||
<Info />
|
||||
@ -33,12 +45,17 @@ export const Setting = () => {
|
||||
},
|
||||
}}
|
||||
variant="text"
|
||||
onClick={() => setQuizExpand(!quizExpand)}
|
||||
>
|
||||
Свернуть
|
||||
</Button>
|
||||
</Box>
|
||||
<CustomWrapper sx={{ mt: "30px" }} text="Показывать результат" />
|
||||
<CustomWrapper sx={{ mt: "30px" }} text="Настройки почты" />
|
||||
|
||||
|
||||
<WhenCard quizExpand={quizExpand} />
|
||||
{quiz.config.resultInfo.when === "email" && <EmailSettingsCard quizExpand={quizExpand} />}
|
||||
|
||||
|
||||
<Box
|
||||
sx={{ display: "flex", alignItems: "center", mb: "15px", mt: "15px" }}
|
||||
>
|
||||
@ -60,52 +77,14 @@ export const Setting = () => {
|
||||
},
|
||||
}}
|
||||
variant="text"
|
||||
onClick={() => setResultContract(!resultContract)}
|
||||
>
|
||||
Развернуть все
|
||||
</Button>
|
||||
</Box>
|
||||
<CustomWrapper result={true} text="Показывать результат" />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
columnGap: "10px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
width: "100%",
|
||||
height: "1px",
|
||||
backgroundPosition: "bottom",
|
||||
backgroundRepeat: "repeat-x",
|
||||
backgroundSize: "20px 1px",
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)",
|
||||
}}
|
||||
/>
|
||||
<IconPlus />
|
||||
</Box>
|
||||
<CustomWrapper result={true} text="Настройки почты" />
|
||||
<Box sx={{ pt: "30px", display: "flex", alignItems: "center" }}>
|
||||
<Plus />
|
||||
<Typography component="div" sx={{ ml: "auto" }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{ padding: "10px 20px", borderRadius: "8px" }}
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
<Button variant="contained" sx={{ ml: "10px" }} onClick={incrementCurrentStep}>
|
||||
Настроить форму
|
||||
</Button>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<SettingForm />
|
||||
<ResultListForm />
|
||||
<DescriptionForm />
|
||||
|
||||
<ResultCard resultContract={resultContract} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -5,10 +5,11 @@ import * as React from "react";
|
||||
interface Props {
|
||||
text: string;
|
||||
icon: string;
|
||||
onClick?: () => void;
|
||||
onClick?: (a:any) => void;
|
||||
value: boolean
|
||||
}
|
||||
|
||||
export const SwitchSetting = ({ text, icon, onClick }: Props) => {
|
||||
export const SwitchSetting = ({ text, icon, onClick, value }: Props) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -24,7 +25,7 @@ export const SwitchSetting = ({ text, icon, onClick }: Props) => {
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "left", maxWidth: "756px", width: "100%" }}>
|
||||
<img src={icon} alt="icon" />
|
||||
<FormControlLabel
|
||||
value="start"
|
||||
checked={value}
|
||||
control={<CustomizedSwitch />}
|
||||
label={text}
|
||||
labelPlacement="start"
|
||||
251
src/pages/ResultPage/cards/EmailSettingsCard.tsx
Normal file
251
src/pages/ResultPage/cards/EmailSettingsCard.tsx
Normal file
@ -0,0 +1,251 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||
|
||||
import {
|
||||
Box,
|
||||
TextField,
|
||||
IconButton,
|
||||
Paper,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
import { updateQuiz } from "@root/quizes/actions";
|
||||
|
||||
|
||||
interface Props {
|
||||
quizExpand: boolean
|
||||
}
|
||||
|
||||
export const EmailSettingsCard = ({ quizExpand }: Props) => {
|
||||
const quiz = useCurrentQuiz()
|
||||
const theme = useTheme();
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
|
||||
|
||||
const [expand, setExpand] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
setExpand(false)
|
||||
}, [quizExpand])
|
||||
|
||||
const debouncedCallback = useDebouncedCallback((callback) => {
|
||||
callback();
|
||||
}, 200);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
data-cy="quiz-question-card"
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
width: "100%",
|
||||
borderRadius: "12px",
|
||||
backgroundColor: expand ? "white" : "#EEE4FC",
|
||||
border: expand ? "none" : "1px solid #9A9AAF",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
m: "20px 0"
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: expand ? isMobile ? "10px 10px 0 10px" : "20px 20px 0 20px" : isMobile ? "10px" : "20px",
|
||||
flexDirection: isMobile ? "column" : null,
|
||||
justifyContent: "space-between",
|
||||
minHeight: "40px",
|
||||
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
margin: isMobile ? "10px 0" : 0,
|
||||
color: expand ? "#9A9AAF" : "#000000",
|
||||
}}
|
||||
>
|
||||
Настройки почты
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
width: isMobile ? "100%" : "auto",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{ padding: "0", margin: "5px" }}
|
||||
disableRipple
|
||||
data-cy="expand-question"
|
||||
onClick={() => setExpand(!expand)}
|
||||
>
|
||||
{expand ? (
|
||||
<ExpandLessIconBG />
|
||||
) : (
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
fill: theme.palette.brightPurple.main,
|
||||
background: "#FFF",
|
||||
borderRadius: "6px",
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{expand && (
|
||||
<Box
|
||||
sx={{
|
||||
p: "0 20px 20px 20px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
m: "20px 0 14px 0",
|
||||
fontSize: "18px",
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
Тема письма
|
||||
</Typography>
|
||||
<TextField
|
||||
value={quiz.config.resultInfo.theme}
|
||||
placeholder={"Заголовок вопроса"}
|
||||
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.config.resultInfo.theme = target.value
|
||||
})) }}
|
||||
sx={{
|
||||
margin: isMobile ? "10px 0" : 0,
|
||||
width:"100%",
|
||||
"& .MuiInputBase-root": {
|
||||
color: "#000000",
|
||||
backgroundColor: expand
|
||||
? theme.palette.background.default
|
||||
: "transparent",
|
||||
height: "48px",
|
||||
borderRadius: "10px",
|
||||
".MuiOutlinedInput-notchedOutline": {
|
||||
borderWidth: "1px !important",
|
||||
border: expand ? "none" : null,
|
||||
},
|
||||
"& .MuiInputBase-input::placeholder": {
|
||||
color: "#4D4D4D",
|
||||
opacity: 0.8,
|
||||
},
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
paddingLeft: "18px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
m: "20px 0 14px 0",
|
||||
fontSize: "18px",
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
E-mail ответа
|
||||
</Typography>
|
||||
<TextField
|
||||
value={quiz.config.resultInfo.reply}
|
||||
placeholder={"noreplay@example.ru"}
|
||||
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.config.resultInfo.reply = target.value
|
||||
})) }}
|
||||
sx={{
|
||||
margin: isMobile ? "10px 0" : 0,
|
||||
width:"100%",
|
||||
"& .MuiInputBase-root": {
|
||||
color: "#000000",
|
||||
backgroundColor: expand
|
||||
? theme.palette.background.default
|
||||
: "transparent",
|
||||
height: "48px",
|
||||
borderRadius: "10px",
|
||||
".MuiOutlinedInput-notchedOutline": {
|
||||
borderWidth: "1px !important",
|
||||
border: expand ? "none" : null,
|
||||
},
|
||||
"& .MuiInputBase-input::placeholder": {
|
||||
color: "#4D4D4D",
|
||||
opacity: 0.8,
|
||||
},
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
paddingLeft: "18px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
m: "20px 0 14px 0",
|
||||
fontSize: "18px",
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
Имя отправителя
|
||||
</Typography>
|
||||
<TextField
|
||||
value={quiz.config.resultInfo.replname}
|
||||
placeholder={"Название компании"}
|
||||
onChange={({ target }: { target: HTMLInputElement }) => {debouncedCallback(updateQuiz(quiz.id, (quiz) => {
|
||||
quiz.config.resultInfo.replname = target.value
|
||||
})) }}
|
||||
sx={{
|
||||
margin: isMobile ? "10px 0" : 0,
|
||||
width:"100%",
|
||||
"& .MuiInputBase-root": {
|
||||
color: "#000000",
|
||||
backgroundColor: expand
|
||||
? theme.palette.background.default
|
||||
: "transparent",
|
||||
height: "48px",
|
||||
borderRadius: "10px",
|
||||
".MuiOutlinedInput-notchedOutline": {
|
||||
borderWidth: "1px !important",
|
||||
border: expand ? "none" : null,
|
||||
},
|
||||
"& .MuiInputBase-input::placeholder": {
|
||||
color: "#4D4D4D",
|
||||
opacity: 0.8,
|
||||
},
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
paddingLeft: "18px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
291
src/pages/ResultPage/cards/ResultCard.tsx
Normal file
291
src/pages/ResultPage/cards/ResultCard.tsx
Normal file
@ -0,0 +1,291 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { updateQuiz } from "@root/quizes/actions"
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||
|
||||
import { SwitchSetting } from "../SwichResult";
|
||||
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Paper,
|
||||
Button,
|
||||
Typography,
|
||||
TextField,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
import ShareNetwork from "@icons/ShareNetwork.svg";
|
||||
import ArrowCounterClockWise from "@icons/ArrowCounterClockWise.svg";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import SwitchResult from "../DescriptionForm/SwitchResult";
|
||||
import ButtonsOptionsForm from "../DescriptionForm/ButtinsOptionsForm";
|
||||
import PriceButtons from "../DescriptionForm/PriceButton";
|
||||
import DiscountButtons from "../DescriptionForm/DiscountButtons";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { OneIcon } from "@icons/questionsPage/OneIcon";
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
|
||||
import Info from "@icons/Info";
|
||||
import ImageAndVideoButtons from "../DescriptionForm/ImageAndVideoButtons";
|
||||
|
||||
interface Props {
|
||||
resultContract: boolean;
|
||||
}
|
||||
|
||||
export const ResultCard = ({ resultContract }:Props) => {
|
||||
const quiz = useCurrentQuiz()
|
||||
const theme = useTheme();
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
|
||||
|
||||
const [expand, setExpand] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
setExpand(true)
|
||||
}, [resultContract])
|
||||
|
||||
|
||||
const [switchState, setSwitchState] = useState<string>("");
|
||||
const [priceButtonsActive, setPriceButtonsActive] = useState<number>(0);
|
||||
const [priceButtonsType, setPriceButtonsType] = useState<string>();
|
||||
const [forwarding, setForwarding] = useState<boolean>(false);
|
||||
|
||||
const buttonsActive = (index: number, type: string) => {
|
||||
setPriceButtonsActive(index);
|
||||
setPriceButtonsType(type);
|
||||
};
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
setSwitchState(data);
|
||||
};
|
||||
|
||||
return(
|
||||
<Paper
|
||||
data-cy="quiz-question-card"
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
width: "100%",
|
||||
borderRadius: "12px",
|
||||
backgroundColor: expand ? "white" : "#EEE4FC",
|
||||
border: expand ? "none" : "1px solid #9A9AAF",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
m: "20px 0"
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: isMobile ? "10px" : "20px",
|
||||
flexDirection: isMobile ? "column" : null,
|
||||
justifyContent: "space-between",
|
||||
minHeight: "40px",
|
||||
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
margin: isMobile ? "10px 0" : 0,
|
||||
color: expand ? "#9A9AAF" : "#000000",
|
||||
}}
|
||||
>
|
||||
Заголовок результата
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
width: isMobile ? "100%" : "auto",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{ padding: "0", margin: "5px" }}
|
||||
disableRipple
|
||||
data-cy="expand-question"
|
||||
onClick={() => setExpand(!expand)}
|
||||
>
|
||||
{expand ? (
|
||||
<ExpandLessIconBG />
|
||||
) : (
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
fill: theme.palette.brightPurple.main,
|
||||
background: "#FFF",
|
||||
borderRadius: "6px",
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{expand && (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
maxWidth: "796px",
|
||||
height: "100%",
|
||||
bgcolor: "#FFFFFF",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: "0 20px", pt: "30px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: "760px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "10px",
|
||||
mb: "19px",
|
||||
}}
|
||||
>
|
||||
<CustomTextField placeholder="Заголовок вопроса" text={""} />
|
||||
<IconButton>
|
||||
<ExpandMoreIcon />
|
||||
</IconButton>
|
||||
<OneIcon />
|
||||
<PointsIcon style={{ color: "#9A9AAF" }} />
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<PriceButtons
|
||||
ButtonsActive={buttonsActive}
|
||||
priceButtonsActive={priceButtonsActive}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder="Описание"
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
width: "100%",
|
||||
height: "110px",
|
||||
borderRadius: "10px",
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ImageAndVideoButtons />
|
||||
{priceButtonsType === "smooth" ? (
|
||||
<Box sx={{ mb: "20px" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", mb: "14xp" }}>
|
||||
<Typography
|
||||
component={"h6"}
|
||||
sx={{ weight: "500", fontSize: "18px" }}
|
||||
>
|
||||
Призыв к действию
|
||||
</Typography>
|
||||
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
||||
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TextField
|
||||
placeholder="Узнать подробнее"
|
||||
fullWidth
|
||||
sx={{
|
||||
width: "410px",
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
width: "410px",
|
||||
height: "48px",
|
||||
borderRadius: "10px",
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => setForwarding(true)}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
display: forwarding ? "none" : "",
|
||||
ml: "20px",
|
||||
mb: "20px",
|
||||
}}
|
||||
>
|
||||
Переадресация +
|
||||
</Button>
|
||||
{forwarding ? (
|
||||
<Box sx={{ ml: "20px", mt: "-36px" }}>
|
||||
<Box
|
||||
sx={{ display: "flex", alignItems: "center", mb: "14xp" }}
|
||||
>
|
||||
<Typography
|
||||
component={"h6"}
|
||||
sx={{ weight: "500", fontSize: "18px" }}
|
||||
>
|
||||
Переадресация
|
||||
</Typography>
|
||||
<IconButton sx={{ borderRadius: "6px", padding: "2px" }}>
|
||||
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
<Info />
|
||||
</Box>
|
||||
<Box>
|
||||
<TextField
|
||||
placeholder="https://exemple.ru"
|
||||
fullWidth
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
width: "326px",
|
||||
height: "48px",
|
||||
borderRadius: "10px",
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
fontSize: "18px",
|
||||
lineHeight: "21px",
|
||||
py: 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Button variant="outlined" sx={{ mb: "20px" }}>
|
||||
Кнопка +
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<ButtonsOptionsForm switchState={switchState} SSHC={SSHC} />
|
||||
<SwitchResult switchState={switchState} totalIndex={0} />
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
187
src/pages/ResultPage/cards/WhenCard.tsx
Normal file
187
src/pages/ResultPage/cards/WhenCard.tsx
Normal file
@ -0,0 +1,187 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { updateQuiz } from "@root/quizes/actions"
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||
|
||||
import { SwitchSetting } from "../SwichResult";
|
||||
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Paper,
|
||||
Button,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import ExpandLessIconBG from "@icons/ExpandLessIconBG";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
import ShareNetwork from "@icons/ShareNetwork.svg";
|
||||
import ArrowCounterClockWise from "@icons/ArrowCounterClockWise.svg";
|
||||
|
||||
const whenValues = [
|
||||
{
|
||||
title: "До формы контактов",
|
||||
value: "before",
|
||||
},
|
||||
{
|
||||
title: "После формы контактов",
|
||||
value: "after",
|
||||
},
|
||||
{
|
||||
title: "Отправить на E-mail",
|
||||
value: "email",
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
quizExpand: boolean
|
||||
}
|
||||
|
||||
export const WhenCard = ({ quizExpand }: Props) => {
|
||||
const quiz = useCurrentQuiz()
|
||||
const theme = useTheme();
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1100));
|
||||
|
||||
const [expand, setExpand] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
setExpand(false)
|
||||
}, [quizExpand])
|
||||
|
||||
|
||||
return (
|
||||
<Paper
|
||||
data-cy="quiz-question-card"
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
width: "100%",
|
||||
borderRadius: "12px",
|
||||
backgroundColor: expand ? "white" : "#EEE4FC",
|
||||
border: expand ? "none" : "1px solid #9A9AAF",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
m: "20px 0"
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: isMobile ? "10px" : "20px",
|
||||
flexDirection: isMobile ? "column" : null,
|
||||
justifyContent: "space-between",
|
||||
minHeight: "40px",
|
||||
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
margin: isMobile ? "10px 0" : 0,
|
||||
color: expand ? "#9A9AAF" : "#000000",
|
||||
}}
|
||||
>
|
||||
Показывать результат
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
width: isMobile ? "100%" : "auto",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{ padding: "0", margin: "5px" }}
|
||||
disableRipple
|
||||
data-cy="expand-question"
|
||||
onClick={() => setExpand(!expand)}
|
||||
>
|
||||
{expand ? (
|
||||
<ExpandLessIconBG />
|
||||
) : (
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
fill: theme.palette.brightPurple.main,
|
||||
background: "#FFF",
|
||||
borderRadius: "6px",
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{expand && (
|
||||
<>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
p: "33px 20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: isSmallMonitor ? "column" : "row",
|
||||
justifyContent: "space-between",
|
||||
gap: "20px",
|
||||
mb: "20px",
|
||||
}}
|
||||
>
|
||||
{whenValues.map(({ title, value }, index) => (
|
||||
<Button
|
||||
onClick={() => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.when = value)}
|
||||
key={title}
|
||||
sx={{
|
||||
bgcolor: quiz?.config.resultInfo.when === value ? " #7E2AEA" : "#F2F3F7",
|
||||
color: quiz?.config.resultInfo.when === value ? " white" : "#9A9AAF",
|
||||
minWidth: isSmallMonitor ? "300px" : "auto",
|
||||
borderRadius: "8px",
|
||||
width: "237px",
|
||||
height: "44px",
|
||||
border: quiz?.config.resultInfo.when === value ? "none" : "1px solid #9A9AAF",
|
||||
"&:hover": {
|
||||
backgroundColor: quiz?.config.resultInfo.when === value ? "#581CA7" : "#7E2AEA",
|
||||
color: "white"
|
||||
|
||||
}
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
|
||||
{
|
||||
(quiz?.config.resultInfo.when !== "email") && <SwitchSetting
|
||||
icon={ShareNetwork}
|
||||
text="Поделиться результатами"
|
||||
onClick={(event) => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.share = event.target.checked)}
|
||||
value={quiz?.config.resultInfo.share}
|
||||
/>
|
||||
}
|
||||
{
|
||||
quiz?.config.resultInfo.when === "before" && <SwitchSetting
|
||||
icon={ArrowCounterClockWise}
|
||||
text="Кнопка `Пройти тест заново`"
|
||||
onClick={(event) => updateQuiz(quiz.id, (quiz) => quiz.config.resultInfo.replay = event.target.checked)}
|
||||
value={quiz?.config.resultInfo.replay}
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
)
|
||||
}
|
||||
@ -19,13 +19,69 @@ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
|
||||
const [disabledQuestionsId, setDisabledQuestionsId] = useState<Set<string>>(
|
||||
new Set()
|
||||
);
|
||||
const [disablePreviousButton, setDisablePreviousButton] =
|
||||
useState<boolean>(false);
|
||||
const [disableNextButton, setDisableNextButton] = useState<boolean>(false);
|
||||
const { answers } = useQuizViewStore();
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
// Логика для аргумента disabled у кнопки "Назад"
|
||||
if (question?.content.rule.parentId === "root") {
|
||||
setDisablePreviousButton(true);
|
||||
} else {
|
||||
setDisablePreviousButton(false);
|
||||
}
|
||||
|
||||
// Логика для аргумента disabled у кнопки "Далее"
|
||||
const nextQuestionId = getNextQuestionId();
|
||||
if (nextQuestionId) {
|
||||
setDisableNextButton(false);
|
||||
} else {
|
||||
const nextQuestion = getQuestionByContentId(
|
||||
question.content.rule.default
|
||||
);
|
||||
if (nextQuestion?.type) {
|
||||
setDisableNextButton(false);
|
||||
} else {
|
||||
setDisableNextButton(true);
|
||||
}
|
||||
}
|
||||
}, [question]);
|
||||
|
||||
const getNextQuestionId = () => {
|
||||
if (answers.length) {
|
||||
const answer = answers.find(
|
||||
({ questionId }) => questionId === question.content.id
|
||||
);
|
||||
|
||||
let readyBeNextQuestion = "";
|
||||
|
||||
question.content.rule.main.forEach(({ next, rules }) => {
|
||||
let longerArray = Math.max(
|
||||
rules[0].answers.length,
|
||||
[answer?.answer].length
|
||||
);
|
||||
|
||||
for (
|
||||
var i = 0;
|
||||
i < longerArray;
|
||||
i++ // Цикл по всем элементам бОльшего массива
|
||||
) {
|
||||
if (rules[0].answers[i] === answer?.answer) {
|
||||
readyBeNextQuestion = next; // Если хоть один элемент отличается, массивы не равны
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return readyBeNextQuestion;
|
||||
}
|
||||
};
|
||||
|
||||
const followPreviousStep = () => {
|
||||
if (question?.content.rule.parentId !== "root") {
|
||||
const parent = getQuestionByContentId(question?.content.rule.parentId);
|
||||
if (parent) {
|
||||
if (parent?.type) {
|
||||
setCurrentQuestion(parent);
|
||||
} else {
|
||||
enqueueSnackbar("не могу получить предыдущий вопрос");
|
||||
@ -36,45 +92,25 @@ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
|
||||
};
|
||||
|
||||
const followNextStep = () => {
|
||||
if (answers.length) {
|
||||
let readyBeNextQuestion = "";
|
||||
const nextQuestionId = getNextQuestionId();
|
||||
|
||||
question.content.rule.main.forEach(({ next, rules }) => {
|
||||
if (nextQuestionId) {
|
||||
const nextQuestion = getQuestionByContentId(nextQuestionId);
|
||||
|
||||
let longerArray = Math.max(
|
||||
rules[0].answers.length,
|
||||
[answers.at(-1)?.answer].length
|
||||
);
|
||||
|
||||
for (
|
||||
var i = 0;
|
||||
i < longerArray;
|
||||
i++ // Цикл по всем элементам бОльшего массива
|
||||
) {
|
||||
if (rules[0].answers[i] === answers.at(-1)?.answer) {
|
||||
readyBeNextQuestion = next; // Если хоть один элемент отличается, массивы не равны
|
||||
}
|
||||
}
|
||||
});
|
||||
if (readyBeNextQuestion) {
|
||||
|
||||
const nextQuestion = getQuestionByContentId(readyBeNextQuestion);
|
||||
|
||||
if (nextQuestion) {
|
||||
setCurrentQuestion(nextQuestion);
|
||||
return;
|
||||
} else {
|
||||
enqueueSnackbar("не могу получить последующий вопрос");
|
||||
}
|
||||
if (nextQuestion?.type) {
|
||||
setCurrentQuestion(nextQuestion);
|
||||
return;
|
||||
} else {
|
||||
const nextQuestion = getQuestionByContentId(
|
||||
question.content.rule.default
|
||||
);
|
||||
if (nextQuestion) {
|
||||
setCurrentQuestion(nextQuestion);
|
||||
} else {
|
||||
enqueueSnackbar("не могу получить последующий вопрос (дефолтный)");
|
||||
}
|
||||
enqueueSnackbar("не могу получить последующий вопрос");
|
||||
}
|
||||
} else {
|
||||
const nextQuestion = getQuestionByContentId(
|
||||
question.content.rule.default
|
||||
);
|
||||
if (nextQuestion?.type) {
|
||||
setCurrentQuestion(nextQuestion);
|
||||
} else {
|
||||
enqueueSnackbar("не могу получить последующий вопрос (дефолтный)");
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -128,6 +164,7 @@ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
|
||||
</Typography> */}
|
||||
</Box>
|
||||
<Button
|
||||
disabled={disablePreviousButton}
|
||||
variant="contained"
|
||||
sx={{ fontSize: "16px", padding: "10px 15px" }}
|
||||
onClick={followPreviousStep}
|
||||
@ -135,6 +172,7 @@ export const Footer = ({ setCurrentQuestion, question }: FooterProps) => {
|
||||
← Назад
|
||||
</Button>
|
||||
<Button
|
||||
disabled={disableNextButton}
|
||||
variant="contained"
|
||||
sx={{ fontSize: "16px", padding: "10px 15px" }}
|
||||
onClick={followNextStep}
|
||||
|
||||
@ -55,8 +55,8 @@ export const Question = ({
|
||||
sx={{
|
||||
minHeight: "calc(100vh - 75px)",
|
||||
width: "100%",
|
||||
maxWidth: "1000px",
|
||||
padding: "20px 10px 0",
|
||||
maxWidth: "1440px",
|
||||
padding: "40px 25px 20px",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
|
||||
@ -13,7 +13,10 @@ type DateProps = {
|
||||
|
||||
export const Date = ({ currentQuestion }: DateProps) => {
|
||||
const { answers } = useQuizViewStore();
|
||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {};
|
||||
const { answer } =
|
||||
answers.find(
|
||||
({ questionId }) => questionId === currentQuestion.content.id
|
||||
) ?? {};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@ -28,7 +31,18 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
||||
>
|
||||
<DatePicker
|
||||
selected={answer ? new window.Date(answer) : new window.Date()}
|
||||
onChange={(date) => updateAnswer(currentQuestion.content.id, String(date))}
|
||||
onChange={(date) =>
|
||||
updateAnswer(
|
||||
currentQuestion.content.id,
|
||||
String(
|
||||
date?.toLocaleDateString("ru-RU", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
useTheme,
|
||||
useTheme, FormControl,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
||||
@ -50,31 +50,49 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
<Box sx={{ display: "flex", width: "100%", gap: "42px" }}>
|
||||
{currentQuestion.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>
|
||||
<FormControl
|
||||
key={id}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
overflow: "hidden",
|
||||
maxWidth: "317px",
|
||||
width: "100%",
|
||||
height: "255px"
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ display: "flex", alignItems: "center", height: "193px", background: "#ffffff" }}
|
||||
>
|
||||
<Box sx={{ width: "100%", display: "flex", justifyContent: "center" }}>
|
||||
{extendedText && (
|
||||
<Typography fontSize={"100px"}>{extendedText}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<FormControlLabel
|
||||
key={id}
|
||||
sx={{
|
||||
margin: 0,
|
||||
padding: "15px",
|
||||
color: "#4D4D4D",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
value={index}
|
||||
control={
|
||||
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", gap: "10px" }}>
|
||||
<Typography>{answer}</Typography>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
useTheme,
|
||||
useMediaQuery, FormControl,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
||||
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
|
||||
@ -14,7 +14,8 @@ export const Page = ({ currentQuestion }: PageProps) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5">{currentQuestion.title}</Typography>
|
||||
<Typography variant="h5" sx={{ paddingBottom: "25px" }}>{currentQuestion.title}</Typography>
|
||||
<Typography>{currentQuestion.content.text}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -24,17 +25,20 @@ export const Page = ({ currentQuestion }: PageProps) => {
|
||||
}}
|
||||
>
|
||||
{currentQuestion.content.picture && (
|
||||
<img
|
||||
src={currentQuestion.content.picture}
|
||||
alt=""
|
||||
style={{
|
||||
display: "block",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
maxHeight: "80vh",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
<Box sx={{borderRadius: "12px",
|
||||
border: "1px solid #9A9AAF", overflow: "hidden" }}>
|
||||
<img
|
||||
src={currentQuestion.content.picture}
|
||||
alt=""
|
||||
style={{
|
||||
display: "block",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
)}
|
||||
{currentQuestion.content.video && (
|
||||
<video
|
||||
|
||||
@ -53,20 +53,22 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
<Box sx={{ display: "flex", flexDirection: "row", flexWrap: "wrap", width: "100%", gap: "20px", }}>
|
||||
{currentQuestion.content.variants.map(({ id, answer }, index) => (
|
||||
<FormControlLabel
|
||||
key={id}
|
||||
sx={{
|
||||
marginBottom: "15px",
|
||||
borderRadius: "5px",
|
||||
margin: "0",
|
||||
borderRadius: "12px",
|
||||
padding: "15px",
|
||||
color: theme.palette.grey2.main,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
maxWidth: "685px",
|
||||
justifyContent: "space-between",
|
||||
width: "100%"
|
||||
}}
|
||||
value={index}
|
||||
labelPlacement="start"
|
||||
control={
|
||||
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5">{currentQuestion.title}</Typography>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<Box sx={{ display: "flex", marginTop: "20px", }}>
|
||||
<RadioGroup
|
||||
name={currentQuestion.id}
|
||||
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)}
|
||||
@ -50,7 +50,6 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
flexBasis: "100%",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
@ -61,7 +60,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
marginBottom: "15px",
|
||||
borderRadius: "5px",
|
||||
padding: "15px",
|
||||
color: theme.palette.grey2.main,
|
||||
color: "#4D4D4D",
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
display: "flex",
|
||||
}}
|
||||
@ -75,7 +74,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
</Box>
|
||||
</RadioGroup>
|
||||
{(variant?.extendedText || currentQuestion.content.back) && (
|
||||
<Box sx={{ maxWidth: "400px", width: "100%", height: "300px" }}>
|
||||
<Box sx={{ maxWidth: "450px", width: "100%", height: "450px", border: "1px solid #E3E3E3", borderRadius: "12px", overflow: "hidden", }}>
|
||||
<img
|
||||
src={answer ? variant?.extendedText : currentQuestion.content.back}
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
|
||||
@ -9,8 +9,8 @@ import {
|
||||
Button,
|
||||
Container,
|
||||
FormControl,
|
||||
IconButton,
|
||||
TextField,
|
||||
IconButton, Switch,
|
||||
TextField, Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
@ -29,10 +29,13 @@ import { useEffect, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import useSWR from "swr";
|
||||
import { SidebarMobile } from "./Sidebar/SidebarMobile";
|
||||
import { cleanQuestions } from "@root/questions/actions";
|
||||
import {cleanQuestions, updateOpenBranchingPanel} from "@root/questions/actions";
|
||||
import {BranchingPanel} from "../Questions/BranchingPanel";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import { useQuestions } from "@root/questions/hooks";
|
||||
|
||||
|
||||
export default function StartPage() {
|
||||
export default function EditPage() {
|
||||
useSWR("quizes", () => quizApi.getList(), {
|
||||
onSuccess: setQuizes,
|
||||
onError: error => {
|
||||
@ -42,6 +45,9 @@ export default function StartPage() {
|
||||
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const editQuizId = useQuizStore(state => state.editQuizId);
|
||||
@ -50,8 +56,9 @@ export default function StartPage() {
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||
const [mobileSidebar, setMobileSidebar] = useState<boolean>(false);
|
||||
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
const quizConfig = quiz?.config;
|
||||
const { questions, isLoading } = useQuestions();
|
||||
|
||||
useEffect(() => {
|
||||
if (editQuizId === null) navigate("/list");
|
||||
@ -212,6 +219,7 @@ export default function StartPage() {
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
>
|
||||
{/* Выбор текущей страницы редактирования чего-либо находится здесь */}
|
||||
{quizConfig &&
|
||||
<>
|
||||
<Stepper activeStep={currentStep} />
|
||||
@ -228,9 +236,9 @@ export default function StartPage() {
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
left: isMobile ? 0 : "230px",
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
width: isMobile ? "100%" : "calc(100% - 230px)",
|
||||
width: "100%",
|
||||
padding: "20px 40px",
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
@ -238,6 +246,75 @@ export default function StartPage() {
|
||||
background: "#FFF",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: openBranchingPanel ? "none" : "display",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
padding: "18px",
|
||||
background: "#fff",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
value={openBranchingPanel}
|
||||
onChange={(_, value) => {
|
||||
updateOpenBranchingPanel(value)
|
||||
}}
|
||||
sx={{
|
||||
width: 50,
|
||||
height: 30,
|
||||
padding: 0,
|
||||
"& .MuiSwitch-switchBase": {
|
||||
padding: 0,
|
||||
margin: "2px",
|
||||
transitionDuration: "300ms",
|
||||
"&.Mui-checked": {
|
||||
transform: "translateX(20px)",
|
||||
color: theme.palette.brightPurple.main,
|
||||
"& + .MuiSwitch-track": {
|
||||
backgroundColor: "#E8DCF9",
|
||||
opacity: 1,
|
||||
border: 0,
|
||||
},
|
||||
"&.Mui-disabled + .MuiSwitch-track": { opacity: 0.5 },
|
||||
},
|
||||
"&.Mui-disabled .MuiSwitch-thumb": {
|
||||
color:
|
||||
theme.palette.mode === "light"
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.grey[600],
|
||||
},
|
||||
"&.Mui-disabled + .MuiSwitch-track": {
|
||||
opacity: theme.palette.mode === "light" ? 0.7 : 0.3,
|
||||
},
|
||||
},
|
||||
"& .MuiSwitch-thumb": {
|
||||
boxSizing: "border-box",
|
||||
width: 25,
|
||||
height: 25,
|
||||
},
|
||||
"& .MuiSwitch-track": {
|
||||
borderRadius: 13,
|
||||
backgroundColor:
|
||||
theme.palette.mode === "light" ? "#E9E9EA" : "#39393D",
|
||||
opacity: 1,
|
||||
transition: theme.transitions.create(["background-color"], {
|
||||
duration: 500,
|
||||
}),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
|
||||
Логика ветвления
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontSize: "12px" }}>
|
||||
Настройте связи между вопросами
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
@ -4,21 +4,23 @@ import { devlog } from "@frontend/kitui";
|
||||
import { questionToEditQuestionRequest } from "@model/question/edit";
|
||||
import { QuestionType, RawQuestion, rawQuestionToQuestion } from "@model/question/question";
|
||||
import { AnyTypedQuizQuestion, QuestionVariant, UntypedQuizQuestion, createQuestionVariant } from "@model/questionTypes/shared";
|
||||
import { updateRootContentId } from "@root/quizes/actions";
|
||||
import { defaultQuestionByType } from "../../constants/default";
|
||||
import { produce } from "immer";
|
||||
import { nanoid } from "nanoid";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { defaultQuestionByType } from "../../constants/default";
|
||||
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
||||
import { RequestQueue } from "../../utils/requestQueue";
|
||||
import { updateRootContentId } from "@root/quizes/actions"
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||
import { QuestionsStore, useQuestionsStore } from "./store";
|
||||
import { withErrorBoundary } from "react-error-boundary";
|
||||
|
||||
|
||||
export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => {
|
||||
const untypedQuestions = state.questions.filter(q => q.type === null);
|
||||
const untypedResultQuestions = state.questions.filter(q => q.type === null || q.type === "result");
|
||||
|
||||
state.questions = questions?.map(rawQuestionToQuestion) ?? [];
|
||||
state.questions.push(...untypedQuestions);
|
||||
state.questions.push(...untypedResultQuestions);
|
||||
}, {
|
||||
type: "setQuestions",
|
||||
questions,
|
||||
@ -86,7 +88,7 @@ const setQuestionBackendId = (questionId: string, backendId: number) => setProdu
|
||||
|
||||
const updateQuestionOrders = () => {
|
||||
const questions = useQuestionsStore.getState().questions.filter(
|
||||
(question): question is AnyTypedQuizQuestion => question.type !== null
|
||||
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
|
||||
);
|
||||
|
||||
questions.forEach((question, index) => {
|
||||
@ -345,10 +347,45 @@ export const deleteQuestion = async (questionId: string, quizId: string) => requ
|
||||
try {
|
||||
await questionApi.delete(question.backendId);
|
||||
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
|
||||
updateRootContentId(quizId, "");
|
||||
clearRoleForAll();
|
||||
}
|
||||
updateRootContentId(quizId, "")
|
||||
clearRuleForAll()
|
||||
} else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков
|
||||
const clearQuestions = [] as string[]
|
||||
|
||||
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
||||
questions.forEach((targetQuestion) => {
|
||||
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
|
||||
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id)
|
||||
getChildren(targetQuestion) //и ищем его потомков
|
||||
}
|
||||
})
|
||||
}
|
||||
getChildren(question)
|
||||
//чистим потомков от инфы ветвления
|
||||
clearQuestions.forEach((id) => {
|
||||
updateQuestion(id, question => {
|
||||
question.content.rule.parentId = ""
|
||||
question.content.rule.main = []
|
||||
question.content.rule.default = ""
|
||||
})
|
||||
})
|
||||
|
||||
//чистим rule родителя
|
||||
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
|
||||
const newRule = {}
|
||||
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
|
||||
newRule.parentId = parentQuestion.content.rule.parentId
|
||||
newRule.default = questions.filter((q) => {
|
||||
return q.content.rule.parentId === question.content.rule.parentId && q.content.id !== question.content.id
|
||||
})[0]?.content.id || ""
|
||||
//Если этот вопрос был дефолтным у родителя - чистим дефолт
|
||||
//Смотрим можем ли мы заменить id на один из main
|
||||
|
||||
console.log(newRule)
|
||||
updateQuestion(question.content.rule.parentId, (PQ) => {
|
||||
PQ.content.rule = newRule
|
||||
})
|
||||
}
|
||||
removeQuestion(questionId);
|
||||
} catch (error) {
|
||||
devlog("Error deleting question", error);
|
||||
@ -363,7 +400,8 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
|
||||
const frontId = nanoid();
|
||||
if (question.type === null) {
|
||||
const copiedQuestion = structuredClone(question);
|
||||
copiedQuestion.id = frontId;
|
||||
copiedQuestion.id = frontId
|
||||
copiedQuestion.content.id = frontId
|
||||
|
||||
setProducedState(state => {
|
||||
state.questions.push(copiedQuestion);
|
||||
@ -430,17 +468,92 @@ export const updateDragQuestionContentId = (contentId?: string) => {
|
||||
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
|
||||
};
|
||||
|
||||
export const clearRoleForAll = () => {
|
||||
const { questions } = useQuestionsStore.getState();
|
||||
export const clearRuleForAll = () => {
|
||||
const { questions } = useQuestionsStore.getState()
|
||||
questions.forEach(question => {
|
||||
if (question.type !== null && (question.content.rule.main.length > 0 || question.content.rule.default.length > 0 || question.content.rule.parentId.length > 0)) {
|
||||
updateQuestion(question.content.id, question => {
|
||||
question.content.rule.parentId = "";
|
||||
question.content.rule.main = [];
|
||||
question.content.rule.default = "";
|
||||
});
|
||||
question.content.rule.parentId = ""
|
||||
question.content.rule.main = []
|
||||
question.content.rule.default = ""
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export const updateOpenBranchingPanel = (value: boolean) => useQuestionsStore.setState({ openBranchingPanel: !value });
|
||||
export const updateOpenBranchingPanel = (value: boolean) => useQuestionsStore.setState({ openBranchingPanel: value });
|
||||
|
||||
|
||||
let UDTOABM: ReturnType<typeof setTimeout>;
|
||||
export const updateDesireToOpenABranchingModal = (contentId: string) => {
|
||||
useQuestionsStore.setState({ desireToOpenABranchingModal: contentId })
|
||||
clearTimeout(UDTOABM)
|
||||
UDTOABM = setTimeout(() => {
|
||||
useQuestionsStore.setState({ desireToOpenABranchingModal: null })
|
||||
}, 7000)
|
||||
}
|
||||
export const clearDesireToOpenABranchingModal = () => {
|
||||
useQuestionsStore.setState({ desireToOpenABranchingModal: null })
|
||||
}
|
||||
export const updateEditSomeQuestion = (contentId?: string) => {
|
||||
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId })
|
||||
}
|
||||
|
||||
export const createFrontResult = (quizId: number, parentContentId?: string) => setProducedState(state => {
|
||||
const frontId = nanoid()
|
||||
const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content))
|
||||
content.id = frontId
|
||||
if (parentContentId) content.rule.parentId = parentContentId
|
||||
state.questions.push({
|
||||
id: frontId,
|
||||
quizId,
|
||||
type: "result",
|
||||
title: "",
|
||||
description: "",
|
||||
deleted: false,
|
||||
expanded: true,
|
||||
page: 101,
|
||||
required: true,
|
||||
content
|
||||
});
|
||||
}, {
|
||||
type: "createFrontResult",
|
||||
quizId,
|
||||
});
|
||||
|
||||
|
||||
export const createBackResult = async (
|
||||
questionId: string,
|
||||
type: QuestionType,
|
||||
) => requestQueue.enqueue(async () => {
|
||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||
if (!question) return;
|
||||
if (question.type !== "result") throw new Error("Cannot upgrade already typed question");
|
||||
|
||||
try {
|
||||
const createdQuestion = await questionApi.create({
|
||||
quiz_id: question.quizId,
|
||||
type,
|
||||
title: question.title,
|
||||
description: question.description,
|
||||
page: 0,
|
||||
required: true,
|
||||
content: JSON.stringify(defaultQuestionByType[type].content),
|
||||
});
|
||||
|
||||
setProducedState(state => {
|
||||
const questionIndex = state.questions.findIndex(q => q.id === questionId);
|
||||
if (questionIndex !== -1) state.questions.splice(
|
||||
questionIndex,
|
||||
1,
|
||||
rawQuestionToQuestion(createdQuestion)
|
||||
);
|
||||
}, {
|
||||
type: "createTypedQuestion",
|
||||
question,
|
||||
});
|
||||
} catch (error) {
|
||||
devlog("Error creating question", error);
|
||||
enqueueSnackbar("Не удалось создать вопрос");
|
||||
}
|
||||
});
|
||||
|
||||
@ -9,6 +9,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
|
||||
export function useQuestions() {
|
||||
console.log("вызываю вопросы")
|
||||
const quiz = useCurrentQuiz();
|
||||
const { isLoading, error, isValidating } = useSWR(["questions", quiz?.backendId], ([, id]) => questionApi.getList({ quiz_id: id }), {
|
||||
onSuccess: setQuestions,
|
||||
|
||||
@ -8,6 +8,8 @@ export type QuestionsStore = {
|
||||
openedModalSettingsId: string | null;
|
||||
dragQuestionContentId: string | null;
|
||||
openBranchingPanel: boolean;
|
||||
desireToOpenABranchingModal: string | null;
|
||||
editSomeQuestion: string | null;
|
||||
};
|
||||
|
||||
const initialState: QuestionsStore = {
|
||||
@ -15,6 +17,9 @@ const initialState: QuestionsStore = {
|
||||
openedModalSettingsId: null as null,
|
||||
dragQuestionContentId: null,
|
||||
openBranchingPanel: false,
|
||||
desireToOpenABranchingModal: null as null,
|
||||
editSomeQuestion: null as null,
|
||||
|
||||
};
|
||||
|
||||
export const useQuestionsStore = create<QuestionsStore>()(
|
||||
|
||||
@ -65,7 +65,7 @@ export default function QuizPreviewLayout() {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
p: "16px",
|
||||
p: "40px 20px 20px",
|
||||
whiteSpace: "break-spaces",
|
||||
overflowY: "auto",
|
||||
flexGrow: 1,
|
||||
|
||||
@ -7,12 +7,14 @@ import {
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Typography, useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionEmoji } from "model/questionTypes/emoji";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionEmoji;
|
||||
@ -20,45 +22,78 @@ interface Props {
|
||||
|
||||
export default function Emoji({ question }: Props) {
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
|
||||
const theme = useTheme();
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue((event.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<FormLabel id="quiz-question-radio-group" data-cy="question-title">{question.title}</FormLabel>
|
||||
<FormLabel id="quiz-question-radio-group" data-cy="question-title" sx={{fontSize: "24px", fontWeight: 500, marginBottom: "25px"}}>{question.title}</FormLabel>
|
||||
<RadioGroup
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
sx={{display: "flex",
|
||||
flexDirection: "row", gap: "42px"}}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
control={
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
overflow: "hidden",
|
||||
maxWidth: "317px",
|
||||
width: "100%",
|
||||
height: "255px"
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }} data-cy="variant-answer">
|
||||
<Typography>{`${variant.extendedText} ${variant.answer}`}</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title="Подсказка" placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<Box
|
||||
sx={{ display: "flex", alignItems: "center", height: "193px", background: "#ffffff" }}
|
||||
>
|
||||
<Box sx={{ width: "100%", display: "flex", justifyContent: "center" }}>
|
||||
{variant.extendedText && (
|
||||
<Typography fontSize={"100px"}>{`${variant.extendedText}`}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
sx={{
|
||||
margin: 0,
|
||||
padding: "15px",
|
||||
color: "#4D4D4D",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
background: "#f2f3f7"
|
||||
}}
|
||||
control={
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
}}
|
||||
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
|
||||
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }} data-cy="variant-answer">
|
||||
<Typography>{`${variant.answer}`}</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title="Подсказка" placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
@ -16,19 +16,22 @@ export default function Page({ question }: Props) {
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" data-cy="question-title">{question.title}</Typography>
|
||||
<Typography data-cy="question-text">{question.content.text}</Typography>
|
||||
<Typography variant="h6" data-cy="question-title" sx={{ paddingBottom: "25px" }}>{question.title}</Typography>
|
||||
<Typography data-cy="question-text" sx={{ paddingBottom: "20px" }}>{question.content.text}</Typography>
|
||||
{question.content.picture && (
|
||||
<img
|
||||
src={question.content.picture}
|
||||
alt=""
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "block",
|
||||
objectFit: "scale-down",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
/>
|
||||
<Box sx={{borderRadius: "12px",
|
||||
border: "1px solid #9A9AAF", width: "100%", overflow: "hidden"}}>
|
||||
<img
|
||||
src={question.content.picture}
|
||||
alt=""
|
||||
style={{
|
||||
display: "block",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -1,82 +1,89 @@
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
useRadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionVariant } from "model/questionTypes/variant";
|
||||
import CustomRadio from "@ui_kit/CustomRadio";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionVariant;
|
||||
question: QuizQuestionVariant;
|
||||
}
|
||||
|
||||
export default function Variant({ question }: Props) {
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue((event.target as HTMLInputElement).value);
|
||||
};
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue((event.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}>
|
||||
<Typography>{question.title}</Typography>
|
||||
<RadioGroup
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
data-cy="variant-answer"
|
||||
control={
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Typography>
|
||||
{variant.answer}
|
||||
</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
<img
|
||||
src={question.content.back}
|
||||
style={{
|
||||
display: "block",
|
||||
objectFit: "scale-down",
|
||||
alignSelf: "start",
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<FormLabel id="quiz-question-radio-group" data-cy="question-title" sx={{color: "#000000", marginBottom: "20px", fontSize: "24px", fontWeight: 500}}>
|
||||
{question.title}
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
gap: "20px"
|
||||
}}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
data-cy="variant-answer"
|
||||
labelPlacement="start"
|
||||
sx={{borderRadius: "12px",
|
||||
border: value === value ? "1px solid #7E2AEA" : "1px solid #9A9AAF",
|
||||
padding: "20px",
|
||||
justifyContent: "space-between",
|
||||
maxWidth: "685px",
|
||||
width: "100%",
|
||||
margin: 0
|
||||
}}
|
||||
control={
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
}}
|
||||
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Typography>
|
||||
{variant.answer}
|
||||
</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useState, ChangeEvent, useEffect } from "react";
|
||||
import { useState, ChangeEvent } from "react";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
@ -7,122 +7,129 @@ import {
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Typography, useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import type { QuestionVariant } from "model/questionTypes/shared";
|
||||
import type { QuizQuestionVarImg } from "model/questionTypes/varimg";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionVarImg;
|
||||
question: QuizQuestionVarImg;
|
||||
}
|
||||
|
||||
export default function Varimg({ question }: Props) {
|
||||
const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1);
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setSelectedVariantIndex(
|
||||
question.content.variants.findIndex(
|
||||
(variant) => variant.answer === event.target.value
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const currentVariant: QuestionVariant | undefined =
|
||||
question.content.variants[selectedVariantIndex];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<FormLabel
|
||||
id="quiz-question-radio-group"
|
||||
data-cy="question-title"
|
||||
>
|
||||
{question.title}
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={currentVariant?.answer ?? ""}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
data-cy="variant-answer"
|
||||
control={
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Typography>{variant.answer}</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #E3E3E3",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{currentVariant ?
|
||||
currentVariant.extendedText ? (
|
||||
<img
|
||||
src={currentVariant.extendedText}
|
||||
data-cy="variant-image"
|
||||
alt="question variant"
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "block",
|
||||
objectFit: "scale-down",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography p={2}>Картинка отсутствует</Typography>
|
||||
) : question.content.back ? (
|
||||
<img
|
||||
src={question.content.back}
|
||||
data-cy="variant-image"
|
||||
alt="question variant"
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "block",
|
||||
objectFit: "scale-down",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography p={2}>Выберите вариант</Typography>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1);
|
||||
const theme = useTheme();
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setSelectedVariantIndex(
|
||||
question.content.variants.findIndex(
|
||||
(variant) => variant.answer === event.target.value
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const currentVariant: QuestionVariant | undefined =
|
||||
question.content.variants[selectedVariantIndex];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "40px",
|
||||
}}
|
||||
>
|
||||
<FormControl sx={{maxWidth: "900px",
|
||||
width: "100%", gap: "25px"}}>
|
||||
<FormLabel
|
||||
id="quiz-question-radio-group"
|
||||
data-cy="question-title"
|
||||
sx={{fontSize: "24px", fontWeight: 500}}
|
||||
>
|
||||
{question.title}
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={currentVariant?.answer ?? ""}
|
||||
onChange={handleChange}
|
||||
sx={{gap: "20px"}}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
data-cy="variant-answer"
|
||||
sx={{
|
||||
margin: 0,
|
||||
borderRadius: "5px",
|
||||
padding: "15px",
|
||||
color: "#4D4D4D",
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
display: "flex",
|
||||
}}
|
||||
control={
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
}}
|
||||
checkedIcon={<RadioCheck />} icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Typography>{variant.answer}</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #E3E3E3",
|
||||
maxWidth: "400px",
|
||||
height: "400px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderRadius: "12px",
|
||||
marginTop: "50px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{currentVariant?.extendedText ? (
|
||||
<img
|
||||
src={currentVariant.extendedText}
|
||||
data-cy="variant-image"
|
||||
alt="question variant"
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "block",
|
||||
objectFit: "scale-down",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography p={2}>
|
||||
{selectedVariantIndex === -1
|
||||
? "Выберите вариант"
|
||||
: "Картинка отсутствует"}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,6 +46,7 @@ export default function Sidebar() {
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
boxSizing: "border-box",
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
|
||||
@ -4,8 +4,8 @@ import InstallQuiz from "../pages/InstallQuiz/InstallQuiz";
|
||||
import FormQuestionsPage from "../pages/Questions/Form/FormQuestionsPage";
|
||||
import QuestionsPage from "../pages/Questions/QuestionsPage";
|
||||
import { QuestionsMap } from "../pages/QuestionsMap";
|
||||
import { Result } from "../pages/Result/Result";
|
||||
import { Setting } from "../pages/Result/Setting";
|
||||
import { ResultPage } from "../pages/ResultPage/ResultPage";
|
||||
import { ResultSettings } from "../pages/ResultPage/ResultSettings";
|
||||
import StartPageSettings from "../pages/startPage/StartPageSettings";
|
||||
import StepOne from "../pages/startPage/stepOne";
|
||||
import Steptwo from "../pages/startPage/steptwo";
|
||||
@ -31,10 +31,7 @@ export default function SwitchStepPages({
|
||||
return <StartPageSettings />;
|
||||
}
|
||||
case 1: return quizType === "form" ? <FormQuestionsPage /> : <QuestionsPage />;
|
||||
case 2: {
|
||||
if (!quizResults) return <Result />;
|
||||
return <Setting />;
|
||||
}
|
||||
case 2: return <ResultPage />;
|
||||
case 3: return <QuestionsMap />;
|
||||
case 4: return <ContactFormPage />;
|
||||
case 5: return <InstallQuiz />;
|
||||
|
||||
@ -8628,6 +8628,13 @@ react-easy-crop@^5.0.0:
|
||||
normalize-wheel "^1.0.1"
|
||||
tslib "2.0.1"
|
||||
|
||||
react-error-boundary@^4.0.11:
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c"
|
||||
integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
|
||||
react-error-overlay@^6.0.11:
|
||||
version "6.0.11"
|
||||
resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user