feat: staged
This commit is contained in:
commit
f0f08c4f7c
@ -6,8 +6,8 @@
|
||||
"@craco/craco": "^7.0.0",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@frontend/kitui": "^1.0.78",
|
||||
"@frontend/squzanswerer": "^1.0.22",
|
||||
"@frontend/kitui": "^1.0.82",
|
||||
"@frontend/squzanswerer": "^1.0.37",
|
||||
"@mui/icons-material": "^5.10.14",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@mui/x-charts": "^6.19.5",
|
||||
|
@ -21,10 +21,10 @@ import {
|
||||
createUserAccount,
|
||||
devlog,
|
||||
getMessageFromFetchError,
|
||||
makeRequest,
|
||||
UserAccount,
|
||||
useUserFetcher,
|
||||
} from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import type { OriginalUserAccount } from "@root/user";
|
||||
import {
|
||||
clearUserData,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
|
||||
import type {
|
||||
LoginRequest,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { UserAccount, makeRequest } from "@frontend/kitui";
|
||||
import { AxiosError } from "axios";
|
||||
import { UserAccount } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
|
||||
|
43
src/api/makeRequest.ts
Normal file
43
src/api/makeRequest.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import * as KIT from "@frontend/kitui";
|
||||
import { Method, ResponseType, AxiosError } from "axios";
|
||||
import { clearAuthToken } from "@frontend/kitui";
|
||||
import { cleanAuthTicketData } from "@root/ticket";
|
||||
import { clearUserData } from "@root/user";
|
||||
import { clearQuizData } from "@root/quizes/store";
|
||||
import { redirect } from "react-router-dom";
|
||||
|
||||
interface MakeRequest {
|
||||
method?: Method | undefined;
|
||||
url: string;
|
||||
body?: unknown;
|
||||
useToken?: boolean | undefined;
|
||||
contentType?: boolean | undefined;
|
||||
responseType?: ResponseType | undefined;
|
||||
signal?: AbortSignal | undefined;
|
||||
withCredentials?: boolean | undefined;
|
||||
}
|
||||
|
||||
async function makeRequest<TRequest = unknown, TResponse = unknown>(
|
||||
data: MakeRequest,
|
||||
): Promise<TResponse> {
|
||||
try {
|
||||
const response = await KIT.makeRequest<unknown>(data);
|
||||
|
||||
return response as TResponse;
|
||||
} catch (e) {
|
||||
const error = e as AxiosError;
|
||||
//@ts-ignore
|
||||
if (
|
||||
error.response?.status === 400 &&
|
||||
error.response?.data?.message === "refreshToken is empty"
|
||||
) {
|
||||
cleanAuthTicketData();
|
||||
clearAuthToken();
|
||||
clearUserData();
|
||||
clearQuizData();
|
||||
redirect("/");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
export default makeRequest;
|
@ -1,4 +1,4 @@
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
|
||||
@ -15,7 +15,6 @@ export async function activatePromocode(promocode: string) {
|
||||
contentType: true,
|
||||
body: { codeword: promocode },
|
||||
});
|
||||
console.log(response);
|
||||
|
||||
return response.greetings;
|
||||
} catch (nativeError) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import { CreateQuestionRequest } from "model/question/create";
|
||||
import { RawQuestion } from "model/question/question";
|
||||
import {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import { defaultQuizConfig } from "@model/quizSettings";
|
||||
import { CopyQuizRequest, CopyQuizResponse } from "model/quiz/copy";
|
||||
import { CreateQuizRequest } from "model/quiz/create";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import { RawResult } from "@model/result/result";
|
||||
|
||||
interface IResultListBody {
|
||||
@ -45,21 +45,6 @@ function deleteResult(resultId: number) {
|
||||
});
|
||||
}
|
||||
|
||||
// export const obsolescenceResult = async (idResultArray: string[]) => {
|
||||
// try {
|
||||
// const response = await makeRequest<unknown, unknown>({
|
||||
// url: process.env.REACT_APP_DOMAIN + `/squiz/result/seen`,
|
||||
// body: {
|
||||
// answers: idResultArray,
|
||||
// },
|
||||
// method: "PATCH",
|
||||
// });
|
||||
// return response;
|
||||
// } catch (e) {
|
||||
// console.log("ошибка", e);
|
||||
// }
|
||||
// };
|
||||
|
||||
function obsolescenceResult(idResultArray: number[]) {
|
||||
return makeRequest<unknown, unknown>({
|
||||
url: process.env.REACT_APP_DOMAIN + `/squiz/result/seen`,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import { parseAxiosError } from "../utils/parse-error";
|
||||
|
||||
import { SendTicketMessageRequest } from "@frontend/kitui";
|
||||
|
@ -70,6 +70,7 @@ export type QuizTheme =
|
||||
| "Design10";
|
||||
|
||||
export interface QuizConfig {
|
||||
spec: undefined | true;
|
||||
type: QuizType;
|
||||
noStartPage: boolean;
|
||||
startpageType: QuizStartpageType;
|
||||
@ -140,6 +141,7 @@ export type FieldSettingsDrawerState = {
|
||||
};
|
||||
|
||||
export const defaultQuizConfig: QuizConfig = {
|
||||
spec: undefined,
|
||||
type: null,
|
||||
noStartPage: false,
|
||||
startpageType: null,
|
||||
|
@ -41,9 +41,6 @@ export default function Analytics() {
|
||||
const [isOpenEnd, setOpenEnd] = useState<boolean>(false);
|
||||
const [from, setFrom] = useState<Moment | null>(null);
|
||||
const [to, setTo] = useState<Moment | null>(moment().add(1, "days"));
|
||||
console.log(moment(to).unix() - moment(from).unix());
|
||||
console.log(86400 - (moment(to).unix() - moment(from).unix()));
|
||||
console.log(86400 - (moment(to).unix() - moment(from).unix()) > 0);
|
||||
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { FC, useEffect, useMemo, useState } from "react";
|
||||
import type { PaginationRenderItemParams } from "@mui/material";
|
||||
import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
@ -16,8 +17,7 @@ import { ReactComponent as DoubleCheckIcon } from "@icons/Analytics/doubleCheck.
|
||||
import { ReactComponent as NextIcon } from "@icons/Analytics/next.svg";
|
||||
import { ReactComponent as LeftArrowIcon } from "@icons/Analytics/leftArrow.svg";
|
||||
import { ReactComponent as RightArrowIcon } from "@icons/Analytics/rightArrow.svg";
|
||||
|
||||
import type { PaginationRenderItemParams } from "@mui/material";
|
||||
import { extractOrder } from "@utils/extractOrder";
|
||||
|
||||
type AnswerProps = {
|
||||
title: string;
|
||||
@ -200,16 +200,17 @@ const Pagination = ({ page, setPage, pagesAmount }: PaginationProps) => {
|
||||
export const Answers: FC<AnswersProps> = ({ data }) => {
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const theme = useTheme();
|
||||
const answers = useMemo(
|
||||
() => (data === null ? [] : Object.entries(data ?? {})),
|
||||
[data],
|
||||
);
|
||||
const answers = useMemo(() => {
|
||||
const unsortedAnswers = data === null ? [] : Object.entries(data ?? {});
|
||||
return unsortedAnswers.sort(
|
||||
([titleA], [titleB]) => extractOrder(titleA) - extractOrder(titleB),
|
||||
);
|
||||
}, [data]);
|
||||
const currentAnswer = answers[page - 1];
|
||||
const percentsSum = Object.values(currentAnswer?.[1] ?? {}).reduce(
|
||||
(total, item) => (total += item),
|
||||
0,
|
||||
);
|
||||
|
||||
const currentAnswerExtended =
|
||||
percentsSum >= 100
|
||||
? Object.entries(currentAnswer?.[1] ?? {})
|
||||
|
@ -80,7 +80,6 @@ export const DesignPage = ({ heightSidebar, mobileSidebar }: Props) => {
|
||||
follow={followNewPage}
|
||||
cancel={() => setShowConfirmLeaveModal(false)}
|
||||
/>
|
||||
{createPortal(<QuizPreview />, document.body)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -3,12 +3,10 @@ import { useUserStore } from "@root/user";
|
||||
import { Link } from "react-router-dom";
|
||||
export const InfoPrivilege = () => {
|
||||
const user = useUserStore();
|
||||
console.log(user);
|
||||
return (
|
||||
<Box>
|
||||
<Link to="/list">К списку квизов</Link>
|
||||
{Object.values(user?.userAccount?.privileges || {}).map((privilege) => {
|
||||
console.log(privilege);
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -27,7 +27,7 @@ export const useGetData = (filterNew: string, filterDate: string): void => {
|
||||
parseFilters(filterNew, filterDate),
|
||||
);
|
||||
if (result.total_count === 0) {
|
||||
console.log("No results found");
|
||||
console.error("No results found");
|
||||
}
|
||||
setResults(result);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { logout } from "@api/auth";
|
||||
import { activatePromocode } from "@api/promocode";
|
||||
import type { Tariff } from "@frontend/kitui";
|
||||
import { clearAuthToken, makeRequest, useToken } from "@frontend/kitui";
|
||||
import { useToken } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
||||
import type { GetTariffsResponse } from "@model/tariff";
|
||||
import {
|
||||
@ -108,8 +109,6 @@ function TariffPage() {
|
||||
|
||||
const openModalHC = (tariffInfo: any) => setOpenModal(tariffInfo);
|
||||
const tryBuy = async ({ id, price }: { id: string; price: number }) => {
|
||||
console.log("цена", price);
|
||||
// console.log("мои деньги", (user.wallet.cash / 100))
|
||||
openModalHC({});
|
||||
//Если в корзине что-то было - выкладываем содержимое и запоминаем чо там лежало
|
||||
if (user.cart.length > 0) {
|
||||
@ -127,7 +126,6 @@ function TariffPage() {
|
||||
method: "POST",
|
||||
url: process.env.REACT_APP_DOMAIN + "/customer/cart/pay",
|
||||
});
|
||||
console.log(data);
|
||||
setCash(
|
||||
currencyFormatter.format(Number(data.wallet.cash) / 100),
|
||||
Number(data.wallet.cash),
|
||||
@ -142,6 +140,7 @@ function TariffPage() {
|
||||
link.href = `https://${isTestServer ? "s" : ""}hub.pena.digital/quizpayment?action=squizpay&dif=${cashDif}&data=${token}&userid=${userId}`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
return;
|
||||
}
|
||||
//другая ошибка
|
||||
enqueueSnackbar("Произошла ошибка. Попробуйте позже");
|
||||
@ -390,7 +389,7 @@ export const inCart = () => {
|
||||
}
|
||||
localStorage.setItem("saveCart", JSON.stringify(saveCart));
|
||||
} catch (e) {
|
||||
console.log("Я не смог добавить тариф в корзину :( " + id);
|
||||
console.error("Я не смог добавить тариф в корзину :( " + id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -409,7 +408,7 @@ const outCart = (cart: string[]) => {
|
||||
saveCart = saveCart.push(id);
|
||||
localStorage.setItem("saveCart", JSON.stringify(saveCart));
|
||||
} catch (e) {
|
||||
console.log("Я не смог удалить из корзины тариф :(");
|
||||
console.error("Я не смог удалить из корзины тариф :(");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -41,11 +41,11 @@ export default function ViewPublicationPage() {
|
||||
);
|
||||
|
||||
if (quizesError) {
|
||||
console.log(`Error fetching quiz ${quizId}`, quizesError);
|
||||
console.error(`Error fetching quiz ${quizId}`, quizesError);
|
||||
return null;
|
||||
}
|
||||
if (questionsError) {
|
||||
console.log(`Error fetching questions ${quizId}`, questionsError);
|
||||
console.error(`Error fetching questions ${quizId}`, questionsError);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -78,6 +78,7 @@ export default function ViewPublicationPage() {
|
||||
rep: quiz.repeatable,
|
||||
cfg: quiz.config,
|
||||
},
|
||||
show_badge: true,
|
||||
}}
|
||||
quizId={quizId}
|
||||
preview
|
||||
|
@ -18,8 +18,8 @@ import { object, string } from "yup";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useUserStore } from "@root/user";
|
||||
|
||||
import { getAuthToken, makeRequest, setAuthToken } from "@frontend/kitui";
|
||||
import { FormContactFieldName } from "@model/quizSettings";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import { setAuthToken } from "@frontend/kitui";
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
interface Values {
|
||||
password: string;
|
||||
@ -32,7 +32,10 @@ const initialValues: Values = {
|
||||
const validationSchema = object({
|
||||
password: string()
|
||||
.min(8, "Минимум 8 символов")
|
||||
.matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы")
|
||||
.matches(
|
||||
/^[.,:;\-_+!&()*<>\[\]\{\}`@"#$\%\^\=?\d\w]+$/,
|
||||
"Некорректные символы",
|
||||
)
|
||||
.required("Поле обязательно"),
|
||||
});
|
||||
|
||||
|
@ -39,7 +39,7 @@ const validationSchema = object({
|
||||
password: string()
|
||||
.min(8, "Минимум 8 символов")
|
||||
.matches(
|
||||
/^[.,:;\-_+!&()<>\[\]{}№`@"'~*|#$₽%^=?\d\w]+$/,
|
||||
/^[.,:;\-_+!&()*<>\[\]\{\}`@"#$\%\^\=?\d\w]+$/,
|
||||
"Некорректные символы",
|
||||
)
|
||||
.required("Поле обязательно"),
|
||||
|
@ -29,7 +29,6 @@ export default function AvailablePrivilege() {
|
||||
}
|
||||
},
|
||||
});
|
||||
console.log("это доступные привелегии", userPrivileges);
|
||||
const DayForm = ["день", "дня", "дней"];
|
||||
function declOfNum(n: number, text_forms: string[]) {
|
||||
n = Math.abs(n) % 100;
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
import { deleteQuiz, setEditQuizId } from "@root/quizes/actions";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { inCart } from "../../pages/Tariffs/Tariffs";
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
||||
import CopyIcon from "@icons/CopyIcon";
|
||||
|
@ -11,6 +11,5 @@ export const setCash = (
|
||||
cashCop: number,
|
||||
cashRub: number,
|
||||
) => {
|
||||
console.log("я получил ", cashString);
|
||||
useWallet.setState({ cashString, cashCop, cashRub });
|
||||
};
|
||||
|
@ -3,14 +3,12 @@ import { devtools } from "zustand/middleware";
|
||||
|
||||
interface QuizPreviewStore {
|
||||
isPreviewShown: boolean;
|
||||
currentQuestionIndex: number;
|
||||
}
|
||||
|
||||
export const useQuizPreviewStore = create<QuizPreviewStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
isPreviewShown: false,
|
||||
currentQuestionIndex: 0,
|
||||
}),
|
||||
{
|
||||
name: "quizPreview",
|
||||
@ -29,18 +27,3 @@ export const toggleQuizPreview = () =>
|
||||
useQuizPreviewStore.setState((state) => ({
|
||||
isPreviewShown: !state.isPreviewShown,
|
||||
}));
|
||||
|
||||
export const setCurrentQuestionIndex = (step: number) =>
|
||||
useQuizPreviewStore.setState((state) => ({
|
||||
currentQuestionIndex: (state.currentQuestionIndex = step),
|
||||
}));
|
||||
|
||||
export const incrementCurrentQuestionIndex = (maxStep: number) =>
|
||||
useQuizPreviewStore.setState((state) => ({
|
||||
currentQuestionIndex: Math.min(state.currentQuestionIndex + 1, maxStep),
|
||||
}));
|
||||
|
||||
export const decrementCurrentQuestionIndex = () =>
|
||||
useQuizPreviewStore.setState((state) => ({
|
||||
currentQuestionIndex: Math.max(state.currentQuestionIndex - 1, 0),
|
||||
}));
|
||||
|
@ -3,7 +3,7 @@ import { useUserStore } from "@root/user";
|
||||
import { Box, Button, Modal, Typography } from "@mui/material";
|
||||
import { mutate } from "swr";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
|
||||
export function CheckFastlink() {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import {
|
||||
TicketMessage,
|
||||
makeRequest,
|
||||
useSSESubscription,
|
||||
useTicketMessages,
|
||||
useTicketsFetcher,
|
||||
} from "@frontend/kitui";
|
||||
import makeRequest from "@api/makeRequest";
|
||||
import FloatingSupportChat from "./FloatingSupportChat";
|
||||
import { useUserStore } from "@root/user";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
@ -192,7 +192,6 @@ export default () => {
|
||||
// fileType => file.name.toLowerCase().endsWith(fileType)
|
||||
// );
|
||||
// if (!isFileTypeAccepted) return setModalWarningType("errorType");
|
||||
console.log("тут ошибка", modalWarningType);
|
||||
let data;
|
||||
if (!ticket.sessionData?.ticketId) {
|
||||
try {
|
||||
|
@ -1,16 +1,13 @@
|
||||
import { Box, IconButton, ThemeProvider } from "@mui/material";
|
||||
import { toggleQuizPreview, useQuizPreviewStore } from "@root/quizPreview";
|
||||
import { QuizAnswerer } from "@frontend/squzanswerer";
|
||||
import ResizeIcon from "@icons/ResizeIcon";
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { Box, ThemeProvider } from "@mui/material";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useQuizPreviewStore } from "@root/quizPreview";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { themesPublication } from "@utils/themes/Publication/themePublication";
|
||||
import { useLayoutEffect, useRef } from "react";
|
||||
import { Rnd } from "react-rnd";
|
||||
import { useWindowSize } from "../../utils/hooks/useWindowSize";
|
||||
import QuizPreviewLayout from "./QuizPreviewLayout";
|
||||
import ResizeIcon from "@icons/ResizeIcon";
|
||||
import { themesPublication } from "@utils/themes/Publication/themePublication";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
const DRAG_PARENT_MARGIN = 0;
|
||||
const NAVBAR_HEIGHT = 0;
|
||||
const DRAG_PARENT_BOTTOM_MARGIN = 0;
|
||||
|
||||
interface RndPositionAndSize {
|
||||
x: number;
|
||||
@ -23,6 +20,9 @@ export default function QuizPreview() {
|
||||
const isPreviewShown = useQuizPreviewStore((state) => state.isPreviewShown);
|
||||
const rndParentRef = useRef<HTMLDivElement>(null);
|
||||
const quiz = useCurrentQuiz();
|
||||
const questions = useQuestionsStore((state) => state.questions).filter(
|
||||
(q): q is AnyTypedQuizQuestion => q.type !== null,
|
||||
);
|
||||
const rndRef = useRef<Rnd | null>(null);
|
||||
const rndPositionAndSizeRef = useRef<RndPositionAndSize>({
|
||||
x: 0,
|
||||
@ -32,6 +32,8 @@ export default function QuizPreview() {
|
||||
});
|
||||
const isFirstShowRef = useRef<boolean>(true);
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
useLayoutEffect(
|
||||
function stickPreviewToBottomRight() {
|
||||
const rnd = rndRef.current;
|
||||
@ -68,8 +70,8 @@ export default function QuizPreview() {
|
||||
data-cy="quiz-preview-container"
|
||||
sx={{
|
||||
position: "fixed",
|
||||
top: NAVBAR_HEIGHT + DRAG_PARENT_MARGIN,
|
||||
left: DRAG_PARENT_MARGIN,
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 70,
|
||||
right: 70,
|
||||
// backgroundColor: "rgba(0, 100, 0, 0.2)",
|
||||
@ -119,10 +121,31 @@ export default function QuizPreview() {
|
||||
overflow: "hidden",
|
||||
pointerEvents: "auto",
|
||||
boxShadow: "0px 5px 10px 2px rgba(34, 60, 80, 0.2)",
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
cancel=".cancel"
|
||||
>
|
||||
<QuizPreviewLayout />
|
||||
<QuizAnswerer
|
||||
className="quiz-preview-draghandle"
|
||||
quizSettings={{
|
||||
cnt: questions.length,
|
||||
questions,
|
||||
recentlyCompleted: false,
|
||||
settings: {
|
||||
fp: quiz.fingerprinting,
|
||||
delay: 0,
|
||||
due: quiz.due_to,
|
||||
lim: quiz.limit,
|
||||
name: quiz.name,
|
||||
pausable: quiz.pausable,
|
||||
rep: quiz.repeatable,
|
||||
cfg: quiz.config,
|
||||
},
|
||||
show_badge: true,
|
||||
}}
|
||||
quizId={quiz.qid}
|
||||
preview
|
||||
/>
|
||||
</Rnd>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -1,298 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
LinearProgress,
|
||||
Paper,
|
||||
Typography,
|
||||
FormControl,
|
||||
Select as MuiSelect,
|
||||
MenuItem,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import {
|
||||
decrementCurrentQuestionIndex,
|
||||
incrementCurrentQuestionIndex,
|
||||
useQuizPreviewStore,
|
||||
setCurrentQuestionIndex,
|
||||
} from "@root/quizPreview";
|
||||
import {
|
||||
AnyTypedQuizQuestion,
|
||||
UntypedQuizQuestion,
|
||||
} from "model/questionTypes/shared";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
||||
import Date from "./QuizPreviewQuestionTypes/Date";
|
||||
import Emoji from "./QuizPreviewQuestionTypes/Emoji";
|
||||
import File from "./QuizPreviewQuestionTypes/File";
|
||||
import Images from "./QuizPreviewQuestionTypes/Images";
|
||||
import Number from "./QuizPreviewQuestionTypes/Number";
|
||||
import Page from "./QuizPreviewQuestionTypes/Page";
|
||||
import Rating from "./QuizPreviewQuestionTypes/Rating";
|
||||
import Select, { ArrowDownTheme } from "./QuizPreviewQuestionTypes/Select";
|
||||
import Text from "./QuizPreviewQuestionTypes/Text";
|
||||
import Variant from "./QuizPreviewQuestionTypes/Variant";
|
||||
import Varimg from "./QuizPreviewQuestionTypes/Varimg";
|
||||
import { notReachable } from "../../utils/notReachable";
|
||||
import ArrowDownIcon from "@icons/ArrowDownIcon";
|
||||
|
||||
export default function QuizPreviewLayout() {
|
||||
const theme = useTheme();
|
||||
const questions = useQuestionsStore((state) => state.questions);
|
||||
const currentQuizStep = useQuizPreviewStore(
|
||||
(state) => state.currentQuestionIndex,
|
||||
);
|
||||
const [widthPreview, setWidthPreview] = useState(null);
|
||||
const nonDeletedQuizQuestions = questions.filter(
|
||||
(question) => !question.deleted && question.type !== "result",
|
||||
);
|
||||
const maxCurrentQuizStep =
|
||||
nonDeletedQuizQuestions.length > 0 ? nonDeletedQuizQuestions.length - 1 : 0;
|
||||
const currentProgress = Math.floor(
|
||||
(currentQuizStep / maxCurrentQuizStep) * 100,
|
||||
);
|
||||
const PreviewWin = useRef(0);
|
||||
const currentQuestion = nonDeletedQuizQuestions[currentQuizStep];
|
||||
|
||||
useEffect(
|
||||
function resetCurrentQuizStep() {
|
||||
if (currentQuizStep > maxCurrentQuizStep) {
|
||||
decrementCurrentQuestionIndex();
|
||||
}
|
||||
},
|
||||
[currentQuizStep, maxCurrentQuizStep],
|
||||
);
|
||||
|
||||
const observer = useRef(
|
||||
new ResizeObserver((entries) => {
|
||||
const { width } = entries[0].contentRect;
|
||||
setWidthPreview(width);
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
observer.current.observe(PreviewWin.current);
|
||||
}, [PreviewWin, observer]);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
ref={PreviewWin}
|
||||
className="quiz-preview-draghandle"
|
||||
data-cy="quiz-preview-layout"
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
borderRadius: "12px",
|
||||
pointerEvents: "auto",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
p: "40px 20px 20px",
|
||||
whiteSpace: "break-spaces",
|
||||
overflowY: "auto",
|
||||
flexGrow: 1,
|
||||
"&::-webkit-scrollbar": { width: 0, display: "none" },
|
||||
msOverflowStyle: "none",
|
||||
scrollbarWidth: "none",
|
||||
}}
|
||||
>
|
||||
<QuestionPreviewComponent
|
||||
question={currentQuestion}
|
||||
widthPreview={widthPreview}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mt: "auto",
|
||||
p: "16px",
|
||||
borderTop: "1px solid #E3E3E3",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ marginBottom: "10px" }}>
|
||||
<FormControl
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{ width: "100%", minWidth: "200px", height: "48px" }}
|
||||
className="cancel"
|
||||
>
|
||||
<MuiSelect
|
||||
id="category-select"
|
||||
variant="outlined"
|
||||
value={currentQuizStep}
|
||||
placeholder="Заголовок вопроса"
|
||||
onChange={({ target }) =>
|
||||
setCurrentQuestionIndex(window.Number(target.value))
|
||||
}
|
||||
sx={{
|
||||
height: "48px",
|
||||
borderRadius: "8px",
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: `1px solid ${theme.palette.primary.main} !important`,
|
||||
},
|
||||
"& .MuiSelect-icon": {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
mt: "8px",
|
||||
p: "4px",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #EEE4FC",
|
||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
},
|
||||
MenuListProps: {
|
||||
sx: {
|
||||
py: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
maxWidth: "330px",
|
||||
"& .Mui-selected": {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
color: theme.palette.primary.main,
|
||||
display: "block",
|
||||
px: "9px",
|
||||
gap: "20px",
|
||||
width: "87%",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
}}
|
||||
IconComponent={ArrowDownTheme}
|
||||
>
|
||||
{Object.values(questions.filter((q) => q.type !== "result")).map(
|
||||
({ id, title }, index) => (
|
||||
<MenuItem
|
||||
key={id}
|
||||
value={index}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
p: "4px",
|
||||
borderRadius: "5px",
|
||||
color: "#9A9AAF",
|
||||
wordBreak: "break-word",
|
||||
whiteSpace: "normal",
|
||||
}}
|
||||
>
|
||||
{`${index + 1}. ${title}`}
|
||||
</MenuItem>
|
||||
),
|
||||
)}
|
||||
</MuiSelect>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
{nonDeletedQuizQuestions.length > 0
|
||||
? `Вопрос ${currentQuizStep + 1} из ${
|
||||
nonDeletedQuizQuestions.length
|
||||
}`
|
||||
: "Нет вопросов"}
|
||||
</Typography>
|
||||
{nonDeletedQuizQuestions.length > 0 && (
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={currentProgress}
|
||||
sx={{
|
||||
"&.MuiLinearProgress-colorPrimary": {
|
||||
backgroundColor: "fadePurple.main",
|
||||
},
|
||||
"& .MuiLinearProgress-barColorPrimary": {
|
||||
backgroundColor: "brightPurple.main",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
ml: 2,
|
||||
display: "flex",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={decrementCurrentQuestionIndex}
|
||||
disabled={currentQuizStep === 0}
|
||||
sx={{ px: 1, minWidth: 0 }}
|
||||
className="cancel"
|
||||
>
|
||||
<ArrowLeft color={theme.palette.primary.main} />
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => incrementCurrentQuestionIndex(maxCurrentQuizStep)}
|
||||
disabled={currentQuizStep >= maxCurrentQuizStep}
|
||||
className="cancel"
|
||||
>
|
||||
Далее
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
function QuestionPreviewComponent({
|
||||
question,
|
||||
widthPreview,
|
||||
}: {
|
||||
question: AnyTypedQuizQuestion | UntypedQuizQuestion | undefined;
|
||||
widthPreview?: number;
|
||||
}) {
|
||||
if (!question || question.type === null) return null;
|
||||
|
||||
switch (question.type) {
|
||||
case "variant":
|
||||
return <Variant question={question} widthPreview={widthPreview} />;
|
||||
case "images":
|
||||
return <Images question={question} widthPreview={widthPreview} />;
|
||||
case "varimg":
|
||||
return <Varimg question={question} widthPreview={widthPreview} />;
|
||||
case "emoji":
|
||||
return <Emoji question={question} widthPreview={widthPreview} />;
|
||||
case "text":
|
||||
return <Text question={question} widthPreview={widthPreview} />;
|
||||
case "select":
|
||||
return <Select question={question} widthPreview={widthPreview} />;
|
||||
case "date":
|
||||
return <Date question={question} widthPreview={widthPreview} />;
|
||||
case "number":
|
||||
return <Number question={question} widthPreview={widthPreview} />;
|
||||
case "file":
|
||||
return <File question={question} widthPreview={widthPreview} />;
|
||||
case "page":
|
||||
return <Page question={question} widthPreview={widthPreview} />;
|
||||
case "rating":
|
||||
return <Rating question={question} widthPreview={widthPreview} />;
|
||||
default:
|
||||
notReachable(question);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
|
||||
import LabeledDatePicker from "@ui_kit/LabeledDatePicker";
|
||||
import type { QuizQuestionDate } from "model/questionTypes/date";
|
||||
import { modes } from "@utils/themes/Publication/themePublication";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionDate;
|
||||
}
|
||||
|
||||
export default function Date({ question }: Props) {
|
||||
const theme = useTheme();
|
||||
const mode = modes;
|
||||
const quiz = useCurrentQuiz();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" data-cy="question-title">
|
||||
{question.title}
|
||||
</Typography>
|
||||
<LabeledDatePicker
|
||||
sxIcon={{
|
||||
"& path": { stroke: theme.palette.primary.main },
|
||||
"& rect": { stroke: theme.palette.primary.main },
|
||||
}}
|
||||
sxDate={{
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: mode[quiz?.config.theme || "StandardTheme"]
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
borderRadius: "10px",
|
||||
maxWidth: "250px",
|
||||
pr: "22px",
|
||||
},
|
||||
}}
|
||||
className="cancel"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
import { useState, ChangeEvent } from "react";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionEmoji } from "model/questionTypes/emoji";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionEmoji;
|
||||
}
|
||||
|
||||
export default function Emoji({ question }: Props) {
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
const theme = useTheme();
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue((event.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth className="cancel">
|
||||
<FormLabel
|
||||
id="quiz-question-radio-group"
|
||||
data-cy="question-title"
|
||||
sx={{ fontSize: "24px", fontWeight: 500, marginBottom: "25px" }}
|
||||
>
|
||||
{question.title}
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
sx={{ display: "flex", flexDirection: "row", gap: "42px" }}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
border: `1px solid`,
|
||||
borderColor:
|
||||
value === variant.answer
|
||||
? theme.palette.primary.main
|
||||
: "#9A9AAF",
|
||||
overflow: "hidden",
|
||||
maxWidth: "317px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
height: "193px",
|
||||
background: "#ffffff",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText && (
|
||||
<Typography
|
||||
fontSize={"100px"}
|
||||
>{`${variant.extendedText}`}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<FormControlLabel
|
||||
className="cancel"
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
sx={{
|
||||
margin: 0,
|
||||
padding: "15px",
|
||||
color: theme.palette.text.primary,
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
background: theme.palette.background.default,
|
||||
alignItems:
|
||||
variant.answer.length <= 60 ? "center" : "flex-start",
|
||||
position: "relative",
|
||||
height: "80px",
|
||||
"& .MuiFormControlLabel-label": {
|
||||
wordBreak: "break-word",
|
||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||
overflow: "auto",
|
||||
paddingLeft: "45px",
|
||||
},
|
||||
}}
|
||||
control={
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
}}
|
||||
checkedIcon={
|
||||
<RadioCheck color={theme.palette.primary.main} />
|
||||
}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box
|
||||
sx={{ display: "flex", alignItems: "center", gap: 2 }}
|
||||
data-cy="variant-answer"
|
||||
>
|
||||
<Typography
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
>{`${variant.answer}`}</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title="Подсказка" placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
|
||||
import type {
|
||||
QuizQuestionFile,
|
||||
UploadFileType,
|
||||
} from "model/questionTypes/file";
|
||||
|
||||
export const UPLOAD_FILE_TYPES_MAP: Record<UploadFileType, string> = {
|
||||
all: "file",
|
||||
picture: "image/*",
|
||||
video: "video/*",
|
||||
audio: "audio/*",
|
||||
document:
|
||||
".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.pdf",
|
||||
} as const;
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionFile;
|
||||
}
|
||||
|
||||
export default function File({ question }: Props) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [acceptedType, setAcceptedType] = useState<any>(
|
||||
UPLOAD_FILE_TYPES_MAP.all,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setAcceptedType(UPLOAD_FILE_TYPES_MAP[question.content.type]);
|
||||
}, [question.content.type]);
|
||||
|
||||
function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
if (!event.target.files?.[0]) return setFile(null);
|
||||
setFile(event.target.files[0]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" data-cy="question-title">
|
||||
{question.title}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="cancel"
|
||||
>
|
||||
Загрузить файл
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileChange}
|
||||
type="file"
|
||||
accept={acceptedType}
|
||||
data-cy="file-upload-input"
|
||||
style={{
|
||||
display: "none",
|
||||
}}
|
||||
className="cancel"
|
||||
/>
|
||||
</Button>
|
||||
{file && (
|
||||
<Typography data-cy="chosen-file-name">
|
||||
Вы загрузили: {file.name}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
Divider,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionImages } from "model/questionTypes/images";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionImages;
|
||||
widthPreview: number;
|
||||
}
|
||||
|
||||
export default function Images({ question, widthPreview }: Props) {
|
||||
const theme = useTheme();
|
||||
const [selectedVariants, setSelectedVariants] = useState<number[]>([]);
|
||||
|
||||
function handleVariantClick(index: number) {
|
||||
if (!question.content.multi) return setSelectedVariants([index]);
|
||||
|
||||
const newSelectedVariants = [...selectedVariants];
|
||||
if (newSelectedVariants.includes(index)) {
|
||||
newSelectedVariants.splice(newSelectedVariants.indexOf(index), 1);
|
||||
} else {
|
||||
newSelectedVariants.push(index);
|
||||
}
|
||||
setSelectedVariants(newSelectedVariants);
|
||||
}
|
||||
|
||||
useEffect(
|
||||
function resetSelectedVariants() {
|
||||
setSelectedVariants([]);
|
||||
},
|
||||
[question.content.multi],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color={theme.palette.text.primary}>
|
||||
{question.title}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
|
||||
gap: "15px",
|
||||
maxWidth: "1050px",
|
||||
}}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<ButtonBase
|
||||
className="cancel"
|
||||
key={index}
|
||||
onClick={() => handleVariantClick(index)}
|
||||
data-cy="variant-button"
|
||||
data-checked={selectedVariants.includes(index)}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "flex-end",
|
||||
borderRadius: "8px",
|
||||
overflow: "hidden",
|
||||
border: "1px solid",
|
||||
borderColor: selectedVariants.includes(index)
|
||||
? theme.palette.primary.main
|
||||
: "#E3E3E3",
|
||||
maxWidth: "300px",
|
||||
minHeight: "250px",
|
||||
maxHeight: "340px",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText ? (
|
||||
<img
|
||||
src={variant.extendedText}
|
||||
alt="question variant"
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "block",
|
||||
objectFit: "scale-down",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography p={2}>Картинка отсутствует</Typography>
|
||||
)}
|
||||
<Divider sx={{ width: "100%" }} />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems:
|
||||
variant.answer.length <= 60 ? "center" : "flex-start",
|
||||
justifyContent: "space-between",
|
||||
overflow: "auto",
|
||||
height: "80px",
|
||||
gap: 2,
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ wordBreak: "break-word" }}>
|
||||
{variant.answer}
|
||||
</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
import { useLayoutEffect, useState } from "react";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
|
||||
import { CustomSlider } from "@ui_kit/CustomSlider";
|
||||
|
||||
import type { QuizQuestionNumber } from "model/questionTypes/number";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { updateAnswer } from "@root/quizView";
|
||||
import { modes } from "@utils/themes/Publication/themePublication";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionNumber;
|
||||
}
|
||||
|
||||
export default function Number({ question }: Props) {
|
||||
const [sliderValues, setSliderValues] = useState<number | number[]>(0);
|
||||
const theme = useTheme();
|
||||
const mode = modes;
|
||||
const quiz = useCurrentQuiz();
|
||||
const start = question.content.start;
|
||||
const min = parseInt(question.content.range.split("—")[0]);
|
||||
const max = parseInt(question.content.range.split("—")[1]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (question.content.chooseRange) {
|
||||
setSliderValues([start, start + (max - start) / 2]);
|
||||
} else {
|
||||
setSliderValues(start);
|
||||
}
|
||||
}, [max, question.content.chooseRange, start]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" data-cy="question-title">
|
||||
{question.title}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
px: 2,
|
||||
}}
|
||||
>
|
||||
<CustomSlider
|
||||
className="cancel"
|
||||
value={sliderValues}
|
||||
onChange={(e, value) => {
|
||||
setSliderValues(value);
|
||||
}}
|
||||
min={min}
|
||||
max={max}
|
||||
defaultValue={start}
|
||||
valueLabelDisplay={"on"}
|
||||
step={question.content.step}
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
"& .MuiSlider-valueLabel": {
|
||||
background: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{!question.content.chooseRange && (
|
||||
<Box sx={{ mt: "30px", maxWidth: "80px" }}>
|
||||
<CustomTextField
|
||||
placeholder="0"
|
||||
value={sliderValues}
|
||||
sx={{
|
||||
borderColor: theme.palette.text.primary,
|
||||
"& .MuiInputBase-input": {
|
||||
textAlign: "center",
|
||||
backgroundColor: mode[quiz?.config.theme || "StandardTheme"]
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{question.content.chooseRange && (
|
||||
<Box
|
||||
sx={{
|
||||
mt: "30px",
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
alignItems: "center",
|
||||
"& .MuiFormControl-root": { width: "auto" },
|
||||
}}
|
||||
>
|
||||
<CustomTextField
|
||||
className="cancel"
|
||||
placeholder="0"
|
||||
value={sliderValues[0]}
|
||||
sx={{
|
||||
maxWidth: "80px",
|
||||
borderColor: theme.palette.text.primary,
|
||||
"& .MuiInputBase-input": {
|
||||
textAlign: "center",
|
||||
backgroundColor: mode[quiz?.config.theme || "StandardTheme"]
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography color={theme.palette.text.primary}>до</Typography>
|
||||
<CustomTextField
|
||||
className="cancel"
|
||||
placeholder="0"
|
||||
value={sliderValues[1]}
|
||||
sx={{
|
||||
maxWidth: "80px",
|
||||
borderColor: theme.palette.text.primary,
|
||||
"& .MuiInputBase-input": {
|
||||
textAlign: "center",
|
||||
backgroundColor: mode[quiz?.config.theme || "StandardTheme"]
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import YoutubeEmbedIframe from "@ui_kit/StartPagePreview/YoutubeEmbedIframe";
|
||||
|
||||
import type { QuizQuestionPage } from "model/questionTypes/page";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionPage;
|
||||
}
|
||||
|
||||
export default function Page({ question }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
data-cy="question-title"
|
||||
sx={{ paddingBottom: "25px" }}
|
||||
>
|
||||
{question.title}
|
||||
</Typography>
|
||||
|
||||
<Typography data-cy="question-text" sx={{ paddingBottom: "20px" }}>
|
||||
{question.content.text}
|
||||
</Typography>
|
||||
|
||||
{question.content.useImage ? (
|
||||
<img
|
||||
src={question.content.back}
|
||||
alt=""
|
||||
style={{
|
||||
display: "block",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<YoutubeEmbedIframe
|
||||
containerSX={{ width: "100%", height: "50vh" }}
|
||||
videoUrl={question.content.video}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
|
||||
import FlagIcon from "@icons/questionsPage/FlagIcon";
|
||||
import StarIconMini from "@icons/questionsPage/StarIconMini";
|
||||
import HashtagIcon from "@icons/questionsPage/hashtagIcon";
|
||||
import HeartIcon from "@icons/questionsPage/heartIcon";
|
||||
import LightbulbIcon from "@icons/questionsPage/lightbulbIcon";
|
||||
import LikeIcon from "@icons/questionsPage/likeIcon";
|
||||
import TropfyIcon from "@icons/questionsPage/tropfyIcon";
|
||||
|
||||
import type { QuizQuestionRating } from "model/questionTypes/rating";
|
||||
|
||||
type RatingIconType =
|
||||
| "star"
|
||||
| "trophie"
|
||||
| "flag"
|
||||
| "heart"
|
||||
| "like"
|
||||
| "bubble"
|
||||
| "hashtag";
|
||||
|
||||
const ratingIconComponentByType: Record<
|
||||
RatingIconType,
|
||||
FC<{ color: string }>
|
||||
> = {
|
||||
star: StarIconMini,
|
||||
trophie: TropfyIcon,
|
||||
flag: FlagIcon,
|
||||
heart: HeartIcon,
|
||||
like: LikeIcon,
|
||||
bubble: LightbulbIcon,
|
||||
hashtag: HashtagIcon,
|
||||
};
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionRating;
|
||||
}
|
||||
|
||||
export default function Rating({ question }: Props) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const [selectedRating, setSelectedRating] = useState<number>(0);
|
||||
|
||||
const RatingIconComponent =
|
||||
ratingIconComponentByType[question.content.form as RatingIconType];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
data-cy="question-title"
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{question.title}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
width: "fit-content",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="cancel"
|
||||
data-cy="rating"
|
||||
data-rating={selectedRating}
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "25px",
|
||||
flexWrap: "wrap",
|
||||
}}
|
||||
>
|
||||
{Array.from(
|
||||
{ length: question.content.steps },
|
||||
(_, index) => index,
|
||||
).map((itemNumber) => (
|
||||
<Box
|
||||
key={itemNumber}
|
||||
onClick={() => setSelectedRating(itemNumber + 1)}
|
||||
data-cy="rating-button"
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
transform: "scale(1.5)",
|
||||
":hover": {
|
||||
transform: "scale(1.7)",
|
||||
transition: "0.2s",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<RatingIconComponent
|
||||
color={
|
||||
selectedRating > itemNumber
|
||||
? theme.palette.primary.main
|
||||
: "#9A9AAF"
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Typography>{question.content.ratingNegativeDescription}</Typography>
|
||||
<Typography>{question.content.ratingPositiveDescription}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import ArrowDownIcon from "@icons/ArrowDownIcon";
|
||||
|
||||
import type { QuizQuestionSelect } from "model/questionTypes/select";
|
||||
interface Props {
|
||||
question: QuizQuestionSelect;
|
||||
}
|
||||
|
||||
export default function Text({ question }: Props) {
|
||||
const theme = useTheme();
|
||||
const [selectValue, setSelectValue] = useState<string>("");
|
||||
const arrowDown = <ArrowDownIcon color={"currentColor"} />;
|
||||
function handleChange(event: SelectChangeEvent<string | null>) {
|
||||
setSelectValue((event.target as HTMLInputElement).value);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" data-cy="question-title">
|
||||
{question.title}
|
||||
</Typography>
|
||||
<FormControl
|
||||
className="cancel"
|
||||
fullWidth
|
||||
size="small"
|
||||
sx={{
|
||||
width: "100%",
|
||||
minWidth: "200px",
|
||||
height: "48px",
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
id="category-select"
|
||||
variant="outlined"
|
||||
value={selectValue}
|
||||
placeholder={question.content.default}
|
||||
displayEmpty
|
||||
onChange={handleChange}
|
||||
data-cy="select"
|
||||
sx={{
|
||||
height: "48px",
|
||||
borderRadius: "8px",
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: `1px solid ${theme.palette.primary.main} !important`,
|
||||
},
|
||||
"& .MuiSelect-icon": {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
mt: "8px",
|
||||
p: "4px",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #EEE4FC",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||
},
|
||||
},
|
||||
MenuListProps: {
|
||||
sx: {
|
||||
py: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
maxWidth: "330px",
|
||||
"& .Mui-selected": {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
inputProps={{
|
||||
sx: {
|
||||
color: theme.palette.primary.main,
|
||||
display: "block",
|
||||
px: "9px",
|
||||
gap: "20px",
|
||||
paddingLeft: "10px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
"& + input": !selectValue && {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: "none",
|
||||
transform: "translateY(-50%)",
|
||||
top: "50%",
|
||||
opacity: 1,
|
||||
color: "#333",
|
||||
fontSize: "16px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
paddingLeft: "10px",
|
||||
width: "88%",
|
||||
},
|
||||
},
|
||||
}}
|
||||
IconComponent={ArrowDownTheme}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant) => (
|
||||
<MenuItem
|
||||
key={variant.answer}
|
||||
value={variant.answer}
|
||||
data-cy="select-option"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "20px",
|
||||
p: "4px",
|
||||
borderRadius: "5px",
|
||||
color: "#9A9AAF",
|
||||
whiteSpace: "normal",
|
||||
}}
|
||||
>
|
||||
{variant.answer}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
export function ArrowDownTheme(props: any) {
|
||||
return (
|
||||
<Box
|
||||
{...props}
|
||||
sx={{
|
||||
top: "25% !important",
|
||||
height: "24px",
|
||||
width: "24px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M19.5 9L12 16.5L4.5 9"
|
||||
stroke={"currentColor"}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
|
||||
import type { QuizQuestionText } from "model/questionTypes/text";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionText;
|
||||
}
|
||||
|
||||
export default function Text({ question }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" data-cy="question-title">
|
||||
{question.title}
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
placeholder={question.content.placeholder}
|
||||
className="cancel"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
useRadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
Checkbox,
|
||||
} from "@mui/material";
|
||||
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionVariant } from "model/questionTypes/variant";
|
||||
import CustomRadio from "@ui_kit/CustomRadio";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
import { modes } from "@utils/themes/Publication/themePublication";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import CheckboxIcon from "@icons/Checkbox";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionVariant;
|
||||
}
|
||||
|
||||
export default function Variant({ question }: Props) {
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
const theme = useTheme();
|
||||
const mode = modes;
|
||||
const quiz = useCurrentQuiz();
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue((event.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl className="cancel" fullWidth>
|
||||
<FormLabel
|
||||
id="quiz-question-radio-group"
|
||||
data-cy="question-title"
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
marginBottom: "20px",
|
||||
fontSize: "24px",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{question.title}
|
||||
</FormLabel>
|
||||
<RadioGroup
|
||||
className="cancel"
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
gap: "20px",
|
||||
}}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<FormControlLabel
|
||||
className="cancel"
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
data-cy="variant-answer"
|
||||
labelPlacement="start"
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
border:
|
||||
value == variant.answer
|
||||
? `1px solid ${theme.palette.primary.main}`
|
||||
: "1px solid #9A9AAF",
|
||||
padding: "20px",
|
||||
justifyContent: "space-between",
|
||||
maxWidth: "685px",
|
||||
backgroundColor: mode[quiz?.config.theme || "StandardTheme"]
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
width: "100%",
|
||||
margin: 0,
|
||||
display: "flex",
|
||||
alignItems:
|
||||
variant.answer.length <= 60 ? "center" : "flex-start",
|
||||
position: "relative",
|
||||
height: "80px",
|
||||
"& .MuiFormControlLabel-label": {
|
||||
wordBreak: "break-word",
|
||||
height: variant.answer.length <= 60 ? undefined : "55px",
|
||||
overflow: "auto",
|
||||
paddingLeft: "45px",
|
||||
},
|
||||
}}
|
||||
control={
|
||||
question.content.multi ? (
|
||||
<Checkbox
|
||||
checkedIcon={
|
||||
<CheckboxIcon
|
||||
checked
|
||||
color={theme.palette.primary.main}
|
||||
/>
|
||||
}
|
||||
icon={<CheckboxIcon />}
|
||||
/>
|
||||
) : (
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
}}
|
||||
checkedIcon={
|
||||
<RadioCheck color={theme.palette.primary.main} />
|
||||
}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Typography
|
||||
color={theme.palette.text.primary}
|
||||
sx={{
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{variant.answer}
|
||||
</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
import { useState, ChangeEvent } from "react";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import InfoIcon from "@icons/InfoIcon";
|
||||
|
||||
import type { QuestionVariant } from "model/questionTypes/shared";
|
||||
import type { QuizQuestionVarImg } from "model/questionTypes/varimg";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
import { modes } from "@utils/themes/Publication/themePublication";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionVarImg;
|
||||
widthPreview: number;
|
||||
}
|
||||
|
||||
export default function Varimg({ question, widthPreview }: Props) {
|
||||
const [selectedVariantIndex, setSelectedVariantIndex] = useState<number>(-1);
|
||||
const theme = useTheme();
|
||||
const mode = modes;
|
||||
const quiz = useCurrentQuiz();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setSelectedVariantIndex(
|
||||
question.content.variants.findIndex(
|
||||
(variant) => variant.answer === event.target.value,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const currentVariant: QuestionVariant | undefined =
|
||||
question.content.variants[selectedVariantIndex];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "40px",
|
||||
}}
|
||||
>
|
||||
<FormControl
|
||||
className="cancel"
|
||||
sx={{
|
||||
//maxWidth: "900px",
|
||||
width: "100%",
|
||||
gap: "25px",
|
||||
}}
|
||||
>
|
||||
<FormLabel
|
||||
id="quiz-question-radio-group"
|
||||
data-cy="question-title"
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
{question.title}
|
||||
</FormLabel>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: widthPreview < 650 ? "column-reverse" : undefined,
|
||||
gap: "30px",
|
||||
}}
|
||||
>
|
||||
<RadioGroup
|
||||
className="cancel"
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={currentVariant?.answer ?? ""}
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
gap: "20px",
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{question.content.variants
|
||||
.filter(({ answer }) => answer)
|
||||
.map((variant, index) => (
|
||||
<FormControlLabel
|
||||
key={index}
|
||||
value={variant.answer}
|
||||
data-cy="variant-answer"
|
||||
sx={{
|
||||
margin: 0,
|
||||
borderRadius: "5px",
|
||||
padding: "15px",
|
||||
color: theme.palette.text.primary,
|
||||
border: `1px solid`,
|
||||
borderColor:
|
||||
selectedVariantIndex == index
|
||||
? theme.palette.primary.main
|
||||
: "#E3E3E3",
|
||||
backgroundColor: mode[quiz?.config.theme || "StandardTheme"]
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
display: "flex",
|
||||
alignItems:
|
||||
variant.answer.length <= 60 ? "center" : "flex-start",
|
||||
position: "relative",
|
||||
height: "80px",
|
||||
"& .MuiFormControlLabel-label": {
|
||||
wordBreak: "break-word",
|
||||
height: variant.answer.length <= 60 ? undefined : "60px",
|
||||
overflow: "auto",
|
||||
paddingLeft: "45px",
|
||||
},
|
||||
}}
|
||||
control={
|
||||
<Radio
|
||||
inputProps={{
|
||||
"data-cy": "variant-radio",
|
||||
}}
|
||||
checkedIcon={
|
||||
<RadioCheck color={theme.palette.primary.main} />
|
||||
}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Typography sx={{ wordBreak: "break-word" }}>
|
||||
{variant.answer}
|
||||
</Typography>
|
||||
{variant.hints && (
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #E3E3E3",
|
||||
width: widthPreview < 650 ? "300px" : "400px",
|
||||
height: widthPreview < 650 ? "300px" : "400px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
borderRadius: "12px",
|
||||
marginTop: widthPreview < 650 ? 0 : "50px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{currentVariant?.extendedText ? (
|
||||
<img
|
||||
src={currentVariant.extendedText}
|
||||
data-cy="variant-image"
|
||||
alt="question variant"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "block",
|
||||
flexGrow: 1,
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography p={2}>
|
||||
{selectedVariantIndex === -1
|
||||
? widthPreview < 650
|
||||
? question?.content.replText || "Выберите вариант ниже"
|
||||
: question?.content.replText || "Выберите вариант"
|
||||
: "Картинка отсутствует"}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</FormControl>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -13,7 +13,7 @@ const backendErrorMessage: Record<string, string> = {
|
||||
const unknownErrorMessage = "Что-то пошло не так. Повторите попытку позже";
|
||||
|
||||
export function getMessageFromFetchError(error: any): string | null {
|
||||
if (process.env.NODE_ENV !== "production") console.log(error);
|
||||
if (process.env.NODE_ENV !== "production") console.error(error);
|
||||
|
||||
const message = backendErrorMessage[error.response?.data?.message];
|
||||
if (message) return message;
|
||||
|
4
src/utils/extractOrder.tsx
Normal file
4
src/utils/extractOrder.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
export const extractOrder = (title: string) => {
|
||||
const match = title.match(/\((\d+)\)$/);
|
||||
return match ? parseInt(match[1]) : 0;
|
||||
};
|
@ -36,7 +36,6 @@ export const parseAxiosError = (nativeError: unknown): [string, number?] => {
|
||||
) {
|
||||
SEMessage = serverError?.error.toLowerCase() || "";
|
||||
}
|
||||
console.log(serverError);
|
||||
const translatedMessage = translateMessage[SEMessage || ""]?.toLowerCase();
|
||||
if (translatedMessage !== undefined) SEMessage = translatedMessage;
|
||||
return [SEMessage || "", serverError.statusCode];
|
||||
|
Loading…
Reference in New Issue
Block a user