diff --git a/src/App.tsx b/src/App.tsx index 7136bf2e..783f38cb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,15 +16,61 @@ import { ResultSettings } from "./pages/ResultPage/ResultSettings"; import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull"; import Main from "./pages/main"; import EditPage from "./pages/startPage/EditPage"; -import { clearAuthToken, getMessageFromFetchError, useUserAccountFetcher, useUserFetcher } from "@frontend/kitui"; +import { clearAuthToken, getMessageFromFetchError, useUserFetcher, UserAccount, makeRequest, devlog, createUserAccount } from "@frontend/kitui"; import { clearUserData, setUser, setUserAccount, useUserStore } from "@root/user"; import { enqueueSnackbar } from "notistack"; import PrivateRoute from "@ui_kit/PrivateRoute"; import { Restore } from "./pages/startPage/Restore"; +import { isAxiosError } from "axios"; +import { useEffect, useLayoutEffect, useRef } from "react"; +export function useUserAccountFetcher({ onError, onNewUserAccount, url, userId }: { + url: string; + userId: string | null; + onNewUserAccount: (response: UserAccount) => void; + onError?: (error: any) => void; +}) { + const onNewUserAccountRef = useRef(onNewUserAccount); + const onErrorRef = useRef(onError); + useLayoutEffect(() => { + onNewUserAccountRef.current = onNewUserAccount; + onErrorRef.current = onError; + }, [onError, onNewUserAccount]); + useEffect(() => { + if (!userId) return; + const controller = new AbortController(); + makeRequest({ + url, + contentType: true, + method: "GET", + useToken: true, + withCredentials: false, + signal: controller.signal, + }).then(result => { + devlog("User account", result); + onNewUserAccountRef.current(result); + }).catch(error => { + devlog("Error fetching user account", error); + if (isAxiosError(error) && error.response?.status === 404) { + createUserAccount(controller.signal, url.replace('get','create')).then(result => { + devlog("Created user account", result); + onNewUserAccountRef.current(result); + }).catch(error => { + devlog("Error creating user account", error); + onErrorRef.current?.(error); + }); + } else { + onErrorRef.current?.(error); + } + }); + return () => controller.abort(); + }, [url, userId]); +} + dayjs.locale("ru"); + const routeslink = [ { path: "/list", page: , header: false, sidebar: false }, { path: "/questions/:quizId", page: , header: true, sidebar: true }, @@ -53,7 +99,7 @@ export default function App() { }); useUserAccountFetcher({ - url: "https://squiz.pena.digital/customer/account", + url: "https://squiz.pena.digital/squiz/account/get", userId, onNewUserAccount: setUserAccount, onError: (error) => { diff --git a/src/assets/icons/ArrowDownIcon.tsx b/src/assets/icons/ArrowDownIcon.tsx index 0b92d66a..59392473 100755 --- a/src/assets/icons/ArrowDownIcon.tsx +++ b/src/assets/icons/ArrowDownIcon.tsx @@ -1,7 +1,12 @@ -import { Box, useTheme } from "@mui/material"; +import {Box, SxProps, Theme, useTheme} from "@mui/material"; - -export default function ArrowDownIcon(props: any) { +interface Color{ + color?: string +} +export default function ArrowDownIcon( + props: any, + {color = "#7E2AEA"}: Color +) { const theme = useTheme(); return ( @@ -17,7 +22,7 @@ export default function ArrowDownIcon(props: any) { }} > - + ); diff --git a/src/assets/icons/ArrowGear.svg b/src/assets/icons/ArrowGear.svg index 735328ac..53a0ca2a 100644 --- a/src/assets/icons/ArrowGear.svg +++ b/src/assets/icons/ArrowGear.svg @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/assets/icons/BackButtonIcon.tsx b/src/assets/icons/BackButtonIcon.tsx new file mode 100644 index 00000000..c0fc7065 --- /dev/null +++ b/src/assets/icons/BackButtonIcon.tsx @@ -0,0 +1,11 @@ +export const BackButtonIcon = () => ( + + + +); diff --git a/src/assets/icons/CalendarIcon.tsx b/src/assets/icons/CalendarIcon.tsx index a3c91293..3c3544e9 100644 --- a/src/assets/icons/CalendarIcon.tsx +++ b/src/assets/icons/CalendarIcon.tsx @@ -1,6 +1,9 @@ -import { Box } from "@mui/material"; +import {Box, SxProps, Theme} from "@mui/material"; -export default function CalendarIcon() { +interface Props { + sx?: SxProps +} +export default function CalendarIcon({sx}:Props) { return ( diff --git a/src/assets/icons/Checkbox.tsx b/src/assets/icons/Checkbox.tsx index 265b62e3..6974b531 100644 --- a/src/assets/icons/Checkbox.tsx +++ b/src/assets/icons/Checkbox.tsx @@ -1,10 +1,11 @@ import { Box, useTheme } from "@mui/material"; -type CheckboxIconProps = { +interface CheckboxIconProps { checked?: boolean; + color?: string; }; -export const CheckboxIcon = ({ checked = false }: CheckboxIconProps) => { +export default function CheckboxIcon ({ checked = false, color = "#7E2AEA", }: CheckboxIconProps) { const theme = useTheme(); return ( @@ -17,9 +18,9 @@ export const CheckboxIcon = ({ checked = false }: CheckboxIconProps) => { justifyContent: "center", alignItems: "center", backgroundColor: checked - ? theme.palette.brightPurple.main - : theme.palette.background.default, - border: `1px solid ${theme.palette.grey2.main}`, + ? color + : "#F2F3F7", + border: `1px solid #9A9AAF`, }} > {checked && ( diff --git a/src/assets/icons/CrossedEyeIcon.tsx b/src/assets/icons/CrossedEyeIcon.tsx index c9021ca9..1d433122 100644 --- a/src/assets/icons/CrossedEyeIcon.tsx +++ b/src/assets/icons/CrossedEyeIcon.tsx @@ -2,14 +2,7 @@ import { FC, SVGProps } from "react"; export const CrossedEyeIcon: FC> = (props) => { return ( - + - - - - - - ); -} \ No newline at end of file + return ( + + + + + + + ); +} diff --git a/src/assets/icons/LinkSimple.tsx b/src/assets/icons/LinkSimple.tsx new file mode 100644 index 00000000..193b7acb --- /dev/null +++ b/src/assets/icons/LinkSimple.tsx @@ -0,0 +1,27 @@ +import { FC, SVGProps } from "react"; + +export const LinkSimple: FC> = (props) => ( + + + + + +); diff --git a/src/assets/icons/NameplateLogo.tsx b/src/assets/icons/NameplateLogo.tsx index a977b2fe..8a999fc6 100644 --- a/src/assets/icons/NameplateLogo.tsx +++ b/src/assets/icons/NameplateLogo.tsx @@ -12,7 +12,7 @@ export const NameplateLogo: FC> = (props) => ( ); diff --git a/src/assets/icons/NameplateLogoFQ.tsx b/src/assets/icons/NameplateLogoFQ.tsx index db350ab5..947a7eae 100644 --- a/src/assets/icons/NameplateLogoFQ.tsx +++ b/src/assets/icons/NameplateLogoFQ.tsx @@ -1,22 +1,57 @@ import { FC, SVGProps } from "react"; export const NameplateLogoFQ: FC> = (props) => ( - + - - - + + + - - - - - + + + + + - + diff --git a/src/assets/icons/NameplateLogoFQDark.tsx b/src/assets/icons/NameplateLogoFQDark.tsx new file mode 100644 index 00000000..651c17b2 --- /dev/null +++ b/src/assets/icons/NameplateLogoFQDark.tsx @@ -0,0 +1,24 @@ +import { FC, SVGProps } from "react"; + +export const NameplateLogoFQDark: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + + +); \ No newline at end of file diff --git a/src/assets/icons/UploadIcon.tsx b/src/assets/icons/UploadIcon.tsx index 46c386c6..573fcbfb 100755 --- a/src/assets/icons/UploadIcon.tsx +++ b/src/assets/icons/UploadIcon.tsx @@ -1,7 +1,10 @@ import { Box, useTheme } from "@mui/material"; +interface Props{ + color?: string +} -export default function UploadIcon() { +export default function UploadIcon({color= "#9A9AAF"}: Props) { const theme = useTheme(); return ( @@ -15,9 +18,9 @@ export default function UploadIcon() { }} > - - - + + + ); diff --git a/src/constants/number.ts b/src/constants/number.ts index 96cbd5a3..76d47ff8 100644 --- a/src/constants/number.ts +++ b/src/constants/number.ts @@ -10,11 +10,11 @@ export const QUIZ_QUESTION_NUMBER: Omit required: false, innerNameCheck: false, innerName: "", - range: "1—100", + range: "0—100", defaultValue: 0, step: 1, steps: 5, - start: 50, + start: 0, chooseRange: false, form: "star", }, diff --git a/src/model/questionTypes/shared.ts b/src/model/questionTypes/shared.ts index 6809b0b8..b0fb8084 100644 --- a/src/model/questionTypes/shared.ts +++ b/src/model/questionTypes/shared.ts @@ -14,109 +14,112 @@ import type { QuizQuestionResult } from "./result"; import { nanoid } from "nanoid"; export interface QuestionBranchingRuleMain { - next: string; - or: boolean; - rules: { - question: string; //id родителя (пока что) - answers: string[] - }[] + next: string; + or: boolean; + rules: { + question: string; //id родителя (пока что) + answers: string[]; + }[]; } export interface QuestionBranchingRule { - - children: string[], - //список условий - main: QuestionBranchingRuleMain[]; - parentId: string | null | "root"; - default: string; + children: string[]; + //список условий + main: QuestionBranchingRuleMain[]; + parentId: string | null | "root"; + default: string; } export interface QuestionHint { - /** Текст подсказки */ - text: string; - /** URL видео подсказки */ - video: string; + /** Текст подсказки */ + text: string; + /** URL видео подсказки */ + video: string; } export type QuestionVariant = { - id: string; - /** Текст */ - answer: string; - /** Текст подсказки */ - hints: string; - /** Дополнительное поле для текста, emoji, ссылки на картинку */ - extendedText: string; - /** Оригинал изображения (до кропа) */ - originalImageUrl: string; + id: string; + /** Текст */ + answer: string; + /** Текст подсказки */ + hints: string; + /** Дополнительное поле для текста, emoji, ссылки на картинку */ + extendedText: string; + /** Оригинал изображения (до кропа) */ + originalImageUrl: string; }; export interface QuizQuestionBase { - backendId: number; - /** Stable id, generated on client */ + backendId: number; + /** Stable id, generated on client */ + id: string; + quizId: number; + title: string; + description: string; + page: number; + type?: QuestionType | null; + expanded: boolean; + openedModalSettings: boolean; + deleted: boolean; + deleteTimeoutId: number; + content: { id: string; - quizId: number; - title: string; - description: string; - page: number; - type?: QuestionType | null; - expanded: boolean; - openedModalSettings: boolean; - deleted: boolean; - deleteTimeoutId: number; - content: { - id: string; - hint: QuestionHint; - rule: QuestionBranchingRule; - back: string; - originalBack: string; - autofill: boolean; - }; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + originalBack: string; + autofill: boolean; + }; } export interface UntypedQuizQuestion { - type: null; - id: string; - quizId: number; - title: string; - description: string; - expanded: boolean; - deleted: boolean; + type: null; + id: string; + quizId: number; + title: string; + description: string; + expanded: boolean; + deleted: boolean; } export type AnyTypedQuizQuestion = - | QuizQuestionVariant - | QuizQuestionImages - | QuizQuestionVarImg - | QuizQuestionEmoji - | QuizQuestionText - | QuizQuestionSelect - | QuizQuestionDate - | QuizQuestionNumber - | QuizQuestionFile - | QuizQuestionPage - | QuizQuestionRating - | QuizQuestionResult; - - + | QuizQuestionVariant + | QuizQuestionImages + | QuizQuestionVarImg + | QuizQuestionEmoji + | QuizQuestionText + | QuizQuestionSelect + | QuizQuestionDate + | QuizQuestionNumber + | QuizQuestionFile + | QuizQuestionPage + | QuizQuestionRating + | QuizQuestionResult; type FilterQuestionsWithVariants = T extends { - content: { variants: QuestionVariant[]; }; -} ? T : never; + content: { variants: QuestionVariant[] }; +} + ? T + : never; export type QuizQuestionsWithVariants = FilterQuestionsWithVariants; - -export const createBranchingRuleMain: (targetId:string, parentId:string) => QuestionBranchingRuleMain = (targetId, parentId) => ({ - next: targetId, - or: false, - rules: [{ - question: parentId, - answers: [] as string[], - }] -}) +export const createBranchingRuleMain: (targetId: string, parentId: string) => QuestionBranchingRuleMain = ( + targetId, + parentId +) => ({ + next: targetId, + or: false, + rules: [ + { + question: parentId, + answers: [] as string[], + }, + ], +}); export const createQuestionVariant: () => QuestionVariant = () => ({ - id: nanoid(), - answer: "", - extendedText: "", - hints: "", - originalImageUrl: "", -}); + id: nanoid(), + answer: "", + extendedText: "", + hints: "", + originalImageUrl: "", +}); diff --git a/src/model/quizSettings.ts b/src/model/quizSettings.ts index 4efd2ff7..02b95274 100644 --- a/src/model/quizSettings.ts +++ b/src/model/quizSettings.ts @@ -33,6 +33,7 @@ export interface QuizConfig { startpageType: QuizStartpageType; results: QuizResultsType; haveRoot: string | null; + theme: "StandardTheme" | "StandardDarkTheme" | "PinkTheme" | "PinkDarkTheme" | "BlackWhiteTheme" | "OliveTheme" | "YellowTheme" | "GoldDarkTheme" | "PurpleTheme" | "BlueTheme" | "BlueDarkTheme"; resultInfo: { when: 'before' | 'after' | 'email', share: true | false, @@ -95,6 +96,7 @@ export const defaultQuizConfig: QuizConfig = { startpageType: null, results: null, haveRoot: null, + theme: "StandardTheme", resultInfo: { when: 'after', share: false, diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index f5ab0a57..3ecb1118 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -56,9 +56,9 @@ function CsComponent({ }: CsComponentProps) { const quiz = useCurrentQuiz(); - const { dragQuestionContentId, desireToOpenABranchingModal, canCreatePublic } = useUiTools() + const { dragQuestionContentId, desireToOpenABranchingModal, canCreatePublic, someWorkBackend } = useUiTools() const trashQuestions = useQuestionsStore().questions - const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null) + const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null && !question.deleted) const [startCreate, setStartCreate] = useState(""); const [startRemove, setStartRemove] = useState(""); @@ -87,13 +87,6 @@ function CsComponent({ gearsContainer, }); - - useEffect(() => { - return () => { - // if (!canCreatePublic) updateModalInfoWhyCantCreate(true) - } - }, []); - useLayoutEffect(() => { const cy = cyRef?.current if (desireToOpenABranchingModal) { @@ -104,11 +97,14 @@ function CsComponent({ cy?.elements().data("eroticeyeblink", false) } }, [desireToOpenABranchingModal]) + //Техническая штучка. Гарантирует не отрисовку модалки по первому входу на страничку. И очистка данных по расскоменчиванию + //Быстро просто дешево и сердито :) useLayoutEffect(() => { updateOpenedModalSettingsId() // updateRootContentId(quiz.id, "") // clearRuleForAll() }, []) + //Отлов mouseup для отрисовки ноды useEffect(() => { if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) { addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId }) @@ -119,24 +115,39 @@ function CsComponent({ const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => { if (quiz) { - //запрещаем работу родителя-ребенка если это один и тот же вопрос if (parentNodeContentId === targetNodeContentId) return const cy = cyRef?.current const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length + + + const parentQuestion = getQuestionByContentId(parentNodeContentId) + //Нельзя добавлять больше 1 ребёнка вопросам типа страница, ползунок, своё поле для ввода и дата + if ((parentQuestion?.type === "date" || parentQuestion?.type === "text" || parentQuestion?.type === "number" || parentQuestion?.type === "page") + && parentQuestion.content.rule.children.length === 1 + ) { + console.log(parentQuestion.content.rule.children) + console.log("parentQuestion.content.rule.children.length === 1",parentQuestion.content.rule.children.length === 1) + enqueueSnackbar("у вопроса этого типа может быть только 1 потомок") + return + } + //если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа const targetQuestion = { ...getQuestionByContentId(targetNodeContentId || dragQuestionContentId) } as AnyTypedQuizQuestion if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) { clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren }) cy?.data('changed', true) createResult(quiz.backendId, targetQuestion.content.id) + console.log("Я собираюсь добавить узел из такого вопроса ", targetQuestion) + console.log("Я собираюсь добавить узел у которого папаша вот такой ", parentNodeContentId) const es = cy?.add([ { data: { id: targetQuestion.content.id, - label: targetQuestion.title === "" || targetQuestion.title === " " ? "noname" : targetQuestion.title + label: targetQuestion.title === "" || targetQuestion.title === " " ? "noname" : targetQuestion.title, + parentType: parentNodeContentId } }, { @@ -188,14 +199,12 @@ function CsComponent({ }) if (!noChild) {//детей больше 1 + console.log("детей ", noChild, " открываем модалку ветвления") //- предупреждаем стор вопросов об открытии модалки ветвления updateOpenedModalSettingsId(targetQuestion.content.id) } - - } - useEffect(() => { if (startCreate) { addNode({ parentNodeContentId: startCreate }); @@ -211,26 +220,40 @@ function CsComponent({ } }, [startRemove]); + //Отработка первичного рендера странички графика + const firstRender = useRef(true) useEffect(() => { - document - .querySelector("#root") - ?.addEventListener("mouseup", cleardragQuestionContentId); - const cy = cyRef.current; - const eles = cy?.add( - storeToNodes( - questions.filter( - (question) => question.type && question.type !== "result" - ) as AnyTypedQuizQuestion[] - ) - ); - cy?.data("changed", true); - // cy.data('changed', true) - const elecs = eles?.layout(layoutOptions).run(); - cy?.on("add", () => cy.data("changed", true)); - cy?.fit(); - //cy?.layout().run() + console.log("____________ПЕРВЧИНЫЙ РЕНДЕР____________") + console.log("______someWorkBackend______", someWorkBackend) + + if (!someWorkBackend && firstRender.current) { + console.log("цс первично отрабатывает") + document + .querySelector("#root") + ?.addEventListener("mouseup", cleardragQuestionContentId); + const cy = cyRef.current; + + console.log("СПИСОК ЭЛЕМЕНТОВ ЦИТОСКЕЙПА В ПЕРВЧИНЫЙ РЕНДЕР") + console.log(cy?.elements()) + + const eles = cy?.add( + storeToNodes( + questions.filter( + (question) => question.type && question.type !== "result" + ) as AnyTypedQuizQuestion[] + ) + ); + cy?.data("changed", true); + // cy.data('changed', true) + const elecs = eles?.layout(layoutOptions).run(); + cy?.on("add", () => cy.data("changed", true)); + cy?.fit(); + //cy?.layout().run() + firstRender.current = false + } return () => { + console.log("разрендер") document .querySelector("#root") ?.removeEventListener("mouseup", cleardragQuestionContentId); @@ -239,7 +262,7 @@ function CsComponent({ crossesContainer.current?.remove(); gearsContainer.current?.remove(); }; - }, []); + }, [someWorkBackend]); return ( diff --git a/src/pages/Questions/BranchingMap/FirstNodeField.tsx b/src/pages/Questions/BranchingMap/FirstNodeField.tsx index ba14fc18..499c2ca6 100644 --- a/src/pages/Questions/BranchingMap/FirstNodeField.tsx +++ b/src/pages/Questions/BranchingMap/FirstNodeField.tsx @@ -17,6 +17,7 @@ export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetCon useLayoutEffect(() => { + console.log("компонент с плюсом") updateOpenedModalSettingsId() updateRootContentId(quiz.id, "") clearRuleForAll() diff --git a/src/pages/Questions/BranchingMap/helper.ts b/src/pages/Questions/BranchingMap/helper.ts index 0423d219..a158e26c 100644 --- a/src/pages/Questions/BranchingMap/helper.ts +++ b/src/pages/Questions/BranchingMap/helper.ts @@ -21,7 +21,8 @@ export const storeToNodes = (questions: AnyTypedQuizQuestion[]) => { if (question.content.rule.parentId) { nodes.push({data: { id: question.content.id, - label: question.title === "" || question.title === " " ? "noname" : question.title + label: question.title === "" || question.title === " " ? "noname" : question.title, + parentType: question.content.rule.parentId }}) // nodes.push({ // data: { diff --git a/src/pages/Questions/BranchingMap/hooks/usePopper.ts b/src/pages/Questions/BranchingMap/hooks/usePopper.ts index 615532a1..b022755a 100644 --- a/src/pages/Questions/BranchingMap/hooks/usePopper.ts +++ b/src/pages/Questions/BranchingMap/hooks/usePopper.ts @@ -7,6 +7,7 @@ import type { NodeSingular, AbstractEventObject, } from "cytoscape"; +import { getQuestionByContentId } from "@root/questions/actions"; type usePopperArgs = { layoutsContainer: MutableRefObject; @@ -111,6 +112,8 @@ export const usePopper = ({ ); }); + cy?.removeAllListeners() + nodesInView.toArray()?.forEach((item) => { const node = item as NodeSingularWithPopper; @@ -199,6 +202,9 @@ export const usePopper = ({ }); let gearsPopper: Popper | null = null; if (node.data().root !== true) { + console.log(node.data("parentType")) + const parentQuestion = getQuestionByContentId(node.data("parentType")) + gearsPopper = node.popper({ popper: { placement: "left", @@ -223,6 +229,10 @@ export const usePopper = ({ updateOpenedModalSettingsId(item.id()); }); + if (parentQuestion?.type === "date" || parentQuestion?.type === "text" || parentQuestion?.type === "number" || parentQuestion?.type === "page") { + gearElement.classList.add("popper-gear-none"); + } + return gearElement; }, }); diff --git a/src/pages/Questions/BranchingMap/index.tsx b/src/pages/Questions/BranchingMap/index.tsx index 6e6c137b..e728927e 100644 --- a/src/pages/Questions/BranchingMap/index.tsx +++ b/src/pages/Questions/BranchingMap/index.tsx @@ -8,6 +8,7 @@ import { useUiTools } from "@root/uiTools/store"; export const BranchingMap = () => { const quiz = useCurrentQuiz(); + console.log("рендер странички ветвления") const { dragQuestionContentId } = useUiTools(); const [modalQuestionParentContentId, setModalQuestionParentContentId] = useState(""); diff --git a/src/pages/Questions/BranchingMap/style/styles.css b/src/pages/Questions/BranchingMap/style/styles.css index 98e76b8d..7302fc6c 100644 --- a/src/pages/Questions/BranchingMap/style/styles.css +++ b/src/pages/Questions/BranchingMap/style/styles.css @@ -43,3 +43,7 @@ background-repeat: no-repeat; background-size: contain; } + +#popper-gears > .popper-gear-none { + display: none +} \ No newline at end of file diff --git a/src/pages/Questions/ButtonsOptions.tsx b/src/pages/Questions/ButtonsOptions.tsx index 1691676b..f6eba2ac 100644 --- a/src/pages/Questions/ButtonsOptions.tsx +++ b/src/pages/Questions/ButtonsOptions.tsx @@ -27,6 +27,8 @@ import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; import { updateRootContentId } from "@root/quizes/actions"; import { useUiTools } from "@root/uiTools/store"; import {useState} from "react"; +import { updateSomeWorkBackend } from "@root/uiTools/actions"; +import { DeleteFunction } from "@utils/deleteFunc"; interface Props { switchState: string; @@ -52,61 +54,6 @@ export default function ButtonsOptions({ updateDesireToOpenABranchingModal(question.content.id); }; - const deleteFn = () => { - if (question.type !== null) { - if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам - updateRootContentId(quiz.id, ""); - clearRuleForAll(); - deleteQuestion(question.id); - } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков - const clearQuestions = [] as string[]; - - //записываем потомков , а их результаты удаляем - const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { - questions.forEach((targetQuestion) => { - if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type !== "result" && targetQuestion.type !== null) { - if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); - getChildren(targetQuestion); //и ищем его потомков - } - } - }); - }; - getChildren(question); - //чистим потомков от инфы ветвления - clearQuestions.forEach((id) => { - updateQuestion(id, question => { - question.content.rule.parentId = ""; - question.content.rule.main = []; - question.content.rule.default = ""; - }); - }); - - //чистим rule родителя - const parentQuestion = getQuestionByContentId(question.content.rule.parentId); - const newRule = {}; - newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу - newRule.parentId = parentQuestion.content.rule.parentId; - newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId; - newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1); - - updateQuestion(question.content.rule.parentId, (PQ) => { - PQ.content.rule = newRule; - }); - deleteQuestion(question.id); - } - - deleteQuestion(question.id); - - const result = questions.find(q => q.type === "result" && q.content.rule.parentId === question.content.id) - if (result) deleteQuestion(result.id); - - } else { - deleteQuestion(question.id); - } - }; - - const buttonSetting: { icon: JSX.Element; title: string; @@ -171,53 +118,57 @@ export default function ButtonsOptions({ > {buttonSetting.map(({ icon, title, value, myFunc }) => ( - {value === "branching" ? ( - - - Будет показан при условии - - - Название - - - Условие 1, Условие 2 - - - Все условия обязательны - - - } - > + {value === "branching" ? + question.type === "page" || question.type === "text" || question.type === "date" || question.type === "number" ? + <> + : + ( + // + // + // Будет показан при условии + // + // + // Название + // + // + // Условие 1, Условие 2 + // + // + // Все условия обязательны + // + // + // } + // > { @@ -246,7 +197,7 @@ export default function ButtonsOptions({ {icon} {isWrappMiniButtonSetting ? null : title} - + // ) : ( <> ))} - <> + {/* <> - + */} DeleteFunction(questions, question, quiz)); } }} @@ -376,7 +327,7 @@ export default function ButtonsOptions({ variant="contained" sx={{ minWidth: "150px" }} onClick={() => { - deleteQuestionWithTimeout(question.id, deleteFn); + deleteQuestionWithTimeout(question.id, () => DeleteFunction(questions, question, quiz)); }} > Подтвердить diff --git a/src/pages/Questions/ButtonsOptionsAndPict.tsx b/src/pages/Questions/ButtonsOptionsAndPict.tsx index 67fe8f5d..332ad705 100644 --- a/src/pages/Questions/ButtonsOptionsAndPict.tsx +++ b/src/pages/Questions/ButtonsOptionsAndPict.tsx @@ -29,6 +29,8 @@ import { enqueueSnackbar } from "notistack"; import { useCurrentQuiz } from "@root/quizes/hooks"; import { updateRootContentId } from "@root/quizes/actions"; import {AnyTypedQuizQuestion} from "@model/questionTypes/shared"; +import { updateSomeWorkBackend } from "@root/uiTools/actions"; +import { DeleteFunction } from "@utils/deleteFunc"; interface Props { @@ -53,60 +55,6 @@ export default function ButtonsOptionsAndPict({ const [openDelete, setOpenDelete] = useState(false); - const deleteFn = () => { - if (question.type !== null) { - if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам - updateRootContentId(quiz.id, ""); - clearRuleForAll(); - deleteQuestion(question.id); - } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков - const clearQuestions = [] as string[]; - - //записываем потомков , а их результаты удаляем - const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { - questions.forEach((targetQuestion) => { - if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type !== null && targetQuestion.type !== "result") { - if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); - getChildren(targetQuestion); //и ищем его потомков - } - } - }); - }; - getChildren(question); - //чистим потомков от инфы ветвления - clearQuestions.forEach((id) => { - updateQuestion(id, question => { - question.content.rule.parentId = ""; - question.content.rule.main = []; - question.content.rule.default = ""; - }); - }); - - //чистим rule родителя - const parentQuestion = getQuestionByContentId(question.content.rule.parentId); - const newRule = {}; - newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу - newRule.parentId = parentQuestion.content.rule.parentId; - newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId; - newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1); - - updateQuestion(question.content.rule.parentId, (PQ) => { - PQ.content.rule = newRule; - }); - deleteQuestion(question.id); - } - - deleteQuestion(question.id); - - const result = questions.find(q => q.type === "result" && q.content.rule.parentId === question.content.id) - if (result) deleteQuestion(result.id); - - } else { - deleteQuestion(question.id); - } - }; - useEffect(() => { if (question.deleteTimeoutId) { clearTimeout(question.deleteTimeoutId); @@ -199,7 +147,7 @@ export default function ButtonsOptionsAndPict({ {isIconMobile ? null : "Подсказка"} <> - } - > + > */} setButtonHover("branching")} onMouseLeave={() => setButtonHover("")} @@ -284,7 +232,7 @@ export default function ButtonsOptionsAndPict({ /> {isIconMobile ? null : "Ветвление"} - + {/* */} setButtonHover("image")} onMouseLeave={() => setButtonHover("")} @@ -318,7 +266,7 @@ export default function ButtonsOptionsAndPict({ /> {isIconMobile ? null : "Изображение"} - setOpenedReallyChangingModal(true)} sx={{ minWidth: "30px", @@ -347,7 +295,7 @@ export default function ButtonsOptionsAndPict({ }} > - + */} DeleteFunction(questions, question, quiz)); } }} data-cy="delete-question" @@ -411,7 +359,7 @@ export default function ButtonsOptionsAndPict({ variant="contained" sx={{ minWidth: "150px" }} onClick={() => { - deleteQuestionWithTimeout(question.id, deleteFn); + deleteQuestionWithTimeout(question.id, () => DeleteFunction(questions, question, quiz)); }} > Подтвердить diff --git a/src/pages/Questions/DeleteNodeModal/index.tsx b/src/pages/Questions/DeleteNodeModal/index.tsx index 0da5039b..c06aff37 100644 --- a/src/pages/Questions/DeleteNodeModal/index.tsx +++ b/src/pages/Questions/DeleteNodeModal/index.tsx @@ -14,7 +14,7 @@ import { getQuestionByContentId } from "@root/questions/actions"; import { updateDeleteId } from "@root/uiTools/actions"; import { useUiTools } from "@root/uiTools/store"; -import { CheckboxIcon } from "@icons/Checkbox"; +import CheckboxIcon from "@icons/Checkbox"; type DeleteNodeModalProps = { removeNode?: (id: string) => void; diff --git a/src/pages/Questions/DraggableList/ChooseAnswerModal.tsx b/src/pages/Questions/DraggableList/ChooseAnswerModal.tsx index 5e067917..e95abc0b 100644 --- a/src/pages/Questions/DraggableList/ChooseAnswerModal.tsx +++ b/src/pages/Questions/DraggableList/ChooseAnswerModal.tsx @@ -12,11 +12,13 @@ import { Typography, useTheme, } from "@mui/material"; -import { changeQuestionType } from "@root/questions/actions"; +import { changeQuestionType, updateQuestion } from "@root/questions/actions"; import type { RefObject } from "react"; import { useState } from "react"; import type { AnyTypedQuizQuestion, UntypedQuizQuestion } from "../../../model/questionTypes/shared"; import { BUTTON_TYPE_QUESTIONS } from "../TypeQuestions"; +import { useQuestionsStore } from "@root/questions/store"; +import { updateSomeWorkBackend } from "@root/uiTools/actions"; type ChooseAnswerModalProps = { @@ -36,6 +38,7 @@ export const ChooseAnswerModal = ({ }: ChooseAnswerModalProps) => { const [openModal, setOpenModal] = useState(false); const [selectedValue, setSelectedValue] = useState("text"); + const { questions } = useQuestionsStore() const theme = useTheme(); return ( @@ -94,8 +97,8 @@ export const ChooseAnswerModal = ({ background: "#FFFFFF", }} > - - Все настройки, кроме заголовка вопроса будут сброшены
+ + Все настройки, кроме заголовка вопроса будут сброшены
(вопрос всё ещё будет участвовать в ветвлении, но его условия будут сброшены)
{ + onClick={async () => { + + updateSomeWorkBackend(true) setOpenModal(false); + const clearQuestions = [] as string[]; + + //записываем потомков , а их результаты удаляем + const getChildren = (parentQuestion: any) => { + questions.forEach((targetQuestion) => { + if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его + if (targetQuestion.type !== "result" && targetQuestion.type !== null) { + if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); + getChildren(targetQuestion); //и ищем его потомков + } + } + }); + }; + getChildren(question); + + updateQuestion(question.id, q => { + q.content.rule.parentId = ""; + q.content.rule.main = []; + q.content.rule.children = []; + q.content.rule.default = ""; + }); + //чистим потомков от инфы ветвления + await Promise.allSettled( + clearQuestions.map((id) => { + updateQuestion(id, question => { + question.content.rule.parentId = ""; + question.content.rule.main = []; + question.content.rule.children = []; + question.content.rule.default = ""; + }); + }) + ) changeQuestionType(question.id, selectedValue); + updateSomeWorkBackend(false) }} > Подтвердить diff --git a/src/pages/Questions/DraggableList/DraggableListItem.tsx b/src/pages/Questions/DraggableList/DraggableListItem.tsx index 2b08eec4..956b47ba 100644 --- a/src/pages/Questions/DraggableList/DraggableListItem.tsx +++ b/src/pages/Questions/DraggableList/DraggableListItem.tsx @@ -29,7 +29,6 @@ function DraggableListItem({ question, isDragging, index }: Props) { updateEditSomeQuestion(); } }, 200); - } }, [editSomeQuestion]); diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index 7d81a89c..eb5e4830 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -55,6 +55,8 @@ import TypeQuestions from "../TypeQuestions"; import { QuestionType } from "@model/question/question"; import { useCurrentQuiz } from "@root/quizes/hooks"; import { useQuestionsStore } from "@root/questions/store"; +import { updateSomeWorkBackend } from "@root/uiTools/actions"; +import { DeleteFunction } from "@utils/deleteFunc"; interface Props { question: AnyTypedQuizQuestion | UntypedQuizQuestion; @@ -87,61 +89,6 @@ const maxLengthTextField = 225; }); }, 200); - const deleteFn = () => { - if (question.type !== null) { - if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам - updateRootContentId(quiz.id, ""); - clearRuleForAll(); - deleteQuestion(question.id); - } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков - const clearQuestions = [] as string[]; - - //записываем потомков , а их результаты удаляем - const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { - questions.forEach((targetQuestion) => { - if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type !== null && targetQuestion.type !== "result") { - if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); - getChildren(targetQuestion); //и ищем его потомков - } - } - }); - }; - getChildren(question); - //чистим потомков от инфы ветвления - clearQuestions.forEach((id) => { - updateQuestion(id, question => { - question.content.rule.parentId = ""; - question.content.rule.main = []; - question.content.rule.default = ""; - }); - }); - - //чистим rule родителя - const parentQuestion = getQuestionByContentId(question.content.rule.parentId); - const newRule = {}; - newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу - newRule.parentId = parentQuestion.content.rule.parentId; - newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId; - newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1); - - updateQuestion(question.content.rule.parentId, (PQ) => { - PQ.content.rule = newRule; - }); - deleteQuestion(question.id); - } - - - deleteQuestion(question.id); - - const result = questions.find(q => q.type === "result" && q.content.rule.parentId === question.content.id) - if (result) deleteQuestion(result.id); - } else { - deleteQuestion(question.id); - } - }; - - const handleInputFocus = () => { setIsTextFieldtActive(true); }; @@ -333,7 +280,7 @@ const maxLengthTextField = 225; if (question.content.rule.parentId.length !== 0) { setOpenDelete(true); } else { - deleteQuestionWithTimeout(question.id, deleteFn); + deleteQuestionWithTimeout(question.id, () => DeleteFunction(questions, question, quiz)); } }} data-cy="delete-question" @@ -371,7 +318,7 @@ const maxLengthTextField = 225; variant="contained" sx={{ minWidth: "150px" }} onClick={() => { - deleteQuestionWithTimeout(question.id, deleteFn); + deleteQuestionWithTimeout(question.id, () => DeleteFunction(questions, question, quiz)); }} > Подтвердить diff --git a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx index 8c0c9a7e..6def0204 100644 --- a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx @@ -12,21 +12,24 @@ import Page from "@icons/questionsPage/page"; import RatingIcon from "@icons/questionsPage/rating"; import Slider from "@icons/questionsPage/slider"; import { - Box, Checkbox, - FormControl, - FormControlLabel, - IconButton, - InputAdornment, - Paper, TextField, - useMediaQuery, - useTheme + Box, + Checkbox, + FormControl, + FormControlLabel, + IconButton, + InputAdornment, + Paper, + TextField, + useMediaQuery, + useTheme, } from "@mui/material"; import { - copyQuestion, - deleteQuestion, deleteQuestionWithTimeout, - toggleExpandQuestion, - updateQuestion, - updateUntypedQuestion + copyQuestion, + deleteQuestion, + deleteQuestionWithTimeout, + toggleExpandQuestion, + updateQuestion, + updateUntypedQuestion, } from "@root/questions/actions"; import CustomTextField from "@ui_kit/CustomTextField"; import { useRef, useState } from "react"; @@ -66,246 +69,236 @@ export default function QuestionsPageCard({ question, questionIndex, draggablePr }); }, 200); - return ( - <> - + + + + - - - - setTitle(target.value)} - sx={{ - width: "100%", - margin: isMobile ? "10px 0" : 0, - "& .MuiInputBase-root": { - color: "#000000", - backgroundColor: question.expanded - ? theme.palette.background.default - : "transparent", - height: "48px", - borderRadius: "10px", - ".MuiOutlinedInput-notchedOutline": { - borderWidth: "1px !important", - border: !question.expanded ? "none" : null, - }, - "& .MuiInputBase-input::placeholder": { - color: "#4D4D4D", - opacity: 0.8, - }, - }, - }} - InputProps={{ - startAdornment: ( - - setOpen((isOpened) => !isOpened)} - > - {IconAndrom(question.type)} - - setOpen(false)} - anchorRef={anchorRef} - question={question} - questionType={question.type} - /> - - ), - }} - /> - - - - toggleExpandQuestion(question.id)} - > - {question.expanded ? ( - - ) : ( - - )} - - - {question.expanded ? ( - <> - ) : ( - - - } - checkedIcon={} - /> - } - label={""} - sx={{ - color: theme.palette.grey2.main, - ml: "-9px", - mr: 0, - userSelect: "none", - }} - /> - copyQuestion(question.id, question.quizId)} - > - - - { - deleteQuestionWithTimeout(question.id, deleteQuestion(question.id)); - - }} - > - - - - )} - - - {questionIndex + 1} - - - - - - - + setTitle(target.value)} + sx={{ + width: "100%", + margin: isMobile ? "10px 0" : 0, + "& .MuiInputBase-root": { + color: "#000000", + backgroundColor: question.expanded ? theme.palette.background.default : "transparent", + height: "48px", + borderRadius: "10px", + ".MuiOutlinedInput-notchedOutline": { + borderWidth: "1px !important", + border: !question.expanded ? "none" : null, + }, + "& .MuiInputBase-input::placeholder": { + color: "#4D4D4D", + opacity: 0.8, + }, + }, + }} + InputProps={{ + startAdornment: ( + + setOpen((isOpened) => !isOpened)} + > + {IconAndrom(question.type)} + + setOpen(false)} + anchorRef={anchorRef} + question={question} + questionType={question.type} + /> - {question.expanded && ( - <> - {question.type === null ? ( - - ) : ( - - )} - - )} + ), + }} + /> + + + + toggleExpandQuestion(question.id)} + > + {question.expanded ? ( + + ) : ( + + )} + + + {question.expanded ? ( + <> + ) : ( + + + } + checkedIcon={} + /> + } + label={""} + sx={{ + color: theme.palette.grey2.main, + ml: "-9px", + mr: 0, + userSelect: "none", + }} + /> + copyQuestion(question.id, question.quizId)}> + + + { + deleteQuestionWithTimeout(question.id, () => deleteQuestion(question.id)); + }} + > + + + + )} + + + {questionIndex + 1} - - - ); + + + + + + + + {question.expanded && ( + <> + {question.type === null ? ( + + ) : ( + + )} + + )} + + + + ); } const IconAndrom = (questionType: QuestionType | null) => { diff --git a/src/pages/Questions/PageOptions/PageOptions.tsx b/src/pages/Questions/PageOptions/PageOptions.tsx index 3f4899b2..a9ccf035 100644 --- a/src/pages/Questions/PageOptions/PageOptions.tsx +++ b/src/pages/Questions/PageOptions/PageOptions.tsx @@ -1,18 +1,13 @@ -import { VideofileIcon } from "@icons/questionsPage/VideofileIcon"; -import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material"; -import { updateQuestion, uploadQuestionImage } from "@root/questions/actions"; -import { useCurrentQuiz } from "@root/quizes/hooks"; -import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; +import { Box, useMediaQuery, useTheme } from "@mui/material"; +import { updateQuestion } from "@root/questions/actions"; import CustomTextField from "@ui_kit/CustomTextField"; -import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; + import { useState } from "react"; import { useDebouncedCallback } from "use-debounce"; import type { QuizQuestionPage } from "../../../model/questionTypes/page"; import ButtonsOptions from "../ButtonsOptions"; -import { UploadImageModal } from "../UploadImage/UploadImageModal"; -import { UploadVideoModal } from "../UploadVideoModal"; import SwitchPageOptions from "./switchPageOptions"; -import { useDisclosure } from "../../../utils/useDisclosure"; +import { MediaSelectionAndDisplay } from "@ui_kit/MediaSelectionAndDisplay"; type Props = { disableInput?: boolean; @@ -20,16 +15,11 @@ type Props = { }; export default function PageOptions({ disableInput, question }: Props) { - const [openVideoModal, setOpenVideoModal] = useState(false); const [switchState, setSwitchState] = useState("setting"); const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down(980)); const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(780)); - const quizQid = useCurrentQuiz()?.qid; - const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } = - useCropModalState(); - const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); const setText = useDebouncedCallback((value) => { updateQuestion(question.id, (question) => { @@ -43,25 +33,6 @@ export default function PageOptions({ disableInput, question }: Props) { setSwitchState(data); }; - async function handleImageUpload(file: File) { - const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { - if (question.type !== "page") return; - - question.content.picture = url; - question.content.originalPicture = url; - }); - closeImageUploadModal(); - openCropModal(file, url); - } - - function handleCropModalSaveClick(imageBlob: Blob) { - uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { - if (question.type !== "page") return; - - question.content.picture = url; - }); - } - return ( <> @@ -82,179 +52,7 @@ export default function PageOptions({ disableInput, question }: Props) { /> - - - { - if (question.content.picture) { - return openCropModal(question.content.picture, question.content.originalPicture); - } - - openImageUploadModal(); - }} - onPlusClick={() => { - openImageUploadModal(); - }} - /> - - - updateQuestion(question.id, (question) => ((question as QuizQuestionPage).content.useImage = true)) - } - > - Изображение - - - - - или - - {isMobile ? ( - - - - - - + - - - ) : ( - - - - - setOpenVideoModal(true)} - style={{ - display: "flex", - alignItems: "center", - justifyContent: "center", - background: "#7E2AEA", - height: "100%", - width: "25px", - color: "white", - fontSize: "15px", - }} - > - + - - - )} - - - updateQuestion(question.id, (question) => ((question as QuizQuestionPage).content.useImage = false)) - } - > - Видео - - - setOpenVideoModal(false)} - video={question.content.video} - onUpload={(url) => { - updateQuestion(question.id, (question) => { - if (question.type !== "page") return; - - question.content.video = url; - }); - }} - /> - + diff --git a/src/pages/Questions/QuestionSwitchWindowTool.tsx b/src/pages/Questions/QuestionSwitchWindowTool.tsx index 5988ecd0..e60f50ca 100644 --- a/src/pages/Questions/QuestionSwitchWindowTool.tsx +++ b/src/pages/Questions/QuestionSwitchWindowTool.tsx @@ -1,26 +1,47 @@ - -import { - Box, useMediaQuery, useTheme, -} from "@mui/material"; +import { useEffect, useLayoutEffect } from "react"; +import { 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 { useQuestionsStore } from "@root/questions/store"; import { useUiTools } from "@root/uiTools/store"; +import { useQuestions } from "@root/questions/hooks"; +import { useCurrentQuiz } from "@root/quizes/hooks"; +import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions"; -export const QuestionSwitchWindowTool = () => { - const {questions} = useQuestionsStore.getState() - const {openBranchingPanel} = useUiTools() - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(600)); - return ( - - - {openBranchingPanel? : } - - - - ) -} \ No newline at end of file +interface Props { + openBranchingPage: boolean; + setOpenBranchingPage: (a: boolean) => void; +} + +export const QuestionSwitchWindowTool = ({ openBranchingPage, setOpenBranchingPage }: Props) => { + const { questions } = useQuestionsStore.getState(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(600)); + const quiz = useCurrentQuiz(); + console.log("Я компонент в котором отвечала"); + + const openBranchingPageHC = () => { + if (!openBranchingPage) { + deleteTimeoutedQuestions(questions, quiz); + } + setOpenBranchingPage(!openBranchingPage); + }; + + return ( + + {openBranchingPage ? : } + {openBranchingPage && ( + + )} + + ); +}; diff --git a/src/pages/Questions/QuestionsPage.tsx b/src/pages/Questions/QuestionsPage.tsx index 61839166..e5ae8a7c 100755 --- a/src/pages/Questions/QuestionsPage.tsx +++ b/src/pages/Questions/QuestionsPage.tsx @@ -13,7 +13,12 @@ import { useQuestionsStore } from "@root/questions/store"; import { updateOpenBranchingPanel, updateEditSomeQuestion } from "@root/uiTools/actions"; import { useUiTools } from "@root/uiTools/store"; -export default function QuestionsPage() { +interface Props { + openBranchingPage: boolean; + setOpenBranchingPage: (a: boolean) => void; +} + +export default function QuestionsPage({ openBranchingPage, setOpenBranchingPage }: Props) { const theme = useTheme(); const { openedModalSettingsId, openBranchingPanel } = useUiTools(); const isMobile = useMediaQuery(theme.breakpoints.down(660)); @@ -55,7 +60,7 @@ export default function QuestionsPage() { Свернуть всё - + void; sx?: SxProps; placeholder?: string; + colorPlaceholder?: string; + colorMain?: string; + color?: string; }; export const Select = ({ @@ -27,6 +30,9 @@ export const Select = ({ onChange, sx, placeholder = "", + colorMain = "#7E2AEA", + colorPlaceholder = "#9A9AAF", + color }: SelectProps) => { const [activeItem, setActiveItem] = useState( empty ? -1 : activeItemIndex @@ -63,7 +69,7 @@ export const Select = ({ value ? ( items[Number(value)] ) : ( - + {placeholder} ) @@ -77,7 +83,7 @@ export const Select = ({ height: "48px", borderRadius: "8px", "& .MuiOutlinedInput-notchedOutline": { - border: `1px solid ${theme.palette.brightPurple.main} !important`, + border: `1px solid ${colorMain} !important`, height: "48px", borderRadius: "10px", }, @@ -100,21 +106,21 @@ export const Select = ({ gap: "8px", "& .Mui-selected": { backgroundColor: theme.palette.background.default, - color: theme.palette.brightPurple.main, + color: colorMain, }, }, }, }} inputProps={{ sx: { - color: theme.palette.brightPurple.main, + color: colorMain, display: "flex", alignItems: "center", px: "9px", gap: "20px", }, }} - IconComponent={(props) => } + IconComponent={(props) => } > {items.map((item, index) => ( {item} diff --git a/src/pages/Questions/SliderOptions/SliderOptions.tsx b/src/pages/Questions/SliderOptions/SliderOptions.tsx index 4a7225fe..b413fd51 100644 --- a/src/pages/Questions/SliderOptions/SliderOptions.tsx +++ b/src/pages/Questions/SliderOptions/SliderOptions.tsx @@ -1,11 +1,16 @@ import { useEffect, useState } from "react"; import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; -import ButtonsOptions from "../ButtonsOptions"; +import { useDebouncedCallback } from "use-debounce"; + import CustomNumberField from "@ui_kit/CustomNumberField"; + +import ButtonsOptions from "../ButtonsOptions"; import SwitchSlider from "./switchSlider"; -import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; + import { updateQuestion } from "@root/questions/actions"; +import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; + interface Props { question: QuizQuestionNumber; } @@ -19,14 +24,31 @@ export default function SliderOptions({ question }: Props) { const [startError, setStartError] = useState(false); const [minError, setMinError] = useState(false); const [maxError, setMaxError] = useState(false); + const startValueDebounce = useDebouncedCallback((value) => { + updateQuestion(question.id, (question) => { + if (question.type !== "number") return; + + question.content.start = value; + }); + }, 2000); useEffect(() => { const min = Number(question.content.range.split("—")[0]); const max = Number(question.content.range.split("—")[1]); const start = Number(question.content.start); - if (start < min || start > max) { + if (start < min) { setStartError(true); + startValueDebounce(min); + + return; + } + + if (start > max && min < max) { + setStartError(true); + startValueDebounce(max); + + return; } if (start >= min && start <= max) { @@ -211,7 +233,11 @@ export default function SliderOptions({ question }: Props) { placeholder={"1"} error={stepError} value={String(question.content.step)} - onChange={({ target }) => { + onChange={({ target, type }) => { + if (type === "blur") { + return; + } + updateQuestion(question.id, (question) => { if (question.type !== "number") return; @@ -219,8 +245,10 @@ export default function SliderOptions({ question }: Props) { }); }} onBlur={({ target }) => { + const min = Number(question.content.range.split("—")[0]); const max = Number(question.content.range.split("—")[1]); const step = Number(target.value); + const range = max - min; if (step > max) { updateQuestion(question.id, (question) => { @@ -229,6 +257,14 @@ export default function SliderOptions({ question }: Props) { question.content.step = max; }); } + + if (range % step) { + updateQuestion(question.id, (question) => { + if (question.type !== "number") return; + + question.content.step = 1; + }); + } }} /> diff --git a/src/pages/Questions/SwitchBranchingPanel/index.tsx b/src/pages/Questions/SwitchBranchingPanel/index.tsx index bd20f70b..67d1f39a 100644 --- a/src/pages/Questions/SwitchBranchingPanel/index.tsx +++ b/src/pages/Questions/SwitchBranchingPanel/index.tsx @@ -1,23 +1,32 @@ - - -import {Box, Typography, Switch, useTheme, Button, useMediaQuery} from "@mui/material"; +import { + Box, + Typography, + Switch, + useTheme, + Button, + useMediaQuery, +} from "@mui/material"; import { QuestionsList } from "./QuestionsList"; import { updateOpenBranchingPanel } from "@root/uiTools/actions"; -import {useQuestionsStore} from "@root/questions/store"; -import {useRef} from "react"; +import { useQuestionsStore } from "@root/questions/store"; +import { useRef } from "react"; import { useUiTools } from "@root/uiTools/store"; +interface Props { + openBranchingPage: boolean; + setOpenBranchingPage: () => void; +} -export const SwitchBranchingPanel = () => { +export const SwitchBranchingPanel = ({ + openBranchingPage, + setOpenBranchingPage}:Props) => { const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(660)); - const isTablet = useMediaQuery(theme.breakpoints.down(1446)); - - const {openBranchingPanel} = useUiTools() - const ref = useRef() - - return ( !isTablet || openBranchingPanel ? + const isMobile = useMediaQuery(theme.breakpoints.down(660)); + const isTablet = useMediaQuery(theme.breakpoints.down(1446)); + const ref = useRef(); + + return !isTablet || openBranchingPage ? ( { }} > updateOpenBranchingPanel(e.target.checked) - } + checked={openBranchingPage} + onChange={setOpenBranchingPage} sx={{ width: 50, height: 30, @@ -87,11 +94,10 @@ export const SwitchBranchingPanel = () => { Настройте связи между вопросами - - { openBranchingPanel && } + {openBranchingPage && } - : + ) : ( <> ); }; diff --git a/src/pages/ResultPage/ResultSettings.tsx b/src/pages/ResultPage/ResultSettings.tsx index cbf592ae..d1b0c5d8 100644 --- a/src/pages/ResultPage/ResultSettings.tsx +++ b/src/pages/ResultPage/ResultSettings.tsx @@ -1,14 +1,18 @@ -import IconPlus from "@icons/IconPlus"; -import Info from "@icons/Info"; -import Plus from "@icons/Plus"; -import ArrowLeft from "@icons/questionsPage/arrowLeft"; -import { Box, Button, Typography, Paper, Modal, TextField } from "@mui/material"; +import { useEffect, useRef, useState } from "react"; +// import { useBlocker } from "react-router-dom"; +import { + Box, + Button, + Typography, + Paper, + Modal, + TextField, +} from "@mui/material"; import { incrementCurrentStep } from "@root/quizes/actions"; import CustomWrapper from "@ui_kit/CustomWrapper"; import { DescriptionForm } from "./DescriptionForm/DescriptionForm"; import { ResultListForm } from "./ResultListForm"; import { SettingForm } from "./SettingForm"; -import { useEffect, useRef, useState } from "react"; import { WhenCard } from "./cards/WhenCard"; import { ResultCard, checkEmptyData } from "./cards/ResultCard"; import { EmailSettingsCard } from "./cards/EmailSettingsCard"; @@ -17,15 +21,27 @@ import { useQuestionsStore } from "@root/questions/store"; import { deleteQuestion } from "@root/questions/actions"; import { QuizQuestionResult } from "@model/questionTypes/result"; +import IconPlus from "@icons/IconPlus"; +import Info from "@icons/Info"; +import Plus from "@icons/Plus"; +import ArrowLeft from "@icons/questionsPage/arrowLeft"; + export const ResultSettings = () => { const { questions } = useQuestionsStore(); const quiz = useCurrentQuiz(); - const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result"); + const results = useQuestionsStore().questions.filter( + (q): q is QuizQuestionResult => q.type === "result" + ); const [quizExpand, setQuizExpand] = useState(true); const [resultContract, setResultContract] = useState(true); + const [triggerExit, setTriggerExit] = useState<{ + follow: boolean; + path: string; + }>({ follow: false, path: "" }); + const [openNotificationModal, setOpenNotificationModal] = + useState(true); const isReadyToLeaveRef = useRef(true); - -console.log('quiz ', quiz) + // const blocker = useBlocker(false); useEffect( function calcIsReadyToLeave() { @@ -42,11 +58,29 @@ console.log('quiz ', quiz) useEffect(() => { return () => { - if (isReadyToLeaveRef.current === false) alert("Пожалуйста, проверьте, что вы заполнили все результаты"); + if (!isReadyToLeaveRef.current && window.location.pathname !== "/edit") { + setOpenNotificationModal(true); + } }; }, []); - const cnsl = results.filter(q=> q.content.usage) + const cnsl = results.filter((q) => q.content.usage); + + const shouldBlock = true; // Replace this + + // useEffect(() => { + // if (shouldBlock) { + // blocker.proceed?.() + // } + // }, [shouldBlock]); + + const leavePage = (leave: boolean) => { + if (leave) { + console.log("ливаем"); + } + + setOpenNotificationModal(false); + }; return ( @@ -81,9 +115,13 @@ console.log('quiz ', quiz) - {quiz.config.resultInfo.when === "email" && } + {quiz.config.resultInfo.when === "email" && ( + + )} - + Создайте результат @@ -109,7 +147,11 @@ console.log('quiz ', quiz) {cnsl.map((resultQuestion) => ( - + ))} { - let check = true +export const checkEmptyData = ({ + resultData, +}: { + resultData: QuizQuestionResult; +}) => { + let check = true; if ( resultData.title.length > 0 || resultData.description.length > 0 || @@ -47,15 +48,18 @@ export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult resultData.content.innerName.length > 0 || resultData.content.text.length > 0 || resultData.content.video.length > 0 || - resultData.content.hint.text.length > 0 - ) check = false - return check -} + resultData.content.hint.text.length > 0 + ) + check = false; + return check; +}; const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => { - const checkEmpty = checkEmptyData({ resultData }) - const question = getQuestionByContentId(resultData.content.rule.parentId) - const [anchorEl, setAnchorEl] = React.useState(null); + const checkEmpty = checkEmptyData({ resultData }); + const question = getQuestionByContentId(resultData.content.rule.parentId); + const [anchorEl, setAnchorEl] = React.useState( + null + ); const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -66,20 +70,18 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => { }; const open = Boolean(anchorEl); - const id = open ? 'simple-popover' : undefined; + const id = open ? "simple-popover" : undefined; return ( <> { anchorEl={anchorEl} onClose={handleClose} anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', + vertical: "bottom", + horizontal: "left", }} > - {resultData?.content.rule.parentId === "line" ? "Единый результат в конце прохождения опросника без ветвления" - : - `Заголовок вопроса, после которого появится результат: "${question?.title || "нет заголовка"}"` - } - + {resultData?.content.rule.parentId === "line" + ? "Единый результат в конце прохождения опросника без ветвления" + : `Заголовок вопроса, после которого появится результат: "${ + question?.title || "нет заголовка" + }"`} - {checkEmpty && + {checkEmpty && ( Вы не заполнили этот результат никакими данными - } - + )} - ) -} + ); +}; export const ResultCard = ({ resultContract, resultData }: Props) => { - - const quizQid = useCurrentQuiz()?.qid; const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isTablet = useMediaQuery(theme.breakpoints.down(800)); - const [expand, setExpand] = React.useState(true) - const [resultCardSettings, setResultCardSettings] = React.useState(false) - const [buttonPlus, setButtonPlus] = React.useState(true) + const [expand, setExpand] = React.useState(true); + const [resultCardSettings, setResultCardSettings] = React.useState(false); + const [buttonPlus, setButtonPlus] = React.useState(true); + const question = getQuestionByContentId(resultData.content.rule.parentId); React.useEffect(() => { - setExpand(true) - }, [resultContract]) - - - const { - isCropModalOpen, - openCropModal, - closeCropModal, - imageBlob, - originalImageUrl, - setCropModalImageBlob, - } = useCropModalState(); - const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); - - - - async function handleImageUpload(file: File) { - const url = await uploadQuestionImage(resultData.id, quizQid, file, (question, url) => { - - question.content.back = url; - question.content.originalBack = url; - }); - closeImageUploadModal(); - openCropModal(file, url); - } - - function handleCropModalSaveClick(imageBlob: Blob) { - uploadQuestionImage(resultData.id, quizQid, imageBlob, (question, url) => { - question.content.back = url; - }); - } - - - + setExpand(true); + }, [resultContract]); return ( { backgroundColor: expand ? "white" : "#EEE4FC", border: expand ? "none" : "1px solid #9A9AAF", boxShadow: "0px 10px 30px #e7e7e7", - m: "20px 0" + m: "20px 0", }} > + + {resultData?.content.rule.parentId === "line" + ? "Единый результат в конце прохождения опросника без ветвления" + : `Заголовок вопроса, после которого появится результат: "${ + question?.title || "нет заголовка" + }"`} + { updateQuestion(resultData.id, question => question.title = target.value)} + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion( + resultData.id, + (question) => (question.title = target.value) + ) + } sx={{ margin: isMobile ? "10px 0" : 0, "& .MuiInputBase-root": { @@ -277,7 +256,7 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { boxShadow: "0px 10px 30px #e7e7e7", }} > - + { updateQuestion(resultData.id, question => question.title = target.value)} /> + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion( + resultData.id, + (question) => (question.title = target.value) + ) + } + /> { - - updateQuestion(resultData.id, (question) => question.description = target.value)} + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion( + resultData.id, + (question) => (question.description = target.value) + ) + } placeholder={"Заголовок пожирнее"} sx={{ borderRadius: "8px", @@ -323,9 +311,13 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { updateQuestion(resultData.id, (question) => question.content.text = target.value)} + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion( + resultData.id, + (question) => (question.content.text = target.value) + ) + } fullWidth placeholder="Описание" multiline @@ -349,198 +341,87 @@ export const ResultCard = ({ resultContract, resultData }: Props) => { }} /> + - - - { + setButtonPlus(false); + }} sx={{ - display: "flex", + display: "inline flex", + height: "48px", + padding: "10px 20px", + justifyContent: "center", + alignItems: "center", + gap: "8px", + flexShrink: 0, + borderRadius: "8px", + border: "1px solid #9A9AAF", + background: " #F2F3F7", + color: "#9A9AAF", + mb: "30px", }} > - - - - + Кнопка + + + ) : ( - - + + Призыв к действию + + { + setButtonPlus(true); + updateQuestion( + resultData.id, + (q) => (q.content.hint.text = "") + ); + }} + > + + + + + + updateQuestion( + resultData.id, + (question) => + (question.content.hint.text = target.value) + ) + } + fullWidth + placeholder="Например: узнать подробнее" + sx={{ + "& .MuiInputBase-root": { + backgroundColor: "#F2F3F7", + width: "409px", + height: "48px", + borderRadius: "8px", + }, + }} + inputProps={{ + sx: { + height: "85px", + borderRadius: "10px", + fontSize: "18px", + lineHeight: "21px", + py: 0, + }, + }} /> - - { - resultData.content.useImage && - - { - if (resultData.content.back) { - return openCropModal( - resultData.content.back, - resultData.content.originalBack - ); - } - - openImageUploadModal(); - }} - onPlusClick={() => { - openImageUploadModal(); - }} - /> - - } - { - !resultData.content.useImage && - - updateQuestion(resultData.id, q => { - q.content.video = e.target.value; - })} - /> - - } - - - - - { - buttonPlus ? - - : - - - - Призыв к действию - - { - setButtonPlus(true) - updateQuestion(resultData.id, (q) => q.content.hint.text = "") - }} - > - - - - - updateQuestion(resultData.id, (question) => question.content.hint.text = target.value)} - fullWidth - placeholder="Например: узнать подробнее" - sx={{ - "& .MuiInputBase-root": { - backgroundColor: "#F2F3F7", - width: "409px", - height: "48px", - borderRadius: "8px", - }, - }} - inputProps={{ - sx: { - height: "85px", - borderRadius: "10px", - fontSize: "18px", - lineHeight: "21px", - py: 0, - }, - }} - /> - - } - - - - - - - + )} { > { - setResultCardSettings(!resultCardSettings) + setResultCardSettings(!resultCardSettings); }} sx={{ - backgroundColor: - resultCardSettings - ? theme.palette.brightPurple.main - : "transparent", - color: - resultCardSettings ? "#ffffff" : theme.palette.grey3.main, + backgroundColor: resultCardSettings + ? theme.palette.brightPurple.main + : "transparent", + color: resultCardSettings + ? "#ffffff" + : theme.palette.grey3.main, "&:hover": { - backgroundColor: resultCardSettings ? "#581CA7" : "#7E2AEA", - color: "white" - } + backgroundColor: resultCardSettings + ? "#581CA7" + : "#7E2AEA", + color: "white", + }, }} > { - { - resultCardSettings && + {resultCardSettings && ( updateQuestion(resultData.id, (question) => question.content.innerName = target.value)} + onChange={({ target }: { target: HTMLInputElement }) => + updateQuestion( + resultData.id, + (question) => (question.content.innerName = target.value) + ) + } /> - } + )} - ) - } - - ) -} + )} + + ); +}; diff --git a/src/pages/ViewPublicationPage/ContactForm.tsx b/src/pages/ViewPublicationPage/ContactForm.tsx index 6689e82d..4066d9ee 100644 --- a/src/pages/ViewPublicationPage/ContactForm.tsx +++ b/src/pages/ViewPublicationPage/ContactForm.tsx @@ -1,4 +1,4 @@ -import { Box, Typography, Button, Paper, TextField, Link, InputAdornment } from "@mui/material"; +import {Box, Typography, Button, Paper, TextField, Link, InputAdornment, useTheme} from "@mui/material"; import NameIcon from "@icons/ContactFormIcon/NameIcon"; import EmailIcon from "@icons/ContactFormIcon/EmailIcon"; import PhoneIcon from "@icons/ContactFormIcon/PhoneIcon"; @@ -13,6 +13,7 @@ import { useQuestionsStore } from "@root/questions/store"; import { checkEmptyData } from "../ResultPage/cards/ResultCard"; import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared"; +import {modes} from "../../utils/themes/Publication/themePublication"; import { enqueueSnackbar } from "notistack"; type ContactFormProps = { @@ -37,8 +38,9 @@ export const ContactForm = ({ setShowResultForm, }: ContactFormProps) => { const quiz = useCurrentQuiz(); + const mode = modes; const { questions } = useQuestionsStore(); - + const theme = useTheme(); const [ready, setReady] = useState(false) const followNextForm = () => { setShowContactForm(false); @@ -68,6 +70,7 @@ export const ContactForm = ({ display: "flex", alignItems: "center", justifyContent: "center", + backgroundColor: theme.palette.background.default, height: "100vh" }} > @@ -82,7 +85,8 @@ export const ContactForm = ({ sx={{ textAlign: "center", m: "20px 0", - fontSize: "28px" + fontSize: "28px", + color: theme.palette.text.primary }} > {quiz?.config.formContact.title || "Заполните форму, чтобы получить результаты теста"} @@ -109,6 +113,7 @@ export const ContactForm = ({ alignItems: "center", justifyContent: "center", flexDirection: "column", + backgroundColor: theme.palette.background.default, p: "30px" }}> @@ -146,13 +151,13 @@ export const ContactForm = ({ width: "450px", }} > - { setReady(target.checked) }} checked={ready} /> + { setReady(target.checked) }} checked={ready} colorIcon={theme.palette.primary.main}/> - С - Положением об обработке персональных данных - и - Политикой конфиденциальности - ознакомлен + С  + Положением об обработке персональных данных +  и  + Политикой конфиденциальности +  ознакомлен @@ -160,11 +165,14 @@ export const ContactForm = ({ sx={{ display: "flex", alignItems: "center", - mt: "20px" + mt: "20px", + gap: "15px" }} > - - Сделано на PenaQuiz + + + Сделано на PenaQuiz + diff --git a/src/pages/ViewPublicationPage/Footer.tsx b/src/pages/ViewPublicationPage/Footer.tsx index 3ea19249..d4d368ea 100644 --- a/src/pages/ViewPublicationPage/Footer.tsx +++ b/src/pages/ViewPublicationPage/Footer.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { Box, Button, Typography, useTheme } from "@mui/material"; +import {Box, Button, Typography, useMediaQuery, useTheme} from "@mui/material"; import { useQuizViewStore } from "@root/quizView"; import { useCurrentQuiz } from "@root/quizes/hooks"; @@ -9,6 +9,8 @@ import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questio import { getQuestionByContentId } from "@root/questions/actions"; import { enqueueSnackbar } from "notistack"; import { NameplateLogoFQ } from "@icons/NameplateLogoFQ"; +import {NameplateLogoFQDark} from "@icons/NameplateLogoFQDark"; +import {modes} from "../../utils/themes/Publication/themePublication"; import { checkEmptyData } from "../ResultPage/cards/ResultCard"; type FooterProps = { @@ -23,9 +25,11 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh const [disableNextButton, setDisableNextButton] = useState(false); const [stepNumber, setStepNumber] = useState(1); const quiz = useCurrentQuiz(); + const mode = modes; const { answers } = useQuizViewStore(); const questions = useQuestionsStore().questions as AnyTypedQuizQuestion[]; const theme = useTheme(); + const isMobileMini = useMediaQuery(theme.breakpoints.down(382)); const linear = !questions.find(({ content }) => content.rule.parentId === "root"); useEffect(() => { @@ -113,10 +117,14 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh }; const getNextQuestionId = () => { + console.log("net") + console.log(question) + let readyBeNextQuestion = ""; + + //вопрос обязателен, анализируем ответ и условия ветвления if (answers.length) { const answer = answers.find(({ questionId }) => questionId === question.content.id); - let readyBeNextQuestion = ""; (question as QuizQuestionBase).content.rule.main.forEach(({ next, rules }) => { let longerArray = Math.max( @@ -143,8 +151,32 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh } }); - return readyBeNextQuestion; + if (readyBeNextQuestion) return readyBeNextQuestion; } + + if (!question.required) {//вопрос не обязателен и не нашли совпадений между ответами и условиями ветвления + console.log("вопрос не обязателен ищем дальше") + const defaultQ = question.content.rule.default + if (defaultQ) return defaultQ + //Вопросы типа страница, ползунок, своё поле для ввода и дата не могут иметь больше 1 ребёнка. Пользователь не может настроить там дефолт + //Кинуть на ребёнка надо даже если там нет дефолта + if ( + (question?.type === "date" || + question?.type === "text" || + question?.type === "number" || + question?.type === "page") && question.content.rule.children.length === 1 + ) return question.content.rule.children[0] + + + } + //ничё не нашли, ищем резулт + console.log("ничё не нашли, ищем резулт ") + return questions.find(q => { + console.log('q.type === "result"', q.type === "result") + console.log('q.content.rule.parentId === question.content.id', q.content.rule.parentId === question.content.id) + return q.type === "result" && q.content.rule.parentId === question.content.id + })?.content.id + }; const followPreviousStep = () => { @@ -192,22 +224,18 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh const nextQuestionId = getNextQuestionId(); + console.log(nextQuestionId) if (nextQuestionId) { const nextQuestion = getQuestionByContentId(nextQuestionId); + console.log(nextQuestion) - if (nextQuestion?.type && nextQuestion.type !== "result") { - setCurrentQuestion(nextQuestion); - return; + if (nextQuestion?.type && nextQuestion.type === "result") { + showResult(nextQuestion); } else { - enqueueSnackbar("не могу получить последующий вопрос"); + setCurrentQuestion(nextQuestion); } } else { - const nextQuestion = getQuestionByContentId(question.content.rule.default); - if (nextQuestion?.type && nextQuestion.type !== "result") { - setCurrentQuestion(nextQuestion); - } else { - showResult(nextQuestion); - } + enqueueSnackbar("не могу получить последующий вопрос"); } }; @@ -233,7 +261,11 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh gap: "10px", }} > - + {/*{mode[quiz.config.theme] ? (*/} + {/* */} + {/*):(*/} + {/* */} + {/*)}*/} {linear && <> Шаг @@ -256,7 +288,7 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh width: "30px", height: "30px", color: "#FFF", - background: theme.palette.brightPurple.main, + background: theme.palette.primary.main, }} > {stepNumber} @@ -268,13 +300,50 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh } + + + {/* Шаг + + {stepNumber} */} + {/* */} + {/* Из + + {questions.length} + */} + + + + + + + + + + + + + + + ); }; diff --git a/src/pages/ViewPublicationPage/questions/Date.tsx b/src/pages/ViewPublicationPage/questions/Date.tsx index 0f33e19f..5408148c 100644 --- a/src/pages/ViewPublicationPage/questions/Date.tsx +++ b/src/pages/ViewPublicationPage/questions/Date.tsx @@ -1,17 +1,22 @@ import dayjs from "dayjs"; import { DatePicker } from "@mui/x-date-pickers"; -import { Box, Typography } from "@mui/material"; +import {Box, Typography, useTheme} from "@mui/material"; import { useQuizViewStore, updateAnswer } from "@root/quizView"; import type { QuizQuestionDate } from "../../../model/questionTypes/date"; import CalendarIcon from "@icons/CalendarIcon"; +import {modes} from "../../../utils/themes/Publication/themePublication"; +import {useCurrentQuiz} from "@root/quizes/hooks"; type DateProps = { currentQuestion: QuizQuestionDate; }; export const Date = ({ currentQuestion }: DateProps) => { + const theme = useTheme(); + const mode = modes; + const quiz = useCurrentQuiz(); const { answers } = useQuizViewStore(); const answer = answers.find( ({ questionId }) => questionId === currentQuestion.content.id @@ -20,7 +25,7 @@ export const Date = ({ currentQuestion }: DateProps) => { return ( - {currentQuestion.title} + {currentQuestion.title} { > , + openPickerIcon: () => , }} value={dayjs( answer @@ -61,10 +69,14 @@ export const Date = ({ currentQuestion }: DateProps) => { }, "data-cy": "open-datepicker", }, + layout: { + sx: {backgroundColor: theme.palette.background.default,} + } }} sx={{ + "& .MuiInputBase-root": { - backgroundColor: "#F2F3F7", + backgroundColor: mode[quiz.config.theme] ? "white" : theme.palette.background.default, borderRadius: "10px", maxWidth: "250px", pr: "22px", @@ -77,6 +89,7 @@ export const Date = ({ currentQuestion }: DateProps) => { borderColor: "#9A9AAF", }, }, + }} /> diff --git a/src/pages/ViewPublicationPage/questions/Emoji.tsx b/src/pages/ViewPublicationPage/questions/Emoji.tsx index 42b69f6e..5fad1a1f 100644 --- a/src/pages/ViewPublicationPage/questions/Emoji.tsx +++ b/src/pages/ViewPublicationPage/questions/Emoji.tsx @@ -5,7 +5,7 @@ import { FormControlLabel, Radio, useTheme, - FormControl, + FormControl, useMediaQuery, } from "@mui/material"; import { useQuizViewStore, updateAnswer, deleteAnswer } from "@root/quizView"; @@ -22,6 +22,7 @@ type EmojiProps = { export const Emoji = ({ currentQuestion }: EmojiProps) => { const { answers } = useQuizViewStore(); const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(650)); const { answer } = answers.find( ({ questionId }) => questionId === currentQuestion.content.id @@ -29,7 +30,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => { return ( - {currentQuestion.title} + {currentQuestion.title} { marginTop: "20px", }} > - + {currentQuestion.content.variants.map((variant, index) => ( { sx={{ margin: 0, padding: "15px", - color: "#4D4D4D", + color: theme.palette.text.primary, display: "flex", gap: "10px", }} @@ -107,7 +109,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => { } }} control={ - } icon={} /> + } icon={} /> } label={ diff --git a/src/pages/ViewPublicationPage/questions/File.tsx b/src/pages/ViewPublicationPage/questions/File.tsx index e107e9ac..2dcff3b0 100644 --- a/src/pages/ViewPublicationPage/questions/File.tsx +++ b/src/pages/ViewPublicationPage/questions/File.tsx @@ -56,7 +56,7 @@ export const File = ({ currentQuestion }: FileProps) => { return ( - {currentQuestion.title} + {currentQuestion.title} { > {answer?.split("|")[0] && ( - Вы загрузили: + Вы загрузили: { alignItems: "center", padding: "33px 44px 33px 55px", backgroundColor: theme.palette.background.default, - border: `1px solid ${theme.palette.grey2.main}`, + border: `1px solid #9A9AAF`, + // border: `1px solid ${theme.palette.grey2.main}`, borderRadius: "8px", }} > @@ -123,7 +124,8 @@ export const File = ({ currentQuestion }: FileProps) => { @@ -134,7 +136,8 @@ export const File = ({ currentQuestion }: FileProps) => { { return ( - {currentQuestion.title} + {currentQuestion.title} { sx={{ cursor: "pointer", borderRadius: "5px", - border: `1px solid ${theme.palette.grey2.main}`, + border: `1px solid`, + borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF", }} onClick={(event) => { event.preventDefault(); @@ -98,12 +99,12 @@ export const Images = ({ currentQuestion }: ImagesProps) => { sx={{ display: "block", textAlign: "center", - color: theme.palette.grey2.main, + color: theme.palette.text.primary, marginTop: "10px", }} value={index} control={ - } icon={} /> + } icon={} /> } label={variant.answer} /> diff --git a/src/pages/ViewPublicationPage/questions/Number.tsx b/src/pages/ViewPublicationPage/questions/Number.tsx index 6056beab..b0b462e3 100644 --- a/src/pages/ViewPublicationPage/questions/Number.tsx +++ b/src/pages/ViewPublicationPage/questions/Number.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { Box, Typography, Slider, useTheme } from "@mui/material"; +import {Box, Typography, Slider, useTheme, useMediaQuery} from "@mui/material"; import { useDebouncedCallback } from "use-debounce"; import CustomTextField from "@ui_kit/CustomTextField"; @@ -8,6 +8,8 @@ import { CustomSlider } from "@ui_kit/CustomSlider"; import { useQuizViewStore, updateAnswer } from "@root/quizView"; import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; +import {modes} from "../../../utils/themes/Publication/themePublication"; +import {useCurrentQuiz} from "@root/quizes/hooks"; type NumberProps = { currentQuestion: QuizQuestionNumber; @@ -17,6 +19,9 @@ export const Number = ({ currentQuestion }: NumberProps) => { const [minRange, setMinRange] = useState("0"); const [maxRange, setMaxRange] = useState("100000000000"); const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(650)); + const mode = modes; + const quiz = useCurrentQuiz(); const { answers } = useQuizViewStore(); const updateMinRangeDebounced = useDebouncedCallback( (value, crowded = false) => { @@ -26,7 +31,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { updateAnswer(currentQuestion.content.id, value); }, - 1000 + 3000 ); const updateMaxRangeDebounced = useDebouncedCallback( (value, crowded = false) => { @@ -36,7 +41,7 @@ export const Number = ({ currentQuestion }: NumberProps) => { updateAnswer(currentQuestion.content.id, value); }, - 1000 + 3000 ); const answer = answers.find( ({ questionId }) => questionId === currentQuestion.content.id @@ -53,14 +58,14 @@ export const Number = ({ currentQuestion }: NumberProps) => { } if (!answer) { - setMinRange(String(currentQuestion.content.start)); + setMinRange(sliderValue.split("—")[0]); setMaxRange(String(max)); } }, []); return ( - {currentQuestion.title} + {currentQuestion.title} { width: "100%", marginTop: "20px", gap: "30px", + paddingRight: isMobile ? "10px" : undefined }} > { setMaxRange(String(range[1])); } }} + sx={{ + color: theme.palette.primary.main, + "& .MuiSlider-valueLabel": { + background: theme.palette.primary.main,} + }} /> {!currentQuestion.content.chooseRange && ( @@ -111,7 +122,11 @@ export const Number = ({ currentQuestion }: NumberProps) => { }} sx={{ maxWidth: "80px", - "& .MuiInputBase-input": { textAlign: "center" }, + borderColor: theme.palette.text.primary, + "& .MuiInputBase-input": { + textAlign: "center", + backgroundColor: mode[quiz.config.theme] ? "white" : theme.palette.background.default, + }, }} /> )} @@ -140,11 +155,15 @@ export const Number = ({ currentQuestion }: NumberProps) => { updateMinRangeDebounced(`${target.value}—${maxRange}`); }} sx={{ - maxWidth: "80px", - "& .MuiInputBase-input": { textAlign: "center" }, + maxWidth: "80px", + borderColor: theme.palette.text.primary, + "& .MuiInputBase-input": { + textAlign: "center", + backgroundColor: mode[quiz.config.theme] ? "white" : theme.palette.background.default, + }, }} /> - до + до { updateMaxRangeDebounced(`${minRange}—${target.value}`); }} sx={{ - maxWidth: "80px", - "& .MuiInputBase-input": { textAlign: "center" }, + maxWidth: "80px", + borderColor: theme.palette.text.primary, + "& .MuiInputBase-input": { + textAlign: "center", + backgroundColor: mode[quiz.config.theme] ? "white" : theme.palette.background.default, + }, }} /> diff --git a/src/pages/ViewPublicationPage/questions/Page.tsx b/src/pages/ViewPublicationPage/questions/Page.tsx index 619aea4b..5ebdc846 100644 --- a/src/pages/ViewPublicationPage/questions/Page.tsx +++ b/src/pages/ViewPublicationPage/questions/Page.tsx @@ -1,8 +1,9 @@ -import { Box, Typography } from "@mui/material"; +import {Box, Typography, useTheme} from "@mui/material"; import { useQuizViewStore, updateAnswer } from "@root/quizView"; import type { QuizQuestionPage } from "../../../model/questionTypes/page"; +import YoutubeEmbedIframe from "@ui_kit/StartPagePreview/YoutubeEmbedIframe"; type PageProps = { currentQuestion: QuizQuestionPage; @@ -11,11 +12,12 @@ type PageProps = { export const Page = ({ currentQuestion }: PageProps) => { const { answers } = useQuizViewStore(); const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; + const theme = useTheme(); return ( - {currentQuestion.title} - {currentQuestion.content.text} + {currentQuestion.title} + {currentQuestion.content.text} { marginTop: "20px", }} > - {currentQuestion.content.picture && ( - - - - - )} - {currentQuestion.content.video && ( - + ) : ( + )} diff --git a/src/pages/ViewPublicationPage/questions/Rating.tsx b/src/pages/ViewPublicationPage/questions/Rating.tsx index 14860935..03c3758d 100644 --- a/src/pages/ViewPublicationPage/questions/Rating.tsx +++ b/src/pages/ViewPublicationPage/questions/Rating.tsx @@ -2,7 +2,7 @@ import { Box, Typography, Rating as RatingComponent, - useTheme, + useTheme, useMediaQuery, } from "@mui/material"; import { useQuizViewStore, updateAnswer } from "@root/quizView"; @@ -55,6 +55,7 @@ const buttonRatingForm = [ export const Rating = ({ currentQuestion }: RatingProps) => { const { answers } = useQuizViewStore(); const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(650)); const { answer } = answers.find( ({ questionId }) => questionId === currentQuestion.content.id @@ -65,16 +66,20 @@ export const Rating = ({ currentQuestion }: RatingProps) => { return ( - {currentQuestion.title} + {currentQuestion.title} - + {currentQuestion.content.ratingNegativeDescription} { onChange={(_, value) => updateAnswer(currentQuestion.content.id, String(value)) } - sx={{ height: "50px", gap: "15px" }} + sx={{ height: "50px", + gap: isMobile ? undefined : "15px", + justifyContent: isMobile ? "space-between" : undefined, + width: isMobile ? "100%" : undefined + }} max={currentQuestion.content.steps} - icon={form?.icon(theme.palette.brightPurple.main)} - emptyIcon={form?.icon(theme.palette.grey2.main)} + icon={form?.icon(theme.palette.primary.main)} + emptyIcon={form?.icon("#9A9AAF")} /> - + {currentQuestion.content.ratingPositiveDescription} diff --git a/src/pages/ViewPublicationPage/questions/Select.tsx b/src/pages/ViewPublicationPage/questions/Select.tsx index 376d8242..f23e906b 100644 --- a/src/pages/ViewPublicationPage/questions/Select.tsx +++ b/src/pages/ViewPublicationPage/questions/Select.tsx @@ -1,4 +1,4 @@ -import { Box, Typography } from "@mui/material"; +import {Box, Typography, useTheme} from "@mui/material"; import { Select as SelectComponent } from "../../../pages/Questions/Select"; @@ -12,6 +12,7 @@ type SelectProps = { export const Select = ({ currentQuestion }: SelectProps) => { const { answers } = useQuizViewStore(); + const theme = useTheme(); const { answer } = answers.find( ({ questionId }) => questionId === currentQuestion.content.id @@ -19,7 +20,7 @@ export const Select = ({ currentQuestion }: SelectProps) => { return ( - {currentQuestion.title} + {currentQuestion.title} { placeholder={currentQuestion.content.default} activeItemIndex={answer ? Number(answer) : -1} items={currentQuestion.content.variants.map(({ answer }) => answer)} + colorMain={theme.palette.primary.main} + color={theme.palette.primary.main} onChange={(_, value) => { if (value < 0) { deleteAnswer(currentQuestion.content.id); diff --git a/src/pages/ViewPublicationPage/questions/Text.tsx b/src/pages/ViewPublicationPage/questions/Text.tsx index bb0d90c8..63caa02a 100644 --- a/src/pages/ViewPublicationPage/questions/Text.tsx +++ b/src/pages/ViewPublicationPage/questions/Text.tsx @@ -1,4 +1,4 @@ -import { Box, Typography } from "@mui/material"; +import {Box, Typography, useTheme} from "@mui/material"; import CustomTextField from "@ui_kit/CustomTextField"; @@ -13,10 +13,10 @@ type TextProps = { export const Text = ({ currentQuestion }: TextProps) => { const { answers } = useQuizViewStore(); const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; - + const theme = useTheme(); return ( - {currentQuestion.title} + {currentQuestion.title} { placeholder={currentQuestion.content.placeholder} value={answer || ""} onChange={({ target }) => updateAnswer(currentQuestion.content.id, target.value)} + sx={{ + "&:focus-visible": { + borderColor: theme.palette.primary.main + } + }} /> diff --git a/src/pages/ViewPublicationPage/questions/Variant.tsx b/src/pages/ViewPublicationPage/questions/Variant.tsx index 1b5276e4..5925ef65 100644 --- a/src/pages/ViewPublicationPage/questions/Variant.tsx +++ b/src/pages/ViewPublicationPage/questions/Variant.tsx @@ -21,10 +21,12 @@ import { import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; -import { CheckboxIcon } from "@icons/Checkbox"; +import CheckboxIcon from "@icons/Checkbox"; +import {modes} from "../../../utils/themes/Publication/themePublication"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; import type { QuestionVariant } from "../../../model/questionTypes/shared"; +import {useCurrentQuiz} from "@root/quizes/hooks"; type VariantProps = { stepNumber: number; @@ -40,6 +42,7 @@ type VariantItemProps = { }; export const Variant = ({ currentQuestion }: VariantProps) => { + const theme = useTheme() const { answers, ownVariants } = useQuizViewStore(); const { answer } = answers.find( @@ -59,7 +62,7 @@ export const Variant = ({ currentQuestion }: VariantProps) => { return ( - {currentQuestion.title} + {currentQuestion.title} { const theme = useTheme(); - + const mode = modes + const quiz = useCurrentQuiz(); return ( } + checkedIcon={} icon={} /> ) : ( - } icon={} /> + } icon={} /> ) } label={own ? : variant.answer} diff --git a/src/pages/ViewPublicationPage/questions/Varimg.tsx b/src/pages/ViewPublicationPage/questions/Varimg.tsx index 5e07766b..56ee2c29 100644 --- a/src/pages/ViewPublicationPage/questions/Varimg.tsx +++ b/src/pages/ViewPublicationPage/questions/Varimg.tsx @@ -1,10 +1,10 @@ import { - Box, - Typography, - RadioGroup, - FormControlLabel, - Radio, - useTheme, + Box, + Typography, + RadioGroup, + FormControlLabel, + Radio, + useTheme, useMediaQuery, } from "@mui/material"; import gag from "./gag.png" @@ -15,6 +15,8 @@ import RadioCheck from "@ui_kit/RadioCheck"; import RadioIcon from "@ui_kit/RadioIcon"; import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg"; +import {modes} from "../../../utils/themes/Publication/themePublication"; +import {useCurrentQuiz} from "@root/quizes/hooks"; type VarimgProps = { currentQuestion: QuizQuestionVarImg; @@ -22,7 +24,10 @@ type VarimgProps = { export const Varimg = ({ currentQuestion }: VarimgProps) => { const { answers } = useQuizViewStore(); + const mode = modes; + const quiz = useCurrentQuiz(); const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(650)); const { answer } = answers.find( ({ questionId }) => questionId === currentQuestion.content.id @@ -33,8 +38,14 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { return ( - {currentQuestion.title} - + {currentQuestion.title} + { flexBasis: "100%", }} > - + {currentQuestion.content.variants.map((variant, index) => ( { marginBottom: "15px", borderRadius: "5px", padding: "15px", - color: "#4D4D4D", - border: `1px solid ${theme.palette.grey2.main}`, + color: theme.palette.text.primary, + backgroundColor: mode[quiz.config.theme] ? "white" : theme.palette.background.default, + border: `1px solid`, + borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF", display: "flex", + margin: isMobile ? 0 : undefined, }} value={index} onClick={(event) => { @@ -74,7 +88,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { } }} control={ - } icon={} /> + } icon={} /> } label={variant.answer} /> @@ -107,7 +121,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { alt="" /> : - (variant?.extendedText || "Выберите вариант ответа слева") + (variant?.extendedText || isMobile ? ("Выберите вариант ответа ниже") : ("Выберите вариант ответа слева")) } diff --git a/src/pages/startPage/ConfirmLeaveModal/index.tsx b/src/pages/startPage/ConfirmLeaveModal/index.tsx new file mode 100644 index 00000000..8953334e --- /dev/null +++ b/src/pages/startPage/ConfirmLeaveModal/index.tsx @@ -0,0 +1,72 @@ +import { Box, Button, Modal, Typography } from "@mui/material"; + +import { useUiTools } from "@root/uiTools/store"; + +type ConfirmLeaveModalProps = { + open: boolean; + follow: () => void; + cancel: () => void; +}; + +export const ConfirmLeaveModal = ({ + open, + follow, + cancel, +}: ConfirmLeaveModalProps) => ( + + + + + Пожалуйста, проверьте, что вы заполнили все результаты + + + + + + + + +); diff --git a/src/pages/startPage/EditPage.tsx b/src/pages/startPage/EditPage.tsx index 0cc5d781..397c6f17 100755 --- a/src/pages/startPage/EditPage.tsx +++ b/src/pages/startPage/EditPage.tsx @@ -4,6 +4,8 @@ import BackArrowIcon from "@icons/BackArrowIcon"; import { Burger } from "@icons/Burger"; import EyeIcon from "@icons/EyeIcon"; import { PenaLogoIcon } from "@icons/PenaLogoIcon"; +import VisibilityIcon from "@mui/icons-material/Visibility"; + import { Box, Button, @@ -16,7 +18,7 @@ import { useMediaQuery, useTheme, } from "@mui/material"; -import { decrementCurrentStep, resetEditConfig, setQuizes, updateQuiz } from "@root/quizes/actions"; +import { decrementCurrentStep, resetEditConfig, setQuizes, updateQuiz, setCurrentStep } from "@root/quizes/actions"; import { useCurrentQuiz } from "@root/quizes/hooks"; import { useQuizStore } from "@root/quizes/store"; import CustomAvatar from "@ui_kit/Header/Avatar"; @@ -33,7 +35,13 @@ import useSWR from "swr"; import { useDebouncedCallback } from "use-debounce"; import { SidebarMobile } from "./Sidebar/SidebarMobile"; import { cleanQuestions, createResult, setQuestions } from "@root/questions/actions"; -import { updateOpenBranchingPanel, updateCanCreatePublic, updateModalInfoWhyCantCreate } from "@root/uiTools/actions"; +import { + updateOpenBranchingPanel, + updateCanCreatePublic, + updateModalInfoWhyCantCreate, + setShowConfirmLeaveModal, + updateSomeWorkBackend, +} from "@root/uiTools/actions"; import { BranchingPanel } from "../Questions/BranchingPanel"; import { useQuestionsStore } from "@root/questions/store"; import { useQuizes } from "@root/quizes/hooks"; @@ -46,94 +54,98 @@ import { clearAuthToken } from "@frontend/kitui"; import { logout } from "@api/auth"; import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { ModalInfoWhyCantCreate } from "./ModalInfoWhyCantCreate"; -import { type } from "os"; +import { ConfirmLeaveModal } from "./ConfirmLeaveModal"; +import { checkQuestionHint } from "@utils/checkQuestionHint"; +import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions"; +import { toggleQuizPreview } from "@root/quizPreview"; +import { LinkSimple } from "@icons/LinkSimple"; +import { BackButtonIcon } from "@icons/BackButtonIcon"; +let init: () => void; export default function EditPage() { const quiz = useCurrentQuiz(); const { editQuizId } = useQuizStore(); const { questions } = useQuestionsStore(); + console.log("quiz ", quiz); + console.log(questions); useEffect(() => { const getData = async () => { const quizes = await quizApi.getList(); setQuizes(quizes); - const questions = await questionApi.getList({ quiz_id: editQuizId }); - setQuestions(questions); + if (editQuizId) { + const questions = await questionApi.getList({ quiz_id: editQuizId }); + setQuestions(questions); + //Всегда должен существовать хоть 1 резулт - "line" + // console.log("сейчас будем ворошиться в этих квешенах ", questions); - //Всегда должен существовать хоть 1 резулт - "line" - console.log(questions) - - if (!questions?.find(q=>q.type === "result" && q.content.includes(':"line"') || q.content.includes(":'line'"))) createResult(quiz?.backendId, "line") - + if ( + !questions?.find( + (q) => (q.type === "result" && q.content.includes(':"line"')) || q.content.includes(":'line'") + ) + ) { + createResult(quiz?.backendId, "line"); + console.log("Я не нашёл линейный резулт и собираюсь создать новый"); + } + } }; getData(); }, []); - const { openBranchingPanel, whyCantCreatePublic, canCreatePublic } = useUiTools(); + const { openBranchingPanel, whyCantCreatePublic, canCreatePublic, showConfirmLeaveModal } = useUiTools(); const theme = useTheme(); const navigate = useNavigate(); const currentStep = useQuizStore((state) => state.currentStep); const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isMobile = useMediaQuery(theme.breakpoints.down(660)); + const isMobileSm = useMediaQuery(theme.breakpoints.down(370)); const [mobileSidebar, setMobileSidebar] = useState(false); + const [nextStep, setNextStep] = useState(0); const quizConfig = quiz?.config; - const disableTest = quiz === undefined ? true : (quiz.config.type === null) + const disableTest = quiz === undefined ? true : quiz.config.type === null; + const [openBranchingPage, setOpenBranchingPage] = useState(false); + const [buttonText, setButtonText] = useState("Опубликовать"); + + + const openBranchingPageHC = () => { + if (!openBranchingPage) { + deleteTimeoutedQuestions(questions, quiz); + } + setOpenBranchingPage((old) => !old); + }; useEffect(() => { if (editQuizId === null) navigate("/list"); }, [navigate, editQuizId]); useEffect( - () => { - return () => { - resetEditConfig(); - cleanQuestions(); - updateModalInfoWhyCantCreate(false) - } + () => () => { + resetEditConfig(); + cleanQuestions(); + updateModalInfoWhyCantCreate(false); + updateSomeWorkBackend(false) }, [] ); - - const updateQuestionHint = useDebouncedCallback((questions: AnyTypedQuizQuestion[]) => { - - const problems: any = {} - - questions.forEach((question) => { - //Если не участвует в ветвлении, или безтиповый, или резулт - он нам не интересен - if (question.type === null - || question.type === "result" - || question.content.rule.parentId.length === 0) return - - //если есть дети, но нет дефолта - логическая ошибка. Так нельзя - if (question.content.rule.children.length > 0 && question.content.rule.default.length === 0) { - problems[question.content.id] = { - name: question.title, - problems: ["Не выбран дефолтный вопрос"] - } - } - - }) - - useUiTools.setState({ whyCantCreatePublic: problems }) + const problems = checkQuestionHint(questions); + useUiTools.setState({ whyCantCreatePublic: problems }); if (Object.keys(problems).length > 0) { - updateQuiz(quiz?.id, (state) => { state.status = "stop" }) - updateCanCreatePublic(false) + updateQuiz(quiz?.id, (state) => { + state.status = "stop"; + }); + updateCanCreatePublic(false); } else { - updateCanCreatePublic(true) + updateCanCreatePublic(true); } - - }, 600); - useEffect(() => { - updateQuestionHint(questions) + updateQuestionHint(questions); }, [questions]); - async function handleLogoutClick() { const [, logoutError] = await logout(); @@ -145,9 +157,38 @@ export default function EditPage() { clearUserData(); navigate("/"); } - console.log(questions) - if (!quizConfig) return <> + const followNewPage = () => { + setShowConfirmLeaveModal(false); + setCurrentStep(nextStep); + }; + + if (!quizConfig) return <>; + + const isConditionMet = [1].includes(currentStep) && !openBranchingPanel && quizConfig.type !== "form"; + + + const handleClickStatusQuiz = () => { + if (Object.keys(whyCantCreatePublic).length === 0) { + if (buttonText === "Опубликовать") { + setButtonText("Опубликовано"); + setTimeout(() => { + setButtonText("Отозвать"); + }, 3000); + } else { + setButtonText("Опубликовать"); + } + + updateQuiz(quiz?.id, (state) => { + state.status = quiz?.status === "start" ? "stop" : "start"; + }); + } else { + updateModalInfoWhyCantCreate(true); + } + }; + + console.log(quiz?.status); + return ( <> {/*хедер*/} @@ -276,168 +317,240 @@ export default function EditPage() { display: isMobile ? "block" : "flex", }} > - {isMobile ? : } + {isMobile ? : } - {/* Выбор текущей страницы редактирования чего-либо находится здесь */} - {quizConfig && ( - <> - - - - )} - - - - {[1].includes(currentStep) && !openBranchingPanel && quizConfig.type !== "form" && ( - - updateOpenBranchingPanel(e.target.checked)} + + {/* Выбор текущей страницы редактирования чего-либо находится здесь */} + {quizConfig && ( + <> + + + + )} + + + {isConditionMet && ( + - - Логика ветвления + > + + + Логика ветвления + - - )} + )} + + + {!canCreatePublic && quiz.config.type !== "form" ? ( + + ) : ( + + + + )} + - {!canCreatePublic && quiz.config.type !== "form" ? - : - - - - } + ) : ( + + + + )} - - {quiz?.status === "start" && https://hbpn.link/{quiz.qid} - } + {isMobile && quiz?.status === "start" && ( + + + + )} + - + + setShowConfirmLeaveModal(false)} + /> ); } diff --git a/src/pages/startPage/ModalInfoWhyCantCreate.tsx b/src/pages/startPage/ModalInfoWhyCantCreate.tsx index 26e38506..51ea68a1 100644 --- a/src/pages/startPage/ModalInfoWhyCantCreate.tsx +++ b/src/pages/startPage/ModalInfoWhyCantCreate.tsx @@ -1,49 +1,44 @@ -import { Box, Modal, Typography, Divider } from "@mui/material" +import { Box, Modal, Typography, Divider } from "@mui/material"; import { useUiTools } from "@root/uiTools/store"; import { updateModalInfoWhyCantCreate } from "@root/uiTools/actions"; import { useLayoutEffect } from "react"; export const ModalInfoWhyCantCreate = () => { + const { whyCantCreatePublic, openModalInfoWhyCantCreate } = useUiTools(); - const { whyCantCreatePublic, openModalInfoWhyCantCreate } = useUiTools(); - - return ( - updateModalInfoWhyCantCreate(false)} - > - updateModalInfoWhyCantCreate(false)}> + - { - Object.values(whyCantCreatePublic).map((data) => { - return ( - - У вопроса "{data.name}" - { - data.problems.map((problem) => {problem}) - } - - - ) - }) - } + boxShadow: 24, + p: "25px", + minHeight: "60vh", + maxHeight: "90vh", + overflow: "auto", + }} + > + {Object.values(whyCantCreatePublic).map((data) => { + return ( + + У вопроса "{data.name}" + {data.problems.map((problem) => ( + {problem} + ))} + - - ) -} \ No newline at end of file + ); + })} + + + ); +}; diff --git a/src/pages/startPage/ModalSizeImage.tsx b/src/pages/startPage/ModalSizeImage.tsx index 3b95af67..f717dcec 100644 --- a/src/pages/startPage/ModalSizeImage.tsx +++ b/src/pages/startPage/ModalSizeImage.tsx @@ -42,7 +42,7 @@ export default function ModalSizeImage() { createData("Варианты и картинка", "380х307 px"), createData("Консультант", "140х140 px"), createData("Логотип", "107х37 px"), - createData("", "1100х600 px"), + createData("Результаты", "1100х600 px"), createData("Бонус", "200х60 px"), createData('Картинка для формата вопроса "Страница"', "860х1250 px"), ]; @@ -100,12 +100,8 @@ export default function ModalSizeImage() { - - Рекомендованный размер зависит от того, как вы будете чаще - использовать квиз: + + Рекомендованный размер зависит от того, как вы будете чаще использовать квиз: @@ -115,24 +111,30 @@ export default function ModalSizeImage() { sx={{ display: "flex", justifyContent: "space-between", + gap: "6px", + position: "relative", width: "100%", paddingBottom: "5px", }} > - + + {name} - - - {size} + + + {size} + ))} @@ -153,24 +155,30 @@ export default function ModalSizeImage() { sx={{ display: "flex", justifyContent: "space-between", + position: "relative", + gap: "6px", width: "100%", paddingBottom: "5px", }} > - + + {name} - - - {size} + + + {size} + ))} diff --git a/src/stores/questions/actions.ts b/src/stores/questions/actions.ts index 97e4b7fb..a5dcd65c 100644 --- a/src/stores/questions/actions.ts +++ b/src/stores/questions/actions.ts @@ -177,7 +177,7 @@ const REQUEST_DEBOUNCE = 200; const requestQueue = new RequestQueue(); let requestTimeoutId: ReturnType; -export const updateQuestion = ( +export const updateQuestion = async ( questionId: string, updateFn: (question: T) => void, skipQueue = false, @@ -481,20 +481,22 @@ export const getQuestionByContentId = (questionContentId: string | null) => { export const clearRuleForAll = () => { const { questions } = useQuestionsStore.getState(); - questions.forEach(question => { - if (question.type !== null && + return Promise.allSettled( + questions.map(question => { + if (question.type !== null && (question.content.rule.main.length > 0 - || question.content.rule.default.length > 0 - || question.content.rule.parentId.length > 0) - && question.type !== "result") { - - updateQuestion(question.content.id, question => { - question.content.rule.parentId = ""; - question.content.rule.main = []; - question.content.rule.default = ""; - }); - } - }); + || question.content.rule.default.length > 0 + || question.content.rule.parentId.length > 0) + && question.type !== "result") { + console.log("вызываю очистку рул вопросов") + updateQuestion(question.content.id, question => { + question.content.rule.parentId = ""; + question.content.rule.main = []; + question.content.rule.default = ""; + }); + } + }) + ) }; export const createResult = async ( @@ -506,7 +508,7 @@ export const createResult = async ( } //Мы получили запрос на создание резулта. Анализируем существует ли такой. Если да - просто делаем его активным - const question = useQuestionsStore.getState().questions.find(q=> q.type !== null && q?.content.rule.parentId === parentContentId) + const question = useQuestionsStore.getState().questions.find(q => q.type !== null && q?.content.rule.parentId === parentContentId) console.log("Получил запрос на создание результа родителю ", parentContentId) console.log("Ищу такой же результ в списке ", question) @@ -519,9 +521,9 @@ export const createResult = async ( } else {//не существует, создаём const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content)); content.rule.parentId = parentContentId; - + try { - const createdQuestion:RawQuestion = await questionApi.create({ + const createdQuestion: RawQuestion = await questionApi.create({ quiz_id: quizId, type: "result", title: "", @@ -530,7 +532,7 @@ export const createResult = async ( required: true, content: JSON.stringify(content), }); - + setProducedState(state => { state.questions.push(rawQuestionToQuestion(createdQuestion)) }, { @@ -541,6 +543,6 @@ export const createResult = async ( devlog("Error creating question", error); enqueueSnackbar("Не удалось создать вопрос"); } - } + } }); diff --git a/src/stores/uiTools/actions.ts b/src/stores/uiTools/actions.ts index cd6da193..7225818e 100644 --- a/src/stores/uiTools/actions.ts +++ b/src/stores/uiTools/actions.ts @@ -38,3 +38,7 @@ export const updateCanCreatePublic = (can: boolean) => useUiTools.setState({ can export const updateModalInfoWhyCantCreate = (can: boolean) => useUiTools.setState({ openModalInfoWhyCantCreate: can }); export const updateDeleteId = (deleteNodeId: string | null = null) => useUiTools.setState({ deleteNodeId }); +export const setShowConfirmLeaveModal = (showConfirmLeaveModal: boolean) => useUiTools.setState({ showConfirmLeaveModal }); + + +export const updateSomeWorkBackend = (someWorkBackend: boolean) => useUiTools.setState({ someWorkBackend }); diff --git a/src/stores/uiTools/store.ts b/src/stores/uiTools/store.ts index 52803348..baa0a444 100644 --- a/src/stores/uiTools/store.ts +++ b/src/stores/uiTools/store.ts @@ -10,8 +10,11 @@ export type UiTools = { canCreatePublic: boolean; whyCantCreatePublic: Record//ид вопроса и список претензий к нему openModalInfoWhyCantCreate: boolean; -deleteNodeId: string | null; + deleteNodeId: string | null; + showConfirmLeaveModal: boolean; + someWorkBackend: boolean; }; + export type WhyCantCreatePublic = { name: string; problems: string[] @@ -27,7 +30,9 @@ const initialState: UiTools = { canCreatePublic: false, whyCantCreatePublic: {}, openModalInfoWhyCantCreate: false, -deleteNodeId: null, + deleteNodeId: null, + showConfirmLeaveModal: false, + someWorkBackend: false }; export const useUiTools = create()( diff --git a/src/ui_kit/CustomCheckbox.tsx b/src/ui_kit/CustomCheckbox.tsx index 32b7d4ef..cb630f5d 100755 --- a/src/ui_kit/CustomCheckbox.tsx +++ b/src/ui_kit/CustomCheckbox.tsx @@ -1,7 +1,7 @@ import { FormControlLabel, Checkbox, useTheme, Box, useMediaQuery } from "@mui/material"; import React from "react"; -import { CheckboxIcon } from "@icons/Checkbox"; +import CheckboxIcon from "@icons/Checkbox"; import type { SxProps } from "@mui/material"; @@ -11,9 +11,10 @@ interface Props { checked?: boolean; sx?: SxProps; dataCy?: string; + colorIcon?: string; } -export default function CustomCheckbox({ label, handleChange, checked, sx, dataCy }: Props) { +export default function CustomCheckbox({ label, handleChange, checked, sx, dataCy, colorIcon }: Props) { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); @@ -24,7 +25,7 @@ export default function CustomCheckbox({ label, handleChange, checked, sx, dataC sx={{ padding: "0px 13px 1px 11px" }} disableRipple icon={} - checkedIcon={} + checkedIcon={} onChange={handleChange} checked={checked} data-cy={dataCy} @@ -32,7 +33,7 @@ export default function CustomCheckbox({ label, handleChange, checked, sx, dataC } label={label} sx={{ - color: theme.palette.grey2.main, + color: "#9A9AAF", height: "26px", ...sx, }} diff --git a/src/ui_kit/CustomSlider.tsx b/src/ui_kit/CustomSlider.tsx index 4438b42c..ab92c02f 100644 --- a/src/ui_kit/CustomSlider.tsx +++ b/src/ui_kit/CustomSlider.tsx @@ -1,4 +1,4 @@ -import { Slider, useTheme } from "@mui/material"; +import {Slider, SxProps, Theme, useTheme} from "@mui/material"; type CustomSliderProps = { defaultValue?: number; @@ -6,6 +6,7 @@ type CustomSliderProps = { min?: number; max?: number; step?: number; + sx?: SxProps onChange?: (_: Event, value: number | number[]) => void; onChangeCommitted?: (_: React.SyntheticEvent | Event, value: number | number[]) => void; }; @@ -18,6 +19,7 @@ export const CustomSlider = ({ step, onChange, onChangeCommitted, + sx, }: CustomSliderProps) => { // const handleChange = ({ type }: Event, newValue: number | number[]) => { // // Для корректной работы слайдера в FireFox @@ -40,11 +42,11 @@ export const CustomSlider = ({ onMouseDown={(e) => e.stopPropagation()} data-cy="slider" sx={{ - color: theme.palette.brightPurple.main, + color: "#7E2AEA", padding: "0", marginTop: "75px", "& .MuiSlider-valueLabel": { - background: theme.palette.brightPurple.main, + background: "#7E2AEA", borderRadius: "8px", minWidth: "60px", width: "auto", @@ -71,6 +73,7 @@ export const CustomSlider = ({ "& .MuiSlider-track": { height: "12px", }, + ...sx }} /> ); diff --git a/src/ui_kit/CustomTextField.tsx b/src/ui_kit/CustomTextField.tsx index bf7ea992..c9023f40 100755 --- a/src/ui_kit/CustomTextField.tsx +++ b/src/ui_kit/CustomTextField.tsx @@ -1,5 +1,11 @@ -import React, { useState } from "react"; -import { Box, FormControl, TextField, Typography, useTheme } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import { + Box, + FormControl, + TextField, + Typography, + useTheme, +} from "@mui/material"; import type { ChangeEvent, KeyboardEvent, FocusEvent } from "react"; import type { InputProps, SxProps, Theme } from "@mui/material"; @@ -19,7 +25,7 @@ interface CustomTextFieldProps { export default function CustomTextField({ placeholder, - value, + value = "", onChange, onKeyDown, onBlur, @@ -32,9 +38,13 @@ export default function CustomTextField({ }: CustomTextFieldProps) { const theme = useTheme(); - const [inputValue, setInputValue] = useState(value || text || ""); + const [inputValue, setInputValue] = useState(""); const [isInputActive, setIsInputActive] = useState(false); + useEffect(() => { + setInputValue(value); + }, [value]); + const handleInputChange = (event: React.ChangeEvent) => { const inputValue = event.target.value; setInputValue(inputValue); @@ -88,8 +98,8 @@ export default function CustomTextField({ fontSize: "18px", lineHeight: "21px", py: 0, + ...sx, }, - ...sx, }} data-cy="textfield" /> diff --git a/src/ui_kit/MediaSelectionAndDisplay.tsx b/src/ui_kit/MediaSelectionAndDisplay.tsx new file mode 100644 index 00000000..88a47ac4 --- /dev/null +++ b/src/ui_kit/MediaSelectionAndDisplay.tsx @@ -0,0 +1,149 @@ +import { FC } from "react"; +import { Box, Button } from "@mui/material"; +import CustomTextField from "./CustomTextField"; +import { updateQuestion, uploadQuestionImage } from "@root/questions/actions"; +import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; + +import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; +import { UploadImageModal } from "../pages/Questions/UploadImage/UploadImageModal"; +import { useDisclosure } from "../utils/useDisclosure"; +import { useCurrentQuiz } from "../stores/quizes/hooks"; +import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; + +interface Iprops { + resultData: AnyTypedQuizQuestion; +} + +export const MediaSelectionAndDisplay: FC = ({ resultData }) => { + const quizQid = useCurrentQuiz()?.qid; + const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } = + useCropModalState(); + const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); + + async function handleImageUpload(file: File) { + const url = await uploadQuestionImage(resultData.id, quizQid, file, (question, url) => { + question.content.back = url; + question.content.originalBack = url; + }); + closeImageUploadModal(); + openCropModal(file, url); + } + + function handleCropModalSaveClick(imageBlob: Blob) { + uploadQuestionImage(resultData.id, quizQid, imageBlob, (question, url) => { + question.content.back = url; + }); + } + + return ( + + + + + + + + + + + + {resultData.content.useImage && ( + + { + if (resultData.content.back) { + return openCropModal(resultData.content.back, resultData.content.originalBack); + } + + openImageUploadModal(); + }} + onPlusClick={() => { + openImageUploadModal(); + }} + /> + + )} + {!resultData.content.useImage && ( + + + updateQuestion(resultData.id, (q) => { + q.content.video = e.target.value; + }) + } + /> + + )} + + ); +}; diff --git a/src/ui_kit/QuizPreview/QuizPreview.tsx b/src/ui_kit/QuizPreview/QuizPreview.tsx index 3fa01fe8..342d2f52 100644 --- a/src/ui_kit/QuizPreview/QuizPreview.tsx +++ b/src/ui_kit/QuizPreview/QuizPreview.tsx @@ -1,4 +1,3 @@ -import VisibilityIcon from '@mui/icons-material/Visibility'; import { Box, IconButton } from "@mui/material"; import { toggleQuizPreview, useQuizPreviewStore } from "@root/quizPreview"; import { useLayoutEffect, useRef } from "react"; @@ -34,13 +33,7 @@ export default function QuizPreview() { function stickPreviewToBottomRight() { const rnd = rndRef.current; const rndSelfElement = rnd?.getSelfElement(); - if ( - !rnd || - !rndSelfElement || - !rndParentRef.current || - !isFirstShowRef.current - ) - return; + if (!rnd || !rndSelfElement || !rndParentRef.current || !isFirstShowRef.current) return; const rndParentRect = rndParentRef.current.getBoundingClientRect(); const rndRect = rndSelfElement.getBoundingClientRect(); @@ -118,18 +111,6 @@ export default function QuizPreview() { )} - - - ); } diff --git a/src/ui_kit/QuizPreview/QuizPreviewLayout.tsx b/src/ui_kit/QuizPreview/QuizPreviewLayout.tsx index 3c4ed097..003d76b7 100644 --- a/src/ui_kit/QuizPreview/QuizPreviewLayout.tsx +++ b/src/ui_kit/QuizPreview/QuizPreviewLayout.tsx @@ -1,10 +1,20 @@ -import { Box, Button, LinearProgress, Paper, Typography, FormControl, Select as MuiSelect, MenuItem, useTheme } from "@mui/material"; +import { + Box, + Button, + LinearProgress, + Paper, + Typography, + FormControl, + Select as MuiSelect, + MenuItem, + useTheme, +} from "@mui/material"; import { useQuestionsStore } from "@root/questions/store"; import { - decrementCurrentQuestionIndex, - incrementCurrentQuestionIndex, - useQuizPreviewStore, - setCurrentQuestionIndex + decrementCurrentQuestionIndex, + incrementCurrentQuestionIndex, + useQuizPreviewStore, + setCurrentQuestionIndex, } from "@root/quizPreview"; import { AnyTypedQuizQuestion, UntypedQuizQuestion } from "model/questionTypes/shared"; import { useEffect } from "react"; @@ -24,217 +34,211 @@ import { notReachable } from "../../utils/notReachable"; import ArrowDownIcon from "@icons/ArrowDownIcon"; export default function QuizPreviewLayout() { - const theme = useTheme(); - const questions = useQuestionsStore(state => state.questions); - const currentQuizStep = useQuizPreviewStore( - (state) => state.currentQuestionIndex - ); + const theme = useTheme(); + const questions = useQuestionsStore((state) => state.questions); + const currentQuizStep = useQuizPreviewStore((state) => state.currentQuestionIndex); - const nonDeletedQuizQuestions = questions.filter( - (question) => !question.deleted - ); - const maxCurrentQuizStep = - nonDeletedQuizQuestions.length > 0 ? nonDeletedQuizQuestions.length - 1 : 0; - const currentProgress = Math.floor( - (currentQuizStep / maxCurrentQuizStep) * 100 - ); + const nonDeletedQuizQuestions = questions.filter((question) => !question.deleted); + const maxCurrentQuizStep = nonDeletedQuizQuestions.length > 0 ? nonDeletedQuizQuestions.length - 1 : 0; + const currentProgress = Math.floor((currentQuizStep / maxCurrentQuizStep) * 100); - const currentQuestion = nonDeletedQuizQuestions[currentQuizStep]; + const currentQuestion = nonDeletedQuizQuestions[currentQuizStep]; - useEffect( - function resetCurrentQuizStep() { - if (currentQuizStep > maxCurrentQuizStep) { - decrementCurrentQuestionIndex(); - } - }, - [currentQuizStep, maxCurrentQuizStep] - ); + useEffect( + function resetCurrentQuizStep() { + if (currentQuizStep > maxCurrentQuizStep) { + decrementCurrentQuestionIndex(); + } + }, + [currentQuizStep, maxCurrentQuizStep] + ); - return ( - + + + + + + + setCurrentQuestionIndex(window.Number(target.value))} + sx={{ + height: "48px", + borderRadius: "8px", + "& .MuiOutlinedInput-notchedOutline": { + border: `1px solid ${theme.palette.brightPurple.main} !important`, + }, + }} + MenuProps={{ + PaperProps: { + sx: { + mt: "8px", + p: "4px", + borderRadius: "8px", + border: "1px solid #EEE4FC", + boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)", + }, + }, + MenuListProps: { + sx: { + py: 0, + display: "flex", + flexDirection: "column", + gap: "8px", + "& .Mui-selected": { + backgroundColor: theme.palette.background.default, + color: theme.palette.brightPurple.main, + }, + }, + }, + }} + inputProps={{ + sx: { + color: theme.palette.brightPurple.main, + display: "flex", + alignItems: "center", + px: "9px", + gap: "20px", + }, + }} + IconComponent={(props) => } + > + {Object.values(questions).map(({ id, title }, index) => ( + + {`${index + 1}. ${title}`} + + ))} + + + + + - + + {nonDeletedQuizQuestions.length > 0 + ? `Вопрос ${currentQuizStep + 1} из ${nonDeletedQuizQuestions.length}` + : "Нет вопросов"} + + {nonDeletedQuizQuestions.length > 0 && ( + + )} + + + + - - - - - - ); + Далее + + + + + + ); } -function QuestionPreviewComponent({ question }: { - question: AnyTypedQuizQuestion | UntypedQuizQuestion | undefined; -}) { - if (!question || question.type === null) return null; +function QuestionPreviewComponent({ question }: { question: AnyTypedQuizQuestion | UntypedQuizQuestion | undefined }) { + if (!question || question.type === null) return null; - switch (question.type) { - case "variant": return ; - case "images": return ; - case "varimg": return ; - case "emoji": return ; - case "text": return ; - case "select": return ; + case "date": + return ; + case "number": + return ; + case "file": + return ; + case "page": + return ; + case "rating": + return ; + default: + notReachable(question); + } } diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx index 4567eee6..1f4559e1 100644 --- a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx @@ -1,4 +1,5 @@ import { Box, Typography } from "@mui/material"; +import YoutubeEmbedIframe from "@ui_kit/StartPagePreview/YoutubeEmbedIframe"; import type { QuizQuestionPage } from "model/questionTypes/page"; @@ -7,6 +8,7 @@ interface Props { } export default function Page({ question }: Props) { + console.log(question); return ( - {question.title} - {question.content.text} - {question.content.picture && ( - - - + + {question.title} + + + + {question.content.text} + + + {question.content.useImage ? ( + + ) : ( + )} ); diff --git a/src/ui_kit/RadioCheck.tsx b/src/ui_kit/RadioCheck.tsx index 311daded..c842d817 100644 --- a/src/ui_kit/RadioCheck.tsx +++ b/src/ui_kit/RadioCheck.tsx @@ -1,7 +1,10 @@ import { Box, useTheme } from "@mui/material"; +interface Props { + color?: string +} -export default function RadioCheck() { +export default function RadioCheck( {color = "#7E2AEA"} : Props) { const theme = useTheme(); return ( @@ -16,7 +19,7 @@ export default function RadioCheck() { }} > - + diff --git a/src/ui_kit/Sidebar.tsx b/src/ui_kit/Sidebar.tsx index 5c858ae5..9dd6dfe3 100755 --- a/src/ui_kit/Sidebar.tsx +++ b/src/ui_kit/Sidebar.tsx @@ -4,118 +4,132 @@ import PencilCircleIcon from "@icons/PencilCircleIcon"; import PuzzlePieceIcon from "@icons/PuzzlePieceIcon"; import TagIcon from "@icons/TagIcon"; import { quizSetupSteps } from "@model/quizSettings"; -import { - Box, - IconButton, - List, - Typography, - useTheme -} from "@mui/material"; +import { Box, IconButton, List, Typography, useTheme } from "@mui/material"; import { setCurrentStep } from "@root/quizes/actions"; import { useQuizStore } from "@root/quizes/store"; import { useState } from "react"; import MenuItem from "./MenuItem"; -import {useCurrentQuiz} from "@root/quizes/hooks"; - +import { useCurrentQuiz } from "@root/quizes/hooks"; +import { setShowConfirmLeaveModal } from "@root/uiTools/actions"; const quizSettingsMenuItems = [ - [TagIcon, "Дополнения"], - [PencilCircleIcon, "Дизайн"], - [PuzzlePieceIcon, "Интеграции"], - [GearIcon, "Настройки"], + [TagIcon, "Дополнения"], + [PencilCircleIcon, "Дизайн"], + [PuzzlePieceIcon, "Интеграции"], + [GearIcon, "Настройки"], ] as const; -export default function Sidebar() { - const theme = useTheme(); - const [isMenuCollapsed, setIsMenuCollapsed] = useState(false); - const currentStep = useQuizStore(state => state.currentStep); - const quiz = useCurrentQuiz(); +type SidebarProps = { + setNextStep: (step: number) => void; +}; +export default function Sidebar({ setNextStep }: SidebarProps) { + const theme = useTheme(); + const [isMenuCollapsed, setIsMenuCollapsed] = useState(false); + const currentStep = useQuizStore((state) => state.currentStep); + const quiz = useCurrentQuiz(); - const handleMenuCollapseToggle = () => setIsMenuCollapsed((prev) => !prev); + const handleMenuCollapseToggle = () => setIsMenuCollapsed((prev) => !prev); - return ( - { + if (currentStep === 2) { + setNextStep(index); + setShowConfirmLeaveModal(true); + + return; + } + + setCurrentStep(index); + }; + + return ( + + + {!isMenuCollapsed && ( + + Создание квиза + + )} + - - {!isMenuCollapsed && ( - - Создание квиза - - )} - - - - - - {quizSetupSteps.map((menuItem, index) => { - const Icon = menuItem.sidebarIcon; - - return ( - setCurrentStep(index)} - key={index} - text={menuItem.sidebarText} - isCollapsed={isMenuCollapsed} - isActive={currentStep === index} - disabled={index===0 ? false : quiz===undefined ? true : (quiz?.config.type === null)} - icon={ - - } - /> - ); - })} - - {/* {!isMenuCollapsed && ( + + + + + {quizSetupSteps.map((menuItem, index) => { + const Icon = menuItem.sidebarIcon; + + return ( + changePage(index)} + key={index} + text={menuItem.sidebarText} + isCollapsed={isMenuCollapsed} + isActive={currentStep === index} + disabled={ + index === 0 + ? false + : quiz === undefined + ? true + : quiz?.config.type === null + } + icon={ + + } + /> + ); + })} + + {/* {!isMenuCollapsed && ( )} */} - - {/* {quizSettingsMenuItems.map((menuItem, index) => { + + {/* {quizSettingsMenuItems.map((menuItem, index) => { const Icon = menuItem[0]; const totalIndex = index + quizSetupSteps.length; const isActive = currentStep === totalIndex + 1; @@ -159,7 +173,7 @@ export default function Sidebar() { /> ); })} */} - - - ); + + + ); } diff --git a/src/ui_kit/StartPagePreview/QuizPreviewLayout.tsx b/src/ui_kit/StartPagePreview/QuizPreviewLayout.tsx index 57472479..672e78a4 100644 --- a/src/ui_kit/StartPagePreview/QuizPreviewLayout.tsx +++ b/src/ui_kit/StartPagePreview/QuizPreviewLayout.tsx @@ -1,240 +1,247 @@ -import { - Box, - Button, - ButtonBase, - Link, - Paper, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; +import { Box, Button, ButtonBase, Link, Paper, Typography, useMediaQuery, useTheme } from "@mui/material"; import { useCurrentQuiz } from "@root/quizes/hooks"; import YoutubeEmbedIframe from "./YoutubeEmbedIframe"; import { QuizStartpageAlignType, QuizStartpageType } from "@model/quizSettings"; import { notReachable } from "../../utils/notReachable"; import { useUADevice } from "../../utils/hooks/useUADevice"; - export default function QuizPreviewLayout() { - const theme = useTheme(); - const quiz = useCurrentQuiz(); - const { isMobileDevice } = useUADevice(); + const theme = useTheme(); + const quiz = useCurrentQuiz(); + const { isMobileDevice } = useUADevice(); - if (!quiz) return null; + if (!quiz) return null; - const handleCopyNumber = () => { - navigator.clipboard.writeText(quiz.config.info.phonenumber); - }; + const handleCopyNumber = () => { + navigator.clipboard.writeText(quiz.config.info.phonenumber); + }; - const background = quiz.config.startpage.background.type === "image" - ? quiz.config.startpage.background.desktop - ? ( + const background = + quiz.config.startpage.background.type === "image" ? ( + quiz.config.startpage.background.desktop ? ( + + ) : null + ) : quiz.config.startpage.background.type === "video" ? ( + quiz.config.startpage.background.video ? ( + + ) : null + ) : null; + + return ( + + + + {quiz.config.startpage.logo && ( - ) - : null - : quiz.config.startpage.background.type === "video" - ? quiz.config.startpage.background.video - ? ( - + )} + {quiz.config.info.orgname} + + + } + quizMainBlock={ + <> + + {quiz.name} + {quiz.config.startpage.description} + + + + + + {quiz.config.info.clickable ? ( + isMobileDevice ? ( + + + {quiz.config.info.phonenumber} + + + ) : ( + + + {quiz.config.info.phonenumber} + + ) - : null - : null; - - return ( - - - - {quiz.config.startpage.logo && ( - - )} - - {quiz.config.info.orgname} - - - } - quizMainBlock={<> - - {quiz.name} - - {quiz.config.startpage.description} - - - - - - - {quiz.config.info.clickable ? ( - isMobileDevice ? ( - - - {quiz.config.info.phonenumber} - - - ) : ( - - - {quiz.config.info.phonenumber} - - - ) - ) : ( - - {quiz.config.info.phonenumber} - - )} - - {quiz.config.info.law} - - - } - backgroundBlock={background} - startpageType={quiz.config.startpageType} - alignType={quiz.config.startpage.position} - /> - - ); + ) : ( + + {quiz.config.info.phonenumber} + + )} + {quiz.config.info.law} + + + } + backgroundBlock={background} + startpageType={quiz.config.startpageType} + alignType={quiz.config.startpage.position} + /> + + ); } -function QuizPreviewLayoutByType({ quizHeaderBlock, quizMainBlock, backgroundBlock, startpageType, alignType }: { - quizHeaderBlock: JSX.Element; - quizMainBlock: JSX.Element; - backgroundBlock: JSX.Element | null; - startpageType: QuizStartpageType; - alignType: QuizStartpageAlignType; +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)); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(630)); - switch (startpageType) { - case null: - case "standard": { - return ( - - - {quizHeaderBlock} - {quizMainBlock} - - - {backgroundBlock} - - - ); - } - case "expanded": { - return ( - - - {quizHeaderBlock} - {quizMainBlock} - - - {backgroundBlock} - - - ); - } - case "centered": { - return ( - - {quizHeaderBlock} - {backgroundBlock && - - {backgroundBlock} - - } - {quizMainBlock} - - ); - } - default: notReachable(startpageType); + switch (startpageType) { + case null: + case "standard": { + return ( + + + {quizHeaderBlock} + {quizMainBlock} + + + {backgroundBlock} + + + ); } + case "expanded": { + return ( + + + {quizHeaderBlock} + {quizMainBlock} + + + {backgroundBlock} + + + ); + } + case "centered": { + return ( + + {quizHeaderBlock} + {backgroundBlock && {backgroundBlock}} + {quizMainBlock} + + ); + } + default: + notReachable(startpageType); + } } const startpageAlignTypeToJustifyContent: Record = { - left: "start", - center: "center", - right: "end", + left: "start", + center: "center", + right: "end", }; diff --git a/src/ui_kit/StartPagePreview/index.tsx b/src/ui_kit/StartPagePreview/index.tsx index 97442480..6e4b782e 100644 --- a/src/ui_kit/StartPagePreview/index.tsx +++ b/src/ui_kit/StartPagePreview/index.tsx @@ -1,4 +1,3 @@ -import VisibilityIcon from "@mui/icons-material/Visibility"; import { Box, IconButton, useTheme, useMediaQuery } from "@mui/material"; import { toggleQuizPreview, useQuizPreviewStore } from "@root/quizPreview"; import { useLayoutEffect, useRef } from "react"; @@ -35,13 +34,7 @@ export const StartPagePreview = () => { function stickPreviewToBottomRight() { const rnd = rndRef.current; const rndSelfElement = rnd?.getSelfElement(); - if ( - !rnd || - !rndSelfElement || - !rndParentRef.current || - !isFirstShowRef.current - ) - return; + if (!rnd || !rndSelfElement || !rndParentRef.current || !isFirstShowRef.current) return; const rndParentRect = rndParentRef.current.getBoundingClientRect(); const rndRect = rndSelfElement.getBoundingClientRect(); @@ -119,17 +112,6 @@ export const StartPagePreview = () => { )} - - - ); }; diff --git a/src/ui_kit/switchStepPages.tsx b/src/ui_kit/switchStepPages.tsx index b89b755a..9e7a1fae 100755 --- a/src/ui_kit/switchStepPages.tsx +++ b/src/ui_kit/switchStepPages.tsx @@ -16,6 +16,8 @@ interface Props { quizType: QuizType; quizStartPageType: QuizStartpageType; quizResults: QuizResultsType; + openBranchingPage: boolean; + setOpenBranchingPage: (a:boolean) => void; } export default function SwitchStepPages({ @@ -23,6 +25,8 @@ export default function SwitchStepPages({ quizType, quizStartPageType, quizResults, + openBranchingPage, + setOpenBranchingPage }: Props) { switch (activeStep) { case 0: { @@ -30,7 +34,10 @@ export default function SwitchStepPages({ if (!quizStartPageType) return ; return ; } - case 1: return quizType === "form" ? : ; + case 1: return quizType === "form" ? : ; case 2: return ; case 3: return ; case 4: return ; diff --git a/src/utils/checkQuestionHint.ts b/src/utils/checkQuestionHint.ts new file mode 100644 index 00000000..51a19eb1 --- /dev/null +++ b/src/utils/checkQuestionHint.ts @@ -0,0 +1,82 @@ +import { AnyTypedQuizQuestion, QuestionBranchingRuleMain } from "@model/questionTypes/shared"; +import { WhyCantCreatePublic } from "@root/uiTools/store"; +import { getQuestionByContentId, updateQuestion } from "@root/questions/actions"; + +export const checkQuestionHint = (questions: AnyTypedQuizQuestion): Record => { + + const problems: any = {} + + const pushProblem = (id: string, problem: string, title: string) => { + //Если первый вопрос с проблемой - создаём запись. Если не первый - добавляем проблему + if (id in problems) { + problems[id].problems.push(problem) + } else { + problems[id] = { + name: title, + problems: [problem] + } + } + } + + questions.forEach((question: AnyTypedQuizQuestion) => { + //Если не участвует в ветвлении, или безтиповый, или резулт - он нам не интересен + if (question.type === null + || question.type === "result" + || question.content.rule.parentId.length === 0) return + + + if ( + question?.type === "date" || + question?.type === "text" || + question?.type === "number" || + question?.type === "page" + ) {//Если у вопроса типа страница, ползунок, своё поле для ввода и дата есть ребёнок, но нет дефолта - молча его добавляем. + + if (question.content.rule.children.length === 1 && question.content.rule.default.length === 0) + updateQuestion(question.id, (q) => { + question.content.rule.default = question.content.rule.children[0] + }) + + } else { + //если есть дети, но нет дефолта - логическая ошибка. Так нельзя + if (question.content.rule.children.length > 0 && question.content.rule.default.length === 0) { + pushProblem(question.content.id, "Не выбран дефолтный вопрос", question.title) + } + } + + + //Rules вопроса не должны совпадать + const buffer: QuestionBranchingRuleMain[] = [] + question.content.rule.main.forEach((condition: QuestionBranchingRuleMain) => { + buffer.forEach((oldCondition: QuestionBranchingRuleMain) => { + if (areRulesEqual(condition.rules, oldCondition.rules)) { + pushProblem( + question.content.id, + `У вопроса "${getQuestionByContentId(condition.next)?.title || "noname"}" и "${getQuestionByContentId(oldCondition.next)?.title || "noname"}" одинаковые условия ветвления`, + question.title + ) + } + }) + buffer.push(condition) + }) + }) + + return problems +} + +const areRulesEqual = (first: any, second: any) => { + const firstArray = first[0].answers + const secondArray = second[0].answers + if (firstArray.length === secondArray.length) { + if (firstArray.length > 1) { + if ( + firstArray.every((element: any, index: number) => element === secondArray[index]) + && + first.or === second.or + ) return true; + } else { + if (firstArray[0] === secondArray[0]) return true; + } + } + return false; +}; \ No newline at end of file diff --git a/src/utils/deleteFunc.ts b/src/utils/deleteFunc.ts new file mode 100644 index 00000000..27098b9d --- /dev/null +++ b/src/utils/deleteFunc.ts @@ -0,0 +1,67 @@ +import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; +import { clearRuleForAll, deleteQuestion, getQuestionByContentId, updateQuestion } from "@root/questions/actions"; +import { updateRootContentId } from "@root/quizes/actions"; + +//Всё здесь нужно сделать последовательно. И пусть весь мир ждёт. + +export const DeleteFunction = async (questions: any, question: any, quiz: any) => { + + + if (question.type !== null) { + if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам + updateRootContentId(quiz.id, ""); + await clearRuleForAll(); + console.log("очистка рулов закончилась") + await deleteQuestion(question.id); + } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков + const clearQuestions = [] as string[]; + + //записываем потомков , а их результаты удаляем + const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { + questions.forEach((targetQuestion) => { + if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его + if (targetQuestion.type !== "result" && targetQuestion.type !== null) { + if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); + getChildren(targetQuestion); //и ищем его потомков + } + } + }); + }; + getChildren(question); + //чистим потомков от инфы ветвления + await Promise.allSettled( + clearQuestions.map((id) => { + updateQuestion(id, question => { + question.content.rule.parentId = ""; + question.content.rule.children = []; + question.content.rule.main = []; + question.content.rule.default = ""; + }); + }) + ) + + + //чистим rule родителя + const parentQuestion = getQuestionByContentId(question.content.rule.parentId); + const newRule = {}; + newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id); //удаляем условия перехода от родителя к этому вопросу + newRule.parentId = parentQuestion.content.rule.parentId; + newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId; + newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1); + + await updateQuestion(question.content.rule.parentId, (PQ) => { + PQ.content.rule = newRule; + }); + await deleteQuestion(question.id); + } + + await deleteQuestion(question.id); + + const result = questions.find(q => q.type === "result" && q.content.rule.parentId === question.content.id) + if (result) await deleteQuestion(result.id); + + } else { + await deleteQuestion(question.id); + } + +} \ No newline at end of file diff --git a/src/utils/deleteTimeoutedQuestions.ts b/src/utils/deleteTimeoutedQuestions.ts new file mode 100644 index 00000000..19c18322 --- /dev/null +++ b/src/utils/deleteTimeoutedQuestions.ts @@ -0,0 +1,25 @@ +import { AnyTypedQuizQuestion, UntypedQuizQuestion } from "@model/questionTypes/shared"; +import { Quiz } from "@model/quiz/quiz"; +import { updateSomeWorkBackend } from "@root/uiTools/actions"; +import { DeleteFunction } from "@utils/deleteFunc"; + +type allQuestionsTypes = AnyTypedQuizQuestion | UntypedQuizQuestion + +export const deleteTimeoutedQuestions = async (questions: allQuestionsTypes[], quiz: Quiz|undefined) => { + console.log("Я отвечаю за удаление неудалёнышей при переключении. Привет, буде знакомы") + const questionsForDeletion = questions.filter( + ({ type, deleted }) => type && type !== "result" && deleted + ) as AnyTypedQuizQuestion[]; + if (questionsForDeletion.length > 0) { + console.log("меняю занятость беком на true") + updateSomeWorkBackend(true) + + + await Promise.allSettled( + questionsForDeletion.map(question => DeleteFunction(questions, question, quiz)) + ) + + console.log("______________меняю на 'можно редактировать дальше'______________") + updateSomeWorkBackend(false) + } +}; \ No newline at end of file diff --git a/src/utils/themes/Publication/genericPublication.ts b/src/utils/themes/Publication/genericPublication.ts new file mode 100644 index 00000000..f6f319e6 --- /dev/null +++ b/src/utils/themes/Publication/genericPublication.ts @@ -0,0 +1,43 @@ +import { createTheme } from "@mui/material"; +import theme from "../generic"; + +const themePublic = createTheme({ + ...theme, + components: { + MuiButton: { + variants: [ + { + props: { + variant: 'contained' + }, + style: { + padding: '13px 20px', + borderRadius: '8px', + boxShadow: "none", + // "&:hover": { + // backgroundColor: "#581CA7" + // } + + }, + }, + { + props: { + variant: 'outlined' + }, + style: { + padding: '10px 20px', + borderRadius: '8px', + "&:hover": { + backgroundColor: "#581CA7", + border: '1px solid #581CA7', + } + + }, + }, + ], + }, + }, + }, +) + +export default themePublic; \ No newline at end of file diff --git a/src/utils/themes/Publication/themePublication.ts b/src/utils/themes/Publication/themePublication.ts new file mode 100644 index 00000000..3c1a499f --- /dev/null +++ b/src/utils/themes/Publication/themePublication.ts @@ -0,0 +1,253 @@ +import { createTheme } from "@mui/material"; +import themePublic from "./genericPublication"; +import theme from "../generic"; + + + +const StandardTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#7E2AEA", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#333647", + secondary: "#7E2AEA", + }, + + background: { + default: "#FFFFFF", + }, + } +}) + +const StandardDarkTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#7E2AEA", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#FFFFFF", + secondary: "#7E2AEA", + }, + + background: { + default: "#333647", + }, + } +}) + +const PinkTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#D34085", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#333647", + secondary: "#D34085", + }, + + background: { + default: "#FFF9FC", + }, + } +}) + +const PinkDarkTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#D34085", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#FFFFFF", + secondary: "#D34085", + }, + + background: { + default: "#333647", + }, + } +}) + +const BlackWhiteTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#4E4D51", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#333647", + secondary: "#FFF9FC", + }, + + background: { + default: "#FFFFFF", + }, + } +}) + +const OliveTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#758E4F", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#333647", + secondary: "#758E4F", + }, + + background: { + default: "#F9FBF1", + }, + } +}) + +const PurpleTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#7E2AEA", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#333647", + secondary: "#7E2AEA", + }, + + background: { + default: "#FBF8FF", + }, + } +}) + +const YellowTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#F2B133", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#333647", + secondary: "#F2B133", + }, + + background: { + default: "#FFFCF6", + }, + } +}) + +const GoldDarkTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#E6AA37", + }, + secondary: { + main: "#FFFCF6", + }, + text: { + primary: "#FFFFFF", + secondary: "#F2B133", + }, + + background: { + default: "#333647", + }, + } +}) + +const BlueTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#4964ED", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#333647", + secondary: "#4964ED", + }, + + background: { + default: "#F5F7FF", + }, + } +}) + +const BlueDarkTheme = createTheme({ + ...themePublic, + palette: { + primary: { + main: "#07A0C3", + }, + secondary: { + main: "#252734" + }, + text: { + primary: "#FFFFFF", + secondary: "#07A0C3", + }, + + background: { + default: "#333647", + }, + } +}) + +export const modes = { + StandardTheme: true, + StandardDarkTheme: false, + PinkTheme: true, + PinkDarkTheme: false, + BlackWhiteTheme: true, + OliveTheme: true, + YellowTheme: true, + GoldDarkTheme: false, + PurpleTheme: true, + BlueTheme: true, + BlueDarkTheme: false +} + +export const themesPublication = { + StandardTheme, + StandardDarkTheme, + PinkTheme, + PinkDarkTheme, + BlackWhiteTheme, + OliveTheme, + YellowTheme, + GoldDarkTheme, + PurpleTheme, + BlueTheme, + BlueDarkTheme, +} \ No newline at end of file diff --git a/tsconfig.extend.json b/tsconfig.extend.json index 81832f9f..eabc7e3d 100755 --- a/tsconfig.extend.json +++ b/tsconfig.extend.json @@ -16,6 +16,9 @@ ], "@model/*": [ "./model/*" + ], + "@utils/*": [ + "./utils/*" ] } }