Merge branch 'dev' into staging
@ -6,26 +6,38 @@ 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;
|
||||
}
|
||||
|
||||
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> {
|
||||
async function makeRequest<TRequest = unknown, TResponse = unknown>(
|
||||
data: MakeRequest,
|
||||
): Promise<TResponse> {
|
||||
try {
|
||||
const response = await KIT.makeRequest<unknown>(data)
|
||||
const response = await KIT.makeRequest<unknown>(data);
|
||||
|
||||
return response as TResponse
|
||||
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") {
|
||||
|
||||
if (
|
||||
error.response?.status === 400 &&
|
||||
error.response?.data?.message === "refreshToken is empty"
|
||||
) {
|
||||
cleanAuthTicketData();
|
||||
clearAuthToken();
|
||||
clearUserData();
|
||||
clearQuizData();
|
||||
redirect("/");
|
||||
}
|
||||
throw e
|
||||
};
|
||||
};
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
export default makeRequest;
|
BIN
src/assets/icons/designs/smallSize/design1.jpg
Normal file
After Width: | Height: | Size: 591 KiB |
BIN
src/assets/icons/designs/smallSize/design10.jpg
Normal file
After Width: | Height: | Size: 433 KiB |
BIN
src/assets/icons/designs/smallSize/design2.jpg
Normal file
After Width: | Height: | Size: 309 KiB |
BIN
src/assets/icons/designs/smallSize/design3.jpg
Normal file
After Width: | Height: | Size: 210 KiB |
BIN
src/assets/icons/designs/smallSize/design4.jpg
Normal file
After Width: | Height: | Size: 281 KiB |
BIN
src/assets/icons/designs/smallSize/design5.jpg
Normal file
After Width: | Height: | Size: 290 KiB |
BIN
src/assets/icons/designs/smallSize/design6.jpg
Normal file
After Width: | Height: | Size: 430 KiB |
BIN
src/assets/icons/designs/smallSize/design7.jpg
Normal file
After Width: | Height: | Size: 352 KiB |
BIN
src/assets/icons/designs/smallSize/design8.jpg
Normal file
After Width: | Height: | Size: 299 KiB |
BIN
src/assets/icons/designs/smallSize/design9.jpg
Normal file
After Width: | Height: | Size: 409 KiB |
@ -154,6 +154,7 @@ export default function Analytics() {
|
||||
onClose={handleClose}
|
||||
onOpen={handleOpen}
|
||||
// defaultValue={now}
|
||||
minDate={moment(quiz?.created_at)}
|
||||
sx={{
|
||||
width: isMobile ? "285px" : "170px",
|
||||
"& .MuiOutlinedInput-root": {
|
||||
@ -199,6 +200,7 @@ export default function Analytics() {
|
||||
onClose={handleCloseEnd}
|
||||
onOpen={handleOpenEnd}
|
||||
// defaultValue={now}
|
||||
minDate={moment(quiz?.created_at)}
|
||||
sx={{
|
||||
width: isMobile ? "285px" : "170px",
|
||||
"& .MuiOutlinedInput-root": {
|
||||
|
@ -112,30 +112,48 @@ const GeneralItemTimeConv = ({
|
||||
numberType,
|
||||
calculateTime = false,
|
||||
conversionValue,
|
||||
day,
|
||||
}: GeneralItemsProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
||||
|
||||
const data = Object.entries(general).sort(
|
||||
([nextValue], [currentValue]) => Number(nextValue) - Number(currentValue),
|
||||
);
|
||||
const days = data.map(([value]) => value);
|
||||
const time = data.map(([_, value]) => value);
|
||||
const data = Object.entries(general)
|
||||
.sort((a, b) => a[0] - b[0]);
|
||||
|
||||
const numberValue = calculateTime
|
||||
? time.reduce((total, value) => total + value, 0) / days.length
|
||||
: conversionValue;
|
||||
const days = [...data].map(e => e[0])
|
||||
|
||||
let buffer = 0
|
||||
|
||||
const time = [...data].map(e => {
|
||||
if (e[1] > 0) {
|
||||
buffer = e[1]
|
||||
}
|
||||
return buffer
|
||||
})
|
||||
|
||||
|
||||
console.log("data", data)
|
||||
console.log("time", time.reduce((a, b) => (Number(a) + Number(b)), 0))
|
||||
console.log("time", getCalculatedTime(time.reduce((a, b) => (Number(a) + Number(b)), 0)))
|
||||
console.log("days", days.length)
|
||||
const numberValue = calculateTime ?
|
||||
(
|
||||
(time.reduce((a, b) => (Number(a) + Number(b)), 0))
|
||||
/
|
||||
(days.length)
|
||||
) || 0
|
||||
:
|
||||
conversionValue
|
||||
|
||||
if (
|
||||
Object.keys(general).length === 0 ||
|
||||
Object.values(general).every((item) => item === 0)
|
||||
Object.values(general).every((x) => x === 0)
|
||||
) {
|
||||
return (
|
||||
<Typography textAlign="center">{`${title} - нет данных`}</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
@ -146,25 +164,24 @@ const GeneralItemTimeConv = ({
|
||||
>
|
||||
<Typography sx={{ margin: "20px 20px 0" }}>{title}</Typography>
|
||||
<Typography sx={{ margin: "10px 20px 0", fontWeight: "bold" }}>
|
||||
{calculateTime
|
||||
? `${getCalculatedTime(numberValue ?? 0)} с`
|
||||
: `${numberValue?.toFixed(2) ?? 0}%`}
|
||||
{calculateTime ? `${getCalculatedTime(numberValue)} с` : `${numberValue.toFixed(2)}%`}
|
||||
</Typography>
|
||||
<LineChart
|
||||
xAxis={[
|
||||
{
|
||||
data: days,
|
||||
valueFormatter: (value) =>
|
||||
moment.unix(Number(value)).format("DD/MM/YYYY HH") + "ч",
|
||||
moment.utc(Number(value) * 1000).format("DD/MM/YYYY"),
|
||||
},
|
||||
]}
|
||||
series={[
|
||||
{
|
||||
data: Object.values(time),
|
||||
valueFormatter: (value) =>
|
||||
calculateTime
|
||||
? getCalculatedTime(value)
|
||||
: String((value * 100).toFixed(2)) + "%",
|
||||
valueFormatter: (value) => {
|
||||
console.log("log", value)
|
||||
return calculateTime ? getCalculatedTime(value) : String((value*100).toFixed(2)) + "%"
|
||||
}
|
||||
,
|
||||
},
|
||||
]}
|
||||
// dataset={Object.entries(general).map(([, v]) => moment.unix(v).format("ss:mm:HH")).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})}
|
||||
@ -179,6 +196,7 @@ const GeneralItemTimeConv = ({
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export const General: FC<GeneralProps> = ({ data, day }) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
|
@ -8,20 +8,19 @@ import {
|
||||
} from "@mui/material";
|
||||
import { updateQuiz } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import type { DesignItem } from "./DesignGroup";
|
||||
import { DesignGroup } from "./DesignGroup";
|
||||
|
||||
import Desgin1 from "@icons/designs/design1.jpg";
|
||||
import Desgin2 from "@icons/designs/design2.jpg";
|
||||
import Desgin3 from "@icons/designs/design3.jpg";
|
||||
import Desgin4 from "@icons/designs/design4.jpg";
|
||||
import Desgin5 from "@icons/designs/design5.jpg";
|
||||
import Desgin6 from "@icons/designs/design6.jpg";
|
||||
import Desgin7 from "@icons/designs/design7.jpg";
|
||||
import Desgin8 from "@icons/designs/design8.jpg";
|
||||
import Desgin9 from "@icons/designs/design9.jpg";
|
||||
import Desgin10 from "@icons/designs/design10.jpg";
|
||||
|
||||
import type { DesignItem } from "./DesignGroup";
|
||||
import Desgin1 from "@icons/designs/smallSize/design1.jpg";
|
||||
import Desgin2 from "@icons/designs/smallSize/design2.jpg";
|
||||
import Desgin3 from "@icons/designs/smallSize/design3.jpg";
|
||||
import Desgin4 from "@icons/designs/smallSize/design4.jpg";
|
||||
import Desgin5 from "@icons/designs/smallSize/design5.jpg";
|
||||
import Desgin6 from "@icons/designs/smallSize/design6.jpg";
|
||||
import Desgin7 from "@icons/designs/smallSize/design7.jpg";
|
||||
import Desgin8 from "@icons/designs/smallSize/design8.jpg";
|
||||
import Desgin9 from "@icons/designs/smallSize/design9.jpg";
|
||||
import Desgin10 from "@icons/designs/smallSize/design10.jpg";
|
||||
|
||||
const LIGHT_THEME_BUTTONS: DesignItem[] = [
|
||||
{
|
||||
|
@ -76,8 +76,10 @@ export default function InstallQuiz() {
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1065));
|
||||
const CopyLink = () => {
|
||||
let one = document.getElementById("inputLinkone").value;
|
||||
let text = document.getElementById("inputLink").value;
|
||||
let one = (document.getElementById("inputLinkone") as HTMLInputElement)
|
||||
?.value;
|
||||
let text = (document.getElementById("inputLink") as HTMLInputElement)
|
||||
?.value;
|
||||
// text.select();
|
||||
navigator.clipboard.writeText(one + text);
|
||||
// document.execCommand("copy");
|
||||
@ -408,7 +410,7 @@ export default function InstallQuiz() {
|
||||
id="outlined-multiline-static"
|
||||
multiline
|
||||
rows={9}
|
||||
value={`<div id="idpena"></div> <script type="module"> import widget from "https://s.hbpn.link/export/pub.js"; widget.create({ selector: "idpena", quizId: ${quiz.qid} }) </script>`}
|
||||
value={`<div id="idpena"></div> <script type="module"> import widget from "https://${isTestServer ? "s." : ""}hbpn.link/export/pub.js"; widget.create({ selector: "idpena", quizId: ${quiz.qid} }) </script>`}
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
maxWidth: "520px",
|
||||
|
@ -140,7 +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
|
||||
return;
|
||||
}
|
||||
//другая ошибка
|
||||
enqueueSnackbar("Произошла ошибка. Попробуйте позже");
|
||||
@ -171,12 +171,12 @@ function TariffPage() {
|
||||
return tariff.privileges[0].privilegeId !== "squizHideBadge";
|
||||
});
|
||||
|
||||
function handleApplyPromocode () {
|
||||
function handleApplyPromocode() {
|
||||
if (!promocodeField) return;
|
||||
|
||||
activatePromocode(promocodeField)
|
||||
.then(async (greetings) => {
|
||||
enqueueSnackbar(greetings)
|
||||
enqueueSnackbar(greetings);
|
||||
|
||||
const discounts = await makeRequest({
|
||||
method: "GET",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { FC, useEffect, useRef, useState } from "react";
|
||||
import React, { ChangeEvent, FC, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
@ -49,7 +49,7 @@ export const SidebarMobile: FC<Iprops> = ({
|
||||
const [inputOpen, setInputOpen] = useState<boolean>(false);
|
||||
const quiz = useCurrentQuiz();
|
||||
const [inputValue, setInputValue] = useState(quiz.name);
|
||||
const ref = useRef(null);
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
const heightSidebar = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
@ -62,15 +62,19 @@ export const SidebarMobile: FC<Iprops> = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (heightSidebar.current) {
|
||||
observer.current.observe(heightSidebar.current);
|
||||
}
|
||||
}, [heightSidebar, observer]);
|
||||
|
||||
const handleClick = (event) => {
|
||||
const handleClick = (event: ChangeEvent<HTMLDivElement>) => {
|
||||
setAnchorEl(anchorEl ? null : event.currentTarget);
|
||||
};
|
||||
|
||||
const clickInput = (event) => {
|
||||
if (ref.current && !ref.current.contains(event.target)) setInputOpen(false);
|
||||
const clickInput = (event: MouseEvent) => {
|
||||
debugger;
|
||||
if (ref.current && !ref.current?.contains(event.target as Node))
|
||||
setInputOpen(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
document.addEventListener("mousedown", clickInput);
|
||||
@ -88,7 +92,7 @@ export const SidebarMobile: FC<Iprops> = ({
|
||||
changePage(index);
|
||||
};
|
||||
const openPopper = Boolean(anchorEl);
|
||||
const id = openPopper ? "simple-popper" : undefined;
|
||||
const id = openPopper ? "simple-popper" : "";
|
||||
return (
|
||||
<Box
|
||||
ref={heightSidebar}
|
||||
|
@ -6,7 +6,7 @@ type SidebarModalProps = {
|
||||
open: boolean;
|
||||
handleClick: () => void;
|
||||
changePage: (step: number) => void;
|
||||
anchorEl: HTMLElement;
|
||||
anchorEl: HTMLElement | null;
|
||||
id: string;
|
||||
};
|
||||
export const SidebarModal = ({
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
import { Quiz } from "@model/quiz/quiz";
|
||||
|
||||
export const checkQuestionHint = (
|
||||
questions: AnyTypedQuizQuestion,
|
||||
questions: AnyTypedQuizQuestion[],
|
||||
quiz: Quiz,
|
||||
): Record<string, WhyCantCreatePublic> => {
|
||||
const problems: any = {};
|
||||
@ -77,12 +77,17 @@ export const checkQuestionHint = (
|
||||
(condition: QuestionBranchingRuleMain) => {
|
||||
buffer.forEach((oldCondition: QuestionBranchingRuleMain) => {
|
||||
if (areRulesEqual(condition.rules, oldCondition.rules)) {
|
||||
const q = getQuestionByContentId(condition.next);
|
||||
const oldq = getQuestionByContentId(oldCondition.next);
|
||||
const currentQuestion = getQuestionByContentId(condition.next);
|
||||
const oldQuestions = getQuestionByContentId(oldCondition.next);
|
||||
|
||||
if (!currentQuestion?.type || !oldQuestions?.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
pushProblem(
|
||||
question.content.id,
|
||||
`У вопроса "${q?.title || "noname №" + q?.page}" и "${
|
||||
oldq?.title || "noname №" + oldq?.page
|
||||
`У вопроса "${currentQuestion.title || "noname №" + currentQuestion.page}" и "${
|
||||
oldQuestions.title || "noname №" + oldQuestions.page
|
||||
}" одинаковые условия ветвления`,
|
||||
question.title,
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import {
|
||||
clearRuleForAll,
|
||||
createResult,
|
||||
@ -10,6 +9,13 @@ import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateRootContentId } from "@root/quizes/actions";
|
||||
import { getCurrentQuiz } from "@root/quizes/hooks";
|
||||
|
||||
import type {
|
||||
AnyTypedQuizQuestion,
|
||||
QuestionBranchingRule,
|
||||
QuestionBranchingRuleMain,
|
||||
} from "@model/questionTypes/shared";
|
||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||
|
||||
//Всё здесь нужно сделать последовательно. И пусть весь мир ждёт.
|
||||
|
||||
export const DeleteFunction = async (questionId: string) => {
|
||||
@ -33,7 +39,9 @@ export const DeleteFunction = async (questionId: string) => {
|
||||
const parentQuestion = getQuestionByContentId(
|
||||
question.content.rule.parentId,
|
||||
);
|
||||
let startCountParentChildren = parentQuestion.content.rule.children;
|
||||
let startCountParentChildren = parentQuestion?.type
|
||||
? parentQuestion.content.rule.children
|
||||
: null;
|
||||
|
||||
//записываем потомков , а их результаты удаляем
|
||||
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
|
||||
@ -67,8 +75,10 @@ export const DeleteFunction = async (questionId: string) => {
|
||||
}),
|
||||
);
|
||||
|
||||
//чистим rule родителя
|
||||
const newRule = {};
|
||||
if (!parentQuestion?.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentChildren = [...parentQuestion.content.rule.children];
|
||||
|
||||
if (parentChildren.includes(question.content.id))
|
||||
@ -77,15 +87,21 @@ export const DeleteFunction = async (questionId: string) => {
|
||||
1,
|
||||
);
|
||||
|
||||
newRule.main = parentQuestion.content.rule.main.filter(
|
||||
(data) => data.next !== question.content.id,
|
||||
const main = parentQuestion.content.rule.main.filter(
|
||||
(data: QuestionBranchingRuleMain) => data.next !== question.content.id,
|
||||
); //удаляем условия перехода от родителя к этому вопросу
|
||||
newRule.parentId = parentQuestion.content.rule.parentId;
|
||||
newRule.default =
|
||||
const defaultValue =
|
||||
parentQuestion.content.rule.parentId === question.content.id
|
||||
? ""
|
||||
: parentQuestion.content.rule.parentId;
|
||||
newRule.children = parentChildren;
|
||||
|
||||
//чистим rule родителя
|
||||
const newRule: QuestionBranchingRule = {
|
||||
main,
|
||||
default: defaultValue,
|
||||
children: parentChildren,
|
||||
parentId: parentQuestion.content.rule.parentId,
|
||||
};
|
||||
|
||||
await updateQuestion(question.content.rule.parentId, (PQ) => {
|
||||
PQ.content.rule = newRule;
|
||||
@ -101,10 +117,13 @@ export const DeleteFunction = async (questionId: string) => {
|
||||
//сделать результ родителя видимым если у него не осталось потомков
|
||||
|
||||
if (startCountParentChildren.length === 1) {
|
||||
if (parentResult) {
|
||||
await updateQuestion(parentResult.content.id, (q) => {
|
||||
q.content.usage = true;
|
||||
});
|
||||
if (parentResult?.type) {
|
||||
await updateQuestion<QuizQuestionResult>(
|
||||
parentResult.content.id,
|
||||
(item) => {
|
||||
item.content.usage = true;
|
||||
},
|
||||
);
|
||||
} else {
|
||||
//почему-то не существует результа у родителя. Создаём. Новосозданные результы видны сразу
|
||||
await createResult(quiz.backendId, parentQuestion.content.id);
|
||||
|