refactor component hierarchy
move logic to dedicated hook use quizId from context, not from settings add ErrorBoundaryFallback component
This commit is contained in:
parent
ba547dcb55
commit
e4d9018962
10
src/App.tsx
10
src/App.tsx
@ -1,20 +1,22 @@
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
import { startTransition, useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import QuizAnswerer from "./QuizAnswerer";
|
import QuizAnswerer from "./QuizAnswerer";
|
||||||
import { QuizIdContext } from "./contexts/QuizIdContext";
|
import { QuizIdContext } from "./contexts/QuizIdContext";
|
||||||
import { RootContainerWidthContext } from "./contexts/RootContainerWidthContext";
|
import { RootContainerWidthContext } from "./contexts/RootContainerWidthContext";
|
||||||
|
|
||||||
|
|
||||||
const defaultQuizId = "ef836ff8-35b1-4031-9acf-af5766bac2b2";
|
const defaultQuizId = "45ef7f9c-784d-4e58-badb-f6b337f08ba0";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const quizId = useParams().quizId ?? defaultQuizId;
|
const quizId = useParams().quizId ?? defaultQuizId;
|
||||||
const [rootContainerSize, setRootContainerSize] = useState<number>(Infinity);
|
const [rootContainerSize, setRootContainerSize] = useState<number>(() => window.innerWidth);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleWindowResize = () => {
|
const handleWindowResize = () => {
|
||||||
setRootContainerSize(window.innerWidth);
|
startTransition(() => {
|
||||||
|
setRootContainerSize(window.innerWidth);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
window.addEventListener("resize", handleWindowResize);
|
window.addEventListener("resize", handleWindowResize);
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { CssBaseline, ThemeProvider } from "@mui/material";
|
|||||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||||
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
||||||
import { ruRU } from '@mui/x-date-pickers/locales';
|
import { ruRU } from '@mui/x-date-pickers/locales';
|
||||||
|
import ErrorBoundaryFallback from "@ui_kit/ErrorBoundaryFallback";
|
||||||
import LoadingSkeleton from "@ui_kit/LoadingSkeleton";
|
import LoadingSkeleton from "@ui_kit/LoadingSkeleton";
|
||||||
import { handleComponentError } from "@utils/handleComponentError";
|
import { handleComponentError } from "@utils/handleComponentError";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
@ -9,8 +10,7 @@ import { SnackbarProvider } from 'notistack';
|
|||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { SWRConfig } from "swr";
|
import { SWRConfig } from "swr";
|
||||||
import { ApologyPage } from "./pages/ViewPublicationPage/ApologyPage";
|
import ViewPublicationPage from "./pages/ViewPublicationPage/ViewPublicationPage";
|
||||||
import { ViewPage } from "./pages/ViewPublicationPage/ViewPublicationPage";
|
|
||||||
import lightTheme from "./utils/themes/light";
|
import lightTheme from "./utils/themes/light";
|
||||||
|
|
||||||
|
|
||||||
@ -32,11 +32,11 @@ export default function QuizAnswerer() {
|
|||||||
>
|
>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
fallback={<ApologyPage message="Что-то пошло не так" />}
|
FallbackComponent={ErrorBoundaryFallback}
|
||||||
onError={handleComponentError}
|
onError={handleComponentError}
|
||||||
>
|
>
|
||||||
<Suspense fallback={<LoadingSkeleton />}>
|
<Suspense fallback={<LoadingSkeleton />}>
|
||||||
<ViewPage />
|
<ViewPublicationPage />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { startTransition, useEffect, useRef, useState } from "react";
|
||||||
import QuizAnswerer from "./QuizAnswerer";
|
import QuizAnswerer from "./QuizAnswerer";
|
||||||
import { QuizIdContext } from "./contexts/QuizIdContext";
|
import { QuizIdContext } from "./contexts/QuizIdContext";
|
||||||
import { RootContainerWidthContext } from "./contexts/RootContainerWidthContext";
|
import { RootContainerWidthContext } from "./contexts/RootContainerWidthContext";
|
||||||
@ -10,14 +10,14 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function WidgetApp({ quizId }: Props) {
|
export default function WidgetApp({ quizId }: Props) {
|
||||||
const [rootContainerSize, setRootContainerSize] = useState<number>(Infinity);
|
const [rootContainerSize, setRootContainerSize] = useState<number>(() => window.innerWidth);
|
||||||
const rootContainerRef = useRef<HTMLDivElement>(null);
|
const rootContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleWindowResize = () => {
|
const handleWindowResize = () => {
|
||||||
if (!rootContainerRef.current) return;
|
startTransition(() => {
|
||||||
|
if (rootContainerRef.current) setRootContainerSize(rootContainerRef.current.clientWidth);
|
||||||
setRootContainerSize(rootContainerRef.current.clientWidth);
|
});
|
||||||
};
|
};
|
||||||
window.addEventListener("resize", handleWindowResize);
|
window.addEventListener("resize", handleWindowResize);
|
||||||
|
|
||||||
|
@ -6,143 +6,124 @@ import type { AxiosError } from "axios";
|
|||||||
let SESSIONS = "";
|
let SESSIONS = "";
|
||||||
|
|
||||||
export const publicationMakeRequest = ({ url, body }: any) => {
|
export const publicationMakeRequest = ({ url, body }: any) => {
|
||||||
console.log(body);
|
console.log(body);
|
||||||
return axios(url, {
|
return axios(url, {
|
||||||
//@ts-ignore
|
data: body,
|
||||||
data: body,
|
headers: {
|
||||||
headers: {
|
"X-Sessionkey": SESSIONS,
|
||||||
"X-Sessionkey": SESSIONS,
|
"Content-Type": "multipart/form-data",
|
||||||
"Content-Type": "multipart/form-data",
|
},
|
||||||
},
|
method: "POST",
|
||||||
method: "POST",
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getData(quizId: string): Promise<{
|
export async function getData(quizId: string): Promise<{
|
||||||
data: GetQuizDataResponse | null;
|
data: GetQuizDataResponse | null;
|
||||||
isRecentlyCompleted: boolean;
|
isRecentlyCompleted: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
}> {
|
}> {
|
||||||
const QID =
|
try {
|
||||||
process.env.NODE_ENV === "production"
|
const { data, headers } = await axios<GetQuizDataResponse>(
|
||||||
? window.location.pathname.replace(/\//g, "")
|
`https://s.hbpn.link/answer/settings`,
|
||||||
: "ef836ff8-35b1-4031-9acf-af5766bac2b2";
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
quiz_id: quizId,
|
||||||
|
limit: 100,
|
||||||
|
page: 0,
|
||||||
|
need_config: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||||
|
|
||||||
try {
|
if (typeof sessions[quizId] === "number") {
|
||||||
const { data, headers } = await axios<GetQuizDataResponse>(
|
// unix время. Если меньше суток прошло - выводить ошибку, иначе пустить дальше
|
||||||
`https://s.hbpn.link/answer/settings`,
|
if (Date.now() - sessions[quizId] < 86400000) {
|
||||||
{
|
return { data, isRecentlyCompleted: true };
|
||||||
method: "POST",
|
}
|
||||||
// headers,
|
}
|
||||||
data: JSON.stringify({
|
|
||||||
quiz_id: quizId,
|
|
||||||
limit: 100,
|
|
||||||
page: 0,
|
|
||||||
need_config: true,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
|
||||||
|
|
||||||
if (typeof sessions[QID] === "number") {
|
SESSIONS = headers["x-sessionkey"];
|
||||||
// unix время. Если меньше суток прошло - выводить ошибку, иначе пустить дальше
|
|
||||||
if (Date.now() - sessions[QID] < 86400000) {
|
return { data, isRecentlyCompleted: false };
|
||||||
return { data, isRecentlyCompleted: true };
|
} catch (nativeError) {
|
||||||
}
|
const error = nativeError as AxiosError;
|
||||||
|
|
||||||
|
return { data: null, isRecentlyCompleted: false, error: error.message };
|
||||||
}
|
}
|
||||||
|
|
||||||
SESSIONS = headers["x-sessionkey"];
|
|
||||||
|
|
||||||
return { data, isRecentlyCompleted: false };
|
|
||||||
} catch (nativeError) {
|
|
||||||
const error = nativeError as AxiosError;
|
|
||||||
|
|
||||||
return { data: null, isRecentlyCompleted: false, error: error.message };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendAnswer({ questionId, body, qid }: any) {
|
export function sendAnswer({ questionId, body, qid }: any) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
console.log(qid);
|
console.log(qid);
|
||||||
const answers = [
|
const answers = [
|
||||||
{
|
{
|
||||||
question_id: questionId,
|
question_id: questionId,
|
||||||
content: body, //тут массив с ответом
|
content: body, //тут массив с ответом
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
formData.append("answers", JSON.stringify(answers));
|
formData.append("answers", JSON.stringify(answers));
|
||||||
formData.append("qid", qid);
|
formData.append("qid", qid);
|
||||||
|
|
||||||
return publicationMakeRequest({
|
return publicationMakeRequest({
|
||||||
url: `https://s.hbpn.link/answer/answer`,
|
url: `https://s.hbpn.link/answer/answer`,
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//body ={file, filename}
|
//body ={file, filename}
|
||||||
export function sendFile({ questionId, body, qid }: any) {
|
export function sendFile({ questionId, body, qid }: any) {
|
||||||
console.log(body);
|
console.log(body);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
const answers: any = [
|
const answers: any = [
|
||||||
{
|
{
|
||||||
question_id: questionId,
|
question_id: questionId,
|
||||||
content: "file:" + body.name,
|
content: "file:" + body.name,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
formData.append("answers", JSON.stringify(answers));
|
formData.append("answers", JSON.stringify(answers));
|
||||||
formData.append(body.name, body.file);
|
formData.append(body.name, body.file);
|
||||||
formData.append("qid", qid);
|
formData.append("qid", qid);
|
||||||
|
|
||||||
return publicationMakeRequest({
|
return publicationMakeRequest({
|
||||||
url: `https://s.hbpn.link/answer/answer`,
|
url: `https://s.hbpn.link/answer/answer`,
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = [
|
|
||||||
"name",
|
|
||||||
"email",
|
|
||||||
"phone",
|
|
||||||
"adress",
|
|
||||||
"telegram",
|
|
||||||
"wechat",
|
|
||||||
"viber",
|
|
||||||
"vk",
|
|
||||||
"skype",
|
|
||||||
"whatsup",
|
|
||||||
"messenger",
|
|
||||||
"text",
|
|
||||||
];
|
|
||||||
|
|
||||||
//форма контактов
|
//форма контактов
|
||||||
export function sendFC({ questionId, body, qid }: any) {
|
export function sendFC({ questionId, body, qid }: any) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
// const keysBody = Object.keys(body)
|
// const keysBody = Object.keys(body)
|
||||||
// const content:any = {}
|
// const content:any = {}
|
||||||
// fields.forEach((key) => {
|
// fields.forEach((key) => {
|
||||||
// if (keysBody.includes(key)) content[key] = body.key
|
// if (keysBody.includes(key)) content[key] = body.key
|
||||||
// })
|
// })
|
||||||
|
|
||||||
const answers = [
|
const answers = [
|
||||||
{
|
{
|
||||||
question_id: questionId,
|
question_id: questionId,
|
||||||
content: JSON.stringify(body),
|
content: JSON.stringify(body),
|
||||||
result: true,
|
result: true,
|
||||||
qid,
|
qid,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
formData.append("answers", JSON.stringify(answers));
|
formData.append("answers", JSON.stringify(answers));
|
||||||
formData.append("qid", qid);
|
formData.append("qid", qid);
|
||||||
|
|
||||||
return publicationMakeRequest({
|
return publicationMakeRequest({
|
||||||
url: `https://s.hbpn.link/answer/answer`,
|
url: `https://s.hbpn.link/answer/answer`,
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ export interface GetQuizDataResponse {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseQuizData(quizDataResponse: GetQuizDataResponse, quizId: string): Omit<QuizSettings, "recentlyCompleted"> {
|
export function parseQuizData(quizDataResponse: GetQuizDataResponse): Omit<QuizSettings, "recentlyCompleted"> {
|
||||||
const items: QuizSettings["questions"] = quizDataResponse.items.map((item) => {
|
const items: QuizSettings["questions"] = quizDataResponse.items.map((item) => {
|
||||||
const content = JSON.parse(item.c);
|
const content = JSON.parse(item.c);
|
||||||
|
|
||||||
@ -40,7 +40,6 @@ export function parseQuizData(quizDataResponse: GetQuizDataResponse, quizId: str
|
|||||||
});
|
});
|
||||||
|
|
||||||
const settings: QuizSettings["settings"] = {
|
const settings: QuizSettings["settings"] = {
|
||||||
qid: quizId,
|
|
||||||
fp: quizDataResponse.settings.fp,
|
fp: quizDataResponse.settings.fp,
|
||||||
rep: quizDataResponse.settings.rep,
|
rep: quizDataResponse.settings.rep,
|
||||||
name: quizDataResponse.settings.name,
|
name: quizDataResponse.settings.name,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { nanoid } from "nanoid";
|
||||||
import type { QuizQuestionDate } from "./date";
|
import type { QuizQuestionDate } from "./date";
|
||||||
import type { QuizQuestionEmoji } from "./emoji";
|
import type { QuizQuestionEmoji } from "./emoji";
|
||||||
import type { QuizQuestionFile } from "./file";
|
import type { QuizQuestionFile } from "./file";
|
||||||
@ -5,24 +6,23 @@ import type { QuizQuestionImages } from "./images";
|
|||||||
import type { QuizQuestionNumber } from "./number";
|
import type { QuizQuestionNumber } from "./number";
|
||||||
import type { QuizQuestionPage } from "./page";
|
import type { QuizQuestionPage } from "./page";
|
||||||
import type { QuizQuestionRating } from "./rating";
|
import type { QuizQuestionRating } from "./rating";
|
||||||
|
import type { QuizQuestionResult } from "./result";
|
||||||
import type { QuizQuestionSelect } from "./select";
|
import type { QuizQuestionSelect } from "./select";
|
||||||
import type { QuizQuestionText } from "./text";
|
import type { QuizQuestionText } from "./text";
|
||||||
import type { QuizQuestionVariant } from "./variant";
|
import type { QuizQuestionVariant } from "./variant";
|
||||||
import type { QuizQuestionVarImg } from "./varimg";
|
import type { QuizQuestionVarImg } from "./varimg";
|
||||||
import type { QuizQuestionResult } from "./result";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
|
|
||||||
export interface QuestionBranchingRuleMain {
|
export interface QuestionBranchingRuleMain {
|
||||||
next: string;
|
next: string;
|
||||||
or: boolean;
|
or: boolean;
|
||||||
rules: {
|
rules: {
|
||||||
question: string; //id родителя (пока что)
|
question: string; //id родителя (пока что)
|
||||||
answers: string[]
|
answers: string[];
|
||||||
}[]
|
}[];
|
||||||
}
|
}
|
||||||
export interface QuestionBranchingRule {
|
|
||||||
|
|
||||||
children: string[],
|
export interface QuestionBranchingRule {
|
||||||
|
children: string[];
|
||||||
//список условий
|
//список условий
|
||||||
main: QuestionBranchingRuleMain[];
|
main: QuestionBranchingRuleMain[];
|
||||||
parentId: string | null | "root";
|
parentId: string | null | "root";
|
||||||
@ -85,7 +85,6 @@ export interface QuizQuestionBase {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type AnyTypedQuizQuestion =
|
export type AnyTypedQuizQuestion =
|
||||||
| QuizQuestionVariant
|
| QuizQuestionVariant
|
||||||
| QuizQuestionImages
|
| QuizQuestionImages
|
||||||
@ -100,7 +99,7 @@ export type AnyTypedQuizQuestion =
|
|||||||
| QuizQuestionRating
|
| QuizQuestionRating
|
||||||
| QuizQuestionResult;
|
| QuizQuestionResult;
|
||||||
|
|
||||||
|
export type RealTypedQuizQuestion = Exclude<AnyTypedQuizQuestion, QuizQuestionResult>;
|
||||||
|
|
||||||
type FilterQuestionsWithVariants<T> = T extends {
|
type FilterQuestionsWithVariants<T> = T extends {
|
||||||
content: { variants: QuestionVariant[]; };
|
content: { variants: QuestionVariant[]; };
|
||||||
@ -109,18 +108,19 @@ type FilterQuestionsWithVariants<T> = T extends {
|
|||||||
export type QuizQuestionsWithVariants = FilterQuestionsWithVariants<AnyTypedQuizQuestion>;
|
export type QuizQuestionsWithVariants = FilterQuestionsWithVariants<AnyTypedQuizQuestion>;
|
||||||
|
|
||||||
|
|
||||||
export const createBranchingRuleMain: (targetId:string, parentId:string) => QuestionBranchingRuleMain = (targetId, parentId) => ({
|
export const createBranchingRuleMain: (targetId: string, parentId: string) => QuestionBranchingRuleMain = (targetId, parentId) => ({
|
||||||
next: targetId,
|
next: targetId,
|
||||||
or: false,
|
or: false,
|
||||||
rules: [{
|
rules: [{
|
||||||
question: parentId,
|
question: parentId,
|
||||||
answers: [] as string[],
|
answers: [] as string[],
|
||||||
}]
|
}]
|
||||||
})
|
});
|
||||||
|
|
||||||
export const createQuestionVariant: () => QuestionVariant = () => ({
|
export const createQuestionVariant: () => QuestionVariant = () => ({
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
answer: "",
|
answer: "",
|
||||||
extendedText: "",
|
extendedText: "",
|
||||||
hints: "",
|
hints: "",
|
||||||
originalImageUrl: "",
|
originalImageUrl: "",
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,8 @@ export type QuizType = "quiz" | "form";
|
|||||||
|
|
||||||
export type QuizResultsType = true | null;
|
export type QuizResultsType = true | null;
|
||||||
|
|
||||||
|
export type QuizStep = "startpage" | "question" | "contactform";
|
||||||
|
|
||||||
export type QuizTheme =
|
export type QuizTheme =
|
||||||
| "StandardTheme"
|
| "StandardTheme"
|
||||||
| "StandardDarkTheme"
|
| "StandardDarkTheme"
|
||||||
@ -32,7 +34,6 @@ export type FCField = {
|
|||||||
export type QuizSettings = {
|
export type QuizSettings = {
|
||||||
questions: AnyTypedQuizQuestion[];
|
questions: AnyTypedQuizQuestion[];
|
||||||
settings: {
|
settings: {
|
||||||
qid: string;
|
|
||||||
fp: boolean;
|
fp: boolean;
|
||||||
rep: boolean;
|
rep: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
|
|
||||||
export const ApologyPage = ({message}:{message: string}) => {
|
type Props = {
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApologyPage = ({ message }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
height: "100vh"
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: "center"
|
textAlign: "center"
|
||||||
}}
|
}}
|
||||||
>{message || "что-то пошло не так"}</Typography>
|
>{message || "Что-то пошло не так"}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -11,69 +11,25 @@ import { FC, useRef, useState } from "react";
|
|||||||
import { sendFC } from "@api/quizRelase";
|
import { sendFC } from "@api/quizRelase";
|
||||||
import { NameplateLogo } from "@icons/NameplateLogo";
|
import { NameplateLogo } from "@icons/NameplateLogo";
|
||||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||||
|
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useQuizId } from "../../contexts/QuizIdContext";
|
||||||
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
||||||
import { ApologyPage } from "./ApologyPage";
|
|
||||||
import { checkEmptyData } from "./tools/checkEmptyData";
|
|
||||||
|
|
||||||
|
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
||||||
const EMAIL_REGEXP = /^(([^<>()[\].,:\s@"]+(\.[^<>()[\].,:\s@"]+)*)|(".+"))@(([^<>()[\].,:\s@"]+\.)+[^<>()[\].,:\s@"]{2,})$/iu;
|
const EMAIL_REGEXP = /^(([^<>()[\].,:\s@"]+(\.[^<>()[\].,:\s@"]+)*)|(".+"))@(([^<>()[\].,:\s@"]+\.)+[^<>()[\].,:\s@"]{2,})$/iu;
|
||||||
|
|
||||||
type ContactFormProps = {
|
type Props = {
|
||||||
currentQuestion: any;
|
currentQuestion: AnyTypedQuizQuestion;
|
||||||
showResultForm: boolean;
|
onShowResult: () => void;
|
||||||
setShowContactForm: (show: boolean) => void;
|
|
||||||
setShowResultForm: (show: boolean) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const icons = [
|
export const ContactForm = ({ currentQuestion, onShowResult }: Props) => {
|
||||||
{
|
|
||||||
type: "name",
|
|
||||||
icon: NameIcon,
|
|
||||||
defaultText: "Введите имя",
|
|
||||||
defaultTitle: "имя",
|
|
||||||
backendName: "name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "email",
|
|
||||||
icon: EmailIcon,
|
|
||||||
defaultText: "Введите Email",
|
|
||||||
defaultTitle: "Email",
|
|
||||||
backendName: "email",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "phone",
|
|
||||||
icon: PhoneIcon,
|
|
||||||
defaultText: "Введите номер телефона",
|
|
||||||
defaultTitle: "номер телефона",
|
|
||||||
backendName: "phone",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
icon: TextIcon,
|
|
||||||
defaultText: "Введите фамилию",
|
|
||||||
defaultTitle: "фамилию",
|
|
||||||
backendName: "adress",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "address",
|
|
||||||
icon: AddressIcon,
|
|
||||||
defaultText: "Введите адрес",
|
|
||||||
defaultTitle: "адрес",
|
|
||||||
backendName: "adress",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ContactForm = ({
|
|
||||||
currentQuestion,
|
|
||||||
showResultForm,
|
|
||||||
setShowContactForm,
|
|
||||||
setShowResultForm,
|
|
||||||
}: ContactFormProps) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const qid = useQuizId();
|
||||||
const { settings, questions } = useQuizData();
|
const { settings, questions } = useQuizData();
|
||||||
|
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
@ -87,78 +43,103 @@ export const ContactForm = ({
|
|||||||
const [fire, setFire] = useState(false);
|
const [fire, setFire] = useState(false);
|
||||||
const isMobile = useRootContainerSize() < 850;
|
const isMobile = useRootContainerSize() < 850;
|
||||||
|
|
||||||
const followNextForm = () => {
|
const resultQuestion = currentQuestion.type === "result"
|
||||||
setShowContactForm(false);
|
? currentQuestion
|
||||||
setShowResultForm(true);
|
: questions.find((question): question is QuizQuestionResult => {
|
||||||
};
|
if (settings?.cfg.haveRoot) {
|
||||||
|
return (
|
||||||
|
question.type === "result" &&
|
||||||
|
question.content.rule.parentId === currentQuestion.content.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
question.type === "result" && question.content.rule.parentId === "line"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//@ts-ignore
|
if (!resultQuestion) throw new Error("Result question not found");
|
||||||
const resultQuestion: QuizQuestionResult = questions.find((question) => {
|
|
||||||
if (settings?.cfg.haveRoot) {
|
|
||||||
//ветвимся
|
|
||||||
return (
|
|
||||||
question.type === "result" &&
|
|
||||||
//@ts-ignore
|
|
||||||
question.content.rule.parentId === currentQuestion.content.id
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// не ветвимся
|
|
||||||
return (
|
|
||||||
question.type === "result" && question.content.rule.parentId === "line"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const inputHC = async () => {
|
const inputHC = async () => {
|
||||||
//@ts-ignore
|
const FC = settings.cfg.formContact.fields || settings.cfg.formContact;
|
||||||
const FC = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
const body = {} as any;
|
||||||
const body = {};
|
|
||||||
//@ts-ignore
|
|
||||||
if (name.length > 0) body.name = name;
|
if (name.length > 0) body.name = name;
|
||||||
//@ts-ignore
|
|
||||||
if (email.length > 0) body.email = email;
|
if (email.length > 0) body.email = email;
|
||||||
//@ts-ignore
|
|
||||||
if (phone.length > 0) body.phone = phone;
|
if (phone.length > 0) body.phone = phone;
|
||||||
//@ts-ignore
|
|
||||||
if (adress.length > 0) body.address = adress;
|
if (adress.length > 0) body.address = adress;
|
||||||
//@ts-ignore
|
|
||||||
if (text.length > 0) body.customs = { [FC.text.text || "Фамилия"]: text };
|
if (text.length > 0) body.customs = { [FC.text.text || "Фамилия"]: text };
|
||||||
|
|
||||||
if (Object.keys(body).length > 0) {
|
if (Object.keys(body).length > 0) {
|
||||||
try {
|
try {
|
||||||
await sendFC({
|
await sendFC({
|
||||||
questionId: resultQuestion?.id,
|
questionId: currentQuestion.id,
|
||||||
body: body,
|
body: body,
|
||||||
qid: settings.qid,
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"sessions",
|
"sessions",
|
||||||
JSON.stringify({ ...sessions, [settings.qid]: new Date().getTime() })
|
JSON.stringify({ ...sessions, [qid]: new Date().getTime() })
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//@ts-ignore
|
|
||||||
let FCcopy: any = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
|
||||||
|
|
||||||
let filteredFC: any = {};
|
const FCcopy: any = settings.cfg.formContact.fields || settings.cfg.formContact;
|
||||||
for (let i in FCcopy) {
|
|
||||||
let field = FCcopy[i];
|
const filteredFC: any = {};
|
||||||
|
for (const i in FCcopy) {
|
||||||
|
const field = FCcopy[i];
|
||||||
console.log(filteredFC);
|
console.log(filteredFC);
|
||||||
if (field.used) {
|
if (field.used) {
|
||||||
filteredFC[i] = field;
|
filteredFC[i] = field;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let isWide = Object.keys(filteredFC).length > 2;
|
const isWide = Object.keys(filteredFC).length > 2;
|
||||||
|
|
||||||
if (!resultQuestion)
|
async function handleShowResultsClick() {
|
||||||
return (
|
//@ts-ignore
|
||||||
<ApologyPage message="не получилось найти результат для этой ветки :(" />
|
const FC: any = settings.cfg.formContact.fields || settings.cfg.formContact;
|
||||||
);
|
if (FC["email"].used !== EMAIL_REGEXP.test(email)) {
|
||||||
|
return enqueueSnackbar("введена некорректная почта");
|
||||||
|
}
|
||||||
|
|
||||||
|
//почта валидна
|
||||||
|
setFire(true);
|
||||||
|
|
||||||
|
if (fireOnce.current) {
|
||||||
|
if (
|
||||||
|
name.length === 0
|
||||||
|
&& email.length === 0
|
||||||
|
&& phone.length === 0
|
||||||
|
&& text.length === 0
|
||||||
|
&& adress.length === 0
|
||||||
|
) return enqueueSnackbar("Пожалуйста, заполните поля");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await inputHC();
|
||||||
|
fireOnce.current = false;
|
||||||
|
const sessions: any = JSON.parse(
|
||||||
|
localStorage.getItem("sessions") || "{}"
|
||||||
|
);
|
||||||
|
sessions[qid] = Date.now();
|
||||||
|
localStorage.setItem(
|
||||||
|
"sessions",
|
||||||
|
JSON.stringify(sessions)
|
||||||
|
);
|
||||||
|
enqueueSnackbar("Данные успешно отправлены");
|
||||||
|
} catch (e) {
|
||||||
|
enqueueSnackbar("повторите попытку позже");
|
||||||
|
}
|
||||||
|
|
||||||
|
onShowResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
setFire(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -167,7 +148,7 @@ export const ContactForm = ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
height: "100vh",
|
height: "100%",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
"&::-webkit-scrollbar": {
|
"&::-webkit-scrollbar": {
|
||||||
width: "0",
|
width: "0",
|
||||||
@ -204,10 +185,10 @@ export const ContactForm = ({
|
|||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings?.cfg.formContact.title ||
|
{settings.cfg.formContact.title ||
|
||||||
"Заполните форму, чтобы получить результаты теста"}
|
"Заполните форму, чтобы получить результаты теста"}
|
||||||
</Typography>
|
</Typography>
|
||||||
{settings?.cfg.formContact.desc && (
|
{settings.cfg.formContact.desc && (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
@ -216,7 +197,7 @@ export const ContactForm = ({
|
|||||||
fontSize: "18px",
|
fontSize: "18px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings?.cfg.formContact.desc}
|
{settings.cfg.formContact.desc}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@ -254,64 +235,13 @@ export const ContactForm = ({
|
|||||||
|
|
||||||
{
|
{
|
||||||
// resultQuestion &&
|
// resultQuestion &&
|
||||||
// settings?.cfg.resultInfo.when === "after" &&
|
// settings.cfg.resultInfo.when === "after" &&
|
||||||
<Button
|
<Button
|
||||||
disabled={!(ready && !fire)}
|
disabled={!(ready && !fire)}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={async () => {
|
onClick={handleShowResultsClick}
|
||||||
//@ts-ignore
|
|
||||||
const FC: any = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
|
||||||
if (FC["email"].used === EMAIL_REGEXP.test(email)) {
|
|
||||||
//почта валидна
|
|
||||||
setFire(true);
|
|
||||||
|
|
||||||
if (fireOnce.current) {
|
|
||||||
if (
|
|
||||||
name.length > 0 ||
|
|
||||||
email.length > 0 ||
|
|
||||||
phone.length > 0 ||
|
|
||||||
text.length > 0 ||
|
|
||||||
adress.length > 0
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
await inputHC();
|
|
||||||
fireOnce.current = false;
|
|
||||||
const QID =
|
|
||||||
process.env.NODE_ENV === "production"
|
|
||||||
? window.location.pathname.replace(/\//g, "")
|
|
||||||
: "ef836ff8-35b1-4031-9acf-af5766bac2b2";
|
|
||||||
const sessions: any = JSON.parse(
|
|
||||||
localStorage.getItem("sessions") || "{}"
|
|
||||||
);
|
|
||||||
sessions[QID] = Date.now();
|
|
||||||
localStorage.setItem(
|
|
||||||
"sessions",
|
|
||||||
JSON.stringify(sessions)
|
|
||||||
);
|
|
||||||
enqueueSnackbar("Данные успешно отправлены");
|
|
||||||
} catch (e) {
|
|
||||||
enqueueSnackbar("повторите попытку позже");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
settings?.cfg.resultInfo.showResultForm === "after" &&
|
|
||||||
!checkEmptyData({ resultData: resultQuestion })
|
|
||||||
) {
|
|
||||||
setShowContactForm(false);
|
|
||||||
setShowResultForm(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar("Пожалуйста, заполните поля");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setFire(false);
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar("введена некорректная почта");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{settings?.cfg.formContact?.button || "Получить результаты"}
|
{settings.cfg.formContact?.button || "Получить результаты"}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,7 +321,7 @@ const Inputs = ({
|
|||||||
const { settings } = useQuizData();
|
const { settings } = useQuizData();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const FC = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
const FC = settings.cfg.formContact.fields || settings.cfg.formContact;
|
||||||
|
|
||||||
if (!FC) return null;
|
if (!FC) return null;
|
||||||
|
|
||||||
|
@ -1,228 +1,17 @@
|
|||||||
import { Box, Button, Typography, useTheme } from "@mui/material";
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared";
|
|
||||||
|
|
||||||
import { checkEmptyData } from "./tools/checkEmptyData";
|
|
||||||
|
|
||||||
import type { QuizQuestionResult } from "@model/questionTypes/result";
|
|
||||||
import { useQuizViewStore } from "@stores/quizView/store";
|
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
|
||||||
|
|
||||||
type FooterProps = {
|
type FooterProps = {
|
||||||
setCurrentQuestion: (step: AnyTypedQuizQuestion) => void;
|
stepNumber: number | null;
|
||||||
question: AnyTypedQuizQuestion;
|
nextButton: ReactNode;
|
||||||
setShowContactForm: (show: boolean) => void;
|
prevButton: ReactNode;
|
||||||
setShowResultForm: (show: boolean) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setShowResultForm }: FooterProps) => {
|
export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { questions } = useQuizData();
|
||||||
const { settings, questions } = useQuizData();
|
|
||||||
const answers = useQuizViewStore(state => state.answers);
|
|
||||||
|
|
||||||
const [stepNumber, setStepNumber] = useState(1);
|
|
||||||
|
|
||||||
const isMobileMini = useRootContainerSize() < 382;
|
|
||||||
const isLinear = !questions.some(({ content }) => content.rule.parentId === "root");
|
|
||||||
|
|
||||||
const getNextQuestionId = useCallback(() => {
|
|
||||||
console.log("Смотрим какой вопрос будет дальше. Что у нас сегодня вкусненького? Щя покажу от какого вопроса мы ищем следующий шаг");
|
|
||||||
console.log(question);
|
|
||||||
console.log("От вот этого /|");
|
|
||||||
let readyBeNextQuestion = "";
|
|
||||||
|
|
||||||
//вопрос обязателен, анализируем ответ и условия ветвления
|
|
||||||
if (answers.length) {
|
|
||||||
const answer = answers.find(({ questionId }) => questionId === question.id);
|
|
||||||
|
|
||||||
|
|
||||||
(question as QuizQuestionBase).content.rule.main.forEach(({ next, rules }) => {
|
|
||||||
const longerArray = Math.max(
|
|
||||||
rules[0].answers.length,
|
|
||||||
answer?.answer && Array.isArray(answer?.answer) ? answer?.answer.length : [answer?.answer].length
|
|
||||||
);
|
|
||||||
|
|
||||||
for (
|
|
||||||
let i = 0;
|
|
||||||
i < longerArray;
|
|
||||||
i++ // Цикл по всем элементам бОльшего массива
|
|
||||||
) {
|
|
||||||
if (Array.isArray(answer?.answer)) {
|
|
||||||
if (answer?.answer.find((item) => String(item === rules[0].answers[i]))) {
|
|
||||||
readyBeNextQuestion = next; // Если хоть один элемент отличается, массивы не равны
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (String(rules[0].answers[i]) === answer?.answer) {
|
|
||||||
readyBeNextQuestion = next; // Если хоть один элемент отличается, массивы не равны
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (readyBeNextQuestion) return readyBeNextQuestion;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!question.required) {//вопрос не обязателен и не нашли совпадений между ответами и условиями ветвления
|
|
||||||
console.log("вопрос не обязателен ищем дальше");
|
|
||||||
const defaultQ = question.content.rule.default;
|
|
||||||
if (defaultQ.length > 1 && 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', q.content.rule.parentId);
|
|
||||||
console.log('question.content.id', question.content.id);
|
|
||||||
return q.type === "result" && q.content.rule.parentId === question.content.id;
|
|
||||||
})?.id;
|
|
||||||
|
|
||||||
}, [answers, questions, question]);
|
|
||||||
|
|
||||||
const isPreviousButtonDisabled = useMemo(() => {
|
|
||||||
// Логика для аргумента disabled у кнопки "Назад"
|
|
||||||
if (isLinear) {
|
|
||||||
const questionIndex = questions.findIndex(({ id }) => id === question.id);
|
|
||||||
|
|
||||||
const previousQuestion = questions[questionIndex - 1];
|
|
||||||
|
|
||||||
return previousQuestion ? false : true;
|
|
||||||
} else {
|
|
||||||
return question?.content.rule.parentId === "root" ? true : false;
|
|
||||||
}
|
|
||||||
}, [questions, isLinear, question?.content.rule.parentId, question.id]);
|
|
||||||
|
|
||||||
const isNextButtonDisabled = useMemo(() => {
|
|
||||||
// Логика для аргумента disabled у кнопки "Далее"
|
|
||||||
const answer = answers.find(({ questionId }) => questionId === question.id);
|
|
||||||
|
|
||||||
if ("required" in question.content && question.content.required && answer) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("required" in question.content && question.content.required && !answer) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLinear) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextQuestionId = getNextQuestionId();
|
|
||||||
if (nextQuestionId) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
const questionId = question.content.rule.default;
|
|
||||||
const nextQuestion = questions.find(q => q.id === questionId || q.content.id === questionId) || null;
|
|
||||||
|
|
||||||
if (nextQuestion?.type) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [answers, getNextQuestionId, isLinear, question.content, question.id]);
|
|
||||||
|
|
||||||
const showResult = (nextQuestion: QuizQuestionResult) => {
|
|
||||||
if (!nextQuestion) return;
|
|
||||||
|
|
||||||
const isEmpty = checkEmptyData({ resultData: nextQuestion });
|
|
||||||
|
|
||||||
if (nextQuestion) {
|
|
||||||
if (nextQuestion && settings?.cfg.resultInfo.showResultForm === "before") {
|
|
||||||
if (isEmpty) {
|
|
||||||
setShowContactForm(true); //до+пустая = кидать на ФК
|
|
||||||
|
|
||||||
} else {
|
|
||||||
setShowResultForm(true); //до+заполнена = показать
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nextQuestion && settings?.cfg.resultInfo.showResultForm === "after") {
|
|
||||||
if (isEmpty) {
|
|
||||||
setShowContactForm(true); //после+пустая
|
|
||||||
|
|
||||||
} else {
|
|
||||||
setShowContactForm(true); //после+заполнена = показать ФК
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const followPreviousStep = () => {
|
|
||||||
if (isLinear) {
|
|
||||||
setStepNumber(q => q - 1);
|
|
||||||
|
|
||||||
const questionIndex = questions.findIndex(({ id }) => id === question.id);
|
|
||||||
|
|
||||||
const previousQuestion = questions[questionIndex - 1];
|
|
||||||
|
|
||||||
if (previousQuestion) {
|
|
||||||
setCurrentQuestion(previousQuestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (question?.content.rule.parentId !== "root") {
|
|
||||||
const questionId = question?.content.rule.parentId;
|
|
||||||
const parent = questions.find(q => q.id === questionId || q.content.id === questionId) || null;
|
|
||||||
if (parent?.type) {
|
|
||||||
setCurrentQuestion(parent);
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar("не могу получить предыдущий вопрос");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar("вы находитесь на первом вопросе");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const followNextStep = () => {
|
|
||||||
if (isLinear) {
|
|
||||||
setStepNumber(q => q + 1);
|
|
||||||
|
|
||||||
const questionIndex = questions.findIndex(({ id }) => id === question.id);
|
|
||||||
const nextQuestion = questions[questionIndex + 1];
|
|
||||||
|
|
||||||
if (nextQuestion && nextQuestion.type !== "result") {
|
|
||||||
setCurrentQuestion(nextQuestion);
|
|
||||||
} else {
|
|
||||||
//@ts-ignore
|
|
||||||
showResult(questions.find(q => q.content.rule.parentId === "line"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextQuestionId = getNextQuestionId();
|
|
||||||
|
|
||||||
if (nextQuestionId) {
|
|
||||||
const nextQuestion = questions.find(q => q.id === nextQuestionId || q.content.id === nextQuestionId) || null;
|
|
||||||
|
|
||||||
if (nextQuestion?.type && nextQuestion.type === "result") {
|
|
||||||
showResult(nextQuestion);
|
|
||||||
} else {
|
|
||||||
//@ts-ignore
|
|
||||||
setCurrentQuestion(nextQuestion);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
enqueueSnackbar("не могу получить последующий вопрос");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -250,7 +39,7 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh
|
|||||||
{/*):(*/}
|
{/*):(*/}
|
||||||
{/* <NameplateLogoFQDark style={{ fontSize: "34px", width:"200px", height:"auto" }} />*/}
|
{/* <NameplateLogoFQDark style={{ fontSize: "34px", width:"200px", height:"auto" }} />*/}
|
||||||
{/*)}*/}
|
{/*)}*/}
|
||||||
{isLinear &&
|
{stepNumber !== null &&
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -313,26 +102,8 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh
|
|||||||
{questions.length}
|
{questions.length}
|
||||||
</Typography> */}
|
</Typography> */}
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
{prevButton}
|
||||||
disabled={isPreviousButtonDisabled}
|
{nextButton}
|
||||||
variant="contained"
|
|
||||||
sx={{ fontSize: "16px", padding: "10px 15px", }}
|
|
||||||
onClick={followPreviousStep}
|
|
||||||
>
|
|
||||||
{isMobileMini ? (
|
|
||||||
"←"
|
|
||||||
) : (
|
|
||||||
"← Назад"
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
disabled={isNextButtonDisabled}
|
|
||||||
variant="contained"
|
|
||||||
sx={{ fontSize: "16px", padding: "10px 15px" }}
|
|
||||||
onClick={followNextStep}
|
|
||||||
>
|
|
||||||
Далее →
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import { Box, useTheme } from "@mui/material";
|
import { Box, useTheme } from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
import { ContactForm } from "./ContactForm";
|
|
||||||
import { Footer } from "./Footer";
|
import { Footer } from "./Footer";
|
||||||
import { ResultForm } from "./ResultForm";
|
|
||||||
import { Date } from "./questions/Date";
|
import { Date } from "./questions/Date";
|
||||||
import { Emoji } from "./questions/Emoji";
|
import { Emoji } from "./questions/Emoji";
|
||||||
import { File } from "./questions/File";
|
import { File } from "./questions/File";
|
||||||
@ -16,108 +13,67 @@ import { Text } from "./questions/Text";
|
|||||||
import { Variant } from "./questions/Variant";
|
import { Variant } from "./questions/Variant";
|
||||||
import { Varimg } from "./questions/Varimg";
|
import { Varimg } from "./questions/Varimg";
|
||||||
|
|
||||||
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
|
import type { RealTypedQuizQuestion } from "../../model/questionTypes/shared";
|
||||||
|
|
||||||
import { NameplateLogoFQ } from "@icons/NameplateLogoFQ";
|
import { NameplateLogoFQ } from "@icons/NameplateLogoFQ";
|
||||||
import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark";
|
import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark";
|
||||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
import { notReachable } from "@utils/notReachable";
|
import { notReachable } from "@utils/notReachable";
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
import { ReactNode } from "react";
|
||||||
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
||||||
|
|
||||||
export const Question = () => {
|
type Props = {
|
||||||
|
currentQuestion: RealTypedQuizQuestion;
|
||||||
|
currentQuestionStepNumber: number | null;
|
||||||
|
nextButton: ReactNode;
|
||||||
|
prevButton: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Question = ({
|
||||||
|
currentQuestion,
|
||||||
|
currentQuestionStepNumber,
|
||||||
|
nextButton,
|
||||||
|
prevButton,
|
||||||
|
}: Props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { settings, questions } = useQuizData();
|
const { settings } = useQuizData();
|
||||||
const [currentQuestion, setCurrentQuestion] = useState<AnyTypedQuizQuestion>();
|
|
||||||
const [showContactForm, setShowContactForm] = useState<boolean>(false);
|
|
||||||
const [showResultForm, setShowResultForm] = useState<boolean>(false);
|
|
||||||
const isMobile = useRootContainerSize() < 650;
|
const isMobile = useRootContainerSize() < 650;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (settings?.cfg.haveRoot) {//ветвимся
|
|
||||||
const questionId = settings?.cfg.haveRoot || "";
|
|
||||||
const nextQuestion = questions.find(q => q.id === questionId || q.content.id === questionId) || null;
|
|
||||||
|
|
||||||
if (nextQuestion?.type) {
|
|
||||||
setCurrentQuestion(nextQuestion);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {//идём прямо
|
|
||||||
setCurrentQuestion(questions[0]);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!currentQuestion || currentQuestion.type === "result") return "не смог отобразить вопрос";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box sx={{
|
||||||
sx={{
|
backgroundColor: theme.palette.background.default,
|
||||||
backgroundColor: theme.palette.background.default,
|
height: isMobile ? undefined : "100%"
|
||||||
height: isMobile ? undefined : "100vh"
|
}}>
|
||||||
}}
|
<Box sx={{
|
||||||
>
|
height: "calc(100% - 75px)",
|
||||||
{!showContactForm && !showResultForm && (
|
width: "100%",
|
||||||
<Box
|
maxWidth: "1440px",
|
||||||
sx={{
|
padding: "40px 25px 20px",
|
||||||
height: "calc(100vh - 75px)",
|
margin: "0 auto",
|
||||||
width: "100%",
|
overflow: "auto",
|
||||||
maxWidth: "1440px",
|
display: "flex",
|
||||||
padding: "40px 25px 20px",
|
flexDirection: "column",
|
||||||
margin: "0 auto",
|
justifyContent: "space-between"
|
||||||
overflow: "auto",
|
}}>
|
||||||
display: "flex",
|
<QuestionByType question={currentQuestion} />
|
||||||
flexDirection: "column",
|
{quizThemes[settings.cfg.theme].isLight ? (
|
||||||
justifyContent: "space-between"
|
<NameplateLogoFQ style={{ fontSize: "34px", width: "200px", height: "auto" }} />
|
||||||
}}
|
) : (
|
||||||
>
|
<NameplateLogoFQDark style={{ fontSize: "34px", width: "200px", height: "auto" }} />
|
||||||
<QuestionByType question={currentQuestion} />
|
)}
|
||||||
{quizThemes[settings.cfg.theme].isLight ? (
|
</Box>
|
||||||
<NameplateLogoFQ style={{ fontSize: "34px", width: "200px", height: "auto" }} />
|
<Footer
|
||||||
) : (
|
stepNumber={currentQuestionStepNumber}
|
||||||
<NameplateLogoFQDark style={{ fontSize: "34px", width: "200px", height: "auto" }} />
|
prevButton={prevButton}
|
||||||
)}
|
nextButton={nextButton}
|
||||||
</Box>
|
/>
|
||||||
)}
|
|
||||||
{showResultForm && settings?.cfg.resultInfo.showResultForm === "before" && (
|
|
||||||
<ResultForm
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
showContactForm={showContactForm}
|
|
||||||
setShowContactForm={setShowContactForm}
|
|
||||||
setShowResultForm={setShowResultForm}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{showContactForm && (
|
|
||||||
<ContactForm
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
showResultForm={showResultForm}
|
|
||||||
setShowContactForm={setShowContactForm}
|
|
||||||
setShowResultForm={setShowResultForm}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{showResultForm && settings?.cfg.resultInfo.showResultForm === "after" && (
|
|
||||||
<ResultForm
|
|
||||||
currentQuestion={currentQuestion}
|
|
||||||
showContactForm={showContactForm}
|
|
||||||
setShowContactForm={setShowContactForm}
|
|
||||||
setShowResultForm={setShowResultForm}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!showContactForm && !showResultForm && (
|
|
||||||
<Footer
|
|
||||||
question={currentQuestion}
|
|
||||||
setCurrentQuestion={setCurrentQuestion}
|
|
||||||
setShowContactForm={setShowContactForm}
|
|
||||||
setShowResultForm={setShowResultForm}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function QuestionByType({ question }: {
|
function QuestionByType({ question }: {
|
||||||
question: Exclude<AnyTypedQuizQuestion, QuizQuestionResult>;
|
question: RealTypedQuizQuestion;
|
||||||
}) {
|
}) {
|
||||||
switch (question.type) {
|
switch (question.type) {
|
||||||
case "variant": return <Variant currentQuestion={question} />;
|
case "variant": return <Variant currentQuestion={question} />;
|
||||||
@ -131,6 +87,6 @@ function QuestionByType({ question }: {
|
|||||||
case "file": return <File currentQuestion={question} />;
|
case "file": return <File currentQuestion={question} />;
|
||||||
case "page": return <Page currentQuestion={question} />;
|
case "page": return <Page currentQuestion={question} />;
|
||||||
case "rating": return <Rating currentQuestion={question} />;
|
case "rating": return <Rating currentQuestion={question} />;
|
||||||
default: return notReachable(question);
|
default: notReachable(question);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,62 +8,21 @@ import {
|
|||||||
import { NameplateLogo } from "@icons/NameplateLogo";
|
import { NameplateLogo } from "@icons/NameplateLogo";
|
||||||
import YoutubeEmbedIframe from "./tools/YoutubeEmbedIframe";
|
import YoutubeEmbedIframe from "./tools/YoutubeEmbedIframe";
|
||||||
|
|
||||||
|
import { setCurrentQuizStep } from "@stores/quizView/store";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { useCallback, useEffect, useMemo } from "react";
|
|
||||||
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
||||||
import type { QuizQuestionResult } from "../../model/questionTypes/result";
|
import type { QuizQuestionResult } from "../../model/questionTypes/result";
|
||||||
|
|
||||||
|
|
||||||
type ResultFormProps = {
|
type ResultFormProps = {
|
||||||
currentQuestion: any;
|
resultQuestion: QuizQuestionResult;
|
||||||
showContactForm: boolean;
|
|
||||||
setShowContactForm: (show: boolean) => void;
|
|
||||||
setShowResultForm: (show: boolean) => void;
|
|
||||||
};
|
};
|
||||||
export const ResultForm = ({
|
|
||||||
currentQuestion,
|
export const ResultForm = ({ resultQuestion }: ResultFormProps) => {
|
||||||
showContactForm,
|
|
||||||
setShowContactForm,
|
|
||||||
setShowResultForm,
|
|
||||||
}: ResultFormProps) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useRootContainerSize() < 650;
|
const isMobile = useRootContainerSize() < 650;
|
||||||
const { settings, questions } = useQuizData();
|
const { settings } = useQuizData();
|
||||||
|
|
||||||
const resultQuestion = useMemo(() => {
|
|
||||||
if (settings?.cfg.haveRoot) {
|
|
||||||
//ищём для ветвления
|
|
||||||
return (questions.find(
|
|
||||||
(question): question is QuizQuestionResult =>
|
|
||||||
question.type === "result" &&
|
|
||||||
question.content.rule.parentId === currentQuestion.content.id
|
|
||||||
) || questions.find(
|
|
||||||
(question): question is QuizQuestionResult =>
|
|
||||||
question.type === "result" &&
|
|
||||||
question.content.rule.parentId === "line"
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
return questions.find(
|
|
||||||
(question): question is QuizQuestionResult =>
|
|
||||||
question.type === "result" &&
|
|
||||||
question.content.rule.parentId === "line"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [currentQuestion.content.id, questions, settings?.cfg.haveRoot]);
|
|
||||||
|
|
||||||
const followNextForm = useCallback(() => {
|
|
||||||
setShowResultForm(false);
|
|
||||||
setShowContactForm(true);
|
|
||||||
}, [setShowContactForm, setShowResultForm]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!resultQuestion) {
|
|
||||||
followNextForm();
|
|
||||||
}
|
|
||||||
}, [followNextForm, resultQuestion]);
|
|
||||||
|
|
||||||
if (!resultQuestion) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -72,7 +31,7 @@ export const ResultForm = ({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
height: "100vh",
|
height: "100%",
|
||||||
width: "100vw",
|
width: "100vw",
|
||||||
pt: "28px",
|
pt: "28px",
|
||||||
overflow: "auto",
|
overflow: "auto",
|
||||||
@ -197,9 +156,9 @@ export const ResultForm = ({
|
|||||||
p: "20px",
|
p: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{settings?.cfg.resultInfo.showResultForm === "before" && (
|
{settings.cfg.resultInfo.showResultForm === "before" && (
|
||||||
<Button
|
<Button
|
||||||
onClick={followNextForm}
|
onClick={() => setCurrentQuizStep("contactform")}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{
|
sx={{
|
||||||
p: "10px 20px",
|
p: "10px 20px",
|
||||||
@ -210,7 +169,7 @@ export const ResultForm = ({
|
|||||||
{resultQuestion.content.hint.text || "Узнать подробнее"}
|
{resultQuestion.content.hint.text || "Узнать подробнее"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{settings?.cfg.resultInfo.showResultForm === "after" &&
|
{settings.cfg.resultInfo.showResultForm === "after" &&
|
||||||
resultQuestion.content.redirect && (
|
resultQuestion.content.redirect && (
|
||||||
<Button
|
<Button
|
||||||
href={resultQuestion.content.redirect}
|
href={resultQuestion.content.redirect}
|
||||||
|
@ -8,13 +8,10 @@ import { QuizStartpageAlignType, QuizStartpageType } from "@model/settingsData";
|
|||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
||||||
|
import { setCurrentQuizStep } from "@stores/quizView/store";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
export const StartPageViewPublication = () => {
|
||||||
setVisualStartPage: (a: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { settings } = useQuizData();
|
const { settings } = useQuizData();
|
||||||
const { isMobileDevice } = useUADevice();
|
const { isMobileDevice } = useUADevice();
|
||||||
@ -52,9 +49,7 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
|
|||||||
height:
|
height:
|
||||||
settings.cfg.startpageType === "centered"
|
settings.cfg.startpageType === "centered"
|
||||||
? "275px"
|
? "275px"
|
||||||
: settings.cfg.startpageType === "expanded"
|
: "100%",
|
||||||
? "100vh"
|
|
||||||
: "100%",
|
|
||||||
borderRadius: settings.cfg.startpageType === "centered" ? "10px" : "0",
|
borderRadius: settings.cfg.startpageType === "centered" ? "10px" : "0",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
"& iframe": {
|
"& iframe": {
|
||||||
@ -76,8 +71,8 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
|
|||||||
<Paper
|
<Paper
|
||||||
className="settings-preview-draghandle"
|
className="settings-preview-draghandle"
|
||||||
sx={{
|
sx={{
|
||||||
height: "100vh",
|
height: "100%",
|
||||||
width: "100vw",
|
width: "100%",
|
||||||
background:
|
background:
|
||||||
settings.cfg.startpageType === "expanded" && !isMobile
|
settings.cfg.startpageType === "expanded" && !isMobile
|
||||||
? settings.cfg.startpage.position === "left"
|
? settings.cfg.startpage.position === "left"
|
||||||
@ -180,7 +175,7 @@ export const StartPageViewPublication = ({ setVisualStartPage }: Props) => {
|
|||||||
padding: "10px 15px",
|
padding: "10px 15px",
|
||||||
width: settings.cfg.startpageType === "standard" ? "100%" : "auto",
|
width: settings.cfg.startpageType === "standard" ? "100%" : "auto",
|
||||||
}}
|
}}
|
||||||
onClick={() => setVisualStartPage(false)}
|
onClick={() => setCurrentQuizStep("question")}
|
||||||
>
|
>
|
||||||
{settings.cfg.startpage.button.trim() ? settings.cfg.startpage.button : "Пройти тест"}
|
{settings.cfg.startpage.button.trim() ? settings.cfg.startpage.button : "Пройти тест"}
|
||||||
</Button>
|
</Button>
|
||||||
@ -277,7 +272,7 @@ function QuizPreviewLayoutByType({
|
|||||||
flexDirection: "column-reverse",
|
flexDirection: "column-reverse",
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
height: "100vh",
|
height: "100%",
|
||||||
"&::-webkit-scrollbar": { width: 0 },
|
"&::-webkit-scrollbar": { width: 0 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -333,7 +328,7 @@ function QuizPreviewLayoutByType({
|
|||||||
flexDirection: alignType === "left" ? (isMobile ? "column-reverse" : "row") : "row-reverse",
|
flexDirection: alignType === "left" ? (isMobile ? "column-reverse" : "row") : "row-reverse",
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
justifyContent: isMobile ? "flex-end" : undefined,
|
justifyContent: isMobile ? "flex-end" : undefined,
|
||||||
height: "100vh",
|
height: "100%",
|
||||||
"&::-webkit-scrollbar": { width: 0 },
|
"&::-webkit-scrollbar": { width: 0 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,43 +1,99 @@
|
|||||||
import { Box, ThemeProvider } from "@mui/material";
|
import { Button, ThemeProvider } from "@mui/material";
|
||||||
|
import { useQuizViewStore } from "@stores/quizView/store";
|
||||||
|
import { useQuestionFlowControl } from "@utils/hooks/useQuestionFlowControl";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
|
import { notReachable } from "@utils/notReachable";
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { useEffect, useState } from "react";
|
import { ReactElement, useEffect } from "react";
|
||||||
import { ApologyPage } from "./ApologyPage";
|
import { useRootContainerSize } from "../../contexts/RootContainerWidthContext";
|
||||||
|
import { ContactForm } from "./ContactForm";
|
||||||
import { Question } from "./Question";
|
import { Question } from "./Question";
|
||||||
|
import { ResultForm } from "./ResultForm";
|
||||||
import { StartPageViewPublication } from "./StartPageViewPublication";
|
import { StartPageViewPublication } from "./StartPageViewPublication";
|
||||||
|
|
||||||
|
|
||||||
export const ViewPage = () => {
|
export default function ViewPublicationPage() {
|
||||||
const { settings, questions, recentlyCompleted } = useQuizData();
|
const { settings, recentlyCompleted } = useQuizData();
|
||||||
const [visualStartPage, setVisualStartPage] = useState<boolean>();
|
let currentQuizStep = useQuizViewStore(state => state.currentQuizStep);
|
||||||
|
const isMobileMini = useRootContainerSize() < 382;
|
||||||
|
const {
|
||||||
|
currentQuestion,
|
||||||
|
currentQuestionStepNumber,
|
||||||
|
isNextButtonDisabled,
|
||||||
|
isPreviousButtonDisabled,
|
||||||
|
moveToPrevQuestion,
|
||||||
|
moveToNextQuestion,
|
||||||
|
showResultAfterContactForm,
|
||||||
|
} = useQuestionFlowControl();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(function setFaviconAndTitle() {
|
||||||
const link = document.querySelector('link[rel="icon"]');
|
const link = document.querySelector('link[rel="icon"]');
|
||||||
if (link && settings.cfg.startpage.favIcon) {
|
if (link && settings.cfg.startpage.favIcon) {
|
||||||
link.setAttribute("href", settings?.cfg.startpage.favIcon);
|
link.setAttribute("href", settings.cfg.startpage.favIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.title = settings.name;
|
document.title = settings.name;
|
||||||
|
|
||||||
setVisualStartPage(!settings.cfg.noStartPage);
|
|
||||||
}, [settings]);
|
}, [settings]);
|
||||||
|
|
||||||
const questionsCount = questions.filter(({ type }) => type !== null && type !== "result").length;
|
let quizStepElement: ReactElement;
|
||||||
if (questionsCount === 0) return <ApologyPage message="Нет созданных вопросов" />;
|
|
||||||
|
if (recentlyCompleted) throw new Error("Quiz already completed");
|
||||||
|
if (currentQuizStep === "startpage" && settings.cfg.noStartPage) currentQuizStep = "question";
|
||||||
|
|
||||||
|
switch (currentQuizStep) {
|
||||||
|
case "startpage": {
|
||||||
|
quizStepElement = <StartPageViewPublication />;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "question": {
|
||||||
|
if (currentQuestion.type === "result") {
|
||||||
|
quizStepElement = <ResultForm resultQuestion={currentQuestion} />;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
quizStepElement = (
|
||||||
|
<Question
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
currentQuestionStepNumber={currentQuestionStepNumber}
|
||||||
|
prevButton={
|
||||||
|
<Button
|
||||||
|
disabled={isPreviousButtonDisabled}
|
||||||
|
variant="contained"
|
||||||
|
sx={{ fontSize: "16px", padding: "10px 15px" }}
|
||||||
|
onClick={moveToPrevQuestion}
|
||||||
|
>
|
||||||
|
{isMobileMini ? "←" : "← Назад"}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
nextButton={
|
||||||
|
<Button
|
||||||
|
disabled={isNextButtonDisabled}
|
||||||
|
variant="contained"
|
||||||
|
sx={{ fontSize: "16px", padding: "10px 15px" }}
|
||||||
|
onClick={moveToNextQuestion}
|
||||||
|
>
|
||||||
|
Далее →
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "contactform": {
|
||||||
|
quizStepElement = (
|
||||||
|
<ContactForm
|
||||||
|
currentQuestion={currentQuestion}
|
||||||
|
onShowResult={showResultAfterContactForm}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: notReachable(currentQuizStep);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={quizThemes[settings.cfg.theme || "StandardTheme"]}>
|
<ThemeProvider theme={quizThemes[settings.cfg.theme || "StandardTheme"].theme}>
|
||||||
{recentlyCompleted ? (
|
{quizStepElement}
|
||||||
<ApologyPage message="Вы уже прошли этот опрос" />
|
|
||||||
) : (
|
|
||||||
<Box>
|
|
||||||
{visualStartPage ? (
|
|
||||||
<StartPageViewPublication setVisualStartPage={setVisualStartPage} />
|
|
||||||
) : (
|
|
||||||
<Question />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
@ -11,6 +11,7 @@ import { sendAnswer } from "@api/quizRelase";
|
|||||||
|
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
|
|
||||||
type DateProps = {
|
type DateProps = {
|
||||||
currentQuestion: QuizQuestionDate;
|
currentQuestion: QuizQuestionDate;
|
||||||
@ -18,7 +19,7 @@ type DateProps = {
|
|||||||
|
|
||||||
export const Date = ({ currentQuestion }: DateProps) => {
|
export const Date = ({ currentQuestion }: DateProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const qid = useQuizId();
|
||||||
const { settings } = useQuizData();
|
const { settings } = useQuizData();
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
const answer = answers.find(
|
const answer = answers.find(
|
||||||
@ -61,7 +62,7 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: moment(date).format("YYYY.MM.DD"),
|
body: moment(date).format("YYYY.MM.DD"),
|
||||||
qid: settings.qid,
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(
|
updateAnswer(
|
||||||
|
@ -16,8 +16,8 @@ import RadioIcon from "@ui_kit/RadioIcon";
|
|||||||
import { sendAnswer } from "@api/quizRelase";
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
|
||||||
|
|
||||||
type EmojiProps = {
|
type EmojiProps = {
|
||||||
currentQuestion: QuizQuestionEmoji;
|
currentQuestion: QuizQuestionEmoji;
|
||||||
@ -25,8 +25,7 @@ type EmojiProps = {
|
|||||||
|
|
||||||
export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const qid = useQuizId();
|
||||||
const { settings } = useQuizData();
|
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
const { answer } =
|
const { answer } =
|
||||||
answers.find(
|
answers.find(
|
||||||
@ -110,7 +109,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: currentQuestion.content.variants[index].extendedText + " " + currentQuestion.content.variants[index].answer,
|
body: currentQuestion.content.variants[index].extendedText + " " + currentQuestion.content.variants[index].answer,
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(
|
updateAnswer(
|
||||||
@ -131,7 +130,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: "",
|
body: "",
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -14,10 +14,10 @@ import UploadIcon from "@icons/UploadIcon";
|
|||||||
import { sendAnswer, sendFile } from "@api/quizRelase";
|
import { sendAnswer, sendFile } from "@api/quizRelase";
|
||||||
import Info from "@icons/Info";
|
import Info from "@icons/Info";
|
||||||
import type { UploadFileType } from "@model/questionTypes/file";
|
import type { UploadFileType } from "@model/questionTypes/file";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import type { DragEvent } from "react";
|
import type { DragEvent } from "react";
|
||||||
import { useState, type ChangeEvent } from "react";
|
import { useState, type ChangeEvent } from "react";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
||||||
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
||||||
|
|
||||||
@ -117,9 +117,8 @@ const UPLOAD_FILE_DESCRIPTIONS_MAP: Record<
|
|||||||
|
|
||||||
export const File = ({ currentQuestion }: FileProps) => {
|
export const File = ({ currentQuestion }: FileProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { settings } = useQuizData();
|
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
|
const qid = useQuizId();
|
||||||
const [statusModal, setStatusModal] = useState<"errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "">("");
|
const [statusModal, setStatusModal] = useState<"errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "">("");
|
||||||
|
|
||||||
const answer = answers.find(
|
const answer = answers.find(
|
||||||
@ -148,16 +147,14 @@ export const File = ({ currentQuestion }: FileProps) => {
|
|||||||
file: file,
|
file: file,
|
||||||
name: file.name
|
name: file.name
|
||||||
},
|
},
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
//@ts-ignore
|
body: `https://storage.yandexcloud.net/squizanswer/${qid}/${currentQuestion.id}/${data.data.fileIDMap[currentQuestion.id]}`,
|
||||||
body: `https://storage.yandexcloud.net/squizanswer/${settings.qid}/${currentQuestion.id}/${data.data.fileIDMap[currentQuestion.id]}`,
|
qid,
|
||||||
//@ts-ignore
|
|
||||||
qid: settings.qid
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(
|
updateAnswer(
|
||||||
|
@ -12,8 +12,8 @@ import RadioCheck from "@ui_kit/RadioCheck";
|
|||||||
import RadioIcon from "@ui_kit/RadioIcon";
|
import RadioIcon from "@ui_kit/RadioIcon";
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
||||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ type ImagesProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Images = ({ currentQuestion }: ImagesProps) => {
|
export const Images = ({ currentQuestion }: ImagesProps) => {
|
||||||
const { settings } = useQuizData();
|
const qid = useQuizId();
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer;
|
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer;
|
||||||
@ -74,7 +74,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(
|
updateAnswer(
|
||||||
@ -94,7 +94,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: "",
|
body: "",
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -13,6 +13,7 @@ import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
|||||||
|
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
||||||
|
|
||||||
type NumberProps = {
|
type NumberProps = {
|
||||||
@ -20,6 +21,7 @@ type NumberProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Number = ({ currentQuestion }: NumberProps) => {
|
export const Number = ({ currentQuestion }: NumberProps) => {
|
||||||
|
const qid = useQuizId();
|
||||||
const { settings } = useQuizData();
|
const { settings } = useQuizData();
|
||||||
const [inputValue, setInputValue] = useState<string>("0");
|
const [inputValue, setInputValue] = useState<string>("0");
|
||||||
const [minRange, setMinRange] = useState<string>("0");
|
const [minRange, setMinRange] = useState<string>("0");
|
||||||
@ -36,8 +38,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: value,
|
body: value,
|
||||||
//@ts-ignore
|
qid,
|
||||||
qid: settings.qid,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, value);
|
updateAnswer(currentQuestion.id, value);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
import { useQuizViewStore, updateAnswer } from "@stores/quizView/store";
|
|
||||||
|
|
||||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||||
import YoutubeEmbedIframe from "../tools/YoutubeEmbedIframe";
|
import YoutubeEmbedIframe from "../tools/YoutubeEmbedIframe";
|
||||||
|
|
||||||
@ -11,8 +9,6 @@ type PageProps = {
|
|||||||
|
|
||||||
export const Page = ({ currentQuestion }: PageProps) => {
|
export const Page = ({ currentQuestion }: PageProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { answers } = useQuizViewStore();
|
|
||||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@ -46,7 +42,7 @@ export const Page = ({ currentQuestion }: PageProps) => {
|
|||||||
<YoutubeEmbedIframe
|
<YoutubeEmbedIframe
|
||||||
containerSX={{
|
containerSX={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "calc( 100vh - 270px)",
|
height: "calc(100% - 270px)",
|
||||||
maxHeight: "80vh",
|
maxHeight: "80vh",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
|
@ -16,8 +16,8 @@ import LikeIcon from "@icons/questionsPage/likeIcon";
|
|||||||
import TropfyIcon from "@icons/questionsPage/tropfyIcon";
|
import TropfyIcon from "@icons/questionsPage/tropfyIcon";
|
||||||
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
||||||
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ const buttonRatingForm = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const Rating = ({ currentQuestion }: RatingProps) => {
|
export const Rating = ({ currentQuestion }: RatingProps) => {
|
||||||
const { settings } = useQuizData();
|
const qid = useQuizId();
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useRootContainerSize() < 650;
|
const isMobile = useRootContainerSize() < 650;
|
||||||
@ -98,7 +98,7 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: String(value) + " из " + currentQuestion.content.steps,
|
body: String(value) + " из " + currentQuestion.content.steps,
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, String(value));
|
updateAnswer(currentQuestion.id, String(value));
|
||||||
|
@ -2,12 +2,12 @@ import { Box, Typography, useTheme } from "@mui/material";
|
|||||||
|
|
||||||
import { Select as SelectComponent } from "../tools//Select";
|
import { Select as SelectComponent } from "../tools//Select";
|
||||||
|
|
||||||
import { useQuizViewStore, updateAnswer, deleteAnswer } from "@stores/quizView/store";
|
import { deleteAnswer, updateAnswer, useQuizViewStore } from "@stores/quizView/store";
|
||||||
|
|
||||||
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
|
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
||||||
|
|
||||||
type SelectProps = {
|
type SelectProps = {
|
||||||
currentQuestion: QuizQuestionSelect;
|
currentQuestion: QuizQuestionSelect;
|
||||||
@ -15,8 +15,7 @@ type SelectProps = {
|
|||||||
|
|
||||||
export const Select = ({ currentQuestion }: SelectProps) => {
|
export const Select = ({ currentQuestion }: SelectProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const qid = useQuizId();
|
||||||
const { settings } = useQuizData();
|
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
const { answer } =
|
const { answer } =
|
||||||
answers.find(
|
answers.find(
|
||||||
@ -47,7 +46,7 @@ export const Select = ({ currentQuestion }: SelectProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: "",
|
body: "",
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -61,7 +60,7 @@ export const Select = ({ currentQuestion }: SelectProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: String(currentQuestion.content.variants[Number(value)].answer),
|
body: String(currentQuestion.content.variants[Number(value)].answer),
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, String(value));
|
updateAnswer(currentQuestion.id, String(value));
|
||||||
|
@ -2,13 +2,13 @@ import { Box, Typography, useTheme } from "@mui/material";
|
|||||||
|
|
||||||
import CustomTextField from "@ui_kit/CustomTextField";
|
import CustomTextField from "@ui_kit/CustomTextField";
|
||||||
|
|
||||||
import { useQuizViewStore, updateAnswer } from "@stores/quizView/store";
|
import { updateAnswer, useQuizViewStore } from "@stores/quizView/store";
|
||||||
|
|
||||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import { sendAnswer } from "@api/quizRelase";
|
import { sendAnswer } from "@api/quizRelase";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
|
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||||
|
|
||||||
type TextProps = {
|
type TextProps = {
|
||||||
currentQuestion: QuizQuestionText;
|
currentQuestion: QuizQuestionText;
|
||||||
@ -16,7 +16,7 @@ type TextProps = {
|
|||||||
|
|
||||||
export const Text = ({ currentQuestion }: TextProps) => {
|
export const Text = ({ currentQuestion }: TextProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { settings } = useQuizData();
|
const qid = useQuizId();
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export const Text = ({ currentQuestion }: TextProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: text,
|
body: text,
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import { enqueueSnackbar } from "notistack";
|
|||||||
import type { QuestionVariant } from "../../../model/questionTypes/shared";
|
import type { QuestionVariant } from "../../../model/questionTypes/shared";
|
||||||
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
|
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
|
||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
|
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||||
|
|
||||||
@ -135,8 +136,9 @@ const VariantItem = ({
|
|||||||
index,
|
index,
|
||||||
own = false,
|
own = false,
|
||||||
}: VariantItemProps) => {
|
}: VariantItemProps) => {
|
||||||
const { settings } = useQuizData();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { settings } = useQuizData();
|
||||||
|
const qid = useQuizId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
@ -186,13 +188,12 @@ const VariantItem = ({
|
|||||||
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
|
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: currentAnswer.includes(variantId)
|
body: currentAnswer.includes(variantId)
|
||||||
? currentAnswer?.filter((item) => item !== variantId)
|
? currentAnswer?.filter((item) => item !== variantId)
|
||||||
: [...currentAnswer, variantId],
|
: [...currentAnswer, variantId],
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(
|
updateAnswer(
|
||||||
@ -201,21 +202,18 @@ const VariantItem = ({
|
|||||||
? currentAnswer?.filter((item) => item !== variantId)
|
? currentAnswer?.filter((item) => item !== variantId)
|
||||||
: [...currentAnswer, variantId]
|
: [...currentAnswer, variantId]
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("ответ не был засчитан");
|
enqueueSnackbar("ответ не был засчитан");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: currentQuestion.content.variants[index].answer,
|
body: currentQuestion.content.variants[index].answer,
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, variantId);
|
updateAnswer(currentQuestion.id, variantId);
|
||||||
@ -230,7 +228,7 @@ const VariantItem = ({
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: "",
|
body: "",
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -239,7 +237,6 @@ const VariantItem = ({
|
|||||||
deleteAnswer(currentQuestion.id);
|
deleteAnswer(currentQuestion.id);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -16,6 +16,7 @@ import BlankImage from "@icons/BlankImage";
|
|||||||
import { useQuizData } from "@utils/hooks/useQuizData";
|
import { useQuizData } from "@utils/hooks/useQuizData";
|
||||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { useQuizId } from "../../../contexts/QuizIdContext";
|
||||||
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
|
||||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ type VarimgProps = {
|
|||||||
export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||||
const { settings } = useQuizData();
|
const { settings } = useQuizData();
|
||||||
const { answers } = useQuizViewStore();
|
const { answers } = useQuizViewStore();
|
||||||
|
const qid = useQuizId();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useRootContainerSize() < 650;
|
const isMobile = useRootContainerSize() < 650;
|
||||||
|
|
||||||
@ -89,7 +91,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateAnswer(
|
updateAnswer(
|
||||||
@ -108,7 +110,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
|||||||
await sendAnswer({
|
await sendAnswer({
|
||||||
questionId: currentQuestion.id,
|
questionId: currentQuestion.id,
|
||||||
body: "",
|
body: "",
|
||||||
qid: settings.qid
|
qid,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
import { QuizQuestionResult } from "@model/questionTypes/result"
|
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||||
|
|
||||||
export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult }) => {
|
export const isResultQuestionEmpty = (resultQuestion: QuizQuestionResult) => {
|
||||||
let check = true
|
|
||||||
if (
|
if (
|
||||||
(resultData.title.length > 0 && resultData.title !== " ") ||
|
(resultQuestion.title.length > 0 && resultQuestion.title !== " ")
|
||||||
(resultData.description.length > 0 && resultData.description !== " ") ||
|
|| (resultQuestion.description.length > 0 && resultQuestion.description !== " ")
|
||||||
(resultData.content.back.length > 0 && resultData.content.back !== " ") ||
|
|| (resultQuestion.content.back.length > 0 && resultQuestion.content.back !== " ")
|
||||||
(resultData.content.originalBack.length > 0 && resultData.content.originalBack !== " ") ||
|
|| (resultQuestion.content.originalBack.length > 0 && resultQuestion.content.originalBack !== " ")
|
||||||
(resultData.content.innerName.length > 0 && resultData.content.innerName !== " ") ||
|
|| (resultQuestion.content.innerName.length > 0 && resultQuestion.content.innerName !== " ")
|
||||||
(resultData.content.text.length > 0 && resultData.content.text !== " ") ||
|
|| (resultQuestion.content.text.length > 0 && resultQuestion.content.text !== " ")
|
||||||
(resultData.content.video.length > 0 && resultData.content.video !== " ") ||
|
|| (resultQuestion.content.video.length > 0 && resultQuestion.content.video !== " ")
|
||||||
(resultData.content.hint.text.length > 0 && resultData.content.hint.text !== " " )
|
|| (resultQuestion.content.hint.text.length > 0 && resultQuestion.content.hint.text !== " ")
|
||||||
) check = false
|
) return false;
|
||||||
return check
|
|
||||||
}
|
return true;
|
||||||
|
};
|
||||||
|
@ -4,6 +4,7 @@ import { nanoid } from "nanoid";
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { devtools } from "zustand/middleware";
|
import { devtools } from "zustand/middleware";
|
||||||
import type { Moment } from "moment";
|
import type { Moment } from "moment";
|
||||||
|
import { QuizStep } from "@model/settingsData";
|
||||||
|
|
||||||
type Answer = {
|
type Answer = {
|
||||||
questionId: string;
|
questionId: string;
|
||||||
@ -18,6 +19,7 @@ type OwnVariant = {
|
|||||||
interface QuizViewStore {
|
interface QuizViewStore {
|
||||||
answers: Answer[];
|
answers: Answer[];
|
||||||
ownVariants: OwnVariant[];
|
ownVariants: OwnVariant[];
|
||||||
|
currentQuizStep: QuizStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useQuizViewStore = create<QuizViewStore>()(
|
export const useQuizViewStore = create<QuizViewStore>()(
|
||||||
@ -25,6 +27,7 @@ export const useQuizViewStore = create<QuizViewStore>()(
|
|||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
answers: [],
|
answers: [],
|
||||||
ownVariants: [],
|
ownVariants: [],
|
||||||
|
currentQuizStep: "startpage",
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "quizView",
|
name: "quizView",
|
||||||
@ -42,8 +45,8 @@ function setProducedState<A extends string | { type: string; }>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const updateAnswer = (
|
export const updateAnswer = (
|
||||||
questionId: string,
|
questionId: string,
|
||||||
answer: string | string[] | Moment
|
answer: string | string[] | Moment
|
||||||
) => setProducedState(state => {
|
) => setProducedState(state => {
|
||||||
const index = state.answers.findIndex(answer => questionId === answer.questionId);
|
const index = state.answers.findIndex(answer => questionId === answer.questionId);
|
||||||
|
|
||||||
@ -93,4 +96,11 @@ export const deleteOwnVariant = (id: string) => useQuizViewStore.setState(state
|
|||||||
}), false, {
|
}), false, {
|
||||||
type: "deleteOwnVariant",
|
type: "deleteOwnVariant",
|
||||||
id
|
id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setCurrentQuizStep = (currentQuizStep: QuizStep) => useQuizViewStore.setState({
|
||||||
|
currentQuizStep
|
||||||
|
}, false, {
|
||||||
|
type: "setCurrentQuizStep",
|
||||||
|
currentQuizStep
|
||||||
|
});
|
||||||
|
27
src/ui_kit/ErrorBoundaryFallback.tsx
Normal file
27
src/ui_kit/ErrorBoundaryFallback.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { FallbackProps } from "react-error-boundary";
|
||||||
|
|
||||||
|
|
||||||
|
export default function ErrorBoundaryFallback({ error }: FallbackProps) {
|
||||||
|
let message = "Что-то пошло не так";
|
||||||
|
|
||||||
|
if (error.message === "No questions found") message = "Нет созданных вопросов";
|
||||||
|
if (error.message === "Quiz already completed") message = "Вы уже прошли этот опрос";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>{message}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
213
src/utils/hooks/useQuestionFlowControl.ts
Normal file
213
src/utils/hooks/useQuestionFlowControl.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||||
|
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||||
|
import { setCurrentQuizStep, useQuizViewStore } from "@stores/quizView/store";
|
||||||
|
import { useCallback, useDebugValue, useMemo, useState } from "react";
|
||||||
|
import { isResultQuestionEmpty } from "../../pages/ViewPublicationPage/tools/checkEmptyData";
|
||||||
|
import { useQuizData } from "./useQuizData";
|
||||||
|
import { devlog } from "@frontend/kitui";
|
||||||
|
|
||||||
|
|
||||||
|
export function useQuestionFlowControl() {
|
||||||
|
const { settings, questions } = useQuizData();
|
||||||
|
const isLinear = questions.every(({ content }) => content.rule.parentId !== "root");
|
||||||
|
const [currentQuestion, setCurrentQuestion] = useState<AnyTypedQuizQuestion>(getFirstQuestion);
|
||||||
|
const answers = useQuizViewStore(state => state.answers);
|
||||||
|
|
||||||
|
const questionIndex = isLinear ? questions.indexOf(currentQuestion) : null;
|
||||||
|
const currentQuestionStepNumber = questionIndex && questionIndex + 1;
|
||||||
|
|
||||||
|
function getFirstQuestion() {
|
||||||
|
if (questions.length === 0) throw new Error("No questions found");
|
||||||
|
|
||||||
|
if (settings.cfg.haveRoot) {
|
||||||
|
const nextQuestion = questions.find(
|
||||||
|
question => question.id === settings.cfg.haveRoot || question.content.id === settings.cfg.haveRoot
|
||||||
|
);
|
||||||
|
if (!nextQuestion) throw new Error("Root question not found");
|
||||||
|
|
||||||
|
return nextQuestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return questions[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextQuestionId = useMemo(() => {
|
||||||
|
console.log("Смотрим какой вопрос будет дальше. Что у нас сегодня вкусненького? Щя покажу от какого вопроса мы ищем следующий шаг");
|
||||||
|
console.log(currentQuestion);
|
||||||
|
console.log("От вот этого /|");
|
||||||
|
let readyBeNextQuestion = "";
|
||||||
|
|
||||||
|
//вопрос обязателен, анализируем ответ и условия ветвления
|
||||||
|
if (answers.length) {
|
||||||
|
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id);
|
||||||
|
|
||||||
|
currentQuestion.content.rule.main.forEach(({ next, rules }) => {
|
||||||
|
const longerArray = Math.max(
|
||||||
|
rules[0].answers.length,
|
||||||
|
answer?.answer && Array.isArray(answer?.answer) ? answer?.answer.length : [answer?.answer].length
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < longerArray; i++) {
|
||||||
|
if (Array.isArray(answer?.answer)) {
|
||||||
|
if (answer?.answer.find((item) => String(item === rules[0].answers[i]))) {
|
||||||
|
readyBeNextQuestion = next; // Если хоть один элемент отличается, массивы не равны
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String(rules[0].answers[i]) === answer?.answer) {
|
||||||
|
readyBeNextQuestion = next; // Если хоть один элемент отличается, массивы не равны
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (readyBeNextQuestion) return readyBeNextQuestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentQuestion.required) {//вопрос не обязателен и не нашли совпадений между ответами и условиями ветвления
|
||||||
|
console.log("вопрос не обязателен ищем дальше");
|
||||||
|
const defaultNextQuestion = currentQuestion.content.rule.default;
|
||||||
|
if (defaultNextQuestion.length > 1 && defaultNextQuestion !== " ") return defaultNextQuestion;
|
||||||
|
//Вопросы типа страница, ползунок, своё поле для ввода и дата не могут иметь больше 1 ребёнка. Пользователь не может настроить там дефолт
|
||||||
|
//Кинуть на ребёнка надо даже если там нет дефолта
|
||||||
|
if (
|
||||||
|
["date", "page", "text", "number"].includes(currentQuestion.type)
|
||||||
|
&& currentQuestion.content.rule.children.length === 1
|
||||||
|
) return currentQuestion.content.rule.children[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
//ничё не нашли, ищем резулт
|
||||||
|
console.log("ничё не нашли, ищем резулт ");
|
||||||
|
return questions.find(q => {
|
||||||
|
return q.type === "result" && q.content.rule.parentId === currentQuestion.content.id;
|
||||||
|
})?.id;
|
||||||
|
}, [answers, currentQuestion, questions]);
|
||||||
|
|
||||||
|
const nextQuestion = questions.find(q => q.id === nextQuestionId || q.content.id === nextQuestionId);
|
||||||
|
|
||||||
|
const resultQuestion = useMemo(() => {
|
||||||
|
if (currentQuestion.type === "result") return currentQuestion;
|
||||||
|
|
||||||
|
if (settings.cfg.haveRoot) return questions.find((question): question is QuizQuestionResult => {
|
||||||
|
return question.type === "result" && question.content.rule.parentId === currentQuestion.content.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return questions.find((question): question is QuizQuestionResult => {
|
||||||
|
return question.type === "result" && question.content.rule.parentId === "line";
|
||||||
|
});
|
||||||
|
}, [currentQuestion, questions, settings.cfg.haveRoot]);
|
||||||
|
|
||||||
|
const showResult = useCallback((resultQuestion: QuizQuestionResult) => {
|
||||||
|
if (settings.cfg.resultInfo.showResultForm === "before" && !isResultQuestionEmpty(resultQuestion)) {
|
||||||
|
return setCurrentQuestion(resultQuestion);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentQuizStep("contactform");
|
||||||
|
}, [settings.cfg.resultInfo.showResultForm]);
|
||||||
|
|
||||||
|
const showResultAfterContactForm = useCallback(() => {
|
||||||
|
if (settings.cfg.resultInfo.showResultForm === "before") return;
|
||||||
|
if (!resultQuestion) throw new Error("Result question not found");
|
||||||
|
if (isResultQuestionEmpty(resultQuestion)) {
|
||||||
|
devlog("Result question is empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentQuizStep("question");
|
||||||
|
setCurrentQuestion(resultQuestion);
|
||||||
|
}, [resultQuestion, settings.cfg.resultInfo.showResultForm]);
|
||||||
|
|
||||||
|
const moveToPrevQuestionLinear = useCallback(() => {
|
||||||
|
if (questionIndex === null) return;
|
||||||
|
|
||||||
|
const question = questions[questionIndex - 1];
|
||||||
|
|
||||||
|
if (question && question.type !== "result") {
|
||||||
|
setCurrentQuestion(question);
|
||||||
|
}
|
||||||
|
}, [questionIndex, questions]);
|
||||||
|
|
||||||
|
const moveToNextQuestionLinear = useCallback(() => {
|
||||||
|
if (questionIndex === null) return;
|
||||||
|
|
||||||
|
const question = questions[questionIndex + 1];
|
||||||
|
|
||||||
|
if (question && question.type !== "result") {
|
||||||
|
return setCurrentQuestion(question);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resultQuestion) throw new Error("Result question not found");
|
||||||
|
showResult(resultQuestion);
|
||||||
|
}, [questionIndex, questions, resultQuestion, showResult]);
|
||||||
|
|
||||||
|
const moveToPrevQuestionBranching = useCallback(() => {
|
||||||
|
if (currentQuestion.content.rule.parentId === "root") throw new Error("No question to go back to");
|
||||||
|
|
||||||
|
const questionId = currentQuestion.content.rule.parentId;
|
||||||
|
const parent = questions.find(q => q.id === questionId || q.content.id === questionId) || null;
|
||||||
|
if (!parent || parent.type === "result") throw new Error("Parent question not found");
|
||||||
|
|
||||||
|
setCurrentQuestion(parent);
|
||||||
|
}, [currentQuestion.content.rule.parentId, questions]);
|
||||||
|
|
||||||
|
const moveToNextQuestionBranching = useCallback(() => {
|
||||||
|
if (!nextQuestion) throw new Error("Next question not found");
|
||||||
|
|
||||||
|
if (nextQuestion.type === "result") {
|
||||||
|
showResult(nextQuestion);
|
||||||
|
} else {
|
||||||
|
setCurrentQuestion(nextQuestion);
|
||||||
|
}
|
||||||
|
}, [nextQuestion, showResult]);
|
||||||
|
|
||||||
|
const isPreviousButtonDisabled = useMemo(() => {
|
||||||
|
if (isLinear) {
|
||||||
|
const questionIndex = questions.findIndex(({ id }) => id === currentQuestion.id);
|
||||||
|
|
||||||
|
const previousQuestion = questions[questionIndex - 1];
|
||||||
|
|
||||||
|
return previousQuestion ? false : true;
|
||||||
|
} else {
|
||||||
|
return currentQuestion.content.rule.parentId === "root" ? true : false;
|
||||||
|
}
|
||||||
|
}, [questions, isLinear, currentQuestion.content.rule.parentId, currentQuestion.id]);
|
||||||
|
|
||||||
|
const isNextButtonDisabled = useMemo(() => {
|
||||||
|
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id);
|
||||||
|
|
||||||
|
if ("required" in currentQuestion.content && currentQuestion.content.required) {
|
||||||
|
return !answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLinear) return false;
|
||||||
|
|
||||||
|
if (nextQuestion) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
const questionId = currentQuestion.content.rule.default;
|
||||||
|
const nextQuestion = questions.find(q => q.id === questionId || q.content.id === questionId) || null;
|
||||||
|
|
||||||
|
if (nextQuestion?.type) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, [answers, currentQuestion.content, currentQuestion.id, isLinear, nextQuestion, questions]);
|
||||||
|
|
||||||
|
useDebugValue({
|
||||||
|
isLinear,
|
||||||
|
currentQuestion,
|
||||||
|
nextQuestion: questions.find(q => q.content.id === nextQuestionId),
|
||||||
|
resultQuestion,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentQuestion,
|
||||||
|
currentQuestionStepNumber,
|
||||||
|
isNextButtonDisabled,
|
||||||
|
isPreviousButtonDisabled,
|
||||||
|
moveToPrevQuestion: isLinear ? moveToPrevQuestionLinear : moveToPrevQuestionBranching,
|
||||||
|
moveToNextQuestion: isLinear ? moveToNextQuestionLinear : moveToNextQuestionBranching,
|
||||||
|
showResultAfterContactForm,
|
||||||
|
};
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import { getData } from "@api/quizRelase";
|
import { getData } from "@api/quizRelase";
|
||||||
import { parseQuizData } from "@model/api/getQuizData";
|
import { parseQuizData } from "@model/api/getQuizData";
|
||||||
import { QuizSettings } from "@model/settingsData";
|
import { QuizSettings } from "@model/settingsData";
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { useQuizId } from "../../contexts/QuizIdContext";
|
import { useQuizId } from "../../contexts/QuizIdContext";
|
||||||
import { replaceSpacesToEmptyLines } from "../../pages/ViewPublicationPage/tools/replaceSpacesToEmptyLines";
|
import { replaceSpacesToEmptyLines } from "../../pages/ViewPublicationPage/tools/replaceSpacesToEmptyLines";
|
||||||
@ -21,14 +20,13 @@ async function getQuizData(quizId: string) {
|
|||||||
const quizDataResponse = response.data;
|
const quizDataResponse = response.data;
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
enqueueSnackbar(response.error);
|
|
||||||
throw new Error(response.error);
|
throw new Error(response.error);
|
||||||
}
|
}
|
||||||
if (!quizDataResponse) {
|
if (!quizDataResponse) {
|
||||||
throw new Error("Quiz not found");
|
throw new Error("Quiz not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const quizSettings = replaceSpacesToEmptyLines(parseQuizData(quizDataResponse, quizId));
|
const quizSettings = replaceSpacesToEmptyLines(parseQuizData(quizDataResponse));
|
||||||
|
|
||||||
return JSON.parse(JSON.stringify({ data: quizSettings }).replaceAll(/\\" \\"/g, '""').replaceAll(/" "/g, '""')).data as QuizSettings;
|
return JSON.parse(JSON.stringify({ data: quizSettings }).replaceAll(/\\" \\"/g, '""').replaceAll(/" "/g, '""')).data as QuizSettings;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user