Merge branch 'dev' of penahub.gitlab.yandexcloud.net:frontend/squiz into view-answers-fixes
This commit is contained in:
commit
5987a20843
@ -35,7 +35,6 @@
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-cytoscapejs": "^2.0.0",
|
||||
"react-datepicker": "^4.24.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
@ -78,7 +77,6 @@
|
||||
"@types/cytoscape-popper": "^2.0.4",
|
||||
"@types/react-beautiful-dnd": "^13.1.4",
|
||||
"@types/react-cytoscapejs": "^1.2.4",
|
||||
"@types/react-datepicker": "^4.19.3",
|
||||
"craco-alias": "^3.0.1",
|
||||
"cypress": "^13.4.0"
|
||||
}
|
||||
|
||||
30
src/App.tsx
30
src/App.tsx
@ -5,7 +5,7 @@ import "dayjs/locale/ru";
|
||||
import SigninDialog from "./pages/auth/Signin";
|
||||
import SignupDialog from "./pages/auth/Signup";
|
||||
import { ViewPage } from "./pages/ViewPublicationPage";
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import { BrowserRouter, Route, Routes, useLocation, useNavigate, Navigate } from "react-router-dom";
|
||||
import "./index.css";
|
||||
import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
|
||||
import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
|
||||
@ -34,6 +34,8 @@ const routeslink = [
|
||||
|
||||
export default function App() {
|
||||
const userId = useUserStore((state) => state.userId);
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useUserFetcher({
|
||||
url: `https://hub.pena.digital/user/${userId}`,
|
||||
@ -48,23 +50,31 @@ export default function App() {
|
||||
}
|
||||
},
|
||||
});
|
||||
if (location.state?.redirectTo)
|
||||
return <Navigate to={location.state.redirectTo} replace state={{ backgroundLocation: location }} />
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContactFormModal />
|
||||
<BrowserRouter>
|
||||
{location.state?.backgroundLocation && (
|
||||
<Routes>
|
||||
{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={<EditPage />} />
|
||||
<Route path="crop" element={<ImageCrop />} />
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route path="/signin" element={<SigninDialog />} />
|
||||
<Route path="/signup" element={<SignupDialog />} />
|
||||
<Route path="/view" element={<ViewPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
)}
|
||||
|
||||
<Routes location={location.state?.backgroundLocation || location}>
|
||||
{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={<EditPage />} />
|
||||
<Route path="crop" element={<ImageCrop />} />
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route path="/signin" element={<Navigate to="/" replace state={{ redirectTo: "/signin" }} />} />
|
||||
<Route path="/signup" element={<Navigate to="/" replace state={{ redirectTo: "/signup" }} />} />/>
|
||||
|
||||
<Route path="/view" element={<ViewPage />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ function createQuestion(body: CreateQuestionRequest) {
|
||||
}
|
||||
|
||||
async function getQuestionList(body?: Partial<GetQuestionListRequest>) {
|
||||
console.log("body" , body)
|
||||
if (!body?.quiz_id) return null;
|
||||
|
||||
const response = await makeRequest<GetQuestionListRequest, GetQuestionListResponse>({
|
||||
|
||||
43
src/assets/icons/Checkbox.tsx
Normal file
43
src/assets/icons/Checkbox.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { Box, useTheme } from "@mui/material";
|
||||
|
||||
type CheckboxIconProps = {
|
||||
checked?: boolean;
|
||||
};
|
||||
|
||||
export const CheckboxIcon = ({ checked = false }: CheckboxIconProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "24px",
|
||||
width: "24px",
|
||||
borderRadius: "6px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: checked
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
}}
|
||||
>
|
||||
{checked && (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 25 18"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M2 9L10 16.5L22.5 1.5"
|
||||
stroke="#ffffff"
|
||||
strokeWidth="4"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -12,6 +12,8 @@ import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import lightTheme from "./utils/themes/light";
|
||||
import { SWRConfig } from "swr";
|
||||
import {BrowserRouter} from "react-router-dom";
|
||||
|
||||
|
||||
|
||||
dayjs.locale("ru");
|
||||
@ -28,13 +30,16 @@ root.render(
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="ru" localeText={localeText}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<SnackbarProvider
|
||||
preventDuplicate={true}
|
||||
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
|
||||
>
|
||||
<CssBaseline />
|
||||
<App />
|
||||
</SnackbarProvider>
|
||||
<BrowserRouter>
|
||||
|
||||
<SnackbarProvider
|
||||
preventDuplicate={true}
|
||||
style={{ backgroundColor: lightTheme.palette.brightPurple.main }}
|
||||
>
|
||||
<CssBaseline />
|
||||
<App />
|
||||
</SnackbarProvider>
|
||||
</BrowserRouter>
|
||||
</ThemeProvider>
|
||||
</LocalizationProvider>
|
||||
</DndProvider>
|
||||
|
||||
@ -60,7 +60,6 @@ export interface QuizQuestionBase {
|
||||
type?: QuestionType | null;
|
||||
expanded: boolean;
|
||||
openedModalSettings: boolean;
|
||||
required: boolean;
|
||||
deleted: boolean;
|
||||
deleteTimeoutId: number;
|
||||
content: {
|
||||
|
||||
@ -7,7 +7,7 @@ import QuizLogo from "./images/icons/QuizLogo";
|
||||
import { useMediaQuery, useTheme } from "@mui/material";
|
||||
import { setIsContactFormOpen } from "../../stores/contactForm";
|
||||
import { useUserStore } from "@root/user";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate, Link, useLocation } from "react-router-dom";
|
||||
|
||||
const buttonMenu = ["Меню 1", "Меню 2", "Меню 3", "Меню 4", "Меню 5", "Меню 1", "Меню 2"];
|
||||
|
||||
@ -18,8 +18,9 @@ export default function Component() {
|
||||
const [select, setSelect] = React.useState(0);
|
||||
const userId = useUserStore((state) => state.userId);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation()
|
||||
|
||||
const onClick = () => (userId ? navigate("/list") : setIsContactFormOpen(true));
|
||||
const onClick = () => (userId ? navigate("/list") : navigate("/signin"));
|
||||
|
||||
return (
|
||||
<SectionStyled
|
||||
@ -66,7 +67,7 @@ export default function Component() {
|
||||
{/* ))}*/}
|
||||
{/*</Box>*/}
|
||||
<Button
|
||||
variant="outlined"
|
||||
variant="outlined"
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
color: "black",
|
||||
|
||||
@ -9,7 +9,7 @@ import Blog from './Blog';
|
||||
import HowItWorks from './HowItWorks';
|
||||
import BusinessPluses from './BusinessPluses';
|
||||
import HowToUse from './HowToUse';
|
||||
import WhatTheySay from './WhatTheySay';
|
||||
|
||||
import StartWithTemplates from './StartWithTemplates';
|
||||
import WhatTheFeatures from './WhatTheFeatures';
|
||||
import FullScreenDialog from "./headerMobileLanding";
|
||||
@ -18,13 +18,14 @@ import Collaboration from "./Collaboration";
|
||||
export default function Landing() {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
|
||||
return (
|
||||
<>
|
||||
<CssBaseline />
|
||||
<Header/>
|
||||
<Hero/>
|
||||
<Counter/>
|
||||
<Collaboration/>
|
||||
{/* <Collaboration/> */}
|
||||
<HowItWorks/>
|
||||
<BusinessPluses/>
|
||||
<HowToUse/>
|
||||
|
||||
@ -7,7 +7,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 { deleteQuestion, cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId, clearRuleForAll } from "@root/questions/actions";
|
||||
import { deleteQuestion, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId, clearRuleForAll } from "@root/questions/actions";
|
||||
import { cleardragQuestionContentId } from "@root/uiTools/actions";
|
||||
import { withErrorBoundary } from "react-error-boundary";
|
||||
|
||||
import { storeToNodes } from "./helper";
|
||||
@ -22,6 +23,7 @@ import type {
|
||||
ElementDefinition,
|
||||
} from "cytoscape";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
type PopperItem = {
|
||||
id: () => string;
|
||||
@ -121,7 +123,7 @@ function CsComponent({
|
||||
}: Props) {
|
||||
const quiz = useCurrentQuiz();
|
||||
|
||||
const { dragQuestionContentId, desireToOpenABranchingModal } = useQuestionsStore()
|
||||
const { dragQuestionContentId, desireToOpenABranchingModal } = useUiTools()
|
||||
const trashQuestions = useQuestionsStore().questions
|
||||
const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null)
|
||||
const [startCreate, setStartCreate] = useState("");
|
||||
@ -145,8 +147,8 @@ function CsComponent({
|
||||
}, [desireToOpenABranchingModal])
|
||||
useLayoutEffect(() => {
|
||||
updateOpenedModalSettingsId()
|
||||
// updateRootContentId(quiz.id, "")
|
||||
// clearRuleForAll()
|
||||
// updateRootContentId(quiz.id, "")
|
||||
// clearRuleForAll()
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
|
||||
@ -157,6 +159,12 @@ function CsComponent({
|
||||
}, [modalQuestionTargetContentId])
|
||||
|
||||
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
|
||||
|
||||
|
||||
//запрещаем работу родителя-ребенка если это один и тот же вопрос
|
||||
if (parentNodeContentId === targetNodeContentId) return
|
||||
|
||||
|
||||
const cy = cyRef?.current
|
||||
const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length
|
||||
//если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
|
||||
@ -218,6 +226,7 @@ function CsComponent({
|
||||
|
||||
|
||||
const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => {
|
||||
console.log("старт удаление")
|
||||
const deleteNodes = [] as string[]
|
||||
const deleteEdges: any = []
|
||||
const cy = cyRef?.current
|
||||
@ -304,7 +313,6 @@ function CsComponent({
|
||||
|
||||
const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => {
|
||||
|
||||
console.log("target ", targetQuestionContentId, "parent ", parentQuestionContentId)
|
||||
|
||||
|
||||
updateQuestion(targetQuestionContentId, question => {
|
||||
@ -317,7 +325,6 @@ function CsComponent({
|
||||
|
||||
//чистим rule родителя
|
||||
const parentQuestion = getQuestionByContentId(parentQuestionContentId)
|
||||
console.log(parentQuestion.content.rule.parentId)
|
||||
const newRule = {}
|
||||
const newChildren = [...parentQuestion.content.rule.children]
|
||||
newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1);
|
||||
@ -631,7 +638,6 @@ function CsComponent({
|
||||
},
|
||||
});
|
||||
let gearsPopper = null
|
||||
console.log('POPE', node.data())
|
||||
if (node.data().root !== true) {
|
||||
gearsPopper = node.popper({
|
||||
popper: {
|
||||
@ -639,7 +645,6 @@ console.log('POPE', node.data())
|
||||
modifiers: [{ name: "flip", options: { boundary: node } }],
|
||||
},
|
||||
content: ([item]) => {
|
||||
console.log('PEPO', item.id())
|
||||
const itemId = item.id();
|
||||
|
||||
const itemElement = gearsContainer.current?.querySelector(
|
||||
@ -670,7 +675,6 @@ console.log('POPE', node.data())
|
||||
};
|
||||
|
||||
const onZoom = (event: AbstractEventObject) => {
|
||||
console.log('ZOOOOM')
|
||||
const zoom = event.cy.zoom();
|
||||
|
||||
//update();
|
||||
|
||||
@ -1,19 +1,30 @@
|
||||
import { Box } from "@mui/material"
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { deleteQuestion, updateDragQuestionContentId, updateQuestion } from "@root/questions/actions"
|
||||
import { useEffect, useRef, useLayoutEffect } from "react";
|
||||
import { deleteQuestion, clearRuleForAll, updateQuestion, updateOpenedModalSettingsId } from "@root/questions/actions"
|
||||
import { updateRootContentId } from "@root/quizes/actions"
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks"
|
||||
import { useQuestionsStore } from "@root/questions/store"
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
interface Props {
|
||||
setOpenedModalQuestions: (open: boolean) => void;
|
||||
modalQuestionTargetContentId: string;
|
||||
}
|
||||
export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => {
|
||||
|
||||
const quiz = useCurrentQuiz();
|
||||
const { dragQuestionContentId, questions } = useQuestionsStore()
|
||||
|
||||
|
||||
useLayoutEffect(() => {
|
||||
updateOpenedModalSettingsId()
|
||||
console.log("first render firstComponent")
|
||||
// updateRootContentId(quiz.id, "")
|
||||
// clearRuleForAll()
|
||||
}, [])
|
||||
|
||||
|
||||
const { questions } = useQuestionsStore()
|
||||
const { dragQuestionContentId } = useUiTools()
|
||||
const Container = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const modalOpen = () => setOpenedModalQuestions(true)
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { FirstNodeField } from "./FirstNodeField";
|
||||
import CsComponent from "./CsComponent";
|
||||
import { useQuestionsStore } from "@root/questions/store"
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useState } from "react";
|
||||
import {BranchingQuestionsModal} from "../BranchingQuestionsModal"
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
|
||||
export const BranchingMap = () => {
|
||||
const quiz = useCurrentQuiz();
|
||||
const { dragQuestionContentId } = useQuestionsStore()
|
||||
const { dragQuestionContentId } = useUiTools()
|
||||
const [modalQuestionParentContentId, setModalQuestionParentContentId] = useState<string>("")
|
||||
const [modalQuestionTargetContentId, setModalQuestionTargetContentId] = useState<string>("")
|
||||
const [openedModalQuestions, setOpenedModalQuestions] = useState<boolean>(false)
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import {Box, Typography, Switch, useTheme, Button, useMediaQuery, SxProps, Theme} from "@mui/material";
|
||||
|
||||
import { QuestionsList } from "./QuestionsList";
|
||||
import { updateOpenBranchingPanel } from "@root/questions/actions";
|
||||
import { updateOpenBranchingPanel } from "@root/uiTools/actions";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import {useRef} from "react";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
|
||||
export const BranchingPanel = (sx?: SxProps<Theme>) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
const {openBranchingPanel} = useUiTools()
|
||||
const ref = useRef()
|
||||
return (
|
||||
<Box sx={{ userSelect: "none", maxWidth: "350px", width: "100%" }}>
|
||||
@ -26,10 +27,10 @@ export const BranchingPanel = (sx?: SxProps<Theme>) => {
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
value={openBranchingPanel}
|
||||
onChange={(_, value) => {
|
||||
updateOpenBranchingPanel(value)
|
||||
}}
|
||||
checked={openBranchingPanel}
|
||||
onChange={
|
||||
(e) => updateOpenBranchingPanel(e.target.checked)
|
||||
}
|
||||
sx={{
|
||||
width: 50,
|
||||
height: 30,
|
||||
@ -76,7 +77,7 @@ export const BranchingPanel = (sx?: SxProps<Theme>) => {
|
||||
/>
|
||||
<Box>
|
||||
<Typography ref={ref} sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
|
||||
Логика ветвления
|
||||
Логика ветвления
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontSize: "12px" }}>
|
||||
Настройте связи между вопросами
|
||||
|
||||
@ -11,7 +11,8 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { copyQuestion, deleteQuestion, updateOpenBranchingPanel, updateDesireToOpenABranchingModal, deleteQuestionWithTimeout } from "@root/questions/actions";
|
||||
import { copyQuestion, deleteQuestion, deleteQuestionWithTimeout, clearRuleForAll, updateQuestion, getQuestionByContentId } from "@root/questions/actions";
|
||||
import { updateOpenBranchingPanel, updateDesireToOpenABranchingModal, } from "@root/uiTools/actions";
|
||||
import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
|
||||
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "../../assets/icons/questionsPage/branching";
|
||||
@ -23,6 +24,8 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateOpenedModalSettingsId } from "@root/questions/actions";
|
||||
import { updateRootContentId } from "@root/quizes/actions";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
@ -40,7 +43,8 @@ export default function ButtonsOptions({
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||
const quiz = useCurrentQuiz();
|
||||
const { openBranchingPanel, questions } = useQuestionsStore.getState();
|
||||
const { questions } = useQuestionsStore.getState();
|
||||
const { openBranchingPanel } = useUiTools();
|
||||
|
||||
const openedModal = () => {
|
||||
updateOpenedModalSettingsId(question.id);
|
||||
@ -64,15 +68,15 @@ export default function ButtonsOptions({
|
||||
title: "Настройки",
|
||||
value: "setting",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Clue
|
||||
color={switchState === "help" ? "#ffffff" : theme.palette.grey3.main}
|
||||
/>
|
||||
),
|
||||
title: "Подсказка",
|
||||
value: "help",
|
||||
},
|
||||
// {
|
||||
// icon: (
|
||||
// <Clue
|
||||
// color={switchState === "help" ? "#ffffff" : theme.palette.grey3.main}
|
||||
// />
|
||||
// ),
|
||||
// title: "Подсказка",
|
||||
// value: "help",
|
||||
// },
|
||||
{
|
||||
icon: (
|
||||
<Branching
|
||||
|
||||
@ -23,7 +23,7 @@ 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, updateDesireToOpenABranchingModal } from "@root/questions/actions";
|
||||
import { updateOpenBranchingPanel, updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
@ -29,7 +29,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
||||
flexDirection: isWrappColumn ? "column" : null,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
{/* <Box
|
||||
sx={{
|
||||
pt: "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
@ -67,7 +67,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
@ -86,10 +86,10 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
checked={!question.content.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
question.required = !target.checked;
|
||||
updateQuestion<QuizQuestionDate>(question.id, question => {
|
||||
question.content.required = !target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -109,7 +109,7 @@ export default function SettingsData({ question }: SettingsDataProps) {
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
updateQuestion<QuizQuestionDate>(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
|
||||
@ -3,8 +3,10 @@ import { Box, ListItem, Typography, useTheme } from "@mui/material";
|
||||
import { memo, useEffect } from "react";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import QuestionsPageCard from "./QuestionPageCard";
|
||||
import { cancelQuestionDeletion, updateEditSomeQuestion } from "@root/questions/actions";
|
||||
import { cancelQuestionDeletion } from "@root/questions/actions";
|
||||
import { updateEditSomeQuestion } from "@root/uiTools/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
|
||||
type Props = {
|
||||
@ -15,7 +17,7 @@ type Props = {
|
||||
|
||||
function DraggableListItem({ question, isDragging, index }: Props) {
|
||||
const theme = useTheme();
|
||||
const { editSomeQuestion } = useQuestionsStore();
|
||||
const { editSomeQuestion } = useUiTools();
|
||||
|
||||
useEffect(() => {
|
||||
if (editSomeQuestion !== null) {
|
||||
|
||||
@ -128,10 +128,10 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
checked={!question.content.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestion(question.id, question => {
|
||||
question.required = !e.target.checked;
|
||||
updateQuestion<QuizQuestionSelect>(question.id, question => {
|
||||
question.content.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -141,7 +141,7 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
updateQuestion<QuizQuestionSelect>(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
|
||||
@ -30,7 +30,7 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
||||
flexDirection: isWrappColumn ? "column" : "none",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
{/* <Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
@ -66,12 +66,12 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
||||
question.content.own = target.checked;
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isTablet ? "20px" : "",
|
||||
pl: "20px",
|
||||
pr: isFigmaTablte ? "30px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@ -85,11 +85,11 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => updateQuestion(question.id, question => {
|
||||
checked={!question.content.required}
|
||||
handleChange={({ target }) => updateQuestion<QuizQuestionEmoji>(question.id, question => {
|
||||
if (question.type !== "emoji") return;
|
||||
|
||||
question.content.required = !e.target.checked;
|
||||
question.content.required = !target.checked;
|
||||
})}
|
||||
/>
|
||||
<Box
|
||||
@ -107,7 +107,7 @@ export default function SettingEmoji({ question }: SettingEmojiProps) {
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||
handleChange={({ target }) => updateQuestion<QuizQuestionEmoji>(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
})}
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
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 FormDraggableListItem from "./FormDraggableListItem";
|
||||
|
||||
|
||||
export const FormDraggableList = () => {
|
||||
const { questions } = useQuestions();
|
||||
|
||||
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||
if (destination) reorderQuestions(source.index, destination.index);
|
||||
|
||||
@ -111,11 +111,11 @@ export default function SettingOptionsAndPict({ question }: SettingOptionsAndPic
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={question.content.required}
|
||||
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||
checked={!question.content.required}
|
||||
handleChange={({ target }) => updateQuestion<QuizQuestionVarImg>(question.id, question => {
|
||||
if (question.type !== "varimg") return;
|
||||
|
||||
question.content.required = target.checked;
|
||||
question.content.required = !target.checked;
|
||||
})}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
@ -126,7 +126,7 @@ export default function SettingOptionsAndPict({ question }: SettingOptionsAndPic
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||
handleChange={({ target }) => updateQuestion<QuizQuestionVarImg>(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = "";
|
||||
})}
|
||||
|
||||
@ -64,7 +64,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
||||
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
{/* <Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
@ -148,7 +148,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
@ -161,7 +161,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
{/* <Box
|
||||
sx={{
|
||||
marginBottom: "5px",
|
||||
opacity: question.content.xy !== "1:1" ? 1 : 0,
|
||||
@ -202,7 +202,7 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
||||
isActive={question.content.format === "masonry"}
|
||||
Icon={FormatIcon1}
|
||||
/>
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Typography
|
||||
sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
@ -211,11 +211,11 @@ export default function SettingOpytionsPict({ question }: SettingOpytionsPictPro
|
||||
<CustomCheckbox
|
||||
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={question.content.required}
|
||||
handleChange={({ target }) => updateQuestion(question.id, question => {
|
||||
checked={!question.content.required}
|
||||
handleChange={({ target }) => updateQuestion<QuizQuestionImages>(question.id, question => {
|
||||
if (question.type !== "images") return;
|
||||
|
||||
question.content.required = target.checked;
|
||||
question.content.required = !target.checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
@ -54,7 +54,7 @@ export default function SettingTextField({
|
||||
marginRight: isFigmaTablte ? "0px" : "32px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
{/* <Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
@ -126,7 +126,7 @@ export default function SettingTextField({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box> */}
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
@ -148,7 +148,7 @@ export default function SettingTextField({
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
{/* <CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
@ -161,7 +161,7 @@ export default function SettingTextField({
|
||||
question.content.autofill = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
@ -169,10 +169,10 @@ export default function SettingTextField({
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
checked={!question.content.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestion(question.id, question => {
|
||||
question.required = !e.target.checked;
|
||||
updateQuestion<QuizQuestionText>(question.id, question => {
|
||||
question.content.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -193,7 +193,7 @@ export default function SettingTextField({
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
updateQuestion<QuizQuestionText>(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked
|
||||
? question.content.innerName
|
||||
|
||||
@ -1,18 +1,23 @@
|
||||
|
||||
import {
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
Box, useMediaQuery, useTheme,
|
||||
} from "@mui/material";
|
||||
import { DraggableList } from "./DraggableList";
|
||||
import { SwitchBranchingPanel } from "./SwitchBranchingPanel";
|
||||
import { BranchingMap } from "./BranchingMap";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
|
||||
export const QuestionSwitchWindowTool = () => {
|
||||
const {openBranchingPanel, questions} = useQuestionsStore.getState()
|
||||
const {questions} = useQuestionsStore.getState()
|
||||
const {openBranchingPanel} = useUiTools()
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
console.log("questions ", questions)
|
||||
console.log("rules ", questions.filter((q) => q.type !== null).map((q) => ({id: q.content.id, rule: q.content.rule})))
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: "20px", flexWrap: "wrap" }}>
|
||||
<Box sx={{ display: "flex", gap: "20px", flexWrap: "wrap", marginBottom: isMobile ? "20px" : undefined }}>
|
||||
<Box sx={{ flexBasis: "796px" }}>
|
||||
{openBranchingPanel? <BranchingMap /> : <DraggableList />}
|
||||
</Box>
|
||||
|
||||
@ -17,12 +17,13 @@ import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
||||
import BranchingQuestions from "./BranchingModal/BranchingQuestionsModal"
|
||||
import { QuestionSwitchWindowTool } from "./QuestionSwitchWindowTool";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateOpenBranchingPanel, updateEditSomeQuestion } from "@root/questions/actions";
|
||||
import { updateOpenBranchingPanel, updateEditSomeQuestion } from "@root/uiTools/actions";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
|
||||
export default function QuestionsPage() {
|
||||
const theme = useTheme();
|
||||
const { openedModalSettingsId, openBranchingPanel } = useQuestionsStore();
|
||||
const { openedModalSettingsId, openBranchingPanel } = useUiTools();
|
||||
const isMobile = false//useMediaQuery(theme.breakpoints.down(660));
|
||||
const quiz = useCurrentQuiz();
|
||||
useLayoutEffect(() => {
|
||||
|
||||
@ -148,12 +148,12 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
checked={!question.content.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestion(question.id, question => {
|
||||
updateQuestion<QuizQuestionRating>(question.id, question => {
|
||||
if (question.type !== "rating") return;
|
||||
|
||||
question.required = !e.target.checked;
|
||||
question.content.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -78,12 +78,12 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px", alignItems: isMobile ? "flex-end" : "center" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
checked={!question.content.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestion(question.id, question => {
|
||||
updateQuestion<QuizQuestionNumber>(question.id, question => {
|
||||
if (question.type !== "number") return;
|
||||
|
||||
question.required = !e.target.checked;
|
||||
question.content.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -2,11 +2,11 @@ import { useParams } from "react-router-dom";
|
||||
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"
|
||||
import { updateOpenBranchingPanel, updateEditSomeQuestion, updateDragQuestionContentId } from "@root/uiTools/actions"
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
|
||||
const getItemStyle = (isDragging: any, draggableStyle: any) => ({
|
||||
@ -24,7 +24,7 @@ const getItemStyle = (isDragging: any, draggableStyle: any) => ({
|
||||
type AnyQuestion = UntypedQuizQuestion | AnyTypedQuizQuestion
|
||||
|
||||
export const QuestionsList = () => {
|
||||
const { desireToOpenABranchingModal } = useQuestionsStore()
|
||||
const { desireToOpenABranchingModal } = useUiTools()
|
||||
const trashQuestions = useQuestionsStore().questions
|
||||
const questions = trashQuestions.filter((question) => question.type !== "result")
|
||||
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
|
||||
|
||||
import {Box, Typography, Switch, useTheme, Button, useMediaQuery} from "@mui/material";
|
||||
|
||||
import { QuestionsList } from "./QuestionsList";
|
||||
import { updateOpenBranchingPanel } from "@root/questions/actions";
|
||||
import { updateOpenBranchingPanel } from "@root/uiTools/actions";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import {useRef} from "react";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
|
||||
export const SwitchBranchingPanel = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(660));
|
||||
const {openBranchingPanel} = useQuestionsStore.getState()
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
|
||||
const {openBranchingPanel} = useUiTools()
|
||||
const ref = useRef()
|
||||
return (
|
||||
|
||||
return ( !isTablet || openBranchingPanel ?
|
||||
<Box sx={{ userSelect: "none", maxWidth: "350px", width: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
@ -25,10 +31,10 @@ export const SwitchBranchingPanel = () => {
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
value={openBranchingPanel}
|
||||
onChange={(_, value) => {
|
||||
updateOpenBranchingPanel(value)
|
||||
}}
|
||||
checked={openBranchingPanel}
|
||||
onChange={
|
||||
(e) => updateOpenBranchingPanel(e.target.checked)
|
||||
}
|
||||
sx={{
|
||||
width: 50,
|
||||
height: 30,
|
||||
@ -85,5 +91,7 @@ export const SwitchBranchingPanel = () => {
|
||||
</Box>
|
||||
{ openBranchingPanel && <QuestionsList /> }
|
||||
</Box>
|
||||
:
|
||||
<></>
|
||||
);
|
||||
};
|
||||
|
||||
@ -40,7 +40,7 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
|
||||
}}
|
||||
>
|
||||
<Typography>Настройки вопроса</Typography>
|
||||
<CustomCheckbox
|
||||
{/* <CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
@ -52,17 +52,17 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
|
||||
question.content.autofill = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
checked={!question.content.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestion(question.id, question => {
|
||||
question.required = !e.target.checked;
|
||||
updateQuestion<QuizQuestionFile>(question.id, question => {
|
||||
question.content.required = !e.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -82,7 +82,7 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
updateQuestion<QuizQuestionFile>(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import {
|
||||
Box,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
@ -12,165 +12,166 @@ import { useDebouncedCallback } from "use-debounce";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
|
||||
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionVariant;
|
||||
question: QuizQuestionVariant;
|
||||
}
|
||||
|
||||
export default function ResponseSettings({ question }: Props) {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const updateQuestionInnerName = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 200);
|
||||
const updateQuestionInnerName = useDebouncedCallback((value) => {
|
||||
setQuestionInnerName(question.id, value);
|
||||
}, 200);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isTablet ? "column" : "none",
|
||||
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
|
||||
}}
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: isTablet ? "column" : "none",
|
||||
marginRight: isFigmaTablte ? (isMobile ? "0" : "0px") : "30px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
{/* <CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Длинный текстовый ответ"}
|
||||
checked={question.content.largeCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (!("largeCheck" in question.content)) return;
|
||||
|
||||
question.content.largeCheck = target.checked;
|
||||
});
|
||||
}}
|
||||
/> */}
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={question.content.multi}
|
||||
dataCy="multiple-answers-checkbox"
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (!("multi" in question.content)) return;
|
||||
|
||||
question.content.multi = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (!("own" in question.content)) return;
|
||||
|
||||
question.content.own = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isTablet ? "20px" : "34px") : "28px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.content.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(question.id, (question) => {
|
||||
question.content.required = !target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "9px",
|
||||
height: isMobile ? "42px" : "26px",
|
||||
alignItems: "start",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion<QuizQuestionVariant>(question.id, (question) => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked
|
||||
? question.content.innerName
|
||||
: "";
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{isMobile && (
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Длинный текстовый ответ"}
|
||||
checked={question.content.largeCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
if (!("largeCheck" in question.content)) return;
|
||||
|
||||
question.content.largeCheck = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={question.content.multi}
|
||||
dataCy="multiple-answers-checkbox"
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
if (!("multi" in question.content)) return;
|
||||
|
||||
question.content.multi = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
if (!("own" in question.content)) return;
|
||||
|
||||
question.content.own = target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isTablet ? "20px" : "34px") : "28px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!question.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
question.required = !target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "9px",
|
||||
height: isMobile ? "42px" : "26px",
|
||||
alignItems: "start",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestion(question.id, question => {
|
||||
question.content.innerNameCheck = target.checked;
|
||||
question.content.innerName = target.checked ? question.content.innerName : "";
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{isMobile && (
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => updateQuestionInnerName(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => updateQuestionInnerName(target.value)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@ -16,17 +16,14 @@ export const FirstEntry = () => {
|
||||
|
||||
const create = () => {
|
||||
if (quiz?.config.haveRoot) {
|
||||
console.log("createFrontResult")
|
||||
questions
|
||||
.filter((question:AnyTypedQuizQuestion) => {
|
||||
console.log(question)
|
||||
return question.type !== null && question.content.rule.parentId.length !== 0 && question.content.rule.children.length === 0
|
||||
})
|
||||
.forEach(question => {
|
||||
createFrontResult(quiz.id, question.content.id)
|
||||
})
|
||||
} else {
|
||||
console.log("createFrontResult")
|
||||
createFrontResult(quiz.id, "line")
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ export const ResultSettings = () => {
|
||||
const { questions } = useQuestionsStore()
|
||||
const quiz = useCurrentQuiz()
|
||||
const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result")
|
||||
console.log("опросник ", quiz)
|
||||
const [quizExpand, setQuizExpand] = useState(true)
|
||||
const [resultContract, setResultContract] = useState(true)
|
||||
const isReadyToLeaveRef = useRef(true);
|
||||
|
||||
@ -3,17 +3,14 @@ import { Box, Button, useTheme } from "@mui/material";
|
||||
|
||||
import { useQuizViewStore } from "@root/quizView";
|
||||
|
||||
import type {
|
||||
AnyTypedQuizQuestion,
|
||||
QuizQuestionBase,
|
||||
} from "../../model/questionTypes/shared";
|
||||
import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared";
|
||||
import { getQuestionByContentId } from "@root/questions/actions";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
type FooterProps = {
|
||||
questions: AnyTypedQuizQuestion[];
|
||||
setCurrentQuestion: (step: AnyTypedQuizQuestion) => void;
|
||||
question: QuizQuestionBase;
|
||||
question: AnyTypedQuizQuestion;
|
||||
};
|
||||
|
||||
export const Footer = ({
|
||||
@ -21,9 +18,6 @@ export const Footer = ({
|
||||
questions,
|
||||
question,
|
||||
}: FooterProps) => {
|
||||
const [disabledQuestionsId, setDisabledQuestionsId] = useState<Set<string>>(
|
||||
new Set()
|
||||
);
|
||||
const [disablePreviousButton, setDisablePreviousButton] =
|
||||
useState<boolean>(false);
|
||||
const [disableNextButton, setDisableNextButton] = useState<boolean>(false);
|
||||
@ -58,11 +52,17 @@ export const Footer = ({
|
||||
({ questionId }) => questionId === question.content.id
|
||||
);
|
||||
|
||||
if (question.required && answer?.changed) {
|
||||
if ("required" in question.content && question.content.required && answer) {
|
||||
setDisableNextButton(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (question.required && !answer?.changed) {
|
||||
if (
|
||||
"required" in question.content &&
|
||||
question.content.required &&
|
||||
!answer
|
||||
) {
|
||||
setDisableNextButton(true);
|
||||
|
||||
return;
|
||||
@ -103,7 +103,7 @@ export const Footer = ({
|
||||
|
||||
let readyBeNextQuestion = "";
|
||||
|
||||
question.content.rule.main.forEach(({ next, rules }) => {
|
||||
(question as QuizQuestionBase).content.rule.main.forEach(({ next, rules }) => {
|
||||
let longerArray = Math.max(
|
||||
rules[0].answers.length,
|
||||
[answer?.answer].length
|
||||
|
||||
@ -1,162 +1,298 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonBase,
|
||||
Link,
|
||||
Paper,
|
||||
Typography,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import useSWR from "swr";
|
||||
import { isAxiosError } from "axios";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import YoutubeEmbedIframe from "../../ui_kit/StartPagePreview/YoutubeEmbedIframe";
|
||||
import { QuizStartpageAlignType, QuizStartpageType } from "@model/quizSettings";
|
||||
import { notReachable } from "../../utils/notReachable";
|
||||
import { useUADevice } from "../../utils/hooks/useUADevice";
|
||||
|
||||
import { quizApi } from "@api/quiz";
|
||||
interface Props {
|
||||
setVisualStartPage: (a:boolean) => void
|
||||
}
|
||||
|
||||
import { useQuizStore } from "@root/quizes/store";
|
||||
import { useQuestions } from "@root/questions/hooks";
|
||||
import { setQuizes } from "@root/quizes/actions";
|
||||
|
||||
type StartPageViewPublicationProps = {
|
||||
setVisualStartPage: (bool: boolean) => void;
|
||||
showNextButton:boolean
|
||||
};
|
||||
|
||||
export const StartPageViewPublication = ({
|
||||
setVisualStartPage,
|
||||
showNextButton
|
||||
}: StartPageViewPublicationProps) => {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { quizes } = useQuizStore();
|
||||
const { questions } = useQuestions();
|
||||
export const StartPageViewPublication = ({setVisualStartPage}:Props) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(630));
|
||||
const quiz = quizes.find(({ backendId }) => quizId === backendId);
|
||||
const isMediaFileExist =
|
||||
quiz?.config.startpage.background.desktop ||
|
||||
quiz?.config.startpage.background.video;
|
||||
const quiz = useCurrentQuiz();
|
||||
const { isMobileDevice } = useUADevice();
|
||||
|
||||
useSWR("quizes", () => quizApi.getList(), {
|
||||
onSuccess: setQuizes,
|
||||
onError: (error: unknown) => {
|
||||
const message = isAxiosError<string>(error)
|
||||
? error.response?.data ?? ""
|
||||
: "";
|
||||
if (!quiz) return null;
|
||||
|
||||
devlog("Error getting quiz list", error);
|
||||
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
||||
},
|
||||
});
|
||||
const handleCopyNumber = () => {
|
||||
navigator.clipboard.writeText(quiz.config.info.phonenumber);
|
||||
};
|
||||
|
||||
const background = quiz.config.startpage.background.type === "image"
|
||||
? quiz.config.startpage.background.desktop
|
||||
? (
|
||||
<img
|
||||
src={quiz.config.startpage.background.desktop}
|
||||
alt=""
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
: quiz.config.startpage.background.type === "video"
|
||||
? quiz.config.startpage.background.video
|
||||
? (
|
||||
<YoutubeEmbedIframe videoUrl={quiz.config.startpage.background.video}
|
||||
containerSX={{
|
||||
width: quiz.config.startpageType === "centered" ? "550px" : quiz.config.startpageType === "expanded" ? "100vw" : "100%",
|
||||
height: quiz.config.startpageType === "centered" ? "275px" : quiz.config.startpageType === "expanded" ? "100vh" : "100%",
|
||||
borderRadius: quiz.config.startpageType === "centered" ? "10px" : "0",
|
||||
overflow: "hidden",
|
||||
"& iframe": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
transform: quiz.config.startpageType === "centered" ? "" : quiz.config.startpageType === "expanded" ? "scale(1.5)" : "scale(2.4)",
|
||||
|
||||
}
|
||||
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
<Paper className="quiz-preview-draghandle"
|
||||
sx={{
|
||||
height: "100vh",
|
||||
display: "flex",
|
||||
flexDirection:
|
||||
quiz?.config.startpage.position === "left" ? "row" : "row-reverse",
|
||||
flexGrow: 1,
|
||||
"&::-webkit-scrollbar": { width: 0 },
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMediaFileExist && !isTablet ? "40%" : "100%",
|
||||
padding: "16px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: isMediaFileExist && !isTablet ? "flex-start" : "center",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
background: quiz.config.startpageType === "expanded" ?
|
||||
quiz.config.startpage.position === "left" ? "linear-gradient(90deg,#272626,transparent)" :
|
||||
quiz.config.startpage.position === "center" ? "linear-gradient(180deg,transparent,#272626)" :
|
||||
"linear-gradient(270deg,#272626,transparent)"
|
||||
: "",
|
||||
color: quiz.config.startpageType === "expanded" ? "white" : "black"
|
||||
|
||||
|
||||
}}>
|
||||
<QuizPreviewLayoutByType
|
||||
quizHeaderBlock={<Box
|
||||
p={quiz.config.startpageType === "standard" ? "" : "16px"}
|
||||
>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
}}
|
||||
>
|
||||
{quiz?.config.startpage.background.mobile && (
|
||||
<img
|
||||
src={quiz.config.startpage.background.mobile}
|
||||
style={{
|
||||
height: "50px",
|
||||
maxWidth: "100px",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
<Typography sx={{ fontSize: "18px" }}>
|
||||
{quiz?.config.info.orgname}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontWeight: "bold", fontSize: "20px" }}>
|
||||
{quiz?.name}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: "16px" }}>
|
||||
{quiz?.config.startpage.description}
|
||||
</Typography>
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ fontSize: "16px", padding: "10px 15px" }}
|
||||
// disabled={!questions.length}
|
||||
onClick={() => setVisualStartPage(false)}
|
||||
>
|
||||
{quiz?.config.startpage.button
|
||||
? quiz?.config.startpage.button
|
||||
: "Пройти тест"}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}
|
||||
>
|
||||
{quiz?.config.info.phonenumber}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: "12px" }}>
|
||||
{quiz?.config.info.law}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
{!isTablet && isMediaFileExist && (
|
||||
<Box sx={{ width: "60%" }}>
|
||||
{quiz?.config.startpage.background.mobile && (
|
||||
<img
|
||||
src={quiz.config.startpage.background.mobile}
|
||||
alt=""
|
||||
style={{
|
||||
display: "block",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{quiz.config.startpage.background.type === "video" &&
|
||||
quiz.config.startpage.background.video && (
|
||||
<video
|
||||
src={quiz.config.startpage.background.video}
|
||||
controls
|
||||
mb: "7px"
|
||||
}}>
|
||||
{quiz.config.startpage.logo && (
|
||||
<img
|
||||
src={quiz.config.startpage.logo}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
height: "37px",
|
||||
maxWidth: "43px",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Typography sx={{ fontSize: "14px" }}>
|
||||
{quiz.config.info.orgname}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Link mb="16px" href={quiz.config.info.site}>
|
||||
<Typography sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}>
|
||||
{quiz.config.info.site}
|
||||
</Typography>
|
||||
</Link>
|
||||
</Box>}
|
||||
quizMainBlock={<>
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: quiz.config.startpageType === "centered" ? "center" :
|
||||
quiz.config.startpageType === "expanded"
|
||||
? quiz.config.startpage.position === "center" ?
|
||||
"center"
|
||||
: "start": "start",
|
||||
mt: "28px",
|
||||
width: "100%"
|
||||
}}>
|
||||
<Typography sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "26px",
|
||||
fontStyle: "normal",
|
||||
fontStretch: "normal",
|
||||
lineHeight: "1.2",
|
||||
}}>{quiz.name}</Typography>
|
||||
<Typography sx={{
|
||||
fontSize: "16px",
|
||||
m: "16px 0"
|
||||
}}>
|
||||
{quiz.config.startpage.description}
|
||||
</Typography>
|
||||
<Box width={ quiz.config.startpageType === "standard" ? "100%" : "auto"}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
padding: "10px 15px",
|
||||
width: quiz.config.startpageType === "standard" ? "100%" : "auto"
|
||||
}}
|
||||
onClick={() => setVisualStartPage(false)}
|
||||
>
|
||||
{quiz.config.startpage.button.trim() ? quiz.config.startpage.button : "Пройти тест"}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mt: "46px"
|
||||
}}
|
||||
>
|
||||
{quiz.config.info.clickable ? (
|
||||
isMobileDevice ? (
|
||||
<Link href={`tel:${quiz.config.info.phonenumber}`}>
|
||||
<Typography sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}>
|
||||
{quiz.config.info.phonenumber}
|
||||
</Typography>
|
||||
</Link>
|
||||
) : (
|
||||
<ButtonBase onClick={handleCopyNumber}>
|
||||
<Typography sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}>
|
||||
{quiz.config.info.phonenumber}
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
)
|
||||
) : (
|
||||
<Typography sx={{ fontSize: "16px", color: theme.palette.brightPurple.main }}>
|
||||
{quiz.config.info.phonenumber}
|
||||
</Typography>
|
||||
)}
|
||||
<Typography sx={{ fontSize: "12px", textAlign: "end" }}>
|
||||
{quiz.config.info.law}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>}
|
||||
backgroundBlock={background}
|
||||
startpageType={quiz.config.startpageType}
|
||||
alignType={quiz.config.startpage.position}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
function QuizPreviewLayoutByType({ quizHeaderBlock, quizMainBlock, backgroundBlock, startpageType, alignType }: {
|
||||
quizHeaderBlock: JSX.Element;
|
||||
quizMainBlock: JSX.Element;
|
||||
backgroundBlock: JSX.Element | null;
|
||||
startpageType: QuizStartpageType;
|
||||
alignType: QuizStartpageAlignType;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(630));
|
||||
|
||||
switch (startpageType) {
|
||||
case null:
|
||||
case "standard": {
|
||||
return (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: alignType === "left" ? "row" : "row-reverse",
|
||||
flexGrow: 1,
|
||||
height: "100vh",
|
||||
"&::-webkit-scrollbar": { width: 0 },
|
||||
}}>
|
||||
<Box sx={{
|
||||
width: !isTablet ? "40%" : "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
alignItems: !isTablet ? "flex-start" : "center",
|
||||
p: "25px"
|
||||
}}>
|
||||
{quizHeaderBlock}
|
||||
{quizMainBlock}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
width: "60%",
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{backgroundBlock}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
case "expanded": {
|
||||
return (
|
||||
<Box sx={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
justifyContent: startpageAlignTypeToJustifyContent[alignType],
|
||||
flexGrow: 1,
|
||||
height: "100%",
|
||||
"&::-webkit-scrollbar": { width: 0 },
|
||||
}}>
|
||||
<Box sx={{
|
||||
width: "40%",
|
||||
position: "relative",
|
||||
padding: "16px",
|
||||
zIndex: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
alignItems: alignType === "center" ? "center" : "start",
|
||||
}}>
|
||||
{quizHeaderBlock}
|
||||
{quizMainBlock}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
zIndex: 1,
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{backgroundBlock}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
case "centered": {
|
||||
return (
|
||||
<Box sx={{
|
||||
padding: "16px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
"&::-webkit-scrollbar": { width: 0 },
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
{quizHeaderBlock}
|
||||
{backgroundBlock &&
|
||||
<Box>
|
||||
{backgroundBlock}
|
||||
</Box>
|
||||
}
|
||||
{quizMainBlock}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
default: notReachable(startpageType);
|
||||
}
|
||||
}
|
||||
|
||||
const startpageAlignTypeToJustifyContent: Record<QuizStartpageAlignType, "start" | "center" | "end"> = {
|
||||
left: "start",
|
||||
center: "center",
|
||||
right: "end",
|
||||
};
|
||||
|
||||
@ -5,15 +5,40 @@ import { StartPageViewPublication } from "./StartPageViewPublication";
|
||||
import { Question } from "./Question";
|
||||
import { useQuestions } from "@root/questions/hooks";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import useSWR from "swr";
|
||||
import { quizApi } from "@api/quiz";
|
||||
import { setQuizes } from "@root/quizes/actions";
|
||||
import { isAxiosError } from "axios";
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { useQuizStore } from "@root/quizes/store";
|
||||
|
||||
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { setQuestions } from "@root/questions/actions";
|
||||
import { questionApi } from "@api/question";
|
||||
|
||||
export const ViewPage = () => {
|
||||
const quiz = useCurrentQuiz();
|
||||
const { questions } = useQuestions();
|
||||
const [visualStartPage, setVisualStartPage] = useState<boolean>(
|
||||
!quiz?.config.noStartPage
|
||||
);
|
||||
const { editQuizId } = useQuizStore();
|
||||
const { questions } = useQuestionsStore();
|
||||
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
const quizes = await quizApi.getList()
|
||||
setQuizes(quizes)
|
||||
|
||||
const questions = await questionApi.getList({ quiz_id: editQuizId })
|
||||
setQuestions(questions)
|
||||
}
|
||||
getData()
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
setVisualStartPage(quiz?.config.noStartPage)
|
||||
}, [questions])
|
||||
|
||||
|
||||
const [visualStartPage, setVisualStartPage] = useState<boolean>();
|
||||
|
||||
useEffect(() => {
|
||||
const link = document.querySelector('link[rel="icon"]');
|
||||
@ -27,13 +52,12 @@ export const ViewPage = () => {
|
||||
questions.filter(({ type }) => type) as AnyTypedQuizQuestion[]
|
||||
).sort((previousItem, item) => previousItem.page - item.page);
|
||||
|
||||
console.log("visualStartPage ", visualStartPage)
|
||||
if (visualStartPage === undefined) return <></>
|
||||
return (
|
||||
<Box>
|
||||
{visualStartPage ? (
|
||||
<StartPageViewPublication
|
||||
setVisualStartPage={setVisualStartPage}
|
||||
showNextButton={!!filteredQuestions.length}
|
||||
/>
|
||||
{!visualStartPage ? (
|
||||
<StartPageViewPublication setVisualStartPage={setVisualStartPage}/>
|
||||
) : (
|
||||
<Question questions={filteredQuestions} />
|
||||
)}
|
||||
|
||||
@ -4,8 +4,6 @@ import { Box, Typography } from "@mui/material";
|
||||
|
||||
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
||||
|
||||
// import "react-datepicker/dist/react-datepicker.css";
|
||||
|
||||
import type { QuizQuestionDate } from "../../../model/questionTypes/date";
|
||||
import CalendarIcon from "@icons/CalendarIcon";
|
||||
|
||||
|
||||
@ -41,17 +41,77 @@ export const File = ({ currentQuestion }: FileProps) => {
|
||||
maxWidth: answer?.split("|")[0] ? "640px" : "550px"
|
||||
}}
|
||||
>
|
||||
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
|
||||
<input
|
||||
onChange={uploadFile}
|
||||
hidden
|
||||
accept={UPLOAD_FILE_TYPES_MAP[currentQuestion.content.type]}
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<UploadBox icon={<UploadIcon />} text="5 MB максимум" />
|
||||
</ButtonBase>
|
||||
{answer && currentQuestion.content.type === "picture" && (
|
||||
{answer?.split("|")[0] && (
|
||||
<Box sx={{display: "flex", alignItems: "center", gap: "15px"}}>
|
||||
<Typography>Вы загрузили:</Typography>
|
||||
<Box sx={{padding: "5px 5px 5px 16px", backgroundColor: theme.palette.brightPurple.main,
|
||||
borderRadius: "8px",
|
||||
color: "#FFFFFF",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px"
|
||||
}}>
|
||||
<Typography>
|
||||
{answer?.split("|")[0]}
|
||||
</Typography>
|
||||
<IconButton
|
||||
sx={{p: 0}}
|
||||
onClick={() => {updateAnswer(currentQuestion.content.id, "");}}
|
||||
>
|
||||
<X/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
|
||||
)}
|
||||
|
||||
{!answer?.split("|")[0] && (
|
||||
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
|
||||
<input
|
||||
onChange={uploadFile}
|
||||
hidden
|
||||
accept={UPLOAD_FILE_TYPES_MAP[currentQuestion.content.type]}
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<Box
|
||||
onDragOver={(event: DragEvent<HTMLDivElement>) => event.preventDefault()}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "120px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "33px 44px 33px 55px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<UploadIcon />
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontWeight: 500
|
||||
}}
|
||||
>Добавить видео</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
}}
|
||||
>
|
||||
Принимает .mp4 и .mov формат — максимум 100мб
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
)}
|
||||
{answer && currentQuestion.content.type === "picture" && (
|
||||
<img
|
||||
src={answer.split("|")[1]}
|
||||
alt=""
|
||||
@ -73,11 +133,6 @@ export const File = ({ currentQuestion }: FileProps) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{answer?.split("|")[0] && (
|
||||
<Typography sx={{ marginTop: "15px" }}>
|
||||
{answer?.split("|")[0]}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -8,6 +8,7 @@ import { CustomSlider } from "@ui_kit/CustomSlider";
|
||||
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
||||
|
||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||
import {CustomSlider} from "@ui_kit/CustomSlider";
|
||||
|
||||
type NumberProps = {
|
||||
currentQuestion: QuizQuestionNumber;
|
||||
@ -47,7 +48,6 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
const max = window.Number(currentQuestion.content.range.split("—")[1]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("ans", currentQuestion.content.start);
|
||||
if (answer) {
|
||||
setMinRange(answer.split("—")[0]);
|
||||
setMaxRange(answer.split("—")[1]);
|
||||
@ -57,8 +57,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
currentQuestion.content.id,
|
||||
currentQuestion.content.chooseRange
|
||||
? `${currentQuestion.content.start}—${max}`
|
||||
: String(currentQuestion.content.start),
|
||||
false
|
||||
: String(currentQuestion.content.start)
|
||||
);
|
||||
|
||||
setMinRange(String(currentQuestion.content.start));
|
||||
|
||||
@ -7,6 +7,12 @@ import {
|
||||
|
||||
import { useQuizViewStore, updateAnswer } from "@root/quizView";
|
||||
|
||||
import TropfyIcon from "@icons/questionsPage/tropfyIcon";
|
||||
import FlagIcon from "@icons/questionsPage/FlagIcon";
|
||||
import HeartIcon from "@icons/questionsPage/heartIcon";
|
||||
import LikeIcon from "@icons/questionsPage/likeIcon";
|
||||
import LightbulbIcon from "@icons/questionsPage/lightbulbIcon";
|
||||
import HashtagIcon from "@icons/questionsPage/hashtagIcon";
|
||||
import StarIconMini from "@icons/questionsPage/StarIconMini";
|
||||
|
||||
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
||||
@ -15,6 +21,37 @@ type RatingProps = {
|
||||
currentQuestion: QuizQuestionRating;
|
||||
};
|
||||
|
||||
const buttonRatingForm = [
|
||||
{
|
||||
name: "star",
|
||||
icon: (color: string) => <StarIconMini width={50} color={color} />,
|
||||
},
|
||||
{
|
||||
name: "trophie",
|
||||
icon: (color: string) => <TropfyIcon color={color} />,
|
||||
},
|
||||
{
|
||||
name: "flag",
|
||||
icon: (color: string) => <FlagIcon color={color} />,
|
||||
},
|
||||
{
|
||||
name: "heart",
|
||||
icon: (color: string) => <HeartIcon color={color} />,
|
||||
},
|
||||
{
|
||||
name: "like",
|
||||
icon: (color: string) => <LikeIcon color={color} />,
|
||||
},
|
||||
{
|
||||
name: "bubble",
|
||||
icon: (color: string) => <LightbulbIcon color={color} />,
|
||||
},
|
||||
{
|
||||
name: "hashtag",
|
||||
icon: (color: string) => <HashtagIcon color={color} />,
|
||||
},
|
||||
];
|
||||
|
||||
export const Rating = ({ currentQuestion }: RatingProps) => {
|
||||
const { answers } = useQuizViewStore();
|
||||
const theme = useTheme();
|
||||
@ -22,54 +59,44 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
|
||||
answers.find(
|
||||
({ questionId }) => questionId === currentQuestion.content.id
|
||||
) ?? {};
|
||||
const form = buttonRatingForm.find(
|
||||
({ name }) => name === currentQuestion.content.form
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5">{currentQuestion.title}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "inline-block",
|
||||
width: "100%",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<RatingComponent
|
||||
value={Number(answer || 0)}
|
||||
onChange={(_, value) =>
|
||||
updateAnswer(currentQuestion.content.id, String(value))
|
||||
}
|
||||
sx={{ height: "50px", gap: "15px" }}
|
||||
max={currentQuestion.content.steps}
|
||||
icon={
|
||||
<StarIconMini
|
||||
color={theme.palette.brightPurple.main}
|
||||
width={50}
|
||||
sx={{ transform: "scale(1.4)" }}
|
||||
/>
|
||||
}
|
||||
emptyIcon={
|
||||
<StarIconMini
|
||||
color={theme.palette.grey2.main}
|
||||
width={50}
|
||||
sx={{ transform: "scale(1.4)" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Typography sx={{ color: theme.palette.grey2.main }}>
|
||||
{currentQuestion.content.ratingNegativeDescription}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
maxWidth: `${currentQuestion.content.steps * 50}px`,
|
||||
color: theme.palette.grey2.main,
|
||||
display: "inline-block",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
{currentQuestion.content.ratingNegativeDescription}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{currentQuestion.content.ratingPositiveDescription}
|
||||
</Typography>
|
||||
<RatingComponent
|
||||
value={Number(answer || 0)}
|
||||
onChange={(_, value) =>
|
||||
updateAnswer(currentQuestion.content.id, String(value))
|
||||
}
|
||||
sx={{ height: "50px", gap: "15px" }}
|
||||
max={currentQuestion.content.steps}
|
||||
icon={form?.icon(theme.palette.brightPurple.main)}
|
||||
emptyIcon={form?.icon(theme.palette.grey2.main)}
|
||||
/>
|
||||
</Box>
|
||||
<Typography sx={{ color: theme.palette.grey2.main }}>
|
||||
{currentQuestion.content.ratingPositiveDescription}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
@ -6,31 +7,56 @@ import {
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
Checkbox,
|
||||
TextField,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useQuizViewStore, updateAnswer, deleteAnswer } from "@root/quizView";
|
||||
import {
|
||||
useQuizViewStore,
|
||||
updateAnswer,
|
||||
deleteAnswer,
|
||||
updateOwnVariant,
|
||||
deleteOwnVariant,
|
||||
} from "@root/quizView";
|
||||
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
import { CheckboxIcon } from "@icons/Checkbox";
|
||||
|
||||
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
|
||||
import type { QuestionVariant } from "../../../model/questionTypes/shared";
|
||||
|
||||
type VariantProps = {
|
||||
stepNumber: number;
|
||||
currentQuestion: QuizQuestionVariant;
|
||||
};
|
||||
|
||||
type VariantItemProps = {
|
||||
currentQuestion: QuizQuestionVariant;
|
||||
variant: QuestionVariant;
|
||||
answer: string | string[] | undefined;
|
||||
index: number;
|
||||
own?: boolean;
|
||||
};
|
||||
|
||||
export const Variant = ({ currentQuestion }: VariantProps) => {
|
||||
const { answers } = useQuizViewStore();
|
||||
const theme = useTheme();
|
||||
const { answers, ownVariants } = useQuizViewStore();
|
||||
const { answer } =
|
||||
answers.find(
|
||||
({ questionId }) => questionId === currentQuestion.content.id
|
||||
) ?? {};
|
||||
const ownVariant = ownVariants.find(
|
||||
(variant) => variant.contentId === currentQuestion.content.id
|
||||
);
|
||||
|
||||
const Group = currentQuestion.content.multi ? FormGroup : RadioGroup;
|
||||
|
||||
useEffect(() => {
|
||||
if (!ownVariant) {
|
||||
updateOwnVariant(currentQuestion.content.id, "");
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5">{currentQuestion.title}</Typography>
|
||||
@ -59,58 +85,23 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
|
||||
}}
|
||||
>
|
||||
{currentQuestion.content.variants.map((variant, index) => (
|
||||
<FormControlLabel
|
||||
<VariantItem
|
||||
key={variant.id}
|
||||
sx={{
|
||||
margin: "0",
|
||||
borderRadius: "12px",
|
||||
padding: "15px",
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
display: "flex",
|
||||
maxWidth: "685px",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
}}
|
||||
value={index}
|
||||
labelPlacement="start"
|
||||
control={
|
||||
currentQuestion.content.multi ? (
|
||||
<Checkbox
|
||||
checked={!!answer?.includes(variant.id)}
|
||||
checkedIcon={<RadioCheck />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
) : (
|
||||
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||
)
|
||||
}
|
||||
label={variant.answer}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
const variantId = currentQuestion.content.variants[index].id;
|
||||
|
||||
if (currentQuestion.content.multi) {
|
||||
const currentAnswer =
|
||||
typeof answer !== "string" ? answer || [] : [];
|
||||
|
||||
updateAnswer(
|
||||
currentQuestion.content.id,
|
||||
currentAnswer?.includes(variantId)
|
||||
? currentAnswer?.filter((item) => item !== variantId)
|
||||
: [...currentAnswer, variantId]
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateAnswer(currentQuestion.content.id, variantId);
|
||||
|
||||
if (answer === variantId) {
|
||||
deleteAnswer(currentQuestion.content.id);
|
||||
}
|
||||
}}
|
||||
currentQuestion={currentQuestion}
|
||||
variant={variant}
|
||||
answer={answer}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
{currentQuestion.content.own && ownVariant && (
|
||||
<VariantItem
|
||||
own
|
||||
currentQuestion={currentQuestion}
|
||||
variant={ownVariant.variant}
|
||||
answer={answer}
|
||||
index={currentQuestion.content.variants.length + 2}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Group>
|
||||
{currentQuestion.content.back && (
|
||||
@ -126,3 +117,70 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const VariantItem = ({
|
||||
currentQuestion,
|
||||
variant,
|
||||
answer,
|
||||
index,
|
||||
own = false,
|
||||
}: VariantItemProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<FormControlLabel
|
||||
key={variant.id}
|
||||
sx={{
|
||||
margin: "0",
|
||||
borderRadius: "12px",
|
||||
padding: "15px",
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
display: "flex",
|
||||
maxWidth: "685px",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
"&.MuiFormControl-root": {
|
||||
width: "100%",
|
||||
|
||||
}
|
||||
}}
|
||||
value={index}
|
||||
labelPlacement="start"
|
||||
control={
|
||||
currentQuestion.content.multi ? (
|
||||
<Checkbox
|
||||
checked={!!answer?.includes(variant.id)}
|
||||
checkedIcon={<CheckboxIcon checked />}
|
||||
icon={<CheckboxIcon />}
|
||||
/>
|
||||
) : (
|
||||
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||
)
|
||||
}
|
||||
label={own ? <TextField label="Другое..." /> : variant.answer}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
const variantId = currentQuestion.content.variants[index].id;
|
||||
|
||||
if (currentQuestion.content.multi) {
|
||||
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
|
||||
|
||||
updateAnswer(
|
||||
currentQuestion.content.id,
|
||||
currentAnswer.includes(variantId)
|
||||
? currentAnswer?.filter((item) => item !== variantId)
|
||||
: [...currentAnswer, variantId]
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateAnswer(currentQuestion.content.id, variantId);
|
||||
|
||||
if (answer === variantId) {
|
||||
deleteAnswer(currentQuestion.content.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,6 +7,8 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import gag from "./gag.png"
|
||||
|
||||
import { useQuizViewStore, updateAnswer, deleteAnswer } from "@root/quizView";
|
||||
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
@ -29,6 +31,8 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
({ id }) => answer === id
|
||||
);
|
||||
|
||||
console.log(currentQuestion)
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5">{currentQuestion.title}</Typography>
|
||||
@ -79,26 +83,37 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
))}
|
||||
</Box>
|
||||
</RadioGroup>
|
||||
{(variant?.extendedText || currentQuestion.content.back) && (
|
||||
{/* {(variant?.extendedText || currentQuestion.content.back) && ( */}
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "450px",
|
||||
width: "100%",
|
||||
height: "450px",
|
||||
border: "1px solid #E3E3E3",
|
||||
border: "1px solid #9A9AAF",
|
||||
borderRadius: "12px",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "#9A9AAF12",
|
||||
color: "#9A9AAF"
|
||||
}}
|
||||
>
|
||||
<img
|
||||
{
|
||||
answer ?
|
||||
<img
|
||||
src={
|
||||
answer ? variant?.extendedText : currentQuestion.content.back
|
||||
variant?.extendedText || gag
|
||||
}
|
||||
style={{ width: "100%", height: "100%", objectFit: "cover" }}
|
||||
alt=""
|
||||
/>
|
||||
:
|
||||
(variant?.extendedText || "Выберите вариант ответа слева")
|
||||
}
|
||||
|
||||
</Box>
|
||||
)}
|
||||
{/* )} */}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
BIN
src/pages/ViewPublicationPage/questions/gag.png
Normal file
BIN
src/pages/ViewPublicationPage/questions/gag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
@ -1,14 +1,14 @@
|
||||
import { login } from "@api/auth";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
IconButton,
|
||||
Link,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
IconButton,
|
||||
Link,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { setUserId, useUserStore } from "@root/user";
|
||||
import InputTextfield from "@ui_kit/InputTextfield";
|
||||
@ -17,7 +17,7 @@ import PasswordInput from "@ui_kit/passwordInput";
|
||||
import { useFormik } from "formik";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
import { Link as RouterLink, useNavigate, useLocation } from "react-router-dom";
|
||||
import { object, string } from "yup";
|
||||
|
||||
interface Values {
|
||||
@ -43,6 +43,8 @@ export default function SigninDialog() {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const formik = useFormik<Values>({
|
||||
initialValues,
|
||||
validationSchema,
|
||||
@ -110,11 +112,11 @@ export default function SigninDialog() {
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0px 15px 80px rgb(210 208 225 / 70%)",
|
||||
"& .MuiFormHelperText-root.Mui-error, & .MuiFormHelperText-root.Mui-error.MuiFormHelperText-filled":
|
||||
{
|
||||
position: "absolute",
|
||||
top: "46px",
|
||||
margin: "0",
|
||||
},
|
||||
{
|
||||
position: "absolute",
|
||||
top: "46px",
|
||||
margin: "0",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@ -153,7 +155,7 @@ export default function SigninDialog() {
|
||||
id="email"
|
||||
label="Email"
|
||||
gap={upMd ? "10px" : "10px"}
|
||||
/>
|
||||
/>
|
||||
<PasswordInput
|
||||
TextfieldProps={{
|
||||
value: formik.values.password,
|
||||
@ -190,16 +192,17 @@ export default function SigninDialog() {
|
||||
Войти
|
||||
</Button>
|
||||
{/* <Link
|
||||
component={RouterLink}
|
||||
to="/"
|
||||
href="#"
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
mb: "15px",
|
||||
}}
|
||||
>
|
||||
Забыли пароль?
|
||||
</Link> */}
|
||||
component={RouterLink}
|
||||
to="/"
|
||||
href="#"
|
||||
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
mb: "15px",
|
||||
}}
|
||||
>
|
||||
Забыли пароль?
|
||||
</Link> */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
||||
@ -17,7 +17,7 @@ import PasswordInput from "@ui_kit/passwordInput";
|
||||
import { useFormik } from "formik";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
import {Link as RouterLink, useLocation, useNavigate} from "react-router-dom";
|
||||
import { object, ref, string } from "yup";
|
||||
|
||||
interface Values {
|
||||
@ -50,6 +50,8 @@ export default function SignupDialog() {
|
||||
const user = useUserStore((state) => state.user);
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const location = useLocation()
|
||||
|
||||
const navigate = useNavigate();
|
||||
const formik = useFormik<Values>({
|
||||
initialValues,
|
||||
@ -220,6 +222,7 @@ export default function SignupDialog() {
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to="/signin"
|
||||
state={{ backgroundLocation: location.state.backgroundLocation }}
|
||||
sx={{
|
||||
color: "#7E2AEA",
|
||||
mt: "auto",
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { quizApi } from "@api/quiz";
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -9,17 +7,14 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { createQuiz } from "@root/quizes/actions";
|
||||
import { useQuizes } from "@root/quizes/hooks";
|
||||
import SectionWrapper from "@ui_kit/SectionWrapper";
|
||||
import { isAxiosError } from "axios";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useSWR from "swr";
|
||||
import ComplexNavText from "./ComplexNavText";
|
||||
import FirstQuiz from "./FirstQuiz";
|
||||
import QuizCard from "./QuizCard";
|
||||
import { setQuizes, createQuiz } from "@root/quizes/actions";
|
||||
import { useQuizStore } from "@root/quizes/store";
|
||||
|
||||
|
||||
interface Props {
|
||||
@ -31,23 +26,14 @@ export default function MyQuizzesFull({
|
||||
outerContainerSx: sx,
|
||||
children,
|
||||
}: Props) {
|
||||
useSWR("quizes", () => quizApi.getList(), {
|
||||
onSuccess: setQuizes,
|
||||
onError: error => {
|
||||
const message = isAxiosError<string>(error) ? (error.response?.data ?? "") : "";
|
||||
|
||||
devlog("Error getting quiz list", error);
|
||||
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
||||
},
|
||||
});
|
||||
const quizArray = useQuizStore(state => state.quizes);
|
||||
const { quizes } = useQuizes();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(500));
|
||||
|
||||
return (
|
||||
<>
|
||||
{quizArray.length === 0 ? (
|
||||
{quizes.length === 0 ? (
|
||||
<FirstQuiz />
|
||||
) : (
|
||||
<SectionWrapper maxWidth="lg">
|
||||
@ -83,7 +69,7 @@ export default function MyQuizzesFull({
|
||||
mb: "60px",
|
||||
}}
|
||||
>
|
||||
{quizArray.map(quiz => (
|
||||
{quizes.map(quiz => (
|
||||
<QuizCard
|
||||
key={quiz.id}
|
||||
quiz={quiz}
|
||||
|
||||
@ -29,36 +29,41 @@ import { useEffect, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import useSWR from "swr";
|
||||
import { SidebarMobile } from "./Sidebar/SidebarMobile";
|
||||
import {cleanQuestions, updateOpenBranchingPanel} from "@root/questions/actions";
|
||||
import {BranchingPanel} from "../Questions/BranchingPanel";
|
||||
import {useQuestionsStore} from "@root/questions/store";
|
||||
import { useQuestions } from "@root/questions/hooks";
|
||||
import { cleanQuestions } from "@root/questions/actions";
|
||||
import { updateOpenBranchingPanel } from "@root/uiTools/actions";
|
||||
import { BranchingPanel } from "../Questions/BranchingPanel";
|
||||
import { setQuestions } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useQuizes } from "@root/quizes/hooks";
|
||||
import { questionApi } from "@api/question";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
|
||||
|
||||
|
||||
|
||||
export default function EditPage() {
|
||||
useSWR("quizes", () => quizApi.getList(), {
|
||||
onSuccess: setQuizes,
|
||||
onError: error => {
|
||||
const message = isAxiosError<string>(error) ? (error.response?.data ?? "") : "";
|
||||
const quiz = useCurrentQuiz();
|
||||
const { editQuizId } = useQuizStore();
|
||||
|
||||
devlog("Error getting quiz list", error);
|
||||
enqueueSnackbar(`Не удалось получить квизы. ${message}`);
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
const getData = async () => {
|
||||
const quizes = await quizApi.getList()
|
||||
setQuizes(quizes)
|
||||
|
||||
|
||||
// if (isLoading && !questions) return <Box>Загрузка вопросов...</Box>;
|
||||
const questions = await questionApi.getList({ quiz_id: editQuizId })
|
||||
setQuestions(questions)
|
||||
}
|
||||
getData()
|
||||
}, [])
|
||||
|
||||
const { openBranchingPanel } = useUiTools()
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const editQuizId = useQuizStore(state => state.editQuizId);
|
||||
const quiz = useCurrentQuiz();
|
||||
const currentStep = useQuizStore(state => state.currentStep);
|
||||
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");
|
||||
@ -69,6 +74,7 @@ export default function EditPage() {
|
||||
cleanQuestions();
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*хедер*/}
|
||||
@ -213,7 +219,7 @@ export default function EditPage() {
|
||||
sx={{
|
||||
background: theme.palette.background.default,
|
||||
width: "100%",
|
||||
padding: isMobile ? "16px" : "25px",
|
||||
padding: isMobile ? "16px 16px 140px 16px" : "25px",
|
||||
height: "calc(100vh - 80px)",
|
||||
overflow: "auto",
|
||||
boxSizing: "border-box",
|
||||
@ -232,7 +238,7 @@ export default function EditPage() {
|
||||
</>
|
||||
}
|
||||
</Box>
|
||||
{isTablet && [1, 2, 3].includes(currentStep) && (
|
||||
{isTablet &&
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -246,75 +252,74 @@ export default function EditPage() {
|
||||
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)
|
||||
}}
|
||||
{[1, 2].includes(currentStep) && !openBranchingPanel && (
|
||||
<Box
|
||||
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,
|
||||
}),
|
||||
},
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
padding: "18px",
|
||||
background: "#fff",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: "bold", color: "#4D4D4D" }}>
|
||||
Логика ветвления
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontSize: "12px" }}>
|
||||
Настройте связи между вопросами
|
||||
</Typography>
|
||||
>
|
||||
<Switch
|
||||
checked={openBranchingPanel}
|
||||
onChange={
|
||||
(e) => updateOpenBranchingPanel(e.target.checked)
|
||||
}
|
||||
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>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
@ -326,7 +331,8 @@ export default function EditPage() {
|
||||
Опубликовать
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
}
|
||||
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -331,74 +331,7 @@ export default function StartPageSettings() {
|
||||
|
||||
<ModalSizeImage />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
mt: "10px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={<IconCheck />}
|
||||
checkedIcon={<MobilePhoneIcon bgcolor={"#EEE4FC"} />}
|
||||
/>
|
||||
}
|
||||
label="мобильная версия"
|
||||
sx={{
|
||||
color: theme.palette.brightPurple.main,
|
||||
textDecorationLine: "underline",
|
||||
textDecorationColor: theme.palette.brightPurple.main,
|
||||
ml: "-9px",
|
||||
userSelect: "none",
|
||||
"& .css-14o5ia4-MuiTypography-root": {
|
||||
fontSize: "16px"
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
MobileVersionHC(!mobileVersion);
|
||||
}}
|
||||
/>
|
||||
{mobileVersion ? (
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "11px",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Изображение для мобильной версии
|
||||
</Typography>
|
||||
<DropZone
|
||||
text={"5 MB максимум"}
|
||||
imageUrl={quiz.config.startpage.background.mobile}
|
||||
originalImageUrl={quiz.config.startpage.background.originalMobile}
|
||||
onImageUploadClick={file => {
|
||||
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||
quiz.config.startpage.background.mobile = url;
|
||||
quiz.config.startpage.background.originalMobile = url;
|
||||
});
|
||||
}}
|
||||
onImageSaveClick={file => {
|
||||
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||
quiz.config.startpage.background.mobile = url;
|
||||
});
|
||||
}}
|
||||
onDeleteClick={() => {
|
||||
updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.background.mobile = null;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@ -415,96 +348,52 @@ export default function StartPageSettings() {
|
||||
quiz.config.startpage.background.video = e.target.value;
|
||||
})}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "20px",
|
||||
mb: "5px",
|
||||
}}
|
||||
>
|
||||
Настройки видео
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
label="Зацикливать видео"
|
||||
checked={quiz.config.startpage.background.cycle}
|
||||
handleChange={e => updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.background.cycle = e.target.checked;
|
||||
})}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "11px",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Изображение для мобильной версии
|
||||
</Typography>
|
||||
<DropZone
|
||||
text={"5 MB максимум"}
|
||||
imageUrl={quiz.config.startpage.background.mobile}
|
||||
originalImageUrl={quiz.config.startpage.background.originalMobile}
|
||||
onImageUploadClick={file => {
|
||||
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||
quiz.config.startpage.background.mobile = url;
|
||||
quiz.config.startpage.background.originalMobile = url;
|
||||
});
|
||||
}}
|
||||
onImageSaveClick={file => {
|
||||
uploadQuizImage(quiz.id, file, (quiz, url) => {
|
||||
quiz.config.startpage.background.mobile = url;
|
||||
});
|
||||
}}
|
||||
onDeleteClick={() => {
|
||||
updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.background.mobile = null;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "20px",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Расположение элементов
|
||||
</Typography>
|
||||
{designType !== "centered" &&
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<SelectableIconButton
|
||||
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.position = "left";
|
||||
})}
|
||||
isActive={quiz.config.startpage.position === "left"}
|
||||
Icon={AlignLeftIcon}
|
||||
/>
|
||||
<SelectableIconButton
|
||||
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.position = "center";
|
||||
})}
|
||||
isActive={quiz.config.startpage.position === "center"}
|
||||
Icon={AlignCenterIcon}
|
||||
sx={{ display: designType === "standard" ? "none" : "flex" }}
|
||||
/>
|
||||
<SelectableIconButton
|
||||
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.position = "right";
|
||||
})}
|
||||
isActive={quiz.config.startpage.position === "right"}
|
||||
Icon={AlignRightIcon}
|
||||
/>
|
||||
</Box>
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
mt: "20px",
|
||||
mb: "14px",
|
||||
}}
|
||||
>
|
||||
Расположение элементов
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<SelectableIconButton
|
||||
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.position = "left";
|
||||
})}
|
||||
isActive={quiz.config.startpage.position === "left"}
|
||||
Icon={AlignLeftIcon}
|
||||
/>
|
||||
<SelectableIconButton
|
||||
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.position = "center";
|
||||
})}
|
||||
isActive={quiz.config.startpage.position === "center"}
|
||||
Icon={AlignCenterIcon}
|
||||
sx={{ display: designType === "standard" ? "none" : "flex" }}
|
||||
/>
|
||||
<SelectableIconButton
|
||||
onClick={() => updateQuiz(quiz.id, quiz => {
|
||||
quiz.config.startpage.position = "right";
|
||||
})}
|
||||
isActive={quiz.config.startpage.position === "right"}
|
||||
Icon={AlignRightIcon}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
}
|
||||
{(isTablet || !isSmallMonitor) && (
|
||||
<>
|
||||
|
||||
@ -99,7 +99,6 @@ const updateQuestionOrders = () => {
|
||||
const questions = useQuestionsStore.getState().questions.filter(
|
||||
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
|
||||
);
|
||||
console.log(questions);
|
||||
|
||||
questions.forEach((question, index) => {
|
||||
updateQuestion(question.id, question => {
|
||||
@ -176,9 +175,9 @@ const REQUEST_DEBOUNCE = 200;
|
||||
const requestQueue = new RequestQueue();
|
||||
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
export const updateQuestion = (
|
||||
export const updateQuestion = <T = AnyTypedQuizQuestion>(
|
||||
questionId: string,
|
||||
updateFn: (question: AnyTypedQuizQuestion) => void,
|
||||
updateFn: (question: T) => void,
|
||||
skipQueue = false,
|
||||
) => {
|
||||
setProducedState(state => {
|
||||
@ -186,7 +185,7 @@ export const updateQuestion = (
|
||||
if (!question) return;
|
||||
if (question.type === null) throw new Error("Cannot update untyped question, use 'updateUntypedQuestion' instead");
|
||||
|
||||
updateFn(question);
|
||||
updateFn(question as T);
|
||||
}, {
|
||||
type: "updateQuestion",
|
||||
questionId,
|
||||
@ -206,8 +205,10 @@ export const updateQuestion = (
|
||||
//Если мы делаем листочек веточкой - удаляем созданный к нему результ
|
||||
const questionResult = useQuestionsStore.getState().questions.find(questionResult => questionResult.type === "result" && questionResult.content.rule.parentId === q.content.id);
|
||||
if (questionResult && q.content.rule.default.length !== 0) deleteQuestion(questionResult.quizId);
|
||||
deleteQuestion;
|
||||
setQuestionBackendId(questionId, response.updated);
|
||||
|
||||
if (q.backendId !== response.updated) {
|
||||
console.warn(`Question backend id has changed from ${q.backendId} to ${response.updated}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (isAxiosCanceledError(error)) return;
|
||||
|
||||
@ -384,13 +385,16 @@ export const createTypedQuestion = async (
|
||||
});
|
||||
|
||||
export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => {
|
||||
console.log("Я получил запрос на удаление. ИД - ", questionId)
|
||||
|
||||
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
|
||||
console.log("delete question ", question)
|
||||
if (!question) return;
|
||||
|
||||
|
||||
|
||||
if (question.type === null) {
|
||||
console.log("removeQuestion")
|
||||
removeQuestion(questionId);
|
||||
return;
|
||||
}
|
||||
@ -415,7 +419,6 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
|
||||
if (question.type === null) {
|
||||
const copiedQuestion = structuredClone(question);
|
||||
copiedQuestion.id = frontId;
|
||||
copiedQuestion.content.id = frontId;
|
||||
|
||||
setProducedState(state => {
|
||||
state.questions.push(copiedQuestion);
|
||||
@ -460,9 +463,7 @@ function setProducedState<A extends string | { type: unknown; }>(
|
||||
};
|
||||
|
||||
|
||||
export const cleardragQuestionContentId = () => {
|
||||
useQuestionsStore.setState({ dragQuestionContentId: null });
|
||||
};
|
||||
|
||||
|
||||
export const getQuestionById = (questionId: string | null) => {
|
||||
if (questionId === null) return null;
|
||||
@ -478,9 +479,7 @@ export const getQuestionByContentId = (questionContentId: string | null) => {
|
||||
};
|
||||
|
||||
export const updateOpenedModalSettingsId = (id?: string) => useQuestionsStore.setState({ openedModalSettingsId: id ? id : null });
|
||||
export const updateDragQuestionContentId = (contentId?: string) => {
|
||||
useQuestionsStore.setState({ dragQuestionContentId: contentId ? contentId : null });
|
||||
};
|
||||
|
||||
|
||||
export const clearRuleForAll = () => {
|
||||
const { questions } = useQuestionsStore.getState();
|
||||
@ -495,23 +494,6 @@ export const clearRuleForAll = () => {
|
||||
});
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
@ -6,6 +6,7 @@ import useSWR from "swr";
|
||||
import { setQuestions } from "./actions";
|
||||
import { useQuestionsStore } from "./store";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useEffect } from "react";
|
||||
|
||||
|
||||
export function useQuestions() {
|
||||
|
||||
@ -5,20 +5,10 @@ import { devtools } from "zustand/middleware";
|
||||
|
||||
export type QuestionsStore = {
|
||||
questions: (AnyTypedQuizQuestion | UntypedQuizQuestion)[];
|
||||
openedModalSettingsId: string | null;
|
||||
dragQuestionContentId: string | null;
|
||||
openBranchingPanel: boolean;
|
||||
desireToOpenABranchingModal: string | null;
|
||||
editSomeQuestion: string | null;
|
||||
};
|
||||
|
||||
const initialState: QuestionsStore = {
|
||||
questions: [],
|
||||
openedModalSettingsId: null as null,
|
||||
dragQuestionContentId: null,
|
||||
openBranchingPanel: false,
|
||||
desireToOpenABranchingModal: null as null,
|
||||
editSomeQuestion: null as null,
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -1,21 +1,28 @@
|
||||
import { create } from "zustand";
|
||||
import { devtools } from "zustand/middleware";
|
||||
|
||||
import type { QuestionVariant } from "../model/questionTypes/shared";
|
||||
|
||||
type Answer = {
|
||||
questionId: string;
|
||||
answer: string | string[];
|
||||
// Поле отвечающее за первое изменение ответа, нужно для галочки "Необязательный вопрос"
|
||||
changed: boolean;
|
||||
};
|
||||
|
||||
type OwnVariant = {
|
||||
contentId: string;
|
||||
variant: QuestionVariant;
|
||||
};
|
||||
|
||||
interface QuizViewStore {
|
||||
answers: Answer[];
|
||||
ownVariants: OwnVariant[];
|
||||
}
|
||||
|
||||
export const useQuizViewStore = create<QuizViewStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
answers: [],
|
||||
ownVariants: [],
|
||||
}),
|
||||
{
|
||||
name: "quizView",
|
||||
@ -23,20 +30,16 @@ export const useQuizViewStore = create<QuizViewStore>()(
|
||||
)
|
||||
);
|
||||
|
||||
export const updateAnswer = (
|
||||
questionId: string,
|
||||
answer: string | string[],
|
||||
changed = true
|
||||
) => {
|
||||
export const updateAnswer = (questionId: string, answer: string | string[]) => {
|
||||
const answers = [...useQuizViewStore.getState().answers];
|
||||
const answerIndex = answers.findIndex(
|
||||
(answer) => questionId === answer.questionId
|
||||
);
|
||||
|
||||
if (answerIndex < 0) {
|
||||
answers.push({ questionId, answer, changed });
|
||||
answers.push({ questionId, answer });
|
||||
} else {
|
||||
answers[answerIndex] = { questionId, answer, changed };
|
||||
answers[answerIndex] = { questionId, answer };
|
||||
}
|
||||
|
||||
useQuizViewStore.setState({ answers });
|
||||
@ -50,3 +53,44 @@ export const deleteAnswer = (questionId: string) => {
|
||||
|
||||
useQuizViewStore.setState({ answers: filteredItems });
|
||||
};
|
||||
|
||||
export const updateOwnVariant = (contentId: string, answer: string) => {
|
||||
const ownVariants = [...useQuizViewStore.getState().ownVariants];
|
||||
const ownVariantIndex = ownVariants.findIndex(
|
||||
(variant) => variant.contentId === contentId
|
||||
);
|
||||
|
||||
if (ownVariantIndex < 0) {
|
||||
ownVariants.push({
|
||||
contentId,
|
||||
variant: {
|
||||
id: getRandom(),
|
||||
answer,
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
originalImageUrl: "",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
ownVariants[ownVariantIndex].variant.answer = answer;
|
||||
}
|
||||
|
||||
useQuizViewStore.setState({ ownVariants });
|
||||
};
|
||||
|
||||
export const deleteOwnVariant = (contentId: string) => {
|
||||
const ownVariants = [...useQuizViewStore.getState().ownVariants];
|
||||
|
||||
const filteredOwnVariants = ownVariants.filter(
|
||||
(variant) => variant.contentId !== contentId
|
||||
);
|
||||
|
||||
useQuizViewStore.setState({ ownVariants: filteredOwnVariants });
|
||||
};
|
||||
|
||||
function getRandom() {
|
||||
const min = Math.ceil(1000000);
|
||||
const max = Math.floor(10000000);
|
||||
|
||||
return String(Math.floor(Math.random() * (max - min)) + min);
|
||||
}
|
||||
|
||||
@ -179,6 +179,7 @@ export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async (
|
||||
export const updateRootContentId = (quizId: string, id:string) => updateQuiz(
|
||||
quizId,
|
||||
quiz => {
|
||||
console.log("Я изменение статуса корня проекта дерева, меняю на ", id)
|
||||
quiz.config.haveRoot = id
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,11 +1,34 @@
|
||||
import useSWR from "swr";
|
||||
import { useQuizStore } from "./store";
|
||||
import { quizApi } from "@api/quiz";
|
||||
import { setQuizes } from "./actions";
|
||||
import { isAxiosError } from "axios";
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
|
||||
export function useCurrentQuiz() {
|
||||
const quizId = useQuizStore(state => state.editQuizId);
|
||||
|
||||
export function useQuizes() {
|
||||
const { isLoading, error, isValidating } = useSWR("quizes", () => quizApi.getList(), {
|
||||
onSuccess: setQuizes,
|
||||
onError: (error: unknown) => {
|
||||
const message = isAxiosError<string>(error)
|
||||
? error.response?.data ?? ""
|
||||
: "";
|
||||
|
||||
devlog("Error getting quiz list", error);
|
||||
enqueueSnackbar("Не удалось получить квизы");
|
||||
},
|
||||
});
|
||||
const quizes = useQuizStore(state => state.quizes);
|
||||
|
||||
const quiz = quizes.find(q => q.backendId === quizId);
|
||||
return { quizes, isLoading, error, isValidating };
|
||||
}
|
||||
|
||||
export function useCurrentQuiz() {
|
||||
const { quizes, editQuizId } = useQuizStore();
|
||||
|
||||
const quiz = quizes.find(q => q.backendId === editQuizId);
|
||||
|
||||
return quiz;
|
||||
}
|
||||
|
||||
31
src/stores/uiTools/actions.ts
Normal file
31
src/stores/uiTools/actions.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { useUiTools } from "./store";
|
||||
|
||||
|
||||
|
||||
export const updateOpenBranchingPanel = (value: boolean) => useUiTools.setState({ openBranchingPanel: value });
|
||||
|
||||
|
||||
export const cleardragQuestionContentId = () => {
|
||||
useUiTools.setState({ dragQuestionContentId: null });
|
||||
};
|
||||
export const updateDragQuestionContentId = (contentId?: string) => {
|
||||
useUiTools.setState({ dragQuestionContentId: contentId ? contentId : null });
|
||||
};
|
||||
|
||||
|
||||
let UDTOABM: ReturnType<typeof setTimeout>;
|
||||
export const updateDesireToOpenABranchingModal = (contentId: string) => {
|
||||
useUiTools.setState({ desireToOpenABranchingModal: contentId });
|
||||
clearTimeout(UDTOABM);
|
||||
UDTOABM = setTimeout(() => {
|
||||
useUiTools.setState({ desireToOpenABranchingModal: null });
|
||||
}, 7000);
|
||||
};
|
||||
export const clearDesireToOpenABranchingModal = () => {
|
||||
useUiTools.setState({ desireToOpenABranchingModal: null });
|
||||
};
|
||||
|
||||
|
||||
export const updateEditSomeQuestion = (contentId?: string) => {
|
||||
useUiTools.setState({ editSomeQuestion: contentId === undefined ? null : contentId });
|
||||
};
|
||||
30
src/stores/uiTools/store.ts
Normal file
30
src/stores/uiTools/store.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { create } from "zustand";
|
||||
import { devtools } from "zustand/middleware";
|
||||
|
||||
|
||||
export type UiTools = {
|
||||
openedModalSettingsId: string | null;
|
||||
dragQuestionContentId: string | null;
|
||||
openBranchingPanel: boolean;
|
||||
desireToOpenABranchingModal: string | null;
|
||||
editSomeQuestion: string | null;
|
||||
};
|
||||
|
||||
const initialState: UiTools = {
|
||||
openedModalSettingsId: null as null,
|
||||
dragQuestionContentId: null,
|
||||
openBranchingPanel: false,
|
||||
desireToOpenABranchingModal: null as null,
|
||||
editSomeQuestion: null as null,
|
||||
};
|
||||
|
||||
export const useUiTools = create<UiTools>()(
|
||||
devtools(
|
||||
() => initialState,
|
||||
{
|
||||
name: "UiTools",
|
||||
enabled: process.env.NODE_ENV === "development",
|
||||
trace: process.env.NODE_ENV === "development",
|
||||
}
|
||||
)
|
||||
);
|
||||
@ -1,6 +1,8 @@
|
||||
import { FormControlLabel, Checkbox, useTheme, Box, useMediaQuery } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
import { CheckboxIcon } from "@icons/Checkbox";
|
||||
|
||||
import type { SxProps } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
@ -21,8 +23,8 @@ export default function CustomCheckbox({ label, handleChange, checked, sx, dataC
|
||||
<Checkbox
|
||||
sx={{ padding: "0px 13px 1px 11px" }}
|
||||
disableRipple
|
||||
icon={<Icon />}
|
||||
checkedIcon={<CheckedIcon />}
|
||||
icon={<CheckboxIcon />}
|
||||
checkedIcon={<CheckboxIcon checked />}
|
||||
onChange={handleChange}
|
||||
checked={checked}
|
||||
data-cy={dataCy}
|
||||
@ -37,42 +39,3 @@ export default function CustomCheckbox({ label, handleChange, checked, sx, dataC
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Icon() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "24px",
|
||||
width: "24px",
|
||||
borderRadius: "6px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CheckedIcon() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "24px",
|
||||
width: "24px",
|
||||
borderRadius: "6px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.palette.brightPurple.main,
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
}}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 25 18" fill="none">
|
||||
<path d="M2 9L10 16.5L22.5 1.5" stroke="#ffffff" strokeWidth="4" strokeLinecap="round" />
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { Box, SxProps } from "@mui/material";
|
||||
|
||||
|
||||
|
||||
interface Props {
|
||||
videoUrl: string;
|
||||
containerSX?: SxProps;
|
||||
}
|
||||
|
||||
export default function YoutubeEmbedIframe({ videoUrl }: Props) {
|
||||
export default function YoutubeEmbedIframe({ videoUrl, containerSX }: Props) {
|
||||
const extractYoutubeVideoId = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/gi;
|
||||
const videoId = extractYoutubeVideoId.exec(videoUrl)?.[1];
|
||||
if (!videoId) return null;
|
||||
@ -21,7 +22,8 @@ export default function YoutubeEmbedIframe({ videoUrl }: Props) {
|
||||
"& iframe": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}
|
||||
},
|
||||
...containerSX
|
||||
}}>
|
||||
<iframe
|
||||
src={embedUrl}
|
||||
|
||||
65
yarn.lock
65
yarn.lock
@ -1034,7 +1034,7 @@
|
||||
core-js-pure "^3.25.1"
|
||||
regenerator-runtime "^0.13.11"
|
||||
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.1", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.23.1", "@babel/runtime@^7.23.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.23.2"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz"
|
||||
integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==
|
||||
@ -1868,7 +1868,7 @@
|
||||
schema-utils "^3.0.0"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@popperjs/core@^2.0.0", "@popperjs/core@^2.11.8", "@popperjs/core@^2.9.2":
|
||||
"@popperjs/core@^2.0.0", "@popperjs/core@^2.11.8":
|
||||
version "2.11.8"
|
||||
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
@ -2412,16 +2412,6 @@
|
||||
"@types/cytoscape" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-datepicker@^4.19.3":
|
||||
version "4.19.3"
|
||||
resolved "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.3.tgz"
|
||||
integrity sha512-85F9eKWu9fGiD9r4KVVMPYAdkJJswR3Wci9PvqplmB6T+D+VbUqPeKtifg96NZ4nEhufjehW+SX4JLrEWVplWw==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.9.2"
|
||||
"@types/react" "*"
|
||||
date-fns "^2.0.1"
|
||||
react-popper "^2.2.5"
|
||||
|
||||
"@types/react-dnd@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/@types/react-dnd/-/react-dnd-3.0.2.tgz"
|
||||
@ -3610,11 +3600,6 @@ cjs-module-lexer@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz"
|
||||
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
|
||||
|
||||
classnames@^2.2.6:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz"
|
||||
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||
|
||||
clean-css@^5.2.2:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.3.1.tgz"
|
||||
@ -4214,13 +4199,6 @@ data-urls@^2.0.0:
|
||||
whatwg-mimetype "^2.3.0"
|
||||
whatwg-url "^8.0.0"
|
||||
|
||||
date-fns@^2.0.1, date-fns@^2.30.0:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz"
|
||||
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.21.0"
|
||||
|
||||
dayjs@^1.10.4, dayjs@^1.11.10:
|
||||
version "1.11.10"
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz"
|
||||
@ -7115,7 +7093,7 @@ log-update@^4.0.0:
|
||||
slice-ansi "^4.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
@ -8544,18 +8522,6 @@ react-cytoscapejs@^2.0.0:
|
||||
dependencies:
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-datepicker@^4.24.0:
|
||||
version "4.24.0"
|
||||
resolved "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.24.0.tgz"
|
||||
integrity sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.11.8"
|
||||
classnames "^2.2.6"
|
||||
date-fns "^2.30.0"
|
||||
prop-types "^15.7.2"
|
||||
react-onclickoutside "^6.13.0"
|
||||
react-popper "^2.3.0"
|
||||
|
||||
react-dev-utils@^12.0.1:
|
||||
version "12.0.1"
|
||||
resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz"
|
||||
@ -8645,11 +8611,6 @@ react-fast-compare@^2.0.1:
|
||||
resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-fast-compare@^3.0.1:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz"
|
||||
integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
|
||||
|
||||
react-image-crop@^10.1.5:
|
||||
version "10.1.8"
|
||||
resolved "https://registry.npmjs.org/react-image-crop/-/react-image-crop-10.1.8.tgz"
|
||||
@ -8675,19 +8636,6 @@ react-is@^18.0.0, react-is@^18.2.0:
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-onclickoutside@^6.13.0:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz"
|
||||
integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==
|
||||
|
||||
react-popper@^2.2.5, react-popper@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz"
|
||||
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
|
||||
dependencies:
|
||||
react-fast-compare "^3.0.1"
|
||||
warning "^4.0.2"
|
||||
|
||||
react-redux@^7.2.0:
|
||||
version "7.2.9"
|
||||
resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz"
|
||||
@ -10260,13 +10208,6 @@ walker@^1.0.7:
|
||||
dependencies:
|
||||
makeerror "1.0.12"
|
||||
|
||||
warning@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz"
|
||||
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
watchpack@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user